summaryrefslogtreecommitdiffstats
path: root/servers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:54:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:54:12 +0000
commitb527294153be3b79563c82c66102adc0004736c0 (patch)
tree9b423a224848441885190b5ea7cf0feb23510c9d /servers
parentInitial commit. (diff)
downloadopenldap-b527294153be3b79563c82c66102adc0004736c0.tar.xz
openldap-b527294153be3b79563c82c66102adc0004736c0.zip
Adding upstream version 2.6.7+dfsg.upstream/2.6.7+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers')
-rw-r--r--servers/Makefile.in17
-rw-r--r--servers/lloadd/Makefile.in50
-rw-r--r--servers/lloadd/Makefile_module.in28
-rw-r--r--servers/lloadd/Makefile_server.in79
-rw-r--r--servers/lloadd/backend.c765
-rw-r--r--servers/lloadd/bind.c996
-rw-r--r--servers/lloadd/client.c805
-rw-r--r--servers/lloadd/config.c4032
-rw-r--r--servers/lloadd/connection.c644
-rw-r--r--servers/lloadd/daemon.c1978
-rw-r--r--servers/lloadd/epoch.c321
-rw-r--r--servers/lloadd/epoch.h148
-rw-r--r--servers/lloadd/extended.c220
-rw-r--r--servers/lloadd/init.c246
-rw-r--r--servers/lloadd/libevent_support.c169
-rw-r--r--servers/lloadd/lload-config.h39
-rw-r--r--servers/lloadd/lload.h601
-rw-r--r--servers/lloadd/lloadd.service13
-rw-r--r--servers/lloadd/main.c955
-rw-r--r--servers/lloadd/module_init.c190
-rw-r--r--servers/lloadd/monitor.c1368
-rw-r--r--servers/lloadd/operation.c725
-rw-r--r--servers/lloadd/proto-lload.h254
-rw-r--r--servers/lloadd/tier.c168
-rw-r--r--servers/lloadd/tier_bestof.c328
-rw-r--r--servers/lloadd/tier_roundrobin.c136
-rw-r--r--servers/lloadd/tier_weighted.c224
-rw-r--r--servers/lloadd/upstream.c1184
-rw-r--r--servers/lloadd/value.c67
-rw-r--r--servers/slapd/Makefile.in467
-rw-r--r--servers/slapd/abandon.c139
-rw-r--r--servers/slapd/aci.c1836
-rw-r--r--servers/slapd/acl.c2687
-rw-r--r--servers/slapd/aclparse.c2783
-rw-r--r--servers/slapd/ad.c1313
-rw-r--r--servers/slapd/add.c693
-rw-r--r--servers/slapd/at.c1123
-rw-r--r--servers/slapd/attr.c722
-rw-r--r--servers/slapd/ava.c149
-rw-r--r--servers/slapd/back-asyncmeta/Makefile.in50
-rw-r--r--servers/slapd/back-asyncmeta/add.c370
-rw-r--r--servers/slapd/back-asyncmeta/back-asyncmeta.h788
-rw-r--r--servers/slapd/back-asyncmeta/bind.c1739
-rw-r--r--servers/slapd/back-asyncmeta/candidates.c239
-rw-r--r--servers/slapd/back-asyncmeta/compare.c312
-rw-r--r--servers/slapd/back-asyncmeta/config.c2536
-rw-r--r--servers/slapd/back-asyncmeta/conn.c1222
-rw-r--r--servers/slapd/back-asyncmeta/delete.c304
-rw-r--r--servers/slapd/back-asyncmeta/dncache.c228
-rw-r--r--servers/slapd/back-asyncmeta/init.c475
-rw-r--r--servers/slapd/back-asyncmeta/map.c224
-rw-r--r--servers/slapd/back-asyncmeta/message_queue.c236
-rw-r--r--servers/slapd/back-asyncmeta/meta_result.c1825
-rw-r--r--servers/slapd/back-asyncmeta/modify.c360
-rw-r--r--servers/slapd/back-asyncmeta/modrdn.c375
-rw-r--r--servers/slapd/back-asyncmeta/proto-asyncmeta.h53
-rw-r--r--servers/slapd/back-asyncmeta/search.c970
-rw-r--r--servers/slapd/back-dnssrv/Makefile.in46
-rw-r--r--servers/slapd/back-dnssrv/bind.c79
-rw-r--r--servers/slapd/back-dnssrv/compare.c46
-rw-r--r--servers/slapd/back-dnssrv/config.c54
-rw-r--r--servers/slapd/back-dnssrv/init.c115
-rw-r--r--servers/slapd/back-dnssrv/proto-dnssrv.h46
-rw-r--r--servers/slapd/back-dnssrv/referral.c129
-rw-r--r--servers/slapd/back-dnssrv/search.c239
-rw-r--r--servers/slapd/back-ldap/Makefile.in45
-rw-r--r--servers/slapd/back-ldap/TODO.proxy101
-rw-r--r--servers/slapd/back-ldap/add.c139
-rw-r--r--servers/slapd/back-ldap/back-ldap.h479
-rw-r--r--servers/slapd/back-ldap/bind.c3230
-rw-r--r--servers/slapd/back-ldap/chain.c2356
-rw-r--r--servers/slapd/back-ldap/compare.c88
-rw-r--r--servers/slapd/back-ldap/config.c2214
-rw-r--r--servers/slapd/back-ldap/delete.c85
-rw-r--r--servers/slapd/back-ldap/distproc.c998
-rw-r--r--servers/slapd/back-ldap/extended.c386
-rw-r--r--servers/slapd/back-ldap/init.c374
-rw-r--r--servers/slapd/back-ldap/modify.c136
-rw-r--r--servers/slapd/back-ldap/modrdn.c123
-rw-r--r--servers/slapd/back-ldap/monitor.c1074
-rw-r--r--servers/slapd/back-ldap/pbind.c173
-rw-r--r--servers/slapd/back-ldap/proto-ldap.h124
-rw-r--r--servers/slapd/back-ldap/search.c1042
-rw-r--r--servers/slapd/back-ldap/unbind.c78
-rw-r--r--servers/slapd/back-ldif/Makefile.in41
-rw-r--r--servers/slapd/back-ldif/ldif.c2159
-rw-r--r--servers/slapd/back-mdb/Makefile.in62
-rw-r--r--servers/slapd/back-mdb/add.c419
-rw-r--r--servers/slapd/back-mdb/attr.c828
-rw-r--r--servers/slapd/back-mdb/back-mdb.h209
-rw-r--r--servers/slapd/back-mdb/bind.c156
-rw-r--r--servers/slapd/back-mdb/compare.c142
-rw-r--r--servers/slapd/back-mdb/config.c1004
-rw-r--r--servers/slapd/back-mdb/delete.c436
-rw-r--r--servers/slapd/back-mdb/dn2entry.c79
-rw-r--r--servers/slapd/back-mdb/dn2id.c981
-rw-r--r--servers/slapd/back-mdb/extended.c54
-rw-r--r--servers/slapd/back-mdb/filterindex.c1173
-rw-r--r--servers/slapd/back-mdb/id2entry.c1160
-rw-r--r--servers/slapd/back-mdb/idl.c1293
-rw-r--r--servers/slapd/back-mdb/idl.h118
-rw-r--r--servers/slapd/back-mdb/index.c577
-rw-r--r--servers/slapd/back-mdb/init.c530
-rw-r--r--servers/slapd/back-mdb/key.c72
-rw-r--r--servers/slapd/back-mdb/modify.c843
-rw-r--r--servers/slapd/back-mdb/modrdn.c612
-rw-r--r--servers/slapd/back-mdb/monitor.c807
-rw-r--r--servers/slapd/back-mdb/nextid.c53
-rw-r--r--servers/slapd/back-mdb/operational.c121
-rw-r--r--servers/slapd/back-mdb/proto-mdb.h413
-rw-r--r--servers/slapd/back-mdb/referral.c151
-rw-r--r--servers/slapd/back-mdb/search.c1541
-rw-r--r--servers/slapd/back-mdb/tools.c1741
-rw-r--r--servers/slapd/back-meta/Makefile.in45
-rw-r--r--servers/slapd/back-meta/add.c211
-rw-r--r--servers/slapd/back-meta/back-meta.h690
-rw-r--r--servers/slapd/back-meta/bind.c1758
-rw-r--r--servers/slapd/back-meta/candidates.c282
-rw-r--r--servers/slapd/back-meta/compare.c154
-rw-r--r--servers/slapd/back-meta/config.c3300
-rw-r--r--servers/slapd/back-meta/conn.c1893
-rw-r--r--servers/slapd/back-meta/delete.c103
-rw-r--r--servers/slapd/back-meta/dncache.c235
-rw-r--r--servers/slapd/back-meta/init.c473
-rw-r--r--servers/slapd/back-meta/map.c924
-rw-r--r--servers/slapd/back-meta/modify.c210
-rw-r--r--servers/slapd/back-meta/modrdn.c177
-rw-r--r--servers/slapd/back-meta/proto-meta.h54
-rw-r--r--servers/slapd/back-meta/search.c2431
-rw-r--r--servers/slapd/back-meta/suffixmassage.c110
-rw-r--r--servers/slapd/back-meta/unbind.c89
-rw-r--r--servers/slapd/back-monitor/Makefile.in49
-rw-r--r--servers/slapd/back-monitor/README243
-rw-r--r--servers/slapd/back-monitor/back-monitor.h334
-rw-r--r--servers/slapd/back-monitor/backend.c152
-rw-r--r--servers/slapd/back-monitor/bind.c48
-rw-r--r--servers/slapd/back-monitor/cache.c486
-rw-r--r--servers/slapd/back-monitor/compare.c76
-rw-r--r--servers/slapd/back-monitor/conn.c526
-rw-r--r--servers/slapd/back-monitor/database.c1000
-rw-r--r--servers/slapd/back-monitor/entry.c236
-rw-r--r--servers/slapd/back-monitor/init.c2567
-rw-r--r--servers/slapd/back-monitor/listener.c131
-rw-r--r--servers/slapd/back-monitor/log.c468
-rw-r--r--servers/slapd/back-monitor/modify.c90
-rw-r--r--servers/slapd/back-monitor/operation.c237
-rw-r--r--servers/slapd/back-monitor/operational.c72
-rw-r--r--servers/slapd/back-monitor/overlay.c133
-rw-r--r--servers/slapd/back-monitor/proto-back-monitor.h343
-rw-r--r--servers/slapd/back-monitor/rww.c225
-rw-r--r--servers/slapd/back-monitor/search.c275
-rw-r--r--servers/slapd/back-monitor/sent.c234
-rw-r--r--servers/slapd/back-monitor/thread.c344
-rw-r--r--servers/slapd/back-monitor/time.c234
-rw-r--r--servers/slapd/back-null/Makefile.in41
-rw-r--r--servers/slapd/back-null/README1
-rw-r--r--servers/slapd/back-null/null.c472
-rw-r--r--servers/slapd/back-passwd/Makefile.in41
-rw-r--r--servers/slapd/back-passwd/back-passwd.h31
-rw-r--r--servers/slapd/back-passwd/config.c73
-rw-r--r--servers/slapd/back-passwd/init.c122
-rw-r--r--servers/slapd/back-passwd/proto-passwd.h33
-rw-r--r--servers/slapd/back-passwd/search.c381
-rw-r--r--servers/slapd/back-perl/Makefile.in46
-rw-r--r--servers/slapd/back-perl/README24
-rw-r--r--servers/slapd/back-perl/SampleLDAP.pm171
-rw-r--r--servers/slapd/back-perl/add.c62
-rw-r--r--servers/slapd/back-perl/asperl_undefs.h38
-rw-r--r--servers/slapd/back-perl/bind.c80
-rw-r--r--servers/slapd/back-perl/close.c59
-rw-r--r--servers/slapd/back-perl/compare.c80
-rw-r--r--servers/slapd/back-perl/config.c256
-rw-r--r--servers/slapd/back-perl/delete.c59
-rw-r--r--servers/slapd/back-perl/init.c176
-rw-r--r--servers/slapd/back-perl/modify.c97
-rw-r--r--servers/slapd/back-perl/modrdn.c63
-rw-r--r--servers/slapd/back-perl/perl_back.h82
-rw-r--r--servers/slapd/back-perl/proto-perl.h43
-rw-r--r--servers/slapd/back-perl/search.c122
-rw-r--r--servers/slapd/back-relay/Makefile.in41
-rw-r--r--servers/slapd/back-relay/README83
-rw-r--r--servers/slapd/back-relay/back-relay.h49
-rw-r--r--servers/slapd/back-relay/init.c255
-rw-r--r--servers/slapd/back-relay/op.c333
-rw-r--r--servers/slapd/back-relay/proto-back-relay.h52
-rw-r--r--servers/slapd/back-sock/Makefile.in47
-rw-r--r--servers/slapd/back-sock/add.c69
-rw-r--r--servers/slapd/back-sock/back-sock.h61
-rw-r--r--servers/slapd/back-sock/bind.c80
-rw-r--r--servers/slapd/back-sock/compare.c88
-rw-r--r--servers/slapd/back-sock/config.c452
-rw-r--r--servers/slapd/back-sock/delete.c75
-rw-r--r--servers/slapd/back-sock/extended.c76
-rw-r--r--servers/slapd/back-sock/init.c97
-rw-r--r--servers/slapd/back-sock/modify.c117
-rw-r--r--servers/slapd/back-sock/modrdn.c81
-rw-r--r--servers/slapd/back-sock/opensock.c71
-rw-r--r--servers/slapd/back-sock/proto-sock.h49
-rw-r--r--servers/slapd/back-sock/result.c168
-rw-r--r--servers/slapd/back-sock/search.c74
-rw-r--r--servers/slapd/back-sock/searchexample.conf23
-rw-r--r--servers/slapd/back-sock/searchexample.pl90
-rw-r--r--servers/slapd/back-sock/unbind.c57
-rw-r--r--servers/slapd/back-sql/Makefile.in45
-rw-r--r--servers/slapd/back-sql/add.c1560
-rw-r--r--servers/slapd/back-sql/api.c211
-rw-r--r--servers/slapd/back-sql/back-sql.h631
-rw-r--r--servers/slapd/back-sql/bind.c115
-rw-r--r--servers/slapd/back-sql/compare.c194
-rw-r--r--servers/slapd/back-sql/config.c778
-rw-r--r--servers/slapd/back-sql/delete.c627
-rw-r--r--servers/slapd/back-sql/docs/bugs16
-rw-r--r--servers/slapd/back-sql/docs/concept1
-rw-r--r--servers/slapd/back-sql/docs/install86
-rw-r--r--servers/slapd/back-sql/docs/platforms8
-rw-r--r--servers/slapd/back-sql/docs/todo12
-rw-r--r--servers/slapd/back-sql/entry-id.c1092
-rw-r--r--servers/slapd/back-sql/init.c661
-rw-r--r--servers/slapd/back-sql/modify.c212
-rw-r--r--servers/slapd/back-sql/modrdn.c510
-rw-r--r--servers/slapd/back-sql/operational.c246
-rw-r--r--servers/slapd/back-sql/proto-sql.h313
-rw-r--r--servers/slapd/back-sql/rdbms_depend/README189
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql59
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf36
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql75
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql18
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql123
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql100
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql14
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf30
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql74
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql24
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql39
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql198
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql58
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql7
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf32
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql86
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql21
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql125
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql90
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql8
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf32
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql68
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql27
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql25
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql252
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql50
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf35
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql55
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql21
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql13
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql146
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql66
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql9
-rwxr-xr-xservers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile48
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp387
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf31
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql36
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql16
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql108
-rwxr-xr-xservers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql42
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql20
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql122
-rw-r--r--servers/slapd/back-sql/schema-map.c1012
-rw-r--r--servers/slapd/back-sql/search.c2874
-rw-r--r--servers/slapd/back-sql/sql-wrap.c529
-rw-r--r--servers/slapd/back-sql/util.c572
-rw-r--r--servers/slapd/back-wt/Makefile.in54
-rw-r--r--servers/slapd/back-wt/add.c373
-rw-r--r--servers/slapd/back-wt/attr.c425
-rw-r--r--servers/slapd/back-wt/back-wt.h120
-rw-r--r--servers/slapd/back-wt/bind.c150
-rw-r--r--servers/slapd/back-wt/cache.c231
-rw-r--r--servers/slapd/back-wt/compare.c154
-rw-r--r--servers/slapd/back-wt/config.c209
-rw-r--r--servers/slapd/back-wt/ctx.c117
-rw-r--r--servers/slapd/back-wt/delete.c419
-rw-r--r--servers/slapd/back-wt/dn2entry.c176
-rw-r--r--servers/slapd/back-wt/dn2id.c453
-rw-r--r--servers/slapd/back-wt/extended.c58
-rw-r--r--servers/slapd/back-wt/filterindex.c718
-rw-r--r--servers/slapd/back-wt/id2entry.c352
-rw-r--r--servers/slapd/back-wt/idl.c789
-rw-r--r--servers/slapd/back-wt/idl.h80
-rw-r--r--servers/slapd/back-wt/index.c423
-rw-r--r--servers/slapd/back-wt/init.c385
-rw-r--r--servers/slapd/back-wt/key.c162
-rw-r--r--servers/slapd/back-wt/modify.c714
-rw-r--r--servers/slapd/back-wt/modrdn.c552
-rw-r--r--servers/slapd/back-wt/nextid.c88
-rw-r--r--servers/slapd/back-wt/operational.c110
-rw-r--r--servers/slapd/back-wt/proto-wt.h268
-rw-r--r--servers/slapd/back-wt/search.c759
-rw-r--r--servers/slapd/back-wt/tools.c721
-rw-r--r--servers/slapd/backend.c2056
-rw-r--r--servers/slapd/backglue.c1603
-rw-r--r--servers/slapd/backover.c1484
-rw-r--r--servers/slapd/bconfig.c8140
-rw-r--r--servers/slapd/bind.c555
-rw-r--r--servers/slapd/cancel.c157
-rw-r--r--servers/slapd/ch_malloc.c142
-rw-r--r--servers/slapd/compare.c413
-rw-r--r--servers/slapd/component.c1393
-rw-r--r--servers/slapd/component.h76
-rw-r--r--servers/slapd/config.c2368
-rw-r--r--servers/slapd/connection.c2119
-rw-r--r--servers/slapd/controls.c2229
-rw-r--r--servers/slapd/cr.c501
-rw-r--r--servers/slapd/ctxcsn.c218
-rw-r--r--servers/slapd/daemon.c3610
-rw-r--r--servers/slapd/delete.c244
-rw-r--r--servers/slapd/dn.c1333
-rw-r--r--servers/slapd/entry.c1025
-rw-r--r--servers/slapd/extended.c463
-rw-r--r--servers/slapd/filter.c1445
-rw-r--r--servers/slapd/filterentry.c986
-rw-r--r--servers/slapd/frontend.c174
-rw-r--r--servers/slapd/globals.c38
-rw-r--r--servers/slapd/index.c91
-rw-r--r--servers/slapd/init.c330
-rw-r--r--servers/slapd/ldapsync.c525
-rw-r--r--servers/slapd/limits.c1355
-rw-r--r--servers/slapd/lock.c83
-rw-r--r--servers/slapd/logging.c836
-rw-r--r--servers/slapd/main.c989
-rw-r--r--servers/slapd/matchedValues.c348
-rw-r--r--servers/slapd/modify.c1099
-rw-r--r--servers/slapd/modrdn.c550
-rw-r--r--servers/slapd/mods.c487
-rw-r--r--servers/slapd/module.c367
-rw-r--r--servers/slapd/mr.c549
-rw-r--r--servers/slapd/mra.c231
-rw-r--r--servers/slapd/nt_svc.c110
-rw-r--r--servers/slapd/oc.c940
-rw-r--r--servers/slapd/oidm.c217
-rw-r--r--servers/slapd/operation.c252
-rw-r--r--servers/slapd/operational.c90
-rw-r--r--servers/slapd/overlays/Makefile.in172
-rw-r--r--servers/slapd/overlays/README5
-rw-r--r--servers/slapd/overlays/accesslog.c2874
-rw-r--r--servers/slapd/overlays/auditlog.c242
-rw-r--r--servers/slapd/overlays/autoca.c1121
-rw-r--r--servers/slapd/overlays/collect.c440
-rw-r--r--servers/slapd/overlays/constraint.c1236
-rw-r--r--servers/slapd/overlays/dds.c2056
-rw-r--r--servers/slapd/overlays/deref.c586
-rw-r--r--servers/slapd/overlays/dyngroup.c234
-rw-r--r--servers/slapd/overlays/dynlist.c2968
-rw-r--r--servers/slapd/overlays/homedir.c2074
-rw-r--r--servers/slapd/overlays/memberof.c2185
-rw-r--r--servers/slapd/overlays/otp.c1004
-rw-r--r--servers/slapd/overlays/overlays.c44
-rw-r--r--servers/slapd/overlays/pcache.c5815
-rw-r--r--servers/slapd/overlays/ppolicy.c3490
-rw-r--r--servers/slapd/overlays/refint.c1086
-rw-r--r--servers/slapd/overlays/remoteauth.c1002
-rw-r--r--servers/slapd/overlays/retcode.c1577
-rw-r--r--servers/slapd/overlays/rwm.c2768
-rw-r--r--servers/slapd/overlays/rwm.h183
-rw-r--r--servers/slapd/overlays/rwmconf.c413
-rw-r--r--servers/slapd/overlays/rwmdn.c215
-rw-r--r--servers/slapd/overlays/rwmmap.c1347
-rw-r--r--servers/slapd/overlays/seqmod.c207
-rw-r--r--servers/slapd/overlays/slapover.txt158
-rw-r--r--servers/slapd/overlays/sssvlv.c1439
-rw-r--r--servers/slapd/overlays/syncprov.c4412
-rw-r--r--servers/slapd/overlays/translucent.c1497
-rw-r--r--servers/slapd/overlays/unique.c1549
-rw-r--r--servers/slapd/overlays/valsort.c585
-rw-r--r--servers/slapd/passwd.c659
-rw-r--r--servers/slapd/phonetic.c459
-rw-r--r--servers/slapd/proto-slap.h2269
-rw-r--r--servers/slapd/proxyp.c226
-rw-r--r--servers/slapd/pwmods/Makefile.in59
-rw-r--r--servers/slapd/pwmods/README.argon297
-rw-r--r--servers/slapd/pwmods/argon2.c240
-rw-r--r--servers/slapd/referral.c363
-rw-r--r--servers/slapd/result.c1922
-rw-r--r--servers/slapd/root_dse.c542
-rw-r--r--servers/slapd/sasl.c2050
-rw-r--r--servers/slapd/saslauthz.c2194
-rw-r--r--servers/slapd/schema.c167
-rw-r--r--servers/slapd/schema/README82
-rw-r--r--servers/slapd/schema/collective.ldif48
-rw-r--r--servers/slapd/schema/corba.ldif42
-rw-r--r--servers/slapd/schema/cosine.ldif200
-rw-r--r--servers/slapd/schema/dsee.ldif113
-rw-r--r--servers/slapd/schema/dsee.schema109
-rw-r--r--servers/slapd/schema/duaconf.ldif83
-rw-r--r--servers/slapd/schema/dyngroup.ldif74
-rw-r--r--servers/slapd/schema/dyngroup.schema98
-rw-r--r--servers/slapd/schema/inetorgperson.ldif69
-rw-r--r--servers/slapd/schema/java.ldif59
-rw-r--r--servers/slapd/schema/misc.ldif45
-rw-r--r--servers/slapd/schema/misc.schema75
-rw-r--r--servers/slapd/schema/msuser.ldif4299
-rw-r--r--servers/slapd/schema/msuser.schema4295
-rw-r--r--servers/slapd/schema/namedobject.ldif32
-rw-r--r--servers/slapd/schema/nis.ldif120
-rw-r--r--servers/slapd/schema/nis.schema237
-rw-r--r--servers/slapd/schema/openldap.ldif88
-rw-r--r--servers/slapd/schema/openldap.schema54
-rw-r--r--servers/slapd/schema/pmi.ldif123
-rw-r--r--servers/slapd/schema_check.c938
-rw-r--r--servers/slapd/schema_init.c6980
-rw-r--r--servers/slapd/schema_prep.c1638
-rw-r--r--servers/slapd/schemaparse.c400
-rw-r--r--servers/slapd/search.c439
-rw-r--r--servers/slapd/sets.c832
-rw-r--r--servers/slapd/sets.h75
-rw-r--r--servers/slapd/sl_malloc.c739
-rw-r--r--servers/slapd/slap-cfglog.h30
-rw-r--r--servers/slapd/slap-config.h241
-rw-r--r--servers/slapd/slap.h3389
-rw-r--r--servers/slapd/slapacl.c411
-rw-r--r--servers/slapd/slapadd.c528
-rw-r--r--servers/slapd/slapauth.c177
-rw-r--r--servers/slapd/slapcat.c175
-rw-r--r--servers/slapd/slapcommon.c1214
-rw-r--r--servers/slapd/slapcommon.h125
-rw-r--r--servers/slapd/slapd.conf79
-rw-r--r--servers/slapd/slapd.ldif99
-rw-r--r--servers/slapd/slapd.service15
-rw-r--r--servers/slapd/slapdn.c107
-rw-r--r--servers/slapd/slapi/Makefile.in51
-rw-r--r--servers/slapd/slapi/TODO16
-rw-r--r--servers/slapd/slapi/plugin.c832
-rw-r--r--servers/slapd/slapi/printmsg.c120
-rw-r--r--servers/slapd/slapi/proto-slapi.h92
-rw-r--r--servers/slapd/slapi/slapi.h204
-rw-r--r--servers/slapd/slapi/slapi_dn.c669
-rw-r--r--servers/slapd/slapi/slapi_ext.c349
-rw-r--r--servers/slapd/slapi/slapi_ops.c954
-rw-r--r--servers/slapd/slapi/slapi_overlay.c952
-rw-r--r--servers/slapd/slapi/slapi_pblock.c1447
-rw-r--r--servers/slapd/slapi/slapi_utils.c3479
-rw-r--r--servers/slapd/slapindex.c110
-rw-r--r--servers/slapd/slapmodify.c650
-rw-r--r--servers/slapd/slappasswd.c306
-rw-r--r--servers/slapd/slapschema.c165
-rw-r--r--servers/slapd/slaptest.c121
-rw-r--r--servers/slapd/starttls.c112
-rw-r--r--servers/slapd/str2filter.c84
-rw-r--r--servers/slapd/syncrepl.c7664
-rw-r--r--servers/slapd/syntax.c457
-rw-r--r--servers/slapd/syslog.c289
-rw-r--r--servers/slapd/txn.c409
-rw-r--r--servers/slapd/unbind.c61
-rw-r--r--servers/slapd/user.c171
-rw-r--r--servers/slapd/value.c798
-rw-r--r--servers/slapd/verbs.c276
-rw-r--r--servers/slapd/zn_malloc.c970
462 files changed, 273278 insertions, 0 deletions
diff --git a/servers/Makefile.in b/servers/Makefile.in
new file mode 100644
index 0000000..31f57e7
--- /dev/null
+++ b/servers/Makefile.in
@@ -0,0 +1,17 @@
+# servers Makefile.in for OpenLDAP
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SUBDIRS= slapd lloadd
+
diff --git a/servers/lloadd/Makefile.in b/servers/lloadd/Makefile.in
new file mode 100644
index 0000000..a4c1dff
--- /dev/null
+++ b/servers/lloadd/Makefile.in
@@ -0,0 +1,50 @@
+# Makefile.in for Load Balancer
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+
+XSRCS = version.c
+
+
+SRCS = backend.c bind.c config.c connection.c client.c \
+ daemon.c epoch.c extended.c init.c operation.c \
+ tier.c tier_roundrobin.c tier_weighted.c tier_bestof.c \
+ upstream.c libevent_support.c \
+ $(@PLAT@_SRCS)
+
+O = o
+
+OBJS = backend.$O bind.$O config.$O connection.$O client.$O \
+ daemon.$O epoch.$O extended.$O init.$O operation.$O \
+ tier.$O tier_roundrobin.$O tier_weighted.$O tier_bestof.$O \
+ upstream.$O libevent_support.$O
+
+LDAP_INCDIR= ../../include -I$(srcdir) -I$(srcdir)/../slapd
+LDAP_LIBDIR= ../../libraries
+
+
+# $(LTHREAD_LIBS) must be last!
+XLIBS = $(LLOADD_L)
+XXLIBS = $(LLOADD_LIBS) $(SECURITY_LIBS) $(LUTIL_LIBS)
+XXXLIBS = $(LTHREAD_LIBS)
+
+NT_DEPENDS = slapd.exp
+NT_OBJECTS = slapd.exp symdummy.o $(LLOADD_OBJS) version.o
+
+UNIX_DEPENDS = version.o $(LLOADD_L)
+UNIX_OBJECTS = $(OBJS) version.o
+
+LLOADD_DEPENDS = $(@PLAT@_DEPENDS)
+LLOADD_OBJECTS = $(@PLAT@_OBJECTS)
+
diff --git a/servers/lloadd/Makefile_module.in b/servers/lloadd/Makefile_module.in
new file mode 100644
index 0000000..8ebb5d6
--- /dev/null
+++ b/servers/lloadd/Makefile_module.in
@@ -0,0 +1,28 @@
+# Makefile.in for Load Balancer module
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+O = lo
+
+SRCS += module_init.c monitor.c
+
+OBJS += module_init.lo monitor.lo
+
+BUILD_OPT = "--enable-balancer=mod"
+BUILD_MOD = @BUILD_BALANCER@
+
+LIBBASE=lloadd
+
+LINK_LIBS=$(LLOADD_LIBS)
+MOD_DEFS = -DSLAPD_IMPORT -DBALANCER_MODULE
diff --git a/servers/lloadd/Makefile_server.in b/servers/lloadd/Makefile_server.in
new file mode 100644
index 0000000..86d99d2
--- /dev/null
+++ b/servers/lloadd/Makefile_server.in
@@ -0,0 +1,79 @@
+# Makefile.in for standalone Load Balancer
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+PROGRAMS = lloadd
+XPROGRAMS = slloadd
+
+NT_SRCS = ../slapd/nt_svc.c
+NT_OBJS = ../slapd/nt_svc.o ../../libraries/liblutil/slapdmsg.res
+
+SRCS += main.c value.c \
+ ../slapd/ch_malloc.c ../slapd/logging.c ../slapd/proxyp.c \
+ ../slapd/sl_malloc.c ../slapd/user.c ../slapd/verbs.c
+
+OBJS += main.o value.o \
+ ../slapd/ch_malloc.o ../slapd/logging.o ../slapd/proxyp.o \
+ ../slapd/sl_malloc.o ../slapd/user.o ../slapd/verbs.o \
+ $(@PLAT@_OBJS)
+
+BUILD_OPT = "--enable-balancer"
+BUILD_SRV = @BUILD_BALANCER@
+
+all-local-srv: $(PROGRAMS) all-cffiles
+
+XXLIBS += $(SYSTEMD_LIBS)
+
+lloadd: $(LLOADD_DEPENDS) version.o
+ $(LTLINK) -o $@ $(OBJS) version.o $(LIBS)
+
+slloadd: version.o
+ $(LTLINK) -static -o $@ $(OBJS) version.o $(LIBS)
+
+version.c: Makefile
+ @-$(RM) $@
+ $(MKVERSION) -s -n Versionstr lloadd > $@
+
+version.o: version.c $(OBJS) $(LLOADD_L)
+
+all-cffiles:
+ @if test -n "$(systemdsystemunitdir)"; then \
+ $(SED) -e "s;%LIBEXECDIR%;$(libexecdir);" \
+ $(srcdir)/lloadd.service > lloadd.service.tmp ; \
+ fi
+ touch all-cffiles
+
+clean-local-srv: FORCE
+ $(RM) *.tmp all-cffiles
+
+install-local-srv: install-lloadd install-conf
+
+install-lloadd: FORCE
+ -$(MKDIR) $(DESTDIR)$(libexecdir)
+ @-$(INSTALL) -m 700 -d $(DESTDIR)$(localstatedir)/openldap-lloadd
+ @( \
+ for prg in $(PROGRAMS); do \
+ $(LTINSTALL) $(INSTALLFLAGS) $(STRIP_OPTS) -m 755 $$prg$(EXEEXT) \
+ $(DESTDIR)$(libexecdir); \
+ done \
+ )
+
+install-conf: FORCE
+ @-$(MKDIR) $(DESTDIR)$(sysconfdir)
+ if test -n "$(systemdsystemunitdir)" && test ! -f $(DESTDIR)$(systemdsystemunitdir)/lloadd.service; then \
+ $(MKDIR) $(DESTDIR)$(systemdsystemunitdir); \
+ echo "installing lloadd.service in $(systemdsystemunitdir)"; \
+ echo "$(INSTALL) $(INSTALLFLAGS) -m 644 lloadd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/lloadd.service"; \
+ $(INSTALL) $(INSTALLFLAGS) -m 644 lloadd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/lloadd.service; \
+ fi
diff --git a/servers/lloadd/backend.c b/servers/lloadd/backend.c
new file mode 100644
index 0000000..d30284e
--- /dev/null
+++ b/servers/lloadd/backend.c
@@ -0,0 +1,765 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <event2/event.h>
+#include <event2/dns.h>
+
+#include "lutil.h"
+#include "lload.h"
+
+static void
+upstream_connect_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadPendingConnection *conn = arg;
+ LloadBackend *b = conn->backend;
+ int error = 0, rc = -1;
+ epoch_t epoch;
+
+ checked_lock( &b->b_mutex );
+ Debug( LDAP_DEBUG_CONNS, "upstream_connect_cb: "
+ "fd=%d connection callback for backend uri='%s'\n",
+ s, b->b_uri.bv_val );
+
+ if ( s != conn->fd ) {
+ /* backend_reset has been here first */
+ goto preempted;
+ }
+
+ epoch = epoch_join();
+
+ if ( what == EV_WRITE ) {
+ socklen_t optlen = sizeof(error);
+
+ if ( getsockopt( conn->fd, SOL_SOCKET, SO_ERROR, (void *)&error,
+ &optlen ) < 0 ) {
+ goto done;
+ }
+ if ( error == EINTR || error == EINPROGRESS || error == EWOULDBLOCK ) {
+ checked_unlock( &b->b_mutex );
+ epoch_leave( epoch );
+ return;
+ } else if ( error ) {
+ goto done;
+ } else if ( upstream_init( s, conn->backend ) == NULL ) {
+ goto done;
+ }
+ rc = LDAP_SUCCESS;
+ }
+
+done:
+ epoch_leave( epoch );
+
+ LDAP_LIST_REMOVE( conn, next );
+ if ( rc ) {
+ evutil_closesocket( conn->fd );
+ b->b_opening--;
+ b->b_failed++;
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_connect_cb: "
+ "fd=%d connection timed out\n",
+ s );
+ } else {
+ char ebuf[128];
+ Debug( LDAP_DEBUG_ANY, "upstream_connect_cb: "
+ "fd=%d connection set up failed%s%s\n",
+ s, error ? ": " : "",
+ error ? sock_errstr( error, ebuf, sizeof(ebuf) ) : "" );
+ }
+ backend_retry( b );
+ }
+preempted:
+ checked_unlock( &b->b_mutex );
+
+ event_free( conn->event );
+ ch_free( conn );
+}
+
+static void
+upstream_name_cb( int result, struct evutil_addrinfo *res, void *arg )
+{
+ LloadBackend *b = arg;
+ ber_socket_t s = AC_SOCKET_INVALID;
+ epoch_t epoch;
+ int rc;
+
+ if ( result == EVUTIL_EAI_CANCEL ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_name_cb: "
+ "cancelled\n" );
+ return;
+ }
+
+ checked_lock( &b->b_mutex );
+ /* We were already running when backend_reset tried to cancel us, but were
+ * already stuck waiting for the mutex, nothing to do and b_opening has
+ * been decremented as well */
+ if ( b->b_dns_req == NULL ) {
+ checked_unlock( &b->b_mutex );
+ return;
+ }
+ b->b_dns_req = NULL;
+
+ epoch = epoch_join();
+ if ( result || !res ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_name_cb: "
+ "name resolution failed for backend '%s': %s\n",
+ b->b_uri.bv_val, evutil_gai_strerror( result ) );
+ goto fail;
+ }
+
+ /* TODO: if we get failures, try the other addrinfos */
+ if ( (s = socket( res->ai_family, SOCK_STREAM, 0 )) ==
+ AC_SOCKET_INVALID ) {
+ goto fail;
+ }
+
+ if ( ber_pvt_socket_set_nonblock( s, 1 ) ) {
+ goto fail;
+ }
+
+#if defined(SO_KEEPALIVE) || defined(TCP_NODELAY)
+ if ( b->b_proto == LDAP_PROTO_TCP ) {
+ int dummy = 1;
+#ifdef SO_KEEPALIVE
+ if ( setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char *)&dummy,
+ sizeof(dummy) ) == AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, SO_KEEPALIVE) failed (ignored).\n",
+ s );
+ }
+ if ( bindconf.sb_keepalive.sk_idle > 0 ) {
+#ifdef TCP_KEEPIDLE
+ if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPIDLE,
+ (void *)&bindconf.sb_keepalive.sk_idle,
+ sizeof(bindconf.sb_keepalive.sk_idle) ) ==
+ AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, TCP_KEEPIDLE) failed (ignored).\n",
+ s );
+ }
+#else
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "sockopt TCP_KEEPIDLE not supported on this system.\n" );
+#endif /* TCP_KEEPIDLE */
+ }
+ if ( bindconf.sb_keepalive.sk_probes > 0 ) {
+#ifdef TCP_KEEPCNT
+ if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPCNT,
+ (void *)&bindconf.sb_keepalive.sk_probes,
+ sizeof(bindconf.sb_keepalive.sk_probes) ) ==
+ AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, TCP_KEEPCNT) failed (ignored).\n",
+ s );
+ }
+#else
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "sockopt TCP_KEEPCNT not supported on this system.\n" );
+#endif /* TCP_KEEPCNT */
+ }
+ if ( bindconf.sb_keepalive.sk_interval > 0 ) {
+#ifdef TCP_KEEPINTVL
+ if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPINTVL,
+ (void *)&bindconf.sb_keepalive.sk_interval,
+ sizeof(bindconf.sb_keepalive.sk_interval) ) ==
+ AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, TCP_KEEPINTVL) failed (ignored).\n",
+ s );
+ }
+#else
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "sockopt TCP_KEEPINTVL not supported on this system.\n" );
+#endif /* TCP_KEEPINTVL */
+ }
+#endif /* SO_KEEPALIVE */
+ if ( bindconf.sb_tcp_user_timeout > 0 ) {
+#ifdef TCP_USER_TIMEOUT
+ if ( setsockopt( s, IPPROTO_TCP, TCP_USER_TIMEOUT,
+ (void *)&bindconf.sb_tcp_user_timeout,
+ sizeof(bindconf.sb_tcp_user_timeout) ) ==
+ AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, TCP_USER_TIMEOUT) failed (ignored).\n",
+ s );
+ }
+#else
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "sockopt TCP_USER_TIMEOUT not supported on this "
+ "system.\n" );
+#endif /* TCP_USER_TIMEOUT */
+ }
+#ifdef TCP_NODELAY
+ if ( setsockopt( s, IPPROTO_TCP, TCP_NODELAY, (char *)&dummy,
+ sizeof(dummy) ) == AC_SOCKET_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: "
+ "setsockopt(%d, TCP_NODELAY) failed (ignored).\n",
+ s );
+ }
+#endif /* TCP_NODELAY */
+ }
+#endif /* SO_KEEPALIVE || TCP_NODELAY */
+
+ if ( res->ai_family == PF_INET ) {
+ struct sockaddr_in *ai = (struct sockaddr_in *)res->ai_addr;
+ ai->sin_port = htons( b->b_port );
+ rc = connect( s, (struct sockaddr *)ai, res->ai_addrlen );
+ } else {
+ struct sockaddr_in6 *ai = (struct sockaddr_in6 *)res->ai_addr;
+ ai->sin6_port = htons( b->b_port );
+ rc = connect( s, (struct sockaddr *)ai, res->ai_addrlen );
+ }
+ /* Asynchronous connect */
+ if ( rc ) {
+ LloadPendingConnection *conn;
+
+ if ( errno != EINPROGRESS && errno != EWOULDBLOCK ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_name_cb: "
+ "failed to connect to server '%s'\n",
+ b->b_uri.bv_val );
+ evutil_closesocket( s );
+ goto fail;
+ }
+
+ conn = ch_calloc( 1, sizeof(LloadPendingConnection) );
+ LDAP_LIST_ENTRY_INIT( conn, next );
+ conn->backend = b;
+ conn->fd = s;
+
+ conn->event = event_new( lload_get_base( s ), s, EV_WRITE|EV_PERSIST,
+ upstream_connect_cb, conn );
+ if ( !conn->event ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_name_cb: "
+ "failed to acquire an event to finish upstream "
+ "connection setup.\n" );
+ ch_free( conn );
+ evutil_closesocket( s );
+ goto fail;
+ }
+
+ event_add( conn->event, lload_timeout_net );
+ LDAP_LIST_INSERT_HEAD( &b->b_connecting, conn, next );
+ Debug( LDAP_DEBUG_CONNS, "upstream_name_cb: "
+ "connection to backend uri=%s in progress\n",
+ b->b_uri.bv_val );
+ } else if ( upstream_init( s, b ) == NULL ) {
+ goto fail;
+ }
+
+ checked_unlock( &b->b_mutex );
+ evutil_freeaddrinfo( res );
+ epoch_leave( epoch );
+ return;
+
+fail:
+ if ( s != AC_SOCKET_INVALID ) {
+ evutil_closesocket( s );
+ }
+ b->b_opening--;
+ b->b_failed++;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ if ( res ) {
+ evutil_freeaddrinfo( res );
+ }
+ epoch_leave( epoch );
+}
+
+int
+try_upstream(
+ LloadBackend *b,
+ lload_c_head *head,
+ LloadOperation *op,
+ LloadConnection *c,
+ int *res,
+ char **message )
+{
+ assert_locked( &b->b_mutex );
+
+ checked_lock( &c->c_io_mutex );
+ CONNECTION_LOCK(c);
+ if ( c->c_state == LLOAD_C_READY && !c->c_pendingber &&
+ ( b->b_max_conn_pending == 0 ||
+ c->c_n_ops_executing < b->b_max_conn_pending ) ) {
+ Debug( LDAP_DEBUG_CONNS, "try_upstream: "
+ "selected connection connid=%lu for client "
+ "connid=%lu msgid=%d\n",
+ c->c_connid, op->o_client_connid, op->o_client_msgid );
+
+ /* c_state is DYING if we're about to be unlinked */
+ assert( IS_ALIVE( c, c_live ) );
+
+ if ( head ) {
+ /*
+ * Round-robin step:
+ * Rotate the queue to put this connection at the end.
+ */
+ LDAP_CIRCLEQ_MAKE_TAIL( head, c, c_next );
+ }
+
+ b->b_n_ops_executing++;
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ b->b_counters[LLOAD_STATS_OPS_BIND].lc_ops_received++;
+ } else {
+ b->b_counters[LLOAD_STATS_OPS_OTHER].lc_ops_received++;
+ }
+ c->c_n_ops_executing++;
+ c->c_counters.lc_ops_received++;
+
+ *res = LDAP_SUCCESS;
+ CONNECTION_ASSERT_LOCKED(c);
+ assert_locked( &c->c_io_mutex );
+ return 1;
+ }
+ CONNECTION_UNLOCK(c);
+ checked_unlock( &c->c_io_mutex );
+ return 0;
+}
+
+int
+backend_select(
+ LloadBackend *b,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ lload_c_head *head;
+ LloadConnection *c;
+
+ assert_locked( &b->b_mutex );
+ if ( b->b_max_pending && b->b_n_ops_executing >= b->b_max_pending ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_select: "
+ "backend %s too busy\n",
+ b->b_uri.bv_val );
+ *res = LDAP_BUSY;
+ *message = "server busy";
+ return 1;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ && !(lload_features & LLOAD_FEATURE_VC)
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ ) {
+ head = &b->b_bindconns;
+ } else {
+ head = &b->b_conns;
+ }
+
+ if ( LDAP_CIRCLEQ_EMPTY( head ) ) {
+ return 0;
+ }
+
+ *res = LDAP_BUSY;
+ *message = "server busy";
+
+ LDAP_CIRCLEQ_FOREACH( c, head, c_next ) {
+ if ( try_upstream( b, head, op, c, res, message ) ) {
+ *cp = c;
+ CONNECTION_ASSERT_LOCKED(c);
+ assert_locked( &c->c_io_mutex );
+ return 1;
+ }
+ }
+
+ return 1;
+}
+
+int
+upstream_select(
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadTier *tier;
+ int finished = 0;
+
+ LDAP_STAILQ_FOREACH( tier, &tiers, t_next ) {
+ if ( (finished = tier->t_type.tier_select(
+ tier, op, cp, res, message )) ) {
+ break;
+ }
+ }
+
+ return finished;
+}
+
+/*
+ * Will schedule a connection attempt if there is a need for it. Need exclusive
+ * access to backend, its b_mutex is not touched here, though.
+ */
+void
+backend_retry( LloadBackend *b )
+{
+ int requested;
+
+ if ( slapd_shutdown ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_retry: "
+ "shutting down\n" );
+ return;
+ }
+ assert_locked( &b->b_mutex );
+
+ requested = b->b_numconns;
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ if ( !(lload_features & LLOAD_FEATURE_VC) )
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ {
+ requested += b->b_numbindconns;
+ }
+
+ if ( b->b_active + b->b_bindavail + b->b_opening >= requested ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_retry: "
+ "no more connections needed for this backend\n" );
+ assert_locked( &b->b_mutex );
+ return;
+ }
+
+ if ( b->b_opening > 0 ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_retry: "
+ "retry in progress already\n" );
+ assert( b->b_opening == 1 );
+ assert_locked( &b->b_mutex );
+ return;
+ }
+
+ /* We incremented b_opening when we activated the event, so it can't be
+ * pending */
+ assert( !event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) );
+ b->b_opening++;
+
+ if ( b->b_failed > 0 ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_retry: "
+ "scheduling a retry in %d ms\n",
+ b->b_retry_timeout );
+ event_add( b->b_retry_event, &b->b_retry_tv );
+ assert_locked( &b->b_mutex );
+ return;
+ }
+
+ Debug( LDAP_DEBUG_CONNS, "backend_retry: "
+ "scheduling re-connection straight away\n" );
+
+ if ( ldap_pvt_thread_pool_submit2(
+ &connection_pool, backend_connect_task, b, &b->b_cookie ) ) {
+ Debug( LDAP_DEBUG_ANY, "backend_retry: "
+ "failed to submit retry task, scheduling a retry instead\n" );
+ /* The current implementation of ldap_pvt_thread_pool_submit2 can fail
+ * and still set (an invalid) cookie */
+ b->b_cookie = NULL;
+ b->b_failed++;
+ event_add( b->b_retry_event, &b->b_retry_tv );
+ }
+ assert_locked( &b->b_mutex );
+}
+
+void
+backend_connect( evutil_socket_t s, short what, void *arg )
+{
+ struct evutil_addrinfo hints = {};
+ LloadBackend *b = arg;
+ struct evdns_getaddrinfo_request *request, *placeholder;
+ char *hostname;
+ epoch_t epoch;
+
+ checked_lock( &b->b_mutex );
+ assert( b->b_dns_req == NULL );
+
+ if ( b->b_cookie ) {
+ b->b_cookie = NULL;
+ }
+
+ if ( slapd_shutdown ) {
+ Debug( LDAP_DEBUG_CONNS, "backend_connect: "
+ "doing nothing, shutdown in progress\n" );
+ b->b_opening--;
+ checked_unlock( &b->b_mutex );
+ return;
+ }
+
+ epoch = epoch_join();
+
+ Debug( LDAP_DEBUG_CONNS, "backend_connect: "
+ "%sattempting connection to %s\n",
+ (what & EV_TIMEOUT) ? "retry timeout finished, " : "",
+ b->b_host );
+
+#ifdef LDAP_PF_LOCAL
+ if ( b->b_proto == LDAP_PROTO_IPC ) {
+ struct sockaddr_un addr;
+ ber_socket_t s = socket( PF_LOCAL, SOCK_STREAM, 0 );
+ int rc;
+
+ if ( s == AC_SOCKET_INVALID ) {
+ goto fail;
+ }
+
+ rc = ber_pvt_socket_set_nonblock( s, 1 );
+ if ( rc ) {
+ evutil_closesocket( s );
+ goto fail;
+ }
+
+ if ( strlen( b->b_host ) > ( sizeof(addr.sun_path) - 1 ) ) {
+ evutil_closesocket( s );
+ goto fail;
+ }
+ memset( &addr, '\0', sizeof(addr) );
+ addr.sun_family = AF_LOCAL;
+ strcpy( addr.sun_path, b->b_host );
+
+ rc = connect(
+ s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un) );
+ /* Asynchronous connect */
+ if ( rc ) {
+ LloadPendingConnection *conn;
+
+ if ( errno != EINPROGRESS && errno != EWOULDBLOCK ) {
+ evutil_closesocket( s );
+ goto fail;
+ }
+
+ conn = ch_calloc( 1, sizeof(LloadPendingConnection) );
+ LDAP_LIST_ENTRY_INIT( conn, next );
+ conn->backend = b;
+ conn->fd = s;
+
+ conn->event = event_new( lload_get_base( s ), s,
+ EV_WRITE|EV_PERSIST, upstream_connect_cb, conn );
+ if ( !conn->event ) {
+ Debug( LDAP_DEBUG_ANY, "backend_connect: "
+ "failed to acquire an event to finish upstream "
+ "connection setup.\n" );
+ ch_free( conn );
+ evutil_closesocket( s );
+ goto fail;
+ }
+
+ event_add( conn->event, lload_timeout_net );
+ LDAP_LIST_INSERT_HEAD( &b->b_connecting, conn, next );
+ Debug( LDAP_DEBUG_CONNS, "backend_connect: "
+ "connection to backend uri=%s in progress\n",
+ b->b_uri.bv_val );
+ } else if ( upstream_init( s, b ) == NULL ) {
+ goto fail;
+ }
+
+ checked_unlock( &b->b_mutex );
+ epoch_leave( epoch );
+ return;
+ }
+#endif /* LDAP_PF_LOCAL */
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = EVUTIL_AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ hostname = b->b_host;
+
+ /*
+ * Picking any value on the stack. This is unique to our thread without
+ * having to call ldap_pvt_thread_self.
+ * We might have to revert to using ldap_pvt_thread_self eventually since
+ * this betrays where exactly our stack lies - potentially weakening some
+ * protections like ASLR.
+ */
+ placeholder = (struct evdns_getaddrinfo_request *)&request;
+ b->b_dns_req = placeholder;
+ checked_unlock( &b->b_mutex );
+
+ request = evdns_getaddrinfo(
+ dnsbase, hostname, NULL, &hints, upstream_name_cb, b );
+
+ checked_lock( &b->b_mutex );
+ assert( request || b->b_dns_req != placeholder );
+
+ /* Record the request, unless upstream_name_cb or another thread
+ * cleared it. Another thread is usually backend_reset or backend_connect
+ * if upstream_name_cb finished and scheduled another one */
+ if ( b->b_dns_req == placeholder ) {
+ b->b_dns_req = request;
+ }
+ checked_unlock( &b->b_mutex );
+ epoch_leave( epoch );
+ return;
+
+fail:
+ b->b_opening--;
+ b->b_failed++;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ epoch_leave( epoch );
+}
+
+void *
+backend_connect_task( void *ctx, void *arg )
+{
+ backend_connect( -1, 0, arg );
+ return NULL;
+}
+
+/*
+ * Needs exclusive access to the backend and no other thread is allowed to call
+ * backend_retry while we're handling this.
+ *
+ * If gentle == 0, a full pause must be in effect, else we risk deadlocking on
+ * event_free().
+ */
+void
+backend_reset( LloadBackend *b, int gentle )
+{
+ assert_locked( &b->b_mutex );
+ if ( b->b_cookie ) {
+ if ( ldap_pvt_thread_pool_retract( b->b_cookie ) ) {
+ b->b_cookie = NULL;
+ b->b_opening--;
+ } else {
+ /*
+ * The task might not be cancelable because it just started
+ * executing.
+ *
+ * Shutdown should be the only time when the thread pool is
+ * in that state. Keep the cookie in to keep an eye on whether
+ * it's finished yet.
+ */
+ assert( slapd_shutdown );
+ }
+ }
+ /* Not safe to hold our mutex and call event_del/free if the event's
+ * callback is running, relinquish the mutex while we do so. */
+ if ( b->b_retry_event &&
+ event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) ) {
+ assert( b->b_failed );
+ checked_unlock( &b->b_mutex );
+ event_del( b->b_retry_event );
+ checked_lock( &b->b_mutex );
+ b->b_opening--;
+ }
+ if ( b->b_dns_req ) {
+ evdns_getaddrinfo_cancel( b->b_dns_req );
+ b->b_dns_req = NULL;
+ b->b_opening--;
+ }
+ while ( !LDAP_LIST_EMPTY( &b->b_connecting ) ) {
+ LloadPendingConnection *pending = LDAP_LIST_FIRST( &b->b_connecting );
+
+ Debug( LDAP_DEBUG_CONNS, "backend_reset: "
+ "destroying socket pending connect() fd=%d\n",
+ pending->fd );
+
+ event_active( pending->event, EV_WRITE, 0 );
+ evutil_closesocket( pending->fd );
+ pending->fd = -1;
+ LDAP_LIST_REMOVE( pending, next );
+
+ if ( !gentle ) {
+ /* None of the event bases are running, we're safe to free the
+ * event right now and potentially free the backend itself */
+ event_free( pending->event );
+ ch_free( pending );
+ }
+ /* else, just let the event dispose of the resources on its own later */
+ b->b_opening--;
+ }
+ connections_walk(
+ &b->b_mutex, &b->b_preparing, lload_connection_close, &gentle );
+ assert( LDAP_CIRCLEQ_EMPTY( &b->b_preparing ) );
+ assert( b->b_opening == ( b->b_cookie ? 1 : 0 ) );
+ b->b_failed = 0;
+
+ connections_walk_last( &b->b_mutex, &b->b_bindconns, b->b_last_bindconn,
+ lload_connection_close, &gentle );
+ assert( gentle || b->b_bindavail == 0 );
+
+ connections_walk_last( &b->b_mutex, &b->b_conns, b->b_last_conn,
+ lload_connection_close, &gentle );
+ assert( gentle || b->b_active == 0 );
+ assert_locked( &b->b_mutex );
+}
+
+LloadBackend *
+lload_backend_new( void )
+{
+ LloadBackend *b;
+
+ b = ch_calloc( 1, sizeof(LloadBackend) );
+
+ LDAP_CIRCLEQ_INIT( &b->b_conns );
+ LDAP_CIRCLEQ_INIT( &b->b_bindconns );
+ LDAP_CIRCLEQ_INIT( &b->b_preparing );
+ LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
+
+ b->b_numconns = 1;
+ b->b_numbindconns = 1;
+ b->b_weight = 1;
+
+ b->b_retry_timeout = 5000;
+
+ ldap_pvt_thread_mutex_init( &b->b_mutex );
+
+ return b;
+}
+
+void
+lload_backend_destroy( LloadBackend *b )
+{
+ Debug( LDAP_DEBUG_CONNS, "lload_backend_destroy: "
+ "destroying backend uri='%s', numconns=%d, numbindconns=%d\n",
+ b->b_uri.bv_val, b->b_numconns, b->b_numbindconns );
+
+ checked_lock( &b->b_mutex );
+ b->b_tier->t_type.tier_remove_backend( b->b_tier, b );
+ b->b_numconns = b->b_numbindconns = 0;
+ backend_reset( b, 0 );
+
+#ifdef BALANCER_MODULE
+ if ( b->b_monitor ) {
+ BackendDB *be;
+ struct berval monitordn = BER_BVC("cn=monitor");
+ int rc;
+
+ be = select_backend( &monitordn, 0 );
+
+ /* FIXME: implement proper subsys shutdown in back-monitor or make
+ * backend just an entry, not a subsys */
+ rc = b->b_monitor->mss_destroy( be, b->b_monitor );
+ assert( rc == LDAP_SUCCESS );
+ }
+#endif /* BALANCER_MODULE */
+
+ checked_unlock( &b->b_mutex );
+ ldap_pvt_thread_mutex_destroy( &b->b_mutex );
+
+ if ( b->b_retry_event ) {
+ event_del( b->b_retry_event );
+ event_free( b->b_retry_event );
+ b->b_retry_event = NULL;
+ }
+
+ ch_free( b->b_host );
+ ch_free( b->b_uri.bv_val );
+ ch_free( b->b_name.bv_val );
+ ch_free( b );
+}
diff --git a/servers/lloadd/bind.c b/servers/lloadd/bind.c
new file mode 100644
index 0000000..eaecb24
--- /dev/null
+++ b/servers/lloadd/bind.c
@@ -0,0 +1,996 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lutil.h"
+#include "lload.h"
+
+struct berval mech_external = BER_BVC("EXTERNAL");
+
+int
+bind_mech_external(
+ LloadConnection *client,
+ LloadOperation *op,
+ struct berval *credentials )
+{
+ BerValue binddn;
+ void *ssl;
+ char *ptr, *message = "";
+ int result = LDAP_SUCCESS;
+
+ CONNECTION_ASSERT_LOCKED(client);
+ client->c_state = LLOAD_C_READY;
+ client->c_type = LLOAD_C_OPEN;
+
+ op->o_res = LLOAD_OP_COMPLETED;
+
+ /*
+ * We only support implicit assertion.
+ *
+ * Although RFC 4513 says the credentials field must be missing, RFC 4422
+ * doesn't and libsasl2 will pass a zero-length string to send. We have to
+ * allow that.
+ */
+ if ( !BER_BVISEMPTY( credentials ) ) {
+ result = LDAP_UNWILLING_TO_PERFORM;
+ message = "proxy authorization is not supported";
+ goto done;
+ }
+
+#ifdef HAVE_TLS
+ ssl = ldap_pvt_tls_sb_ctx( client->c_sb );
+ if ( !ssl || ldap_pvt_tls_get_peer_dn( ssl, &binddn, NULL, 0 ) ) {
+ result = LDAP_INVALID_CREDENTIALS;
+ message = "no externally negotiated identity";
+ goto done;
+ }
+ client->c_auth.bv_len = binddn.bv_len + STRLENOF("dn:");
+ client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
+
+ ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
+ ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
+ *ptr = '\0';
+
+ ber_memfree( binddn.bv_val );
+
+ if ( !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) {
+ client->c_type = LLOAD_C_PRIVILEGED;
+ }
+#else /* ! HAVE_TLS */
+ result = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ message = "requested SASL mechanism not supported";
+#endif /* ! HAVE_TLS */
+
+done:
+ CONNECTION_UNLOCK(client);
+ operation_send_reject( op, result, message, 1 );
+ return LDAP_SUCCESS;
+}
+
+static int
+client_bind(
+ LloadOperation *op,
+ LloadConnection *upstream,
+ struct berval *binddn,
+ ber_tag_t tag,
+ struct berval *auth )
+{
+ ber_printf( upstream->c_pendingber, "t{titOtO}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_upstream_msgid,
+ LDAP_REQ_BIND, &op->o_request,
+ LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
+
+ return 0;
+}
+
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+static int
+client_bind_as_vc(
+ LloadOperation *op,
+ LloadConnection *upstream,
+ struct berval *binddn,
+ ber_tag_t tag,
+ struct berval *auth )
+{
+ CONNECTION_LOCK(upstream);
+ ber_printf( upstream->c_pendingber, "t{tit{tst{{tOOtOtO}}}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_upstream_msgid,
+ LDAP_REQ_EXTENDED,
+ LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_VERIFY_CREDENTIALS,
+ LDAP_TAG_EXOP_REQ_VALUE,
+ LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE, BER_BV_OPTIONAL( &upstream->c_vc_cookie ),
+ &binddn, tag, &auth,
+ LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
+ CONNECTION_UNLOCK(upstream);
+ return 0;
+}
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+
+/*
+ * The client connection can be in the following states:
+ * 1) there are between zero and many non-bind operations pending
+ * client->c_state == LLOAD_C_READY && client->c_pin_id == 0
+ * 2) there is one bind operation pending (waiting on an upstream response)
+ * a) It is a simple bind
+ * b) It is a SASL bind
+ * 3) there is one SASL bind in progress (received a LDAP_SASL_BIND_IN_PROGRESS
+ * response)
+ *
+ * In cases 2 and 3, client->c_state == LLOAD_C_BINDING, a SASL bind is in
+ * progress/pending if c_sasl_bind_mech is set.
+ *
+ * In the first case, client_reset abandons all operations on the respective
+ * upstreams, case 2a has client_reset send an anonymous bind to upstream to
+ * terminate the bind. In cases 2b and 3, c_pin_id is set and we retrieve the
+ * op. The rest is the same for both.
+ *
+ * If c_pin_id is unset, we request an upstream connection assigned, otherwise,
+ * we try to reuse the pinned upstream. In the case of no upstream, we reject
+ * the request. A SASL bind request means we acquire a new pin_id if we don't
+ * have one already.
+ *
+ * We have to reset c_auth (which holds the current or pending identity) and
+ * make sure we set it up eventually:
+ * - In the case of a simple bind, we already know the final identity being
+ * requested so we set it up immediately
+ * - In SASL binds, for mechanisms we implement ourselves (EXTERNAL), we set it
+ * up at some point
+ * - Otherwise, we have to ask the upstream what it thinks as the bind
+ * succeeds, we send an LDAP "Who Am I?" exop, this is one of the few
+ * requests we send on our own. If we implement the mechanism, we provide the
+ * identity (EXTERNAL uses the client certificate DN)
+ *
+ * At the end of the request processing, if nothing goes wrong, we're in state
+ * 2b (with c_pin_id set to the op's o_pin_id), or state 2a (we could reset
+ * c_pin_id/o_pin_id if we wanted but we don't always do that at the moment).
+ * If something does go wrong, we're either tearing down the client or we
+ * reject the request and switch to state 1 (clearing c_pin_id).
+ *
+ * As usual, we have to make any changes to the target connection before we've
+ * sent the PDU over it - while we are in charge of the read side and nothing
+ * happens there without our ceding control, the other read side could wake up
+ * at any time and preempt us.
+ *
+ * On a response (in handle_bind_response):
+ * - to a simple bind, clear c_auth on a failure otherwise keep it while we
+ * just reset the client to state 1
+ * - failure response to a SASL bind - reset client to state 1
+ * - LDAP_SASL_BIND_IN_PROGRESS - clear o_*_msgid from the op (have to
+ * remove+reinsert it from the respective c_ops!), we need it since it is the
+ * vessel maintaining the pin between client and upstream
+ * - all of the above forward the response immediately
+ * - LDAP_SUCCESS for a SASL bind - we send a "Who Am I?" request to retrieve
+ * the client's DN, only on receiving the response do we finalise the
+ * exchange by forwarding the successful bind response
+ *
+ * We can't do the same for VC Exop since the exchange is finished at the end
+ * and we need a change to the VC Exop spec to have the server (optionally?)
+ * respond with the final authzid (saving us a roundtrip as well).
+ */
+int
+request_bind( LloadConnection *client, LloadOperation *op )
+{
+ LloadConnection *upstream = NULL;
+ BerElement *ber, *copy;
+ struct berval binddn, auth, mech = BER_BVNULL;
+ ber_int_t version;
+ ber_tag_t tag;
+ unsigned long pin;
+ int res = LDAP_UNAVAILABLE, rc = LDAP_SUCCESS;
+ char *message = "no connections available";
+ enum op_restriction client_restricted;
+
+ CONNECTION_LOCK(client);
+ pin = client->c_pin_id;
+
+ if ( pin ) {
+ LloadOperation *pinned_op, needle = {
+ .o_client_connid = client->c_connid,
+ .o_client_msgid = 0,
+ .o_pin_id = client->c_pin_id,
+ };
+
+ Debug( LDAP_DEBUG_CONNS, "request_bind: "
+ "client connid=%lu is pinned pin=%lu\n",
+ client->c_connid, pin );
+
+ pinned_op =
+ ldap_tavl_delete( &client->c_ops, &needle, operation_client_cmp );
+ if ( pinned_op ) {
+ assert( op->o_tag == pinned_op->o_tag );
+
+ pinned_op->o_client_msgid = op->o_client_msgid;
+
+ /* Preserve the new BerElement and its pointers, reclaim the old
+ * one in operation_destroy_from_client if it's still there */
+ needle.o_ber = pinned_op->o_ber;
+ pinned_op->o_ber = op->o_ber;
+ op->o_ber = needle.o_ber;
+
+ pinned_op->o_request = op->o_request;
+ pinned_op->o_ctrls = op->o_ctrls;
+
+ /* No one has seen this operation yet, plant the pin back in its stead */
+ client->c_n_ops_executing--;
+ op->o_res = LLOAD_OP_COMPLETED;
+ ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
+ op->o_client = NULL;
+ assert( op->o_upstream == NULL );
+
+ rc = ldap_tavl_insert( &client->c_ops, pinned_op, operation_client_cmp,
+ ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ /* No one has seen this operation yet */
+ op->o_refcnt--;
+ operation_destroy( op );
+
+ /* We didn't start a new operation, just continuing an existing one */
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received--;
+
+ op = pinned_op;
+ }
+ }
+
+ ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
+ client->c_n_ops_executing--;
+
+ client_reset( client );
+
+ client->c_state = LLOAD_C_BINDING;
+ client->c_type = LLOAD_C_OPEN;
+
+ if ( (copy = ber_alloc()) == NULL ) {
+ goto fail;
+ }
+ ber_init2( copy, &op->o_request, 0 );
+
+ tag = ber_get_int( copy, &version );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_PACKETS, "request_bind: "
+ "failed to parse version field\n" );
+ goto fail;
+ } else if ( version != LDAP_VERSION3 ) {
+ CONNECTION_UNLOCK(client);
+ operation_send_reject(
+ op, LDAP_PROTOCOL_ERROR, "LDAP version unsupported", 1 );
+ CONNECTION_LOCK(client);
+ goto fail;
+ }
+
+ tag = ber_get_stringbv( copy, &binddn, LBER_BV_NOTERM );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_PACKETS, "request_bind: "
+ "failed to parse bind name field\n" );
+ goto fail;
+ }
+
+ if ( !BER_BVISNULL( &client->c_auth ) ) {
+ ch_free( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ }
+
+ tag = ber_skip_element( copy, &auth );
+ if ( tag == LDAP_AUTH_SIMPLE ) {
+ if ( !BER_BVISEMPTY( &binddn ) ) {
+ char *ptr;
+ client->c_auth.bv_len = STRLENOF("dn:") + binddn.bv_len;
+ client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 );
+
+ ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" );
+ ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len );
+ *ptr = '\0';
+ }
+
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ } else if ( tag == LDAP_AUTH_SASL ) {
+ ber_init2( copy, &auth, 0 );
+
+ if ( ber_get_stringbv( copy, &mech, LBER_BV_NOTERM ) == LBER_ERROR ) {
+ goto fail;
+ }
+ if ( !ber_bvcmp( &mech, &mech_external ) ) {
+ struct berval credentials = BER_BVNULL;
+
+ ber_get_stringbv( copy, &credentials, LBER_BV_NOTERM );
+ rc = bind_mech_external( client, op, &credentials );
+
+ /* terminate the upstream side if client switched mechanisms */
+ if ( pin ) {
+ operation_abandon( op );
+ }
+
+ ber_free( copy, 0 );
+ return rc;
+ } else if ( BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_dupbv( &client->c_sasl_bind_mech, &mech );
+ } else if ( ber_bvcmp( &mech, &client->c_sasl_bind_mech ) ) {
+ ber_bvreplace( &client->c_sasl_bind_mech, &mech );
+ }
+ } else {
+ goto fail;
+ }
+
+ rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ client->c_n_ops_executing++;
+
+ client_restricted = client->c_restricted;
+ CONNECTION_UNLOCK(client);
+
+ if ( pin ) {
+ checked_lock( &op->o_link_mutex );
+ upstream = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ }
+
+ if ( upstream ) {
+ checked_lock( &upstream->c_io_mutex );
+ CONNECTION_LOCK(upstream);
+ if ( !IS_ALIVE( upstream, c_live ) ) {
+ CONNECTION_UNLOCK(upstream);
+ checked_unlock( &upstream->c_io_mutex );
+ upstream = NULL;
+ }
+ }
+
+ /* If we were pinned but lost the link, don't look for a new upstream, we
+ * have to reject the op and clear pin */
+ if ( upstream ) {
+ /* No need to do anything */
+ } else if ( !pin && client_restricted != LLOAD_OP_RESTRICTED_ISOLATE ) {
+ upstream_select( op, &upstream, &res, &message );
+ } else {
+ Debug( LDAP_DEBUG_STATS, "request_bind: "
+ "connid=%lu, msgid=%d pinned upstream lost\n",
+ op->o_client_connid, op->o_client_msgid );
+ operation_send_reject( op, LDAP_OTHER,
+ "connection to the remote server has been severed", 1 );
+ pin = 0;
+ goto done;
+ }
+
+ if ( !upstream ) {
+ Debug( LDAP_DEBUG_STATS, "request_bind: "
+ "connid=%lu, msgid=%d no available connection found\n",
+ op->o_client_connid, op->o_client_msgid );
+ operation_send_reject( op, res, message, 1 );
+ assert( client->c_pin_id == 0 );
+ goto done;
+ }
+ assert_locked( &upstream->c_io_mutex );
+ /*
+ * At this point, either:
+ * - upstream is READY and pin == 0
+ * - upstream is BINDING, pin != 0 and op->o_upstream_msgid == 0
+ *
+ * A pinned upstream we marked for closing at some point ago should have
+ * closed by now.
+ */
+
+ ber = upstream->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ checked_unlock( &upstream->c_io_mutex );
+ if ( !pin ) {
+ LloadBackend *b = upstream->c_backend;
+
+ upstream->c_n_ops_executing--;
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ operation_update_backend_counters( op, b );
+ checked_unlock( &b->b_mutex );
+ } else {
+ CONNECTION_UNLOCK(upstream);
+ }
+
+ Debug( LDAP_DEBUG_ANY, "request_bind: "
+ "ber_alloc failed\n" );
+
+ OPERATION_UNLINK(op);
+
+ CONNECTION_LOCK(client);
+ goto fail;
+ }
+ upstream->c_pendingber = ber;
+
+ if ( !pin ) {
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_forwarded++;
+ }
+
+ if ( pin ) {
+ ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
+ if ( tag == LDAP_AUTH_SIMPLE ) {
+ pin = op->o_pin_id = 0;
+ }
+ } else if ( tag == LDAP_AUTH_SASL && !op->o_pin_id ) {
+ checked_lock( &lload_pin_mutex );
+ pin = op->o_pin_id = lload_next_pin++;
+ Debug( LDAP_DEBUG_CONNS, "request_bind: "
+ "client connid=%lu allocated pin=%lu linking it to upstream "
+ "connid=%lu\n",
+ op->o_client_connid, pin, upstream->c_connid );
+ checked_unlock( &lload_pin_mutex );
+ }
+
+ op->o_upstream = upstream;
+ op->o_upstream_connid = upstream->c_connid;
+ op->o_upstream_msgid = upstream->c_next_msgid++;
+ op->o_res = LLOAD_OP_FAILED;
+
+ /* Was it unlinked in the meantime? No need to send a response since the
+ * client is dead */
+ if ( !IS_ALIVE( op, o_refcnt ) ) {
+ LloadBackend *b = upstream->c_backend;
+
+ upstream->c_n_ops_executing--;
+ checked_unlock( &upstream->c_io_mutex );
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ checked_unlock( &b->b_mutex );
+
+ assert( !IS_ALIVE( client, c_live ) );
+ checked_lock( &op->o_link_mutex );
+ if ( op->o_upstream ) {
+ op->o_upstream = NULL;
+ }
+ checked_unlock( &op->o_link_mutex );
+ rc = -1;
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &mech ) ) {
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+ } else if ( ber_bvcmp( &upstream->c_sasl_bind_mech, &mech ) ) {
+ ber_bvreplace( &upstream->c_sasl_bind_mech, &mech );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "request_bind: "
+ "added bind from client connid=%lu to upstream connid=%lu "
+ "as msgid=%d\n",
+ op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid );
+ if ( ldap_tavl_insert( &upstream->c_ops, op, operation_upstream_cmp,
+ ldap_avl_dup_error ) ) {
+ assert(0);
+ }
+ upstream->c_state = LLOAD_C_BINDING;
+ CONNECTION_UNLOCK(upstream);
+
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ if ( lload_features & LLOAD_FEATURE_VC ) {
+ rc = client_bind_as_vc( op, upstream, &binddn, tag, &auth );
+ } else
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ {
+ rc = client_bind( op, upstream, &binddn, tag, &auth );
+ }
+ checked_unlock( &upstream->c_io_mutex );
+
+done:
+
+ CONNECTION_LOCK(client);
+ if ( rc == LDAP_SUCCESS ) {
+ client->c_pin_id = pin;
+ CONNECTION_UNLOCK(client);
+
+ if ( upstream ) {
+ connection_write_cb( -1, 0, upstream );
+ }
+ } else {
+fail:
+ rc = -1;
+
+ client->c_pin_id = 0;
+ CONNECTION_DESTROY(client);
+ }
+
+ ber_free( copy, 0 );
+ return rc;
+}
+
+/*
+ * Remember the response, but first ask the server what
+ * authorization identity has been negotiated.
+ *
+ * Also, this request will fail if the server thinks a SASL
+ * confidentiality/integrity layer has been negotiated so we catch
+ * it early and no other clients are affected.
+ */
+int
+finish_sasl_bind(
+ LloadConnection *upstream,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ BerElement *output;
+ LloadOperation *removed;
+ ber_int_t msgid;
+ int rc;
+
+ CONNECTION_ASSERT_LOCKED(upstream);
+ removed = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
+ if ( !removed ) {
+ assert( upstream->c_state != LLOAD_C_BINDING );
+ /* FIXME: has client replaced this bind since? */
+ assert(0);
+ }
+ assert( removed == op && upstream->c_state == LLOAD_C_BINDING );
+
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &upstream->c_io_mutex );
+ output = upstream->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ checked_unlock( &upstream->c_io_mutex );
+ CONNECTION_LOCK_DESTROY(upstream);
+ return -1;
+ }
+ upstream->c_pendingber = output;
+
+ msgid = upstream->c_next_msgid++;
+ ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, msgid,
+ LDAP_REQ_EXTENDED,
+ LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_WHO_AM_I );
+
+ /* Make sure noone flushes the buffer before we re-insert the operation */
+ CONNECTION_LOCK(upstream);
+ checked_unlock( &upstream->c_io_mutex );
+
+ op->o_upstream_msgid = msgid;
+
+ /* remember the response for later */
+ ber_free( op->o_ber, 1 );
+ op->o_ber = ber;
+
+ /* Could we have been unlinked in the meantime? */
+ rc = ldap_tavl_insert(
+ &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ CONNECTION_UNLOCK(upstream);
+
+ Debug( LDAP_DEBUG_TRACE, "finish_sasl_bind: "
+ "SASL exchange in lieu of client connid=%lu to upstream "
+ "connid=%lu finished, resolving final authzid name msgid=%d\n",
+ op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid );
+
+ connection_write_cb( -1, 0, upstream );
+ return LDAP_SUCCESS;
+}
+
+int
+handle_bind_response(
+ LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ LloadConnection *upstream;
+ BerValue response;
+ BerElement *copy;
+ LloadOperation *removed;
+ ber_int_t result;
+ ber_tag_t tag;
+ int rc = LDAP_SUCCESS;
+
+ if ( (copy = ber_alloc()) == NULL ) {
+ rc = -1;
+ goto done;
+ }
+
+ tag = ber_peek_element( ber, &response );
+ assert( tag == LDAP_RES_BIND );
+
+ ber_init2( copy, &response, 0 );
+
+ tag = ber_get_enum( copy, &result );
+ ber_free( copy, 0 );
+
+ if ( tag == LBER_ERROR ) {
+ rc = -1;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "handle_bind_response: "
+ "received response for bind request msgid=%d by client "
+ "connid=%lu, result=%d\n",
+ op->o_client_msgid, op->o_client_connid, result );
+
+ checked_lock( &op->o_link_mutex );
+ upstream = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ if ( !upstream ) {
+ return LDAP_SUCCESS;
+ }
+
+ CONNECTION_LOCK(upstream);
+ if ( !ldap_tavl_find( upstream->c_ops, op, operation_upstream_cmp ) ) {
+ /*
+ * operation might not be found because:
+ * - it has timed out (only happens when debugging/hung/...)
+ * a response has been sent for us, we must not send another
+ * - it has been abandoned (new bind, unbind)
+ * no response is expected
+ * - ???
+ */
+ CONNECTION_UNLOCK(upstream);
+ return LDAP_SUCCESS;
+ }
+
+ /*
+ * We might be marked for closing, forward the response if we can, but do
+ * no more if it's a SASL bind - just finish the operation and send failure
+ * in that case (since we can't resolve the bind identity correctly).
+ */
+ if ( upstream->c_state == LLOAD_C_CLOSING ) {
+ /* FIXME: this is too ad-hoc */
+ if ( ( result == LDAP_SUCCESS ||
+ result == LDAP_SASL_BIND_IN_PROGRESS ) &&
+ !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ CONNECTION_UNLOCK(upstream);
+ operation_send_reject(
+ op, LDAP_OTHER, "upstream connection is closing", 0 );
+
+ ber_free( ber, 1 );
+ return LDAP_SUCCESS;
+ }
+
+ assert( op->o_client_msgid && op->o_upstream_msgid );
+ op->o_pin_id = 0;
+
+ } else if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
+ ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
+ op->o_upstream_msgid = 0;
+ rc = ldap_tavl_insert(
+ &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ } else {
+ int sasl_finished = 0;
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ sasl_finished = 1;
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+
+ assert( op->o_client_msgid && op->o_upstream_msgid );
+ op->o_pin_id = 0;
+
+ if ( (lload_features & LLOAD_FEATURE_PROXYAUTHZ) && sasl_finished &&
+ result == LDAP_SUCCESS ) {
+ return finish_sasl_bind( upstream, op, ber );
+ }
+ op->o_res = LLOAD_OP_COMPLETED;
+ }
+ CONNECTION_UNLOCK(upstream);
+
+ if ( !op->o_pin_id ) {
+ operation_unlink_upstream( op, upstream );
+ }
+
+ CONNECTION_LOCK(client);
+ removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
+ assert( !removed || op == removed );
+
+ if ( client->c_state == LLOAD_C_BINDING ) {
+ assert( removed );
+ switch ( result ) {
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ op->o_saved_msgid = op->o_client_msgid;
+ op->o_client_msgid = 0;
+ rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp,
+ ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ break;
+ case LDAP_SUCCESS:
+ default: {
+ client->c_state = LLOAD_C_READY;
+ client->c_type = LLOAD_C_OPEN;
+ client->c_pin_id = 0;
+ client->c_n_ops_executing--;
+ if ( !BER_BVISNULL( &client->c_auth ) ) {
+ if ( result != LDAP_SUCCESS ) {
+ ber_memfree( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ } else if ( !ber_bvstrcasecmp(
+ &client->c_auth, &lloadd_identity ) ) {
+ client->c_type = LLOAD_C_PRIVILEGED;
+ }
+ }
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ break;
+ }
+ }
+ } else {
+ if ( removed ) {
+ client->c_n_ops_executing--;
+ }
+ assert( client->c_state == LLOAD_C_DYING ||
+ client->c_state == LLOAD_C_CLOSING );
+ }
+ CONNECTION_UNLOCK(client);
+
+done:
+ if ( rc ) {
+ operation_send_reject( op, LDAP_OTHER, "internal error", 1 );
+
+ ber_free( ber, 1 );
+ return LDAP_SUCCESS;
+ }
+ return forward_final_response( client, op, ber );
+}
+
+int
+handle_whoami_response(
+ LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ LloadConnection *upstream;
+ BerValue matched, diagmsg;
+ BerElement *saved_response = op->o_ber;
+ LloadOperation *removed;
+ ber_int_t result;
+ ber_tag_t tag;
+ ber_len_t len;
+
+ Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: "
+ "connid=%ld received whoami response in lieu of connid=%ld\n",
+ op->o_upstream_connid, client->c_connid );
+
+ tag = ber_scanf( ber, "{emm" /* "}" */,
+ &result, &matched, &diagmsg );
+ if ( tag == LBER_ERROR ) {
+ operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 );
+ return -1;
+ }
+
+ checked_lock( &op->o_link_mutex );
+ upstream = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ if ( !upstream ) {
+ return LDAP_SUCCESS;
+ }
+
+ op->o_res = LLOAD_OP_COMPLETED;
+ /* Clear upstream status */
+ operation_unlink_upstream( op, upstream );
+
+ if ( result == LDAP_PROTOCOL_ERROR ) {
+ LloadBackend *b;
+
+ CONNECTION_LOCK(upstream);
+ b = upstream->c_backend;
+ Debug( LDAP_DEBUG_ANY, "handle_whoami_response: "
+ "Who Am I? extended operation not supported on backend %s, "
+ "proxyauthz with clients that do SASL binds will not work "
+ "msg=%s!\n",
+ b->b_uri.bv_val, diagmsg.bv_val );
+ CONNECTION_UNLOCK(upstream);
+ operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 );
+ return -1;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ CONNECTION_LOCK(client);
+
+ assert( client->c_state == LLOAD_C_BINDING ||
+ client->c_state == LLOAD_C_CLOSING );
+
+ assert( BER_BVISNULL( &client->c_auth ) );
+ if ( !BER_BVISNULL( &client->c_auth ) ) {
+ ber_memfree( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ }
+
+ if ( tag == LDAP_TAG_EXOP_RES_VALUE ) {
+ tag = ber_scanf( ber, "o", &client->c_auth );
+ if ( tag == LBER_ERROR ) {
+ CONNECTION_DESTROY(client);
+ return -1;
+ }
+ }
+
+ removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp );
+ assert( !removed || op == removed );
+ op->o_pin_id = 0;
+ if ( removed ) {
+ client->c_n_ops_executing--;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: "
+ "connid=%ld new authid=%s\n",
+ client->c_connid, client->c_auth.bv_val );
+
+ if ( client->c_state == LLOAD_C_BINDING ) {
+ client->c_state = LLOAD_C_READY;
+ client->c_type = LLOAD_C_OPEN;
+ client->c_pin_id = 0;
+ if ( !BER_BVISNULL( &client->c_auth ) &&
+ !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) {
+ client->c_type = LLOAD_C_PRIVILEGED;
+ }
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ }
+
+ CONNECTION_UNLOCK(client);
+
+ /* defer the disposal of ber to operation_destroy */
+ op->o_ber = ber;
+
+ return forward_final_response( client, op, saved_response );
+}
+
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+int
+handle_vc_bind_response(
+ LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ BerElement *output;
+ BerValue matched, diagmsg, creds = BER_BVNULL, controls = BER_BVNULL;
+ ber_int_t result;
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc = 0;
+
+ tag = ber_scanf( ber, "{emm" /* "}" */,
+ &result, &matched, &diagmsg );
+ if ( tag == LBER_ERROR ) {
+ rc = -1;
+ goto done;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( result == LDAP_PROTOCOL_ERROR ) {
+ LloadConnection *upstream;
+
+ checked_lock( &op->o_link_mutex );
+ upstream = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ if ( upstream ) {
+ LloadBackend *b;
+
+ CONNECTION_LOCK(upstream);
+ b = upstream->c_backend;
+ Debug( LDAP_DEBUG_ANY, "handle_vc_bind_response: "
+ "VC extended operation not supported on backend %s\n",
+ b->b_uri.bv_val );
+ CONNECTION_UNLOCK(upstream);
+ }
+ }
+
+ Debug( LDAP_DEBUG_STATS, "handle_vc_bind_response: "
+ "received response for bind request msgid=%d by client "
+ "connid=%lu, result=%d\n",
+ op->o_client_msgid, op->o_client_connid, result );
+
+ CONNECTION_LOCK(client);
+
+ if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE ) {
+ if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
+ ber_memfree( client->c_vc_cookie.bv_val );
+ }
+ tag = ber_scanf( ber, "o", &client->c_vc_cookie );
+ if ( tag == LBER_ERROR ) {
+ rc = -1;
+ CONNECTION_UNLOCK(client);
+ goto done;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_SCREDS ) {
+ tag = ber_scanf( ber, "m", &creds );
+ if ( tag == LBER_ERROR ) {
+ rc = -1;
+ CONNECTION_UNLOCK(client);
+ goto done;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS ) {
+ tag = ber_scanf( ber, "m", &controls );
+ if ( tag == LBER_ERROR ) {
+ rc = -1;
+ CONNECTION_UNLOCK(client);
+ goto done;
+ }
+ }
+
+ if ( client->c_state == LLOAD_C_BINDING ) {
+ switch ( result ) {
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ break;
+ case LDAP_SUCCESS:
+ default: {
+ client->c_state = LLOAD_C_READY;
+ client->c_type = LLOAD_C_OPEN;
+ client->c_pin_id = 0;
+ if ( result != LDAP_SUCCESS ) {
+ ber_memfree( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ } else if ( !ber_bvstrcasecmp(
+ &client->c_auth, &lloadd_identity ) ) {
+ client->c_type = LLOAD_C_PRIVILEGED;
+ }
+ if ( !BER_BVISNULL( &client->c_vc_cookie ) ) {
+ ber_memfree( client->c_vc_cookie.bv_val );
+ BER_BVZERO( &client->c_vc_cookie );
+ }
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ break;
+ }
+ }
+ } else {
+ assert( client->c_state == LLOAD_C_INVALID ||
+ client->c_state == LLOAD_C_CLOSING );
+ }
+ CONNECTION_UNLOCK(client);
+
+ checked_lock( &client->c_io_mutex );
+ output = client->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ rc = -1;
+ checked_unlock( &client->c_io_mutex );
+ goto done;
+ }
+ client->c_pendingber = output;
+
+ rc = ber_printf( output, "t{tit{eOOtO}tO}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_client_msgid, LDAP_RES_BIND,
+ result, &matched, &diagmsg,
+ LDAP_TAG_SASL_RES_CREDS, BER_BV_OPTIONAL( &creds ),
+ LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) );
+
+ checked_unlock( &client->c_io_mutex );
+ if ( rc >= 0 ) {
+ connection_write_cb( -1, 0, client );
+ rc = 0;
+ }
+
+done:
+ OPERATION_UNLINK(op);
+ ber_free( ber, 1 );
+ return rc;
+}
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
diff --git a/servers/lloadd/client.c b/servers/lloadd/client.c
new file mode 100644
index 0000000..1241286
--- /dev/null
+++ b/servers/lloadd/client.c
@@ -0,0 +1,805 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lutil.h"
+#include "lload.h"
+
+long lload_client_max_pending = 0;
+
+lload_c_head clients = LDAP_CIRCLEQ_HEAD_INITIALIZER( clients );
+
+ldap_pvt_thread_mutex_t clients_mutex;
+
+static void client_unlink( LloadConnection *upstream );
+
+int
+request_abandon( LloadConnection *c, LloadOperation *op )
+{
+ LloadOperation *request, needle = { .o_client_connid = c->c_connid };
+ int rc = LDAP_SUCCESS;
+
+ op->o_res = LLOAD_OP_COMPLETED;
+
+ if ( ber_decode_int( &op->o_request, &needle.o_client_msgid ) ) {
+ Debug( LDAP_DEBUG_STATS, "request_abandon: "
+ "connid=%lu msgid=%d invalid integer sent in abandon request\n",
+ c->c_connid, op->o_client_msgid );
+
+ OPERATION_UNLINK(op);
+ CONNECTION_LOCK_DESTROY(c);
+ return -1;
+ }
+
+ CONNECTION_LOCK(c);
+ request = ldap_tavl_find( c->c_ops, &needle, operation_client_cmp );
+ if ( !request ) {
+ Debug( LDAP_DEBUG_STATS, "request_abandon: "
+ "connid=%lu msgid=%d requests abandon of an operation "
+ "msgid=%d not being processed anymore\n",
+ c->c_connid, op->o_client_msgid, needle.o_client_msgid );
+ CONNECTION_UNLOCK(c);
+ goto done;
+ } else if ( request->o_tag == LDAP_REQ_BIND ) {
+ /* RFC 4511 states we must not allow Abandon on Binds */
+ Debug( LDAP_DEBUG_STATS, "request_abandon: "
+ "connid=%lu msgid=%d requests abandon of a bind operation "
+ "msgid=%d\n",
+ c->c_connid, op->o_client_msgid, needle.o_client_msgid );
+ CONNECTION_UNLOCK(c);
+ goto done;
+ }
+ Debug( LDAP_DEBUG_STATS, "request_abandon: "
+ "connid=%lu msgid=%d abandoning %s msgid=%d\n",
+ c->c_connid, op->o_client_msgid,
+ lload_msgtype2str( request->o_tag ), needle.o_client_msgid );
+
+ if ( c->c_state == LLOAD_C_BINDING ) {
+ assert(0);
+ }
+
+ CONNECTION_UNLOCK(c);
+ operation_abandon( request );
+
+done:
+ OPERATION_UNLINK(op);
+ return rc;
+}
+
+int
+request_process( LloadConnection *client, LloadOperation *op )
+{
+ BerElement *output;
+ LloadConnection *upstream = NULL;
+ LloadBackend *b = NULL;
+ ber_int_t msgid;
+ int res = LDAP_UNAVAILABLE, rc = LDAP_SUCCESS;
+ char *message = "no connections available";
+ enum op_restriction client_restricted;
+
+ if ( lload_control_actions && !BER_BVISNULL( &op->o_ctrls ) ) {
+ BerElementBuffer copy_berbuf;
+ BerElement *copy = (BerElement *)&copy_berbuf;
+ struct berval control;
+
+ ber_init2( copy, &op->o_ctrls, 0 );
+
+ while ( ber_skip_element( copy, &control ) == LBER_SEQUENCE ) {
+ struct restriction_entry *entry, needle = {};
+ BerElementBuffer control_berbuf;
+ BerElement *control_ber = (BerElement *)&control_berbuf;
+
+ ber_init2( control_ber, &control, 0 );
+
+ if ( ber_skip_element( control_ber, &needle.oid ) == LBER_ERROR ) {
+ res = LDAP_PROTOCOL_ERROR;
+ message = "invalid control";
+
+ operation_send_reject( op, res, message, 1 );
+ goto fail;
+ }
+
+ entry = ldap_tavl_find(
+ lload_control_actions, &needle, lload_restriction_cmp );
+ if ( entry && op->o_restricted < entry->action ) {
+ op->o_restricted = entry->action;
+ }
+ }
+ }
+ if ( op->o_restricted < LLOAD_OP_RESTRICTED_WRITE &&
+ lload_write_coherence &&
+ op->o_tag != LDAP_REQ_SEARCH &&
+ op->o_tag != LDAP_REQ_COMPARE ) {
+ op->o_restricted = LLOAD_OP_RESTRICTED_WRITE;
+ }
+
+ if ( op->o_restricted == LLOAD_OP_RESTRICTED_REJECT ) {
+ res = LDAP_UNWILLING_TO_PERFORM;
+ message = "extended operation or control disallowed";
+
+ operation_send_reject( op, res, message, 1 );
+ goto fail;
+ }
+
+ CONNECTION_LOCK(client);
+ client_restricted = client->c_restricted;
+ if ( client_restricted ) {
+ if ( client_restricted == LLOAD_OP_RESTRICTED_WRITE &&
+ client->c_restricted_inflight == 0 &&
+ client->c_restricted_at >= 0 &&
+ client->c_restricted_at + lload_write_coherence <
+ op->o_start.tv_sec ) {
+ Debug( LDAP_DEBUG_TRACE, "request_process: "
+ "connid=%lu write coherence to backend '%s' expired\n",
+ client->c_connid, client->c_backend->b_name.bv_val );
+ client->c_backend = NULL;
+ client_restricted = client->c_restricted = LLOAD_OP_NOT_RESTRICTED;
+ }
+ switch ( client_restricted ) {
+ case LLOAD_OP_NOT_RESTRICTED:
+ break;
+ case LLOAD_OP_RESTRICTED_WRITE:
+ case LLOAD_OP_RESTRICTED_BACKEND:
+ b = client->c_backend;
+ assert( b );
+ break;
+ case LLOAD_OP_RESTRICTED_UPSTREAM:
+ case LLOAD_OP_RESTRICTED_ISOLATE:
+ upstream = client->c_linked_upstream;
+ assert( upstream );
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+ if ( op->o_restricted < client_restricted ) {
+ op->o_restricted = client_restricted;
+ }
+ CONNECTION_UNLOCK(client);
+
+ if ( upstream ) {
+ b = upstream->c_backend;
+ checked_lock( &b->b_mutex );
+ if ( !try_upstream( b, NULL, op, upstream, &res, &message ) ) {
+ upstream = NULL;
+ }
+ checked_unlock( &b->b_mutex );
+ } else if ( b ) {
+ backend_select( b, op, &upstream, &res, &message );
+ } else {
+ upstream_select( op, &upstream, &res, &message );
+ }
+
+ if ( !upstream ) {
+ Debug( LDAP_DEBUG_STATS, "request_process: "
+ "connid=%lu, msgid=%d no available connection found\n",
+ op->o_client_connid, op->o_client_msgid );
+
+ operation_send_reject( op, res, message, 1 );
+ goto fail;
+ }
+ CONNECTION_ASSERT_LOCKED(upstream);
+ assert_locked( &upstream->c_io_mutex );
+ op->o_upstream = upstream;
+ op->o_upstream_connid = upstream->c_connid;
+ op->o_res = LLOAD_OP_FAILED;
+
+ /* Was it unlinked in the meantime? No need to send a response since the
+ * client is dead */
+ if ( !IS_ALIVE( op, o_refcnt ) ) {
+ LloadBackend *b = upstream->c_backend;
+
+ upstream->c_n_ops_executing--;
+ checked_unlock( &upstream->c_io_mutex );
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ checked_unlock( &b->b_mutex );
+
+ assert( !IS_ALIVE( client, c_live ) );
+ checked_lock( &op->o_link_mutex );
+ if ( op->o_upstream ) {
+ op->o_upstream = NULL;
+ }
+ checked_unlock( &op->o_link_mutex );
+ return -1;
+ }
+
+ output = upstream->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ LloadBackend *b = upstream->c_backend;
+
+ upstream->c_n_ops_executing--;
+ CONNECTION_UNLOCK(upstream);
+ checked_unlock( &upstream->c_io_mutex );
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ operation_update_backend_counters( op, b );
+ checked_unlock( &b->b_mutex );
+
+ Debug( LDAP_DEBUG_ANY, "request_process: "
+ "ber_alloc failed\n" );
+
+ rc = -1;
+ goto fail;
+ }
+ upstream->c_pendingber = output;
+
+ if ( client_restricted < LLOAD_OP_RESTRICTED_UPSTREAM &&
+ op->o_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM ) {
+ rc = ldap_tavl_insert(
+ &upstream->c_linked, client, lload_upstream_entry_cmp,
+ ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ }
+
+ op->o_upstream_msgid = msgid = upstream->c_next_msgid++;
+ rc = ldap_tavl_insert(
+ &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error );
+
+ CONNECTION_UNLOCK(upstream);
+
+ Debug( LDAP_DEBUG_TRACE, "request_process: "
+ "client connid=%lu added %s msgid=%d to upstream connid=%lu as "
+ "msgid=%d\n",
+ op->o_client_connid, lload_msgtype2str( op->o_tag ),
+ op->o_client_msgid, op->o_upstream_connid, op->o_upstream_msgid );
+ assert( rc == LDAP_SUCCESS );
+
+ lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_forwarded++;
+
+ if ( op->o_restricted > client_restricted ||
+ client_restricted == LLOAD_OP_RESTRICTED_WRITE ) {
+ CONNECTION_LOCK(client);
+ if ( op->o_restricted > client_restricted ) {
+ client->c_restricted = op->o_restricted;
+ }
+ if ( op->o_restricted == LLOAD_OP_RESTRICTED_WRITE ) {
+ client->c_restricted_inflight++;
+ }
+ if ( op->o_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM ) {
+ if ( client_restricted < LLOAD_OP_RESTRICTED_UPSTREAM ) {
+ client->c_linked_upstream = upstream;
+ }
+ assert( client->c_linked_upstream == upstream );
+ client->c_backend = NULL;
+ } else if ( op->o_restricted >= LLOAD_OP_RESTRICTED_WRITE ) {
+ if ( client_restricted < LLOAD_OP_RESTRICTED_WRITE ) {
+ client->c_backend = upstream->c_backend;
+ }
+ assert( client->c_backend == upstream->c_backend );
+ }
+ CONNECTION_UNLOCK(client);
+ }
+
+ if ( (lload_features & LLOAD_FEATURE_PROXYAUTHZ) &&
+ client->c_type != LLOAD_C_PRIVILEGED ) {
+ CONNECTION_LOCK(client);
+ Debug( LDAP_DEBUG_TRACE, "request_process: "
+ "proxying identity %s to upstream\n",
+ client->c_auth.bv_val );
+ ber_printf( output, "t{titOt{{sbO}" /* "}}" */, LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, msgid,
+ op->o_tag, &op->o_request,
+ LDAP_TAG_CONTROLS,
+ LDAP_CONTROL_PROXY_AUTHZ, 1, &client->c_auth );
+ CONNECTION_UNLOCK(client);
+
+ if ( !BER_BVISNULL( &op->o_ctrls ) ) {
+ ber_write( output, op->o_ctrls.bv_val, op->o_ctrls.bv_len, 0 );
+ }
+
+ ber_printf( output, /* "{{" */ "}}" );
+ } else {
+ ber_printf( output, "t{titOtO}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, msgid,
+ op->o_tag, &op->o_request,
+ LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) );
+ }
+ checked_unlock( &upstream->c_io_mutex );
+
+ connection_write_cb( -1, 0, upstream );
+ return rc;
+
+fail:
+ if ( upstream ) {
+ CONNECTION_LOCK_DESTROY(upstream);
+
+ /* We have not committed any restrictions in the end */
+ op->o_restricted = LLOAD_OP_NOT_RESTRICTED;
+ operation_send_reject( op, LDAP_OTHER, "internal error", 0 );
+ }
+
+ OPERATION_UNLINK(op);
+ if ( rc ) {
+ CONNECTION_LOCK_DESTROY(client);
+ }
+ return rc;
+}
+
+int
+handle_one_request( LloadConnection *c )
+{
+ BerElement *ber;
+ LloadOperation *op = NULL;
+ RequestHandler handler = NULL;
+ int over_limit = 0;
+ enum sc_state state;
+ enum sc_io_state io_state;
+
+ ber = c->c_currentber;
+ c->c_currentber = NULL;
+
+ CONNECTION_LOCK(c);
+ op = operation_init( c, ber );
+ if ( !op ) {
+ Debug( LDAP_DEBUG_ANY, "handle_one_request: "
+ "connid=%lu, operation_init failed\n",
+ c->c_connid );
+ CONNECTION_DESTROY(c);
+ ber_free( ber, 1 );
+ return -1;
+ }
+ if ( lload_client_max_pending &&
+ c->c_n_ops_executing >= lload_client_max_pending ) {
+ over_limit = 1;
+ }
+
+ /*
+ * Remember the current state so we don't have to lock again,
+ * we're only screening whether we can keep going, e.g. noone can change
+ * state to LLOAD_C_BINDING from under us (would imply a new operation was
+ * received but that's us), but the opposite is possible - a Bind response
+ * could be received and processed in the meantime.
+ */
+ state = c->c_state;
+ CONNECTION_UNLOCK(c);
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_UNBIND:
+ /* There is never a response for this operation */
+ op->o_res = LLOAD_OP_COMPLETED;
+ OPERATION_UNLINK(op);
+
+ Debug( LDAP_DEBUG_STATS, "handle_one_request: "
+ "received unbind, closing client connid=%lu\n",
+ c->c_connid );
+ CONNECTION_LOCK_DESTROY(c);
+ return -1;
+ case LDAP_REQ_BIND:
+ handler = request_bind;
+ break;
+ case LDAP_REQ_ABANDON:
+ /* We can't send a response to abandon requests even if a bind is
+ * currently in progress */
+ return request_abandon( c, op );
+ case LDAP_REQ_EXTENDED:
+ default:
+ if ( state == LLOAD_C_BINDING ) {
+ operation_send_reject(
+ op, LDAP_PROTOCOL_ERROR, "bind in progress", 0 );
+ return LDAP_SUCCESS;
+ }
+ if ( over_limit ) {
+ operation_send_reject( op, LDAP_BUSY,
+ "pending operation limit reached on this connection",
+ 0 );
+ return LDAP_SUCCESS;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ io_state = c->c_io_state;
+ checked_unlock( &c->c_io_mutex );
+ if ( io_state & LLOAD_C_READ_PAUSE ) {
+ operation_send_reject( op, LDAP_BUSY,
+ "writing side backlogged, please keep reading", 0 );
+ return LDAP_SUCCESS;
+ }
+
+ if ( op->o_tag == LDAP_REQ_EXTENDED ) {
+ handler = request_extended;
+ } else {
+ handler = request_process;
+ }
+ break;
+ }
+
+ if ( state == LLOAD_C_CLOSING ) {
+ operation_send_reject(
+ op, LDAP_UNAVAILABLE, "connection is shutting down", 0 );
+ return LDAP_SUCCESS;
+ }
+
+ return handler( c, op );
+}
+
+#ifdef HAVE_TLS
+/*
+ * The connection has a token assigned to it when the callback is set up.
+ */
+void
+client_tls_handshake_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadConnection *c = arg;
+ epoch_t epoch;
+ int rc = 0;
+
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: "
+ "connid=%lu, timeout reached, destroying\n",
+ c->c_connid );
+ goto fail;
+ }
+
+ /*
+ * In case of StartTLS, make sure we flush the response first.
+ * Also before we try to read anything from the connection, it isn't
+ * permitted to Abandon a StartTLS exop per RFC4511 anyway.
+ */
+ checked_lock( &c->c_io_mutex );
+ if ( c->c_pendingber ) {
+ checked_unlock( &c->c_io_mutex );
+ connection_write_cb( s, what, arg );
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ goto fail;
+ }
+
+ /* Do we still have data pending? If so, connection_write_cb would
+ * already have arranged the write callback to trigger again */
+ checked_lock( &c->c_io_mutex );
+ if ( c->c_pendingber ) {
+ checked_unlock( &c->c_io_mutex );
+ return;
+ }
+ }
+
+ rc = ldap_pvt_tls_accept( c->c_sb, LLOAD_TLS_CTX );
+ checked_unlock( &c->c_io_mutex );
+ if ( rc < 0 ) {
+ goto fail;
+ }
+
+ if ( rc == 0 ) {
+ struct event_base *base = event_get_base( c->c_read_event );
+
+ /*
+ * We're finished, replace the callbacks
+ *
+ * This is deadlock-safe, since both share the same base - the one
+ * that's just running us.
+ */
+ CONNECTION_LOCK(c);
+ event_del( c->c_read_event );
+ event_del( c->c_write_event );
+
+ c->c_read_timeout = NULL;
+ event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST,
+ connection_read_cb, c );
+ if ( IS_ALIVE( c, c_live ) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ }
+
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ connection_write_cb, c );
+ Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: "
+ "connid=%lu finished\n",
+ c->c_connid );
+
+ c->c_is_tls = LLOAD_TLS_ESTABLISHED;
+ CONNECTION_UNLOCK(c);
+ return;
+ } else if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) {
+ if ( IS_ALIVE( c, c_live ) ) {
+ CONNECTION_LOCK(c);
+ event_add( c->c_write_event, lload_write_timeout );
+ CONNECTION_UNLOCK(c);
+ }
+ Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: "
+ "connid=%lu need write rc=%d\n",
+ c->c_connid, rc );
+ }
+ return;
+
+fail:
+ Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: "
+ "connid=%lu failed rc=%d\n",
+ c->c_connid, rc );
+
+ assert( c->c_ops == NULL );
+ epoch = epoch_join();
+ CONNECTION_LOCK_DESTROY(c);
+ epoch_leave( epoch );
+}
+#endif /* HAVE_TLS */
+
+LloadConnection *
+client_init(
+ ber_socket_t s,
+ const char *peername,
+ struct event_base *base,
+ int flags )
+{
+ LloadConnection *c;
+ struct event *event;
+ event_callback_fn read_cb = connection_read_cb,
+ write_cb = connection_write_cb;
+
+ if ( (c = lload_connection_init( s, peername, flags) ) == NULL ) {
+ return NULL;
+ }
+
+ {
+ ber_len_t max = sockbuf_max_incoming_client;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ c->c_state = LLOAD_C_READY;
+
+ if ( flags & CONN_IS_TLS ) {
+#ifdef HAVE_TLS
+ int rc;
+
+ c->c_is_tls = LLOAD_LDAPS;
+
+ rc = ldap_pvt_tls_accept( c->c_sb, LLOAD_TLS_CTX );
+ if ( rc < 0 ) {
+ Debug( LDAP_DEBUG_CONNS, "client_init: "
+ "connid=%lu failed initial TLS accept rc=%d\n",
+ c->c_connid, rc );
+ CONNECTION_LOCK(c);
+ goto fail;
+ }
+
+ if ( rc ) {
+ c->c_read_timeout = lload_timeout_net;
+ read_cb = write_cb = client_tls_handshake_cb;
+ }
+#else /* ! HAVE_TLS */
+ assert(0);
+#endif /* ! HAVE_TLS */
+ }
+
+ event = event_new( base, s, EV_READ|EV_PERSIST, read_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "client_init: "
+ "Read event could not be allocated\n" );
+ CONNECTION_LOCK(c);
+ goto fail;
+ }
+ c->c_read_event = event;
+
+ event = event_new( base, s, EV_WRITE, write_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "client_init: "
+ "Write event could not be allocated\n" );
+ CONNECTION_LOCK(c);
+ goto fail;
+ }
+ c->c_write_event = event;
+
+ CONNECTION_LOCK(c);
+#ifdef BALANCER_MODULE
+ if ( lload_monitor_client_subsys ) {
+ acquire_ref( &c->c_refcnt );
+ CONNECTION_UNLOCK(c);
+ if ( lload_monitor_conn_entry_create(
+ c, lload_monitor_client_subsys ) ) {
+ CONNECTION_LOCK(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ goto fail;
+ }
+ CONNECTION_LOCK(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ }
+#endif /* BALANCER_MODULE */
+
+ c->c_destroy = client_destroy;
+ c->c_unlink = client_unlink;
+ c->c_pdu_cb = handle_one_request;
+
+ /* We only register the write event when we have data pending */
+ event_add( c->c_read_event, c->c_read_timeout );
+
+ checked_lock( &clients_mutex );
+ LDAP_CIRCLEQ_INSERT_TAIL( &clients, c, c_next );
+ checked_unlock( &clients_mutex );
+ CONNECTION_UNLOCK(c);
+
+ return c;
+fail:
+ if ( !IS_ALIVE( c, c_live ) ) {
+ /*
+ * Released while we were unlocked, it's scheduled for destruction
+ * already
+ */
+ return NULL;
+ }
+
+ if ( c->c_write_event ) {
+ event_free( c->c_write_event );
+ c->c_write_event = NULL;
+ }
+ if ( c->c_read_event ) {
+ event_free( c->c_read_event );
+ c->c_read_event = NULL;
+ }
+
+ c->c_state = LLOAD_C_INVALID;
+ c->c_live--;
+ c->c_refcnt--;
+ connection_destroy( c );
+ return NULL;
+}
+
+void
+client_reset( LloadConnection *c )
+{
+ TAvlnode *root;
+ long freed = 0, executing;
+ LloadConnection *linked_upstream = NULL;
+ enum op_restriction restricted = c->c_restricted;
+
+ CONNECTION_ASSERT_LOCKED(c);
+ root = c->c_ops;
+ c->c_ops = NULL;
+ executing = c->c_n_ops_executing;
+ c->c_n_ops_executing = 0;
+
+ if ( !BER_BVISNULL( &c->c_auth ) ) {
+ ch_free( c->c_auth.bv_val );
+ BER_BVZERO( &c->c_auth );
+ }
+ if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) {
+ ch_free( c->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+
+ if ( restricted && restricted < LLOAD_OP_RESTRICTED_ISOLATE ) {
+ if ( c->c_backend ) {
+ assert( c->c_restricted <= LLOAD_OP_RESTRICTED_BACKEND );
+ assert( c->c_restricted_inflight == 0 );
+ c->c_backend = NULL;
+ c->c_restricted_at = 0;
+ } else {
+ assert( c->c_restricted == LLOAD_OP_RESTRICTED_UPSTREAM );
+ assert( c->c_linked_upstream != NULL );
+ linked_upstream = c->c_linked_upstream;
+ c->c_linked_upstream = NULL;
+ }
+ }
+ CONNECTION_UNLOCK(c);
+
+ if ( root ) {
+ freed = ldap_tavl_free( root, (AVL_FREE)operation_abandon );
+ Debug( LDAP_DEBUG_TRACE, "client_reset: "
+ "dropped %ld operations\n",
+ freed );
+ }
+ assert( freed == executing );
+
+ if ( linked_upstream && restricted == LLOAD_OP_RESTRICTED_UPSTREAM ) {
+ LloadConnection *removed = ldap_tavl_delete(
+ &linked_upstream->c_linked, c, lload_upstream_entry_cmp );
+ assert( removed == c );
+ }
+
+ CONNECTION_LOCK(c);
+ CONNECTION_ASSERT_LOCKED(c);
+}
+
+void
+client_unlink( LloadConnection *c )
+{
+ enum sc_state state;
+ struct event *read_event, *write_event;
+
+ Debug( LDAP_DEBUG_CONNS, "client_unlink: "
+ "removing client connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( c->c_state != LLOAD_C_INVALID );
+ assert( c->c_state != LLOAD_C_DYING );
+
+ state = c->c_state;
+ c->c_state = LLOAD_C_DYING;
+
+ if ( c->c_restricted == LLOAD_OP_RESTRICTED_ISOLATE ) {
+ /* Allow upstream connection to be severed in client_reset() */
+ c->c_restricted = LLOAD_OP_RESTRICTED_UPSTREAM;
+ }
+
+ read_event = c->c_read_event;
+ write_event = c->c_write_event;
+ CONNECTION_UNLOCK(c);
+
+ if ( read_event ) {
+ event_del( read_event );
+ }
+
+ if ( write_event ) {
+ event_del( write_event );
+ }
+
+ if ( state != LLOAD_C_DYING ) {
+ checked_lock( &clients_mutex );
+ LDAP_CIRCLEQ_REMOVE( &clients, c, c_next );
+ checked_unlock( &clients_mutex );
+ }
+
+ CONNECTION_LOCK(c);
+ client_reset( c );
+ CONNECTION_ASSERT_LOCKED(c);
+}
+
+void
+client_destroy( LloadConnection *c )
+{
+ Debug( LDAP_DEBUG_CONNS, "client_destroy: "
+ "destroying client connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_LOCK(c);
+ assert( c->c_state == LLOAD_C_DYING );
+
+#ifdef BALANCER_MODULE
+ /*
+ * Can't do this in client_unlink as that could be run from cn=monitor
+ * modify callback.
+ */
+ if ( !BER_BVISNULL( &c->c_monitor_dn ) ) {
+ lload_monitor_conn_unlink( c );
+ }
+#endif /* BALANCER_MODULE */
+
+ c->c_state = LLOAD_C_INVALID;
+
+ assert( c->c_ops == NULL );
+
+ if ( c->c_read_event ) {
+ event_free( c->c_read_event );
+ c->c_read_event = NULL;
+ }
+
+ if ( c->c_write_event ) {
+ event_free( c->c_write_event );
+ c->c_write_event = NULL;
+ }
+
+ assert( c->c_refcnt == 0 );
+ connection_destroy( c );
+}
+
+void
+clients_destroy( int gentle )
+{
+ epoch_t epoch = epoch_join();
+ checked_lock( &clients_mutex );
+ connections_walk(
+ &clients_mutex, &clients, lload_connection_close, &gentle );
+ checked_unlock( &clients_mutex );
+ epoch_leave( epoch );
+}
diff --git a/servers/lloadd/config.c b/servers/lloadd/config.c
new file mode 100644
index 0000000..ab7a26b
--- /dev/null
+++ b/servers/lloadd/config.c
@@ -0,0 +1,4032 @@
+/* config.c - configuration file handling routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef S_ISREG
+#define S_ISREG(m) ( ((m) & _S_IFMT ) == _S_IFREG )
+#endif
+
+#include "lload.h"
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "lload-config.h"
+#include "../slapd/slap-cfglog.h"
+
+#ifdef _WIN32
+#define LUTIL_ATOULX lutil_atoullx
+#define Z "I"
+#else
+#define LUTIL_ATOULX lutil_atoulx
+#define Z "z"
+#endif
+
+#define ARGS_STEP 512
+
+/*
+ * defaults for various global variables
+ */
+#ifdef BALANCER_MODULE
+char *listeners_list = NULL;
+#else /* !BALANCER_MODULE */
+slap_mask_t global_allows = 0;
+slap_mask_t global_disallows = 0;
+int global_gentlehup = 0;
+int global_idletimeout = 0;
+char *global_host = NULL;
+
+char *slapd_pid_file = NULL;
+char *slapd_args_file = NULL;
+#endif /* !BALANCER_MODULE */
+
+static struct timeval timeout_api_tv, timeout_net_tv,
+ timeout_write_tv = { 10, 0 };
+
+lload_features_t lload_features;
+int lload_write_coherence = 0;
+
+ber_len_t sockbuf_max_incoming_client = LLOAD_SB_MAX_INCOMING_CLIENT;
+ber_len_t sockbuf_max_incoming_upstream = LLOAD_SB_MAX_INCOMING_UPSTREAM;
+
+int lload_conn_max_pdus_per_cycle = LLOAD_CONN_MAX_PDUS_PER_CYCLE_DEFAULT;
+
+struct timeval *lload_timeout_api = NULL;
+struct timeval *lload_timeout_net = NULL;
+struct timeval *lload_write_timeout = &timeout_write_tv;
+
+static int fp_getline( FILE *fp, ConfigArgs *c );
+static void fp_getline_init( ConfigArgs *c );
+
+static char *strtok_quote(
+ char *line,
+ char *sep,
+ char **quote_ptr,
+ int *inquote );
+
+typedef struct ConfigFile {
+ struct ConfigFile *c_sibs;
+ struct ConfigFile *c_kids;
+ struct berval c_file;
+ BerVarray c_dseFiles;
+} ConfigFile;
+
+static ConfigFile *cfn;
+
+static ConfigDriver config_fname;
+static ConfigDriver config_generic;
+static ConfigDriver config_tier;
+static ConfigDriver config_backend;
+static ConfigDriver config_bindconf;
+static ConfigDriver config_restrict_oid;
+#ifdef LDAP_TCP_BUFFER
+static ConfigDriver config_tcp_buffer;
+#endif /* LDAP_TCP_BUFFER */
+static ConfigDriver config_restrict;
+static ConfigDriver config_include;
+static ConfigDriver config_feature;
+#ifdef HAVE_TLS
+static ConfigDriver config_tls_option;
+static ConfigDriver config_tls_config;
+#endif
+#ifdef BALANCER_MODULE
+static ConfigDriver config_share_tls_ctx;
+static ConfigDriver backend_cf_gen;
+#endif /* BALANCER_MODULE */
+
+struct slap_bindconf bindconf = {};
+struct berval lloadd_identity = BER_BVNULL;
+
+enum {
+ CFG_ACL = 1,
+ CFG_BACKEND,
+ CFG_BINDCONF,
+ CFG_LISTEN,
+ CFG_LISTEN_URI,
+ CFG_TLS_RAND,
+ CFG_TLS_CIPHER,
+ CFG_TLS_PROTOCOL_MIN,
+ CFG_TLS_CERT_FILE,
+ CFG_TLS_CERT_KEY,
+ CFG_TLS_CA_PATH,
+ CFG_TLS_CA_FILE,
+ CFG_TLS_DH_FILE,
+ CFG_TLS_VERIFY,
+ CFG_TLS_CRLCHECK,
+ CFG_TLS_CRL_FILE,
+ CFG_TLS_SHARE_CTX,
+ CFG_CONCUR,
+ CFG_THREADS,
+ CFG_MIRRORMODE,
+ CFG_IOTHREADS,
+ CFG_MAXBUF_CLIENT,
+ CFG_MAXBUF_UPSTREAM,
+ CFG_FEATURE,
+ CFG_THREADQS,
+ CFG_TLS_ECNAME,
+ CFG_TLS_CACERT,
+ CFG_TLS_CERT,
+ CFG_TLS_KEY,
+ CFG_RESCOUNT,
+ CFG_IOTIMEOUT,
+ CFG_URI,
+ CFG_NUMCONNS,
+ CFG_BINDCONNS,
+ CFG_RETRY,
+ CFG_MAX_PENDING_OPS,
+ CFG_MAX_PENDING_CONNS,
+ CFG_STARTTLS,
+ CFG_CLIENT_PENDING,
+ CFG_RESTRICT_EXOP,
+ CFG_RESTRICT_CONTROL,
+ CFG_TIER,
+ CFG_WEIGHT,
+
+ CFG_LAST
+};
+
+/* alphabetical ordering */
+
+static ConfigTable config_back_cf_table[] = {
+ /* This attr is read-only */
+ { "", "", 0, 0, 0,
+ ARG_MAGIC,
+ &config_fname,
+ NULL, NULL, NULL
+ },
+ { "argsfile", "file", 2, 2, 0,
+ ARG_STRING,
+ &slapd_args_file,
+ NULL, NULL, NULL
+ },
+ { "concurrency", "level", 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_CONCUR,
+ &config_generic,
+ NULL, NULL, NULL
+ },
+ { "tier", "name", 2, 2, 0,
+ ARG_MAGIC|ARG_STRING|CFG_TIER,
+ &config_tier,
+ "( OLcfgBkAt:13.39 "
+ "NAME 'olcBkLloadTierType' "
+ "DESC 'Tier type' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ /* conf-file only option */
+ { "backend-server", "backend options", 2, 0, 0,
+ ARG_MAGIC|CFG_BACKEND,
+ &config_backend,
+ NULL, NULL, NULL
+ },
+ { "bindconf", "backend credentials", 2, 0, 0,
+ ARG_MAGIC|CFG_BINDCONF,
+ &config_bindconf,
+ "( OLcfgBkAt:13.2 "
+ "NAME 'olcBkLloadBindconf' "
+ "DESC 'Backend credentials' "
+ /* No EQUALITY since this is a compound attribute (and needs
+ * splitting up anyway - which is a TODO) */
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "gentlehup", "on|off", 2, 2, 0,
+#ifdef SIGHUP
+ ARG_ON_OFF,
+ &global_gentlehup,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ NULL, NULL, NULL
+ },
+ { "idletimeout", "timeout", 2, 2, 0,
+ ARG_UINT,
+ &global_idletimeout,
+ "( OLcfgBkAt:13.3 "
+ "NAME 'olcBkLloadIdleTimeout' "
+ "DESC 'Connection idle timeout' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "include", "file", 2, 2, 0,
+ ARG_MAGIC,
+ &config_include,
+ NULL, NULL, NULL
+ },
+ { "io-threads", "count", 2, 0, 0,
+ ARG_UINT|ARG_MAGIC|CFG_IOTHREADS,
+ &config_generic,
+ "( OLcfgBkAt:13.4 "
+ "NAME 'olcBkLloadIOThreads' "
+ "DESC 'I/O thread count' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+#ifdef BALANCER_MODULE
+ { "listen", "uri list", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|CFG_LISTEN,
+ &config_generic,
+ NULL, NULL, NULL
+ },
+ { "", "uri", 2, 2, 0,
+ ARG_MAGIC|CFG_LISTEN_URI,
+ &config_generic,
+ "( OLcfgBkAt:13.5 "
+ "NAME 'olcBkLloadListen' "
+ "DESC 'A listener adress' "
+ /* We don't handle adding/removing a value, so no EQUALITY yet */
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+#endif /* BALANCER_MODULE */
+ { "logfile", "file", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|CFG_LOGFILE,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "logfile-format", "debug|syslog-utc|syslog-localtime", 2, 2, 0,
+ ARG_MAGIC|CFG_LOGFILE_FORMAT,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "logfile-only", "on|off", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|CFG_LOGFILE_ONLY,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "logfile-rotate", "max> <Mbyte> <hours", 4, 4, 0,
+ ARG_MAGIC|CFG_LOGFILE_ROTATE,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "loglevel", "level", 2, 0, 0,
+ ARG_MAGIC|CFG_LOGLEVEL,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "pidfile", "file", 2, 2, 0,
+ ARG_STRING,
+ &slapd_pid_file,
+ NULL, NULL, NULL
+ },
+ { "restrict", "op_list", 2, 0, 0,
+ ARG_MAGIC,
+ &config_restrict,
+ NULL, NULL, NULL
+ },
+ { "sockbuf_max_incoming_client", "max", 2, 2, 0,
+ ARG_BER_LEN_T|ARG_MAGIC|CFG_MAXBUF_CLIENT,
+ &config_generic,
+ "( OLcfgBkAt:13.6 "
+ "NAME 'olcBkLloadSockbufMaxClient' "
+ "DESC 'The maximum LDAP PDU size accepted coming from clients' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_ber_t = LLOAD_SB_MAX_INCOMING_CLIENT }
+ },
+ { "sockbuf_max_incoming_upstream", "max", 2, 2, 0,
+ ARG_BER_LEN_T|ARG_MAGIC|CFG_MAXBUF_UPSTREAM,
+ &config_generic,
+ "( OLcfgBkAt:13.7 "
+ "NAME 'olcBkLloadSockbufMaxUpstream' "
+ "DESC 'The maximum LDAP PDU size accepted coming from upstream' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_ber_t = LLOAD_SB_MAX_INCOMING_UPSTREAM }
+ },
+ { "tcp-buffer", "[listener=<listener>] [{read|write}=]size", 0, 0, 0,
+#ifdef LDAP_TCP_BUFFER
+ ARG_MAGIC,
+ &config_tcp_buffer,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.8 "
+ "NAME 'olcBkLloadTcpBuffer' "
+ "DESC 'TCP Buffer size' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "threads", "count", 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_THREADS,
+ &config_generic,
+ NULL, NULL, NULL
+ },
+ { "threadqueues", "count", 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_THREADQS,
+ &config_generic,
+ NULL, NULL, NULL
+ },
+ { "max_pdus_per_cycle", "count", 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_RESCOUNT,
+ &config_generic,
+ "( OLcfgBkAt:13.9 "
+ "NAME 'olcBkLloadMaxPDUPerCycle' "
+ "DESC 'Maximum number of PDUs to handle in a single cycle' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "feature", "name", 2, 0, 0,
+ ARG_MAGIC|CFG_FEATURE,
+ &config_feature,
+ "( OLcfgBkAt:13.10 "
+ "NAME 'olcBkLloadFeature' "
+ "DESC 'Lload features enabled' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+ { "TLSCACertificate", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CACERT|ARG_BINARY|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.11 "
+ "NAME 'olcBkLloadTLSCACertificate' "
+ "DESC 'X.509 certificate, must use ;binary' "
+ "EQUALITY certificateExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCACertificateFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CA_FILE|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.12 "
+ "NAME 'olcBkLloadTLSCACertificateFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCACertificatePath", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CA_PATH|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.13 "
+ "NAME 'olcBkLloadTLSCACertificatePath' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCertificate", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT|ARG_BINARY|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.14 "
+ "NAME 'olcBkLloadTLSCertificate' "
+ "DESC 'X.509 certificate, must use ;binary' "
+ "EQUALITY certificateExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCertificateFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT_FILE|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.15 "
+ "NAME 'olcBkLloadTLSCertificateFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCertificateKey", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_KEY|ARG_BINARY|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.16 "
+ "NAME 'olcBkLloadTLSCertificateKey' "
+ "DESC 'X.509 privateKey, must use ;binary' "
+ "EQUALITY privateKeyMatch "
+ "SYNTAX 1.2.840.113549.1.8.1.1 "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCertificateKeyFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT_KEY|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.17 "
+ "NAME 'olcBkLloadTLSCertificateKeyFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCipherSuite", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CIPHER|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.18 "
+ "NAME 'olcBkLloadTLSCipherSuite' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCRLCheck", NULL, 2, 2, 0,
+#if defined(HAVE_TLS) && defined(HAVE_OPENSSL)
+ CFG_TLS_CRLCHECK|ARG_STRING|ARG_MAGIC,
+ &config_tls_config,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.19 "
+ "NAME 'olcBkLloadTLSCRLCheck' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSCRLFile", NULL, 2, 2, 0,
+#if defined(HAVE_GNUTLS)
+ CFG_TLS_CRL_FILE|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.20 "
+ "NAME 'olcBkLloadTLSCRLFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSRandFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_RAND|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.21 "
+ "NAME 'olcBkLloadTLSRandFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSVerifyClient", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_VERIFY|ARG_STRING|ARG_MAGIC,
+ &config_tls_config,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.22 "
+ "NAME 'olcBkLloadVerifyClient' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSDHParamFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_DH_FILE|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.23 "
+ "NAME 'olcBkLloadTLSDHParamFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSECName", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_ECNAME|ARG_STRING|ARG_MAGIC,
+ &config_tls_option,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.24 "
+ "NAME 'olcBkLloadTLSECName' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSProtocolMin", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC,
+ &config_tls_config,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.25 "
+ "NAME 'olcBkLloadTLSProtocolMin' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "TLSShareSlapdCTX", NULL, 2, 2, 0,
+#if defined(HAVE_TLS) && defined(BALANCER_MODULE)
+ CFG_TLS_SHARE_CTX|ARG_ON_OFF|ARG_MAGIC,
+ &config_share_tls_ctx,
+#else
+ ARG_IGNORED,
+ NULL,
+#endif
+ "( OLcfgBkAt:13.33 "
+ "NAME 'olcBkLloadTLSShareSlapdCTX' "
+ "DESC 'Share slapd TLS context (all other lloadd TLS options cease to take effect)' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "iotimeout", "ms timeout", 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_IOTIMEOUT,
+ &config_generic,
+ "( OLcfgBkAt:13.26 "
+ "NAME 'olcBkLloadIOTimeout' "
+ "DESC 'I/O timeout threshold in milliseconds' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "client_max_pending", NULL, 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|CFG_CLIENT_PENDING,
+ &config_generic,
+ "( OLcfgBkAt:13.35 "
+ "NAME 'olcBkLloadClientMaxPending' "
+ "DESC 'Maximum pending operations per client connection' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_uint = 0 }
+ },
+ { "write_coherence", "seconds", 2, 2, 0,
+ ARG_INT,
+ &lload_write_coherence,
+ "( OLcfgBkAt:13.36 "
+ "NAME 'olcBkLloadWriteCoherence' "
+ "DESC 'Keep operations to the same backend after a write' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_int = 0 }
+ },
+ { "restrict_exop", "OID> <action", 3, 3, 0,
+ ARG_MAGIC|CFG_RESTRICT_EXOP,
+ &config_restrict_oid,
+ "( OLcfgBkAt:13.37 "
+ "NAME 'olcBkLloadRestrictExop' "
+ "DESC 'Restrict upstream selection after forwarding an extended operation' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+ { "restrict_control", "OID> <action", 3, 3, 0,
+ ARG_MAGIC|CFG_RESTRICT_CONTROL,
+ &config_restrict_oid,
+ "( OLcfgBkAt:13.38 "
+ "NAME 'olcBkLloadRestrictControl' "
+ "DESC 'Restrict upstream selection after forwarding a control' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+
+ /* cn=config only options */
+#ifdef BALANCER_MODULE
+ { "", "uri", 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|CFG_URI,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.27 "
+ "NAME 'olcBkLloadBackendUri' "
+ "DESC 'URI to contact the server on' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_NUMCONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.28 "
+ "NAME 'olcBkLloadNumconns' "
+ "DESC 'Number of regular connections to maintain' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_BINDCONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.29 "
+ "NAME 'olcBkLloadBindconns' "
+ "DESC 'Number of bind connections to maintain' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_RETRY,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.30 "
+ "NAME 'olcBkLloadRetry' "
+ "DESC 'Number of seconds to wait before trying to reconnect' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_MAX_PENDING_OPS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.31 "
+ "NAME 'olcBkLloadMaxPendingOps' "
+ "DESC 'Maximum number of pending operations for this backend' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_MAX_PENDING_CONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.32 "
+ "NAME 'olcBkLloadMaxPendingConns' "
+ "DESC 'Maximum number of pending operations on each connection' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|CFG_STARTTLS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.34 "
+ "NAME 'olcBkLloadStartTLS' "
+ "DESC 'Whether StartTLS should be attempted on the connection' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|CFG_WEIGHT,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.40 "
+ "NAME 'olcBkLloadWeight' "
+ "DESC 'Backend weight' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_uint = 0 },
+ },
+#endif /* BALANCER_MODULE */
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL }
+};
+
+#ifdef BALANCER_MODULE
+static ConfigCfAdd lload_cfadd;
+
+static ConfigLDAPadd lload_backend_ldadd;
+static ConfigLDAPadd lload_tier_ldadd;
+
+#ifdef SLAP_CONFIG_DELETE
+static ConfigLDAPdel lload_backend_lddel;
+static ConfigLDAPdel lload_tier_lddel;
+#endif /* SLAP_CONFIG_DELETE */
+
+static ConfigOCs lloadocs[] = {
+ { "( OLcfgBkOc:13.1 "
+ "NAME 'olcBkLloadConfig' "
+ "DESC 'Lload backend configuration' "
+ "SUP olcBackendConfig "
+ "MUST ( olcBkLloadBindconf "
+ "$ olcBkLloadIOThreads "
+ "$ olcBkLloadListen "
+ "$ olcBkLloadSockbufMaxClient "
+ "$ olcBkLloadSockbufMaxUpstream "
+ "$ olcBkLloadMaxPDUPerCycle "
+ "$ olcBkLloadIOTimeout ) "
+ "MAY ( olcBkLloadFeature "
+ "$ olcBkLloadTcpBuffer "
+ "$ olcBkLloadTLSCACertificateFile "
+ "$ olcBkLloadTLSCACertificatePath "
+ "$ olcBkLloadTLSCertificateFile "
+ "$ olcBkLloadTLSCertificateKeyFile "
+ "$ olcBkLloadTLSCipherSuite "
+ "$ olcBkLloadTLSCRLCheck "
+ "$ olcBkLloadTLSRandFile "
+ "$ olcBkLloadVerifyClient "
+ "$ olcBkLloadTLSDHParamFile "
+ "$ olcBkLloadTLSECName "
+ "$ olcBkLloadTLSProtocolMin "
+ "$ olcBkLloadTLSCRLFile "
+ "$ olcBkLloadTLSShareSlapdCTX "
+ "$ olcBkLloadClientMaxPending "
+ "$ olcBkLloadWriteCoherence "
+ "$ olcBkLloadRestrictExop "
+ "$ olcBkLloadRestrictControl "
+ ") )",
+ Cft_Backend, config_back_cf_table,
+ NULL,
+ lload_cfadd,
+ },
+ { "( OLcfgBkOc:13.2 "
+ "NAME 'olcBkLloadBackendConfig' "
+ "DESC 'Lload backend server configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( cn "
+ "$ olcBkLloadBackendUri "
+ "$ olcBkLloadNumconns "
+ "$ olcBkLloadBindconns "
+ "$ olcBkLloadRetry "
+ "$ olcBkLloadMaxPendingOps "
+ "$ olcBkLloadMaxPendingConns ) "
+ "MAY ( olcBkLloadStartTLS "
+ "$ olcBkLloadWeight ) "
+ ") )",
+ Cft_Misc, config_back_cf_table,
+ lload_backend_ldadd,
+ NULL,
+#ifdef SLAP_CONFIG_DELETE
+ lload_backend_lddel,
+#endif /* SLAP_CONFIG_DELETE */
+ },
+ { "( OLcfgBkOc:13.3 "
+ "NAME 'olcBkLloadTierConfig' "
+ "DESC 'Lload tier configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( cn "
+ "$ olcBkLloadTierType "
+ ") )",
+ Cft_Misc, config_back_cf_table,
+ lload_tier_ldadd,
+ NULL,
+#ifdef SLAP_CONFIG_DELETE
+ lload_tier_lddel,
+#endif /* SLAP_CONFIG_DELETE */
+ },
+ { NULL, 0, NULL }
+};
+#endif /* BALANCER_MODULE */
+
+static int
+config_generic( ConfigArgs *c )
+{
+ enum lcf_daemon flag = 0;
+ int rc = LDAP_SUCCESS;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch ( c->type ) {
+ case CFG_IOTHREADS:
+ c->value_uint = lload_daemon_threads;
+ break;
+ case CFG_LISTEN_URI: {
+ LloadListener **ll = lloadd_get_listeners();
+ struct berval bv = BER_BVNULL;
+
+ for ( ; ll && *ll; ll++ ) {
+ /* The same url could have spawned several consecutive
+ * listeners */
+ if ( !BER_BVISNULL( &bv ) &&
+ !ber_bvcmp( &bv, &(*ll)->sl_url ) ) {
+ continue;
+ }
+ ber_dupbv( &bv, &(*ll)->sl_url );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ } break;
+ case CFG_MAXBUF_CLIENT:
+ c->value_uint = sockbuf_max_incoming_client;
+ break;
+ case CFG_MAXBUF_UPSTREAM:
+ c->value_uint = sockbuf_max_incoming_upstream;
+ break;
+ case CFG_RESCOUNT:
+ c->value_uint = lload_conn_max_pdus_per_cycle;
+ break;
+ case CFG_IOTIMEOUT:
+ c->value_uint = 1000 * lload_write_timeout->tv_sec +
+ lload_write_timeout->tv_usec / 1000;
+ break;
+ case CFG_CLIENT_PENDING:
+ c->value_uint = lload_client_max_pending;
+ break;
+ default:
+ rc = 1;
+ break;
+ }
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* We only need to worry about deletions to multi-value or MAY
+ * attributes that belong to the lloadd module - we don't have any at
+ * the moment */
+ return rc;
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+
+ switch ( c->type ) {
+ case CFG_CONCUR:
+ ldap_pvt_thread_set_concurrency( c->value_uint );
+ break;
+ case CFG_LISTEN:
+ if ( lloadd_inited ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "listen directive can only be specified once" );
+ ch_free( c->value_string );
+ return 1;
+ }
+ if ( lloadd_listeners_init( c->value_string ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "could not open one of the listener sockets: %s",
+ c->value_string );
+ ch_free( c->value_string );
+ return 1;
+ }
+ ch_free( c->value_string );
+ break;
+ case CFG_LISTEN_URI: {
+ LDAPURLDesc *lud;
+ LloadListener *l;
+
+ if ( ldap_url_parse_ext(
+ c->line, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "string %s could not be parsed as an LDAP URL",
+ c->line );
+ goto fail;
+ }
+
+ /* A sanity check, although it will not catch everything */
+ if ( ( l = lload_config_check_my_url( c->line, lud ) ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "Load Balancer already configured to listen on %s "
+ "(while adding %s)",
+ l->sl_url.bv_val, c->line );
+ goto fail;
+ }
+
+ if ( !lloadd_inited ) {
+ if ( lload_open_new_listener( c->line, lud ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "could not open a listener for %s", c->line );
+ goto fail;
+ }
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "listener changes will not take effect until restart: "
+ "%s",
+ c->line );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ }
+ } break;
+ case CFG_THREADS:
+ if ( c->value_uint < 2 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "threads=%d smaller than minimum value 2",
+ c->value_uint );
+ goto fail;
+
+ } else if ( c->value_uint > 2 * SLAP_MAX_WORKER_THREADS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "warning, threads=%d larger than twice the default "
+ "(2*%d=%d); YMMV",
+ c->value_uint, SLAP_MAX_WORKER_THREADS,
+ 2 * SLAP_MAX_WORKER_THREADS );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ }
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_maxthreads(
+ &connection_pool, c->value_uint );
+ connection_pool_max = c->value_uint; /* save for reference */
+ break;
+
+ case CFG_THREADQS:
+ if ( c->value_uint < 1 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "threadqueues=%d smaller than minimum value 1",
+ c->value_uint );
+ goto fail;
+ }
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_queues( &connection_pool, c->value_uint );
+ connection_pool_queues = c->value_uint; /* save for reference */
+ break;
+
+ case CFG_IOTHREADS: {
+ int mask = 0;
+ /* use a power of two */
+ while ( c->value_uint > 1 ) {
+ c->value_uint >>= 1;
+ mask <<= 1;
+ mask |= 1;
+ }
+ if ( !lloadd_inited ) {
+ lload_daemon_mask = mask;
+ lload_daemon_threads = mask + 1;
+ flag = LLOAD_DAEMON_MOD_THREADS;
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "io thread changes will not take effect until "
+ "restart" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ }
+ } break;
+
+ case CFG_RESCOUNT:
+ lload_conn_max_pdus_per_cycle = c->value_uint;
+ break;
+
+ case CFG_IOTIMEOUT:
+ if ( c->value_uint > 0 ) {
+ timeout_write_tv.tv_sec = c->value_uint / 1000;
+ timeout_write_tv.tv_usec = 1000 * ( c->value_uint % 1000 );
+ lload_write_timeout = &timeout_write_tv;
+ } else {
+ lload_write_timeout = NULL;
+ }
+ break;
+ case CFG_MAXBUF_CLIENT:
+ sockbuf_max_incoming_client = c->value_uint;
+ break;
+ case CFG_MAXBUF_UPSTREAM:
+ sockbuf_max_incoming_upstream = c->value_uint;
+ break;
+ case CFG_CLIENT_PENDING:
+ lload_client_max_pending = c->value_uint;
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "%s: unknown CFG_TYPE %d\n",
+ c->log, c->type );
+ return 1;
+ }
+
+ lload_change.flags.daemon |= flag;
+
+ return 0;
+
+fail:
+ if ( lload_change.type == LLOAD_CHANGE_ADD ) {
+ /* Abort the ADD */
+ lload_change.type = LLOAD_CHANGE_DEL;
+ }
+
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+}
+
+static int
+lload_backend_finish( ConfigArgs *ca )
+{
+ LloadBackend *b = ca->ca_private;
+
+ if ( ca->reply.err != LDAP_SUCCESS ) {
+ /* Not reached since cleanup is only called on success */
+ goto fail;
+ }
+
+ if ( b->b_numconns <= 0 || b->b_numbindconns <= 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_backend_finish: "
+ "invalid connection pool configuration\n" );
+ goto fail;
+ }
+
+ if ( b->b_retry_timeout < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_backend_finish: "
+ "invalid retry timeout configuration\n" );
+ goto fail;
+ }
+
+ b->b_retry_tv.tv_sec = b->b_retry_timeout / 1000;
+ b->b_retry_tv.tv_usec = ( b->b_retry_timeout % 1000 ) * 1000;
+
+ /* daemon_base is only allocated after initial configuration happens, those
+ * events are allocated on startup, we only deal with online Adds */
+ if ( !b->b_retry_event && daemon_base ) {
+ struct event *event;
+ assert( CONFIG_ONLINE_ADD( ca ) );
+ event = evtimer_new( daemon_base, backend_connect, b );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "lload_backend_finish: "
+ "failed to allocate retry event\n" );
+ goto fail;
+ }
+ b->b_retry_event = event;
+ }
+
+ if ( BER_BVISEMPTY( &b->b_name ) ) {
+ struct berval bv;
+ LloadBackend *b2;
+ int i = 1;
+
+ LDAP_CIRCLEQ_FOREACH ( b2, &b->b_tier->t_backends, b_next ) {
+ i++;
+ }
+
+ bv.bv_val = ca->cr_msg;
+ bv.bv_len =
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "server %d", i );
+
+ ber_dupbv( &b->b_name, &bv );
+ }
+
+ if ( b->b_tier->t_type.tier_add_backend( b->b_tier, b ) ) {
+ goto fail;
+ }
+
+ return LDAP_SUCCESS;
+
+fail:
+ if ( lload_change.type == LLOAD_CHANGE_ADD ) {
+ /* Abort the ADD */
+ lload_change.type = LLOAD_CHANGE_DEL;
+ }
+
+ lload_backend_destroy( b );
+ return -1;
+}
+
+static int
+backend_config_url( LloadBackend *b, struct berval *uri )
+{
+ LDAPURLDesc *lud = NULL;
+ char *host = NULL;
+ int rc, proto, tls = b->b_tls_conf;
+
+ /* Effect no changes until we've checked everything */
+
+ rc = ldap_url_parse_ext( uri->bv_val, &lud, LDAP_PVT_URL_PARSE_DEF_PORT );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "backend_config_url: "
+ "listen URL \"%s\" parse error=%d\n",
+ uri->bv_val, rc );
+ return -1;
+ }
+
+ if ( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) {
+#ifdef HAVE_TLS
+ /* Specifying ldaps:// overrides starttls= settings */
+ tls = LLOAD_LDAPS;
+#else /* ! HAVE_TLS */
+
+ Debug( LDAP_DEBUG_ANY, "backend_config_url: "
+ "TLS not supported (%s)\n",
+ uri->bv_val );
+ rc = -1;
+ goto done;
+#endif /* ! HAVE_TLS */
+ }
+
+ proto = ldap_pvt_url_scheme2proto( lud->lud_scheme );
+ if ( proto == LDAP_PROTO_IPC ) {
+#ifdef LDAP_PF_LOCAL
+ if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) {
+ host = LDAPI_SOCK;
+ }
+#else /* ! LDAP_PF_LOCAL */
+
+ Debug( LDAP_DEBUG_ANY, "backend_config_url: "
+ "URL scheme not supported: %s",
+ url );
+ rc = -1;
+ goto done;
+#endif /* ! LDAP_PF_LOCAL */
+ } else {
+ if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) {
+ Debug( LDAP_DEBUG_ANY, "backend_config_url: "
+ "backend url missing hostname: '%s'\n",
+ uri->bv_val );
+ rc = -1;
+ goto done;
+ }
+ }
+ if ( !host ) {
+ host = lud->lud_host;
+ }
+
+ if ( b->b_host ) {
+ ch_free( b->b_host );
+ }
+
+ b->b_proto = proto;
+ b->b_tls = tls;
+ b->b_port = lud->lud_port;
+ b->b_host = ch_strdup( host );
+
+done:
+ ldap_free_urldesc( lud );
+ return rc;
+}
+
+static int
+config_backend( ConfigArgs *c )
+{
+ LloadBackend *b;
+ LloadTier *tier;
+ int i, rc = 0;
+
+ tier = LDAP_STAILQ_LAST( &tiers, LloadTier, t_next );
+ if ( !tier ) {
+ Debug( LDAP_DEBUG_ANY, "config_backend: "
+ "no tier configured yet\n" );
+ return -1;
+ }
+
+ /* FIXME: maybe tier_add_backend could allocate it? */
+ b = lload_backend_new();
+ b->b_tier = tier;
+
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( lload_backend_parse( c->argv[i], b ) ) {
+ if ( !tier->t_type.tier_backend_config ||
+ tier->t_type.tier_backend_config( tier, b, c->argv[i] ) ) {
+ Debug( LDAP_DEBUG_ANY, "config_backend: "
+ "error parsing backend configuration item '%s'\n",
+ c->argv[i] );
+ return -1;
+ }
+ }
+ }
+
+ if ( BER_BVISNULL( &b->b_uri ) ) {
+ Debug( LDAP_DEBUG_ANY, "config_backend: "
+ "backend address not specified\n" );
+ rc = -1;
+ goto done;
+ }
+
+ if ( backend_config_url( b, &b->b_uri ) ) {
+ rc = -1;
+ goto done;
+ }
+
+ c->ca_private = b;
+ rc = lload_backend_finish( c );
+done:
+ if ( rc ) {
+ ch_free( b );
+ }
+ return rc;
+}
+
+static int
+config_bindconf( ConfigArgs *c )
+{
+ int i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv;
+
+ lload_bindconf_unparse( &bindconf, &bv );
+
+ for ( i = 0; isspace( (unsigned char)bv.bv_val[i] ); i++ )
+ /* count spaces */;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[i], bv.bv_len + 1 );
+ }
+
+ value_add_one( &c->rvalue_vals, &bv );
+ ber_memfree( bv.bv_val );
+ return LDAP_SUCCESS;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* It's a MUST single-valued attribute, noop for now */
+ lload_bindconf_free( &bindconf );
+ return LDAP_SUCCESS;
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+ lload_change.flags.daemon |= LLOAD_DAEMON_MOD_BINDCONF;
+
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( lload_bindconf_parse( c->argv[i], &bindconf ) ) {
+ Debug( LDAP_DEBUG_ANY, "config_bindconf: "
+ "error parsing backend configuration item '%s'\n",
+ c->argv[i] );
+ return -1;
+ }
+ }
+
+ if ( bindconf.sb_method == LDAP_AUTH_SASL ) {
+#ifndef HAVE_CYRUS_SASL
+ Debug( LDAP_DEBUG_ANY, "config_bindconf: "
+ "no sasl support available\n" );
+ return -1;
+#endif
+ }
+
+ if ( !BER_BVISNULL( &bindconf.sb_authzId ) ) {
+ ber_bvreplace( &lloadd_identity, &bindconf.sb_authzId );
+ } else if ( !BER_BVISNULL( &bindconf.sb_authcId ) ) {
+ ber_bvreplace( &lloadd_identity, &bindconf.sb_authcId );
+ } else if ( !BER_BVISNULL( &bindconf.sb_binddn ) ) {
+ char *ptr;
+
+ lloadd_identity.bv_len = STRLENOF("dn:") + bindconf.sb_binddn.bv_len;
+ lloadd_identity.bv_val = ch_realloc(
+ lloadd_identity.bv_val, lloadd_identity.bv_len + 1 );
+
+ ptr = lutil_strcopy( lloadd_identity.bv_val, "dn:" );
+ ptr = lutil_strncopy(
+ ptr, bindconf.sb_binddn.bv_val, bindconf.sb_binddn.bv_len );
+ *ptr = '\0';
+ }
+
+ if ( bindconf.sb_timeout_api ) {
+ timeout_api_tv.tv_sec = bindconf.sb_timeout_api;
+ lload_timeout_api = &timeout_api_tv;
+ if ( lload_timeout_event ) {
+ event_add( lload_timeout_event, lload_timeout_api );
+ }
+ } else {
+ lload_timeout_api = NULL;
+ if ( lload_timeout_event ) {
+ event_del( lload_timeout_event );
+ }
+ }
+
+ if ( bindconf.sb_timeout_net ) {
+ timeout_net_tv.tv_sec = bindconf.sb_timeout_net;
+ lload_timeout_net = &timeout_net_tv;
+ } else {
+ lload_timeout_net = NULL;
+ }
+
+#ifdef HAVE_TLS
+ if ( bindconf.sb_tls_do_init ) {
+ lload_bindconf_tls_set( &bindconf, lload_tls_backend_ld );
+ }
+#endif /* HAVE_TLS */
+ return 0;
+}
+
+#ifndef BALANCER_MODULE
+char *
+oidm_find( char *oid )
+{
+ if ( OID_LEADCHAR( *oid ) ) {
+ return oid;
+ }
+ Debug( LDAP_DEBUG_ANY, "oidm_find: "
+ "full OID parsing only available when compiled as a module\n" );
+ return NULL;
+}
+#endif /* !BALANCER_MODULE */
+
+static struct {
+ const char *name;
+ enum op_restriction action;
+} restrictopts[] = {
+ { "ignore", LLOAD_OP_NOT_RESTRICTED },
+ { "write", LLOAD_OP_RESTRICTED_WRITE },
+ { "backend", LLOAD_OP_RESTRICTED_BACKEND },
+ { "connection", LLOAD_OP_RESTRICTED_UPSTREAM },
+ { "isolate", LLOAD_OP_RESTRICTED_ISOLATE },
+ { "reject", LLOAD_OP_RESTRICTED_REJECT },
+ { NULL }
+};
+
+void
+lload_restriction_free( struct restriction_entry *restriction )
+{
+ ch_free( restriction->oid.bv_val );
+ ch_free( restriction );
+}
+
+static int
+config_restrict_oid( ConfigArgs *c )
+{
+ TAvlnode *node = NULL, **root = ( c->type == CFG_RESTRICT_EXOP ) ?
+ &lload_exop_actions :
+ &lload_control_actions;
+ struct restriction_entry *entry = NULL;
+ char *parsed_oid;
+ int i, rc = -1;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = { .bv_val = c->cr_msg };
+
+ if ( c->type == CFG_RESTRICT_EXOP && lload_default_exop_action ) {
+ bv.bv_len = snprintf( bv.bv_val, sizeof(c->cr_msg), "1.1 %s",
+ restrictopts[lload_default_exop_action].name );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ for ( node = ldap_tavl_end( *root, TAVL_DIR_LEFT );
+ node;
+ node = ldap_tavl_next( node, TAVL_DIR_RIGHT ) ) {
+ entry = node->avl_data;
+
+ bv.bv_len = snprintf( bv.bv_val, sizeof(c->cr_msg), "%s %s",
+ entry->oid.bv_val, restrictopts[entry->action].name );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+
+ return LDAP_SUCCESS;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ ldap_tavl_free( *root, (AVL_FREE)lload_restriction_free );
+ *root = NULL;
+ if ( c->type == CFG_RESTRICT_EXOP ) {
+ lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED;
+ }
+ rc = LDAP_SUCCESS;
+ } else {
+ struct restriction_entry needle;
+
+ parsed_oid = strchr( c->line, ' ' );
+ if ( !parsed_oid ) {
+ return rc;
+ }
+
+ memcpy( c->cr_msg, c->line, parsed_oid - c->line );
+ c->cr_msg[parsed_oid - c->line] = '\0';
+
+ needle.oid.bv_val = oidm_find( c->cr_msg );
+ needle.oid.bv_len = strlen( needle.oid.bv_val );
+
+ if ( !needle.oid.bv_val ) {
+ return rc;
+ } else if ( c->type == CFG_RESTRICT_EXOP &&
+ !strcmp( needle.oid.bv_val, "1.1" ) ) {
+ lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED;
+ } else {
+ /* back-config should have checked we have this value */
+ entry = ldap_tavl_delete( root, &needle,
+ lload_restriction_cmp );
+ assert( entry != NULL );
+ }
+ rc = LDAP_SUCCESS;
+ }
+ return rc;
+ }
+
+ parsed_oid = oidm_find( c->argv[1] );
+ if ( !parsed_oid ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "Could not parse oid %s",
+ c->argv[1] );
+ goto done;
+ }
+
+ for ( i = 0; restrictopts[i].name; i++ ) {
+ if ( !strcasecmp( c->argv[2], restrictopts[i].name ) ) {
+ break;
+ }
+ }
+
+ if ( !restrictopts[i].name ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "Could not parse action %s",
+ c->argv[2] );
+ goto done;
+ }
+
+ if ( !strcmp( parsed_oid, "1.1" ) ) {
+ if ( lload_default_exop_action ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "Default already set" );
+ goto done;
+ } else {
+ lload_default_exop_action = i;
+ }
+ }
+
+ entry = ch_malloc( sizeof(struct restriction_entry) );
+ /* Copy only if a reference to argv[1] was returned */
+ ber_str2bv( parsed_oid, 0, parsed_oid == c->argv[1], &entry->oid );
+ entry->action = i;
+
+ if ( ldap_tavl_insert( root, entry, lload_restriction_cmp,
+ ldap_avl_dup_error ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "%s with OID %s already restricted",
+ c->type == CFG_RESTRICT_EXOP ? "Extended operation" : "Control",
+ c->argv[1] );
+ goto done;
+ }
+
+ rc = LDAP_SUCCESS;
+done:
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ if ( parsed_oid ) ch_free( parsed_oid );
+ if ( entry ) ch_free( entry );
+ }
+
+ return rc;
+}
+
+static int
+config_tier( ConfigArgs *c )
+{
+ int rc = LDAP_SUCCESS;
+ struct lload_tier_type *tier_impl;
+ LloadTier *tier = c->ca_private;
+ struct berval bv;
+ int i = 1;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch ( c->type ) {
+ case CFG_TIER:
+ c->value_string = ch_strdup( tier->t_type.tier_name );
+ break;
+ default:
+ goto fail;
+ break;
+ }
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( lload_change.type != LLOAD_CHANGE_DEL ) {
+ /*
+ * TODO: Shouldn't really happen while this attribute is in the
+ * RDN, but we don't enforce it yet.
+ *
+ * How would we go about changing the backend type if we ever supported that?
+ */
+ goto fail;
+ }
+ return rc;
+ }
+
+ if ( CONFIG_ONLINE_ADD( c ) ) {
+ assert( tier );
+ lload_change.target = tier;
+ ch_free( c->value_string );
+ return rc;
+ }
+
+ tier_impl = lload_tier_find( c->value_string );
+ ch_free( c->value_string );
+ if ( !tier_impl ) {
+ goto fail;
+ }
+ tier = tier_impl->tier_init();
+ if ( !tier ) {
+ goto fail;
+ }
+
+ lload_change.target = tier;
+
+ if ( LDAP_STAILQ_EMPTY( &tiers ) ) {
+ LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next );
+ } else {
+ LloadTier *tier2;
+ LDAP_STAILQ_FOREACH ( tier2, &tiers, t_next ) {
+ i++;
+ }
+ LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next );
+ }
+
+ bv.bv_val = c->cr_msg;
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "tier %d", i );
+ ber_dupbv( &tier->t_name, &bv );
+
+ return rc;
+
+fail:
+ if ( lload_change.type == LLOAD_CHANGE_ADD ) {
+ /* Abort the ADD */
+ lload_change.type = LLOAD_CHANGE_DEL;
+ }
+ return 1;
+}
+
+static int
+config_fname( ConfigArgs *c )
+{
+ return 0;
+}
+
+/*
+ * [listener=<listener>] [{read|write}=]<size>
+ */
+
+#ifdef LDAP_TCP_BUFFER
+static BerVarray tcp_buffer;
+static int tcp_buffer_num;
+
+#define SLAP_TCP_RMEM ( 0x1U )
+#define SLAP_TCP_WMEM ( 0x2U )
+
+static int
+tcp_buffer_parse(
+ struct berval *val,
+ int argc,
+ char **argv,
+ int *size,
+ int *rw,
+ LloadListener **l )
+{
+ int i, rc = LDAP_SUCCESS;
+ LDAPURLDesc *lud = NULL;
+ char *ptr;
+
+ if ( val != NULL && argv == NULL ) {
+ char *s = val->bv_val;
+
+ argv = ldap_str2charray( s, " \t" );
+ if ( argv == NULL ) {
+ return LDAP_OTHER;
+ }
+ }
+
+ i = 0;
+ if ( strncasecmp( argv[i], "listener=", STRLENOF("listener=") ) == 0 ) {
+ char *url = argv[i] + STRLENOF("listener=");
+
+ if ( ldap_url_parse_ext( url, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ *l = lload_config_check_my_url( url, lud );
+ if ( *l == NULL ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ i++;
+ }
+
+ ptr = argv[i];
+ if ( strncasecmp( ptr, "read=", STRLENOF("read=") ) == 0 ) {
+ *rw |= SLAP_TCP_RMEM;
+ ptr += STRLENOF("read=");
+
+ } else if ( strncasecmp( ptr, "write=", STRLENOF("write=") ) == 0 ) {
+ *rw |= SLAP_TCP_WMEM;
+ ptr += STRLENOF("write=");
+
+ } else {
+ *rw |= ( SLAP_TCP_RMEM | SLAP_TCP_WMEM );
+ }
+
+ /* accept any base */
+ if ( lutil_atoix( size, ptr, 0 ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+done:;
+ if ( val != NULL && argv != NULL ) {
+ ldap_charray_free( argv );
+ }
+
+ if ( lud != NULL ) {
+ ldap_free_urldesc( lud );
+ }
+
+ return rc;
+}
+
+#ifdef BALANCER_MODULE
+static int
+tcp_buffer_delete_one( struct berval *val )
+{
+ int rc = 0;
+ int size = -1, rw = 0;
+ LloadListener *l = NULL;
+
+ rc = tcp_buffer_parse( val, 0, NULL, &size, &rw, &l );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ if ( l != NULL ) {
+ int i;
+ LloadListener **ll = lloadd_get_listeners();
+
+ for ( i = 0; ll[i] != NULL; i++ ) {
+ if ( ll[i] == l ) break;
+ }
+
+ if ( ll[i] == NULL ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = -1;
+
+ for ( i++; ll[i] != NULL && bvmatch( &l->sl_url, &ll[i]->sl_url );
+ i++ ) {
+ if ( rw & SLAP_TCP_RMEM ) ll[i]->sl_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) ll[i]->sl_tcp_wmem = -1;
+ }
+
+ } else {
+ /* NOTE: this affects listeners without a specific setting,
+ * does not reset all listeners. If a listener without
+ * specific settings was assigned a buffer because of
+ * a global setting, it will not be reset. In any case,
+ * buffer changes will only take place at restart. */
+ if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = -1;
+ }
+
+ return rc;
+}
+
+static int
+tcp_buffer_delete( BerVarray vals )
+{
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ ) {
+ tcp_buffer_delete_one( &vals[i] );
+ }
+
+ return 0;
+}
+#endif /* BALANCER_MODULE */
+
+static int
+tcp_buffer_unparse( int size, int rw, LloadListener *l, struct berval *val )
+{
+ char buf[sizeof("2147483648")], *ptr;
+
+ /* unparse for later use */
+ val->bv_len = snprintf( buf, sizeof(buf), "%d", size );
+ if ( l != NULL ) {
+ val->bv_len += STRLENOF( "listener="
+ " " ) +
+ l->sl_url.bv_len;
+ }
+
+ if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) {
+ if ( rw & SLAP_TCP_RMEM ) {
+ val->bv_len += STRLENOF("read=");
+ } else if ( rw & SLAP_TCP_WMEM ) {
+ val->bv_len += STRLENOF("write=");
+ }
+ }
+
+ val->bv_val = SLAP_MALLOC( val->bv_len + 1 );
+
+ ptr = val->bv_val;
+
+ if ( l != NULL ) {
+ ptr = lutil_strcopy( ptr, "listener=" );
+ ptr = lutil_strncopy( ptr, l->sl_url.bv_val, l->sl_url.bv_len );
+ *ptr++ = ' ';
+ }
+
+ if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) {
+ if ( rw & SLAP_TCP_RMEM ) {
+ ptr = lutil_strcopy( ptr, "read=" );
+ } else if ( rw & SLAP_TCP_WMEM ) {
+ ptr = lutil_strcopy( ptr, "write=" );
+ }
+ }
+
+ ptr = lutil_strcopy( ptr, buf );
+ *ptr = '\0';
+
+ assert( val->bv_val + val->bv_len == ptr );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+tcp_buffer_add_one( int argc, char **argv )
+{
+ int rc = 0;
+ int size = -1, rw = 0;
+ LloadListener *l = NULL;
+
+ struct berval val;
+
+ /* parse */
+ rc = tcp_buffer_parse( NULL, argc, argv, &size, &rw, &l );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ /* unparse for later use */
+ rc = tcp_buffer_unparse( size, rw, l, &val );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ /* use parsed values */
+ if ( l != NULL ) {
+ int i;
+ LloadListener **ll = lloadd_get_listeners();
+
+ for ( i = 0; ll[i] != NULL; i++ ) {
+ if ( ll[i] == l ) break;
+ }
+
+ if ( ll[i] == NULL ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ /* buffer only applies to TCP listeners;
+ * we do not do any check here, and delegate them
+ * to setsockopt(2) */
+ if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = size;
+
+ for ( i++; ll[i] != NULL && bvmatch( &l->sl_url, &ll[i]->sl_url );
+ i++ ) {
+ if ( rw & SLAP_TCP_RMEM ) ll[i]->sl_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) ll[i]->sl_tcp_wmem = size;
+ }
+
+ } else {
+ /* NOTE: this affects listeners without a specific setting,
+ * does not set all listeners */
+ if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = size;
+ }
+
+ tcp_buffer = SLAP_REALLOC(
+ tcp_buffer, sizeof(struct berval) * ( tcp_buffer_num + 2 ) );
+ /* append */
+ tcp_buffer[tcp_buffer_num] = val;
+
+ tcp_buffer_num++;
+ BER_BVZERO( &tcp_buffer[tcp_buffer_num] );
+
+ return rc;
+}
+
+static int
+config_tcp_buffer( ConfigArgs *c )
+{
+ int rc = LDAP_SUCCESS;
+
+#ifdef BALANCER_MODULE
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( tcp_buffer == NULL || BER_BVISNULL( &tcp_buffer[0] ) ) {
+ return 1;
+ }
+ value_add( &c->rvalue_vals, tcp_buffer );
+ value_add( &c->rvalue_nvals, tcp_buffer );
+
+ return 0;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ tcp_buffer_delete( tcp_buffer );
+ ber_bvarray_free( tcp_buffer );
+ tcp_buffer = NULL;
+ tcp_buffer_num = 0;
+
+ } else {
+ int size = -1, rw = 0;
+ LloadListener *l = NULL;
+
+ struct berval val = BER_BVNULL;
+
+ int i;
+
+ if ( tcp_buffer_num == 0 ) {
+ return 1;
+ }
+
+ /* parse */
+ rc = tcp_buffer_parse(
+ NULL, c->argc - 1, &c->argv[1], &size, &rw, &l );
+ if ( rc != 0 ) {
+ return 1;
+ }
+
+ /* unparse for later use */
+ rc = tcp_buffer_unparse( size, rw, l, &val );
+ if ( rc != LDAP_SUCCESS ) {
+ return 1;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &tcp_buffer[i] ); i++ ) {
+ if ( bvmatch( &tcp_buffer[i], &val ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &tcp_buffer[i] ) ) {
+ /* not found */
+ rc = 1;
+ goto done;
+ }
+
+ tcp_buffer_delete_one( &tcp_buffer[i] );
+ ber_memfree( tcp_buffer[i].bv_val );
+ for ( ; i < tcp_buffer_num; i++ ) {
+ tcp_buffer[i] = tcp_buffer[i + 1];
+ }
+ tcp_buffer_num--;
+
+done:;
+ if ( !BER_BVISNULL( &val ) ) {
+ SLAP_FREE(val.bv_val);
+ }
+ }
+
+ return rc;
+ }
+#endif /* BALANCER_MODULE */
+
+ rc = tcp_buffer_add_one( c->argc - 1, &c->argv[1] );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> unable to add value #%d",
+ c->argv[0], tcp_buffer_num );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* LDAP_TCP_BUFFER */
+
+static int
+config_restrict( ConfigArgs *c )
+{
+ slap_mask_t restrictops = 0;
+ int i;
+ slap_verbmasks restrictable_ops[] = {
+ { BER_BVC("bind"), SLAP_RESTRICT_OP_BIND },
+ { BER_BVC("add"), SLAP_RESTRICT_OP_ADD },
+ { BER_BVC("modify"), SLAP_RESTRICT_OP_MODIFY },
+ { BER_BVC("rename"), SLAP_RESTRICT_OP_RENAME },
+ { BER_BVC("modrdn"), 0 },
+ { BER_BVC("delete"), SLAP_RESTRICT_OP_DELETE },
+ { BER_BVC("search"), SLAP_RESTRICT_OP_SEARCH },
+ { BER_BVC("compare"), SLAP_RESTRICT_OP_COMPARE },
+ { BER_BVC("read"), SLAP_RESTRICT_OP_READS },
+ { BER_BVC("write"), SLAP_RESTRICT_OP_WRITES },
+ { BER_BVC("extended"), SLAP_RESTRICT_OP_EXTENDED },
+ { BER_BVC("extended=" LDAP_EXOP_START_TLS), SLAP_RESTRICT_EXOP_START_TLS },
+ { BER_BVC("extended=" LDAP_EXOP_MODIFY_PASSWD), SLAP_RESTRICT_EXOP_MODIFY_PASSWD },
+ { BER_BVC("extended=" LDAP_EXOP_X_WHO_AM_I), SLAP_RESTRICT_EXOP_WHOAMI },
+ { BER_BVC("extended=" LDAP_EXOP_X_CANCEL), SLAP_RESTRICT_EXOP_CANCEL },
+ { BER_BVC("all"), SLAP_RESTRICT_OP_ALL },
+ { BER_BVNULL, 0 }
+ };
+
+ i = verbs_to_mask( c->argc, c->argv, restrictable_ops, &restrictops );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> unknown operation",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i] );
+ return 1;
+ }
+ if ( restrictops & SLAP_RESTRICT_OP_EXTENDED )
+ restrictops &= ~SLAP_RESTRICT_EXOP_MASK;
+ return 0;
+}
+
+static int
+config_include( ConfigArgs *c )
+{
+ int savelineno = c->lineno;
+ int rc;
+ ConfigFile *cf;
+ ConfigFile *cfsave = cfn;
+ ConfigFile *cf2 = NULL;
+
+ /* Leftover from RE23. No dynamic config for include files */
+ if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) return 1;
+
+ cf = ch_calloc( 1, sizeof(ConfigFile) );
+ if ( cfn->c_kids ) {
+ for ( cf2 = cfn->c_kids; cf2 && cf2->c_sibs; cf2 = cf2->c_sibs )
+ /* empty */;
+ cf2->c_sibs = cf;
+ } else {
+ cfn->c_kids = cf;
+ }
+ cfn = cf;
+ ber_str2bv( c->argv[1], 0, 1, &cf->c_file );
+ rc = lload_read_config_file(
+ c->argv[1], c->depth + 1, c, config_back_cf_table );
+ c->lineno = savelineno - 1;
+ cfn = cfsave;
+ if ( rc ) {
+ if ( cf2 )
+ cf2->c_sibs = NULL;
+ else
+ cfn->c_kids = NULL;
+ ch_free( cf->c_file.bv_val );
+ ch_free( cf );
+ } else {
+ c->ca_private = cf;
+ }
+ return rc;
+}
+
+static int
+config_feature( ConfigArgs *c )
+{
+ slap_verbmasks features[] = {
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ { BER_BVC("vc"), LLOAD_FEATURE_VC },
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ { BER_BVC("proxyauthz"), LLOAD_FEATURE_PROXYAUTHZ },
+ { BER_BVC("read_pause"), LLOAD_FEATURE_PAUSE },
+ { BER_BVNULL, 0 }
+ };
+ slap_mask_t mask = 0;
+ int i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ return mask_to_verbs( features, lload_features, &c->rvalue_vals );
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+ lload_change.flags.daemon |= LLOAD_DAEMON_MOD_FEATURES;
+ if ( !lload_change.target ) {
+ lload_change.target = (void *)(uintptr_t)~lload_features;
+ }
+
+ if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ /* Last value has been deleted */
+ lload_features = 0;
+ } else {
+ i = verb_to_mask( c->line, features );
+ lload_features &= ~features[i].mask;
+ }
+ return 0;
+ }
+
+ i = verbs_to_mask( c->argc, c->argv, features, &mask );
+ if ( i ) {
+ Debug( LDAP_DEBUG_ANY, "%s: <%s> unknown feature %s\n", c->log,
+ c->argv[0], c->argv[i] );
+ return 1;
+ }
+
+ if ( mask & ~LLOAD_FEATURE_SUPPORTED_MASK ) {
+ for ( i = 1; i < c->argc; i++ ) {
+ int j = verb_to_mask( c->argv[i], features );
+ if ( features[j].mask & ~LLOAD_FEATURE_SUPPORTED_MASK ) {
+ Debug( LDAP_DEBUG_ANY, "%s: <%s> "
+ "experimental feature %s is undocumented, unsupported "
+ "and can change or disappear at any time!\n",
+ c->log, c->argv[0], c->argv[i] );
+ }
+ }
+ }
+
+ lload_features |= mask;
+ return 0;
+}
+
+#ifdef HAVE_TLS
+static int
+config_tls_cleanup( ConfigArgs *c )
+{
+ int rc = 0;
+
+ if ( lload_tls_ld ) {
+ int opt = 1;
+
+ ldap_pvt_tls_ctx_free( lload_tls_ctx );
+ lload_tls_ctx = NULL;
+
+ /* Force new ctx to be created */
+ rc = ldap_pvt_tls_set_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if ( rc == 0 ) {
+ /* The ctx's refcount is bumped up here */
+ ldap_pvt_tls_get_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_CTX, &lload_tls_ctx );
+ } else {
+ if ( rc == LDAP_NOT_SUPPORTED )
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ else
+ rc = LDAP_OTHER;
+ }
+ }
+ return rc;
+}
+
+static int
+config_tls_option( ConfigArgs *c )
+{
+ int flag;
+ int berval = 0;
+ LDAP *ld = lload_tls_ld;
+
+ switch ( c->type ) {
+ case CFG_TLS_RAND:
+ flag = LDAP_OPT_X_TLS_RANDOM_FILE;
+ ld = NULL;
+ break;
+ case CFG_TLS_CIPHER:
+ flag = LDAP_OPT_X_TLS_CIPHER_SUITE;
+ break;
+ case CFG_TLS_CERT_FILE:
+ flag = LDAP_OPT_X_TLS_CERTFILE;
+ break;
+ case CFG_TLS_CERT_KEY:
+ flag = LDAP_OPT_X_TLS_KEYFILE;
+ break;
+ case CFG_TLS_CA_PATH:
+ flag = LDAP_OPT_X_TLS_CACERTDIR;
+ break;
+ case CFG_TLS_CA_FILE:
+ flag = LDAP_OPT_X_TLS_CACERTFILE;
+ break;
+ case CFG_TLS_DH_FILE:
+ flag = LDAP_OPT_X_TLS_DHFILE;
+ break;
+ case CFG_TLS_ECNAME:
+ flag = LDAP_OPT_X_TLS_ECNAME;
+ break;
+#ifdef HAVE_GNUTLS
+ case CFG_TLS_CRL_FILE:
+ flag = LDAP_OPT_X_TLS_CRLFILE;
+ break;
+#endif
+ case CFG_TLS_CACERT:
+ flag = LDAP_OPT_X_TLS_CACERT;
+ berval = 1;
+ break;
+ case CFG_TLS_CERT:
+ flag = LDAP_OPT_X_TLS_CERT;
+ berval = 1;
+ break;
+ case CFG_TLS_KEY:
+ flag = LDAP_OPT_X_TLS_KEY;
+ berval = 1;
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "unknown tls_option <0x%x>\n",
+ c->log, c->type );
+ return 1;
+ }
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ return ldap_pvt_tls_get_option( ld, flag,
+ berval ? (void *)&c->value_bv : (void *)&c->value_string );
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+ lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS;
+
+ config_push_cleanup( c, config_tls_cleanup );
+ if ( c->op == LDAP_MOD_DELETE ) {
+ return ldap_pvt_tls_set_option( ld, flag, NULL );
+ }
+ if ( !berval ) ch_free( c->value_string );
+ return ldap_pvt_tls_set_option(
+ ld, flag, berval ? (void *)&c->value_bv : (void *)c->argv[1] );
+}
+
+/* FIXME: this ought to be provided by libldap */
+static int
+config_tls_config( ConfigArgs *c )
+{
+ int i, flag;
+
+ switch ( c->type ) {
+ case CFG_TLS_CRLCHECK:
+ flag = LDAP_OPT_X_TLS_CRLCHECK;
+ break;
+ case CFG_TLS_VERIFY:
+ flag = LDAP_OPT_X_TLS_REQUIRE_CERT;
+ break;
+ case CFG_TLS_PROTOCOL_MIN:
+ flag = LDAP_OPT_X_TLS_PROTOCOL_MIN;
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "unknown tls_option <0x%x>\n",
+ c->log, c->type );
+ return 1;
+ }
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ return lload_tls_get_config( lload_tls_ld, flag, &c->value_string );
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+ lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS;
+
+ config_push_cleanup( c, config_tls_cleanup );
+ if ( c->op == LDAP_MOD_DELETE ) {
+ int i = 0;
+ return ldap_pvt_tls_set_option( lload_tls_ld, flag, &i );
+ }
+ ch_free( c->value_string );
+ if ( isdigit( (unsigned char)c->argv[1][0] ) &&
+ c->type != CFG_TLS_PROTOCOL_MIN ) {
+ if ( lutil_atoi( &i, c->argv[1] ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "unable to parse %s \"%s\"\n",
+ c->log, c->argv[0], c->argv[1] );
+ return 1;
+ }
+ return ldap_pvt_tls_set_option( lload_tls_ld, flag, &i );
+ } else {
+ return ldap_pvt_tls_config( lload_tls_ld, flag, c->argv[1] );
+ }
+}
+#endif
+
+#ifdef BALANCER_MODULE
+static int
+config_share_tls_ctx( ConfigArgs *c )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ c->value_int = lload_use_slap_tls_ctx;
+ return rc;
+ }
+
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ lload_change.object = LLOAD_DAEMON;
+ lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS;
+
+ if ( c->op == LDAP_MOD_DELETE ) {
+ lload_use_slap_tls_ctx = 0;
+ return rc;
+ }
+
+ lload_use_slap_tls_ctx = c->value_int;
+ return rc;
+}
+#endif /* BALANCER_MODULE */
+
+void
+lload_init_config_argv( ConfigArgs *c )
+{
+ c->argv = ch_calloc( ARGS_STEP + 1, sizeof(*c->argv) );
+ c->argv_size = ARGS_STEP + 1;
+}
+
+ConfigTable *
+lload_config_find_keyword( ConfigTable *Conf, ConfigArgs *c )
+{
+ int i;
+
+ for ( i = 0; Conf[i].name; i++ )
+ if ( ( Conf[i].length &&
+ ( !strncasecmp(
+ c->argv[0], Conf[i].name, Conf[i].length ) ) ) ||
+ ( !strcasecmp( c->argv[0], Conf[i].name ) ) )
+ break;
+ if ( !Conf[i].name ) return NULL;
+ if ( (Conf[i].arg_type & ARGS_TYPES) == ARG_BINARY ) {
+ size_t decode_len = LUTIL_BASE64_DECODE_LEN( c->linelen );
+ ch_free( c->tline );
+ c->tline = ch_malloc( decode_len + 1 );
+ c->linelen = lutil_b64_pton( c->line, c->tline, decode_len );
+ if ( c->linelen < 0 ) {
+ ch_free( c->tline );
+ c->tline = NULL;
+ return NULL;
+ }
+ c->line = c->tline;
+ }
+ c->ca_desc = Conf + i;
+ return c->ca_desc;
+}
+
+int
+lload_config_check_vals( ConfigTable *Conf, ConfigArgs *c, int check_only )
+{
+ int arg_user, arg_type, arg_syn, iarg;
+ unsigned uiarg;
+ long larg;
+ unsigned long ularg;
+ ber_len_t barg;
+
+ if ( Conf->arg_type == ARG_IGNORED ) {
+ Debug( LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n",
+ c->log, Conf->name );
+ return 0;
+ }
+ arg_type = Conf->arg_type & ARGS_TYPES;
+ arg_user = Conf->arg_type & ARGS_USERLAND;
+ arg_syn = Conf->arg_type & ARGS_SYNTAX;
+
+ if ( Conf->min_args && ( c->argc < Conf->min_args ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> missing <%s> argument",
+ c->argv[0], Conf->what ? Conf->what : "" );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( Conf->max_args && ( c->argc > Conf->max_args ) ) {
+ char *ignored = " ignored";
+
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> extra cruft after <%s>",
+ c->argv[0], Conf->what );
+
+ ignored = "";
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s%s\n",
+ c->log, c->cr_msg, ignored );
+ return ARG_BAD_CONF;
+ }
+ if ( (arg_syn & ARG_PAREN) && *c->argv[1] != '(' /*')'*/ ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> old format not supported",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( arg_type && !Conf->arg_item && !(arg_syn & ARG_OFFSET) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> invalid config_table, arg_item is NULL",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ c->type = arg_user;
+ memset( &c->values, 0, sizeof(c->values) );
+ if ( arg_type == ARG_STRING ) {
+ assert( c->argc == 2 );
+ if ( !check_only ) c->value_string = ch_strdup( c->argv[1] );
+ } else if ( arg_type == ARG_BERVAL ) {
+ assert( c->argc == 2 );
+ if ( !check_only ) ber_str2bv( c->argv[1], 0, 1, &c->value_bv );
+ } else if ( arg_type == ARG_BINARY ) {
+ assert( c->argc == 2 );
+ if ( !check_only ) {
+ c->value_bv.bv_len = c->linelen;
+ c->value_bv.bv_val = ch_malloc( c->linelen );
+ AC_MEMCPY( c->value_bv.bv_val, c->line, c->linelen );
+ }
+ } else { /* all numeric */
+ int j;
+ iarg = 0;
+ larg = 0;
+ barg = 0;
+ switch ( arg_type ) {
+ case ARG_INT:
+ assert( c->argc == 2 );
+ if ( lutil_atoix( &iarg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> unable to parse \"%s\" as int",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ break;
+ case ARG_UINT:
+ assert( c->argc == 2 );
+ if ( lutil_atoux( &uiarg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> unable to parse \"%s\" as unsigned int",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ break;
+ case ARG_LONG:
+ assert( c->argc == 2 );
+ if ( lutil_atolx( &larg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> unable to parse \"%s\" as long",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ break;
+ case ARG_ULONG:
+ assert( c->argc == 2 );
+ if ( LUTIL_ATOULX( &ularg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> unable to parse \"%s\" as unsigned long",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ break;
+ case ARG_BER_LEN_T: {
+ unsigned long l;
+ assert( c->argc == 2 );
+ if ( lutil_atoulx( &l, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> unable to parse \"%s\" as ber_len_t",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ barg = (ber_len_t)l;
+ } break;
+ case ARG_ON_OFF:
+ /* note: this is an explicit exception
+ * to the "need exactly 2 args" rule */
+ if ( c->argc == 1 ) {
+ iarg = 1;
+ } else if ( !strcasecmp( c->argv[1], "on" ) ||
+ !strcasecmp( c->argv[1], "true" ) ||
+ !strcasecmp( c->argv[1], "yes" ) ) {
+ iarg = 1;
+ } else if ( !strcasecmp( c->argv[1], "off" ) ||
+ !strcasecmp( c->argv[1], "false" ) ||
+ !strcasecmp( c->argv[1], "no" ) ) {
+ iarg = 0;
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> invalid value",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ break;
+ }
+ j = (arg_type & ARG_NONZERO) ? 1 : 0;
+ if ( iarg < j && larg < j && barg < (unsigned)j ) {
+ larg = larg ? larg : ( barg ? (long)barg : iarg );
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> invalid value",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ switch ( arg_type ) {
+ case ARG_ON_OFF:
+ case ARG_INT:
+ c->value_int = iarg;
+ break;
+ case ARG_UINT:
+ c->value_uint = uiarg;
+ break;
+ case ARG_LONG:
+ c->value_long = larg;
+ break;
+ case ARG_ULONG:
+ c->value_ulong = ularg;
+ break;
+ case ARG_BER_LEN_T:
+ c->value_ber_t = barg;
+ break;
+ }
+ }
+ return 0;
+}
+
+int
+lload_config_set_vals( ConfigTable *Conf, ConfigArgs *c )
+{
+ int rc, arg_type;
+ void *ptr = NULL;
+
+ arg_type = Conf->arg_type;
+ if ( arg_type & ARG_MAGIC ) {
+ c->cr_msg[0] = '\0';
+ rc = ( *( (ConfigDriver *)Conf->arg_item ) )( c );
+ if ( rc ) {
+ if ( !c->cr_msg[0] ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> handler exited with %d",
+ c->argv[0], rc );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s!\n", c->log, c->cr_msg );
+ }
+ return ARG_BAD_CONF;
+ }
+ return 0;
+ }
+ if ( arg_type & ARG_OFFSET ) {
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "<%s> offset is missing base pointer",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s!\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ ptr = (void *)( (char *)ptr + (long)Conf->arg_item );
+ } else if ( arg_type & ARGS_TYPES ) {
+ ptr = Conf->arg_item;
+ }
+ if ( arg_type & ARGS_TYPES ) switch ( arg_type & ARGS_TYPES ) {
+ case ARG_ON_OFF:
+ case ARG_INT:
+ *(int *)ptr = c->value_int;
+ break;
+ case ARG_UINT:
+ *(unsigned *)ptr = c->value_uint;
+ break;
+ case ARG_LONG:
+ *(long *)ptr = c->value_long;
+ break;
+ case ARG_ULONG:
+ *(size_t *)ptr = c->value_ulong;
+ break;
+ case ARG_BER_LEN_T:
+ *(ber_len_t *)ptr = c->value_ber_t;
+ break;
+ case ARG_STRING: {
+ char *cc = *(char **)ptr;
+ if ( cc ) {
+ if ( (arg_type & ARG_UNIQUE) &&
+ c->op == SLAP_CONFIG_ADD ) {
+ Debug( LDAP_DEBUG_CONFIG, "%s: already set %s!\n",
+ c->log, Conf->name );
+ return ARG_BAD_CONF;
+ }
+ ch_free( cc );
+ }
+ *(char **)ptr = c->value_string;
+ break;
+ }
+ case ARG_BERVAL:
+ case ARG_BINARY:
+ *(struct berval *)ptr = c->value_bv;
+ break;
+ }
+ return 0;
+}
+
+int
+lload_config_add_vals( ConfigTable *Conf, ConfigArgs *c )
+{
+ int rc, arg_type;
+
+ arg_type = Conf->arg_type;
+ if ( arg_type == ARG_IGNORED ) {
+ Debug( LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n",
+ c->log, Conf->name );
+ return 0;
+ }
+ rc = lload_config_check_vals( Conf, c, 0 );
+ if ( rc ) return rc;
+ return lload_config_set_vals( Conf, c );
+}
+
+int
+lload_read_config_file(
+ const char *fname,
+ int depth,
+ ConfigArgs *cf,
+ ConfigTable *cft )
+{
+ FILE *fp;
+ ConfigTable *ct;
+ ConfigArgs *c;
+ int rc;
+ struct stat s;
+
+ c = ch_calloc( 1, sizeof(ConfigArgs) );
+ if ( c == NULL ) {
+ return 1;
+ }
+
+ if ( depth ) {
+ memcpy( c, cf, sizeof(ConfigArgs) );
+ } else {
+ c->depth = depth; /* XXX */
+ }
+
+ c->valx = -1;
+ c->fname = fname;
+ lload_init_config_argv( c );
+
+ if ( stat( fname, &s ) != 0 ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ ldap_syslog = 1;
+ Debug( LDAP_DEBUG_ANY, "could not stat config file \"%s\": %s (%d)\n",
+ fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ),
+ saved_errno );
+ ch_free( c->argv );
+ ch_free( c );
+ return 1;
+ }
+
+ if ( !S_ISREG(s.st_mode) ) {
+ ldap_syslog = 1;
+ Debug( LDAP_DEBUG_ANY, "regular file expected, got \"%s\"\n", fname );
+ ch_free( c->argv );
+ ch_free( c );
+ return 1;
+ }
+
+ fp = fopen( fname, "r" );
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ ldap_syslog = 1;
+ Debug( LDAP_DEBUG_ANY, "could not open config file \"%s\": %s (%d)\n",
+ fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ),
+ saved_errno );
+ ch_free( c->argv );
+ ch_free( c );
+ return 1;
+ }
+
+ Debug( LDAP_DEBUG_CONFIG, "reading config file %s\n", fname );
+
+ fp_getline_init( c );
+
+ c->tline = NULL;
+
+ while ( fp_getline( fp, c ) ) {
+ /* skip comments and blank lines */
+ if ( c->line[0] == '#' || c->line[0] == '\0' ) {
+ continue;
+ }
+
+ snprintf( c->log, sizeof(c->log), "%s: line %d",
+ c->fname, c->lineno );
+
+ c->argc = 0;
+ ch_free( c->tline );
+ if ( lload_config_fp_parse_line( c ) ) {
+ rc = 1;
+ goto done;
+ }
+
+ if ( c->argc < 1 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: bad config line\n", c->log );
+ rc = 1;
+ goto done;
+ }
+
+ c->op = SLAP_CONFIG_ADD;
+
+ ct = lload_config_find_keyword( cft, c );
+ if ( ct ) {
+ c->table = Cft_Global;
+ rc = lload_config_add_vals( ct, c );
+ if ( !rc ) continue;
+
+ if ( rc & ARGS_USERLAND ) {
+ /* XXX a usertype would be opaque here */
+ Debug( LDAP_DEBUG_CONFIG, "%s: unknown user type <%s>\n",
+ c->log, c->argv[0] );
+ rc = 1;
+ goto done;
+
+ } else if ( rc == ARG_BAD_CONF ) {
+ rc = 1;
+ goto done;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: unknown directive "
+ "<%s> outside backend info and database definitions\n",
+ c->log, *c->argv );
+ rc = 1;
+ goto done;
+ }
+ }
+
+ rc = 0;
+
+done:
+ ch_free( c->tline );
+ fclose( fp );
+ ch_free( c->argv );
+ ch_free( c );
+ return rc;
+}
+
+int
+lload_read_config( const char *fname, const char *dir )
+{
+ if ( !fname ) fname = LLOADD_DEFAULT_CONFIGFILE;
+
+ cfn = ch_calloc( 1, sizeof(ConfigFile) );
+
+ return lload_read_config_file( fname, 0, NULL, config_back_cf_table );
+}
+
+#ifndef BALANCER_MODULE
+int
+config_push_cleanup( ConfigArgs *ca, ConfigDriver *cleanup )
+{
+ /* Stub, cleanups only run in online config */
+ return 0;
+}
+#endif /* !BALANCER_MODULE */
+
+static slap_verbmasks tlskey[] = {
+ { BER_BVC("no"), LLOAD_CLEARTEXT },
+ { BER_BVC("yes"), LLOAD_STARTTLS_OPTIONAL },
+ { BER_BVC("critical"), LLOAD_STARTTLS },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks crlkeys[] = {
+ { BER_BVC("none"), LDAP_OPT_X_TLS_CRL_NONE },
+ { BER_BVC("peer"), LDAP_OPT_X_TLS_CRL_PEER },
+ { BER_BVC("all"), LDAP_OPT_X_TLS_CRL_ALL },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks vfykeys[] = {
+ { BER_BVC("never"), LDAP_OPT_X_TLS_NEVER },
+ { BER_BVC("allow"), LDAP_OPT_X_TLS_ALLOW },
+ { BER_BVC("try"), LDAP_OPT_X_TLS_TRY },
+ { BER_BVC("demand"), LDAP_OPT_X_TLS_DEMAND },
+ { BER_BVC("hard"), LDAP_OPT_X_TLS_HARD },
+ { BER_BVC("true"), LDAP_OPT_X_TLS_HARD },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks methkey[] = {
+ { BER_BVC("none"), LDAP_AUTH_NONE },
+ { BER_BVC("simple"), LDAP_AUTH_SIMPLE },
+#ifdef HAVE_CYRUS_SASL
+ { BER_BVC("sasl"), LDAP_AUTH_SASL },
+#endif
+ { BER_BVNULL, 0 }
+};
+
+int
+lload_keepalive_parse(
+ struct berval *val,
+ void *bc,
+ slap_cf_aux_table *tab0,
+ const char *tabmsg,
+ int unparse )
+{
+ if ( unparse ) {
+ slap_keepalive *sk = (slap_keepalive *)bc;
+ int rc = snprintf( val->bv_val, val->bv_len, "%d:%d:%d",
+ sk->sk_idle, sk->sk_probes, sk->sk_interval );
+ if ( rc < 0 ) {
+ return -1;
+ }
+
+ if ( (unsigned)rc >= val->bv_len ) {
+ return -1;
+ }
+
+ val->bv_len = rc;
+
+ } else {
+ char *s = val->bv_val;
+ char *next;
+ slap_keepalive *sk = (slap_keepalive *)bc;
+ slap_keepalive sk2;
+
+ if ( s[0] == ':' ) {
+ sk2.sk_idle = 0;
+ s++;
+
+ } else {
+ sk2.sk_idle = strtol( s, &next, 10 );
+ if ( next == s || next[0] != ':' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_idle < 0 ) {
+ return -1;
+ }
+
+ s = ++next;
+ }
+
+ if ( s[0] == ':' ) {
+ sk2.sk_probes = 0;
+ s++;
+
+ } else {
+ sk2.sk_probes = strtol( s, &next, 10 );
+ if ( next == s || next[0] != ':' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_probes < 0 ) {
+ return -1;
+ }
+
+ s = ++next;
+ }
+
+ if ( *s == '\0' ) {
+ sk2.sk_interval = 0;
+
+ } else {
+ sk2.sk_interval = strtol( s, &next, 10 );
+ if ( next == s || next[0] != '\0' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_interval < 0 ) {
+ return -1;
+ }
+ }
+
+ *sk = sk2;
+
+ ber_memfree( val->bv_val );
+ BER_BVZERO( val );
+ }
+
+ return 0;
+}
+
+static slap_cf_aux_table backendkey[] = {
+ { BER_BVC("uri="), offsetof(LloadBackend, b_uri), 'b', 1, NULL },
+
+ { BER_BVC("numconns="), offsetof(LloadBackend, b_numconns), 'i', 0, NULL },
+ { BER_BVC("bindconns="), offsetof(LloadBackend, b_numbindconns), 'i', 0, NULL },
+ { BER_BVC("retry="), offsetof(LloadBackend, b_retry_timeout), 'i', 0, NULL },
+
+ { BER_BVC("max-pending-ops="), offsetof(LloadBackend, b_max_pending), 'i', 0, NULL },
+ { BER_BVC("conn-max-pending="), offsetof(LloadBackend, b_max_conn_pending), 'i', 0, NULL },
+ { BER_BVC("starttls="), offsetof(LloadBackend, b_tls_conf), 'i', 0, tlskey },
+
+ { BER_BVC("weight="), offsetof(LloadBackend, b_weight), 'i', 0, NULL },
+
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+static slap_cf_aux_table bindkey[] = {
+ { BER_BVC("bindmethod="), offsetof(slap_bindconf, sb_method), 'i', 0, methkey },
+ { BER_BVC("timeout="), offsetof(slap_bindconf, sb_timeout_api), 'i', 0, NULL },
+ { BER_BVC("network-timeout="), offsetof(slap_bindconf, sb_timeout_net), 'i', 0, NULL },
+ { BER_BVC("binddn="), offsetof(slap_bindconf, sb_binddn), 'b', 1, NULL },
+ { BER_BVC("credentials="), offsetof(slap_bindconf, sb_cred), 'b', 1, NULL },
+ { BER_BVC("saslmech="), offsetof(slap_bindconf, sb_saslmech), 'b', 0, NULL },
+ { BER_BVC("secprops="), offsetof(slap_bindconf, sb_secprops), 's', 0, NULL },
+ { BER_BVC("realm="), offsetof(slap_bindconf, sb_realm), 'b', 0, NULL },
+ { BER_BVC("authcID="), offsetof(slap_bindconf, sb_authcId), 'b', 1, NULL },
+ { BER_BVC("authzID="), offsetof(slap_bindconf, sb_authzId), 'b', 1, NULL },
+ { BER_BVC("keepalive="), offsetof(slap_bindconf, sb_keepalive), 'x', 0, (slap_verbmasks *)lload_keepalive_parse },
+ { BER_BVC("tcp-user-timeout="), offsetof(slap_bindconf, sb_tcp_user_timeout), 'u', 0, NULL },
+#ifdef HAVE_TLS
+ /* NOTE: replace "12" with the actual index
+ * of the first TLS-related line */
+#define aux_TLS (bindkey+12) /* beginning of TLS keywords */
+
+ { BER_BVC("tls_cert="), offsetof(slap_bindconf, sb_tls_cert), 's', 1, NULL },
+ { BER_BVC("tls_key="), offsetof(slap_bindconf, sb_tls_key), 's', 1, NULL },
+ { BER_BVC("tls_cacert="), offsetof(slap_bindconf, sb_tls_cacert), 's', 1, NULL },
+ { BER_BVC("tls_cacertdir="), offsetof(slap_bindconf, sb_tls_cacertdir), 's', 1, NULL },
+ { BER_BVC("tls_reqcert="), offsetof(slap_bindconf, sb_tls_reqcert), 's', 0, NULL },
+ { BER_BVC("tls_reqsan="), offsetof(slap_bindconf, sb_tls_reqsan), 's', 0, NULL },
+ { BER_BVC("tls_cipher_suite="), offsetof(slap_bindconf, sb_tls_cipher_suite), 's', 0, NULL },
+ { BER_BVC("tls_protocol_min="), offsetof(slap_bindconf, sb_tls_protocol_min), 's', 0, NULL },
+ { BER_BVC("tls_ecname="), offsetof(slap_bindconf, sb_tls_ecname), 's', 0, NULL },
+#ifdef HAVE_OPENSSL
+ { BER_BVC("tls_crlcheck="), offsetof(slap_bindconf, sb_tls_crlcheck), 's', 0, NULL },
+#endif
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+/*
+ * 's': char *
+ * 'b': struct berval
+ * 'i': int; if !NULL, compute using ((slap_verbmasks *)aux)
+ * 'u': unsigned
+ * 'I': long
+ * 'U': unsigned long
+ */
+
+int
+lload_cf_aux_table_parse(
+ const char *word,
+ void *dst,
+ slap_cf_aux_table *tab0,
+ LDAP_CONST char *tabmsg )
+{
+ int rc = SLAP_CONF_UNKNOWN;
+ slap_cf_aux_table *tab;
+
+ for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) {
+ if ( !strncasecmp( word, tab->key.bv_val, tab->key.bv_len ) ) {
+ char **cptr;
+ int *iptr, j;
+ unsigned *uptr;
+ long *lptr;
+ unsigned long *ulptr;
+ struct berval *bptr;
+ const char *val = word + tab->key.bv_len;
+
+ switch ( tab->type ) {
+ case 's':
+ cptr = (char **)( (char *)dst + tab->off );
+ *cptr = ch_strdup( val );
+ rc = 0;
+ break;
+
+ case 'b':
+ bptr = (struct berval *)( (char *)dst + tab->off );
+ assert( tab->aux == NULL );
+ ber_str2bv( val, 0, 1, bptr );
+ rc = 0;
+ break;
+
+ case 'i':
+ iptr = (int *)( (char *)dst + tab->off );
+
+ if ( tab->aux != NULL ) {
+ slap_verbmasks *aux = (slap_verbmasks *)tab->aux;
+
+ assert( aux != NULL );
+
+ rc = 1;
+ for ( j = 0; !BER_BVISNULL( &aux[j].word ); j++ ) {
+ if ( !strcasecmp( val, aux[j].word.bv_val ) ) {
+ *iptr = aux[j].mask;
+ rc = 0;
+ break;
+ }
+ }
+
+ } else {
+ rc = lutil_atoix( iptr, val, 0 );
+ }
+ break;
+
+ case 'u':
+ uptr = (unsigned *)( (char *)dst + tab->off );
+
+ rc = lutil_atoux( uptr, val, 0 );
+ break;
+
+ case 'I':
+ lptr = (long *)( (char *)dst + tab->off );
+
+ rc = lutil_atolx( lptr, val, 0 );
+ break;
+
+ case 'U':
+ ulptr = (unsigned long *)( (char *)dst + tab->off );
+
+ rc = lutil_atoulx( ulptr, val, 0 );
+ break;
+
+ case 'x':
+ if ( tab->aux != NULL ) {
+ struct berval value;
+ lload_cf_aux_table_parse_x *func =
+ (lload_cf_aux_table_parse_x *)tab->aux;
+
+ ber_str2bv( val, 0, 1, &value );
+
+ rc = func( &value, (void *)( (char *)dst + tab->off ),
+ tab, tabmsg, 0 );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "invalid %s value %s\n", tabmsg, word );
+ }
+
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+int
+lload_cf_aux_table_unparse(
+ void *src,
+ struct berval *bv,
+ slap_cf_aux_table *tab0 )
+{
+ char buf[AC_LINE_MAX], *ptr;
+ slap_cf_aux_table *tab;
+ struct berval tmp;
+
+ ptr = buf;
+ for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) {
+ char **cptr;
+ int *iptr, i;
+ unsigned *uptr;
+ long *lptr;
+ unsigned long *ulptr;
+ struct berval *bptr;
+
+ cptr = (char **)( (char *)src + tab->off );
+
+ switch ( tab->type ) {
+ case 'b':
+ bptr = (struct berval *)( (char *)src + tab->off );
+ cptr = &bptr->bv_val;
+
+ case 's':
+ if ( *cptr ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ if ( tab->quote ) *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, *cptr );
+ if ( tab->quote ) *ptr++ = '"';
+ }
+ break;
+
+ case 'i':
+ iptr = (int *)( (char *)src + tab->off );
+
+ if ( tab->aux != NULL ) {
+ slap_verbmasks *aux = (slap_verbmasks *)tab->aux;
+
+ for ( i = 0; !BER_BVISNULL( &aux[i].word ); i++ ) {
+ if ( *iptr == aux[i].mask ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr = lutil_strcopy( ptr, aux[i].word.bv_val );
+ break;
+ }
+ }
+
+ } else {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%d",
+ *iptr );
+ }
+ break;
+
+ case 'u':
+ uptr = (unsigned *)( (char *)src + tab->off );
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%u",
+ *uptr );
+ break;
+
+ case 'I':
+ lptr = (long *)( (char *)src + tab->off );
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%ld",
+ *lptr );
+ break;
+
+ case 'U':
+ ulptr = (unsigned long *)( (char *)src + tab->off );
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%lu",
+ *ulptr );
+ break;
+
+ case 'x': {
+ char *saveptr = ptr;
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ if ( tab->quote ) *ptr++ = '"';
+ if ( tab->aux != NULL ) {
+ struct berval value;
+ lload_cf_aux_table_parse_x *func =
+ (lload_cf_aux_table_parse_x *)tab->aux;
+ int rc;
+
+ value.bv_val = ptr;
+ value.bv_len = buf + sizeof(buf) - ptr;
+
+ rc = func( &value, (void *)( (char *)src + tab->off ), tab,
+ "(unparse)", 1 );
+ if ( rc == 0 ) {
+ if ( value.bv_len ) {
+ ptr += value.bv_len;
+ } else {
+ ptr = saveptr;
+ break;
+ }
+ }
+ }
+ if ( tab->quote ) *ptr++ = '"';
+ } break;
+
+ default:
+ assert(0);
+ }
+ }
+ tmp.bv_val = buf;
+ tmp.bv_len = ptr - buf;
+ ber_dupbv( bv, &tmp );
+ return 0;
+}
+
+int
+lload_tls_get_config( LDAP *ld, int opt, char **val )
+{
+#ifdef HAVE_TLS
+ slap_verbmasks *keys;
+ int i, ival;
+
+ *val = NULL;
+ switch ( opt ) {
+ case LDAP_OPT_X_TLS_CRLCHECK:
+ keys = crlkeys;
+ break;
+ case LDAP_OPT_X_TLS_REQUIRE_CERT:
+ keys = vfykeys;
+ break;
+ case LDAP_OPT_X_TLS_PROTOCOL_MIN: {
+ char buf[8];
+ ldap_pvt_tls_get_option( ld, opt, &ival );
+ snprintf( buf, sizeof(buf), "%d.%d",
+ ( ival >> 8 ) & 0xff, ival & 0xff );
+ *val = ch_strdup( buf );
+ return 0;
+ }
+ default:
+ return -1;
+ }
+ ldap_pvt_tls_get_option( ld, opt, &ival );
+ for ( i = 0; !BER_BVISNULL( &keys[i].word ); i++ ) {
+ if ( keys[i].mask == ival ) {
+ *val = ch_strdup( keys[i].word.bv_val );
+ return 0;
+ }
+ }
+#endif
+ return -1;
+}
+
+#ifdef HAVE_TLS
+static struct {
+ const char *key;
+ size_t offset;
+ int opt;
+} bindtlsopts[] = {
+ { "tls_cert", offsetof(slap_bindconf, sb_tls_cert), LDAP_OPT_X_TLS_CERTFILE },
+ { "tls_key", offsetof(slap_bindconf, sb_tls_key), LDAP_OPT_X_TLS_KEYFILE },
+ { "tls_cacert", offsetof(slap_bindconf, sb_tls_cacert), LDAP_OPT_X_TLS_CACERTFILE },
+ { "tls_cacertdir", offsetof(slap_bindconf, sb_tls_cacertdir), LDAP_OPT_X_TLS_CACERTDIR },
+ { "tls_cipher_suite", offsetof(slap_bindconf, sb_tls_cipher_suite), LDAP_OPT_X_TLS_CIPHER_SUITE },
+ { "tls_ecname", offsetof(slap_bindconf, sb_tls_ecname), LDAP_OPT_X_TLS_ECNAME },
+ { NULL, 0 }
+};
+
+int
+lload_bindconf_tls_set( slap_bindconf *bc, LDAP *ld )
+{
+ int i, rc, newctx = 0, res = 0;
+ char *ptr = (char *)bc, **word;
+
+ if ( bc->sb_tls_do_init ) {
+ for ( i = 0; bindtlsopts[i].opt; i++ ) {
+ word = (char **)( ptr + bindtlsopts[i].offset );
+ if ( *word ) {
+ rc = ldap_set_option( ld, bindtlsopts[i].opt, *word );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: "
+ "failed to set %s to %s\n",
+ bindtlsopts[i].key, *word );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+ }
+ if ( bc->sb_tls_reqcert ) {
+ rc = ldap_pvt_tls_config(
+ ld, LDAP_OPT_X_TLS_REQUIRE_CERT, bc->sb_tls_reqcert );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: "
+ "failed to set tls_reqcert to %s\n",
+ bc->sb_tls_reqcert );
+ res = -1;
+ } else {
+ newctx = 1;
+ /* retrieve the parsed setting for later use */
+ ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ &bc->sb_tls_int_reqcert );
+ }
+ }
+ if ( bc->sb_tls_reqsan ) {
+ rc = ldap_pvt_tls_config(
+ ld, LDAP_OPT_X_TLS_REQUIRE_SAN, bc->sb_tls_reqsan );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: "
+ "failed to set tls_reqsan to %s\n",
+ bc->sb_tls_reqsan );
+ res = -1;
+ } else {
+ newctx = 1;
+ /* retrieve the parsed setting for later use */
+ ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_SAN,
+ &bc->sb_tls_int_reqsan );
+ }
+ }
+ if ( bc->sb_tls_protocol_min ) {
+ rc = ldap_pvt_tls_config(
+ ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, bc->sb_tls_protocol_min );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: "
+ "failed to set tls_protocol_min to %s\n",
+ bc->sb_tls_protocol_min );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+#ifdef HAVE_OPENSSL
+ if ( bc->sb_tls_crlcheck ) {
+ rc = ldap_pvt_tls_config(
+ ld, LDAP_OPT_X_TLS_CRLCHECK, bc->sb_tls_crlcheck );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: "
+ "failed to set tls_crlcheck to %s\n",
+ bc->sb_tls_crlcheck );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+#endif
+ if ( !res ) bc->sb_tls_do_init = 0;
+ }
+
+ if ( newctx ) {
+ int opt = 0;
+
+ if ( bc->sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( bc->sb_tls_ctx );
+ bc->sb_tls_ctx = NULL;
+ }
+ rc = ldap_set_option( ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if ( rc )
+ res = rc;
+ else
+ ldap_get_option( ld, LDAP_OPT_X_TLS_CTX, &bc->sb_tls_ctx );
+ } else if ( bc->sb_tls_ctx ) {
+ rc = ldap_set_option( ld, LDAP_OPT_X_TLS_CTX, bc->sb_tls_ctx );
+ if ( rc == LDAP_SUCCESS ) {
+ /* these options aren't actually inside the ctx, so have to be set again */
+ ldap_set_option(
+ ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &bc->sb_tls_int_reqcert );
+ ldap_set_option(
+ ld, LDAP_OPT_X_TLS_REQUIRE_SAN, &bc->sb_tls_int_reqsan );
+ } else
+ res = rc;
+ }
+
+ return res;
+}
+#endif
+
+int
+lload_bindconf_tls_parse( const char *word, slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ if ( lload_cf_aux_table_parse( word, bc, aux_TLS, "tls config" ) == 0 ) {
+ bc->sb_tls_do_init = 1;
+ return 0;
+ }
+#endif
+ return -1;
+}
+
+int
+lload_backend_parse( const char *word, LloadBackend *b )
+{
+ return lload_cf_aux_table_parse( word, b, backendkey, "backend config" );
+}
+
+int
+lload_bindconf_parse( const char *word, slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ /* Detect TLS config changes explicitly */
+ if ( lload_bindconf_tls_parse( word, bc ) == 0 ) {
+ return 0;
+ }
+#endif
+ return lload_cf_aux_table_parse( word, bc, bindkey, "bind config" );
+}
+
+int
+lload_bindconf_unparse( slap_bindconf *bc, struct berval *bv )
+{
+ return lload_cf_aux_table_unparse( bc, bv, bindkey );
+}
+
+void
+lload_bindconf_free( slap_bindconf *bc )
+{
+ if ( !BER_BVISNULL( &bc->sb_uri ) ) {
+ ch_free( bc->sb_uri.bv_val );
+ BER_BVZERO( &bc->sb_uri );
+ }
+ if ( !BER_BVISNULL( &bc->sb_binddn ) ) {
+ ch_free( bc->sb_binddn.bv_val );
+ BER_BVZERO( &bc->sb_binddn );
+ }
+ if ( !BER_BVISNULL( &bc->sb_cred ) ) {
+ ch_free( bc->sb_cred.bv_val );
+ BER_BVZERO( &bc->sb_cred );
+ }
+ if ( !BER_BVISNULL( &bc->sb_saslmech ) ) {
+ ch_free( bc->sb_saslmech.bv_val );
+ BER_BVZERO( &bc->sb_saslmech );
+ }
+ if ( bc->sb_secprops ) {
+ ch_free( bc->sb_secprops );
+ bc->sb_secprops = NULL;
+ }
+ if ( !BER_BVISNULL( &bc->sb_realm ) ) {
+ ch_free( bc->sb_realm.bv_val );
+ BER_BVZERO( &bc->sb_realm );
+ }
+ if ( !BER_BVISNULL( &bc->sb_authcId ) ) {
+ ch_free( bc->sb_authcId.bv_val );
+ BER_BVZERO( &bc->sb_authcId );
+ }
+ if ( !BER_BVISNULL( &bc->sb_authzId ) ) {
+ ch_free( bc->sb_authzId.bv_val );
+ BER_BVZERO( &bc->sb_authzId );
+ }
+#ifdef HAVE_TLS
+ if ( bc->sb_tls_cert ) {
+ ch_free( bc->sb_tls_cert );
+ bc->sb_tls_cert = NULL;
+ }
+ if ( bc->sb_tls_key ) {
+ ch_free( bc->sb_tls_key );
+ bc->sb_tls_key = NULL;
+ }
+ if ( bc->sb_tls_cacert ) {
+ ch_free( bc->sb_tls_cacert );
+ bc->sb_tls_cacert = NULL;
+ }
+ if ( bc->sb_tls_cacertdir ) {
+ ch_free( bc->sb_tls_cacertdir );
+ bc->sb_tls_cacertdir = NULL;
+ }
+ if ( bc->sb_tls_reqcert ) {
+ ch_free( bc->sb_tls_reqcert );
+ bc->sb_tls_reqcert = NULL;
+ }
+ if ( bc->sb_tls_cipher_suite ) {
+ ch_free( bc->sb_tls_cipher_suite );
+ bc->sb_tls_cipher_suite = NULL;
+ }
+ if ( bc->sb_tls_protocol_min ) {
+ ch_free( bc->sb_tls_protocol_min );
+ bc->sb_tls_protocol_min = NULL;
+ }
+#ifdef HAVE_OPENSSL_CRL
+ if ( bc->sb_tls_crlcheck ) {
+ ch_free( bc->sb_tls_crlcheck );
+ bc->sb_tls_crlcheck = NULL;
+ }
+#endif
+ if ( bc->sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( bc->sb_tls_ctx );
+ bc->sb_tls_ctx = NULL;
+ }
+#endif
+}
+
+void
+lload_bindconf_tls_defaults( slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ if ( bc->sb_tls_do_init ) {
+ if ( !bc->sb_tls_cacert )
+ ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CACERTFILE,
+ &bc->sb_tls_cacert );
+ if ( !bc->sb_tls_cacertdir )
+ ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CACERTDIR,
+ &bc->sb_tls_cacertdir );
+ if ( !bc->sb_tls_cert )
+ ldap_pvt_tls_get_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_CERTFILE, &bc->sb_tls_cert );
+ if ( !bc->sb_tls_key )
+ ldap_pvt_tls_get_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_KEYFILE, &bc->sb_tls_key );
+ if ( !bc->sb_tls_cipher_suite )
+ ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ &bc->sb_tls_cipher_suite );
+ if ( !bc->sb_tls_reqcert ) bc->sb_tls_reqcert = ch_strdup( "demand" );
+#ifdef HAVE_OPENSSL_CRL
+ if ( !bc->sb_tls_crlcheck )
+ lload_tls_get_config( lload_tls_ld, LDAP_OPT_X_TLS_CRLCHECK,
+ &bc->sb_tls_crlcheck );
+#endif
+ }
+#endif
+}
+
+/* -------------------------------------- */
+
+static char *
+strtok_quote( char *line, char *sep, char **quote_ptr, int *iqp )
+{
+ int inquote;
+ char *tmp;
+ static char *next;
+
+ *quote_ptr = NULL;
+ if ( line != NULL ) {
+ next = line;
+ }
+ while ( *next && strchr( sep, *next ) ) {
+ next++;
+ }
+
+ if ( *next == '\0' ) {
+ next = NULL;
+ return NULL;
+ }
+ tmp = next;
+
+ for ( inquote = 0; *next; ) {
+ switch ( *next ) {
+ case '"':
+ if ( inquote ) {
+ inquote = 0;
+ } else {
+ inquote = 1;
+ }
+ AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
+ break;
+
+ case '\\':
+ if ( next[1] )
+ AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
+ next++; /* dont parse the escaped character */
+ break;
+
+ default:
+ if ( !inquote ) {
+ if ( strchr( sep, *next ) != NULL ) {
+ *quote_ptr = next;
+ *next++ = '\0';
+ return tmp;
+ }
+ }
+ next++;
+ break;
+ }
+ }
+ *iqp = inquote;
+
+ return tmp;
+}
+
+static char buf[AC_LINE_MAX];
+static char *line;
+static size_t lmax, lcur;
+
+#define CATLINE( buf ) \
+ do { \
+ size_t len = strlen( buf ); \
+ while ( lcur + len + 1 > lmax ) { \
+ lmax += AC_LINE_MAX; \
+ line = (char *)ch_realloc( line, lmax ); \
+ } \
+ strcpy( line + lcur, buf ); \
+ lcur += len; \
+ } while (0)
+
+static void
+fp_getline_init( ConfigArgs *c )
+{
+ c->lineno = -1;
+ buf[0] = '\0';
+}
+
+static int
+fp_getline( FILE *fp, ConfigArgs *c )
+{
+ char *p;
+
+ lcur = 0;
+ CATLINE( buf );
+ c->lineno++;
+
+ /* avoid stack of bufs */
+ if ( strncasecmp( line, "include", STRLENOF("include") ) == 0 ) {
+ buf[0] = '\0';
+ c->line = line;
+ return 1;
+ }
+
+ while ( fgets( buf, sizeof(buf), fp ) ) {
+ p = strchr( buf, '\n' );
+ if ( p ) {
+ if ( p > buf && p[-1] == '\r' ) {
+ --p;
+ }
+ *p = '\0';
+ }
+ /* XXX ugly */
+ c->line = line;
+ if ( line[0] && ( p = line + strlen( line ) - 1 )[0] == '\\' &&
+ p[-1] != '\\' ) {
+ p[0] = '\0';
+ lcur--;
+
+ } else {
+ if ( !isspace( (unsigned char)buf[0] ) ) {
+ return 1;
+ }
+ buf[0] = ' ';
+ }
+ CATLINE( buf );
+ c->lineno++;
+ }
+
+ buf[0] = '\0';
+ c->line = line;
+ return ( line[0] ? 1 : 0 );
+}
+
+int
+lload_config_fp_parse_line( ConfigArgs *c )
+{
+ char *token;
+ static char *const hide[] = { "bindconf", NULL };
+ static char *const raw[] = { NULL };
+ char *quote_ptr;
+ int i = (int)( sizeof(hide) / sizeof(hide[0]) ) - 1;
+ int inquote = 0;
+
+ c->tline = ch_strdup( c->line );
+ c->linelen = strlen( c->line );
+ token = strtok_quote( c->tline, " \t", &quote_ptr, &inquote );
+
+ if ( token )
+ for ( i = 0; hide[i]; i++ )
+ if ( !strcasecmp( token, hide[i] ) ) break;
+ if ( quote_ptr ) *quote_ptr = ' ';
+ Debug( LDAP_DEBUG_CONFIG, "%s (%s%s)\n",
+ c->log, hide[i] ? hide[i] : c->line, hide[i] ? " ***" : "" );
+ if ( quote_ptr ) *quote_ptr = '\0';
+
+ for ( ;; token = strtok_quote( NULL, " \t", &quote_ptr, &inquote ) ) {
+ if ( c->argc >= c->argv_size ) {
+ char **tmp;
+ tmp = ch_realloc( c->argv,
+ ( c->argv_size + ARGS_STEP ) * sizeof(*c->argv) );
+ if ( !tmp ) {
+ Debug( LDAP_DEBUG_ANY, "%s: out of memory\n", c->log );
+ return -1;
+ }
+ c->argv = tmp;
+ c->argv_size += ARGS_STEP;
+ }
+ if ( token == NULL ) break;
+ c->argv[c->argc++] = token;
+ }
+ c->argv[c->argc] = NULL;
+ if ( inquote ) {
+ /* these directives parse c->line independently of argv tokenizing */
+ for ( i = 0; raw[i]; i++ )
+ if ( !strcasecmp( c->argv[0], raw[i] ) ) return 0;
+
+ Debug( LDAP_DEBUG_ANY, "%s: unterminated quoted string \"%s\"\n",
+ c->log, c->argv[c->argc - 1] );
+ return -1;
+ }
+ return 0;
+}
+
+void
+lload_config_destroy( void )
+{
+ free( line );
+ if ( slapd_args_file ) free( slapd_args_file );
+ if ( slapd_pid_file ) free( slapd_pid_file );
+ slap_loglevel_destroy();
+}
+
+/* See if the given URL (in plain and parsed form) matches
+ * any of the server's listener addresses. Return matching
+ * LloadListener or NULL for no match.
+ */
+LloadListener *
+lload_config_check_my_url( const char *url, LDAPURLDesc *lud )
+{
+ LloadListener **l = lloadd_get_listeners();
+ int i, isMe;
+
+ /* Try a straight compare with LloadListener strings */
+ for ( i = 0; l && l[i]; i++ ) {
+ if ( !strcasecmp( url, l[i]->sl_url.bv_val ) ) {
+ return l[i];
+ }
+ }
+
+ isMe = 0;
+ /* If hostname is empty, or is localhost, or matches
+ * our hostname, this url refers to this host.
+ * Compare it against listeners and ports.
+ */
+ if ( !lud->lud_host || !lud->lud_host[0] ||
+ !strncasecmp(
+ "localhost", lud->lud_host, STRLENOF("localhost") ) ||
+ !strcasecmp( global_host, lud->lud_host ) ) {
+ for ( i = 0; l && l[i]; i++ ) {
+ LDAPURLDesc *lu2;
+ ldap_url_parse_ext(
+ l[i]->sl_url.bv_val, &lu2, LDAP_PVT_URL_PARSE_DEF_PORT );
+ do {
+ if ( strcasecmp( lud->lud_scheme, lu2->lud_scheme ) ) break;
+ if ( lud->lud_port != lu2->lud_port ) break;
+ /* Listener on ANY address */
+ if ( !lu2->lud_host || !lu2->lud_host[0] ) {
+ isMe = 1;
+ break;
+ }
+ /* URL on ANY address */
+ if ( !lud->lud_host || !lud->lud_host[0] ) {
+ isMe = 1;
+ break;
+ }
+ /* Listener has specific host, must
+ * match it
+ */
+ if ( !strcasecmp( lud->lud_host, lu2->lud_host ) ) {
+ isMe = 1;
+ break;
+ }
+ } while (0);
+ ldap_free_urldesc( lu2 );
+ if ( isMe ) {
+ return l[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+#ifdef BALANCER_MODULE
+static int
+backend_cf_gen( ConfigArgs *c )
+{
+ LloadBackend *b = c->ca_private;
+ enum lcf_backend flag = 0;
+ int rc = LDAP_SUCCESS;
+
+ assert( b != NULL );
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch ( c->type ) {
+ case CFG_URI:
+ c->value_bv = b->b_uri;
+ break;
+ case CFG_NUMCONNS:
+ c->value_uint = b->b_numconns;
+ break;
+ case CFG_BINDCONNS:
+ c->value_uint = b->b_numbindconns;
+ break;
+ case CFG_RETRY:
+ c->value_uint = b->b_retry_timeout;
+ break;
+ case CFG_MAX_PENDING_CONNS:
+ c->value_uint = b->b_max_conn_pending;
+ break;
+ case CFG_MAX_PENDING_OPS:
+ c->value_uint = b->b_max_pending;
+ break;
+ case CFG_STARTTLS:
+ enum_to_verb( tlskey, b->b_tls_conf, &c->value_bv );
+ break;
+ case CFG_WEIGHT:
+ c->value_uint = b->b_weight;
+ break;
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* We only need to worry about deletions to multi-value or MAY
+ * attributes */
+ switch ( c->type ) {
+ case CFG_STARTTLS:
+ b->b_tls_conf = LLOAD_CLEARTEXT;
+ break;
+ default:
+ break;
+ }
+ return rc;
+ }
+
+ switch ( c->type ) {
+ case CFG_URI:
+ rc = backend_config_url( b, &c->value_bv );
+ if ( rc ) {
+ backend_config_url( b, &b->b_uri );
+ goto fail;
+ }
+ if ( !BER_BVISNULL( &b->b_uri ) ) {
+ ch_free( b->b_uri.bv_val );
+ }
+ b->b_uri = c->value_bv;
+ flag = LLOAD_BACKEND_MOD_OTHER;
+ break;
+ case CFG_NUMCONNS:
+ if ( !c->value_uint ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "invalid connection pool configuration" );
+ goto fail;
+ }
+ b->b_numconns = c->value_uint;
+ flag = LLOAD_BACKEND_MOD_CONNS;
+ break;
+ case CFG_BINDCONNS:
+ if ( !c->value_uint ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "invalid connection pool configuration" );
+ goto fail;
+ }
+ b->b_numbindconns = c->value_uint;
+ flag = LLOAD_BACKEND_MOD_CONNS;
+ break;
+ case CFG_RETRY:
+ b->b_retry_timeout = c->value_uint;
+ break;
+ case CFG_MAX_PENDING_CONNS:
+ b->b_max_conn_pending = c->value_uint;
+ break;
+ case CFG_MAX_PENDING_OPS:
+ b->b_max_pending = c->value_uint;
+ break;
+ case CFG_STARTTLS: {
+ int i = bverb_to_mask( &c->value_bv, tlskey );
+ if ( BER_BVISNULL( &tlskey[i].word ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "invalid starttls configuration" );
+ goto fail;
+ }
+#ifndef HAVE_TLS
+ if ( tlskey[i].mask == LLOAD_STARTTLS_OPTIONAL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "lloadd compiled without TLS but starttls specified, "
+ "it will be ignored\n",
+ c->log );
+ } else if ( tlskey[i].mask != LLOAD_CLEARTEXT ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "invalid starttls configuration when compiled without "
+ "TLS support" );
+ goto fail;
+ }
+#endif /* ! HAVE_TLS */
+ b->b_tls_conf = tlskey[i].mask;
+ } break;
+ case CFG_WEIGHT:
+ b->b_weight = c->value_uint;
+ break;
+ default:
+ rc = 1;
+ break;
+ }
+
+ /* do not set this if it has already been set by another callback, e.g.
+ * lload_backend_ldadd */
+ if ( lload_change.type == LLOAD_CHANGE_UNDEFINED ) {
+ lload_change.type = LLOAD_CHANGE_MODIFY;
+ }
+ lload_change.object = LLOAD_BACKEND;
+ lload_change.target = b;
+ lload_change.flags.backend |= flag;
+
+ config_push_cleanup( c, lload_backend_finish );
+ return rc;
+
+fail:
+ if ( lload_change.type == LLOAD_CHANGE_ADD ) {
+ /* Abort the ADD */
+ lload_change.type = LLOAD_CHANGE_DEL;
+ }
+
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+}
+
+int
+lload_back_init_cf( BackendInfo *bi )
+{
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( CFG_LAST );
+
+ bi->bi_cf_ocs = lloadocs;
+
+ return config_register_schema( config_back_cf_table, lloadocs );
+}
+
+static int
+lload_tier_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ LloadTier *tier;
+ Attribute *a;
+ AttributeDescription *ad = NULL;
+ struct lload_tier_type *tier_impl;
+ struct berval bv, type, rdn;
+ const char *text;
+ char *name;
+
+ Debug( LDAP_DEBUG_TRACE, "lload_tier_ldadd: "
+ "a new tier is being added\n" );
+
+ if ( p->ce_type != Cft_Backend || !p->ce_bi ||
+ p->ce_bi->bi_cf_ocs != lloadocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ dnRdn( &e->e_name, &rdn );
+ type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val;
+ type.bv_val = rdn.bv_val;
+
+ /* Find attr */
+ slap_bv2ad( &type, &ad, &text );
+ if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION;
+
+ a = attr_find( e->e_attrs, ad );
+ if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION;
+ bv = a->a_vals[0];
+
+ if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) {
+ name++;
+ bv.bv_len -= name - bv.bv_val;
+ bv.bv_val = name;
+ }
+
+ ad = NULL;
+ slap_str2ad( "olcBkLloadTierType", &ad, &text );
+ assert( ad != NULL );
+
+ a = attr_find( e->e_attrs, ad );
+ if ( !a || a->a_numvals != 1 ) return LDAP_OBJECT_CLASS_VIOLATION;
+
+ tier_impl = lload_tier_find( a->a_vals[0].bv_val );
+ if ( !tier_impl ) {
+ Debug( LDAP_DEBUG_ANY, "lload_tier_ldadd: "
+ "tier type %s not recongnised\n",
+ bv.bv_val );
+ return LDAP_OTHER;
+ }
+
+ tier = tier_impl->tier_init();
+ if ( !tier ) {
+ return LDAP_OTHER;
+ }
+
+ ber_dupbv( &tier->t_name, &bv );
+
+ ca->bi = p->ce_bi;
+ ca->ca_private = tier;
+
+ if ( !lloadd_inited ) {
+ if ( LDAP_STAILQ_EMPTY( &tiers ) ) {
+ LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next );
+ }
+ }
+
+ /* ca cleanups are only run in the case of online config but we use it to
+ * save the new config when done with the entry */
+ ca->lineno = 0;
+
+ lload_change.type = LLOAD_CHANGE_ADD;
+ lload_change.object = LLOAD_TIER;
+ lload_change.target = tier;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_backend_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ LloadTier *tier = p->ce_private;
+ LloadBackend *b;
+ Attribute *a;
+ AttributeDescription *ad = NULL;
+ struct berval bv, type, rdn;
+ const char *text;
+ char *name;
+
+ Debug( LDAP_DEBUG_TRACE, "lload_backend_ldadd: "
+ "a new backend-server is being added\n" );
+
+ if ( p->ce_type != Cft_Misc || !p->ce_bi ||
+ p->ce_bi->bi_cf_ocs != lloadocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ dnRdn( &e->e_name, &rdn );
+ type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val;
+ type.bv_val = rdn.bv_val;
+
+ /* Find attr */
+ slap_bv2ad( &type, &ad, &text );
+ if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION;
+
+ a = attr_find( e->e_attrs, ad );
+ if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION;
+ bv = a->a_vals[0];
+
+ if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) {
+ name++;
+ bv.bv_len -= name - bv.bv_val;
+ bv.bv_val = name;
+ }
+
+ b = lload_backend_new();
+ ber_dupbv( &b->b_name, &bv );
+ b->b_tier = tier;
+
+ ca->bi = p->ce_bi;
+ ca->ca_private = b;
+ config_push_cleanup( ca, lload_backend_finish );
+
+ /* ca cleanups are only run in the case of online config but we use it to
+ * save the new config when done with the entry */
+ ca->lineno = 0;
+
+ lload_change.type = LLOAD_CHANGE_ADD;
+ lload_change.object = LLOAD_BACKEND;
+ lload_change.target = b;
+
+ return LDAP_SUCCESS;
+}
+
+#ifdef SLAP_CONFIG_DELETE
+static int
+lload_backend_lddel( CfEntryInfo *ce, Operation *op )
+{
+ LloadBackend *b = ce->ce_private;
+
+ lload_change.type = LLOAD_CHANGE_DEL;
+ lload_change.object = LLOAD_BACKEND;
+ lload_change.target = b;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_tier_lddel( CfEntryInfo *ce, Operation *op )
+{
+ LloadTier *tier = ce->ce_private;
+
+ lload_change.type = LLOAD_CHANGE_DEL;
+ lload_change.object = LLOAD_TIER;
+ lload_change.target = tier;
+
+ return LDAP_SUCCESS;
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+static int
+lload_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c )
+{
+ struct berval bv;
+ LloadTier *tier;
+ int i = 0;
+
+ bv.bv_val = c->cr_msg;
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ LloadBackend *b;
+ ConfigOCs *coc;
+ Entry *e;
+ int j = 0;
+
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "cn=" SLAP_X_ORDERED_FMT "%s", i, tier->t_name.bv_val );
+
+ c->ca_private = tier;
+ c->valx = i;
+
+ for ( coc = lloadocs; coc->co_type; coc++ ) {
+ if ( !ber_bvcmp( coc->co_name, &tier->t_type.tier_oc ) ) {
+ break;
+ }
+ }
+ assert( coc->co_type );
+
+ e = config_build_entry( op, rs, p->e_private, c, &bv, coc, NULL );
+ if ( !e ) {
+ return 1;
+ }
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "cn=" SLAP_X_ORDERED_FMT "%s", j, b->b_name.bv_val );
+
+ for ( coc = lloadocs; coc->co_type; coc++ ) {
+ if ( !ber_bvcmp(
+ coc->co_name, &tier->t_type.tier_backend_oc ) ) {
+ break;
+ }
+ }
+ assert( coc->co_type );
+
+ c->ca_private = b;
+ c->valx = j;
+
+ if ( !config_build_entry(
+ op, rs, e->e_private, c, &bv, coc, NULL ) ) {
+ return 1;
+ }
+
+ j++;
+ }
+
+ i++;
+ }
+ return LDAP_SUCCESS;
+}
+#endif /* BALANCER_MODULE */
diff --git a/servers/lloadd/connection.c b/servers/lloadd/connection.c
new file mode 100644
index 0000000..a3f5323
--- /dev/null
+++ b/servers/lloadd/connection.c
@@ -0,0 +1,644 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lload.h"
+
+#include "lutil.h"
+#include "lutil_ldap.h"
+
+static unsigned long conn_nextid = 0;
+
+static void
+lload_connection_assign_nextid( LloadConnection *conn )
+{
+ conn->c_connid = __atomic_fetch_add( &conn_nextid, 1, __ATOMIC_RELAXED );
+}
+
+/*
+ * We start off with the connection muted and c_currentber holding the pdu we
+ * received.
+ *
+ * We run c->c_pdu_cb for each pdu, stopping once we hit an error, have to wait
+ * on reading or after we process lload_conn_max_pdus_per_cycle pdus so as to
+ * maintain fairness and not hog the worker thread forever.
+ *
+ * If we've run out of pdus immediately available from the stream or hit the
+ * budget, we unmute the connection.
+ *
+ * c->c_pdu_cb might return an 'error' and not free the connection. That can
+ * happen when changing the state or when client is blocked on writing and
+ * already has a pdu pending on the same operation, it's their job to make sure
+ * we're woken up again.
+ */
+void *
+handle_pdus( void *ctx, void *arg )
+{
+ LloadConnection *c = arg;
+ int pdus_handled = 0;
+ epoch_t epoch;
+
+ /* A reference was passed on to us */
+ assert( IS_ALIVE( c, c_refcnt ) );
+
+ epoch = epoch_join();
+ for ( ;; ) {
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+
+ if ( c->c_pdu_cb( c ) ) {
+ /* Error/reset, get rid ouf our reference and bail */
+ goto done;
+ }
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ break;
+ }
+
+ if ( ++pdus_handled >= lload_conn_max_pdus_per_cycle ) {
+ /* Do not read now, re-enable read event instead */
+ break;
+ }
+
+ ber = c->c_currentber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "handle_pdus: "
+ "connid=%lu, ber_alloc failed\n",
+ c->c_connid );
+ CONNECTION_LOCK_DESTROY(c);
+ goto done;
+ }
+ c->c_currentber = ber;
+
+ checked_lock( &c->c_io_mutex );
+ if ( (lload_features & LLOAD_FEATURE_PAUSE) &&
+ (c->c_io_state & LLOAD_C_READ_PAUSE) ) {
+ goto pause;
+ }
+ tag = ber_get_next( c->c_sb, &len, ber );
+ checked_unlock( &c->c_io_mutex );
+ if ( tag != LDAP_TAG_MESSAGE ) {
+ int err = sock_errno();
+
+ if ( err != EWOULDBLOCK && err != EAGAIN ) {
+ if ( err || tag == LBER_ERROR ) {
+ char ebuf[128];
+ Debug( LDAP_DEBUG_ANY, "handle_pdus: "
+ "ber_get_next on fd=%d failed errno=%d (%s)\n",
+ c->c_fd, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ } else {
+ Debug( LDAP_DEBUG_STATS, "handle_pdus: "
+ "ber_get_next on fd=%d connid=%lu received "
+ "a strange PDU tag=%lx\n",
+ c->c_fd, c->c_connid, tag );
+ }
+
+ c->c_currentber = NULL;
+ ber_free( ber, 1 );
+ CONNECTION_LOCK_DESTROY(c);
+ goto done;
+ }
+ break;
+ }
+
+ assert( IS_ALIVE( c, c_refcnt ) );
+ epoch_leave( epoch );
+ epoch = epoch_join();
+ assert( IS_ALIVE( c, c_refcnt ) );
+ }
+
+ checked_lock( &c->c_io_mutex );
+ if ( !(lload_features & LLOAD_FEATURE_PAUSE) ||
+ !(c->c_io_state & LLOAD_C_READ_PAUSE) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ Debug( LDAP_DEBUG_CONNS, "handle_pdus: "
+ "re-enabled read event on connid=%lu\n",
+ c->c_connid );
+ }
+pause:
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+done:
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ epoch_leave( epoch );
+ return NULL;
+}
+
+/*
+ * Initial read on the connection, if we get an LDAP PDU, submit the
+ * processing of this and successive ones to the work queue.
+ *
+ * If we can't submit it to the queue (overload), process this one and return
+ * to the event loop immediately after.
+ */
+void
+connection_read_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadConnection *c = arg;
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ epoch_t epoch;
+ int pause;
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ event_del( c->c_read_event );
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "suspended read event on a dead connid=%lu\n",
+ c->c_connid );
+ return;
+ }
+
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "connid=%lu, timeout reached, destroying\n",
+ c->c_connid );
+ /* Make sure the connection stays around for us to unlock it */
+ epoch = epoch_join();
+ CONNECTION_LOCK_DESTROY(c);
+ epoch_leave( epoch );
+ return;
+ }
+
+ if ( !acquire_ref( &c->c_refcnt ) ) {
+ return;
+ }
+ epoch = epoch_join();
+
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "connection connid=%lu ready to read\n",
+ c->c_connid );
+
+ ber = c->c_currentber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "connection_read_cb: "
+ "connid=%lu, ber_alloc failed\n",
+ c->c_connid );
+ goto out;
+ }
+ c->c_currentber = ber;
+
+ checked_lock( &c->c_io_mutex );
+ assert( !(c->c_io_state & LLOAD_C_READ_HANDOVER) );
+ tag = ber_get_next( c->c_sb, &len, ber );
+ pause = c->c_io_state & LLOAD_C_READ_PAUSE;
+ checked_unlock( &c->c_io_mutex );
+
+ if ( tag != LDAP_TAG_MESSAGE ) {
+ int err = sock_errno();
+
+ if ( err != EWOULDBLOCK && err != EAGAIN ) {
+ if ( err || tag == LBER_ERROR ) {
+ char ebuf[128];
+ Debug( LDAP_DEBUG_STATS, "connection_read_cb: "
+ "ber_get_next on fd=%d failed errno=%d (%s)\n",
+ c->c_fd, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ } else {
+ Debug( LDAP_DEBUG_STATS, "connection_read_cb: "
+ "ber_get_next on fd=%d connid=%lu received "
+ "a strange PDU tag=%lx\n",
+ c->c_fd, c->c_connid, tag );
+ }
+
+ c->c_currentber = NULL;
+ ber_free( ber, 1 );
+
+ event_del( c->c_read_event );
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "suspended read event on dying connid=%lu\n",
+ c->c_connid );
+ CONNECTION_LOCK_DESTROY(c);
+ goto out;
+ }
+ if ( !(lload_features & LLOAD_FEATURE_PAUSE) || !pause ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "re-enabled read event on connid=%lu\n",
+ c->c_connid );
+ }
+ goto out;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state |= LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+ event_del( c->c_read_event );
+
+ if ( !lload_conn_max_pdus_per_cycle ||
+ ldap_pvt_thread_pool_submit( &connection_pool, handle_pdus, c ) ) {
+ /* If we're overloaded or configured as such, process one and resume in
+ * the next cycle. */
+ int rc = c->c_pdu_cb( c );
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ if ( rc == LDAP_SUCCESS &&
+ ( !(lload_features & LLOAD_FEATURE_PAUSE) ||
+ !(c->c_io_state & LLOAD_C_READ_PAUSE) ) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ }
+ checked_unlock( &c->c_io_mutex );
+ goto out;
+ }
+
+ Debug( LDAP_DEBUG_CONNS, "connection_read_cb: "
+ "suspended read event on connid=%lu\n",
+ c->c_connid );
+
+ /*
+ * We have scheduled a call to handle_pdus to take care of handling this
+ * and further requests, its reference is now owned by that task.
+ */
+ epoch_leave( epoch );
+ return;
+
+out:
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ epoch_leave( epoch );
+}
+
+void
+connection_write_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadConnection *c = arg;
+ epoch_t epoch;
+
+ Debug( LDAP_DEBUG_CONNS, "connection_write_cb: "
+ "considering writing to%s connid=%lu what=%hd\n",
+ c->c_live ? " live" : " dead", c->c_connid, what );
+ if ( !IS_ALIVE( c, c_live ) ) {
+ return;
+ }
+
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_CONNS, "connection_write_cb: "
+ "connid=%lu, timeout reached, destroying\n",
+ c->c_connid );
+ /* Make sure the connection stays around for us to unlock it */
+ epoch = epoch_join();
+ CONNECTION_LOCK_DESTROY(c);
+ epoch_leave( epoch );
+ return;
+ }
+
+ /* Before we acquire any locks */
+ event_del( c->c_write_event );
+
+ if ( !acquire_ref( &c->c_refcnt ) ) {
+ return;
+ }
+
+ /* If what == 0, we have a caller as opposed to being a callback */
+ if ( what ) {
+ epoch = epoch_join();
+ }
+
+ checked_lock( &c->c_io_mutex );
+ Debug( LDAP_DEBUG_CONNS, "connection_write_cb: "
+ "have something to write to connection connid=%lu\n",
+ c->c_connid );
+
+ /* We might have been beaten to flushing the data by another thread */
+ if ( c->c_pendingber && ber_flush( c->c_sb, c->c_pendingber, 1 ) ) {
+ int err = sock_errno();
+
+ if ( err != EWOULDBLOCK && err != EAGAIN ) {
+ char ebuf[128];
+ checked_unlock( &c->c_io_mutex );
+ Debug( LDAP_DEBUG_ANY, "connection_write_cb: "
+ "ber_flush on fd=%d failed errno=%d (%s)\n",
+ c->c_fd, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ CONNECTION_LOCK_DESTROY(c);
+ goto done;
+ }
+
+ if ( !(c->c_io_state & LLOAD_C_READ_PAUSE) ) {
+ Debug( LDAP_DEBUG_CONNS, "connection_write_cb: "
+ "connection connid=%lu blocked on writing, marking "
+ "paused\n",
+ c->c_connid );
+ }
+ c->c_io_state |= LLOAD_C_READ_PAUSE;
+
+ /* TODO: Do not reset write timeout unless we wrote something */
+ event_add( c->c_write_event, lload_write_timeout );
+ } else {
+ c->c_pendingber = NULL;
+ if ( c->c_io_state & LLOAD_C_READ_PAUSE ) {
+ c->c_io_state ^= LLOAD_C_READ_PAUSE;
+ Debug( LDAP_DEBUG_CONNS, "connection_write_cb: "
+ "Unpausing connection connid=%lu\n",
+ c->c_connid );
+ if ( !(c->c_io_state & LLOAD_C_READ_HANDOVER) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ }
+ }
+ }
+ checked_unlock( &c->c_io_mutex );
+
+done:
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ if ( what ) {
+ epoch_leave( epoch );
+ }
+}
+
+void
+connection_destroy( LloadConnection *c )
+{
+ assert( c );
+ Debug( LDAP_DEBUG_CONNS, "connection_destroy: "
+ "destroying connection connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( c->c_live == 0 );
+ assert( c->c_refcnt == 0 );
+ assert( c->c_state == LLOAD_C_INVALID );
+
+ ber_sockbuf_free( c->c_sb );
+
+ if ( c->c_currentber ) {
+ ber_free( c->c_currentber, 1 );
+ c->c_currentber = NULL;
+ }
+ if ( c->c_pendingber ) {
+ ber_free( c->c_pendingber, 1 );
+ c->c_pendingber = NULL;
+ }
+
+ if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) {
+ ber_memfree( c->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+#ifdef HAVE_CYRUS_SASL
+ if ( c->c_sasl_defaults ) {
+ lutil_sasl_freedefs( c->c_sasl_defaults );
+ c->c_sasl_defaults = NULL;
+ }
+ if ( c->c_sasl_authctx ) {
+#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */
+ if ( c->c_sasl_cbinding ) {
+ ch_free( c->c_sasl_cbinding );
+ }
+#endif
+ sasl_dispose( &c->c_sasl_authctx );
+ }
+#endif /* HAVE_CYRUS_SASL */
+
+ CONNECTION_UNLOCK(c);
+
+ ldap_pvt_thread_mutex_destroy( &c->c_io_mutex );
+ ldap_pvt_thread_mutex_destroy( &c->c_mutex );
+
+ ch_free( c );
+
+ listeners_reactivate();
+}
+
+/*
+ * Called holding mutex, will walk cq calling cb on all connections whose
+ * c_connid <= cq_last->c_connid that still exist at the time we get to them.
+ */
+void
+connections_walk_last(
+ ldap_pvt_thread_mutex_t *cq_mutex,
+ lload_c_head *cq,
+ LloadConnection *cq_last,
+ CONNCB cb,
+ void *arg )
+{
+ LloadConnection *c = cq_last;
+ uintptr_t last_connid;
+
+ if ( LDAP_CIRCLEQ_EMPTY( cq ) ) {
+ return;
+ }
+ assert_locked( cq_mutex );
+
+ last_connid = c->c_connid;
+ c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next );
+
+ while ( !acquire_ref( &c->c_refcnt ) ) {
+ c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next );
+ if ( c->c_connid >= last_connid ) {
+ assert_locked( cq_mutex );
+ return;
+ }
+ }
+
+ /*
+ * Notes:
+ * - we maintain the connections in the cq CIRCLEQ_ in ascending c_connid
+ * order
+ * - the connection with the highest c_connid is passed in cq_last
+ * - we can only use cq when we hold cq_mutex
+ * - connections might be added to or removed from cq while we're busy
+ * processing connections
+ * - we need a way to detect we've finished looping around cq for some
+ * definition of looping around
+ */
+ do {
+ int rc;
+
+ checked_unlock( cq_mutex );
+
+ rc = cb( c, arg );
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+
+ checked_lock( cq_mutex );
+ if ( rc || LDAP_CIRCLEQ_EMPTY( cq ) ) {
+ break;
+ }
+
+ do {
+ LloadConnection *old = c;
+ c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next );
+ if ( c->c_connid <= old->c_connid || c->c_connid > last_connid ) {
+ assert_locked( cq_mutex );
+ return;
+ }
+ } while ( !acquire_ref( &c->c_refcnt ) );
+ } while ( c->c_connid <= last_connid );
+ assert_locked( cq_mutex );
+}
+
+void
+connections_walk(
+ ldap_pvt_thread_mutex_t *cq_mutex,
+ lload_c_head *cq,
+ CONNCB cb,
+ void *arg )
+{
+ LloadConnection *cq_last = LDAP_CIRCLEQ_LAST( cq );
+ return connections_walk_last( cq_mutex, cq, cq_last, cb, arg );
+}
+
+int
+lload_connection_close( LloadConnection *c, void *arg )
+{
+ int unlock, gentle = *(int *)arg;
+ LloadOperation *op;
+
+ Debug( LDAP_DEBUG_CONNS, "lload_connection_close: "
+ "marking connection connid=%lu closing\n",
+ c->c_connid );
+
+ /* We were approached from the connection list or cn=monitor */
+ assert( IS_ALIVE( c, c_refcnt ) );
+
+ /* Need to acquire this first, even if we won't need it */
+ unlock = 1;
+ checked_lock( &c->c_io_mutex );
+ CONNECTION_LOCK(c);
+
+ /* Only if it's a usable client */
+ if ( ( c->c_state == LLOAD_C_READY || c->c_state == LLOAD_C_BINDING ) &&
+ c->c_destroy == client_destroy ) {
+ if ( c->c_pendingber != NULL ||
+ (c->c_pendingber = ber_alloc()) != NULL ) {
+ ber_printf( c->c_pendingber, "t{tit{essts}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, LDAP_RES_UNSOLICITED,
+ LDAP_RES_EXTENDED, LDAP_UNAVAILABLE, "",
+ "connection closing",
+ LDAP_TAG_EXOP_RES_OID, LDAP_NOTICE_OF_DISCONNECTION );
+ unlock = 0;
+ checked_unlock( &c->c_io_mutex );
+ CONNECTION_UNLOCK(c);
+ connection_write_cb( -1, 0, c );
+ CONNECTION_LOCK(c);
+ }
+ }
+ if ( unlock )
+ checked_unlock( &c->c_io_mutex );
+
+ if ( !gentle || !c->c_ops ) {
+ CONNECTION_DESTROY(c);
+ return LDAP_SUCCESS;
+ }
+
+ /* The first thing we do is make sure we don't get new Operations in */
+ c->c_state = LLOAD_C_CLOSING;
+
+ do {
+ TAvlnode *node = ldap_tavl_end( c->c_ops, TAVL_DIR_LEFT );
+ op = node->avl_data;
+
+ /* Close operations that would need client action to resolve,
+ * only SASL binds in progress do that right now */
+ if ( op->o_client_msgid || op->o_upstream_msgid ) {
+ break;
+ }
+
+ CONNECTION_UNLOCK(c);
+ OPERATION_UNLINK(op);
+ CONNECTION_LOCK(c);
+ } while ( c->c_ops );
+
+ CONNECTION_UNLOCK(c);
+ return LDAP_SUCCESS;
+}
+
+LloadConnection *
+lload_connection_init( ber_socket_t s, const char *peername, int flags )
+{
+ LloadConnection *c;
+
+ assert( peername != NULL );
+
+ if ( s == AC_SOCKET_INVALID ) {
+ Debug( LDAP_DEBUG_ANY, "lload_connection_init: "
+ "init of socket fd=%ld invalid\n",
+ (long)s );
+ return NULL;
+ }
+
+ assert( s >= 0 );
+
+ c = ch_calloc( 1, sizeof(LloadConnection) );
+
+ c->c_fd = s;
+ c->c_sb = ber_sockbuf_alloc();
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_FD, &s );
+
+#ifdef LDAP_PF_LOCAL
+ if ( flags & CONN_IS_IPC ) {
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" );
+#endif
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_fd,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)&s );
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)"tcp_" );
+#endif
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_tcp,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)&s );
+ }
+
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io(
+ c->c_sb, &ber_sockbuf_io_debug, INT_MAX, (void *)"lload_" );
+#endif
+
+ c->c_next_msgid = 1;
+ c->c_refcnt = c->c_live = 1;
+ c->c_destroy = connection_destroy;
+
+ LDAP_CIRCLEQ_ENTRY_INIT( c, c_next );
+
+ ldap_pvt_thread_mutex_init( &c->c_mutex );
+ ldap_pvt_thread_mutex_init( &c->c_io_mutex );
+
+ lload_connection_assign_nextid( c );
+
+ Debug( LDAP_DEBUG_CONNS, "lload_connection_init: "
+ "connection connid=%lu allocated for socket fd=%d peername=%s\n",
+ c->c_connid, s, peername );
+
+ c->c_state = LLOAD_C_ACTIVE;
+
+ return c;
+}
diff --git a/servers/lloadd/daemon.c b/servers/lloadd/daemon.c
new file mode 100644
index 0000000..a93879e
--- /dev/null
+++ b/servers/lloadd/daemon.c
@@ -0,0 +1,1978 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2007 by Howard Chu, Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <event2/event.h>
+#include <event2/dns.h>
+#include <event2/listener.h>
+
+#include "lload.h"
+#include "ldap_pvt_thread.h"
+#include "lutil.h"
+
+#include "ldap_rq.h"
+
+#ifdef HAVE_SYSTEMD_SD_DAEMON_H
+#include <systemd/sd-daemon.h>
+#endif
+
+#ifdef LDAP_PF_LOCAL
+#include <sys/stat.h>
+/* this should go in <ldap.h> as soon as it is accepted */
+#define LDAPI_MOD_URLEXT "x-mod"
+#endif /* LDAP_PF_LOCAL */
+
+#ifndef BALANCER_MODULE
+#ifdef LDAP_PF_INET6
+int slap_inet4or6 = AF_UNSPEC;
+#else /* ! INETv6 */
+int slap_inet4or6 = AF_INET;
+#endif /* ! INETv6 */
+
+/* globals */
+time_t starttime;
+struct runqueue_s slapd_rq;
+
+#ifdef LDAP_TCP_BUFFER
+int slapd_tcp_rmem;
+int slapd_tcp_wmem;
+#endif /* LDAP_TCP_BUFFER */
+
+volatile sig_atomic_t slapd_shutdown = 0;
+volatile sig_atomic_t slapd_gentle_shutdown = 0;
+volatile sig_atomic_t slapd_abrupt_shutdown = 0;
+#endif /* !BALANCER_MODULE */
+
+static int emfile;
+
+ldap_pvt_thread_mutex_t lload_wait_mutex;
+ldap_pvt_thread_cond_t lload_wait_cond;
+ldap_pvt_thread_cond_t lload_pause_cond;
+
+#ifndef SLAPD_MAX_DAEMON_THREADS
+#define SLAPD_MAX_DAEMON_THREADS 16
+#endif
+int lload_daemon_threads = 1;
+int lload_daemon_mask;
+
+struct event_base *listener_base = NULL;
+LloadListener **lload_listeners = NULL;
+static ldap_pvt_thread_t listener_tid, *daemon_tid;
+
+#ifndef RESOLV_CONF_PATH
+#define RESOLV_CONF_PATH "/etc/resolv.conf"
+#endif
+char *lload_resolvconf_path = RESOLV_CONF_PATH;
+
+struct event_base *daemon_base = NULL;
+struct evdns_base *dnsbase;
+
+struct event *lload_timeout_event;
+struct event *lload_stats_event;
+
+/*
+ * global lload statistics. Not mutex protected to preserve performance -
+ * increment is atomic, at most we risk a bit of inconsistency
+ */
+lload_global_stats_t lload_stats = {};
+
+#ifndef SLAPD_LISTEN_BACKLOG
+#define SLAPD_LISTEN_BACKLOG 1024
+#endif /* ! SLAPD_LISTEN_BACKLOG */
+
+#define DAEMON_ID(fd) ( fd & lload_daemon_mask )
+
+#ifdef HAVE_WINSOCK
+ldap_pvt_thread_mutex_t slapd_ws_mutex;
+SOCKET *slapd_ws_sockets;
+#define SD_READ 1
+#define SD_WRITE 2
+#define SD_ACTIVE 4
+#define SD_LISTENER 8
+#endif
+
+#ifdef HAVE_TCPD
+static ldap_pvt_thread_mutex_t sd_tcpd_mutex;
+#endif /* TCP Wrappers */
+
+typedef struct listener_item {
+ struct evconnlistener *listener;
+ ber_socket_t fd;
+} listener_item;
+
+typedef struct lload_daemon_st {
+ ldap_pvt_thread_mutex_t sd_mutex;
+
+ struct event_base *base;
+ struct event *wakeup_event;
+} lload_daemon_st;
+
+static lload_daemon_st lload_daemon[SLAPD_MAX_DAEMON_THREADS];
+
+static void daemon_wakeup_cb( evutil_socket_t sig, short what, void *arg );
+
+static void
+lloadd_close( ber_socket_t s )
+{
+ Debug( LDAP_DEBUG_CONNS, "lloadd_close: "
+ "closing fd=%ld\n",
+ (long)s );
+ tcp_close( s );
+}
+
+static void
+lload_free_listener_addresses( struct sockaddr **sal )
+{
+ struct sockaddr **sap;
+ if ( sal == NULL ) return;
+ for ( sap = sal; *sap != NULL; sap++ )
+ ch_free(*sap);
+ ch_free( sal );
+}
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+static int
+get_url_perms( char **exts, mode_t *perms, int *crit )
+{
+ int i;
+
+ assert( exts != NULL );
+ assert( perms != NULL );
+ assert( crit != NULL );
+
+ *crit = 0;
+ for ( i = 0; exts[i]; i++ ) {
+ char *type = exts[i];
+ int c = 0;
+
+ if ( type[0] == '!' ) {
+ c = 1;
+ type++;
+ }
+
+ if ( strncasecmp( type, LDAPI_MOD_URLEXT "=",
+ sizeof(LDAPI_MOD_URLEXT "=") - 1 ) == 0 ) {
+ char *value = type + ( sizeof(LDAPI_MOD_URLEXT "=") - 1 );
+ mode_t p = 0;
+ int j;
+
+ switch ( strlen( value ) ) {
+ case 4:
+ /* skip leading '0' */
+ if ( value[0] != '0' ) return LDAP_OTHER;
+ value++;
+
+ case 3:
+ for ( j = 0; j < 3; j++ ) {
+ int v;
+
+ v = value[j] - '0';
+
+ if ( v < 0 || v > 7 ) return LDAP_OTHER;
+
+ p |= v << 3 * ( 2 - j );
+ }
+ break;
+
+ case 10:
+ for ( j = 1; j < 10; j++ ) {
+ static mode_t m[] = { 0, S_IRUSR, S_IWUSR, S_IXUSR,
+ S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH,
+ S_IXOTH };
+ static const char c[] = "-rwxrwxrwx";
+
+ if ( value[j] == c[j] ) {
+ p |= m[j];
+
+ } else if ( value[j] != '-' ) {
+ return LDAP_OTHER;
+ }
+ }
+ break;
+
+ default:
+ return LDAP_OTHER;
+ }
+
+ *crit = c;
+ *perms = p;
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_OTHER;
+}
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+/* port = 0 indicates AF_LOCAL */
+static int
+lload_get_listener_addresses(
+ const char *host,
+ unsigned short port,
+ struct sockaddr ***sal )
+{
+ struct sockaddr **sap;
+
+#ifdef LDAP_PF_LOCAL
+ if ( port == 0 ) {
+ sap = *sal = ch_malloc( 2 * sizeof(void *) );
+
+ *sap = ch_calloc( 1, sizeof(struct sockaddr_un) );
+ sap[1] = NULL;
+
+ if ( strlen( host ) >
+ ( sizeof( ((struct sockaddr_un *)*sap)->sun_path ) - 1 ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
+ "domain socket path (%s) too long in URL\n",
+ host );
+ goto errexit;
+ }
+
+ (*sap)->sa_family = AF_LOCAL;
+ strcpy( ((struct sockaddr_un *)*sap)->sun_path, host );
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef HAVE_GETADDRINFO
+ struct addrinfo hints, *res, *sai;
+ int n, err;
+ char serv[7];
+
+ memset( &hints, '\0', sizeof(hints) );
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = slap_inet4or6;
+ snprintf( serv, sizeof(serv), "%d", port );
+
+ if ( (err = getaddrinfo( host, serv, &hints, &res )) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
+ "getaddrinfo() failed: %s\n",
+ AC_GAI_STRERROR(err) );
+ return -1;
+ }
+
+ sai = res;
+ for ( n = 2; ( sai = sai->ai_next ) != NULL; n++ ) {
+ /* EMPTY */;
+ }
+ sap = *sal = ch_calloc( n, sizeof(void *) );
+
+ *sap = NULL;
+
+ for ( sai = res; sai; sai = sai->ai_next ) {
+ if ( sai->ai_addr == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
+ "getaddrinfo ai_addr is NULL?\n" );
+ freeaddrinfo( res );
+ goto errexit;
+ }
+
+ switch ( sai->ai_family ) {
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+ *sap = ch_malloc( sizeof(struct sockaddr_in6) );
+ *(struct sockaddr_in6 *)*sap =
+ *((struct sockaddr_in6 *)sai->ai_addr);
+ break;
+#endif /* LDAP_PF_INET6 */
+ case AF_INET:
+ *sap = ch_malloc( sizeof(struct sockaddr_in) );
+ *(struct sockaddr_in *)*sap =
+ *((struct sockaddr_in *)sai->ai_addr);
+ break;
+ default:
+ *sap = NULL;
+ break;
+ }
+
+ if ( *sap != NULL ) {
+ (*sap)->sa_family = sai->ai_family;
+ sap++;
+ *sap = NULL;
+ }
+ }
+
+ freeaddrinfo( res );
+
+#else /* ! HAVE_GETADDRINFO */
+ int i, n = 1;
+ struct in_addr in;
+ struct hostent *he = NULL;
+
+ if ( host == NULL ) {
+ in.s_addr = htonl( INADDR_ANY );
+
+ } else if ( !inet_aton( host, &in ) ) {
+ he = gethostbyname( host );
+ if ( he == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
+ "invalid host %s\n",
+ host );
+ return -1;
+ }
+ for ( n = 0; he->h_addr_list[n]; n++ ) /* empty */;
+ }
+
+ sap = *sal = ch_malloc( ( n + 1 ) * sizeof(void *) );
+
+ for ( i = 0; i < n; i++ ) {
+ sap[i] = ch_calloc( 1, sizeof(struct sockaddr_in) );
+ sap[i]->sa_family = AF_INET;
+ ((struct sockaddr_in *)sap[i])->sin_port = htons( port );
+ AC_MEMCPY( &((struct sockaddr_in *)sap[i])->sin_addr,
+ he ? (struct in_addr *)he->h_addr_list[i] : &in,
+ sizeof(struct in_addr) );
+ }
+ sap[i] = NULL;
+#endif /* ! HAVE_GETADDRINFO */
+ }
+
+ return 0;
+
+errexit:
+ lload_free_listener_addresses(*sal);
+ return -1;
+}
+
+static int
+lload_open_listener(
+ const char *url,
+ LDAPURLDesc *lud,
+ int *listeners,
+ int *cur )
+{
+ int num, tmp, rc;
+ LloadListener l;
+ LloadListener *li;
+ unsigned short port;
+ int err, addrlen = 0;
+ struct sockaddr **sal = NULL, **psal;
+ int socktype = SOCK_STREAM; /* default to COTS */
+ ber_socket_t s;
+ char ebuf[128];
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+ /*
+ * use safe defaults
+ */
+ int crit = 1;
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+ assert( url );
+ assert( lud );
+
+ l.sl_url.bv_val = NULL;
+ l.sl_mute = 0;
+ l.sl_busy = 0;
+
+#ifndef HAVE_TLS
+ if ( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "TLS not supported (%s)\n",
+ url );
+ ldap_free_urldesc( lud );
+ return -1;
+ }
+
+ if ( !lud->lud_port ) lud->lud_port = LDAP_PORT;
+
+#else /* HAVE_TLS */
+ l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
+#endif /* HAVE_TLS */
+
+ l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
+
+#ifdef LDAP_TCP_BUFFER
+ l.sl_tcp_rmem = 0;
+ l.sl_tcp_wmem = 0;
+#endif /* LDAP_TCP_BUFFER */
+
+ port = (unsigned short)lud->lud_port;
+
+ tmp = ldap_pvt_url_scheme2proto( lud->lud_scheme );
+ if ( tmp == LDAP_PROTO_IPC ) {
+#ifdef LDAP_PF_LOCAL
+ if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) {
+ err = lload_get_listener_addresses( LDAPI_SOCK, 0, &sal );
+ } else {
+ err = lload_get_listener_addresses( lud->lud_host, 0, &sal );
+ }
+#else /* ! LDAP_PF_LOCAL */
+
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "URL scheme not supported: %s\n",
+ url );
+ ldap_free_urldesc( lud );
+ return -1;
+#endif /* ! LDAP_PF_LOCAL */
+ } else {
+ if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ||
+ strcmp( lud->lud_host, "*" ) == 0 ) {
+ err = lload_get_listener_addresses( NULL, port, &sal );
+ } else {
+ err = lload_get_listener_addresses( lud->lud_host, port, &sal );
+ }
+ }
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+ if ( lud->lud_exts ) {
+ err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit );
+ } else {
+ l.sl_perms = S_IRWXU | S_IRWXO;
+ }
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+ ldap_free_urldesc( lud );
+ if ( err ) {
+ lload_free_listener_addresses( sal );
+ return -1;
+ }
+
+ /* If we got more than one address returned, we need to make space
+ * for it in the lload_listeners array.
+ */
+ for ( num = 0; sal[num]; num++ ) /* empty */;
+ if ( num > 1 ) {
+ *listeners += num - 1;
+ lload_listeners = ch_realloc( lload_listeners,
+ ( *listeners + 1 ) * sizeof(LloadListener *) );
+ }
+
+ psal = sal;
+ while ( *sal != NULL ) {
+ char *af;
+ switch ( (*sal)->sa_family ) {
+ case AF_INET:
+ af = "IPv4";
+ break;
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+ af = "IPv6";
+ break;
+#endif /* LDAP_PF_INET6 */
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+ af = "Local";
+ break;
+#endif /* LDAP_PF_LOCAL */
+ default:
+ sal++;
+ continue;
+ }
+
+ s = socket( (*sal)->sa_family, socktype, 0 );
+ if ( s == AC_SOCKET_INVALID ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "%s socket() failed errno=%d (%s)\n",
+ af, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ sal++;
+ continue;
+ }
+ ber_pvt_socket_set_nonblock( s, 1 );
+ l.sl_sd = s;
+
+#ifdef LDAP_PF_LOCAL
+ if ( (*sal)->sa_family == AF_LOCAL ) {
+ unlink( ((struct sockaddr_un *)*sal)->sun_path );
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef SO_REUSEADDR
+ /* enable address reuse */
+ tmp = 1;
+ rc = setsockopt(
+ s, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): "
+ "setsockopt(SO_REUSEADDR) failed errno=%d (%s)\n",
+ (long)l.sl_sd, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+#endif /* SO_REUSEADDR */
+ }
+
+ switch ( (*sal)->sa_family ) {
+ case AF_INET:
+ addrlen = sizeof(struct sockaddr_in);
+ break;
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+#ifdef IPV6_V6ONLY
+ /* Try to use IPv6 sockets for IPv6 only */
+ tmp = 1;
+ rc = setsockopt( s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&tmp,
+ sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): "
+ "setsockopt(IPV6_V6ONLY) failed errno=%d (%s)\n",
+ (long)l.sl_sd, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+#endif /* IPV6_V6ONLY */
+ addrlen = sizeof(struct sockaddr_in6);
+ break;
+#endif /* LDAP_PF_INET6 */
+
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+#ifdef LOCAL_CREDS
+ {
+ int one = 1;
+ setsockopt( s, 0, LOCAL_CREDS, &one, sizeof(one) );
+ }
+#endif /* LOCAL_CREDS */
+
+ addrlen = sizeof(struct sockaddr_un);
+ break;
+#endif /* LDAP_PF_LOCAL */
+ }
+
+#ifdef LDAP_PF_LOCAL
+ /* create socket with all permissions set for those systems
+ * that honor permissions on sockets (e.g. Linux); typically,
+ * only write is required. To exploit filesystem permissions,
+ * place the socket in a directory and use directory's
+ * permissions. Need write perms to the directory to
+ * create/unlink the socket; likely need exec perms to access
+ * the socket (ITS#4709) */
+ {
+ mode_t old_umask = 0;
+
+ if ( (*sal)->sa_family == AF_LOCAL ) {
+ old_umask = umask( 0 );
+ }
+#endif /* LDAP_PF_LOCAL */
+ rc = bind( s, *sal, addrlen );
+#ifdef LDAP_PF_LOCAL
+ if ( old_umask != 0 ) {
+ umask( old_umask );
+ }
+ }
+#endif /* LDAP_PF_LOCAL */
+ if ( rc ) {
+ err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "bind(%ld) failed errno=%d (%s)\n",
+ (long)l.sl_sd, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ tcp_close( s );
+ sal++;
+ continue;
+ }
+
+ switch ( (*sal)->sa_family ) {
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL: {
+ char *path = ((struct sockaddr_un *)*sal)->sun_path;
+ l.sl_name.bv_len = strlen( path ) + STRLENOF("PATH=");
+ l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len + 1 );
+ snprintf( l.sl_name.bv_val, l.sl_name.bv_len + 1, "PATH=%s",
+ path );
+ } break;
+#endif /* LDAP_PF_LOCAL */
+
+ case AF_INET: {
+ char addr[INET_ADDRSTRLEN];
+ const char *s;
+#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_NTOP)
+ s = inet_ntop( AF_INET,
+ &((struct sockaddr_in *)*sal)->sin_addr, addr,
+ sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ s = inet_ntoa( ((struct sockaddr_in *)*sal)->sin_addr );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ if ( !s ) s = SLAP_STRING_UNKNOWN;
+ port = ntohs( ((struct sockaddr_in *)*sal)->sin_port );
+ l.sl_name.bv_val =
+ ch_malloc( sizeof("IP=255.255.255.255:65535") );
+ snprintf( l.sl_name.bv_val,
+ sizeof("IP=255.255.255.255:65535"), "IP=%s:%d", s,
+ port );
+ l.sl_name.bv_len = strlen( l.sl_name.bv_val );
+ } break;
+
+#ifdef LDAP_PF_INET6
+ case AF_INET6: {
+ char addr[INET6_ADDRSTRLEN];
+ const char *s;
+ s = inet_ntop( AF_INET6,
+ &((struct sockaddr_in6 *)*sal)->sin6_addr, addr,
+ sizeof(addr) );
+ if ( !s ) s = SLAP_STRING_UNKNOWN;
+ port = ntohs( ((struct sockaddr_in6 *)*sal)->sin6_port );
+ l.sl_name.bv_len = strlen( s ) + sizeof("IP=[]:65535");
+ l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len );
+ snprintf( l.sl_name.bv_val, l.sl_name.bv_len, "IP=[%s]:%d", s,
+ port );
+ l.sl_name.bv_len = strlen( l.sl_name.bv_val );
+ } break;
+#endif /* LDAP_PF_INET6 */
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "unsupported address family (%d)\n",
+ (int)(*sal)->sa_family );
+ break;
+ }
+
+ AC_MEMCPY( &l.sl_sa, *sal, addrlen );
+ ber_str2bv( url, 0, 1, &l.sl_url );
+ li = ch_malloc( sizeof(LloadListener) );
+ *li = l;
+ lload_listeners[*cur] = li;
+ (*cur)++;
+ sal++;
+ }
+
+ lload_free_listener_addresses( psal );
+
+ if ( l.sl_url.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+ "failed on %s\n",
+ url );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "lload_open_listener: "
+ "listener initialized %s\n",
+ l.sl_url.bv_val );
+
+ return 0;
+}
+
+int
+lload_open_new_listener( const char *url, LDAPURLDesc *lud )
+{
+ int rc, i, j = 0;
+
+ for ( i = 0; lload_listeners && lload_listeners[i] != NULL;
+ i++ ) /* count */
+ ;
+ j = i;
+
+ i++;
+ lload_listeners = ch_realloc(
+ lload_listeners, ( i + 1 ) * sizeof(LloadListener *) );
+
+ rc = lload_open_listener( url, lud, &i, &j );
+ lload_listeners[j] = NULL;
+ return rc;
+}
+
+int lloadd_inited = 0;
+
+int
+lloadd_listeners_init( const char *urls )
+{
+ int i, j, n;
+ char **u;
+ LDAPURLDesc *lud;
+
+ Debug( LDAP_DEBUG_ARGS, "lloadd_listeners_init: %s\n",
+ urls ? urls : "<null>" );
+
+#ifdef HAVE_TCPD
+ ldap_pvt_thread_mutex_init( &sd_tcpd_mutex );
+#endif /* TCP Wrappers */
+
+ if ( urls == NULL ) urls = "ldap:///";
+
+ u = ldap_str2charray( urls, " " );
+
+ if ( u == NULL || u[0] == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
+ "no urls (%s) provided\n",
+ urls );
+ if ( u ) ldap_charray_free( u );
+ return -1;
+ }
+
+ for ( i = 0; u[i] != NULL; i++ ) {
+ Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
+ "listen on %s\n",
+ u[i] );
+ }
+
+ if ( i == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
+ "no listeners to open (%s)\n",
+ urls );
+ ldap_charray_free( u );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
+ "%d listeners to open...\n",
+ i );
+ lload_listeners = ch_malloc( ( i + 1 ) * sizeof(LloadListener *) );
+
+ for ( n = 0, j = 0; u[n]; n++ ) {
+ if ( ldap_url_parse_ext( u[n], &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
+ "could not parse url %s\n",
+ u[n] );
+ ldap_charray_free( u );
+ return -1;
+ }
+
+ if ( lload_open_listener( u[n], lud, &i, &j ) ) {
+ ldap_charray_free( u );
+ return -1;
+ }
+ }
+ lload_listeners[j] = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
+ "%d listeners opened\n",
+ i );
+
+ ldap_charray_free( u );
+
+ return !i;
+}
+
+int
+lloadd_daemon_destroy( void )
+{
+ epoch_shutdown();
+ if ( lloadd_inited ) {
+ int i;
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ ldap_pvt_thread_mutex_destroy( &lload_daemon[i].sd_mutex );
+ if ( lload_daemon[i].wakeup_event ) {
+ event_free( lload_daemon[i].wakeup_event );
+ }
+ if ( lload_daemon[i].base ) {
+ event_base_free( lload_daemon[i].base );
+ }
+ }
+
+ event_free( lload_stats_event );
+ event_free( lload_timeout_event );
+
+ event_base_free( daemon_base );
+ daemon_base = NULL;
+
+ lloadd_inited = 0;
+#ifdef HAVE_TCPD
+ ldap_pvt_thread_mutex_destroy( &sd_tcpd_mutex );
+#endif /* TCP Wrappers */
+ }
+
+ return 0;
+}
+
+static void
+destroy_listeners( void )
+{
+ LloadListener *lr, **ll = lload_listeners;
+
+ if ( ll == NULL ) return;
+
+ ldap_pvt_thread_join( listener_tid, (void *)NULL );
+
+ while ( (lr = *ll++) != NULL ) {
+ if ( lr->sl_url.bv_val ) {
+ ber_memfree( lr->sl_url.bv_val );
+ }
+
+ if ( lr->sl_name.bv_val ) {
+ ber_memfree( lr->sl_name.bv_val );
+ }
+
+#ifdef LDAP_PF_LOCAL
+ if ( lr->sl_sa.sa_addr.sa_family == AF_LOCAL ) {
+ unlink( lr->sl_sa.sa_un_addr.sun_path );
+ }
+#endif /* LDAP_PF_LOCAL */
+
+ evconnlistener_free( lr->listener );
+
+ free( lr );
+ }
+
+ free( lload_listeners );
+ lload_listeners = NULL;
+
+ if ( listener_base ) {
+ event_base_free( listener_base );
+ }
+}
+
+static void
+lload_listener(
+ struct evconnlistener *listener,
+ ber_socket_t s,
+ struct sockaddr *a,
+ int len,
+ void *arg )
+{
+ LloadListener *sl = arg;
+ LloadConnection *c;
+ Sockaddr *from = (Sockaddr *)a;
+ char peername[LDAP_IPADDRLEN];
+ struct berval peerbv = BER_BVC(peername);
+ int cflag;
+ int tid;
+ char ebuf[128];
+
+ Debug( LDAP_DEBUG_TRACE, ">>> lload_listener(%s)\n", sl->sl_url.bv_val );
+
+ peername[0] = '\0';
+
+ /* Resume the listener FD to allow concurrent-processing of
+ * additional incoming connections.
+ */
+ sl->sl_busy = 0;
+
+ tid = DAEMON_ID(s);
+
+ Debug( LDAP_DEBUG_CONNS, "lload_listener: "
+ "listen=%ld, new connection fd=%ld\n",
+ (long)sl->sl_sd, (long)s );
+
+#if defined(SO_KEEPALIVE) || defined(TCP_NODELAY)
+#ifdef LDAP_PF_LOCAL
+ /* for IPv4 and IPv6 sockets only */
+ if ( from->sa_addr.sa_family != AF_LOCAL )
+#endif /* LDAP_PF_LOCAL */
+ {
+ int rc;
+ int tmp;
+#ifdef SO_KEEPALIVE
+ /* enable keep alives */
+ tmp = 1;
+ rc = setsockopt(
+ s, SOL_SOCKET, SO_KEEPALIVE, (char *)&tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener(%ld): "
+ "setsockopt(SO_KEEPALIVE) failed errno=%d (%s)\n",
+ (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+#endif /* SO_KEEPALIVE */
+#ifdef TCP_NODELAY
+ /* enable no delay */
+ tmp = 1;
+ rc = setsockopt(
+ s, IPPROTO_TCP, TCP_NODELAY, (char *)&tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener(%ld): "
+ "setsockopt(TCP_NODELAY) failed errno=%d (%s)\n",
+ (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+#endif /* TCP_NODELAY */
+ }
+#endif /* SO_KEEPALIVE || TCP_NODELAY */
+
+ if ( sl->sl_is_proxied ) {
+ if ( !proxyp( s, from ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_listener: "
+ "proxyp(%ld) failed\n",
+ (long)s );
+ lloadd_close( s );
+ return;
+ }
+ }
+
+ cflag = 0;
+ switch ( from->sa_addr.sa_family ) {
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+ cflag |= CONN_IS_IPC;
+
+ /* FIXME: apparently accept doesn't fill the sun_path member */
+ sprintf( peername, "PATH=%s", sl->sl_sa.sa_un_addr.sun_path );
+ break;
+#endif /* LDAP_PF_LOCAL */
+
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+#endif /* LDAP_PF_INET6 */
+ case AF_INET:
+ ldap_pvt_sockaddrstr( from, &peerbv );
+ break;
+
+ default:
+ lloadd_close( s );
+ return;
+ }
+
+#ifdef HAVE_TLS
+ if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS;
+#endif
+ c = client_init( s, peername, lload_daemon[tid].base, cflag );
+
+ if ( !c ) {
+ Debug( LDAP_DEBUG_ANY, "lload_listener: "
+ "client_init(%ld, %s, %s) failed\n",
+ (long)s, peername, sl->sl_name.bv_val );
+ lloadd_close( s );
+ }
+
+ return;
+}
+
+static void *
+lload_listener_thread( void *ctx )
+{
+ /* ITS#9984 Survive the listeners being paused if we run out of fds */
+ int rc = event_base_loop( listener_base, EVLOOP_NO_EXIT_ON_EMPTY );
+ Debug( LDAP_DEBUG_ANY, "lload_listener_thread: "
+ "event loop finished: rc=%d\n",
+ rc );
+
+ return (void *)NULL;
+}
+
+static void
+listener_error_cb( struct evconnlistener *lev, void *arg )
+{
+ LloadListener *l = arg;
+ int err = EVUTIL_SOCKET_ERROR();
+
+ assert( l->listener == lev );
+ if (
+#ifdef EMFILE
+ err == EMFILE ||
+#endif /* EMFILE */
+#ifdef ENFILE
+ err == ENFILE ||
+#endif /* ENFILE */
+ 0 ) {
+ ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
+ emfile++;
+ /* Stop listening until an existing session closes */
+ l->sl_mute = 1;
+ evconnlistener_disable( lev );
+ ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
+ Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
+ "too many open files, cannot accept new connections on "
+ "url=%s\n",
+ l->sl_url.bv_val );
+ } else {
+ char ebuf[128];
+ Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
+ "received an error on a listener, shutting down: '%s'\n",
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ event_base_loopexit( l->base, NULL );
+ }
+}
+
+void
+listeners_reactivate( void )
+{
+ int i;
+
+ ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
+ for ( i = 0; emfile && lload_listeners[i] != NULL; i++ ) {
+ LloadListener *lr = lload_listeners[i];
+
+ if ( lr->sl_sd == AC_SOCKET_INVALID ) continue;
+ if ( lr->sl_mute ) {
+ emfile--;
+ evconnlistener_enable( lr->listener );
+ lr->sl_mute = 0;
+ Debug( LDAP_DEBUG_CONNS, "listeners_reactivate: "
+ "reactivated listener url=%s\n",
+ lr->sl_url.bv_val );
+ }
+ }
+ if ( emfile && lload_listeners[i] == NULL ) {
+ /* Walked the entire list without enabling anything; emfile
+ * counter is stale. Reset it. */
+ emfile = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
+}
+
+static int
+lload_listener_activate( void )
+{
+ struct evconnlistener *listener;
+ int l, rc;
+ char ebuf[128];
+
+ listener_base = event_base_new();
+ if ( !listener_base ) return -1;
+
+ for ( l = 0; lload_listeners[l] != NULL; l++ ) {
+ if ( lload_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue;
+
+ /* FIXME: TCP-only! */
+#ifdef LDAP_TCP_BUFFER
+ if ( 1 ) {
+ int origsize, size, realsize, rc;
+ socklen_t optlen;
+
+ size = 0;
+ if ( lload_listeners[l]->sl_tcp_rmem > 0 ) {
+ size = lload_listeners[l]->sl_tcp_rmem;
+ } else if ( slapd_tcp_rmem > 0 ) {
+ size = slapd_tcp_rmem;
+ }
+
+ if ( size > 0 ) {
+ optlen = sizeof(origsize);
+ rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_RCVBUF, (void *)&origsize, &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, AC_STRERROR_R( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ optlen = sizeof(size);
+ rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_RCVBUF, (const void *)&size, optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "setsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ optlen = sizeof(realsize);
+ rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_RCVBUF, (void *)&realsize, &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "url=%s (#%d) RCVBUF original size=%d requested "
+ "size=%d real size=%d\n",
+ lload_listeners[l]->sl_url.bv_val, l, origsize, size,
+ realsize );
+ }
+
+ size = 0;
+ if ( lload_listeners[l]->sl_tcp_wmem > 0 ) {
+ size = lload_listeners[l]->sl_tcp_wmem;
+ } else if ( slapd_tcp_wmem > 0 ) {
+ size = slapd_tcp_wmem;
+ }
+
+ if ( size > 0 ) {
+ optlen = sizeof(origsize);
+ rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_SNDBUF, (void *)&origsize, &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+ err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ optlen = sizeof(size);
+ rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_SNDBUF, (const void *)&size, optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "setsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+ err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ optlen = sizeof(realsize);
+ rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
+ SO_SNDBUF, (void *)&realsize, &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+ err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "url=%s (#%d) SNDBUF original size=%d requested "
+ "size=%d real size=%d\n",
+ lload_listeners[l]->sl_url.bv_val, l, origsize, size,
+ realsize );
+ }
+ }
+#endif /* LDAP_TCP_BUFFER */
+
+ lload_listeners[l]->sl_busy = 1;
+ listener = evconnlistener_new( listener_base, lload_listener,
+ lload_listeners[l],
+ LEV_OPT_THREADSAFE|LEV_OPT_DEFERRED_ACCEPT,
+ SLAPD_LISTEN_BACKLOG, lload_listeners[l]->sl_sd );
+ if ( !listener ) {
+ int err = sock_errno();
+
+#ifdef LDAP_PF_INET6
+ /* If error is EADDRINUSE, we are trying to listen to INADDR_ANY and
+ * we are already listening to in6addr_any, then we want to ignore
+ * this and continue.
+ */
+ if ( err == EADDRINUSE ) {
+ int i;
+ struct sockaddr_in sa = lload_listeners[l]->sl_sa.sa_in_addr;
+ struct sockaddr_in6 sa6;
+
+ if ( sa.sin_family == AF_INET &&
+ sa.sin_addr.s_addr == htonl( INADDR_ANY ) ) {
+ for ( i = 0; i < l; i++ ) {
+ sa6 = lload_listeners[i]->sl_sa.sa_in6_addr;
+ if ( sa6.sin6_family == AF_INET6 &&
+ !memcmp( &sa6.sin6_addr, &in6addr_any,
+ sizeof(struct in6_addr) ) ) {
+ break;
+ }
+ }
+
+ if ( i < l ) {
+ /* We are already listening to in6addr_any */
+ Debug( LDAP_DEBUG_CONNS, "lload_listener_activate: "
+ "Attempt to listen to 0.0.0.0 failed, "
+ "already listening on ::, assuming IPv4 "
+ "included\n" );
+ lloadd_close( lload_listeners[l]->sl_sd );
+ lload_listeners[l]->sl_sd = AC_SOCKET_INVALID;
+ continue;
+ }
+ }
+ }
+#endif /* LDAP_PF_INET6 */
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
+ "listen(%s, 5) failed errno=%d (%s)\n",
+ lload_listeners[l]->sl_url.bv_val, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ return -1;
+ }
+
+ lload_listeners[l]->base = listener_base;
+ lload_listeners[l]->listener = listener;
+ evconnlistener_set_error_cb( listener, listener_error_cb );
+ }
+
+ rc = ldap_pvt_thread_create(
+ &listener_tid, 0, lload_listener_thread, lload_listeners[l] );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_listener_activate(%d): "
+ "submit failed (%d)\n",
+ lload_listeners[l]->sl_sd, rc );
+ }
+ return rc;
+}
+
+static void *
+lloadd_io_task( void *ptr )
+{
+ int rc;
+ int tid = (ldap_pvt_thread_t *)ptr - daemon_tid;
+ struct event_base *base = lload_daemon[tid].base;
+ struct event *event;
+
+ event = event_new( base, -1, EV_WRITE, daemon_wakeup_cb, ptr );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd_io_task: "
+ "failed to set up the wakeup event\n" );
+ return (void *)-1;
+ }
+ event_add( event, NULL );
+ lload_daemon[tid].wakeup_event = event;
+
+ /* run */
+ rc = event_base_dispatch( base );
+ Debug( LDAP_DEBUG_ANY, "lloadd_io_task: "
+ "Daemon %d, event loop finished: rc=%d\n",
+ tid, rc );
+
+ if ( !slapd_gentle_shutdown ) {
+ slapd_abrupt_shutdown = 1;
+ }
+
+ return NULL;
+}
+
+int
+lloadd_daemon( struct event_base *daemon_base )
+{
+ int i, rc;
+ LloadTier *tier;
+ struct event_base *base;
+ struct event *event;
+ struct timeval second = { 1, 0 };
+
+ assert( daemon_base != NULL );
+
+ dnsbase = evdns_base_new( daemon_base, 0 );
+ if ( !dnsbase ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd startup: "
+ "failed to set up for async name resolution\n" );
+ return -1;
+ }
+
+ /*
+ * ITS#10070: Allow both operation without working DNS (test environments)
+ * and e.g. containers that don't have a /etc/resolv.conf but do have a
+ * server listening on 127.0.0.1 which is the default.
+ */
+ (void)evdns_base_resolv_conf_parse( dnsbase,
+ DNS_OPTION_NAMESERVERS|DNS_OPTION_HOSTSFILE,
+ lload_resolvconf_path );
+
+ if ( lload_daemon_threads > SLAPD_MAX_DAEMON_THREADS )
+ lload_daemon_threads = SLAPD_MAX_DAEMON_THREADS;
+
+ daemon_tid =
+ ch_malloc( lload_daemon_threads * sizeof(ldap_pvt_thread_t) );
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ base = event_base_new();
+ if ( !base ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd startup: "
+ "failed to acquire event base for an I/O thread\n" );
+ return -1;
+ }
+ lload_daemon[i].base = base;
+
+ ldap_pvt_thread_mutex_init( &lload_daemon[i].sd_mutex );
+ /* threads that handle client and upstream sockets */
+ rc = ldap_pvt_thread_create(
+ &daemon_tid[i], 0, lloadd_io_task, &daemon_tid[i] );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd startup: "
+ "listener ldap_pvt_thread_create failed (%d)\n",
+ rc );
+ return rc;
+ }
+ }
+
+ if ( (rc = lload_listener_activate()) != 0 ) {
+ return rc;
+ }
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ if ( tier->t_type.tier_startup( tier ) ) {
+ return -1;
+ }
+ }
+
+ event = event_new( daemon_base, -1, EV_TIMEOUT|EV_PERSIST,
+ lload_tiers_update, NULL );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd: "
+ "failed to allocate stats update event\n" );
+ return -1;
+ }
+ lload_stats_event = event;
+ event_add( event, &second );
+
+ event = evtimer_new( daemon_base, operations_timeout, event_self_cbarg() );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd: "
+ "failed to allocate timeout event\n" );
+ return -1;
+ }
+ lload_timeout_event = event;
+
+ /* TODO: should we just add it with any timeout and re-add when the timeout
+ * changes? */
+ if ( lload_timeout_api ) {
+ event_add( event, lload_timeout_api );
+ }
+
+ checked_lock( &lload_wait_mutex );
+ lloadd_inited = 1;
+ ldap_pvt_thread_cond_signal( &lload_wait_cond );
+ checked_unlock( &lload_wait_mutex );
+#if !defined(BALANCER_MODULE) && defined(HAVE_SYSTEMD)
+ rc = sd_notify( 1, "READY=1" );
+ if ( rc < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lloadd startup: "
+ "systemd sd_notify failed (%d)\n", rc );
+ }
+#endif /* !BALANCER_MODULE && HAVE_SYSTEMD */
+
+ rc = event_base_dispatch( daemon_base );
+ Debug( LDAP_DEBUG_ANY, "lloadd shutdown: "
+ "Main event loop finished: rc=%d\n",
+ rc );
+
+ /* shutdown */
+ event_base_loopexit( listener_base, 0 );
+
+ /* wait for the listener threads to complete */
+ destroy_listeners();
+
+ /* Mark upstream connections closing and prevent from opening new ones */
+ lload_tiers_shutdown();
+
+ /* Do the same for clients */
+ clients_destroy( 1 );
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ /*
+ * https://github.com/libevent/libevent/issues/623
+ * deleting the event doesn't notify the base, just activate it and
+ * let it delete itself
+ */
+ event_active( lload_daemon[i].wakeup_event, EV_READ, 0 );
+ }
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ ldap_pvt_thread_join( daemon_tid[i], (void *)NULL );
+ }
+
+#ifndef BALANCER_MODULE
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ int t = ldap_pvt_thread_pool_backload( &connection_pool );
+ Debug( LDAP_DEBUG_ANY, "lloadd shutdown: "
+ "waiting for %d operations/tasks to finish\n",
+ t );
+ }
+ ldap_pvt_thread_pool_close( &connection_pool, 1 );
+#endif
+
+ lload_tiers_destroy();
+ clients_destroy( 0 );
+ lload_bindconf_free( &bindconf );
+ evdns_base_free( dnsbase, 0 );
+
+ ch_free( daemon_tid );
+ daemon_tid = NULL;
+
+ lloadd_daemon_destroy();
+
+ /* If we're a slapd module, let the thread that initiated the shut down
+ * know we've finished */
+ checked_lock( &lload_wait_mutex );
+ ldap_pvt_thread_cond_signal( &lload_wait_cond );
+ checked_unlock( &lload_wait_mutex );
+
+ return 0;
+}
+
+static void
+daemon_wakeup_cb( evutil_socket_t sig, short what, void *arg )
+{
+ int tid = (ldap_pvt_thread_t *)arg - daemon_tid;
+
+ Debug( LDAP_DEBUG_TRACE, "daemon_wakeup_cb: "
+ "Daemon thread %d woken up\n",
+ tid );
+ event_del( lload_daemon[tid].wakeup_event );
+}
+
+LloadChange lload_change = { .type = LLOAD_CHANGE_UNDEFINED };
+
+#ifdef BALANCER_MODULE
+int
+backend_conn_cb( ldap_pvt_thread_start_t *start, void *startarg, void *arg )
+{
+ LloadConnection *c = startarg;
+ LloadBackend *b = arg;
+
+ if ( b == NULL || c->c_backend == b ) {
+ CONNECTION_LOCK_DESTROY(c);
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef HAVE_TLS
+int
+client_tls_cb( ldap_pvt_thread_start_t *start, void *startarg, void *arg )
+{
+ LloadConnection *c = startarg;
+
+ if ( c->c_destroy == client_destroy &&
+ c->c_is_tls == LLOAD_TLS_ESTABLISHED ) {
+ CONNECTION_LOCK_DESTROY(c);
+ return 1;
+ }
+ return 0;
+}
+#endif /* HAVE_TLS */
+
+static int
+detach_linked_backend_cb( LloadConnection *client, LloadBackend *b )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( client->c_backend != b ) {
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_CONNS, "detach_linked_backend_cb: "
+ "detaching backend '%s' from connid=%lu%s\n",
+ b->b_name.bv_val, client->c_connid,
+ client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND ?
+ " and closing the connection" :
+ "" );
+
+ /* We were approached from the connection list */
+ assert( IS_ALIVE( client, c_refcnt ) );
+
+ assert( client->c_restricted == LLOAD_OP_RESTRICTED_WRITE ||
+ client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND );
+ if ( client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND ) {
+ int gentle = 1;
+ CONNECTION_LOCK(client);
+ rc = lload_connection_close( client, &gentle );
+ CONNECTION_UNLOCK(client);
+ }
+
+ client->c_restricted = LLOAD_OP_NOT_RESTRICTED;
+ client->c_restricted_at = 0;
+ client->c_restricted_inflight = 0;
+
+ return rc;
+}
+
+void
+lload_handle_backend_invalidation( LloadChange *change )
+{
+ LloadBackend *b = change->target;
+ LloadTier *tier = b->b_tier;
+
+ assert( change->object == LLOAD_BACKEND );
+
+ if ( change->type == LLOAD_CHANGE_ADD ) {
+ BackendInfo *mi = backend_info( "monitor" );
+
+ if ( mi ) {
+ monitor_extra_t *mbe = mi->bi_extra;
+ if ( mbe->is_configured() ) {
+ lload_monitor_backend_init( mi, tier->t_monitor, b );
+ }
+ }
+
+ if ( tier->t_type.tier_change ) {
+ tier->t_type.tier_change( tier, change );
+ }
+
+ checked_lock( &b->b_mutex );
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ return;
+ } else if ( change->type == LLOAD_CHANGE_DEL ) {
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, handle_pdus, backend_conn_cb, b );
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, upstream_bind, backend_conn_cb, b );
+
+ checked_lock( &clients_mutex );
+ connections_walk(
+ &clients_mutex, &clients,
+ (CONNCB)detach_linked_backend_cb, b );
+ checked_unlock( &clients_mutex );
+
+ if ( tier->t_type.tier_change ) {
+ tier->t_type.tier_change( tier, change );
+ }
+ lload_backend_destroy( b );
+ return;
+ }
+ assert( change->type == LLOAD_CHANGE_MODIFY );
+
+ /*
+ * A change that can't be handled gracefully, terminate all connections and
+ * start over.
+ */
+ if ( change->flags.backend & LLOAD_BACKEND_MOD_OTHER ) {
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, handle_pdus, backend_conn_cb, b );
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, upstream_bind, backend_conn_cb, b );
+ checked_lock( &b->b_mutex );
+ backend_reset( b, 0 );
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ return;
+ }
+
+ /*
+ * Handle changes to number of connections:
+ * - a change might get the connection limit above the pool size:
+ * - consider closing (in order of priority?):
+ * - connections awaiting connect() completion
+ * - connections currently preparing
+ * - bind connections over limit (which is 0 if 'feature vc' is on
+ * - regular connections over limit
+ * - below pool size
+ * - call backend_retry if there are no opening connections
+ * - one pool size above and one below the configured size
+ * - still close the ones above limit, it should sort itself out
+ * the only issue is if a closing connection isn't guaranteed to do
+ * that at some point
+ */
+ if ( change->flags.backend & LLOAD_BACKEND_MOD_CONNS ) {
+ int bind_requested = 0, need_close = 0, need_open = 0;
+ LloadConnection *c;
+
+ bind_requested =
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ (lload_features & LLOAD_FEATURE_VC) ? 0 :
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ b->b_numbindconns;
+
+ if ( b->b_bindavail > bind_requested ) {
+ need_close += b->b_bindavail - bind_requested;
+ } else if ( b->b_bindavail < bind_requested ) {
+ need_open = 1;
+ }
+
+ if ( b->b_active > b->b_numconns ) {
+ need_close += b->b_active - b->b_numconns;
+ } else if ( b->b_active < b->b_numconns ) {
+ need_open = 1;
+ }
+
+ if ( !need_open ) {
+ need_close += b->b_opening;
+
+ while ( !LDAP_LIST_EMPTY( &b->b_connecting ) ) {
+ LloadPendingConnection *p = LDAP_LIST_FIRST( &b->b_connecting );
+
+ LDAP_LIST_REMOVE( p, next );
+ event_free( p->event );
+ evutil_closesocket( p->fd );
+ ch_free( p );
+ b->b_opening--;
+ need_close--;
+ }
+ }
+
+ if ( need_close || !need_open ) {
+ /* It might be too late to repurpose a preparing connection, just
+ * close them all */
+ while ( !LDAP_CIRCLEQ_EMPTY( &b->b_preparing ) ) {
+ c = LDAP_CIRCLEQ_FIRST( &b->b_preparing );
+
+ event_del( c->c_read_event );
+ CONNECTION_LOCK_DESTROY(c);
+ assert( c == NULL );
+ b->b_opening--;
+ need_close--;
+ }
+ if ( event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) ) {
+ event_del( b->b_retry_event );
+ b->b_opening--;
+ }
+ assert( b->b_opening == 0 );
+ }
+
+ if ( b->b_bindavail > bind_requested ) {
+ int diff = b->b_bindavail - bind_requested;
+
+ assert( need_close >= diff );
+
+ LDAP_CIRCLEQ_FOREACH ( c, &b->b_bindconns, c_next ) {
+ int gentle = 1;
+
+ lload_connection_close( c, &gentle );
+ need_close--;
+ diff--;
+ if ( !diff ) {
+ break;
+ }
+ }
+ assert( diff == 0 );
+ }
+
+ if ( b->b_active > b->b_numconns ) {
+ int diff = b->b_active - b->b_numconns;
+
+ assert( need_close >= diff );
+
+ LDAP_CIRCLEQ_FOREACH ( c, &b->b_conns, c_next ) {
+ int gentle = 1;
+
+ lload_connection_close( c, &gentle );
+ need_close--;
+ diff--;
+ if ( !diff ) {
+ break;
+ }
+ }
+ assert( diff == 0 );
+ }
+ assert( need_close == 0 );
+
+ if ( need_open ) {
+ checked_lock( &b->b_mutex );
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ }
+ }
+}
+
+void
+lload_handle_tier_invalidation( LloadChange *change )
+{
+ LloadTier *tier;
+
+ assert( change->object == LLOAD_TIER );
+ tier = change->target;
+
+ if ( change->type == LLOAD_CHANGE_ADD ) {
+ BackendInfo *mi = backend_info( "monitor" );
+
+ if ( mi ) {
+ monitor_extra_t *mbe = mi->bi_extra;
+ if ( mbe->is_configured() ) {
+ lload_monitor_tier_init( mi, tier );
+ }
+ }
+
+ tier->t_type.tier_startup( tier );
+ if ( LDAP_STAILQ_EMPTY( &tiers ) ) {
+ LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next );
+ }
+ return;
+ } else if ( change->type == LLOAD_CHANGE_DEL ) {
+ LDAP_STAILQ_REMOVE( &tiers, tier, LloadTier, t_next );
+ tier->t_type.tier_reset( tier, 1 );
+ tier->t_type.tier_destroy( tier );
+ return;
+ }
+ assert( change->type == LLOAD_CHANGE_MODIFY );
+
+ if ( tier->t_type.tier_change ) {
+ tier->t_type.tier_change( tier, change );
+ }
+}
+
+void
+lload_handle_global_invalidation( LloadChange *change )
+{
+ assert( change->type == LLOAD_CHANGE_MODIFY );
+ assert( change->object == LLOAD_DAEMON );
+
+ if ( change->flags.daemon & LLOAD_DAEMON_MOD_THREADS ) {
+ /* walk the task queue to remove any tasks belonging to us. */
+ /* TODO: initiate a full module restart, everything will fall into
+ * place at that point */
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, handle_pdus, backend_conn_cb, NULL );
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, upstream_bind, backend_conn_cb, NULL );
+ assert(0);
+ return;
+ }
+
+ if ( change->flags.daemon & LLOAD_DAEMON_MOD_FEATURES ) {
+ lload_features_t feature_diff =
+ lload_features ^ ( ~(uintptr_t)change->target );
+ /* Feature change handling:
+ * - VC (TODO):
+ * - on: terminate all bind connections
+ * - off: cancel all bind operations in progress, reopen bind connections
+ * - ProxyAuthz:
+ * - on: nothing needed
+ * - off: clear c_auth/privileged on each client
+ * - read pause (WIP):
+ * - nothing needed?
+ */
+
+ assert( change->target );
+ if ( feature_diff & LLOAD_FEATURE_VC ) {
+ assert(0);
+ feature_diff &= ~LLOAD_FEATURE_VC;
+ }
+ if ( feature_diff & LLOAD_FEATURE_PAUSE ) {
+ feature_diff &= ~LLOAD_FEATURE_PAUSE;
+ }
+ if ( feature_diff & LLOAD_FEATURE_PROXYAUTHZ ) {
+ if ( !(lload_features & LLOAD_FEATURE_PROXYAUTHZ) ) {
+ LloadConnection *c;
+ /* We switched proxyauthz off */
+ LDAP_CIRCLEQ_FOREACH ( c, &clients, c_next ) {
+ if ( !BER_BVISNULL( &c->c_auth ) ) {
+ ber_memfree( c->c_auth.bv_val );
+ BER_BVZERO( &c->c_auth );
+ }
+ if ( c->c_type == LLOAD_C_PRIVILEGED ) {
+ c->c_type = LLOAD_C_OPEN;
+ }
+ }
+ }
+ feature_diff &= ~LLOAD_FEATURE_PROXYAUTHZ;
+ }
+ assert( !feature_diff );
+ }
+
+#ifdef HAVE_TLS
+ if ( change->flags.daemon & LLOAD_DAEMON_MOD_TLS ) {
+ /* terminate all clients with TLS set up */
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, handle_pdus, client_tls_cb, NULL );
+ if ( !LDAP_CIRCLEQ_EMPTY( &clients ) ) {
+ LloadConnection *c = LDAP_CIRCLEQ_FIRST( &clients );
+ unsigned long first_connid = c->c_connid;
+
+ while ( c ) {
+ LloadConnection *next =
+ LDAP_CIRCLEQ_LOOP_NEXT( &clients, c, c_next );
+ if ( c->c_is_tls ) {
+ CONNECTION_LOCK_DESTROY(c);
+ assert( c == NULL );
+ }
+ c = next;
+ if ( c->c_connid <= first_connid ) {
+ c = NULL;
+ }
+ }
+ }
+ }
+#endif /* HAVE_TLS */
+
+ if ( change->flags.daemon & LLOAD_DAEMON_MOD_BINDCONF ) {
+ LloadConnection *c;
+
+ /*
+ * Only timeout changes can be handled gracefully, terminate all
+ * connections and start over.
+ */
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, handle_pdus, backend_conn_cb, NULL );
+ ldap_pvt_thread_pool_walk(
+ &connection_pool, upstream_bind, backend_conn_cb, NULL );
+
+ lload_tiers_reset( 0 );
+
+ /* Reconsider the PRIVILEGED flag on all clients */
+ LDAP_CIRCLEQ_FOREACH ( c, &clients, c_next ) {
+ int privileged = ber_bvstrcasecmp( &c->c_auth, &lloadd_identity );
+
+ /* We have just terminated all pending operations (even pins), there
+ * should be no connections still binding/closing */
+ assert( c->c_state == LLOAD_C_READY );
+
+ c->c_type = privileged ? LLOAD_C_PRIVILEGED : LLOAD_C_OPEN;
+ }
+ }
+}
+
+int
+lload_handle_invalidation( LloadChange *change )
+{
+ if ( (change->type == LLOAD_CHANGE_MODIFY) &&
+ change->flags.generic == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_handle_invalidation: "
+ "a modify where apparently nothing changed\n" );
+ }
+
+ switch ( change->object ) {
+ case LLOAD_BACKEND:
+ lload_handle_backend_invalidation( change );
+ break;
+ case LLOAD_TIER:
+ lload_handle_tier_invalidation( change );
+ break;
+ case LLOAD_DAEMON:
+ lload_handle_global_invalidation( change );
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "lload_handle_invalidation: "
+ "unrecognised change\n" );
+ assert(0);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static void
+lload_pause_event_cb( evutil_socket_t s, short what, void *arg )
+{
+ /*
+ * We are pausing, signal the pausing thread we've finished and
+ * wait until the thread pool resumes operation.
+ *
+ * Do this in lockstep with the pausing thread.
+ */
+ checked_lock( &lload_wait_mutex );
+ ldap_pvt_thread_cond_signal( &lload_wait_cond );
+
+ /* Now wait until we unpause, then we can resume operation */
+ ldap_pvt_thread_cond_wait( &lload_pause_cond, &lload_wait_mutex );
+ checked_unlock( &lload_wait_mutex );
+}
+
+/*
+ * Signal the event base to terminate processing as soon as it can and wait for
+ * lload_pause_event_cb to notify us this has happened.
+ */
+static int
+lload_pause_base( struct event_base *base )
+{
+ int rc;
+
+ checked_lock( &lload_wait_mutex );
+ event_base_once( base, -1, EV_TIMEOUT, lload_pause_event_cb, base, NULL );
+ rc = ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex );
+ checked_unlock( &lload_wait_mutex );
+
+ return rc;
+}
+
+void
+lload_pause_server( void )
+{
+ LloadChange ch = { .type = LLOAD_CHANGE_UNDEFINED };
+ int i;
+
+ lload_pause_base( listener_base );
+ lload_pause_base( daemon_base );
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ lload_pause_base( lload_daemon[i].base );
+ }
+
+ lload_change = ch;
+}
+
+void
+lload_unpause_server( void )
+{
+ if ( lload_change.type != LLOAD_CHANGE_UNDEFINED ) {
+ lload_handle_invalidation( &lload_change );
+ }
+
+ /*
+ * Make sure lloadd is completely ready to unpause by now:
+ *
+ * After the broadcast, we handle I/O and begin filling the thread pool, in
+ * high load conditions, we might hit the pool limits and start processing
+ * operations in the I/O threads (one PDU per socket at a time for fairness
+ * sake) even before a pause has finished from slapd's point of view!
+ *
+ * When (max_pdus_per_cycle == 0) we don't use the pool for these at all and
+ * most lload processing starts immediately making this even more prominent.
+ */
+ ldap_pvt_thread_cond_broadcast( &lload_pause_cond );
+}
+#endif /* BALANCER_MODULE */
+
+void
+lload_sig_shutdown( evutil_socket_t sig, short what, void *arg )
+{
+ struct event_base *daemon_base = arg;
+ int save_errno = errno;
+ int i;
+
+ /*
+ * If the NT Service Manager is controlling the server, we don't
+ * want SIGBREAK to kill the server. For some strange reason,
+ * SIGBREAK is generated when a user logs out.
+ */
+
+#if defined(HAVE_NT_SERVICE_MANAGER) && defined(SIGBREAK)
+ if ( is_NT_Service && sig == SIGBREAK ) {
+ /* empty */;
+ } else
+#endif /* HAVE_NT_SERVICE_MANAGER && SIGBREAK */
+#ifdef SIGHUP
+ if ( sig == SIGHUP && global_gentlehup && slapd_gentle_shutdown == 0 ) {
+ slapd_gentle_shutdown = 1;
+ } else
+#endif /* SIGHUP */
+ {
+ slapd_shutdown = 1;
+ }
+
+ for ( i = 0; i < lload_daemon_threads; i++ ) {
+ event_base_loopexit( lload_daemon[i].base, NULL );
+ }
+ event_base_loopexit( daemon_base, NULL );
+
+ errno = save_errno;
+}
+
+struct event_base *
+lload_get_base( ber_socket_t s )
+{
+ int tid = DAEMON_ID(s);
+ return lload_daemon[tid].base;
+}
+
+LloadListener **
+lloadd_get_listeners( void )
+{
+ /* Could return array with no listeners if !listening, but current
+ * callers mostly look at the URLs. E.g. syncrepl uses this to
+ * identify the server, which means it wants the startup arguments.
+ */
+ return lload_listeners;
+}
+
+/* Reject all incoming requests */
+void
+lload_suspend_listeners( void )
+{
+ int i;
+ for ( i = 0; lload_listeners[i]; i++ ) {
+ lload_listeners[i]->sl_mute = 1;
+ evconnlistener_disable( lload_listeners[i]->listener );
+ listen( lload_listeners[i]->sl_sd, 0 );
+ }
+}
+
+/* Resume after a suspend */
+void
+lload_resume_listeners( void )
+{
+ int i;
+ for ( i = 0; lload_listeners[i]; i++ ) {
+ lload_listeners[i]->sl_mute = 0;
+ listen( lload_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG );
+ evconnlistener_enable( lload_listeners[i]->listener );
+ }
+}
diff --git a/servers/lloadd/epoch.c b/servers/lloadd/epoch.c
new file mode 100644
index 0000000..72b6d5d
--- /dev/null
+++ b/servers/lloadd/epoch.c
@@ -0,0 +1,321 @@
+/* epoch.c - epoch based memory reclamation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2018-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/** @file epoch.c
+ *
+ * Implementation of epoch based memory reclamation, in principle
+ * similar to the algorithm presented in
+ * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf
+ *
+ * Not completely lock-free at the moment.
+ *
+ * Also the problems with epoch based memory reclamation are still
+ * present - a thread actively observing an epoch getting stuck will
+ * prevent managed objects (in our case connections and operations)
+ * from being freed, potentially running out of memory.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+#include <epoch.h>
+
+/* Has to be >= 3 */
+#define EPOCH_MASK ( 1 << 2 )
+#define EPOCH_PREV(epoch) ( ( (epoch) + EPOCH_MASK - 1 ) % EPOCH_MASK )
+#define EPOCH_NEXT(epoch) ( ( (epoch) + 1 ) % EPOCH_MASK )
+
+struct pending_ref {
+ void *object;
+ dispose_cb *dispose;
+ struct pending_ref *next;
+};
+
+ldap_pvt_thread_rdwr_t epoch_mutex;
+
+static epoch_t current_epoch;
+static uintptr_t epoch_threads[EPOCH_MASK];
+static struct pending_ref *references[EPOCH_MASK];
+
+void
+epoch_init( void )
+{
+ epoch_t epoch;
+
+ current_epoch = 0;
+ for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) {
+ assert( !epoch_threads[epoch] );
+ assert( !references[epoch] );
+ }
+
+ ldap_pvt_thread_rdwr_init( &epoch_mutex );
+}
+
+void
+epoch_shutdown( void )
+{
+ epoch_t epoch;
+ struct pending_ref *old, *next;
+
+ for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) {
+ assert( !epoch_threads[epoch] );
+ }
+
+ /*
+ * Even with the work in epoch_leave(), shutdown code doesn't currently
+ * observe any epoch, so there might still be references left to free.
+ */
+ epoch = EPOCH_PREV(current_epoch);
+ next = references[epoch];
+ references[epoch] = NULL;
+ for ( old = next; old; old = next ) {
+ next = old->next;
+
+ old->dispose( old->object );
+ ch_free( old );
+ }
+
+ epoch = current_epoch;
+ next = references[epoch];
+ references[epoch] = NULL;
+ for ( old = next; old; old = next ) {
+ next = old->next;
+
+ old->dispose( old->object );
+ ch_free( old );
+ }
+
+ /* No references should exist anywhere now */
+ for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) {
+ assert( !references[epoch] );
+ }
+
+ ldap_pvt_thread_rdwr_destroy( &epoch_mutex );
+}
+
+epoch_t
+epoch_join( void )
+{
+ epoch_t epoch;
+ struct pending_ref *old, *ref = NULL;
+
+retry:
+ /* TODO: make this completely lock-free */
+ ldap_pvt_thread_rdwr_rlock( &epoch_mutex );
+ epoch = current_epoch;
+ __atomic_add_fetch( &epoch_threads[epoch], 1, __ATOMIC_ACQ_REL );
+ ldap_pvt_thread_rdwr_runlock( &epoch_mutex );
+
+ if ( __atomic_load_n(
+ &epoch_threads[EPOCH_PREV(epoch)], __ATOMIC_ACQUIRE ) ) {
+ return epoch;
+ }
+
+ __atomic_exchange(
+ &references[EPOCH_PREV(epoch)], &ref, &ref, __ATOMIC_ACQ_REL );
+
+ Debug( LDAP_DEBUG_TRACE, "epoch_join: "
+ "advancing epoch to %zu with %s objects to free\n",
+ EPOCH_NEXT(epoch), ref ? "some" : "no" );
+
+ ldap_pvt_thread_rdwr_wlock( &epoch_mutex );
+ current_epoch = EPOCH_NEXT(epoch);
+ ldap_pvt_thread_rdwr_wunlock( &epoch_mutex );
+
+ if ( !ref ) {
+ return epoch;
+ }
+
+ /*
+ * The below is now safe to free outside epochs and we don't want to make
+ * the current epoch last any longer than necessary.
+ *
+ * Looks like there might be fairness issues in massively parallel
+ * environments but they haven't been observed on 32-core machines.
+ */
+ epoch_leave( epoch );
+
+ for ( old = ref; old; old = ref ) {
+ ref = old->next;
+
+ old->dispose( old->object );
+ ch_free( old );
+ }
+
+ goto retry;
+}
+
+void
+epoch_leave( epoch_t epoch )
+{
+ struct pending_ref *p, *next, *old_refs = NULL, *current_refs = NULL;
+
+ /* Are there other threads observing our epoch? */
+ if ( __atomic_sub_fetch( &epoch_threads[epoch], 1, __ATOMIC_ACQ_REL ) ) {
+ return;
+ }
+
+ /*
+ * Optimisation for the case when we're mostly idle. Otherwise we won't
+ * release resources until another thread comes by and joins the epoch
+ * (twice), and there's no idea how soon (or late) that is going to happen.
+ *
+ * NB. There is no limit to the number of threads executing the following
+ * code in parallel.
+ */
+ ldap_pvt_thread_rdwr_rlock( &epoch_mutex );
+ /*
+ * Anything could happen between the subtract and the lock being acquired
+ * above, so check again. But once we hold this lock (and confirm no more
+ * threads still observe either prospective epoch), noone will be able to
+ * finish epoch_join until we've released epoch_mutex since we *first* make
+ * sure it holds that:
+ *
+ * epoch_threads[EPOCH_PREV(current_epoch)] == 0
+ *
+ * and that leads epoch_join() to acquire a write lock on &epoch_mutex.
+ */
+ if ( epoch != current_epoch && epoch != EPOCH_PREV(current_epoch) ) {
+ /* Epoch counter has run away from us, no need to do anything */
+ ldap_pvt_thread_rdwr_runlock( &epoch_mutex );
+ return;
+ }
+ if ( __atomic_load_n(
+ &epoch_threads[EPOCH_PREV(current_epoch)],
+ __ATOMIC_ACQUIRE ) ) {
+ /* There is another thread still running */
+ ldap_pvt_thread_rdwr_runlock( &epoch_mutex );
+ return;
+ }
+ if ( __atomic_load_n( &epoch_threads[current_epoch], __ATOMIC_ACQUIRE ) ) {
+ /* There is another thread still running */
+ ldap_pvt_thread_rdwr_runlock( &epoch_mutex );
+ return;
+ }
+
+ /*
+ * We're all alone (apart from anyone who reached epoch_leave() at the same
+ * time), it's safe to claim all references and free them.
+ */
+ __atomic_exchange(
+ &references[EPOCH_PREV(current_epoch)], &old_refs, &old_refs,
+ __ATOMIC_ACQ_REL );
+ __atomic_exchange(
+ &references[current_epoch], &current_refs, &current_refs,
+ __ATOMIC_ACQ_REL );
+ ldap_pvt_thread_rdwr_runlock( &epoch_mutex );
+
+ for ( p = old_refs; p; p = next ) {
+ next = p->next;
+
+ p->dispose( p->object );
+ ch_free( p );
+ }
+
+ for ( p = current_refs; p; p = next ) {
+ next = p->next;
+
+ p->dispose( p->object );
+ ch_free( p );
+ }
+}
+
+/*
+ * Add the object to the "current global epoch", not the epoch our thread
+ * entered.
+ */
+void
+epoch_append( void *ptr, dispose_cb *cb )
+{
+ struct pending_ref *new;
+ epoch_t epoch = __atomic_load_n( &current_epoch, __ATOMIC_ACQUIRE );
+
+ /*
+ * BTW, the following is not appropriate here:
+ * assert( __atomic_load_n( &epoch_threads[epoch], __ATOMIC_RELAXED ) );
+ *
+ * We might be a thread lagging behind in the "previous epoch" with no
+ * other threads executing at all.
+ */
+
+ new = ch_malloc( sizeof(struct pending_ref) );
+ new->object = ptr;
+ new->dispose = cb;
+ new->next = __atomic_load_n( &references[epoch], __ATOMIC_ACQUIRE );
+
+ while ( !__atomic_compare_exchange( &references[epoch], &new->next, &new, 0,
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED ) )
+ /* iterate until we succeed */;
+}
+
+int
+acquire_ref( uintptr_t *refp )
+{
+ uintptr_t refcnt, new_refcnt;
+
+ refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE );
+
+ /*
+ * If we just incremented the refcnt and checked for zero after, another
+ * thread might falsely believe the object was going to stick around.
+ *
+ * Checking whether the object is still dead at disposal time might not be
+ * able to distinguish it from being freed in a later epoch.
+ */
+ do {
+ if ( !refcnt ) {
+ return refcnt;
+ }
+
+ new_refcnt = refcnt + 1;
+ } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0,
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED ) );
+ assert( new_refcnt == refcnt + 1 );
+
+ return refcnt;
+}
+
+int
+try_release_ref(
+ uintptr_t *refp,
+ void *object,
+ dispose_cb *unlink_cb,
+ dispose_cb *destroy_cb )
+{
+ uintptr_t refcnt, new_refcnt;
+
+ refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE );
+
+ /* We promise the caller that we won't decrease refcnt below 0 */
+ do {
+ if ( !refcnt ) {
+ return refcnt;
+ }
+
+ new_refcnt = refcnt - 1;
+ } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0,
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED ) );
+ assert( new_refcnt == refcnt - 1 );
+
+ if ( !new_refcnt ) {
+ if ( unlink_cb ) {
+ unlink_cb( object );
+ }
+ epoch_append( object, destroy_cb );
+ }
+
+ return refcnt;
+}
diff --git a/servers/lloadd/epoch.h b/servers/lloadd/epoch.h
new file mode 100644
index 0000000..06b70be
--- /dev/null
+++ b/servers/lloadd/epoch.h
@@ -0,0 +1,148 @@
+/* epoch.h - epoch based memory reclamation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2018-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef __LLOAD_EPOCH_H
+#define __LLOAD_EPOCH_H
+
+/** @file epoch.h
+ *
+ * Implementation of epoch based memory reclamation, in principle
+ * similar to the algorithm presented in
+ * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf
+ */
+
+typedef uintptr_t epoch_t;
+
+/** @brief A callback function used to free object and associated data */
+typedef void (dispose_cb)( void *object );
+
+/** @brief Initiate global state */
+void epoch_init( void );
+
+/** @brief Finalise global state and free any objects still pending */
+void epoch_shutdown( void );
+
+/** @brief Register thread as active
+ *
+ * In order to safely access managed objects, a thread should call
+ * this function or make sure no other thread is running (e.g. config
+ * pause, late shutdown). After calling this, it is guaranteed that no
+ * reachable objects will be freed before all threads have called
+ * `epoch_leave( current_epoch + 1 )` so it is essential that there
+ * is an upper limit to the amount of time between #epoch_join and
+ * corresponding #epoch_leave or the number of unfreed objects might
+ * grow without bounds.
+ *
+ * To simplify locking, memory is only freed when the current epoch
+ * is advanced rather than on leaving it.
+ *
+ * Can be safely called multiple times by the same thread as long as
+ * a matching #epoch_leave() call is made eventually.
+ *
+ * @return The observed epoch, to be passed to #epoch_leave()
+ */
+epoch_t epoch_join( void );
+
+/** @brief Register thread as inactive
+ *
+ * A thread should call this after they are finished with work
+ * performed since matching call to #epoch_join(). It is not safe
+ * to keep a local reference to managed objects after this call
+ * unless other precautions have been made to prevent it being
+ * released.
+ *
+ * @param[in] epoch Epoch identifier returned by a previous call to
+ * #epoch_join().
+ */
+void epoch_leave( epoch_t epoch );
+
+/** @brief Return an unreachable object to be freed
+ *
+ * The object should already be unreachable at the point of call and
+ * cb will be invoked when no other thread that could have seen it
+ * is active any more. This happens when we have advanced by two
+ * epochs.
+ *
+ * @param[in] ptr Object to be released/freed
+ * @param[in] cb Callback to invoke when safe to do so
+ */
+void epoch_append( void *ptr, dispose_cb *cb );
+
+/**
+ * \defgroup Reference counting helpers
+ */
+/**@{*/
+
+/** @brief Acquire a reference if possible
+ *
+ * Atomically, check reference count is non-zero and increment if so.
+ * Returns old reference count.
+ *
+ * @param[in] refp Pointer to a reference counter
+ * @return 0 if reference was already zero, non-zero if reference
+ * count was successfully incremented
+ */
+int acquire_ref( uintptr_t *refp );
+
+/** @brief Check reference count and try to decrement
+ *
+ * Atomically, decrement reference count if non-zero and register
+ * object if decremented to zero. Returning previous reference count.
+ *
+ * @param[in] refp Pointer to a reference counter
+ * @param[in] object The managed object
+ * @param[in] cb Callback to invoke when safe to do so
+ * @return 0 if reference was already zero, non-zero if reference
+ * count was non-zero at the time of call
+ */
+int try_release_ref(
+ uintptr_t *refp,
+ void *object,
+ dispose_cb *unlink_cb,
+ dispose_cb *destroy_cb );
+
+/** @brief Read reference count
+ *
+ * @param[in] object Pointer to the managed object
+ * @param[in] ref_field Member where reference count is stored in
+ * the object
+ * @return Current value of reference counter
+ */
+#define IS_ALIVE( object, ref_field ) \
+ __atomic_load_n( &(object)->ref_field, __ATOMIC_ACQUIRE )
+
+/** @brief Release reference
+ *
+ * A cheaper alternative to #try_release_ref(), safe only when we know
+ * reference count was already non-zero.
+ *
+ * @param[in] object The managed object
+ * @param[in] ref_field Member where reference count is stored in
+ * the object
+ * @param[in] cb Callback to invoke when safe to do so
+ */
+#define RELEASE_REF( object, ref_field, cb ) \
+ do { \
+ assert( IS_ALIVE( (object), ref_field ) ); \
+ if ( !__atomic_sub_fetch( \
+ &(object)->ref_field, 1, __ATOMIC_ACQ_REL ) ) { \
+ epoch_append( object, (dispose_cb *)cb ); \
+ } \
+ } while (0)
+
+/**@}*/
+
+#endif /* __LLOAD_EPOCH_H */
diff --git a/servers/lloadd/extended.c b/servers/lloadd/extended.c
new file mode 100644
index 0000000..54d9700
--- /dev/null
+++ b/servers/lloadd/extended.c
@@ -0,0 +1,220 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+
+#include "lutil.h"
+#include "lload.h"
+
+Avlnode *lload_exop_handlers = NULL;
+
+#ifdef HAVE_TLS
+void *lload_tls_ctx;
+LDAP *lload_tls_ld, *lload_tls_backend_ld;
+#ifdef BALANCER_MODULE
+int lload_use_slap_tls_ctx = 0;
+#endif
+#endif /* HAVE_TLS */
+
+int
+handle_starttls( LloadConnection *c, LloadOperation *op )
+{
+ struct event_base *base = event_get_base( c->c_read_event );
+ LloadOperation *found;
+ BerElement *output;
+ char *msg = NULL;
+ int rc = LDAP_SUCCESS;
+
+ CONNECTION_LOCK(c);
+ found = ldap_tavl_delete( &c->c_ops, op, operation_client_cmp );
+ assert( op == found );
+ c->c_n_ops_executing--;
+
+#ifdef HAVE_TLS
+ if ( c->c_is_tls == LLOAD_TLS_ESTABLISHED ) {
+ rc = LDAP_OPERATIONS_ERROR;
+ msg = "TLS layer already in effect";
+ } else if ( c->c_state == LLOAD_C_BINDING ) {
+ rc = LDAP_OPERATIONS_ERROR;
+ msg = "bind in progress";
+ } else if ( c->c_ops ) {
+ rc = LDAP_OPERATIONS_ERROR;
+ msg = "cannot start TLS when operations are outstanding";
+ } else if ( !LLOAD_TLS_CTX ) {
+ rc = LDAP_UNAVAILABLE;
+ msg = "Could not initialize TLS";
+ }
+#else /* ! HAVE_TLS */
+ rc = LDAP_UNAVAILABLE;
+ msg = "Could not initialize TLS";
+#endif /* ! HAVE_TLS */
+
+ CONNECTION_UNLOCK(c);
+
+ Debug( LDAP_DEBUG_STATS, "handle_starttls: "
+ "handling StartTLS exop connid=%lu rc=%d msg=%s\n",
+ c->c_connid, rc, msg );
+
+ if ( rc ) {
+ /* We've already removed the operation from the queue */
+ operation_send_reject( op, rc, msg, 1 );
+ return LDAP_SUCCESS;
+ }
+
+#ifdef HAVE_TLS
+ event_del( c->c_read_event );
+ event_del( c->c_write_event );
+ /*
+ * At this point, we are the only thread handling the connection:
+ * - there are no upstream operations
+ * - the I/O callbacks have been successfully removed
+ *
+ * This means we can safely reconfigure both I/O events now.
+ */
+
+ checked_lock( &c->c_io_mutex );
+ output = c->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ OPERATION_UNLINK(op);
+ CONNECTION_LOCK_DESTROY(c);
+ return -1;
+ }
+ c->c_pendingber = output;
+ ber_printf( output, "t{tit{ess}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_client_msgid,
+ LDAP_RES_EXTENDED, LDAP_SUCCESS, "", "" );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+ CONNECTION_LOCK(c);
+ c->c_read_timeout = lload_timeout_net;
+ event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST,
+ client_tls_handshake_cb, c );
+ event_add( c->c_read_event, c->c_read_timeout );
+
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ client_tls_handshake_cb, c );
+ /* We already have something to write */
+ event_add( c->c_write_event, lload_write_timeout );
+
+ op->o_res = LLOAD_OP_COMPLETED;
+ CONNECTION_UNLOCK(c);
+
+ OPERATION_UNLINK(op);
+
+ return -1;
+#endif /* HAVE_TLS */
+}
+
+int
+request_extended( LloadConnection *c, LloadOperation *op )
+{
+ ExopHandler *handler, needle = {};
+ struct restriction_entry *restriction, rneedle = {};
+ BerElement *copy;
+ struct berval bv;
+ ber_tag_t tag;
+
+ if ( (copy = ber_alloc()) == NULL ) {
+ operation_send_reject( op, LDAP_OTHER, "internal error", 0 );
+ CONNECTION_LOCK_DESTROY(c);
+ return -1;
+ }
+
+ ber_init2( copy, &op->o_request, 0 );
+
+ tag = ber_skip_element( copy, &bv );
+ if ( tag != LDAP_TAG_EXOP_REQ_OID ) {
+ Debug( LDAP_DEBUG_STATS, "request_extended: "
+ "no OID present in extended request\n" );
+ operation_send_reject( op, LDAP_PROTOCOL_ERROR, "decoding error", 0 );
+ CONNECTION_LOCK_DESTROY(c);
+ return -1;
+ }
+
+ needle.oid = bv;
+
+ handler = ldap_avl_find( lload_exop_handlers, &needle, exop_handler_cmp );
+ if ( handler ) {
+ Debug( LDAP_DEBUG_TRACE, "request_extended: "
+ "handling exop OID %.*s internally\n",
+ (int)bv.bv_len, bv.bv_val );
+ ber_free( copy, 0 );
+ return handler->func( c, op );
+ }
+ ber_free( copy, 0 );
+
+ rneedle.oid = bv;
+ restriction = ldap_tavl_find( lload_exop_actions, &rneedle,
+ lload_restriction_cmp );
+ if ( restriction ) {
+ op->o_restricted = restriction->action;
+ } else {
+ op->o_restricted = lload_default_exop_action;
+ }
+
+ return request_process( c, op );
+}
+
+ExopHandler lload_exops[] = {
+ { BER_BVC(LDAP_EXOP_START_TLS), handle_starttls },
+ { BER_BVNULL }
+};
+
+int
+exop_handler_cmp( const void *left, const void *right )
+{
+ const struct lload_exop_handlers_t *l = left, *r = right;
+ return ber_bvcmp( &l->oid, &r->oid );
+}
+
+int
+lload_register_exop_handlers( struct lload_exop_handlers_t *handler )
+{
+ for ( ; !BER_BVISNULL( &handler->oid ); handler++ ) {
+ Debug( LDAP_DEBUG_TRACE, "lload_register_exop_handlers: "
+ "registering handler for exop oid=%s\n",
+ handler->oid.bv_val );
+ if ( ldap_avl_insert( &lload_exop_handlers, handler, exop_handler_cmp,
+ ldap_avl_dup_error ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_register_exop_handlers: "
+ "failed to register handler for exop oid=%s\n",
+ handler->oid.bv_val );
+ return -1;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+lload_exop_init( void )
+{
+ if ( lload_register_exop_handlers( lload_exops ) ) {
+ return -1;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+void
+lload_exop_destroy( void )
+{
+ ldap_avl_free( lload_exop_handlers, NULL );
+ lload_exop_handlers = NULL;
+}
diff --git a/servers/lloadd/init.c b/servers/lloadd/init.c
new file mode 100644
index 0000000..5607465
--- /dev/null
+++ b/servers/lloadd/init.c
@@ -0,0 +1,246 @@
+/* init.c - initialize various things */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lload.h"
+#include "lber_pvt.h"
+
+#include "ldap_rq.h"
+
+#ifndef BALANCER_MODULE
+/*
+ * read-only global variables or variables only written by the listener
+ * thread (after they are initialized) - no need to protect them with a mutex.
+ */
+int slap_debug = 0;
+
+#ifdef LDAP_DEBUG
+int ldap_syslog = LDAP_DEBUG_STATS;
+#else
+int ldap_syslog;
+#endif
+
+#ifdef LOG_DEBUG
+int ldap_syslog_level = LOG_DEBUG;
+#endif
+
+/*
+ * global variables that need mutex protection
+ */
+ldap_pvt_thread_pool_t connection_pool;
+int connection_pool_max = SLAP_MAX_WORKER_THREADS;
+int connection_pool_queues = 1;
+int slap_tool_thread_max = 1;
+
+int slapMode = SLAP_UNDEFINED_MODE;
+#endif /* !BALANCER_MODULE */
+
+static const char *lload_name = NULL;
+
+int
+lload_global_init( void )
+{
+ int rc;
+
+ if ( lload_libevent_init() ) {
+ return -1;
+ }
+
+#ifdef HAVE_TLS
+ if ( ldap_create( &lload_tls_backend_ld ) ) {
+ return -1;
+ }
+ if ( ldap_create( &lload_tls_ld ) ) {
+ return -1;
+ }
+
+ /* Library defaults to full certificate checking. This is correct when
+ * a client is verifying a server because all servers should have a
+ * valid cert. But few clients have valid certs, so we want our default
+ * to be no checking. The config file can override this as usual.
+ */
+ rc = LDAP_OPT_X_TLS_NEVER;
+ (void)ldap_pvt_tls_set_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &rc );
+#endif
+
+ ldap_pvt_thread_mutex_init( &lload_wait_mutex );
+ ldap_pvt_thread_cond_init( &lload_wait_cond );
+ ldap_pvt_thread_cond_init( &lload_pause_cond );
+
+ ldap_pvt_thread_mutex_init( &clients_mutex );
+ ldap_pvt_thread_mutex_init( &lload_pin_mutex );
+
+ if ( lload_exop_init() ) {
+ return -1;
+ }
+ return 0;
+}
+
+int
+lload_global_destroy( void )
+{
+ if ( !BER_BVISNULL( &lloadd_identity ) ) {
+ ch_free( lloadd_identity.bv_val );
+ BER_BVZERO( &lloadd_identity );
+ }
+
+ lload_exop_destroy();
+ ldap_tavl_free( lload_control_actions, (AVL_FREE)lload_restriction_free );
+ ldap_tavl_free( lload_exop_actions, (AVL_FREE)lload_restriction_free );
+
+#ifdef HAVE_TLS
+ if ( lload_tls_backend_ld ) {
+ ldap_unbind_ext( lload_tls_backend_ld, NULL, NULL );
+ }
+ if ( lload_tls_ld ) {
+ ldap_unbind_ext( lload_tls_ld, NULL, NULL );
+ }
+ if ( lload_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( lload_tls_ctx );
+ }
+#endif
+
+ ldap_pvt_thread_mutex_destroy( &lload_wait_mutex );
+ ldap_pvt_thread_cond_destroy( &lload_wait_cond );
+ ldap_pvt_thread_cond_destroy( &lload_pause_cond );
+
+ ldap_pvt_thread_mutex_destroy( &clients_mutex );
+ ldap_pvt_thread_mutex_destroy( &lload_pin_mutex );
+
+ lload_libevent_destroy();
+
+ return 0;
+}
+
+int
+lload_tls_init( void )
+{
+#ifdef HAVE_TLS
+ int rc, opt = 1;
+
+ /* Force new ctx to be created */
+ rc = ldap_pvt_tls_set_option( lload_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if ( rc == 0 ) {
+ /* The ctx's refcount is bumped up here */
+ ldap_pvt_tls_get_option(
+ lload_tls_ld, LDAP_OPT_X_TLS_CTX, &lload_tls_ctx );
+ } else if ( rc != LDAP_NOT_SUPPORTED ) {
+ Debug( LDAP_DEBUG_ANY, "lload_global_init: "
+ "TLS init def ctx failed: %d\n",
+ rc );
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+int
+lload_init( int mode, const char *name )
+{
+ int rc = LDAP_SUCCESS;
+
+ assert( mode );
+
+ if ( slapMode != SLAP_UNDEFINED_MODE ) {
+ /* Make sure we write something to stderr */
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY, "%s init: "
+ "init called twice (old=%d, new=%d)\n",
+ name, slapMode, mode );
+
+ return 1;
+ }
+
+ slapMode = mode;
+
+ switch ( slapMode & SLAP_MODE ) {
+ case SLAP_SERVER_MODE:
+ Debug( LDAP_DEBUG_TRACE, "%s init: "
+ "initiated server.\n",
+ name );
+
+ lload_name = name;
+
+ ldap_pvt_thread_pool_init_q( &connection_pool, connection_pool_max,
+ 0, connection_pool_queues );
+
+ ldap_pvt_thread_mutex_init( &slapd_rq.rq_mutex );
+ LDAP_STAILQ_INIT( &slapd_rq.task_list );
+ LDAP_STAILQ_INIT( &slapd_rq.run_list );
+
+ rc = lload_global_init();
+ break;
+
+ default:
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY, "%s init: "
+ "undefined mode (%d).\n",
+ name, mode );
+
+ rc = 1;
+ break;
+ }
+
+ return rc;
+}
+
+int
+lload_destroy( void )
+{
+ int rc = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "%s destroy: "
+ "freeing system resources.\n",
+ lload_name );
+
+ ldap_pvt_thread_pool_free( &connection_pool );
+
+ switch ( slapMode & SLAP_MODE ) {
+ case SLAP_SERVER_MODE:
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "lload_destroy(): "
+ "undefined mode (%d).\n",
+ slapMode );
+
+ rc = 1;
+ break;
+ }
+
+ ldap_pvt_thread_destroy();
+
+ /* should destroy the above mutex */
+ return rc;
+}
diff --git a/servers/lloadd/libevent_support.c b/servers/lloadd/libevent_support.c
new file mode 100644
index 0000000..79e7845
--- /dev/null
+++ b/servers/lloadd/libevent_support.c
@@ -0,0 +1,169 @@
+/* libevent_support.c - routines to bridge libldap and libevent */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2017-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/time.h>
+
+#include <event2/event.h>
+#include <event2/thread.h>
+
+#include "lload.h"
+#include "ldap_pvt_thread.h"
+
+static void *
+lload_libevent_mutex_init( unsigned locktype )
+{
+ int rc;
+ ldap_pvt_thread_mutex_t *mutex =
+ ch_malloc( sizeof(ldap_pvt_thread_mutex_t) );
+
+ if ( locktype & EVTHREAD_LOCKTYPE_RECURSIVE ) {
+ rc = ldap_pvt_thread_mutex_recursive_init( mutex );
+ } else {
+ rc = ldap_pvt_thread_mutex_init( mutex );
+ }
+ if ( rc ) {
+ ch_free( mutex );
+ mutex = NULL;
+ }
+ return mutex;
+}
+
+static void
+lload_libevent_mutex_destroy( void *lock, unsigned locktype )
+{
+ int rc;
+ ldap_pvt_thread_mutex_t *mutex = lock;
+
+ rc = ldap_pvt_thread_mutex_destroy( mutex );
+ assert( rc == 0 );
+ ch_free( mutex );
+}
+
+static int
+lload_libevent_mutex_lock( unsigned mode, void *lock )
+{
+ ldap_pvt_thread_mutex_t *mutex = lock;
+
+ if ( mode & EVTHREAD_TRY ) {
+ return ldap_pvt_thread_mutex_trylock( mutex );
+ } else {
+ return ldap_pvt_thread_mutex_lock( mutex );
+ }
+}
+
+static int
+lload_libevent_mutex_unlock( unsigned mode, void *lock )
+{
+ ldap_pvt_thread_mutex_t *mutex = lock;
+
+ return ldap_pvt_thread_mutex_unlock( mutex );
+}
+
+static void *
+lload_libevent_cond_init( unsigned condtype )
+{
+ int rc;
+ ldap_pvt_thread_cond_t *cond =
+ ch_malloc( sizeof(ldap_pvt_thread_cond_t) );
+
+ assert( condtype == 0 );
+ rc = ldap_pvt_thread_cond_init( cond );
+ if ( rc ) {
+ ch_free( cond );
+ cond = NULL;
+ }
+ return cond;
+}
+
+static void
+lload_libevent_cond_destroy( void *c )
+{
+ int rc;
+ ldap_pvt_thread_cond_t *cond = c;
+
+ rc = ldap_pvt_thread_cond_destroy( cond );
+ assert( rc == 0 );
+ ch_free( c );
+}
+
+static int
+lload_libevent_cond_signal( void *c, int broadcast )
+{
+ ldap_pvt_thread_cond_t *cond = c;
+
+ if ( broadcast ) {
+ return ldap_pvt_thread_cond_broadcast( cond );
+ } else {
+ return ldap_pvt_thread_cond_signal( cond );
+ }
+}
+
+static int
+lload_libevent_cond_timedwait(
+ void *c,
+ void *lock,
+ const struct timeval *timeout )
+{
+ ldap_pvt_thread_cond_t *cond = c;
+ ldap_pvt_thread_mutex_t *mutex = lock;
+
+ /*
+ * libevent does not seem to request a timeout, this is true as of 2.1.8
+ * that has just been marked the first stable release of the 2.1 series
+ */
+ assert( timeout == NULL );
+
+ return ldap_pvt_thread_cond_wait( cond, mutex );
+}
+
+unsigned long
+lload_libevent_thread_self( void )
+{
+ return (unsigned long)ldap_pvt_thread_self();
+}
+
+int
+lload_libevent_init( void )
+{
+ struct evthread_lock_callbacks cbs = {
+ EVTHREAD_LOCK_API_VERSION,
+ EVTHREAD_LOCKTYPE_RECURSIVE,
+ lload_libevent_mutex_init,
+ lload_libevent_mutex_destroy,
+ lload_libevent_mutex_lock,
+ lload_libevent_mutex_unlock
+ };
+ struct evthread_condition_callbacks cond_cbs = {
+ EVTHREAD_CONDITION_API_VERSION,
+ lload_libevent_cond_init,
+ lload_libevent_cond_destroy,
+ lload_libevent_cond_signal,
+ lload_libevent_cond_timedwait
+ };
+
+ evthread_set_lock_callbacks( &cbs );
+ evthread_set_condition_callbacks( &cond_cbs );
+ evthread_set_id_callback( lload_libevent_thread_self );
+ return 0;
+}
+
+void
+lload_libevent_destroy( void )
+{
+ libevent_global_shutdown();
+}
diff --git a/servers/lloadd/lload-config.h b/servers/lloadd/lload-config.h
new file mode 100644
index 0000000..e8ab431
--- /dev/null
+++ b/servers/lloadd/lload-config.h
@@ -0,0 +1,39 @@
+/* lload-config.h - configuration abstraction structure */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef LLOAD_CONFIG_H /* not CONFIG_H because it overlaps with the one from slapd */
+#define LLOAD_CONFIG_H
+
+#include <ac/string.h>
+#include "../slapd/slap-config.h"
+
+LDAP_BEGIN_DECL
+
+int lload_config_fp_parse_line( ConfigArgs *c );
+
+int lload_config_get_vals( ConfigTable *ct, ConfigArgs *c );
+int lload_config_add_vals( ConfigTable *ct, ConfigArgs *c );
+
+void lload_init_config_argv( ConfigArgs *c );
+int lload_read_config_file( const char *fname, int depth, ConfigArgs *cf, ConfigTable *cft );
+
+ConfigTable *lload_config_find_keyword( ConfigTable *ct, ConfigArgs *c );
+
+LloadListener *lload_config_check_my_url( const char *url, LDAPURLDesc *lud );
+
+LDAP_END_DECL
+
+#endif /* LLOAD_CONFIG_H */
diff --git a/servers/lloadd/lload.h b/servers/lloadd/lload.h
new file mode 100644
index 0000000..f9144a5
--- /dev/null
+++ b/servers/lloadd/lload.h
@@ -0,0 +1,601 @@
+/* lload.h - load balancer include file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#ifndef _LLOAD_H_
+#define _LLOAD_H_
+
+#include "ldap_defaults.h"
+
+#include <stdio.h>
+#include <ac/stdlib.h>
+
+#include <sys/types.h>
+#include <ac/syslog.h>
+#include <ac/regex.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/time.h>
+#include <ac/param.h>
+
+#include "ldap_avl.h"
+
+#include "../servers/slapd/slap.h"
+#include "../slapd/back-monitor/back-monitor.h"
+
+#ifndef ldap_debug
+#define ldap_debug slap_debug
+#endif
+
+#include "ldap_log.h"
+
+#include <ldap.h>
+#include <ldap_schema.h>
+
+#include "lber_pvt.h"
+#include "ldap_pvt.h"
+#include "ldap_pvt_thread.h"
+#include "ldap_queue.h"
+
+#include <event2/event.h>
+
+#ifdef HAVE_CYRUS_SASL
+#ifdef HAVE_SASL_SASL_H
+#include <sasl/sasl.h>
+#else
+#include <sasl.h>
+#endif
+#endif /* HAVE_CYRUS_SASL */
+
+LDAP_BEGIN_DECL
+
+#ifdef SERVICE_NAME
+#undef SERVICE_NAME
+#endif
+
+#define SERVICE_NAME OPENLDAP_PACKAGE "-lloadd"
+
+#define LLOAD_SB_MAX_INCOMING_CLIENT ( ( 1 << 24 ) - 1 )
+#define LLOAD_SB_MAX_INCOMING_UPSTREAM ( ( 1 << 24 ) - 1 )
+
+#define LLOAD_CONN_MAX_PDUS_PER_CYCLE_DEFAULT 10
+
+#define BER_BV_OPTIONAL( bv ) ( BER_BVISNULL( bv ) ? NULL : ( bv ) )
+
+#include <epoch.h>
+
+#define checked_lock( mutex ) \
+ if ( ldap_pvt_thread_mutex_lock( mutex ) != 0 ) assert(0)
+#define checked_unlock( mutex ) \
+ if ( ldap_pvt_thread_mutex_unlock( mutex ) != 0 ) assert(0)
+
+#ifdef LDAP_THREAD_DEBUG
+#define assert_locked( mutex ) \
+ if ( ldap_pvt_thread_mutex_trylock( mutex ) == 0 ) assert(0)
+#else
+#define assert_locked( mutex ) ( (void)0 )
+#endif
+
+typedef struct LloadTier LloadTier;
+typedef struct LloadBackend LloadBackend;
+typedef struct LloadPendingConnection LloadPendingConnection;
+typedef struct LloadConnection LloadConnection;
+typedef struct LloadOperation LloadOperation;
+typedef struct LloadChange LloadChange;
+/* end of forward declarations */
+
+typedef LDAP_STAILQ_HEAD(TierSt, LloadTier) lload_t_head;
+typedef LDAP_CIRCLEQ_HEAD(BeSt, LloadBackend) lload_b_head;
+typedef LDAP_CIRCLEQ_HEAD(ConnSt, LloadConnection) lload_c_head;
+
+LDAP_SLAPD_V (lload_t_head) tiers;
+LDAP_SLAPD_V (lload_c_head) clients;
+LDAP_SLAPD_V (struct slap_bindconf) bindconf;
+LDAP_SLAPD_V (struct berval) lloadd_identity;
+
+/* Used to coordinate server (un)pause, shutdown */
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) lload_wait_mutex;
+LDAP_SLAPD_V (ldap_pvt_thread_cond_t) lload_pause_cond;
+LDAP_SLAPD_V (ldap_pvt_thread_cond_t) lload_wait_cond;
+
+typedef int lload_cf_aux_table_parse_x( struct berval *val,
+ void *bc,
+ slap_cf_aux_table *tab0,
+ const char *tabmsg,
+ int unparse );
+
+typedef struct LloadListener LloadListener;
+
+enum lc_type {
+ LLOAD_CHANGE_UNDEFINED = 0,
+ LLOAD_CHANGE_MODIFY,
+ LLOAD_CHANGE_ADD,
+ LLOAD_CHANGE_DEL,
+};
+
+enum lc_object {
+ LLOAD_UNDEFINED = 0,
+ LLOAD_DAEMON,
+ /*
+ LLOAD_BINDCONF,
+ */
+ LLOAD_TIER,
+ LLOAD_BACKEND,
+};
+
+enum lcf_daemon {
+ LLOAD_DAEMON_MOD_THREADS = 1 << 0,
+ LLOAD_DAEMON_MOD_FEATURES = 1 << 1,
+ LLOAD_DAEMON_MOD_TLS = 1 << 2,
+ LLOAD_DAEMON_MOD_LISTENER_ADD = 1 << 3,
+ LLOAD_DAEMON_MOD_LISTENER_REPLACE = 1 << 4,
+ LLOAD_DAEMON_MOD_BINDCONF = 1 << 5,
+};
+
+enum lcf_tier {
+ LLOAD_TIER_MOD_TYPE = 1 << 0,
+};
+
+enum lcf_backend {
+ LLOAD_BACKEND_MOD_OTHER = 1 << 0,
+ LLOAD_BACKEND_MOD_CONNS = 1 << 1,
+};
+
+struct LloadChange {
+ enum lc_type type;
+ enum lc_object object;
+ union {
+ int generic;
+ enum lcf_daemon daemon;
+ enum lcf_tier tier;
+ enum lcf_backend backend;
+ } flags;
+ void *target;
+};
+
+typedef enum {
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ LLOAD_FEATURE_VC = 1 << 0,
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ LLOAD_FEATURE_PROXYAUTHZ = 1 << 1,
+ LLOAD_FEATURE_PAUSE = 1 << 2,
+} lload_features_t;
+
+#define LLOAD_FEATURE_SUPPORTED_MASK ( \
+ LLOAD_FEATURE_PROXYAUTHZ | \
+ 0 )
+
+#ifdef BALANCER_MODULE
+#define LLOAD_TLS_CTX ( lload_use_slap_tls_ctx ? slap_tls_ctx : lload_tls_ctx )
+#else
+#define LLOAD_TLS_CTX ( lload_tls_ctx )
+#endif
+
+enum lload_tls_type {
+ LLOAD_CLEARTEXT = 0,
+ LLOAD_LDAPS,
+ LLOAD_STARTTLS_OPTIONAL,
+ LLOAD_STARTTLS,
+ LLOAD_TLS_ESTABLISHED,
+};
+
+struct LloadPendingConnection {
+ LloadBackend *backend;
+
+ struct event *event;
+ ber_socket_t fd;
+
+ LDAP_LIST_ENTRY(LloadPendingConnection) next;
+};
+
+typedef struct lload_counters_t {
+ ldap_pvt_mp_t lc_ops_completed;
+ ldap_pvt_mp_t lc_ops_received;
+ ldap_pvt_mp_t lc_ops_forwarded;
+ ldap_pvt_mp_t lc_ops_rejected;
+ ldap_pvt_mp_t lc_ops_failed;
+} lload_counters_t;
+
+enum {
+ LLOAD_STATS_OPS_BIND = 0,
+ LLOAD_STATS_OPS_OTHER,
+ LLOAD_STATS_OPS_LAST
+};
+
+typedef struct lload_global_stats_t {
+ ldap_pvt_mp_t global_incoming;
+ ldap_pvt_mp_t global_outgoing;
+ lload_counters_t counters[LLOAD_STATS_OPS_LAST];
+} lload_global_stats_t;
+
+typedef LloadTier *(LloadTierInit)( void );
+typedef int (LloadTierConfigCb)( LloadTier *tier, char *arg );
+typedef int (LloadTierBackendConfigCb)( LloadTier *tier, LloadBackend *b, char *arg );
+typedef int (LloadTierCb)( LloadTier *tier );
+typedef int (LloadTierResetCb)( LloadTier *tier, int shutdown );
+typedef int (LloadTierBackendCb)( LloadTier *tier, LloadBackend *b );
+typedef void (LloadTierChange)( LloadTier *tier, LloadChange *change );
+typedef int (LloadTierSelect)( LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message );
+
+struct lload_tier_type {
+ char *tier_name;
+
+ struct berval tier_oc, tier_backend_oc;
+
+ LloadTierInit *tier_init;
+ LloadTierConfigCb *tier_config;
+ LloadTierBackendConfigCb *tier_backend_config;
+ LloadTierCb *tier_startup;
+ LloadTierCb *tier_update;
+ LloadTierResetCb *tier_reset;
+ LloadTierCb *tier_destroy;
+
+ LloadTierBackendCb *tier_add_backend;
+ LloadTierBackendCb *tier_remove_backend;
+ LloadTierChange *tier_change;
+
+ LloadTierSelect *tier_select;
+};
+
+struct LloadTier {
+ struct lload_tier_type t_type;
+ ldap_pvt_thread_mutex_t t_mutex;
+
+ lload_b_head t_backends;
+ int t_nbackends;
+
+ enum {
+ LLOAD_TIER_EXCLUSIVE = 1 << 0, /* Reject if busy */
+ } t_flags;
+
+ struct berval t_name;
+#ifdef BALANCER_MODULE
+ monitor_subsys_t *t_monitor;
+#endif /* BALANCER_MODULE */
+
+ void *t_private;
+ LDAP_STAILQ_ENTRY(LloadTier) t_next;
+};
+
+/* Can hold mutex when locking a linked connection */
+struct LloadBackend {
+ ldap_pvt_thread_mutex_t b_mutex;
+
+ struct berval b_name, b_uri;
+ int b_proto, b_port;
+ enum lload_tls_type b_tls, b_tls_conf;
+ char *b_host;
+
+ int b_retry_timeout, b_failed;
+ struct event *b_retry_event;
+ struct timeval b_retry_tv;
+
+ int b_numconns, b_numbindconns;
+ int b_bindavail, b_active, b_opening;
+ lload_c_head b_conns, b_bindconns, b_preparing;
+ LDAP_LIST_HEAD(ConnectingSt, LloadPendingConnection) b_connecting;
+ LloadConnection *b_last_conn, *b_last_bindconn;
+
+ long b_max_pending, b_max_conn_pending;
+ long b_n_ops_executing;
+
+ lload_counters_t b_counters[LLOAD_STATS_OPS_LAST];
+
+ LloadTier *b_tier;
+
+ time_t b_last_update;
+ uintptr_t b_fitness;
+ int b_weight;
+
+ uintptr_t b_operation_count;
+ uintptr_t b_operation_time;
+
+#ifdef BALANCER_MODULE
+ monitor_subsys_t *b_monitor;
+#endif /* BALANCER_MODULE */
+
+ struct evdns_getaddrinfo_request *b_dns_req;
+ void *b_cookie;
+
+ LDAP_CIRCLEQ_ENTRY(LloadBackend) b_next;
+};
+
+typedef int (*LloadOperationHandler)( LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber );
+typedef int (*RequestHandler)( LloadConnection *c, LloadOperation *op );
+typedef struct lload_exop_handlers_t {
+ struct berval oid;
+ RequestHandler func;
+} ExopHandler;
+
+typedef int (*CONNECTION_PDU_CB)( LloadConnection *c );
+typedef void (*CONNECTION_DESTROY_CB)( LloadConnection *c );
+
+/* connection state (protected by c_mutex) */
+enum sc_state {
+ LLOAD_C_INVALID = 0, /* MUST BE ZERO (0) */
+ LLOAD_C_READY, /* ready */
+ LLOAD_C_CLOSING, /* closing */
+ LLOAD_C_ACTIVE, /* exclusive operation (tls setup, ...) in progress */
+ LLOAD_C_BINDING, /* binding */
+ LLOAD_C_DYING, /* part-processed dead waiting to be freed, someone
+ * might still be observing it */
+};
+enum sc_type {
+ LLOAD_C_OPEN = 0, /* regular connection */
+ LLOAD_C_PREPARING, /* upstream connection not assigned yet */
+ LLOAD_C_BIND, /* connection used to handle bind client requests if VC not enabled */
+ LLOAD_C_PRIVILEGED, /* connection can override proxyauthz control */
+};
+enum sc_io_state {
+ LLOAD_C_OPERATIONAL = 0, /* all is good */
+ LLOAD_C_READ_HANDOVER = 1 << 0, /* A task to process PDUs is scheduled or
+ * running, do not re-enable c_read_event */
+ LLOAD_C_READ_PAUSE = 1 << 1, /* We want to pause reading until the client
+ * has sufficiently caught up with what we
+ * sent */
+};
+
+/* Tracking whether an operation might cause a client to restrict which
+ * upstreams are eligible */
+enum op_restriction {
+ LLOAD_OP_NOT_RESTRICTED, /* no restrictions in place */
+ LLOAD_OP_RESTRICTED_WRITE, /* client is restricted to a certain backend with
+ * a timeout attached */
+ LLOAD_OP_RESTRICTED_BACKEND, /* client is restricted to a certain backend,
+ * without a timeout */
+ LLOAD_OP_RESTRICTED_UPSTREAM, /* client is restricted to a certain upstream */
+ LLOAD_OP_RESTRICTED_ISOLATE, /* TODO: client is restricted to a certain
+ * upstream and removes the upstream from the
+ * pool */
+ LLOAD_OP_RESTRICTED_REJECT, /* operation should not be forwarded to any
+ * backend, either it is processed internally
+ * or rejected */
+};
+
+/*
+ * represents a connection from an ldap client/to ldap server
+ */
+struct LloadConnection {
+ enum sc_state c_state; /* connection state */
+ enum sc_type c_type;
+ enum sc_io_state c_io_state;
+ ber_socket_t c_fd;
+
+/*
+ * LloadConnection reference counting:
+ * - connection has a reference counter in c_refcnt
+ * - also a liveness/validity token is added to c_refcnt during
+ * lload_connection_init, its existence is tracked in c_live and is usually the
+ * only one that prevents it from being destroyed
+ * - anyone who needs to be able to relock the connection after unlocking it has
+ * to use acquire_ref(), they need to make sure a matching
+ * RELEASE_REF( c, c_refcnt, c->c_destroy ); is run eventually
+ * - when a connection is considered dead, use CONNECTION_DESTROY on a locked
+ * connection, it will be made unreachable from normal places and either
+ * scheduled for reclamation when safe to do so or if anyone still holds a
+ * reference, it just gets unlocked and reclaimed after the last ref is
+ * released
+ * - CONNECTION_LOCK_DESTROY is a shorthand for locking and CONNECTION_DESTROY
+ */
+ ldap_pvt_thread_mutex_t c_mutex; /* protect the connection */
+ uintptr_t c_refcnt, c_live;
+ CONNECTION_DESTROY_CB c_unlink;
+ CONNECTION_DESTROY_CB c_destroy;
+ CONNECTION_PDU_CB c_pdu_cb;
+#define CONNECTION_ASSERT_LOCKED(c) assert_locked( &(c)->c_mutex )
+#define CONNECTION_LOCK(c) \
+ do { \
+ checked_lock( &(c)->c_mutex ); \
+ } while (0)
+#define CONNECTION_UNLOCK(c) \
+ do { \
+ checked_unlock( &(c)->c_mutex ); \
+ } while (0)
+#define CONNECTION_UNLINK_(c) \
+ do { \
+ if ( __atomic_exchange_n( &(c)->c_live, 0, __ATOMIC_ACQ_REL ) ) { \
+ (c)->c_unlink( (c) ); \
+ RELEASE_REF( (c), c_refcnt, c->c_destroy ); \
+ } \
+ } while (0)
+#define CONNECTION_DESTROY(c) \
+ do { \
+ CONNECTION_UNLINK_(c); \
+ CONNECTION_UNLOCK(c); \
+ } while (0)
+#define CONNECTION_LOCK_DESTROY(c) \
+ do { \
+ CONNECTION_LOCK(c); \
+ CONNECTION_DESTROY(c); \
+ } while (0);
+
+ Sockbuf *c_sb; /* ber connection stuff */
+
+ /* set by connection_init */
+ unsigned long c_connid; /* unique id of this connection */
+ struct berval c_peer_name; /* peer name (trans=addr:port) */
+ time_t c_starttime; /* when the connection was opened */
+
+ time_t c_activitytime; /* when the connection was last used */
+ ber_int_t c_next_msgid; /* msgid of the next message */
+
+ /* must not be used while holding either mutex */
+ struct event *c_read_event, *c_write_event;
+ struct timeval *c_read_timeout;
+
+ /* can only be changed by binding thread */
+ struct berval c_sasl_bind_mech; /* mech in progress */
+ struct berval c_auth; /* authcDN (possibly in progress) */
+
+ unsigned long c_pin_id;
+
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *c_sasl_authctx;
+ void *c_sasl_defaults;
+#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */
+ sasl_channel_binding_t *c_sasl_cbinding; /* Else cyrus-sasl would happily
+ * leak it on sasl_dispose */
+#endif /* SASL_CHANNEL_BINDING */
+#endif /* HAVE_CYRUS_SASL */
+
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ struct berval c_vc_cookie;
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+
+ /* Can be held while acquiring c_mutex to inject things into c_ops or
+ * destroy the connection */
+ ldap_pvt_thread_mutex_t c_io_mutex; /* only one pdu written at a time */
+
+ BerElement *c_currentber; /* ber we're attempting to read */
+ BerElement *c_pendingber; /* ber we're attempting to write */
+
+ TAvlnode *c_ops; /* Operations pending on the connection */
+
+#ifdef HAVE_TLS
+ enum lload_tls_type c_is_tls; /* true if this LDAP over raw TLS */
+#endif
+
+ long c_n_ops_executing; /* num of ops currently executing */
+ long c_n_ops_completed; /* num of ops completed */
+ lload_counters_t c_counters; /* per connection operation counters */
+
+ enum op_restriction c_restricted;
+ uintptr_t c_restricted_inflight;
+ time_t c_restricted_at;
+ LloadBackend *c_backend;
+ LloadConnection *c_linked_upstream;
+
+ TAvlnode *c_linked;
+
+#ifdef BALANCER_MODULE
+ struct berval c_monitor_dn;
+#endif /* BALANCER_MODULE */
+
+ /*
+ * Protected by the CIRCLEQ mutex:
+ * - Client: clients_mutex
+ * - Upstream: b->b_mutex
+ */
+ LDAP_CIRCLEQ_ENTRY(LloadConnection) c_next;
+};
+
+enum op_state {
+ LLOAD_OP_NOT_FREEING = 0,
+ LLOAD_OP_DETACHING_CLIENT = 1 << 1,
+ LLOAD_OP_DETACHING_UPSTREAM = 1 << 0,
+};
+
+#define LLOAD_OP_DETACHING_MASK \
+ ( LLOAD_OP_DETACHING_UPSTREAM | LLOAD_OP_DETACHING_CLIENT )
+
+/* operation result for monitoring purposes */
+enum op_result {
+ LLOAD_OP_REJECTED, /* operation was not forwarded */
+ LLOAD_OP_COMPLETED, /* operation sent and response received */
+ LLOAD_OP_FAILED, /* operation was forwarded, but no response was received */
+};
+
+/*
+ * Operation reference tracking:
+ * - o_refcnt is set to 1, never incremented
+ * - OPERATION_UNLINK sets it to 0 and on transition from 1 clears both
+ * connection links (o_client, o_upstream)
+ */
+struct LloadOperation {
+ uintptr_t o_refcnt;
+#define OPERATION_UNLINK(op) \
+ try_release_ref( &(op)->o_refcnt, (op), \
+ (dispose_cb *)operation_unlink, \
+ (dispose_cb *)operation_destroy )
+
+ LloadConnection *o_client;
+ unsigned long o_client_connid;
+ ber_int_t o_client_msgid;
+ ber_int_t o_saved_msgid;
+ enum op_restriction o_restricted;
+
+ LloadConnection *o_upstream;
+ unsigned long o_upstream_connid;
+ ber_int_t o_upstream_msgid;
+ struct timeval o_last_response;
+
+ /* Protects o_client, o_upstream links */
+ ldap_pvt_thread_mutex_t o_link_mutex;
+
+ ber_tag_t o_tag;
+ struct timeval o_start;
+ unsigned long o_pin_id;
+
+ enum op_result o_res;
+ BerElement *o_ber;
+ BerValue o_request, o_ctrls;
+};
+
+struct restriction_entry {
+ struct berval oid;
+ enum op_restriction action;
+};
+
+/*
+ * listener; need to access it from monitor backend
+ */
+struct LloadListener {
+ struct berval sl_url;
+ struct berval sl_name;
+ mode_t sl_perms;
+#ifdef HAVE_TLS
+ int sl_is_tls;
+#endif
+ int sl_is_proxied;
+ struct event_base *base;
+ struct evconnlistener *listener;
+ int sl_mute; /* Listener is temporarily disabled due to emfile */
+ int sl_busy; /* Listener is busy (accept thread activated) */
+ ber_socket_t sl_sd;
+ Sockaddr sl_sa;
+#define sl_addr sl_sa.sa_in_addr
+#define LDAP_TCP_BUFFER
+#ifdef LDAP_TCP_BUFFER
+ int sl_tcp_rmem; /* custom TCP read buffer size */
+ int sl_tcp_wmem; /* custom TCP write buffer size */
+#endif
+};
+
+typedef int (*CONNCB)( LloadConnection *c, void *arg );
+
+/* config requires a bi_private with configuration data - dummy for now */
+struct lload_conf_info {
+ int dummy;
+};
+LDAP_END_DECL
+
+#include "proto-lload.h"
+#endif /* _LLOAD_H_ */
diff --git a/servers/lloadd/lloadd.service b/servers/lloadd/lloadd.service
new file mode 100644
index 0000000..062b8ca
--- /dev/null
+++ b/servers/lloadd/lloadd.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=LDAP Load Balancer Daemon
+After=syslog.target network-online.target
+Documentation=man:lloadd.conf
+
+[Service]
+Type=notify
+Environment="LLOADD_URLS=ldap:/// ldapi:///" "LLOADD_OPTIONS="
+EnvironmentFile=/etc/sysconfig/lloadd
+ExecStart=%LIBEXECDIR%/lloadd -d 0 -h ${LLOADD_URLS} $LLOADD_OPTIONS
+
+[Install]
+WantedBy=multi-user.target
diff --git a/servers/lloadd/main.c b/servers/lloadd/main.c
new file mode 100644
index 0000000..b117726
--- /dev/null
+++ b/servers/lloadd/main.c
@@ -0,0 +1,955 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+#include <ac/errno.h>
+
+#include <event2/event.h>
+
+#include "lload.h"
+#include "lutil.h"
+#include "ldif.h"
+
+#ifdef LDAP_SIGCHLD
+static void wait4child( evutil_socket_t sig, short what, void *arg );
+#endif
+
+#ifdef SIGPIPE
+static void sigpipe( evutil_socket_t sig, short what, void *arg );
+#endif
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+#define MAIN_RETURN(x) return
+static struct sockaddr_in bind_addr;
+
+#define SERVICE_EXIT( e, n ) \
+ do { \
+ if ( is_NT_Service ) { \
+ lutil_ServiceStatus.dwWin32ExitCode = (e); \
+ lutil_ServiceStatus.dwServiceSpecificExitCode = (n); \
+ } \
+ } while (0)
+
+#else
+#define SERVICE_EXIT( e, n )
+#define MAIN_RETURN(x) return (x)
+#endif
+
+struct signal_handler {
+ int signal;
+ event_callback_fn handler;
+ struct event *event;
+} signal_handlers[] = {
+ { LDAP_SIGUSR2, lload_sig_shutdown },
+
+#ifdef SIGPIPE
+ { SIGPIPE, sigpipe },
+#endif
+#ifdef SIGHUP
+ { SIGHUP, lload_sig_shutdown },
+#endif
+ { SIGINT, lload_sig_shutdown },
+ { SIGTERM, lload_sig_shutdown },
+#ifdef SIGTRAP
+ { SIGTRAP, lload_sig_shutdown },
+#endif
+#ifdef LDAP_SIGCHLD
+ { LDAP_SIGCHLD, wait4child },
+#endif
+#ifdef SIGBREAK
+ /* SIGBREAK is generated when Ctrl-Break is pressed. */
+ { SIGBREAK, lload_sig_shutdown },
+#endif
+ { 0, NULL }
+};
+
+/* in logging.c */
+extern char *serverName;
+extern int slap_debug_orig;
+
+/*
+ * when more than one lloadd is running on one machine, each one might have
+ * it's own LOCAL for syslogging and must have its own pid/args files
+ */
+
+#ifndef HAVE_MKVERSION
+const char Versionstr[] = OPENLDAP_PACKAGE
+ " " OPENLDAP_VERSION " LDAP Load Balancer Server (lloadd)";
+#endif
+
+#define CHECK_NONE 0x00
+#define CHECK_CONFIG 0x01
+#define CHECK_LOGLEVEL 0x02
+static int check = CHECK_NONE;
+static int version = 0;
+
+static int
+slapd_opt_slp( const char *val, void *arg )
+{
+#ifdef HAVE_SLP
+ /* NULL is default */
+ if ( val == NULL || *val == '(' || strcasecmp( val, "on" ) == 0 ) {
+ slapd_register_slp = 1;
+ slapd_slp_attrs = ( val != NULL && *val == '(' ) ? val : NULL;
+
+ } else if ( strcasecmp( val, "off" ) == 0 ) {
+ slapd_register_slp = 0;
+
+ /* NOTE: add support for URL specification? */
+
+ } else {
+ fprintf( stderr, "unrecognized value \"%s\" for SLP option\n", val );
+ return -1;
+ }
+
+ return 0;
+
+#else
+ fputs( "lloadd: SLP support is not available\n", stderr );
+ return 0;
+#endif
+}
+
+/*
+ * Option helper structure:
+ *
+ * oh_nam is left-hand part of <option>[=<value>]
+ * oh_fnc is handler function
+ * oh_arg is an optional arg to oh_fnc
+ * oh_usage is the one-line usage string related to the option,
+ * which is assumed to start with <option>[=<value>]
+ *
+ * please leave valid options in the structure, and optionally #ifdef
+ * their processing inside the helper, so that reasonable and helpful
+ * error messages can be generated if a disabled option is requested.
+ */
+struct option_helper {
+ struct berval oh_name;
+ int (*oh_fnc)( const char *val, void *arg );
+ void *oh_arg;
+ const char *oh_usage;
+} option_helpers[] = {
+ { BER_BVC("slp"), slapd_opt_slp, NULL,
+ "slp[={on|off|(attrs)}] enable/disable SLP using (attrs)" },
+ { BER_BVNULL, 0, NULL, NULL }
+};
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+#ifdef LOG_LOCAL4
+int
+parse_syslog_user( const char *arg, int *syslogUser )
+{
+ static slap_verbmasks syslogUsers[] = {
+ { BER_BVC("LOCAL0"), LOG_LOCAL0 },
+ { BER_BVC("LOCAL1"), LOG_LOCAL1 },
+ { BER_BVC("LOCAL2"), LOG_LOCAL2 },
+ { BER_BVC("LOCAL3"), LOG_LOCAL3 },
+ { BER_BVC("LOCAL4"), LOG_LOCAL4 },
+ { BER_BVC("LOCAL5"), LOG_LOCAL5 },
+ { BER_BVC("LOCAL6"), LOG_LOCAL6 },
+ { BER_BVC("LOCAL7"), LOG_LOCAL7 },
+#ifdef LOG_USER
+ { BER_BVC("USER"), LOG_USER },
+#endif /* LOG_USER */
+#ifdef LOG_DAEMON
+ { BER_BVC("DAEMON"), LOG_DAEMON },
+#endif /* LOG_DAEMON */
+ { BER_BVNULL, 0 }
+};
+ int i = verb_to_mask( arg, syslogUsers );
+
+ if ( BER_BVISNULL( &syslogUsers[i].word ) ) {
+ Debug( LDAP_DEBUG_ANY, "unrecognized syslog user \"%s\".\n", arg );
+ return 1;
+ }
+
+ *syslogUser = syslogUsers[i].mask;
+
+ return 0;
+}
+#endif /* LOG_LOCAL4 */
+
+int
+parse_syslog_level( const char *arg, int *levelp )
+{
+ static slap_verbmasks str2syslog_level[] = {
+ { BER_BVC("EMERG"), LOG_EMERG },
+ { BER_BVC("ALERT"), LOG_ALERT },
+ { BER_BVC("CRIT"), LOG_CRIT },
+ { BER_BVC("ERR"), LOG_ERR },
+ { BER_BVC("WARNING"), LOG_WARNING },
+ { BER_BVC("NOTICE"), LOG_NOTICE },
+ { BER_BVC("INFO"), LOG_INFO },
+ { BER_BVC("DEBUG"), LOG_DEBUG },
+ { BER_BVNULL, 0 }
+};
+ int i = verb_to_mask( arg, str2syslog_level );
+ if ( BER_BVISNULL( &str2syslog_level[i].word ) ) {
+ Debug( LDAP_DEBUG_ANY, "unknown syslog level \"%s\".\n", arg );
+ return 1;
+ }
+
+ *levelp = str2syslog_level[i].mask;
+
+ return 0;
+}
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+int
+parse_debug_unknowns( char **unknowns, int *levelp )
+{
+ int i, level, rc = 0;
+
+ for ( i = 0; unknowns[i] != NULL; i++ ) {
+ level = 0;
+ if ( str2loglevel( unknowns[i], &level ) ) {
+ fprintf( stderr, "unrecognized log level \"%s\"\n", unknowns[i] );
+ rc = 1;
+ } else {
+ *levelp |= level;
+ }
+ }
+ return rc;
+}
+
+int
+parse_debug_level( const char *arg, int *levelp, char ***unknowns )
+{
+ int level;
+
+ if ( arg && arg[0] != '-' && !isdigit( (unsigned char)arg[0] ) ) {
+ int i;
+ char **levels;
+
+ levels = ldap_str2charray( arg, "," );
+
+ for ( i = 0; levels[i] != NULL; i++ ) {
+ level = 0;
+
+ if ( str2loglevel( levels[i], &level ) ) {
+ /* remember this for later */
+ ldap_charray_add( unknowns, levels[i] );
+ fprintf( stderr, "unrecognized log level \"%s\" (deferred)\n",
+ levels[i] );
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ ldap_charray_free( levels );
+
+ } else {
+ int rc;
+
+ if ( arg[0] == '-' ) {
+ rc = lutil_atoix( &level, arg, 0 );
+ } else {
+ unsigned ulevel;
+
+ rc = lutil_atoux( &ulevel, arg, 0 );
+ level = (int)ulevel;
+ }
+
+ if ( rc ) {
+ fprintf( stderr,
+ "unrecognized log level "
+ "\"%s\"\n",
+ arg );
+ return 1;
+ }
+
+ if ( level == 0 ) {
+ *levelp = 0;
+
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ return 0;
+}
+
+static void
+usage( char *name )
+{
+ fprintf( stderr, "usage: %s options\n", name );
+ fprintf( stderr,
+ "\t-4\t\tIPv4 only\n"
+ "\t-6\t\tIPv6 only\n"
+ "\t-d level\tDebug level"
+ "\n"
+ "\t-f filename\tConfiguration file\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-g group\tGroup (id or name) to run as\n"
+#endif
+ "\t-h URLs\t\tList of URLs to serve\n"
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ "\t-l facility\tSyslog facility (default: LOCAL4)\n"
+#endif
+ "\t-n serverName\tService name\n"
+ "\t-o <opt>[=val] generic means to specify options" );
+ if ( !BER_BVISNULL( &option_helpers[0].oh_name ) ) {
+ int i;
+
+ fprintf( stderr, "; supported options:\n" );
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++ ) {
+ fprintf( stderr, "\t\t%s\n", option_helpers[i].oh_usage );
+ }
+ } else {
+ fprintf( stderr, "\n" );
+ }
+ fprintf( stderr,
+#ifdef HAVE_CHROOT
+ "\t-r directory\tSandbox directory to chroot to\n"
+#endif
+ "\t-s level\tSyslog level\n"
+ "\t-t\t\tCheck configuration file\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-u user\t\tUser (id or name) to run as\n"
+#endif
+ "\t-V\t\tprint version info (-VV exit afterwards)\n" );
+}
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+void WINAPI
+ServiceMain( DWORD argc, LPTSTR *argv )
+#else
+int
+main( int argc, char **argv )
+#endif
+{
+ int i, no_detach = 0;
+ int rc = 1;
+ char *urls = NULL;
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ char *username = NULL;
+ char *groupname = NULL;
+#endif
+#if defined(HAVE_CHROOT)
+ char *sandbox = NULL;
+#endif
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ int syslogUser = SLAP_DEFAULT_SYSLOG_USER;
+#endif
+
+#ifndef HAVE_WINSOCK
+ int pid, waitfds[2];
+#endif
+ int g_argc = argc;
+ char **g_argv = argv;
+
+ char *configfile = NULL;
+ char *configdir = NULL;
+ int serverMode = SLAP_SERVER_MODE;
+
+ char **debug_unknowns = NULL;
+ char **syslog_unknowns = NULL;
+
+ int slapd_pid_file_unlink = 0, slapd_args_file_unlink = 0;
+ int firstopt = 1;
+
+ slap_sl_mem_init();
+
+ (void) ldap_pvt_thread_initialize();
+ ldap_pvt_thread_mutex_init( &logfile_mutex );
+
+ serverName = lutil_progname( "lloadd", argc, argv );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ {
+ int *ip;
+ char *newConfigFile;
+ char *newConfigDir;
+ char *newUrls;
+ char *regService = NULL;
+
+ if ( is_NT_Service ) {
+ lutil_CommenceStartupProcessing( serverName, lload_sig_shutdown );
+ if ( strcmp( serverName, SERVICE_NAME ) ) regService = serverName;
+ }
+
+ ip = (int *)lutil_getRegParam( regService, "DebugLevel" );
+ if ( ip != NULL ) {
+ slap_debug = *ip;
+ Debug( LDAP_DEBUG_ANY, "new debug level from registry is: %d\n",
+ slap_debug );
+ }
+
+ newUrls = (char *)lutil_getRegParam( regService, "Urls" );
+ if ( newUrls ) {
+ if ( urls ) ch_free( urls );
+
+ urls = ch_strdup( newUrls );
+ Debug( LDAP_DEBUG_ANY, "new urls from registry: %s\n", urls );
+ }
+
+ newConfigFile = (char *)lutil_getRegParam( regService, "ConfigFile" );
+ if ( newConfigFile != NULL ) {
+ configfile = ch_strdup( newConfigFile );
+ Debug( LDAP_DEBUG_ANY, "new config file from registry is: %s\n",
+ configfile );
+ }
+
+ newConfigDir = (char *)lutil_getRegParam( regService, "ConfigDir" );
+ if ( newConfigDir != NULL ) {
+ configdir = ch_strdup( newConfigDir );
+ Debug( LDAP_DEBUG_ANY, "new config dir from registry is: %s\n",
+ configdir );
+ }
+ }
+#endif
+
+ epoch_init();
+
+ while ( (i = getopt( argc, argv,
+ "c:d:f:F:h:n:o:s:tV"
+#ifdef LDAP_PF_INET6
+ "46"
+#endif
+#ifdef HAVE_CHROOT
+ "r:"
+#endif
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ "S:"
+#ifdef LOG_LOCAL4
+ "l:"
+#endif
+#endif
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "u:g:"
+#endif
+ )) != EOF ) {
+ switch ( i ) {
+#ifdef LDAP_PF_INET6
+ case '4':
+ slap_inet4or6 = AF_INET;
+ break;
+ case '6':
+ slap_inet4or6 = AF_INET6;
+ break;
+#endif
+
+ case 'h': /* listen URLs */
+ if ( urls != NULL ) free( urls );
+ urls = ch_strdup( optarg );
+ break;
+
+ case 'd': { /* set debug level and 'do not detach' flag */
+ int level = 0;
+
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ no_detach = 1;
+ if ( parse_debug_level( optarg, &level, &debug_unknowns ) ) {
+ goto destroy;
+ }
+#ifdef LDAP_DEBUG
+ slap_debug |= level;
+#else
+ if ( level != 0 )
+ fputs( "must compile with LDAP_DEBUG for debugging\n",
+ stderr );
+#endif
+ } break;
+
+ case 'f': /* read config file */
+ configfile = ch_strdup( optarg );
+ break;
+
+ case 'o': {
+ char *val = strchr( optarg, '=' );
+ struct berval opt;
+
+ opt.bv_val = optarg;
+
+ if ( val ) {
+ opt.bv_len = ( val - optarg );
+ val++;
+
+ } else {
+ opt.bv_len = strlen( optarg );
+ }
+
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name );
+ i++ ) {
+ if ( ber_bvstrcasecmp( &option_helpers[i].oh_name, &opt ) ==
+ 0 ) {
+ assert( option_helpers[i].oh_fnc != NULL );
+ if ( (*option_helpers[i].oh_fnc)(
+ val, option_helpers[i].oh_arg ) == -1 ) {
+ /* we assume the option parsing helper
+ * issues appropriate and self-explanatory
+ * error messages... */
+ goto stop;
+ }
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &option_helpers[i].oh_name ) ) {
+ goto unhandled_option;
+ }
+ break;
+ }
+
+ case 's': /* set syslog level */
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ if ( parse_debug_level(
+ optarg, &ldap_syslog, &syslog_unknowns ) ) {
+ goto destroy;
+ }
+ break;
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ case 'S':
+ if ( parse_syslog_level( optarg, &ldap_syslog_level ) ) {
+ goto destroy;
+ }
+ break;
+
+#ifdef LOG_LOCAL4
+ case 'l': /* set syslog local user */
+ if ( parse_syslog_user( optarg, &syslogUser ) ) {
+ goto destroy;
+ }
+ break;
+#endif
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+#ifdef HAVE_CHROOT
+ case 'r':
+ if ( sandbox ) free( sandbox );
+ sandbox = ch_strdup( optarg );
+ break;
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ case 'u': /* user name */
+ if ( username ) free( username );
+ username = ch_strdup( optarg );
+ break;
+
+ case 'g': /* group name */
+ if ( groupname ) free( groupname );
+ groupname = ch_strdup( optarg );
+ break;
+#endif /* SETUID && GETUID */
+
+ case 'n': /* NT service name */
+ serverName = ch_strdup( optarg );
+ break;
+
+ case 't':
+ check |= CHECK_CONFIG;
+ break;
+
+ case 'V':
+ version++;
+ break;
+
+ default:
+unhandled_option:;
+ usage( argv[0] );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 15 );
+ goto stop;
+ }
+
+ if ( firstopt ) {
+ firstopt = 0;
+ }
+ }
+
+ if ( optind != argc ) goto unhandled_option;
+
+ ber_set_option( NULL, LBER_OPT_LOG_PRINT_FN, slap_debug_print );
+ ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug );
+ ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug );
+ ldif_debug = slap_debug;
+ slap_debug_orig = slap_debug;
+
+ if ( version ) {
+ fprintf( stderr, "%s\n", Versionstr );
+
+ if ( version > 1 ) goto stop;
+ }
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ {
+ char *logName;
+#ifdef HAVE_EBCDIC
+ logName = ch_strdup( serverName );
+ __atoe( logName );
+#else
+ logName = serverName;
+#endif
+
+#ifdef LOG_LOCAL4
+ openlog( logName, OPENLOG_OPTIONS, syslogUser );
+#elif defined LOG_DEBUG
+ openlog( logName, OPENLOG_OPTIONS );
+#endif
+#ifdef HAVE_EBCDIC
+ free( logName );
+#endif
+ }
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+ Debug( LDAP_DEBUG_ANY, "%s", Versionstr );
+
+ global_host = ldap_pvt_get_fqdn( NULL );
+
+ if ( check == CHECK_NONE && lloadd_listeners_init( urls ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 16 );
+ goto stop;
+ }
+
+#if defined(HAVE_CHROOT)
+ if ( sandbox ) {
+ if ( chdir( sandbox ) ) {
+ perror( "chdir" );
+ rc = 1;
+ goto stop;
+ }
+ if ( chroot( sandbox ) ) {
+ perror( "chroot" );
+ rc = 1;
+ goto stop;
+ }
+ if ( chdir( "/" ) ) {
+ perror( "chdir" );
+ rc = 1;
+ goto stop;
+ }
+ }
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ if ( username != NULL || groupname != NULL ) {
+ slap_init_user( username, groupname );
+ }
+#endif
+
+ rc = lload_init( serverMode, serverName );
+ if ( rc ) {
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 18 );
+ goto destroy;
+ }
+
+ if ( lload_read_config( configfile, configdir ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 19 );
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check failed\n" );
+ }
+
+ goto destroy;
+ }
+
+ if ( debug_unknowns ) {
+ rc = parse_debug_unknowns( debug_unknowns, &slap_debug );
+ ldap_charray_free( debug_unknowns );
+ debug_unknowns = NULL;
+ if ( rc ) goto destroy;
+ }
+ if ( syslog_unknowns ) {
+ rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog );
+ ldap_charray_free( syslog_unknowns );
+ syslog_unknowns = NULL;
+ if ( rc ) goto destroy;
+ }
+
+ if ( check & CHECK_LOGLEVEL ) {
+ rc = 0;
+ goto destroy;
+ }
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check succeeded\n" );
+
+ check &= ~CHECK_CONFIG;
+ if ( check == CHECK_NONE ) {
+ rc = 0;
+ goto destroy;
+ }
+ }
+
+#ifdef HAVE_TLS
+ rc = ldap_pvt_tls_init( 1 );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "TLS init failed: %d\n",
+ rc );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+
+ if ( lload_tls_init() ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+#endif
+
+ daemon_base = event_base_new();
+ if ( !daemon_base ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "main event base allocation failed\n" );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 );
+ goto destroy;
+ }
+
+ for ( i = 0; signal_handlers[i].signal; i++ ) {
+ struct event *event;
+ event = evsignal_new( daemon_base, signal_handlers[i].signal,
+ signal_handlers[i].handler, daemon_base );
+ if ( !event || event_add( event, NULL ) ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "failed to register a handler for signal %d\n",
+ signal_handlers[i].signal );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 );
+ goto destroy;
+ }
+ signal_handlers[i].event = event;
+ }
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ if ( lutil_pair( waitfds ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "lutil_pair failed\n" );
+ rc = 1;
+ goto destroy;
+ }
+ pid = lutil_detach( no_detach, 0 );
+ if ( pid ) {
+ char buf[4];
+ rc = EXIT_SUCCESS;
+ close( waitfds[1] );
+ if ( read( waitfds[0], buf, 1 ) != 1 ) rc = EXIT_FAILURE;
+ _exit( rc );
+ } else {
+ close( waitfds[0] );
+ }
+ }
+#endif /* HAVE_WINSOCK */
+
+ if ( slapd_pid_file != NULL ) {
+ FILE *fp = fopen( slapd_pid_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open pid file "
+ "\"%s\": %d (%s)\n",
+ slapd_pid_file, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_pid_file );
+ slapd_pid_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+ fprintf( fp, "%d\n", (int)getpid() );
+ fclose( fp );
+ slapd_pid_file_unlink = 1;
+ }
+
+ if ( slapd_args_file != NULL ) {
+ FILE *fp = fopen( slapd_args_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open args file "
+ "\"%s\": %d (%s)\n",
+ slapd_args_file, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_args_file );
+ slapd_args_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+
+ for ( i = 0; i < g_argc; i++ ) {
+ fprintf( fp, "%s ", g_argv[i] );
+ }
+ fprintf( fp, "\n" );
+ fclose( fp );
+ slapd_args_file_unlink = 1;
+ }
+
+ /*
+ * FIXME: moved here from lloadd_daemon_task()
+ * because back-monitor db_open() needs it
+ */
+ time( &starttime );
+
+ Debug( LDAP_DEBUG_ANY, "lloadd starting\n" );
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ (void)!write( waitfds[1], "1", 1 );
+ close( waitfds[1] );
+ }
+#endif
+
+#ifdef HAVE_NT_EVENT_LOG
+ if ( is_NT_Service )
+ lutil_LogStartedEvent( serverName, slap_debug,
+ configfile ? configfile : LLOADD_DEFAULT_CONFIGFILE, urls );
+#endif
+
+ rc = lloadd_daemon( daemon_base );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ /* Throw away the event that we used during the startup process. */
+ if ( is_NT_Service ) ldap_pvt_thread_cond_destroy( &started_event );
+#endif
+
+destroy:
+ if ( daemon_base ) {
+ for ( i = 0; signal_handlers[i].signal; i++ ) {
+ if ( signal_handlers[i].event ) {
+ event_del( signal_handlers[i].event );
+ event_free( signal_handlers[i].event );
+ }
+ }
+ event_base_free( daemon_base );
+ }
+
+ if ( check & CHECK_LOGLEVEL ) {
+ (void)loglevel_print( stdout );
+ }
+ /* remember an error during destroy */
+ rc |= lload_global_destroy();
+ rc |= lload_destroy();
+
+stop:
+#ifdef HAVE_NT_EVENT_LOG
+ if ( is_NT_Service ) lutil_LogStoppedEvent( serverName );
+#endif
+
+ Debug( LDAP_DEBUG_ANY, "lloadd stopped.\n" );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ lutil_ReportShutdownComplete();
+#endif
+
+#ifdef LOG_DEBUG
+ closelog();
+#endif
+ lloadd_daemon_destroy();
+
+#ifdef HAVE_TLS
+ ldap_pvt_tls_destroy();
+#endif
+
+ if ( slapd_pid_file_unlink ) {
+ unlink( slapd_pid_file );
+ }
+ if ( slapd_args_file_unlink ) {
+ unlink( slapd_args_file );
+ }
+
+ lload_config_destroy();
+
+ if ( configfile ) ch_free( configfile );
+ if ( configdir ) ch_free( configdir );
+ if ( urls ) ch_free( urls );
+ if ( global_host ) ch_free( global_host );
+
+ /* kludge, get symbols referenced */
+ ldap_tavl_free( NULL, NULL );
+
+ ldap_pvt_thread_mutex_destroy( &logfile_mutex );
+ MAIN_RETURN(rc);
+}
+
+#ifdef SIGPIPE
+
+/*
+ * Catch and discard terminated child processes, to avoid zombies.
+ */
+
+static void
+sigpipe( evutil_socket_t sig, short what, void *arg )
+{
+}
+
+#endif /* SIGPIPE */
+
+#ifdef LDAP_SIGCHLD
+
+/*
+ * Catch and discard terminated child processes, to avoid zombies.
+ */
+
+static void
+wait4child( evutil_socket_t sig, short what, void *arg )
+{
+ int save_errno = errno;
+
+#ifdef WNOHANG
+ do
+ errno = 0;
+#ifdef HAVE_WAITPID
+ while ( waitpid( (pid_t)-1, NULL, WNOHANG ) > 0 || errno == EINTR );
+#else
+ while ( wait3( NULL, WNOHANG, NULL ) > 0 || errno == EINTR );
+#endif
+#else
+ (void)wait( NULL );
+#endif
+ errno = save_errno;
+}
+
+#endif /* LDAP_SIGCHLD */
diff --git a/servers/lloadd/module_init.c b/servers/lloadd/module_init.c
new file mode 100644
index 0000000..a122a52
--- /dev/null
+++ b/servers/lloadd/module_init.c
@@ -0,0 +1,190 @@
+/* module_init.c - module initialization functions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "../servers/slapd/slap.h"
+#include "../servers/slapd/slap-config.h"
+
+#include "lload.h"
+#include "lber_pvt.h"
+
+#include "ldap_rq.h"
+
+ldap_pvt_thread_t lloadd_main_thread;
+struct lload_conf_info lload_info;
+
+void *
+lload_start_daemon( void *arg )
+{
+ int rc = 0;
+
+ daemon_base = event_base_new();
+ if ( !daemon_base ) {
+ Debug( LDAP_DEBUG_ANY, "lload_start_daemon: "
+ "main event base allocation failed\n" );
+ rc = 1;
+ goto done;
+ }
+
+ rc = lloadd_daemon( daemon_base );
+done:
+ if ( rc != LDAP_SUCCESS ) {
+ assert( lloadd_inited == 0 );
+ checked_lock( &lload_wait_mutex );
+ ldap_pvt_thread_cond_signal( &lload_wait_cond );
+ checked_unlock( &lload_wait_mutex );
+ }
+ return (void *)(uintptr_t)rc;
+}
+
+static int
+lload_pause_cb( BackendInfo *bi )
+{
+ if ( daemon_base ) {
+ lload_pause_server();
+ }
+ return 0;
+}
+
+static int
+lload_unpause_cb( BackendInfo *bi )
+{
+ if ( daemon_base ) {
+ lload_unpause_server();
+ }
+ return 0;
+}
+
+int
+lload_back_open( BackendInfo *bi )
+{
+ int rc = 0;
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ /* This will fail if we ever try to instantiate more than one lloadd within
+ * the process */
+ epoch_init();
+
+ if ( lload_tls_init() != 0 ) {
+ return -1;
+ }
+
+ if ( lload_monitor_open() != 0 ) {
+ return -1;
+ }
+
+ assert( lloadd_get_listeners() );
+
+ checked_lock( &lload_wait_mutex );
+ rc = ldap_pvt_thread_create( &lloadd_main_thread,
+ 0, lload_start_daemon, NULL );
+ if ( !rc ) {
+ ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex );
+ if ( lloadd_inited != 1 ) {
+ ldap_pvt_thread_join( lloadd_main_thread, (void *)NULL );
+ rc = -1;
+ }
+ }
+ checked_unlock( &lload_wait_mutex );
+ return rc;
+}
+
+int
+lload_back_close( BackendInfo *bi )
+{
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ assert( lloadd_inited == 1 );
+
+ checked_lock( &lload_wait_mutex );
+ event_base_loopexit( daemon_base, NULL );
+ ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex );
+ checked_unlock( &lload_wait_mutex );
+ ldap_pvt_thread_join( lloadd_main_thread, (void *)NULL );
+
+ return 0;
+}
+
+int
+lload_back_destroy( BackendInfo *bi )
+{
+ return lload_global_destroy();
+}
+
+int
+lload_back_initialize( BackendInfo *bi )
+{
+ bi->bi_flags = SLAP_BFLAG_STANDALONE;
+ bi->bi_open = lload_back_open;
+ bi->bi_pause = lload_pause_cb;
+ bi->bi_unpause = lload_unpause_cb;
+ bi->bi_close = lload_back_close;
+ bi->bi_destroy = lload_back_destroy;
+
+ bi->bi_db_init = 0;
+ bi->bi_db_config = 0;
+ bi->bi_db_open = 0;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = 0;
+
+ bi->bi_op_bind = 0;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = 0;
+ bi->bi_op_compare = 0;
+ bi->bi_op_modify = 0;
+ bi->bi_op_modrdn = 0;
+ bi->bi_op_add = 0;
+ bi->bi_op_delete = 0;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ if ( lload_global_init() ) {
+ return -1;
+ }
+
+ bi->bi_private = &lload_info;
+ return lload_back_init_cf( bi );
+}
+
+SLAP_BACKEND_INIT_MODULE( lload )
diff --git a/servers/lloadd/monitor.c b/servers/lloadd/monitor.c
new file mode 100644
index 0000000..9eaeecf
--- /dev/null
+++ b/servers/lloadd/monitor.c
@@ -0,0 +1,1368 @@
+/* init.c - initialize various things */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lload.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+
+#include "ldap_rq.h"
+#include "lload-config.h"
+#include "../slapd/back-monitor/back-monitor.h"
+
+#define LLOAD_MONITOR_BALANCER_NAME "Load Balancer"
+#define LLOAD_MONITOR_BALANCER_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_BALANCER_NAME
+#define LLOAD_MONITOR_BALANCER_DN \
+ LLOAD_MONITOR_BALANCER_RDN "," SLAPD_MONITOR_BACKEND_DN
+
+#define LLOAD_MONITOR_INCOMING_NAME "Incoming Connections"
+#define LLOAD_MONITOR_INCOMING_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_INCOMING_NAME
+#define LLOAD_MONITOR_INCOMING_DN \
+ LLOAD_MONITOR_INCOMING_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+#define LLOAD_MONITOR_OPERATIONS_NAME "Operations"
+#define LLOAD_MONITOR_OPERATIONS_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_OPERATIONS_NAME
+#define LLOAD_MONITOR_OPERATIONS_DN \
+ LLOAD_MONITOR_OPERATIONS_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+#define LLOAD_MONITOR_TIERS_NAME "Backend Tiers"
+#define LLOAD_MONITOR_TIERS_RDN SLAPD_MONITOR_AT "=" LLOAD_MONITOR_TIERS_NAME
+#define LLOAD_MONITOR_TIERS_DN \
+ LLOAD_MONITOR_TIERS_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+struct lload_monitor_ops_t {
+ struct berval rdn;
+} lload_monitor_op[] = {
+ { BER_BVC("cn=Bind") },
+ { BER_BVC("cn=Other") },
+
+ { BER_BVNULL }
+};
+
+static ObjectClass *oc_olmBalancer;
+static ObjectClass *oc_olmBalancerServer;
+static ObjectClass *oc_olmBalancerConnection;
+static ObjectClass *oc_olmBalancerOperation;
+
+static ObjectClass *oc_monitorContainer;
+static ObjectClass *oc_monitorCounterObject;
+
+static AttributeDescription *ad_olmServerURI;
+static AttributeDescription *ad_olmReceivedOps;
+static AttributeDescription *ad_olmForwardedOps;
+static AttributeDescription *ad_olmRejectedOps;
+static AttributeDescription *ad_olmCompletedOps;
+static AttributeDescription *ad_olmFailedOps;
+static AttributeDescription *ad_olmConnectionType;
+static AttributeDescription *ad_olmConnectionState;
+static AttributeDescription *ad_olmPendingOps;
+static AttributeDescription *ad_olmPendingConnections;
+static AttributeDescription *ad_olmActiveConnections;
+static AttributeDescription *ad_olmIncomingConnections;
+static AttributeDescription *ad_olmOutgoingConnections;
+
+monitor_subsys_t *lload_monitor_client_subsys;
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "olmBalancerAttributes", "olmModuleAttributes:1" },
+ { "olmBalancerObjectClasses", "olmModuleObjectClasses:1" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **ad;
+} s_at[] = {
+ { "( olmBalancerAttributes:1 "
+ "NAME ( 'olmServerURI' ) "
+ "DESC 'URI of a backend server' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "EQUALITY caseIgnoreMatch "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmServerURI },
+ { "( olmBalancerAttributes:2 "
+ "NAME ( 'olmReceivedOps' ) "
+ "DESC 'monitor received operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmReceivedOps },
+ { "( olmBalancerAttributes:3 "
+ "NAME ( 'olmForwardedOps' ) "
+ "DESC 'monitor forwarded operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmForwardedOps },
+ { "( olmBalancerAttributes:4 "
+ "NAME ( 'olmRejectedOps' ) "
+ "DESC 'monitor rejected operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmRejectedOps },
+ { "( olmBalancerAttributes:5 "
+ "NAME ( 'olmCompletedOps' ) "
+ "DESC 'monitor completed operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmCompletedOps },
+ { "( olmBalancerAttributes:6 "
+ "NAME ( 'olmFailedOps' ) "
+ "DESC 'monitor failed operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmFailedOps },
+ { "( olmBalancerAttributes:7 "
+ "NAME ( 'olmPendingOps' ) "
+ "DESC 'monitor number of pending operations' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmPendingOps },
+ { "( olmBalancerAttributes:8 "
+ "NAME ( 'olmPendingConnections' ) "
+ "DESC 'monitor number of pending connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmPendingConnections },
+ { "( olmBalancerAttributes:9 "
+ "NAME ( 'olmActiveConnections' ) "
+ "DESC 'monitor number of active connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmActiveConnections },
+ { "( olmBalancerAttributes:10 "
+ "NAME ( 'olmConnectionType' ) "
+ "DESC 'Connection type' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmConnectionType },
+ { "( olmBalancerAttributes:11 "
+ "NAME ( 'olmIncomingConnections' ) "
+ "DESC 'monitor number of incoming connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmIncomingConnections },
+ { "( olmBalancerAttributes:12 "
+ "NAME ( 'olmOutgoingConnections' ) "
+ "DESC 'monitor number of active connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmOutgoingConnections },
+ { "( olmBalancerAttributes:13 "
+ "NAME ( 'olmConnectionState' ) "
+ "DESC 'Connection state' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "USAGE dSAOperation )",
+ &ad_olmConnectionState },
+
+ { NULL }
+};
+
+static struct {
+ char *name;
+ ObjectClass **oc;
+} s_moc[] = {
+ { "monitorContainer", &oc_monitorContainer },
+ { "monitorCounterObject", &oc_monitorCounterObject },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **oc;
+} s_oc[] = {
+ { "( olmBalancerObjectClasses:1 "
+ "NAME ( 'olmBalancer' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmIncomingConnections "
+ "$ olmOutgoingConnections "
+ ") )",
+ &oc_olmBalancer },
+ { "( olmBalancerObjectClasses:2 "
+ "NAME ( 'olmBalancerServer' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmServerURI "
+ "$ olmActiveConnections "
+ "$ olmPendingConnections "
+ "$ olmPendingOps"
+ "$ olmReceivedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerServer },
+
+ { "( olmBalancerObjectClasses:3 "
+ "NAME ( 'olmBalancerOperation' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmReceivedOps "
+ "$ olmForwardedOps "
+ "$ olmRejectedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerOperation },
+ { "( olmBalancerObjectClasses:4 "
+ "NAME ( 'olmBalancerConnection' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmConnectionType "
+ "$ olmConnectionState "
+ "$ olmPendingOps "
+ "$ olmReceivedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerConnection },
+ { NULL }
+};
+
+static int
+lload_monitor_subsystem_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ ch_free( ms->mss_dn.bv_val );
+ ch_free( ms->mss_ndn.bv_val );
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_subsystem_free( BackendDB *be, monitor_subsys_t *ms )
+{
+ lload_monitor_subsystem_destroy( be, ms );
+ ch_free( ms );
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_backend_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ LloadBackend *b = ms->mss_private;
+ monitor_extra_t *mbe;
+ int rc = LDAP_SUCCESS;
+
+ ms->mss_destroy = lload_monitor_subsystem_free;
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+ if ( b->b_monitor ) {
+ assert( b->b_monitor == ms );
+ b->b_monitor = NULL;
+
+ rc = mbe->unregister_entry( &ms->mss_ndn );
+ }
+
+ return rc;
+}
+
+static int
+lload_monitor_tier_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ LloadTier *tier = ms->mss_private;
+
+ assert( slapd_shutdown || ( tier && tier->t_monitor == ms ) );
+
+ ms->mss_destroy = lload_monitor_subsystem_free;
+
+ if ( !slapd_shutdown ) {
+ monitor_extra_t *mbe;
+
+ tier->t_monitor = NULL;
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+ return mbe->unregister_entry( &ms->mss_ndn );
+ }
+
+ return ms->mss_destroy( be, ms );
+}
+
+static void
+lload_monitor_balancer_dispose( void **priv )
+{
+ return;
+}
+
+static int
+lload_monitor_balancer_free( Entry *e, void **priv )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_balancer_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ Attribute *a;
+
+ a = attr_find( e->e_attrs, ad_olmIncomingConnections );
+ assert( a != NULL );
+
+ UI2BV( &a->a_vals[0], lload_stats.global_incoming );
+
+ a = attr_find( e->e_attrs, ad_olmOutgoingConnections );
+ assert( a != NULL );
+
+ UI2BV( &a->a_vals[0], lload_stats.global_outgoing );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+lload_monitor_ops_update( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Attribute *a;
+ lload_counters_t *counters = (lload_counters_t *)priv;
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_received );
+
+ a = attr_find( e->e_attrs, ad_olmForwardedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_forwarded );
+
+ a = attr_find( e->e_attrs, ad_olmRejectedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_rejected );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+lload_monitor_ops_dispose( void **priv )
+{
+ return;
+}
+
+static int
+lload_monitor_ops_free( Entry *e, void **priv )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_balancer_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e;
+ int rc;
+ monitor_callback_t *cb;
+ struct berval value = BER_BVC("0");
+
+ assert( be != NULL );
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_olmBalancer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_balancer_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_balancer_update;
+ cb->mc_free = lload_monitor_balancer_free;
+ cb->mc_dispose = lload_monitor_balancer_dispose;
+ cb->mc_private = NULL;
+
+ attr_merge_normalize_one( e, ad_olmIncomingConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmOutgoingConnections, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_balancer_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_ops_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e, *parent;
+ int rc;
+ int i;
+ struct berval value = BER_BVC("0");
+
+ assert( be != NULL );
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+ ms->mss_destroy = lload_monitor_subsystem_destroy;
+
+ parent = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( parent == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &parent->e_name );
+ ber_dupbv( &ms->mss_ndn, &parent->e_nname );
+
+ rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ parent->e_name.bv_val );
+ goto done;
+ }
+
+ for ( i = 0; lload_monitor_op[i].rdn.bv_val != NULL; i++ ) {
+ monitor_callback_t *cb;
+ e = mbe->entry_stub( &parent->e_name, &parent->e_nname,
+ &lload_monitor_op[i].rdn, oc_olmBalancerOperation, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ lload_monitor_op[i].rdn.bv_val, parent->e_nname.bv_val );
+ return -1;
+ }
+
+ /* attr_merge_normalize_one( e, ad_olmDbOperations, &value, NULL ); */
+
+ /*
+ * We cannot share a single callback between entries.
+ *
+ * monitor_cache_destroy() tries to free all callbacks and it's called
+ * before mss_destroy() so we have no chance of handling it ourselves
+ */
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_ops_update;
+ cb->mc_free = lload_monitor_ops_free;
+ cb->mc_dispose = lload_monitor_ops_dispose;
+ cb->mc_private = &lload_stats.counters[i];
+
+ attr_merge_normalize_one( e, ad_olmReceivedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmForwardedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmRejectedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmCompletedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmFailedOps, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+
+ entry_free( e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ ch_free( cb );
+ break;
+ }
+ }
+
+done:
+ entry_free( parent );
+ return rc;
+}
+
+static void *
+lload_monitor_release_conn( void *ctx, void *arg )
+{
+ LloadConnection *c = arg;
+ epoch_t epoch = epoch_join();
+
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ epoch_leave( epoch );
+ return NULL;
+}
+
+static int
+lload_monitor_conn_modify( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Modifications *m;
+ LloadConnection *c = priv;
+ int rc = SLAP_CB_CONTINUE;
+ epoch_t epoch;
+
+ if ( !acquire_ref( &c->c_refcnt ) ) {
+ /* Shutting down, pretend it's already happened */
+ return LDAP_NO_SUCH_OBJECT;
+ }
+ epoch = epoch_join();
+
+ for ( m = op->orm_modlist; m; m = m->sml_next ) {
+ struct berval closing = BER_BVC("closing");
+ int gentle = 1;
+
+ if ( m->sml_flags & SLAP_MOD_INTERNAL ) continue;
+
+ if ( m->sml_desc != ad_olmConnectionState ||
+ m->sml_op != LDAP_MOD_REPLACE || m->sml_numvals != 1 ||
+ ber_bvcmp( &m->sml_nvalues[0], &closing ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( lload_connection_close( c, &gentle ) ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+done:
+ epoch_leave( epoch );
+ /*
+ * The connection might have been ready to disappear in epoch_leave(), that
+ * involves deleting this monitor entry. Make sure that doesn't happen
+ * punting the decref into a separate task that's not holding any locks and
+ * finishes after we did.
+ *
+ * FIXME: It would probably be cleaner to defer the entry deletion into a
+ * separate task instead but the entry holds a pointer to this connection
+ * that might not be safe to manipulate.
+ */
+ ldap_pvt_thread_pool_submit(
+ &connection_pool, lload_monitor_release_conn, c );
+ return rc;
+}
+
+/*
+ * Monitor cache is locked, the connection cannot be unlinked and freed under us.
+ * That also means we need to unlock and finish as soon as possible.
+ */
+static int
+lload_monitor_conn_update( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Attribute *a;
+ LloadConnection *c = priv;
+ struct berval bv_type, bv_state;
+ ldap_pvt_mp_t active, pending, received, completed, failed;
+
+ CONNECTION_LOCK(c);
+
+ pending = (ldap_pvt_mp_t)c->c_n_ops_executing;
+ received = c->c_counters.lc_ops_received;
+ completed = c->c_counters.lc_ops_completed;
+ failed = c->c_counters.lc_ops_failed;
+
+ switch ( c->c_type ) {
+ case LLOAD_C_OPEN: {
+ struct berval bv = BER_BVC("regular");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_PREPARING: {
+ struct berval bv = BER_BVC("preparing");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_BIND: {
+ struct berval bv = BER_BVC("bind");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_PRIVILEGED: {
+ struct berval bv = BER_BVC("privileged");
+ bv_type = bv;
+ } break;
+ default: {
+ struct berval bv = BER_BVC("unknown");
+ bv_type = bv;
+ } break;
+ }
+
+ switch ( c->c_state ) {
+ case LLOAD_C_INVALID: {
+ /* *_destroy removes the entry from list before setting c_state to
+ * INVALID */
+ assert(0);
+ } break;
+ case LLOAD_C_READY: {
+ struct berval bv = BER_BVC("ready");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_CLOSING: {
+ struct berval bv = BER_BVC("closing");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_ACTIVE: {
+ struct berval bv = BER_BVC("active");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_BINDING: {
+ struct berval bv = BER_BVC("binding");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_DYING: {
+ /* I guess we got it before it was unlinked? */
+ struct berval bv = BER_BVC("dying");
+ bv_state = bv;
+ } break;
+ default: {
+ struct berval bv = BER_BVC("unknown");
+ bv_state = bv;
+ } break;
+ }
+
+ CONNECTION_UNLOCK(c);
+
+ a = attr_find( e->e_attrs, ad_olmConnectionType );
+ assert( a != NULL );
+ if ( !(a->a_flags & SLAP_ATTR_DONT_FREE_DATA) ) {
+ ber_memfree( a->a_vals[0].bv_val );
+ a->a_flags |= SLAP_ATTR_DONT_FREE_DATA;
+ }
+ a->a_vals[0] = bv_type;
+
+ a = attr_find( e->e_attrs, ad_olmConnectionState );
+ assert( a != NULL );
+ if ( !(a->a_flags & SLAP_ATTR_DONT_FREE_DATA) ) {
+ ber_memfree( a->a_vals[0].bv_val );
+ a->a_flags |= SLAP_ATTR_DONT_FREE_DATA;
+ }
+ a->a_vals[0] = bv_state;
+
+ a = attr_find( e->e_attrs, ad_olmPendingOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], pending );
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], received );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+int
+lload_monitor_conn_unlink( LloadConnection *c )
+{
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe = mi->bi_extra;
+
+ assert( mbe && mbe->is_configured() );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( !BER_BVISNULL( &c->c_monitor_dn ) );
+
+ /*
+ * Avoid a lock inversion with threads holding monitor cache locks in turn
+ * waiting on CONNECTION_LOCK(c)
+ */
+ CONNECTION_UNLOCK(c);
+ mbe->unregister_entry( &c->c_monitor_dn );
+ CONNECTION_LOCK(c);
+
+ ber_memfree( c->c_monitor_dn.bv_val );
+ BER_BVZERO( &c->c_monitor_dn );
+
+ return 0;
+}
+
+int
+lload_monitor_conn_entry_create( LloadConnection *c, monitor_subsys_t *ms )
+{
+ char buf[SLAP_TEXT_BUFLEN];
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];
+ struct tm tm;
+ struct berval bv_rdn, bv_timestamp, zero = BER_BVC("0"),
+ value = BER_BVC("unknown");
+ monitor_entry_t *mp;
+ monitor_callback_t *cb;
+ Entry *e;
+ Attribute *a;
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe = mi->bi_extra;
+
+ assert( mbe && mbe->is_configured() );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( BER_BVISNULL( &c->c_monitor_dn ) );
+
+ bv_rdn.bv_val = buf;
+ bv_rdn.bv_len = snprintf(
+ bv_rdn.bv_val, SLAP_TEXT_BUFLEN, "cn=Connection %lu", c->c_connid );
+
+ ldap_pvt_gmtime( &c->c_activitytime, &tm );
+ bv_timestamp.bv_len = lutil_gentime( timebuf, sizeof(timebuf), &tm );
+ bv_timestamp.bv_val = timebuf;
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv_rdn,
+ oc_olmBalancerConnection, &bv_timestamp, &bv_timestamp );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_conn_update;
+ cb->mc_modify = lload_monitor_conn_modify;
+ cb->mc_private = c;
+
+ attr_merge_one( e, ad_olmConnectionType, &value, NULL );
+ attr_merge_one( e, ad_olmConnectionState, &value, NULL );
+ attr_merge_one( e, ad_olmPendingOps, &zero, NULL );
+ attr_merge_one( e, ad_olmReceivedOps, &zero, NULL );
+ attr_merge_one( e, ad_olmCompletedOps, &zero, NULL );
+ attr_merge_one( e, ad_olmFailedOps, &zero, NULL );
+
+ if ( mbe->register_entry( e, cb, NULL, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_conn_entry_create: "
+ "failed to register monitor entry for connid=%lu\n",
+ c->c_connid );
+
+ ch_free( cb );
+ entry_free( e );
+ return -1;
+ }
+
+ ber_dupbv( &c->c_monitor_dn, &e->e_nname );
+ entry_free( e );
+
+ return 0;
+}
+
+static int
+lload_monitor_incoming_conn_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ ms->mss_destroy = lload_monitor_subsystem_destroy;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_incoming_conn_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, 0 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_incoming_conn_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ lload_monitor_client_subsys = ms;
+
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_server_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ Attribute *a;
+ LloadBackend *b = priv;
+ LloadConnection *c;
+ LloadPendingConnection *pc;
+ ldap_pvt_mp_t active = 0, pending = 0, received = 0, completed = 0,
+ failed = 0;
+ int i;
+
+ checked_lock( &b->b_mutex );
+ active = b->b_active + b->b_bindavail;
+
+ LDAP_CIRCLEQ_FOREACH ( c, &b->b_preparing, c_next ) {
+ pending++;
+ }
+
+ LDAP_LIST_FOREACH( pc, &b->b_connecting, next ) {
+ pending++;
+ }
+
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ received += b->b_counters[i].lc_ops_received;
+ completed += b->b_counters[i].lc_ops_completed;
+ failed += b->b_counters[i].lc_ops_failed;
+ }
+
+ a = attr_find( e->e_attrs, ad_olmPendingOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], (long long unsigned int)b->b_n_ops_executing );
+
+ checked_unlock( &b->b_mutex );
+
+ /* Right now, there is no way to retrieve the entry from monitor's
+ * cache to replace URI at the moment it is modified */
+ a = attr_find( e->e_attrs, ad_olmServerURI );
+ assert( a != NULL );
+ ber_bvreplace( &a->a_vals[0], &b->b_uri );
+
+ a = attr_find( e->e_attrs, ad_olmActiveConnections );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], active );
+
+ a = attr_find( e->e_attrs, ad_olmPendingConnections );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], pending );
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], received );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+lload_monitor_backend_open( BackendDB *be, monitor_subsys_t *ms )
+{
+ Entry *e;
+ struct berval value = BER_BVC("0");
+ monitor_extra_t *mbe;
+ monitor_callback_t *cb;
+ LloadBackend *b = ms->mss_private;
+ LloadTier *tier = b->b_tier;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ e = mbe->entry_stub( &tier->t_monitor->mss_dn, &tier->t_monitor->mss_ndn,
+ &ms->mss_rdn, oc_olmBalancerServer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_open: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, tier->t_monitor->mss_dn.bv_val );
+ return -1;
+ }
+
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_server_update;
+ cb->mc_free = NULL;
+ cb->mc_dispose = NULL;
+ cb->mc_private = b;
+
+ attr_merge_normalize_one( e, ad_olmServerURI, &b->b_uri, NULL );
+ attr_merge_normalize_one( e, ad_olmActiveConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmPendingConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmPendingOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmReceivedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmCompletedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmFailedOps, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_open: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ ms->mss_destroy = lload_monitor_backend_destroy;
+
+done:
+ entry_free( e );
+ return rc;
+}
+
+int
+lload_monitor_backend_init(
+ BackendInfo *bi,
+ monitor_subsys_t *ms,
+ LloadBackend *b )
+{
+ monitor_extra_t *mbe = bi->bi_extra;
+ monitor_subsys_t *bk_mss;
+
+ /* FIXME: With back-monitor as it works now, there is no way to know when
+ * this can be safely freed so we leak it on shutdown */
+ bk_mss = ch_calloc( 1, sizeof(monitor_subsys_t) );
+ bk_mss->mss_rdn.bv_len = sizeof("cn=") + b->b_name.bv_len;
+ bk_mss->mss_rdn.bv_val = ch_malloc( bk_mss->mss_rdn.bv_len );
+ bk_mss->mss_rdn.bv_len = snprintf( bk_mss->mss_rdn.bv_val,
+ bk_mss->mss_rdn.bv_len, "cn=%s", b->b_name.bv_val );
+
+ bk_mss->mss_name = b->b_name.bv_val;
+ bk_mss->mss_flags = MONITOR_F_NONE;
+ bk_mss->mss_open = lload_monitor_backend_open;
+ bk_mss->mss_destroy = lload_monitor_subsystem_destroy;
+ bk_mss->mss_update = NULL;
+ bk_mss->mss_private = b;
+
+ if ( mbe->register_subsys_late( bk_mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_init: "
+ "failed to register backend %s\n",
+ bk_mss->mss_name );
+ ch_free( bk_mss );
+ return -1;
+ }
+
+ b->b_monitor = bk_mss;
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_tier_open( BackendDB *be, monitor_subsys_t *ms )
+{
+ Entry *e;
+ monitor_extra_t *mbe;
+ LloadTier *tier = ms->mss_private;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_open: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, MONITOR_F_PERSISTENT_CH );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_open: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ tier->t_monitor = ms;
+ ms->mss_destroy = lload_monitor_tier_destroy;
+
+done:
+ entry_free( e );
+ return rc;
+}
+
+int
+lload_monitor_tier_init( BackendInfo *bi, LloadTier *tier )
+{
+ monitor_extra_t *mbe;
+ monitor_subsys_t *mss;
+ LloadBackend *b;
+
+ mbe = (monitor_extra_t *)bi->bi_extra;
+
+ mss = ch_calloc( 1, sizeof(monitor_subsys_t) );
+ mss->mss_rdn.bv_len = sizeof("cn=") + tier->t_name.bv_len;
+ mss->mss_rdn.bv_val = ch_malloc( mss->mss_rdn.bv_len );
+ mss->mss_rdn.bv_len = snprintf( mss->mss_rdn.bv_val, mss->mss_rdn.bv_len,
+ "cn=%s", tier->t_name.bv_val );
+
+ ber_str2bv( LLOAD_MONITOR_TIERS_DN, 0, 0, &mss->mss_dn );
+ mss->mss_name = tier->t_name.bv_val;
+ mss->mss_open = lload_monitor_tier_open;
+ mss->mss_destroy = lload_monitor_subsystem_destroy;
+ mss->mss_update = NULL;
+ mss->mss_private = tier;
+
+ if ( mbe->register_subsys_late( mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_init: "
+ "failed to register backend %s\n",
+ mss->mss_name );
+ return -1;
+ }
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ if ( lload_monitor_backend_init( bi, mss, b ) ) {
+ return -1;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+lload_monitor_tiers_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ LloadTier *tier;
+ Entry *e;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tiers_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, MONITOR_F_PERSISTENT_CH );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tiers_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ if ( (rc = lload_monitor_tier_init( be->bd_info, tier )) ) {
+ break;
+ }
+ }
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_incoming_count( LloadConnection *conn, void *argv )
+{
+ lload_global_stats_t *tmp_stats = argv;
+ tmp_stats->global_incoming++;
+ return 0;
+}
+
+/*
+ * Update all global statistics other than rejected and received,
+ * which are updated in real time
+ */
+void *
+lload_monitor_update_global_stats( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ lload_global_stats_t tmp_stats = {};
+ LloadTier *tier;
+ int i;
+
+ Debug( LDAP_DEBUG_TRACE, "lload_monitor_update_global_stats: "
+ "updating stats\n" );
+
+ /* count incoming connections */
+ checked_lock( &clients_mutex );
+ connections_walk( &clients_mutex, &clients, lload_monitor_incoming_count,
+ &tmp_stats );
+ checked_unlock( &clients_mutex );
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ checked_lock( &b->b_mutex );
+ tmp_stats.global_outgoing += b->b_active + b->b_bindavail;
+
+ /* merge completed and failed stats */
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ tmp_stats.counters[i].lc_ops_completed +=
+ b->b_counters[i].lc_ops_completed;
+ tmp_stats.counters[i].lc_ops_failed +=
+ b->b_counters[i].lc_ops_failed;
+ }
+ checked_unlock( &b->b_mutex );
+ }
+ }
+
+ /* update lload_stats */
+ lload_stats.global_outgoing = tmp_stats.global_outgoing;
+ lload_stats.global_incoming = tmp_stats.global_incoming;
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ lload_stats.counters[i].lc_ops_completed =
+ tmp_stats.counters[i].lc_ops_completed;
+ lload_stats.counters[i].lc_ops_failed =
+ tmp_stats.counters[i].lc_ops_failed;
+ }
+
+ /* reschedule */
+ checked_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ checked_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
+static char *lload_subsys_rdn[] = {
+ LLOAD_MONITOR_BALANCER_RDN,
+ LLOAD_MONITOR_INCOMING_RDN,
+ LLOAD_MONITOR_OPERATIONS_RDN,
+ LLOAD_MONITOR_TIERS_RDN,
+ NULL
+};
+
+static struct monitor_subsys_t balancer_subsys[] = {
+ {
+ LLOAD_MONITOR_BALANCER_NAME,
+ BER_BVNULL,
+ BER_BVC(SLAPD_MONITOR_BACKEND_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer information"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_balancer_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_INCOMING_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer incoming connections"),
+ BER_BVNULL },
+ MONITOR_F_NONE,
+ lload_monitor_incoming_conn_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_OPERATIONS_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer global operation statistics"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_ops_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_TIERS_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer Backends information"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_tiers_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ { NULL }
+};
+
+int
+lload_monitor_open( void )
+{
+ static int lload_monitor_initialized_failure = 1;
+ static int lload_monitor_initialized = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+ monitor_subsys_t *mss;
+ ConfigArgs c;
+ char *argv[3], **rdn;
+ int i, rc;
+
+ /* check if monitor is configured and usable */
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ Debug( LDAP_DEBUG_CONFIG, "lload_monitor_open: "
+ "monitor backend not available, monitoring disabled\n" );
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG, "lload_monitor_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ return 0;
+ }
+
+ if ( lload_monitor_initialized++ ) {
+ return lload_monitor_initialized_failure;
+ }
+
+ argv[0] = "lload monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+ for ( i = 0; s_oid[i].name; i++ ) {
+ argv[1] = s_oid[i].name;
+ argv[2] = s_oid[i].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "unable to add objectIdentifier \"%s=%s\"\n",
+ s_oid[i].name, s_oid[i].oid );
+ return 2;
+ }
+ }
+
+ for ( i = 0; s_at[i].desc != NULL; i++ ) {
+ rc = register_at( s_at[i].desc, s_at[i].ad, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "register_at failed for attributeType (%s)\n",
+ s_at[i].desc );
+ return 3;
+
+ } else {
+ (*s_at[i].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+ rc = register_oc( s_oc[i].desc, s_oc[i].oc, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "register_oc failed for objectClass (%s)\n",
+ s_oc[i].desc );
+ return 4;
+
+ } else {
+ (*s_oc[i].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+ }
+
+ for ( i = 0; s_moc[i].name != NULL; i++ ) {
+ *s_moc[i].oc = oc_find( s_moc[i].name );
+ if ( !*s_moc[i].oc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "failed to find objectClass (%s)\n",
+ s_moc[i].name );
+ return 5;
+ }
+ }
+
+ /* register the subsystems - Servers are registered in backends_init */
+ for ( mss = balancer_subsys, rdn = lload_subsys_rdn; mss->mss_name;
+ mss++, rdn++ ) {
+ ber_str2bv( *rdn, 0, 1, &mss->mss_rdn );
+ if ( mbe->register_subsys_late( mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "failed to register %s subsystem\n",
+ mss->mss_name );
+ return -1;
+ }
+ }
+
+ checked_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, 1, lload_monitor_update_global_stats,
+ NULL, "lload_monitor_update_global_stats", "lloadd" );
+ checked_unlock( &slapd_rq.rq_mutex );
+
+ return (lload_monitor_initialized_failure = LDAP_SUCCESS);
+}
diff --git a/servers/lloadd/operation.c b/servers/lloadd/operation.c
new file mode 100644
index 0000000..73f91a1
--- /dev/null
+++ b/servers/lloadd/operation.c
@@ -0,0 +1,725 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lutil.h"
+#include "lload.h"
+
+ldap_pvt_thread_mutex_t lload_pin_mutex;
+unsigned long lload_next_pin = 1;
+
+TAvlnode *lload_control_actions = NULL;
+TAvlnode *lload_exop_actions = NULL;
+enum op_restriction lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED;
+
+ber_tag_t
+slap_req2res( ber_tag_t tag )
+{
+ switch ( tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_EXTENDED:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ tag++;
+ break;
+
+ case LDAP_REQ_DELETE:
+ tag = LDAP_RES_DELETE;
+ break;
+
+ case LDAP_REQ_ABANDON:
+ case LDAP_REQ_UNBIND:
+ tag = LBER_SEQUENCE;
+ break;
+
+ case LDAP_REQ_SEARCH:
+ tag = LDAP_RES_SEARCH_RESULT;
+ break;
+
+ default:
+ tag = LBER_SEQUENCE;
+ }
+
+ return tag;
+}
+
+const char *
+lload_msgtype2str( ber_tag_t tag )
+{
+ switch ( tag ) {
+ case LDAP_REQ_ABANDON: return "abandon request";
+ case LDAP_REQ_ADD: return "add request";
+ case LDAP_REQ_BIND: return "bind request";
+ case LDAP_REQ_COMPARE: return "compare request";
+ case LDAP_REQ_DELETE: return "delete request";
+ case LDAP_REQ_EXTENDED: return "extended request";
+ case LDAP_REQ_MODIFY: return "modify request";
+ case LDAP_REQ_RENAME: return "rename request";
+ case LDAP_REQ_SEARCH: return "search request";
+ case LDAP_REQ_UNBIND: return "unbind request";
+
+ case LDAP_RES_ADD: return "add result";
+ case LDAP_RES_BIND: return "bind result";
+ case LDAP_RES_COMPARE: return "compare result";
+ case LDAP_RES_DELETE: return "delete result";
+ case LDAP_RES_EXTENDED: return "extended result";
+ case LDAP_RES_INTERMEDIATE: return "intermediate response";
+ case LDAP_RES_MODIFY: return "modify result";
+ case LDAP_RES_RENAME: return "rename result";
+ case LDAP_RES_SEARCH_ENTRY: return "search-entry response";
+ case LDAP_RES_SEARCH_REFERENCE: return "search-reference response";
+ case LDAP_RES_SEARCH_RESULT: return "search result";
+ }
+ return "unknown message";
+}
+
+int
+lload_restriction_cmp( const void *left, const void *right )
+{
+ const struct restriction_entry *l = left, *r = right;
+ return ber_bvcmp( &l->oid, &r->oid );
+}
+
+int
+operation_client_cmp( const void *left, const void *right )
+{
+ const LloadOperation *l = left, *r = right;
+
+ assert( l->o_client_connid == r->o_client_connid );
+ if ( l->o_client_msgid || r->o_client_msgid ) {
+ return ( l->o_client_msgid < r->o_client_msgid ) ?
+ -1 :
+ ( l->o_client_msgid > r->o_client_msgid );
+ } else {
+ return ( l->o_pin_id < r->o_pin_id ) ? -1 :
+ ( l->o_pin_id > r->o_pin_id );
+ }
+}
+
+int
+operation_upstream_cmp( const void *left, const void *right )
+{
+ const LloadOperation *l = left, *r = right;
+
+ assert( l->o_upstream_connid == r->o_upstream_connid );
+ if ( l->o_upstream_msgid || r->o_upstream_msgid ) {
+ return ( l->o_upstream_msgid < r->o_upstream_msgid ) ?
+ -1 :
+ ( l->o_upstream_msgid > r->o_upstream_msgid );
+ } else {
+ return ( l->o_pin_id < r->o_pin_id ) ? -1 :
+ ( l->o_pin_id > r->o_pin_id );
+ }
+}
+
+/*
+ * Entered holding c_mutex for now.
+ */
+LloadOperation *
+operation_init( LloadConnection *c, BerElement *ber )
+{
+ LloadOperation *op;
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc;
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ return NULL;
+ }
+
+ op = ch_calloc( 1, sizeof(LloadOperation) );
+ op->o_client = c;
+ op->o_client_connid = c->c_connid;
+ op->o_ber = ber;
+ gettimeofday( &op->o_start, NULL );
+
+ ldap_pvt_thread_mutex_init( &op->o_link_mutex );
+
+ op->o_refcnt = 1;
+
+ tag = ber_get_int( ber, &op->o_client_msgid );
+ if ( tag != LDAP_TAG_MSGID ) {
+ goto fail;
+ }
+
+ if ( !op->o_client_msgid ) {
+ goto fail;
+ }
+
+ CONNECTION_ASSERT_LOCKED(c);
+ rc = ldap_tavl_insert( &c->c_ops, op, operation_client_cmp, ldap_avl_dup_error );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_PACKETS, "operation_init: "
+ "several operations with same msgid=%d in-flight "
+ "from client connid=%lu\n",
+ op->o_client_msgid, op->o_client_connid );
+ goto fail;
+ }
+
+ tag = op->o_tag = ber_skip_element( ber, &op->o_request );
+ switch ( tag ) {
+ case LBER_ERROR:
+ rc = -1;
+ break;
+ }
+ if ( rc ) {
+ ldap_tavl_delete( &c->c_ops, op, operation_client_cmp );
+ goto fail;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_CONTROLS ) {
+ ber_skip_element( ber, &op->o_ctrls );
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND:
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received++;
+ break;
+ default:
+ lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_received++;
+ break;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "operation_init: "
+ "received a new operation, %s with msgid=%d for client "
+ "connid=%lu\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_msgid,
+ op->o_client_connid );
+
+ c->c_n_ops_executing++;
+ return op;
+
+fail:
+ ch_free( op );
+ return NULL;
+}
+
+void
+operation_destroy( LloadOperation *op )
+{
+ Debug( LDAP_DEBUG_TRACE, "operation_destroy: "
+ "op=%p destroyed operation from client connid=%lu, "
+ "client msgid=%d\n",
+ op, op->o_client_connid, op->o_client_msgid );
+
+ assert( op->o_refcnt == 0 );
+ assert( op->o_client == NULL );
+ assert( op->o_upstream == NULL );
+
+ ber_free( op->o_ber, 1 );
+ ldap_pvt_thread_mutex_destroy( &op->o_link_mutex );
+ ch_free( op );
+}
+
+int
+operation_unlink( LloadOperation *op )
+{
+ LloadConnection *client, *upstream;
+ uintptr_t prev_refcnt;
+ int result = 0;
+
+ assert( op->o_refcnt == 0 );
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink: "
+ "unlinking operation between client connid=%lu and upstream "
+ "connid=%lu "
+ "client msgid=%d\n",
+ op->o_client_connid, op->o_upstream_connid, op->o_client_msgid );
+
+ checked_lock( &op->o_link_mutex );
+ client = op->o_client;
+ upstream = op->o_upstream;
+
+ op->o_client = NULL;
+ op->o_upstream = NULL;
+ checked_unlock( &op->o_link_mutex );
+
+ assert( client || upstream );
+
+ if ( client ) {
+ result |= operation_unlink_client( op, client );
+ operation_update_global_rejected( op );
+ }
+
+ if ( upstream ) {
+ result |= operation_unlink_upstream( op, upstream );
+ }
+
+ return result;
+}
+
+int
+operation_unlink_client( LloadOperation *op, LloadConnection *client )
+{
+ LloadOperation *removed;
+ int result = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink_client: "
+ "unlinking operation op=%p msgid=%d client connid=%lu\n",
+ op, op->o_client_msgid, op->o_client_connid );
+
+ CONNECTION_LOCK(client);
+ if ( (removed = ldap_tavl_delete(
+ &client->c_ops, op, operation_client_cmp )) ) {
+ result = LLOAD_OP_DETACHING_CLIENT;
+
+ assert( op == removed );
+ client->c_n_ops_executing--;
+
+ if ( op->o_restricted == LLOAD_OP_RESTRICTED_WRITE ) {
+ if ( !--client->c_restricted_inflight &&
+ client->c_restricted_at >= 0 ) {
+ if ( lload_write_coherence < 0 ) {
+ client->c_restricted_at = -1;
+ } else if ( timerisset( &op->o_last_response ) ) {
+ client->c_restricted_at = op->o_last_response.tv_sec;
+ } else {
+ /* We have to default to o_start just in case we abandoned an
+ * operation that the backend actually processed */
+ client->c_restricted_at = op->o_start.tv_sec;
+ }
+ }
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND &&
+ client->c_state == LLOAD_C_BINDING ) {
+ client->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &client->c_auth ) ) {
+ ber_memfree( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ }
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ if ( op->o_pin_id ) {
+ client->c_pin_id = 0;
+ }
+ }
+ }
+ if ( client->c_state == LLOAD_C_CLOSING && !client->c_ops ) {
+ CONNECTION_DESTROY(client);
+ } else {
+ CONNECTION_UNLOCK(client);
+ }
+
+ return result;
+}
+
+int
+operation_unlink_upstream( LloadOperation *op, LloadConnection *upstream )
+{
+ LloadOperation *removed;
+ LloadBackend *b = NULL;
+ int result = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink_upstream: "
+ "unlinking operation op=%p msgid=%d upstream connid=%lu\n",
+ op, op->o_upstream_msgid, op->o_upstream_connid );
+
+ CONNECTION_LOCK(upstream);
+ if ( (removed = ldap_tavl_delete(
+ &upstream->c_ops, op, operation_upstream_cmp )) ) {
+ result |= LLOAD_OP_DETACHING_UPSTREAM;
+
+ assert( op == removed );
+ upstream->c_n_ops_executing--;
+
+ if ( upstream->c_state == LLOAD_C_BINDING ) {
+ assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
+ upstream->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+ }
+ operation_update_conn_counters( op, upstream );
+ b = upstream->c_backend;
+ }
+ if ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) {
+ CONNECTION_DESTROY(upstream);
+ } else {
+ CONNECTION_UNLOCK(upstream);
+ }
+
+ if ( b ) {
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ operation_update_backend_counters( op, b );
+ checked_unlock( &b->b_mutex );
+ }
+
+ return result;
+}
+
+int
+operation_send_abandon( LloadOperation *op, LloadConnection *upstream )
+{
+ BerElement *ber;
+ int rc = -1;
+
+ if ( !IS_ALIVE( upstream, c_live ) ) {
+ return rc;
+ }
+
+ checked_lock( &upstream->c_io_mutex );
+ ber = upstream->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "operation_send_abandon: "
+ "ber_alloc failed\n" );
+ goto done;
+ }
+ upstream->c_pendingber = ber;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_send_abandon: "
+ "abandoning %s msgid=%d on connid=%lu\n",
+ lload_msgtype2str( op->o_tag ), op->o_upstream_msgid,
+ op->o_upstream_connid );
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ rc = ber_printf( ber, "t{tit{ist{s}}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, upstream->c_next_msgid++,
+ LDAP_REQ_BIND, LDAP_VERSION3, "", LDAP_AUTH_SASL, "" );
+ } else {
+ rc = ber_printf( ber, "t{titi}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, upstream->c_next_msgid++,
+ LDAP_REQ_ABANDON, op->o_upstream_msgid );
+ }
+
+ if ( rc < 0 ) {
+ ber_free( ber, 1 );
+ upstream->c_pendingber = NULL;
+ goto done;
+ }
+ rc = LDAP_SUCCESS;
+
+done:
+ checked_unlock( &upstream->c_io_mutex );
+ return rc;
+}
+
+/*
+ * Will remove the operation from its upstream and if it was still there,
+ * sends an abandon request.
+ *
+ * Being called from client_reset or request_abandon, the following hold:
+ * - noone else is processing the read part of the client connection (no new
+ * operations come in there - relevant for the c_state checks)
+ * - op->o_client_refcnt > op->o_client_live (and it follows that op->o_client != NULL)
+ */
+void
+operation_abandon( LloadOperation *op )
+{
+ LloadConnection *c;
+
+ checked_lock( &op->o_link_mutex );
+ c = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ if ( !c || !IS_ALIVE( c, c_live ) ) {
+ goto done;
+ }
+
+ /* for now consider all abandoned operations completed,
+ * perhaps add a separate counter later */
+ op->o_res = LLOAD_OP_COMPLETED;
+ if ( !operation_unlink_upstream( op, c ) ) {
+ /* The operation has already been abandoned or finished */
+ Debug( LDAP_DEBUG_TRACE, "operation_abandon: "
+ "%s from connid=%lu msgid=%d not present in connid=%lu any "
+ "more\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid,
+ op->o_client_msgid, op->o_upstream_connid );
+ goto done;
+ }
+
+ if ( operation_send_abandon( op, c ) == LDAP_SUCCESS ) {
+ connection_write_cb( -1, 0, c );
+ }
+
+done:
+ OPERATION_UNLINK(op);
+}
+
+void
+operation_send_reject(
+ LloadOperation *op,
+ int result,
+ const char *msg,
+ int send_anyway )
+{
+ LloadConnection *c;
+ BerElement *ber;
+ int found;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "rejecting %s from client connid=%lu with message: \"%s\"\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid, msg );
+
+ checked_lock( &op->o_link_mutex );
+ c = op->o_client;
+ checked_unlock( &op->o_link_mutex );
+ if ( !c || !IS_ALIVE( c, c_live ) ) {
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "not sending msgid=%d, client connid=%lu is dead\n",
+ op->o_client_msgid, op->o_client_connid );
+
+ goto done;
+ }
+
+ found = operation_unlink_client( op, c );
+ if ( !found && !send_anyway ) {
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "msgid=%d not scheduled for client connid=%lu anymore, "
+ "not sending\n",
+ op->o_client_msgid, c->c_connid );
+ goto done;
+ }
+
+ if ( op->o_client_msgid == 0 ) {
+ assert( op->o_saved_msgid == 0 && op->o_pin_id );
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "operation pin=%lu is just a pin, not sending\n",
+ op->o_pin_id );
+ goto done;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ ber = c->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ Debug( LDAP_DEBUG_ANY, "operation_send_reject: "
+ "ber_alloc failed, closing connid=%lu\n",
+ c->c_connid );
+ CONNECTION_LOCK_DESTROY(c);
+ goto done;
+ }
+ c->c_pendingber = ber;
+
+ ber_printf( ber, "t{tit{ess}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_client_msgid,
+ slap_req2res( op->o_tag ), result, "", msg );
+
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+done:
+ OPERATION_UNLINK(op);
+}
+
+/*
+ * Upstream is shutting down, signal the client if necessary, but we have to
+ * call operation_destroy_from_upstream ourselves to detach upstream from the
+ * op.
+ *
+ * Only called from upstream_destroy.
+ */
+void
+operation_lost_upstream( LloadOperation *op )
+{
+ operation_send_reject( op, LDAP_OTHER,
+ "connection to the remote server has been severed", 0 );
+}
+
+int
+connection_timeout( LloadConnection *upstream, void *arg )
+{
+ LloadOperation *op;
+ TAvlnode *ops = NULL, *node, *next;
+ LloadBackend *b = upstream->c_backend;
+ struct timeval *threshold = arg;
+ int rc, nops = 0;
+
+ CONNECTION_LOCK(upstream);
+ for ( node = ldap_tavl_end( upstream->c_ops, TAVL_DIR_LEFT );
+ node && timercmp( &((LloadOperation *)node->avl_data)->o_start,
+ threshold, < ); /* shortcut */
+ node = next ) {
+ LloadOperation *found_op;
+
+ next = ldap_tavl_next( node, TAVL_DIR_RIGHT );
+ op = node->avl_data;
+
+ /* Have we received another response since? */
+ if ( timerisset( &op->o_last_response ) &&
+ !timercmp( &op->o_last_response, threshold, < ) ) {
+ continue;
+ }
+
+ op->o_res = LLOAD_OP_FAILED;
+ found_op = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
+ assert( op == found_op );
+
+ if ( upstream->c_state == LLOAD_C_BINDING ) {
+ assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
+ upstream->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+ }
+
+ rc = ldap_tavl_insert( &ops, op, operation_upstream_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ Debug( LDAP_DEBUG_STATS2, "connection_timeout: "
+ "timing out %s from connid=%lu msgid=%d sent to connid=%lu as "
+ "msgid=%d\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid,
+ op->o_client_msgid, op->o_upstream_connid,
+ op->o_upstream_msgid );
+ nops++;
+ }
+
+ if ( nops == 0 ) {
+ CONNECTION_UNLOCK(upstream);
+ return LDAP_SUCCESS;
+ }
+ upstream->c_n_ops_executing -= nops;
+ upstream->c_counters.lc_ops_failed += nops;
+ Debug( LDAP_DEBUG_STATS, "connection_timeout: "
+ "timing out %d operations for connid=%lu\n",
+ nops, upstream->c_connid );
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing -= nops;
+ checked_unlock( &b->b_mutex );
+
+ for ( node = ldap_tavl_end( ops, TAVL_DIR_LEFT ); node;
+ node = ldap_tavl_next( node, TAVL_DIR_RIGHT ) ) {
+ op = node->avl_data;
+
+ operation_send_reject( op,
+ op->o_tag == LDAP_REQ_SEARCH ? LDAP_TIMELIMIT_EXCEEDED :
+ LDAP_ADMINLIMIT_EXCEEDED,
+ "upstream did not respond in time", 0 );
+
+ if ( upstream->c_type != LLOAD_C_BIND && rc == LDAP_SUCCESS ) {
+ rc = operation_send_abandon( op, upstream );
+ }
+ OPERATION_UNLINK(op);
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ connection_write_cb( -1, 0, upstream );
+ }
+
+ CONNECTION_LOCK(upstream);
+ /* ITS#9799: If a Bind timed out, connection is in an unknown state */
+ if ( upstream->c_type == LLOAD_C_BIND || rc != LDAP_SUCCESS ||
+ ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) ) {
+ CONNECTION_DESTROY(upstream);
+ } else {
+ CONNECTION_UNLOCK(upstream);
+ }
+
+ /* just dispose of the AVL, most operations should already be gone */
+ ldap_tavl_free( ops, NULL );
+ return LDAP_SUCCESS;
+}
+
+void
+operations_timeout( evutil_socket_t s, short what, void *arg )
+{
+ struct event *self = arg;
+ LloadTier *tier;
+ time_t threshold;
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "running timeout task\n" );
+ if ( !lload_timeout_api ) goto done;
+
+ threshold = slap_get_time() - lload_timeout_api->tv_sec;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ epoch_t epoch;
+
+ checked_lock( &b->b_mutex );
+ if ( b->b_n_ops_executing == 0 ) {
+ checked_unlock( &b->b_mutex );
+ continue;
+ }
+
+ epoch = epoch_join();
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timing out binds for backend uri=%s\n",
+ b->b_uri.bv_val );
+ connections_walk_last( &b->b_mutex, &b->b_bindconns,
+ b->b_last_bindconn, connection_timeout, &threshold );
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timing out other operations for backend uri=%s\n",
+ b->b_uri.bv_val );
+ connections_walk_last( &b->b_mutex, &b->b_conns, b->b_last_conn,
+ connection_timeout, &threshold );
+
+ epoch_leave( epoch );
+ checked_unlock( &b->b_mutex );
+ }
+ }
+done:
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timeout task finished\n" );
+ evtimer_add( self, lload_timeout_api );
+}
+
+void
+operation_update_global_rejected( LloadOperation *op )
+{
+ if ( op->o_res == LLOAD_OP_REJECTED ) {
+ assert( op->o_upstream_connid == 0 );
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND:
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_rejected++;
+ break;
+ default:
+ lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_rejected++;
+ break;
+ }
+ }
+}
+
+void
+operation_update_conn_counters( LloadOperation *op, LloadConnection *upstream )
+{
+ if ( op->o_res == LLOAD_OP_COMPLETED ) {
+ upstream->c_counters.lc_ops_completed++;
+ } else {
+ upstream->c_counters.lc_ops_failed++;
+ }
+}
+
+void
+operation_update_backend_counters( LloadOperation *op, LloadBackend *b )
+{
+ int stat_type = op->o_tag == LDAP_REQ_BIND ? LLOAD_STATS_OPS_BIND :
+ LLOAD_STATS_OPS_OTHER;
+
+ assert( b != NULL );
+ if ( op->o_res == LLOAD_OP_COMPLETED ) {
+ b->b_counters[stat_type].lc_ops_completed++;
+ } else {
+ b->b_counters[stat_type].lc_ops_failed++;
+ }
+}
diff --git a/servers/lloadd/proto-lload.h b/servers/lloadd/proto-lload.h
new file mode 100644
index 0000000..cfbbd95
--- /dev/null
+++ b/servers/lloadd/proto-lload.h
@@ -0,0 +1,254 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#ifndef PROTO_LLOAD_H
+#define PROTO_LLOAD_H
+
+#include <ldap_cdefs.h>
+#include "ldap_pvt.h"
+
+#include <event2/event.h>
+
+LDAP_BEGIN_DECL
+
+/*
+ * backend.c
+ */
+
+LDAP_SLAPD_F (void) backend_connect( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void *) backend_connect_task( void *ctx, void *arg );
+LDAP_SLAPD_F (void) backend_retry( LloadBackend *b );
+LDAP_SLAPD_F (int) upstream_select( LloadOperation *op, LloadConnection **c, int *res, char **message );
+LDAP_SLAPD_F (int) backend_select( LloadBackend *b, LloadOperation *op, LloadConnection **c, int *res, char **message );
+LDAP_SLAPD_F (int) try_upstream( LloadBackend *b, lload_c_head *head, LloadOperation *op, LloadConnection *c, int *res, char **message );
+LDAP_SLAPD_F (void) backend_reset( LloadBackend *b, int gentle );
+LDAP_SLAPD_F (LloadBackend *) lload_backend_new( void );
+LDAP_SLAPD_F (void) lload_backend_destroy( LloadBackend *b );
+
+/*
+ * bind.c
+ */
+LDAP_SLAPD_F (int) request_bind( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) handle_bind_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) handle_whoami_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) handle_vc_bind_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+
+/*
+ * client.c
+ */
+LDAP_SLAPD_F (int) request_abandon( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) request_process( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) handle_one_request( LloadConnection *c );
+LDAP_SLAPD_F (void) client_tls_handshake_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (LloadConnection *) client_init( ber_socket_t s, const char *peername, struct event_base *base, int use_tls );
+LDAP_SLAPD_F (void) client_reset( LloadConnection *c );
+LDAP_SLAPD_F (void) client_destroy( LloadConnection *c );
+LDAP_SLAPD_F (void) clients_destroy( int gentle );
+LDAP_SLAPD_V (long) lload_client_max_pending;
+
+/*
+ * config.c
+ */
+LDAP_SLAPD_F (int) lload_read_config( const char *fname, const char *dir );
+LDAP_SLAPD_F (void) lload_config_destroy( void );
+LDAP_SLAPD_F (int) verb_to_mask( const char *word, slap_verbmasks *v );
+LDAP_SLAPD_F (int) lload_tls_get_config( LDAP *ld, int opt, char **val );
+LDAP_SLAPD_F (void) lload_bindconf_tls_defaults( slap_bindconf *bc );
+LDAP_SLAPD_F (int) lload_backend_parse( const char *word, LloadBackend *b );
+LDAP_SLAPD_F (int) lload_bindconf_parse( const char *word, slap_bindconf *bc );
+LDAP_SLAPD_F (int) lload_bindconf_unparse( slap_bindconf *bc, struct berval *bv );
+LDAP_SLAPD_F (int) lload_bindconf_tls_set( slap_bindconf *bc, LDAP *ld );
+LDAP_SLAPD_F (void) lload_bindconf_free( slap_bindconf *bc );
+LDAP_SLAPD_F (void) lload_restriction_free( struct restriction_entry *entry );
+#ifdef BALANCER_MODULE
+LDAP_SLAPD_F (int) lload_back_init_cf( BackendInfo *bi );
+#endif
+
+/*
+ * connection.c
+ */
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) clients_mutex;
+LDAP_SLAPD_F (void *) handle_pdus( void *ctx, void *arg );
+LDAP_SLAPD_F (void) connection_write_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) connection_read_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (int) lload_connection_close( LloadConnection *c, void *arg );
+LDAP_SLAPD_F (LloadConnection *) lload_connection_init( ber_socket_t s, const char *peername, int use_tls );
+LDAP_SLAPD_F (void) connection_destroy( LloadConnection *c );
+LDAP_SLAPD_F (void) connections_walk_last( ldap_pvt_thread_mutex_t *cq_mutex,
+ lload_c_head *cq,
+ LloadConnection *cq_last,
+ CONNCB cb,
+ void *arg );
+LDAP_SLAPD_F (void) connections_walk( ldap_pvt_thread_mutex_t *cq_mutex, lload_c_head *cq, CONNCB cb, void *arg );
+
+/*
+ * daemon.c
+ */
+LDAP_SLAPD_F (int) lload_open_new_listener( const char *urls, LDAPURLDesc *lud );
+LDAP_SLAPD_F (int) lloadd_listeners_init( const char *urls );
+LDAP_SLAPD_F (int) lloadd_daemon_destroy( void );
+LDAP_SLAPD_F (int) lloadd_daemon( struct event_base *daemon_base );
+LDAP_SLAPD_F (LloadListener **) lloadd_get_listeners( void );
+LDAP_SLAPD_F (void) listeners_reactivate( void );
+LDAP_SLAPD_F (struct event_base *) lload_get_base( ber_socket_t s );
+LDAP_SLAPD_V (int) lload_daemon_threads;
+LDAP_SLAPD_V (int) lload_daemon_mask;
+
+LDAP_SLAPD_F (void) lload_sig_shutdown( evutil_socket_t sig, short what, void *arg );
+
+LDAP_SLAPD_F (void) lload_pause_server( void );
+LDAP_SLAPD_F (void) lload_unpause_server( void );
+
+LDAP_SLAPD_V (struct event_base *) daemon_base;
+LDAP_SLAPD_V (struct evdns_base *) dnsbase;
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_shutdown;
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_gentle_shutdown;
+LDAP_SLAPD_V (int) lloadd_inited;
+LDAP_SLAPD_V (struct LloadChange) lload_change;
+
+LDAP_SLAPD_V (struct event *) lload_timeout_event;
+
+LDAP_SLAPD_V (LDAP *) lload_tls_backend_ld;
+LDAP_SLAPD_V (LDAP *) lload_tls_ld;
+LDAP_SLAPD_V (void *) lload_tls_ctx;
+#ifdef BALANCER_MODULE
+LDAP_SLAPD_V (int) lload_use_slap_tls_ctx;
+#endif /* BALANCER_MODULE */
+
+/*
+ * extended.c
+ */
+LDAP_SLAPD_V (Avlnode *) lload_exop_handlers;
+LDAP_SLAPD_F (int) exop_handler_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) request_extended( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) lload_exop_init( void );
+LDAP_SLAPD_F (void) lload_exop_destroy( void );
+
+/*
+ * init.c
+ */
+LDAP_SLAPD_F (int) lload_global_init( void );
+LDAP_SLAPD_F (int) lload_global_destroy( void );
+LDAP_SLAPD_F (int) lload_tls_init( void );
+LDAP_SLAPD_F (int) lload_init( int mode, const char *name );
+LDAP_SLAPD_F (int) lload_destroy( void );
+LDAP_SLAPD_F (void) lload_counters_init( void );
+
+/*
+ * libevent_support.c
+ */
+LDAP_SLAPD_F (int) lload_libevent_init( void );
+LDAP_SLAPD_F (void) lload_libevent_destroy( void );
+
+#ifdef BALANCER_MODULE
+/*
+ * monitor.c
+ */
+LDAP_SLAPD_V (monitor_subsys_t *) lload_monitor_client_subsys;
+LDAP_SLAPD_F (int) lload_monitor_open( void );
+LDAP_SLAPD_F (int) lload_monitor_conn_entry_create( LloadConnection *c, monitor_subsys_t *ms );
+LDAP_SLAPD_F (int) lload_monitor_conn_unlink( LloadConnection *c );
+LDAP_SLAPD_F (int) lload_monitor_backend_init( BackendInfo *bi, monitor_subsys_t *ms, LloadBackend *b );
+LDAP_SLAPD_F (int) lload_monitor_tier_init( BackendInfo *bi, LloadTier *tier );
+#endif /* BALANCER_MODULE */
+
+/*
+ * operation.c
+ */
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) lload_pin_mutex;
+LDAP_SLAPD_V (unsigned long) lload_next_pin;
+LDAP_SLAPD_V (TAvlnode *) lload_control_actions;
+LDAP_SLAPD_V (TAvlnode *) lload_exop_actions;
+LDAP_SLAPD_V (enum op_restriction) lload_default_exop_action;
+LDAP_SLAPD_F (int) lload_restriction_cmp( const void *left, const void *right );
+LDAP_SLAPD_F (const char *) lload_msgtype2str( ber_tag_t tag );
+LDAP_SLAPD_F (int) operation_upstream_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) operation_client_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (LloadOperation *) operation_init( LloadConnection *c, BerElement *ber );
+LDAP_SLAPD_F (int) operation_send_abandon( LloadOperation *op, LloadConnection *c );
+LDAP_SLAPD_F (void) operation_abandon( LloadOperation *op );
+LDAP_SLAPD_F (void) operation_send_reject( LloadOperation *op, int result, const char *msg, int send_anyway );
+LDAP_SLAPD_F (int) operation_send_reject_locked( LloadOperation *op, int result, const char *msg, int send_anyway );
+LDAP_SLAPD_F (void) operation_lost_upstream( LloadOperation *op );
+LDAP_SLAPD_F (void) operation_destroy( LloadOperation *op );
+LDAP_SLAPD_F (int) operation_unlink( LloadOperation *op );
+LDAP_SLAPD_F (int) operation_unlink_client( LloadOperation *op, LloadConnection *client );
+LDAP_SLAPD_F (int) operation_unlink_upstream( LloadOperation *op, LloadConnection *upstream );
+LDAP_SLAPD_F (void) operations_timeout( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) operation_update_conn_counters( LloadOperation *op, LloadConnection *upstream );
+LDAP_SLAPD_F (void) operation_update_backend_counters( LloadOperation *op, LloadBackend *b );
+LDAP_SLAPD_F (void) operation_update_global_rejected( LloadOperation *op );
+
+/*
+ * tier.c
+ */
+LDAP_SLAPD_F (int) tier_startup( LloadTier *tier );
+LDAP_SLAPD_F (int) tier_reset( LloadTier *tier, int shutdown );
+LDAP_SLAPD_F (int) tier_destroy( LloadTier *tier );
+LDAP_SLAPD_F (void) lload_tiers_shutdown( void );
+LDAP_SLAPD_F (void) lload_tiers_reset( int shutdown );
+LDAP_SLAPD_F (void) lload_tiers_update( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) lload_tiers_destroy( void );
+LDAP_SLAPD_F (struct lload_tier_type *) lload_tier_find( char *type );
+
+/*
+ * upstream.c
+ */
+LDAP_SLAPD_F (int) lload_upstream_entry_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) forward_final_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) forward_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (void *) upstream_bind( void *ctx, void *arg );
+LDAP_SLAPD_F (LloadConnection *) upstream_init( ber_socket_t s, LloadBackend *b );
+LDAP_SLAPD_F (void) upstream_destroy( LloadConnection *c );
+
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_client;
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_upstream;
+LDAP_SLAPD_V (int) lload_conn_max_pdus_per_cycle;
+
+LDAP_SLAPD_V (int) lload_write_coherence;
+
+LDAP_SLAPD_V (lload_features_t) lload_features;
+
+LDAP_SLAPD_V (slap_mask_t) global_allows;
+LDAP_SLAPD_V (slap_mask_t) global_disallows;
+
+LDAP_SLAPD_V (const char) Versionstr[];
+
+LDAP_SLAPD_V (int) global_gentlehup;
+LDAP_SLAPD_V (int) global_idletimeout;
+
+LDAP_SLAPD_V (struct timeval *) lload_timeout_api;
+LDAP_SLAPD_V (struct timeval *) lload_timeout_net;
+LDAP_SLAPD_V (struct timeval *) lload_write_timeout;
+
+LDAP_SLAPD_V (char *) global_host;
+LDAP_SLAPD_V (int) lber_debug;
+LDAP_SLAPD_V (int) ldap_syslog;
+
+LDAP_SLAPD_V (lload_global_stats_t) lload_stats;
+LDAP_SLAPD_V (char *) listeners_list;
+LDAP_END_DECL
+
+#endif /* PROTO_LLOAD_H */
diff --git a/servers/lloadd/tier.c b/servers/lloadd/tier.c
new file mode 100644
index 0000000..84ead03
--- /dev/null
+++ b/servers/lloadd/tier.c
@@ -0,0 +1,168 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+
+lload_t_head tiers;
+
+int
+tier_startup( LloadTier *tier )
+{
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ checked_lock( &b->b_mutex );
+ if ( !b->b_retry_event ) {
+ b->b_retry_event = evtimer_new( daemon_base, backend_connect, b );
+ if ( !b->b_retry_event ) {
+ Debug( LDAP_DEBUG_ANY, "tier_startup: "
+ "%s failed to allocate retry event\n",
+ tier->t_type.tier_name );
+ return -1;
+ }
+ }
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ }
+ return LDAP_SUCCESS;
+}
+
+int
+tier_reset( LloadTier *tier, int shutdown )
+{
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ epoch_t epoch = epoch_join();
+
+ checked_lock( &b->b_mutex );
+ if ( shutdown ) {
+ b->b_numconns = b->b_numbindconns = 0;
+ }
+ backend_reset( b, 1 );
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+
+ epoch_leave( epoch );
+ }
+ return LDAP_SUCCESS;
+}
+
+int
+tier_destroy( LloadTier *tier )
+{
+ while ( !LDAP_CIRCLEQ_EMPTY( &tier->t_backends ) ) {
+ LloadBackend *b = LDAP_CIRCLEQ_FIRST( &tier->t_backends );
+ epoch_t epoch = epoch_join();
+
+ lload_backend_destroy( b );
+
+ epoch_leave( epoch );
+ }
+
+#ifdef BALANCER_MODULE
+ if ( tier->t_monitor ) {
+ /* FIXME: implement proper subsys shutdown in back-monitor or make
+ * backend just an entry, not a subsys */
+ if ( slapd_shutdown ) {
+ /* Just drop backlink, back-monitor will call mss_destroy later */
+ assert( tier->t_monitor->mss_private == tier );
+ tier->t_monitor->mss_private = NULL;
+ } else {
+ BackendDB *be;
+ struct berval monitordn = BER_BVC("cn=monitor");
+ int rc;
+
+ be = select_backend( &monitordn, 0 );
+
+ rc = tier->t_monitor->mss_destroy( be, tier->t_monitor );
+ assert( rc == LDAP_SUCCESS );
+ }
+ }
+#endif /* BALANCER_MODULE */
+
+ ch_free( tier->t_name.bv_val );
+ ch_free( tier );
+ return LDAP_SUCCESS;
+}
+
+void
+lload_tiers_destroy( void )
+{
+ while ( !LDAP_STAILQ_EMPTY( &tiers ) ) {
+ LloadTier *tier = LDAP_STAILQ_FIRST( &tiers );
+
+ LDAP_STAILQ_REMOVE_HEAD( &tiers, t_next );
+ tier->t_type.tier_destroy( tier );
+ }
+}
+
+void
+lload_tiers_shutdown( void )
+{
+ lload_tiers_reset( 1 );
+}
+
+void
+lload_tiers_reset( int shutdown )
+{
+ LloadTier *tier;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ tier->t_type.tier_reset( tier, shutdown );
+ }
+}
+
+void
+lload_tiers_update( evutil_socket_t s, short what, void *arg )
+{
+ LloadTier *tier;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ if ( tier->t_type.tier_update ) {
+ tier->t_type.tier_update( tier );
+ }
+ }
+}
+
+extern struct lload_tier_type roundrobin_tier;
+extern struct lload_tier_type weighted_tier;
+extern struct lload_tier_type bestof_tier;
+
+struct {
+ char *name;
+ struct lload_tier_type *type;
+} tier_types[] = {
+ { "roundrobin", &roundrobin_tier },
+ { "weighted", &weighted_tier },
+ { "bestof", &bestof_tier },
+
+ { NULL }
+};
+
+struct lload_tier_type *
+lload_tier_find( char *name )
+{
+ int i;
+
+ for ( i = 0; tier_types[i].name; i++ ) {
+ if ( !strcasecmp( name, tier_types[i].name ) ) {
+ return tier_types[i].type;
+ }
+ }
+ return NULL;
+}
diff --git a/servers/lloadd/tier_bestof.c b/servers/lloadd/tier_bestof.c
new file mode 100644
index 0000000..0c44d4e
--- /dev/null
+++ b/servers/lloadd/tier_bestof.c
@@ -0,0 +1,328 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <math.h>
+
+#include "lload.h"
+#include "lutil.h"
+
+static LloadTierInit bestof_init;
+static LloadTierBackendConfigCb bestof_backend_options;
+static LloadTierBackendCb bestof_add_backend;
+static LloadTierBackendCb bestof_remove_backend;
+static LloadTierSelect bestof_select;
+
+struct lload_tier_type bestof_tier;
+
+/*
+ * xorshift - we don't need high quality randomness, and we don't want to
+ * interfere with anyone else's use of srand() but we still want something with
+ * little bias.
+ *
+ * The PRNG here cycles thru 2^64−1 numbers.
+ */
+static uint64_t bestof_seed;
+
+static void
+bestof_srand( int seed )
+{
+ bestof_seed = seed;
+}
+
+static uint64_t
+bestof_rand()
+{
+ uint64_t val = bestof_seed;
+ val ^= val << 13;
+ val ^= val >> 7;
+ val ^= val << 17;
+ bestof_seed = val;
+ return val;
+}
+
+static int
+bestof_cmp( const void *left, const void *right )
+{
+ const LloadBackend *l = left;
+ const LloadBackend *r = right;
+ struct timeval now;
+ uintptr_t count, diff;
+ float a = l->b_fitness, b = r->b_fitness, factor = 1;
+
+ gettimeofday( &now, NULL );
+ /* We assume this is less than a second after the last update */
+ factor = 1 / ( pow( ( 1 / factor ) + 1, now.tv_usec / 1000000.0 ) - 1 );
+
+ count = __atomic_load_n( &l->b_operation_count, __ATOMIC_RELAXED );
+ diff = __atomic_load_n( &l->b_operation_time, __ATOMIC_RELAXED );
+ if ( count ) {
+ a = ( a * factor + (float)diff * l->b_weight / count ) / ( factor + 1 );
+ }
+
+ count = __atomic_load_n( &r->b_operation_count, __ATOMIC_RELAXED );
+ diff = __atomic_load_n( &r->b_operation_time, __ATOMIC_RELAXED );
+ if ( count ) {
+ b = ( b * factor + (float)diff * r->b_weight / count ) / ( factor + 1 );
+ }
+
+ return (a - b < 0) ? -1 : (a - b == 0) ? 0 : 1;
+}
+
+LloadTier *
+bestof_init( void )
+{
+ LloadTier *tier;
+ int seed;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = bestof_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ /* Make sure we don't pass 0 as a seed */
+ do {
+ seed = rand();
+ } while ( !seed );
+ bestof_srand( seed );
+
+ return tier;
+}
+
+int
+bestof_add_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert( b->b_tier == tier );
+
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next );
+ if ( !tier->t_private ) {
+ tier->t_private = b;
+ }
+ tier->t_nbackends++;
+ return LDAP_SUCCESS;
+}
+
+static int
+bestof_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+ assert( tier->t_private );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
+
+ if ( b == next ) {
+ tier->t_private = NULL;
+ } else {
+ tier->t_private = next;
+ }
+ tier->t_nbackends--;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+bestof_backend_options( LloadTier *tier, LloadBackend *b, char *arg )
+{
+ struct berval weight = BER_BVC("weight=");
+ unsigned long l;
+
+ if ( !strncasecmp( arg, weight.bv_val, weight.bv_len ) ) {
+ if ( lutil_atoulx( &l, &arg[weight.bv_len], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "bestof_backend_options: "
+ "cannot parse %s as weight\n",
+ arg );
+ return 1;
+ }
+ b->b_weight = l;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+bestof_update( LloadTier *tier )
+{
+ LloadBackend *b, *first, *next;
+ time_t now = slap_get_time();
+
+ checked_lock( &tier->t_mutex );
+ first = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return LDAP_SUCCESS;
+
+ do {
+ int steps;
+ checked_lock( &b->b_mutex );
+
+ steps = now - b->b_last_update;
+ if ( b->b_weight && steps > 0 ) {
+ uintptr_t count, diff;
+ float factor = 1;
+
+ count = __atomic_exchange_n(
+ &b->b_operation_count, 0, __ATOMIC_RELAXED );
+ diff = __atomic_exchange_n(
+ &b->b_operation_time, 0, __ATOMIC_RELAXED );
+
+ /* Smear values over time - rolling average */
+ if ( count ) {
+ float fitness = b->b_weight * diff;
+
+ /* Stretch factor accordingly favouring the latest value */
+ if ( steps > 10 ) {
+ factor = 0; /* No recent data */
+ } else if ( steps > 1 ) {
+ factor = 1 / ( pow( ( 1 / factor ) + 1, steps ) - 1 );
+ }
+
+ b->b_fitness = ( factor * b->b_fitness + fitness / count ) /
+ ( factor + 1 );
+ b->b_last_update = now;
+ }
+ }
+
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+ checked_unlock( &b->b_mutex );
+ b = next;
+ } while ( b != first );
+
+ return LDAP_SUCCESS;
+}
+
+int
+bestof_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *first, *next, *b, *b0, *b1;
+ int result = 0, rc = 0, n = tier->t_nbackends;
+ int i0, i1, i = 0;
+
+ checked_lock( &tier->t_mutex );
+ first = b0 = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return rc;
+
+ if ( tier->t_nbackends == 1 ) {
+ goto fallback;
+ }
+
+ /* Pick two backend indices at random */
+ i0 = bestof_rand() % n;
+ i1 = bestof_rand() % ( n - 1 );
+ if ( i1 >= i0 ) {
+ i1 += 1;
+ } else {
+ int tmp = i0;
+ i0 = i1;
+ i1 = tmp;
+ }
+ assert( i0 < i1 );
+
+ /*
+ * FIXME: use a static array in t_private so we don't have to do any of
+ * this
+ */
+ for ( i = 0; i < i1; i++ ) {
+ if ( i == i0 ) {
+ b0 = b;
+ }
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+ checked_unlock( &b->b_mutex );
+ b = next;
+ }
+ b1 = b;
+ assert( b0 != b1 );
+
+ if ( bestof_cmp( b0, b1 ) < 0 ) {
+ checked_lock( &b0->b_mutex );
+ result = backend_select( b0, op, cp, res, message );
+ checked_unlock( &b0->b_mutex );
+ } else {
+ checked_lock( &b1->b_mutex );
+ result = backend_select( b1, op, cp, res, message );
+ checked_unlock( &b1->b_mutex );
+ }
+
+ rc |= result;
+ if ( result && *cp ) {
+ checked_lock( &tier->t_mutex );
+ tier->t_private = LDAP_CIRCLEQ_LOOP_NEXT(
+ &tier->t_backends, (*cp)->c_backend, b_next );
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ /* Preferred backends deemed unusable, do a round robin from scratch */
+ b = first;
+fallback:
+ do {
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ rc = backend_select( b, op, cp, res, message );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc && *cp ) {
+ /*
+ * Round-robin step:
+ * Rotate the queue to put this backend at the end. The race here
+ * is acceptable.
+ */
+ checked_lock( &tier->t_mutex );
+ tier->t_private = next;
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ b = next;
+ } while ( b != first );
+
+ return rc;
+}
+
+struct lload_tier_type bestof_tier = {
+ .tier_name = "bestof",
+
+ .tier_init = bestof_init,
+ .tier_startup = tier_startup,
+ .tier_update = bestof_update,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = bestof_add_backend,
+ .tier_remove_backend = bestof_remove_backend,
+
+ .tier_select = bestof_select,
+};
diff --git a/servers/lloadd/tier_roundrobin.c b/servers/lloadd/tier_roundrobin.c
new file mode 100644
index 0000000..d299fb9
--- /dev/null
+++ b/servers/lloadd/tier_roundrobin.c
@@ -0,0 +1,136 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+
+static LloadTierInit roundrobin_init;
+static LloadTierBackendCb roundrobin_add_backend;
+static LloadTierBackendCb roundrobin_remove_backend;
+static LloadTierSelect roundrobin_select;
+
+struct lload_tier_type roundrobin_tier;
+
+static LloadTier *
+roundrobin_init( void )
+{
+ LloadTier *tier;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = roundrobin_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ return tier;
+}
+
+static int
+roundrobin_add_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert( b->b_tier == tier );
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next );
+ if ( !tier->t_private ) {
+ tier->t_private = b;
+ }
+ tier->t_nbackends++;
+ return LDAP_SUCCESS;
+}
+
+static int
+roundrobin_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ if ( b == tier->t_private ) {
+ if ( tier->t_nbackends ) {
+ tier->t_private = next;
+ } else {
+ assert( b == next );
+ tier->t_private = NULL;
+ }
+ }
+ tier->t_nbackends--;
+ return LDAP_SUCCESS;
+}
+
+static int
+roundrobin_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *b, *first, *next;
+ int rc = 0;
+
+ checked_lock( &tier->t_mutex );
+ first = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return rc;
+
+ do {
+ int result;
+
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ result = backend_select( b, op, cp, res, message );
+ checked_unlock( &b->b_mutex );
+
+ rc |= result;
+ if ( result && *cp ) {
+ /*
+ * Round-robin step:
+ * Rotate the queue to put this backend at the end. The race here
+ * is acceptable.
+ */
+ checked_lock( &tier->t_mutex );
+ tier->t_private = next;
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ b = next;
+ } while ( b != first );
+
+ return rc;
+}
+
+struct lload_tier_type roundrobin_tier = {
+ .tier_name = "roundrobin",
+
+ .tier_init = roundrobin_init,
+ .tier_startup = tier_startup,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = roundrobin_add_backend,
+ .tier_remove_backend = roundrobin_remove_backend,
+
+ .tier_select = roundrobin_select,
+};
diff --git a/servers/lloadd/tier_weighted.c b/servers/lloadd/tier_weighted.c
new file mode 100644
index 0000000..1255104
--- /dev/null
+++ b/servers/lloadd/tier_weighted.c
@@ -0,0 +1,224 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+
+#include "lload.h"
+#include "lutil.h"
+
+static LloadTierInit weighted_init;
+static LloadTierBackendCb weighted_add_backend;
+static LloadTierBackendCb weighted_remove_backend;
+static LloadTierSelect weighted_select;
+
+struct lload_tier_type weighted_tier;
+
+/*
+ * Linear Congruential Generator - we don't need
+ * high quality randomness, and we don't want to
+ * interfere with anyone else's use of srand().
+ *
+ * The PRNG here cycles thru 941,955 numbers.
+ */
+static float weighted_seed;
+
+static void
+weighted_srand( int seed )
+{
+ weighted_seed = (float)seed / (float)RAND_MAX;
+}
+
+static float
+weighted_rand()
+{
+ float val = 9821.0 * weighted_seed + .211327;
+ weighted_seed = val - (int)val;
+ return weighted_seed;
+}
+
+static void
+weighted_shuffle( LloadBackend **b, int n )
+{
+ int i, j, p;
+ uintptr_t total = 0, r;
+
+ for ( i = 0; i < n; i++ )
+ total += b[i]->b_weight;
+
+ /* all weights are zero, do a straight Fisher-Yates shuffle */
+ if ( !total ) {
+ while ( n ) {
+ LloadBackend *t;
+ i = weighted_rand() * n--;
+ t = b[n];
+ b[n] = b[i];
+ b[i] = t;
+ }
+ return;
+ }
+
+ /* Do a shuffle per RFC2782 Page 4 */
+ p = n;
+ for ( i = 0; i < n - 1; i++ ) {
+ r = weighted_rand() * total;
+ for ( j = 0; j < p; j++ ) {
+ r -= b[j]->b_weight;
+ if ( r <= 0 ) {
+ if ( j ) {
+ LloadBackend *t = b[0];
+ b[0] = b[j];
+ b[j] = t;
+ }
+ total -= b[0]->b_weight;
+ b++;
+ p--;
+ break;
+ }
+ }
+ /* TODO: once we have total == 0, should we jump over to the previous
+ * case? */
+ }
+}
+
+LloadTier *
+weighted_init( void )
+{
+ LloadTier *tier;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = weighted_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ weighted_srand( rand() );
+
+ return tier;
+}
+
+int
+weighted_add_backend( LloadTier *tier, LloadBackend *to_add )
+{
+ LloadBackend *b;
+ uintptr_t added = 1;
+
+ assert( to_add->b_tier == tier );
+
+ /* This requires us to use LDAP_CIRCLEQ_ENTRY_INIT() every time we have
+ * removed the backend from the list */
+ if ( LDAP_CIRCLEQ_NEXT( to_add, b_next ) ) {
+ added = 0;
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, to_add, b_next );
+ }
+
+ /*
+ * Keep it sorted. The only thing RFC 2782 specifies is that weight 0
+ * entries are at the front of the list so they have a chance to be
+ * selected.
+ *
+ * Even with that in mind, there is a problem outlined in the RFC 2782
+ * errata[0] where the ordering affects the likelihood of an entry being
+ * selected with weight 0 entries in the mix - they are an afterthought
+ * into the design after all.
+ *
+ * [0]. https://www.rfc-editor.org/errata/eid2984
+ */
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ if ( to_add->b_weight < b->b_weight ) {
+ LDAP_CIRCLEQ_INSERT_BEFORE( &tier->t_backends, b, to_add, b_next );
+ goto done;
+ }
+ }
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, to_add, b_next );
+
+done:
+ tier->t_nbackends += added;
+ return LDAP_SUCCESS;
+}
+
+static int
+weighted_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+ assert( tier->t_nbackends );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
+ tier->t_nbackends--;
+
+ return LDAP_SUCCESS;
+}
+
+int
+weighted_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *b, **sorted;
+ int rc = 0, i = 0;
+
+ if ( !tier->t_nbackends ) return rc;
+
+ sorted = ch_malloc( tier->t_nbackends * sizeof(LloadBackend *) );
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ sorted[i++] = b;
+ }
+
+ assert( i == tier->t_nbackends );
+
+ weighted_shuffle( sorted, tier->t_nbackends );
+
+ for ( i = 0; i < tier->t_nbackends; i++ ) {
+ int result;
+
+ checked_lock( &sorted[i]->b_mutex );
+ result = backend_select( sorted[i], op, cp, res, message );
+ checked_unlock( &sorted[i]->b_mutex );
+
+ rc |= result;
+ if ( result && *cp ) {
+ break;
+ }
+ }
+
+ ch_free( sorted );
+ return rc;
+}
+
+struct lload_tier_type weighted_tier = {
+ .tier_name = "weighted",
+
+ .tier_init = weighted_init,
+ .tier_startup = tier_startup,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = weighted_add_backend,
+ .tier_remove_backend = weighted_remove_backend,
+
+ .tier_select = weighted_select,
+};
diff --git a/servers/lloadd/upstream.c b/servers/lloadd/upstream.c
new file mode 100644
index 0000000..2784532
--- /dev/null
+++ b/servers/lloadd/upstream.c
@@ -0,0 +1,1184 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lload.h"
+
+#include "lutil.h"
+#include "lutil_ldap.h"
+
+#ifdef HAVE_CYRUS_SASL
+static const sasl_callback_t client_callbacks[] = {
+#ifdef SASL_CB_GETREALM
+ { SASL_CB_GETREALM, NULL, NULL },
+#endif
+ { SASL_CB_USER, NULL, NULL },
+ { SASL_CB_AUTHNAME, NULL, NULL },
+ { SASL_CB_PASS, NULL, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+#endif /* HAVE_CYRUS_SASL */
+
+static void upstream_unlink( LloadConnection *upstream );
+
+int
+lload_upstream_entry_cmp( const void *l, const void *r )
+{
+ return SLAP_PTRCMP( l, r );
+}
+
+static void
+linked_upstream_lost( LloadConnection *client )
+{
+ int gentle = 1;
+
+ CONNECTION_LOCK(client);
+ assert( client->c_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM );
+ assert( client->c_linked_upstream );
+
+ client->c_restricted = LLOAD_OP_NOT_RESTRICTED;
+ client->c_linked_upstream = NULL;
+ CONNECTION_UNLOCK(client);
+ lload_connection_close( client, &gentle );
+}
+
+int
+forward_response( LloadConnection *client, LloadOperation *op, BerElement *ber )
+{
+ BerElement *output;
+ BerValue response, controls = BER_BVNULL;
+ ber_int_t msgid;
+ ber_tag_t tag, response_tag;
+ ber_len_t len;
+
+ CONNECTION_LOCK(client);
+ if ( op->o_client_msgid ) {
+ msgid = op->o_client_msgid;
+ } else {
+ assert( op->o_pin_id );
+ msgid = op->o_saved_msgid;
+ op->o_saved_msgid = 0;
+ }
+ CONNECTION_UNLOCK(client);
+
+ response_tag = ber_skip_element( ber, &response );
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_CONTROLS ) {
+ ber_skip_element( ber, &controls );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "forward_response: "
+ "%s to client connid=%lu request msgid=%d\n",
+ lload_msgtype2str( response_tag ), op->o_client_connid, msgid );
+
+ checked_lock( &client->c_io_mutex );
+ output = client->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ ber_free( ber, 1 );
+ checked_unlock( &client->c_io_mutex );
+ return -1;
+ }
+ client->c_pendingber = output;
+
+ ber_printf( output, "t{titOtO}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, msgid,
+ response_tag, &response,
+ LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) );
+
+ checked_unlock( &client->c_io_mutex );
+
+ ber_free( ber, 1 );
+ connection_write_cb( -1, 0, client );
+ return 0;
+}
+
+int
+forward_final_response(
+ LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_STATS, "forward_final_response: "
+ "connid=%lu msgid=%d finishing up with a request for "
+ "client connid=%lu\n",
+ op->o_upstream_connid, op->o_upstream_msgid, op->o_client_connid );
+
+ rc = forward_response( client, op, ber );
+
+ op->o_res = LLOAD_OP_COMPLETED;
+ if ( !op->o_pin_id ) {
+ OPERATION_UNLINK(op);
+ }
+
+ return rc;
+}
+
+static int
+handle_unsolicited( LloadConnection *c, BerElement *ber )
+{
+ CONNECTION_ASSERT_LOCKED(c);
+
+ assert( c->c_state != LLOAD_C_INVALID );
+ if ( c->c_state == LLOAD_C_DYING ) {
+ CONNECTION_UNLOCK(c);
+ goto out;
+ }
+ c->c_state = LLOAD_C_CLOSING;
+
+ Debug( LDAP_DEBUG_STATS, "handle_unsolicited: "
+ "teardown for upstream connection connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_DESTROY(c);
+
+out:
+ ber_free( ber, 1 );
+ return -1;
+}
+
+/*
+ * Pull c->c_currentber from the connection and try to look up the operation on
+ * the upstream.
+ *
+ * If it's a notice of disconnection, we won't find it and need to tear down
+ * the connection and tell the clients, if we can't find the operation, ignore
+ * the message (either client already disconnected/abandoned it or the upstream
+ * is pulling our leg).
+ *
+ * Some responses need special handling:
+ * - Bind response
+ * - VC response where the client requested a Bind (both need to update the
+ * client's bind status)
+ * - search entries/referrals and intermediate responses (will not trigger
+ * operation to be removed)
+ *
+ * If the worker pool is overloaded, we might be called directly from
+ * the read callback, at that point, the connection hasn't been muted.
+ *
+ * TODO: when the client already has data pending on write, we should mute the
+ * upstream.
+ * - should record the BerElement on the Op and the Op on the client
+ *
+ * The following hold on entering any of the handlers:
+ * - op->o_upstream_refcnt > 0
+ * - op->o_upstream->c_refcnt > 0
+ * - op->o_client->c_refcnt > 0
+ */
+static int
+handle_one_response( LloadConnection *c )
+{
+ BerElement *ber;
+ LloadOperation *op = NULL, needle = { .o_upstream_connid = c->c_connid };
+ LloadOperationHandler handler = NULL;
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc = LDAP_SUCCESS;
+
+ ber = c->c_currentber;
+ c->c_currentber = NULL;
+
+ tag = ber_get_int( ber, &needle.o_upstream_msgid );
+ if ( tag != LDAP_TAG_MSGID ) {
+ rc = -1;
+ ber_free( ber, 1 );
+ goto fail;
+ }
+
+ CONNECTION_LOCK(c);
+ if ( needle.o_upstream_msgid == 0 ) {
+ return handle_unsolicited( c, ber );
+ } else if ( !( op = ldap_tavl_find(
+ c->c_ops, &needle, operation_upstream_cmp ) ) ) {
+ /* Already abandoned, do nothing */
+ CONNECTION_UNLOCK(c);
+ ber_free( ber, 1 );
+ return rc;
+ /*
+ } else if ( op->o_response_pending ) {
+ c->c_pendingop = op;
+ event_del( c->c_read_event );
+ */
+ } else {
+ CONNECTION_UNLOCK(c);
+ /*
+ op->o_response_pending = ber;
+ */
+
+ tag = ber_peek_tag( ber, &len );
+ switch ( tag ) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ case LDAP_RES_INTERMEDIATE:
+ handler = forward_response;
+ break;
+ case LDAP_RES_BIND:
+ handler = handle_bind_response;
+ break;
+ case LDAP_RES_EXTENDED:
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ if ( lload_features & LLOAD_FEATURE_VC ) {
+ handler = handle_vc_bind_response;
+ } else
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ {
+ handler = handle_whoami_response;
+ }
+ }
+ break;
+ }
+ if ( !handler ) {
+ handler = forward_final_response;
+ }
+ }
+ if ( op ) {
+ struct timeval tv, tvdiff;
+ uintptr_t diff;
+
+ gettimeofday( &tv, NULL );
+ if ( !timerisset( &op->o_last_response ) ) {
+ LloadBackend *b = c->c_backend;
+
+ timersub( &tv, &op->o_start, &tvdiff );
+ diff = 1000000 * tvdiff.tv_sec + tvdiff.tv_usec;
+
+ __atomic_add_fetch( &b->b_operation_count, 1, __ATOMIC_RELAXED );
+ __atomic_add_fetch( &b->b_operation_time, diff, __ATOMIC_RELAXED );
+ }
+ op->o_last_response = tv;
+
+ Debug( LDAP_DEBUG_STATS2, "handle_one_response: "
+ "upstream connid=%lu, processing response for "
+ "client connid=%lu, msgid=%d\n",
+ c->c_connid, op->o_client_connid, op->o_client_msgid );
+ } else {
+ tag = ber_peek_tag( ber, &len );
+ Debug( LDAP_DEBUG_STATS2, "handle_one_response: "
+ "upstream connid=%lu, %s, msgid=%d not for a pending "
+ "operation\n",
+ c->c_connid, lload_msgtype2str( tag ),
+ needle.o_upstream_msgid );
+ }
+
+ if ( handler ) {
+ LloadConnection *client;
+
+ checked_lock( &op->o_link_mutex );
+ client = op->o_client;
+ checked_unlock( &op->o_link_mutex );
+ if ( client && IS_ALIVE( client, c_live ) ) {
+ rc = handler( client, op, ber );
+ } else {
+ ber_free( ber, 1 );
+ }
+ } else {
+ assert(0);
+ ber_free( ber, 1 );
+ }
+
+fail:
+ if ( rc ) {
+ Debug( LDAP_DEBUG_STATS, "handle_one_response: "
+ "error on processing a response (%s) on upstream connection "
+ "connid=%ld, tag=%lx\n",
+ lload_msgtype2str( tag ), c->c_connid, tag );
+ CONNECTION_LOCK_DESTROY(c);
+ }
+ return rc;
+}
+
+#ifdef HAVE_CYRUS_SASL
+static int
+sasl_bind_step( LloadConnection *c, BerValue *scred, BerValue *ccred )
+{
+ LloadBackend *b = c->c_backend;
+ sasl_conn_t *ctx = c->c_sasl_authctx;
+ sasl_interact_t *prompts = NULL;
+ unsigned credlen;
+ int rc = -1;
+
+ if ( !ctx ) {
+ const char *mech = NULL;
+#ifdef HAVE_TLS
+ void *ssl;
+#endif /* HAVE_TLS */
+
+ if ( sasl_client_new( "ldap", b->b_host, NULL, NULL, client_callbacks,
+ 0, &ctx ) != SASL_OK ) {
+ goto done;
+ }
+ c->c_sasl_authctx = ctx;
+
+ assert( c->c_sasl_defaults == NULL );
+ c->c_sasl_defaults =
+ lutil_sasl_defaults( NULL, bindconf.sb_saslmech.bv_val,
+ bindconf.sb_realm.bv_val, bindconf.sb_authcId.bv_val,
+ bindconf.sb_cred.bv_val, bindconf.sb_authzId.bv_val );
+
+#ifdef HAVE_TLS
+ /* Check for TLS */
+ ssl = ldap_pvt_tls_sb_ctx( c->c_sb );
+ if ( ssl ) {
+ struct berval authid = BER_BVNULL;
+ ber_len_t ssf;
+
+ ssf = ldap_pvt_tls_get_strength( ssl );
+ (void)ldap_pvt_tls_get_my_dn( ssl, &authid, NULL, 0 );
+
+ sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf );
+ sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid.bv_val );
+ ch_free( authid.bv_val );
+#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */
+ {
+ char cbinding[64];
+ struct berval cbv = { sizeof(cbinding), cbinding };
+ if ( ldap_pvt_tls_get_unique( ssl, &cbv, 0 ) ) {
+ sasl_channel_binding_t *cb =
+ ch_malloc( sizeof(*cb) + cbv.bv_len );
+ void *cb_data;
+ cb->name = "ldap";
+ cb->critical = 0;
+ cb->len = cbv.bv_len;
+ cb->data = cb_data = cb + 1;
+ memcpy( cb_data, cbv.bv_val, cbv.bv_len );
+ sasl_setprop( ctx, SASL_CHANNEL_BINDING, cb );
+ c->c_sasl_cbinding = cb;
+ }
+ }
+#endif
+ }
+#endif
+
+#if !defined(_WIN32)
+ /* Check for local */
+ if ( b->b_proto == LDAP_PROTO_IPC ) {
+ char authid[sizeof( "gidNumber=4294967295+uidNumber=4294967295,"
+ "cn=peercred,cn=external,cn=auth" )];
+ int ssf = LDAP_PVT_SASL_LOCAL_SSF;
+
+ sprintf( authid,
+ "gidNumber=%u+uidNumber=%u,"
+ "cn=peercred,cn=external,cn=auth",
+ getegid(), geteuid() );
+ sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf );
+ sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid );
+ }
+#endif
+
+ do {
+ rc = sasl_client_start( ctx, bindconf.sb_saslmech.bv_val,
+ &prompts,
+ (const char **)&ccred->bv_val, &credlen,
+ &mech );
+
+ if ( rc == SASL_INTERACT ) {
+ if ( lutil_sasl_interact( NULL, LDAP_SASL_QUIET,
+ c->c_sasl_defaults, prompts ) ) {
+ break;
+ }
+ }
+ } while ( rc == SASL_INTERACT );
+
+ ber_str2bv( mech, 0, 0, &c->c_sasl_bind_mech );
+ } else {
+ assert( c->c_sasl_defaults );
+
+ do {
+ rc = sasl_client_step( ctx,
+ (scred == NULL) ? NULL : scred->bv_val,
+ (scred == NULL) ? 0 : scred->bv_len,
+ &prompts,
+ (const char **)&ccred->bv_val, &credlen);
+
+ if ( rc == SASL_INTERACT ) {
+ if ( lutil_sasl_interact( NULL, LDAP_SASL_QUIET,
+ c->c_sasl_defaults, prompts ) ) {
+ break;
+ }
+ }
+ } while ( rc == SASL_INTERACT );
+ }
+
+ if ( rc == SASL_OK ) {
+ sasl_ssf_t *ssf;
+ rc = sasl_getprop( ctx, SASL_SSF, (const void **)(char *)&ssf );
+ if ( rc == SASL_OK && ssf && *ssf ) {
+ Debug( LDAP_DEBUG_CONNS, "sasl_bind_step: "
+ "connid=%lu mech=%s setting up a new SASL security layer\n",
+ c->c_connid, c->c_sasl_bind_mech.bv_val );
+ ldap_pvt_sasl_install( c->c_sb, ctx );
+ }
+ }
+ ccred->bv_len = credlen;
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "sasl_bind_step: "
+ "connid=%lu next step for SASL bind mech=%s rc=%d\n",
+ c->c_connid, c->c_sasl_bind_mech.bv_val, rc );
+ return rc;
+}
+#endif /* HAVE_CYRUS_SASL */
+
+int
+upstream_bind_cb( LloadConnection *c )
+{
+ BerElement *ber = c->c_currentber;
+ LloadBackend *b = c->c_backend;
+ BerValue matcheddn, message;
+ ber_tag_t tag;
+ ber_int_t msgid, result;
+
+ c->c_currentber = NULL;
+
+ if ( ber_scanf( ber, "it", &msgid, &tag ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "protocol violation from server\n" );
+ goto fail;
+ }
+
+ if ( msgid != ( c->c_next_msgid - 1 ) || tag != LDAP_RES_BIND ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "unexpected %s from server, msgid=%d\n",
+ lload_msgtype2str( tag ), msgid );
+ goto fail;
+ }
+
+ if ( ber_scanf( ber, "{emm" /* "}" */, &result, &matcheddn, &message ) ==
+ LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "response does not conform with a bind response\n" );
+ goto fail;
+ }
+
+ switch ( result ) {
+ case LDAP_SUCCESS:
+#ifdef HAVE_CYRUS_SASL
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) {
+ BerValue scred = BER_BVNULL, ccred;
+ ber_len_t len;
+ int rc;
+
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SASL_RES_CREDS &&
+ ber_scanf( ber, "m", &scred ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "sasl bind response malformed\n" );
+ goto fail;
+ }
+
+ rc = sasl_bind_step( c, &scred, &ccred );
+ if ( rc != SASL_OK &&
+ ( rc != SASL_CONTINUE || result == LDAP_SUCCESS ) ) {
+ goto fail;
+ }
+
+ if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
+ BerElement *outber;
+
+ checked_lock( &c->c_io_mutex );
+ outber = c->c_pendingber;
+ if ( outber == NULL && (outber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ goto fail;
+ }
+ c->c_pendingber = outber;
+
+ msgid = c->c_next_msgid++;
+ ber_printf( outber, "{it{iOt{OON}N}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SASL,
+ &c->c_sasl_bind_mech, BER_BV_OPTIONAL( &ccred ) );
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+ if ( rc == SASL_OK ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+ break;
+ }
+ }
+ if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
+ goto fail;
+ }
+#endif /* HAVE_CYRUS_SASL */
+ CONNECTION_LOCK(c);
+ c->c_pdu_cb = handle_one_response;
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_OPEN;
+ c->c_read_timeout = NULL;
+ Debug( LDAP_DEBUG_CONNS, "upstream_bind_cb: "
+ "connection connid=%lu for backend server '%s' is ready "
+ "for use\n",
+ c->c_connid, b->b_name.bv_val );
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ b->b_active++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_conn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER(
+ &b->b_conns, b->b_last_conn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_conns, c, c_next );
+ }
+ b->b_last_conn = c;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "upstream bind failed, rc=%d, message='%s'\n",
+ result, message.bv_val );
+ goto fail;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+ event_add( c->c_read_event, c->c_read_timeout );
+ ber_free( ber, 1 );
+ return -1;
+
+fail:
+ CONNECTION_LOCK_DESTROY(c);
+ ber_free( ber, 1 );
+ return -1;
+}
+
+void *
+upstream_bind( void *ctx, void *arg )
+{
+ LloadConnection *c = arg;
+ BerElement *ber;
+ ber_int_t msgid;
+
+ /* A reference was passed on to us */
+ assert( IS_ALIVE( c, c_refcnt ) );
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+ }
+
+ CONNECTION_LOCK(c);
+ assert( !event_pending( c->c_read_event, EV_READ, NULL ) );
+ c->c_pdu_cb = upstream_bind_cb;
+ CONNECTION_UNLOCK(c);
+
+ checked_lock( &c->c_io_mutex );
+ ber = c->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ goto fail;
+ }
+ c->c_pendingber = ber;
+ msgid = c->c_next_msgid++;
+
+ if ( bindconf.sb_method == LDAP_AUTH_SIMPLE ) {
+ /* simple bind */
+ ber_printf( ber, "{it{iOtON}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SIMPLE,
+ &bindconf.sb_cred );
+
+#ifdef HAVE_CYRUS_SASL
+ } else {
+ BerValue cred;
+ int rc;
+
+ rc = sasl_bind_step( c, NULL, &cred );
+ if ( rc != SASL_OK && rc != SASL_CONTINUE ) {
+ goto fail;
+ }
+
+ ber_printf( ber, "{it{iOt{OON}N}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SASL,
+ &c->c_sasl_bind_mech, BER_BV_OPTIONAL( &cred ) );
+
+ if ( rc == SASL_OK ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+#endif /* HAVE_CYRUS_SASL */
+ }
+ /* TODO: can we be paused at this point? Then we'd have to move this line
+ * after connection_write_cb */
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+ CONNECTION_LOCK(c);
+ c->c_read_timeout = lload_timeout_net;
+ event_add( c->c_read_event, c->c_read_timeout );
+ CONNECTION_UNLOCK(c);
+
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+
+fail:
+ checked_unlock( &c->c_io_mutex );
+ CONNECTION_LOCK_DESTROY(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+}
+
+/*
+ * The backend is already locked when entering the function.
+ */
+static int
+upstream_finish( LloadConnection *c )
+{
+ LloadBackend *b = c->c_backend;
+ int is_bindconn = 0;
+
+ assert_locked( &b->b_mutex );
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( c->c_live );
+ c->c_pdu_cb = handle_one_response;
+
+ /* Unless we are configured to use the VC exop, consider allocating the
+ * connection into the bind conn pool. Start off by allocating one for
+ * general use, then one for binds, then we start filling up the general
+ * connection pool, finally the bind pool */
+ if (
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ !(lload_features & LLOAD_FEATURE_VC) &&
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ b->b_active && b->b_numbindconns ) {
+ if ( !b->b_bindavail ) {
+ is_bindconn = 1;
+ } else if ( b->b_active >= b->b_numconns &&
+ b->b_bindavail < b->b_numbindconns ) {
+ is_bindconn = 1;
+ }
+ }
+
+ if ( is_bindconn ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_BIND;
+ b->b_bindavail++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_bindconn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER(
+ &b->b_bindconns, b->b_last_bindconn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_bindconns, c, c_next );
+ }
+ b->b_last_bindconn = c;
+ } else if ( bindconf.sb_method == LDAP_AUTH_NONE ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_OPEN;
+ b->b_active++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_conn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER( &b->b_conns, b->b_last_conn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_conns, c, c_next );
+ }
+ b->b_last_conn = c;
+ } else {
+ if ( ldap_pvt_thread_pool_submit(
+ &connection_pool, upstream_bind, c ) ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_finish: "
+ "failed to set up a bind callback for connid=%lu\n",
+ c->c_connid );
+ return -1;
+ }
+ /* keep a reference for upstream_bind */
+ acquire_ref( &c->c_refcnt );
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_finish: "
+ "scheduled a bind callback for connid=%lu\n",
+ c->c_connid );
+ return LDAP_SUCCESS;
+ }
+ event_add( c->c_read_event, c->c_read_timeout );
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_finish: "
+ "%sconnection connid=%lu for backend server '%s' is ready for "
+ "use\n",
+ is_bindconn ? "bind " : "", c->c_connid, b->b_name.bv_val );
+
+ backend_retry( b );
+ return LDAP_SUCCESS;
+}
+
+#ifdef HAVE_TLS
+static void
+upstream_tls_handshake_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadConnection *c = arg;
+ LloadBackend *b;
+ epoch_t epoch;
+ int rc = LDAP_SUCCESS;
+
+ CONNECTION_LOCK(c);
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu, timeout reached, destroying\n",
+ c->c_connid );
+ goto fail;
+ }
+ b = c->c_backend;
+
+ rc = ldap_pvt_tls_connect( lload_tls_backend_ld, c->c_sb, b->b_host );
+ if ( rc < 0 ) {
+ goto fail;
+ }
+
+ if ( rc == 0 ) {
+ struct event_base *base = event_get_base( c->c_read_event );
+
+ /*
+ * We're finished, replace the callbacks
+ *
+ * This is deadlock-safe, since both share the same base - the one
+ * that's just running us.
+ */
+ event_del( c->c_read_event );
+ event_del( c->c_write_event );
+
+ c->c_read_timeout = NULL;
+ event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST,
+ connection_read_cb, c );
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ connection_write_cb, c );
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu finished\n",
+ c->c_connid );
+ c->c_is_tls = LLOAD_TLS_ESTABLISHED;
+
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+
+ rc = upstream_finish( c );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc ) {
+ goto fail;
+ }
+ } else if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) {
+ event_add( c->c_write_event, lload_write_timeout );
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu need write rc=%d\n",
+ c->c_connid, rc );
+ }
+ CONNECTION_UNLOCK(c);
+ return;
+
+fail:
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu failed rc=%d\n",
+ c->c_connid, rc );
+
+ assert( c->c_ops == NULL );
+ epoch = epoch_join();
+ CONNECTION_DESTROY(c);
+ epoch_leave( epoch );
+}
+
+static int
+upstream_starttls( LloadConnection *c )
+{
+ BerValue matcheddn, message, responseOid,
+ startTLSOid = BER_BVC(LDAP_EXOP_START_TLS);
+ BerElement *ber = c->c_currentber;
+ struct event_base *base;
+ ber_int_t msgid, result;
+ ber_tag_t tag;
+
+ c->c_currentber = NULL;
+ CONNECTION_LOCK(c);
+
+ if ( ber_scanf( ber, "it", &msgid, &tag ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation from server\n" );
+ goto fail;
+ }
+
+ if ( msgid != ( c->c_next_msgid - 1 ) || tag != LDAP_RES_EXTENDED ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "unexpected %s from server, msgid=%d\n",
+ lload_msgtype2str( tag ), msgid );
+ goto fail;
+ }
+
+ if ( ber_scanf( ber, "{emm}", &result, &matcheddn, &message ) ==
+ LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation on StartTLS response\n" );
+ goto fail;
+ }
+
+ if ( (tag = ber_get_tag( ber )) != LBER_DEFAULT ) {
+ if ( tag != LDAP_TAG_EXOP_RES_OID ||
+ ber_scanf( ber, "{m}", &responseOid ) == LBER_DEFAULT ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation on StartTLS response\n" );
+ goto fail;
+ }
+
+ if ( ber_bvcmp( &responseOid, &startTLSOid ) ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "oid=%s not a StartTLS response\n",
+ responseOid.bv_val );
+ goto fail;
+ }
+ }
+
+ if ( result != LDAP_SUCCESS ) {
+ LloadBackend *b = c->c_backend;
+ int rc;
+
+ Debug( LDAP_DEBUG_STATS, "upstream_starttls: "
+ "server doesn't support StartTLS rc=%d message='%s'%s\n",
+ result, message.bv_val,
+ (c->c_is_tls == LLOAD_STARTTLS_OPTIONAL) ? ", ignored" : "" );
+ if ( c->c_is_tls != LLOAD_STARTTLS_OPTIONAL ) {
+ goto fail;
+ }
+ c->c_is_tls = LLOAD_CLEARTEXT;
+
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+
+ rc = upstream_finish( c );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc ) {
+ goto fail;
+ }
+
+ ber_free( ber, 1 );
+ CONNECTION_UNLOCK(c);
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+ /* Do not keep handle_pdus running, we have adjusted c_read_event as we
+ * need it. */
+ return -1;
+ }
+
+ base = event_get_base( c->c_read_event );
+
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ event_del( c->c_read_event );
+ event_del( c->c_write_event );
+
+ c->c_read_timeout = lload_timeout_net;
+ event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST,
+ upstream_tls_handshake_cb, c );
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ upstream_tls_handshake_cb, c );
+
+ event_add( c->c_read_event, c->c_read_timeout );
+ event_add( c->c_write_event, lload_write_timeout );
+
+ CONNECTION_UNLOCK(c);
+
+ ber_free( ber, 1 );
+ return -1;
+
+fail:
+ ber_free( ber, 1 );
+ CONNECTION_DESTROY(c);
+ return -1;
+}
+#endif /* HAVE_TLS */
+
+/*
+ * We must already hold b->b_mutex when called.
+ */
+LloadConnection *
+upstream_init( ber_socket_t s, LloadBackend *b )
+{
+ LloadConnection *c;
+ struct event_base *base = lload_get_base( s );
+ struct event *event;
+ int flags;
+
+ assert( b != NULL );
+
+ flags = (b->b_proto == LDAP_PROTO_IPC) ? CONN_IS_IPC : 0;
+ if ( (c = lload_connection_init( s, b->b_host, flags )) == NULL ) {
+ return NULL;
+ }
+
+ CONNECTION_LOCK(c);
+ c->c_backend = b;
+#ifdef HAVE_TLS
+ c->c_is_tls = b->b_tls;
+#endif
+ c->c_pdu_cb = handle_one_response;
+
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_preparing, c, c_next );
+ c->c_type = LLOAD_C_PREPARING;
+
+ {
+ ber_len_t max = sockbuf_max_incoming_upstream;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ event = event_new( base, s, EV_READ|EV_PERSIST, connection_read_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_init: "
+ "Read event could not be allocated\n" );
+ goto fail;
+ }
+ c->c_read_event = event;
+
+ event = event_new( base, s, EV_WRITE, connection_write_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_init: "
+ "Write event could not be allocated\n" );
+ goto fail;
+ }
+ /* We only add the write event when we have data pending */
+ c->c_write_event = event;
+
+#ifdef BALANCER_MODULE
+ if ( b->b_monitor ) {
+ acquire_ref( &c->c_refcnt );
+ CONNECTION_UNLOCK(c);
+ checked_unlock( &b->b_mutex );
+ if ( lload_monitor_conn_entry_create( c, b->b_monitor ) ) {
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+ goto fail;
+ }
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ }
+#endif /* BALANCER_MODULE */
+
+ c->c_destroy = upstream_destroy;
+ c->c_unlink = upstream_unlink;
+
+#ifdef HAVE_TLS
+ if ( c->c_is_tls == LLOAD_CLEARTEXT ) {
+#endif /* HAVE_TLS */
+ if ( upstream_finish( c ) ) {
+ goto fail;
+ }
+#ifdef HAVE_TLS
+ } else if ( c->c_is_tls == LLOAD_LDAPS ) {
+ event_assign( c->c_read_event, base, s, EV_READ|EV_PERSIST,
+ upstream_tls_handshake_cb, c );
+ event_add( c->c_read_event, c->c_read_timeout );
+ event_assign( c->c_write_event, base, s, EV_WRITE,
+ upstream_tls_handshake_cb, c );
+ event_add( c->c_write_event, lload_write_timeout );
+ } else if ( c->c_is_tls == LLOAD_STARTTLS ||
+ c->c_is_tls == LLOAD_STARTTLS_OPTIONAL ) {
+ BerElement *output;
+
+ checked_lock( &c->c_io_mutex );
+ if ( (output = c->c_pendingber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ goto fail;
+ }
+ ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, c->c_next_msgid++,
+ LDAP_REQ_EXTENDED,
+ LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_START_TLS );
+ checked_unlock( &c->c_io_mutex );
+
+ c->c_pdu_cb = upstream_starttls;
+ CONNECTION_UNLOCK(c);
+ connection_write_cb( s, 0, c );
+ CONNECTION_LOCK(c);
+ if ( IS_ALIVE( c, c_live ) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ }
+ }
+#endif /* HAVE_TLS */
+ CONNECTION_UNLOCK(c);
+
+ return c;
+
+fail:
+ if ( !IS_ALIVE( c, c_live ) ) {
+ /*
+ * Released while we were unlocked, it's scheduled for destruction
+ * already
+ */
+ return NULL;
+ }
+
+ if ( c->c_write_event ) {
+ event_del( c->c_write_event );
+ event_free( c->c_write_event );
+ }
+ if ( c->c_read_event ) {
+ event_del( c->c_read_event );
+ event_free( c->c_read_event );
+ }
+
+ c->c_state = LLOAD_C_INVALID;
+ c->c_live--;
+ c->c_refcnt--;
+ connection_destroy( c );
+
+ return NULL;
+}
+
+static void
+upstream_unlink( LloadConnection *c )
+{
+ LloadBackend *b = c->c_backend;
+ struct event *read_event, *write_event;
+ TAvlnode *root, *linked_root;
+ long freed, executing;
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_unlink: "
+ "removing upstream connid=%lu\n",
+ c->c_connid );
+ CONNECTION_ASSERT_LOCKED(c);
+
+ assert( c->c_state != LLOAD_C_INVALID );
+ assert( c->c_state != LLOAD_C_DYING );
+
+ c->c_state = LLOAD_C_DYING;
+
+ read_event = c->c_read_event;
+ write_event = c->c_write_event;
+
+ root = c->c_ops;
+ c->c_ops = NULL;
+ executing = c->c_n_ops_executing;
+ c->c_n_ops_executing = 0;
+
+ linked_root = c->c_linked;
+ c->c_linked = NULL;
+
+ CONNECTION_UNLOCK(c);
+
+ freed = ldap_tavl_free( root, (AVL_FREE)operation_lost_upstream );
+ assert( freed == executing );
+
+ ldap_tavl_free( linked_root, (AVL_FREE)linked_upstream_lost );
+
+ /*
+ * Avoid a deadlock:
+ * event_del will block if the event is currently executing its callback,
+ * that callback might be waiting to lock c->c_mutex
+ */
+ if ( read_event ) {
+ event_del( read_event );
+ }
+
+ if ( write_event ) {
+ event_del( write_event );
+ }
+
+ checked_lock( &b->b_mutex );
+ if ( c->c_type == LLOAD_C_PREPARING ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ b->b_opening--;
+ b->b_failed++;
+ } else if ( c->c_type == LLOAD_C_BIND ) {
+ if ( c == b->b_last_bindconn ) {
+ LloadConnection *prev =
+ LDAP_CIRCLEQ_LOOP_PREV( &b->b_bindconns, c, c_next );
+ if ( prev == c ) {
+ b->b_last_bindconn = NULL;
+ } else {
+ b->b_last_bindconn = prev;
+ }
+ }
+ LDAP_CIRCLEQ_REMOVE( &b->b_bindconns, c, c_next );
+ b->b_bindavail--;
+ } else {
+ if ( c == b->b_last_conn ) {
+ LloadConnection *prev =
+ LDAP_CIRCLEQ_LOOP_PREV( &b->b_conns, c, c_next );
+ if ( prev == c ) {
+ b->b_last_conn = NULL;
+ } else {
+ b->b_last_conn = prev;
+ }
+ }
+ LDAP_CIRCLEQ_REMOVE( &b->b_conns, c, c_next );
+ b->b_active--;
+ }
+ b->b_n_ops_executing -= executing;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+
+ CONNECTION_LOCK(c);
+ CONNECTION_ASSERT_LOCKED(c);
+}
+
+void
+upstream_destroy( LloadConnection *c )
+{
+ Debug( LDAP_DEBUG_CONNS, "upstream_destroy: "
+ "freeing connection connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_LOCK(c);
+ assert( c->c_state == LLOAD_C_DYING );
+
+#ifdef BALANCER_MODULE
+ /*
+ * Can't do this in upstream_unlink as that could be run from cn=monitor
+ * modify callback.
+ */
+ if ( !BER_BVISNULL( &c->c_monitor_dn ) ) {
+ lload_monitor_conn_unlink( c );
+ }
+#endif /* BALANCER_MODULE */
+
+ c->c_state = LLOAD_C_INVALID;
+
+ assert( c->c_ops == NULL );
+
+ if ( c->c_read_event ) {
+ event_free( c->c_read_event );
+ c->c_read_event = NULL;
+ }
+
+ if ( c->c_write_event ) {
+ event_free( c->c_write_event );
+ c->c_write_event = NULL;
+ }
+
+ if ( c->c_type != LLOAD_C_BIND ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+ connection_destroy( c );
+}
diff --git a/servers/lloadd/value.c b/servers/lloadd/value.c
new file mode 100644
index 0000000..ec71444
--- /dev/null
+++ b/servers/lloadd/value.c
@@ -0,0 +1,67 @@
+/* value.c - routines for dealing with values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/*
+ * Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+
+int
+value_add_one( BerVarray *vals, struct berval *addval )
+{
+ int n;
+ BerVarray v2;
+
+ if ( *vals == NULL ) {
+ *vals = (BerVarray)SLAP_MALLOC( 2 * sizeof(struct berval) );
+ if ( *vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "value_add_one: "
+ "SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ n = 0;
+
+ } else {
+ for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) {
+ ; /* Empty */
+ }
+ *vals = (BerVarray)SLAP_REALLOC(
+ (char *)*vals, ( n + 2 ) * sizeof(struct berval) );
+ if ( *vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "value_add_one: "
+ "SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ }
+
+ v2 = &(*vals)[n];
+ ber_dupbv( v2, addval );
+
+ v2++;
+ BER_BVZERO( v2 );
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in
new file mode 100644
index 0000000..a1bc722
--- /dev/null
+++ b/servers/slapd/Makefile.in
@@ -0,0 +1,467 @@
+## Makefile.in for slapd
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SLAPTOOLS=slapadd slapcat slapdn slapindex slapmodify slappasswd slaptest slapauth slapacl slapschema
+PROGRAMS=slapd $(SLAPTOOLS)
+XPROGRAMS=sslapd libbackends.a .backend liboverlays.a
+XSRCS=version.c
+
+SUBDIRS=back-* slapi overlays pwmods
+
+NT_SRCS = nt_svc.c
+NT_OBJS = nt_svc.o ../../libraries/liblutil/slapdmsg.res
+
+SRCS = main.c globals.c bconfig.c config.c daemon.c \
+ connection.c search.c filter.c add.c cr.c \
+ attr.c entry.c backend.c result.c operation.c \
+ dn.c compare.c modify.c delete.c modrdn.c ch_malloc.c \
+ value.c verbs.c ava.c bind.c unbind.c abandon.c filterentry.c \
+ phonetic.c acl.c str2filter.c aclparse.c init.c user.c \
+ lock.c logging.c controls.c extended.c passwd.c proxyp.c \
+ schema.c schema_check.c schema_init.c schema_prep.c \
+ schemaparse.c ad.c at.c mr.c syntax.c oc.c saslauthz.c \
+ oidm.c starttls.c index.c sets.c referral.c root_dse.c \
+ sasl.c module.c mra.c mods.c sl_malloc.c zn_malloc.c limits.c \
+ operational.c matchedValues.c cancel.c syncrepl.c \
+ backglue.c backover.c ctxcsn.c ldapsync.c frontend.c \
+ slapadd.c slapcat.c slapcommon.c slapdn.c slapindex.c \
+ slappasswd.c slaptest.c slapauth.c slapacl.c component.c \
+ aci.c txn.c slapschema.c slapmodify.c \
+ $(@PLAT@_SRCS)
+
+OBJS = main.o globals.o bconfig.o config.o daemon.o \
+ connection.o search.o filter.o add.o cr.o \
+ attr.o entry.o backend.o backends.o result.o operation.o \
+ dn.o compare.o modify.o delete.o modrdn.o ch_malloc.o \
+ value.o verbs.o ava.o bind.o unbind.o abandon.o filterentry.o \
+ phonetic.o acl.o str2filter.o aclparse.o init.o user.o \
+ lock.o logging.o controls.o extended.o passwd.o proxyp.o \
+ schema.o schema_check.o schema_init.o schema_prep.o \
+ schemaparse.o ad.o at.o mr.o syntax.o oc.o saslauthz.o \
+ oidm.o starttls.o index.o sets.o referral.o root_dse.o \
+ sasl.o module.o mra.o mods.o sl_malloc.o zn_malloc.o limits.o \
+ operational.o matchedValues.o cancel.o syncrepl.o \
+ backglue.o backover.o ctxcsn.o ldapsync.o frontend.o \
+ slapadd.o slapcat.o slapcommon.o slapdn.o slapindex.o \
+ slappasswd.o slaptest.o slapauth.o slapacl.o component.o \
+ aci.o txn.o slapschema.o slapmodify.o \
+ $(@PLAT@_OBJS)
+
+LDAP_INCDIR= ../../include -I$(srcdir) -I$(srcdir)/slapi -I.
+LDAP_LIBDIR= ../../libraries
+
+SLAP_DIR=
+SLAPD_STATIC_DEPENDS=@SLAPD_NO_STATIC@ libbackends.a liboverlays.a
+SLAPD_STATIC_BACKENDS=@SLAPD_STATIC_BACKENDS@
+SLAPD_DYNAMIC_BACKENDS=@SLAPD_DYNAMIC_BACKENDS@
+
+SLAPI_LIBS=@LIBSLAPI@ @SLAPI_LIBS@
+
+XDEFS = $(MODULES_CPPFLAGS)
+XLDFLAGS = $(MODULES_LDFLAGS)
+
+XLIBS = $(SLAPD_STATIC_DEPENDS) $(SLAPD_L) $(MODULES_LIBS)
+XXLIBS = $(SLAPD_LIBS) $(SECURITY_LIBS) $(LUTIL_LIBS)
+XXXLIBS = $(LTHREAD_LIBS) $(SLAPI_LIBS)
+
+BUILD_OPT = "--enable-slapd"
+BUILD_SRV = @BUILD_SLAPD@
+
+all-local-srv: all-cffiles
+
+NT_SLAPD_DEPENDS = slapd.exp
+NT_SLAPD_OBJECTS = slapd.exp symdummy.o $(OBJS) version.o
+
+UNIX_SLAPD_DEPENDS = $(SLAPD_STATIC_DEPENDS) version.o $(SLAPD_L)
+UNIX_SLAPD_OBJECTS = $(OBJS) version.o
+
+SLAPD_DEPENDS = $(@PLAT@_SLAPD_DEPENDS)
+SLAPD_OBJECTS = $(@PLAT@_SLAPD_OBJECTS)
+
+# Notes about slapd for Windows
+# =============================
+# slapd.exe must export all of its global symbols, just like a DLL.
+# The purpose of this is to allow dynamic modules (dynamic backends
+# or external dynamic modules) to bind with the symbols at run-time.
+#
+# Exporting symbols from an .EXE is a bit tricky and involves multiple
+# steps. First a .DEF file must be generated. The .DEF file indicates
+# the set of symbols that are to be exported. Many times, it's possible
+# to manually create this file with an editor. However, with slapd,
+# we want to export EVERY global symbol that it knows about (NOT including
+# symbols that are imported from other DLLs). The set of symbols to
+# export INCLUDES symbols from all static libraries that slapd gets
+# linked with, e.g. avl, lunicode, lutil, etc. This list
+# will also include liblber and libldap if they were built as static
+# libraries. ALSO included will be symbols from other STATIC libraries
+# outside the domain of the OpenLDAP source tree, e.g. regex, ltdl,
+# crypto, ssl, sasl, etc. (If these libraries are dynamic, we won't want
+# to include their symbols in the list). The correct set of symbols
+# CAN be determined at build time. The slapd.def target automatically
+# determines the correct set of symbols and generates the slapd.def file.
+#
+# The slapd.def file, serving multiple purposes, will:
+#
+# 1) be used to generate libslapd.a, the import library for slapd.exe.
+#
+# 2) be used to generate the symdummy.c file.
+#
+# 3) be used to help create slapd.exp, the binary-formatted slapd export file.
+#
+# The import library is used by dynamic modules at link time. With this
+# library, dynamic modules indicate to the linker that it will resolve
+# these symbols from the slapd.exe binary at run-time. Of course, whenever
+# a module imports dynamic symbols, those symbols should be marked with
+# the __declspec(dllimport) directive in the header files that the dynamic
+# modules build with. In OpenLDAP, this is handled automatically in the
+# header files. (See ldap_cdefs.h for an explanation). Writers of
+# dynamic backend modules should keep in mind that slapd.exe might export
+# other global symbols that are not part of OpenLDAP (e.g. regex, ltdl,
+# crypto, ssl, sasl, etc.) When a writer actually uses (i.e. imports) these
+# symbols, he must verify that the header files from these external packages
+# include a mechanism to mark imported symbols with the __declspec(dllimport)
+# directive. Whether or not such a mechanism exists, the writer must be
+# able to include these directives appropriately when their symbols are
+# being imported from slapd.exe. The directive is not completely necessary
+# for functions, but it is required for variables.
+#
+# The symdummy.c file basically references EVERY symbol available to slapd.exe,
+# including symbols that slapd.exe never actually referenced. The file
+# is compiled and included at link time. Without this object file, slapd.exe
+# would NOT export symbols that it never referenced. The reason that these
+# symbols must still be exported is because a dynamic module may want to
+# use a symbol even if it had not been referenced by slapd.exe.
+#
+
+#
+# slapd.def REALLY depends upon all slapd objects and all static libraries
+# included in $(LIBS), including static libraries outside of OpenLDAP.
+# When slapd.def is built, the absolute paths to all static libraries
+# (both inside and outside of OpenLDAP) are generated. We don't have
+# any way to include this generated list as a dependency of slapd.def (sigh).
+# Thus, we do the best we can by depending on version.o, which depends
+# on its own very long list of dependencies.
+#
+slapd.def: libbackends.a liboverlays.a version.o
+ @for i in XX $(LDFLAGS) ; do \
+ path=`expr "$$i" : "-L\(.*\)"`; \
+ if test $$? != 0; then continue; fi; \
+ paths="$$paths $$path"; \
+ done; \
+ objs=""; \
+ for i in $(OBJS) version.o $(LIBS) ; do \
+ obj="" ; \
+ case $$i in \
+ -l*) \
+ done="" ;\
+ base=`expr "$$i" : "-l\(.*\)"`; \
+ for p in . $$paths ; do \
+ for ext in la dll dll.a a ; do \
+ path=$$p/lib$$base.$$ext; \
+ test ! -f $$path && continue; \
+ if test $$ext = la ; then \
+ for t in dlname old_library ; do \
+ line=`grep "^$$t=" $$path`; \
+ lib=`expr "$$line" : "[^']*'\(.*\)'"`; \
+ test -n "$$lib" && test -f $$p/$$lib && \
+ path=$$p/$$lib && break; \
+ done; \
+ test $$t = dlname && ext=dll; \
+ test $$t = old_library && ext=a; \
+ fi; \
+ if test $$ext = a ; then \
+ obj=$$path; \
+ fi; \
+ done=done; \
+ break; \
+ done; \
+ test -n "$$done" && break; \
+ done; \
+ test -z "$$obj" && continue; \
+ ;; \
+ *.la) \
+ if test -n "$(LTSTATIC)"; then \
+ base=`expr "$$i" : ".*/\(.*\).la"`; \
+ path=`expr "$$i" : "\(.*/\).*"`; \
+ obj=$$path.libs/$$base.a; \
+ fi; \
+ ;; \
+ *.dll.a) \
+ ;; \
+ *.o | *.a) \
+ obj=$$i; \
+ esac; \
+ objs="$$objs $$obj"; \
+ done; \
+ echo dlltool --exclude-symbols main,ServiceMain@8 --export-all-symbols \
+ --output-def $@.tmp $$objs; \
+ dlltool --exclude-symbols main,ServiceMain@8 --export-all-symbols \
+ --output-def $@.tmp $$objs;
+ echo EXPORTS > $@
+ $(SED) -e 1,2d -e 's/ @ [0-9][0-9]*//' -e '/\.refptr\./d' $@.tmp | sort >> $@
+ $(RM) $@.tmp
+
+symdummy.c: slapd.def
+ $(RM) $@
+ @echo "generating $@..."; \
+ echo "static void never_called() {" > $@.tmp; \
+ cat $< | while read line; \
+ do \
+ set dummy $$line; \
+ case $$# in \
+ 3) \
+ echo "int $$2();" >> $@; \
+ echo "$$2();" >> $@.tmp; \
+ ;; \
+ 4) \
+ echo "extern int $$2;" >> $@; \
+ echo "$$2 = 0;" >> $@.tmp; \
+ ;; \
+ esac; \
+ done; \
+ echo "" >> $@; \
+ echo "}" >> $@.tmp; \
+ cat $@.tmp >> $@; \
+ $(RM) $@.tmp
+
+libslapd.a: symdummy.o
+ dlltool --dllname slapd.exe --input-def slapd.def --output-lib $@
+
+slapd.exp: libslapd.a
+ @echo $(LTLINK) -Wl,--base-file,slapd.base -o slapd \
+ $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS); \
+ $(LTLINK) -Wl,--base-file,slapd.base -o slapd \
+ $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS)
+ $(RM) slapd.exe
+ @echo dlltool --dllname slapd.exe --input-def slapd.def \
+ --base-file slapd.base --output-exp $@; \
+ dlltool --dllname slapd.exe --input-def slapd.def \
+ --base-file slapd.base --output-exp $@; \
+ echo $(LTLINK) -Wl,--base-file,slapd.base -o slapd $@ \
+ $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS); \
+ $(LTLINK) -Wl,--base-file,slapd.base -o slapd $@ \
+ $(OBJS) symdummy.o version.o $(LIBS) $(WRAP_LIBS)
+ $(RM) slapd.exe
+ @echo dlltool --dllname slapd.exe --input-def slapd.def \
+ --base-file slapd.base --output-exp $@; \
+ dlltool --dllname slapd.exe --input-def slapd.def \
+ --base-file slapd.base --output-exp $@
+
+slapi/libslapi.la: FORCE
+ cd slapi && $(MAKE) $(MFLAGS) all
+
+slapd: $(SLAPD_DEPENDS) @LIBSLAPI@
+ $(LTLINK) -o $@ $(SLAPD_OBJECTS) $(LIBS) \
+ $(WRAP_LIBS)
+ $(RM) $(SLAPTOOLS)
+ for i in $(SLAPTOOLS); do \
+ $(LN_S) slapd$(EXEEXT) $$i$(EXEEXT); done
+
+
+sslapd: version.o
+ $(LTLINK) -static -o $@ $(OBJS) version.o $(LIBS) $(WRAP_LIBS)
+
+dummy $(SLAPD_DYNAMIC_BACKENDS): slapd
+ cd $@ && $(MAKE) $(MFLAGS) all
+ @touch $@
+
+dynamic_overlays: slapd
+ cd overlays && $(MAKE) $(MFLAGS) dynamic
+
+dynamic_pwmods: slapd
+ cd pwmods && $(MAKE) $(MFLAGS) dynamic
+
+#
+# In Windows, dynamic backends have to be built after slapd. For this
+# reason, we only build static backends now and dynamic backends later.
+#
+.backend: FORCE
+ @if test -n "$(SLAPD_STATIC_BACKENDS)"; then \
+ echo "building static backends..."; \
+ for i in XX $(SLAPD_STATIC_BACKENDS); do \
+ if test $$i != XX; then \
+ echo " "; echo " cd $$i && $(MAKE) $(MFLAGS) all"; \
+ ( cd $$i && $(MAKE) $(MFLAGS) all ); \
+ if test $$? != 0; then exit 1; fi; \
+ fi; \
+ done; \
+ echo " "; \
+ fi
+
+libbackends.a: .backend
+ @$(RM) -r tmp
+ @$(MKDIR) tmp
+ @-for i in back-*/*.a; do \
+ ( \
+ cd tmp; \
+ $(AR) x ../$$i; \
+ pre=`echo $$i | $(SED) -e 's/\/.*$$//' -e 's/back-//'`; \
+ for j in *.o; do \
+ mv $$j $${pre}$$j; \
+ done; \
+ $(AR) ruv libbackends.a *.o 2>&1 | grep -v truncated; \
+ $(RM) *.o __.SYMDEF ________64ELEL_ ; \
+ echo "added backend library $$i"; \
+ echo ""; \
+ ); \
+ done
+ @mv -f tmp/libbackends.a ./libbackends.a
+ @$(RM) -r tmp
+ @if test ! -z "$(RANLIB)" ; then \
+ $(RANLIB) libbackends.a; \
+ fi
+ @ls -l libbackends.a; echo ""
+
+liboverlays.a: FORCE
+ cd overlays && $(MAKE) $(MFLAGS) static
+
+version.c: Makefile
+ @-$(RM) $@
+ $(MKVERSION) -s -n Versionstr slapd > $@
+
+version.o: version.c $(OBJS) $(SLAPD_LIBDEPEND)
+
+backends.o: backends.c $(srcdir)/slap.h
+
+depend-local-srv: FORCE
+ @for i in $(SUBDIRS); do \
+ if test -d $$i && test -f $$i/Makefile ; then \
+ echo; echo " cd $$i && $(MAKE) $(MFLAGS) depend"; \
+ ( cd $$i && $(MAKE) $(MFLAGS) depend ); \
+ if test $$? != 0 ; then exit 1; fi ; \
+ fi; \
+ done
+ @echo ""
+
+clean-local:
+ $(RM) *.exp *.def *.base *.a *.objs symdummy.c
+
+veryclean-local:
+ $(RM) backends.c
+
+clean-local-srv: FORCE
+ @for i in $(SUBDIRS); do \
+ if test -d $$i && test -f $$i/Makefile ; then \
+ echo; echo " cd $$i && $(MAKE) $(MFLAGS) clean"; \
+ ( cd $$i && $(MAKE) $(MFLAGS) clean ); \
+ if test $$? != 0 ; then exit 1; fi ; \
+ fi; \
+ done
+ $(RM) *.tmp all-cffiles
+
+veryclean-local-srv: FORCE
+ @for i in $(SUBDIRS); do \
+ if test -d $$i && test -f $$i/Makefile ; then \
+ echo; echo " cd $$i && $(MAKE) $(MFLAGS) clean"; \
+ ( cd $$i && $(MAKE) $(MFLAGS) veryclean ); \
+ fi; \
+ done
+
+install-local-srv: install-slapd install-tools \
+ install-conf install-schema install-tools
+
+install-slapd: FORCE
+ -$(MKDIR) $(DESTDIR)$(libexecdir)
+ -$(MKDIR) $(DESTDIR)$(localstatedir)/run
+ $(LTINSTALL) $(INSTALLFLAGS) $(STRIP_OPTS) -m 755 \
+ slapd$(EXEEXT) $(DESTDIR)$(libexecdir)
+ @for i in $(SUBDIRS); do \
+ if test -d $$i && test -f $$i/Makefile ; then \
+ echo; echo " cd $$i && $(MAKE) $(MFLAGS) install"; \
+ ( cd $$i && $(MAKE) $(MFLAGS) install ); \
+ if test $$? != 0 ; then exit 1; fi ; \
+ fi; \
+ done
+
+all-cffiles: slapd $(SLAPD_DYNAMIC_BACKENDS) dynamic_overlays dynamic_pwmods
+ @if test $(PLAT) = NT; then \
+ sysconfdir=`cygpath -w $(sysconfdir) | \
+ $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \
+ localstatedir=`cygpath -w $(localstatedir) | \
+ $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \
+ moduledir=`cygpath -w $(moduledir) | \
+ $(SED) -e 's/\\\\/\\\\\\\\\\\\\\\\/g'`; \
+ else \
+ sysconfdir=$(sysconfdir); \
+ localstatedir=$(localstatedir); \
+ moduledir=$(moduledir); \
+ fi; \
+ $(SED) -e "s;%SYSCONFDIR%;$$sysconfdir;" \
+ -e "s;%LOCALSTATEDIR%;$$localstatedir;" \
+ -e "s;%MODULEDIR%;$$moduledir;" \
+ $(srcdir)/slapd.conf > slapd.conf.tmp || exit $$? ; \
+ $(SED) -e "s;%SYSCONFDIR%;$$sysconfdir;" \
+ -e "s;%LOCALSTATEDIR%;$$localstatedir;" \
+ -e "s;%MODULEDIR%;$$moduledir;" \
+ $(srcdir)/slapd.ldif > slapd.ldif.tmp || exit $$? ;
+ @if test -n "$(systemdsystemunitdir)"; then \
+ $(SED) -e "s;%LIBEXECDIR%;$(libexecdir);" \
+ $(srcdir)/slapd.service > slapd.service.tmp ; \
+ fi
+ touch all-cffiles
+
+install-schema: FORCE
+ @if test -d $(DESTDIR)$(schemadir) ; then \
+ echo "MOVING EXISTING SCHEMA DIR to $(DESTDIR)$(schemadir).$$$$" ; \
+ mv $(DESTDIR)$(schemadir) $(DESTDIR)$(schemadir).$$$$ ; \
+ fi
+ $(MKDIR) $(DESTDIR)$(schemadir)
+ @SD=$(DESTDIR)$(schemadir) ; \
+ files=`cd $(srcdir)/schema ; echo README *.ldif *.schema` ; \
+ for i in $$files ; do \
+ echo $(INSTALL) $(INSTALLFLAGS) -m 444 schema/$$i $$SD/$$i ; \
+ $(INSTALL) $(INSTALLFLAGS) -m 444 $(srcdir)/schema/$$i $$SD/$$i ; \
+ done
+
+install-conf: FORCE
+ @-$(MKDIR) $(DESTDIR)$(sysconfdir)
+ $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf.default
+ if test ! -f $(DESTDIR)$(sysconfdir)/slapd.conf; then \
+ echo "installing slapd.conf in $(sysconfdir)"; \
+ echo "$(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf"; \
+ $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.conf.tmp $(DESTDIR)$(sysconfdir)/slapd.conf; \
+ else \
+ echo "PRESERVING EXISTING CONFIGURATION FILE $(DESTDIR)$(sysconfdir)/slapd.conf" ; \
+ fi
+ $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif.default
+ if test ! -f $(DESTDIR)$(sysconfdir)/slapd.ldif; then \
+ echo "installing slapd.ldif in $(sysconfdir)"; \
+ echo "$(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif"; \
+ $(INSTALL) $(INSTALLFLAGS) -m 600 slapd.ldif.tmp $(DESTDIR)$(sysconfdir)/slapd.ldif; \
+ else \
+ echo "PRESERVING EXISTING CONFIGURATION FILE $(DESTDIR)$(sysconfdir)/slapd.ldif" ; \
+ fi
+ if test -n "$(systemdsystemunitdir)" && test ! -f $(DESTDIR)$(systemdsystemunitdir)/slapd.service; then \
+ $(MKDIR) $(DESTDIR)$(systemdsystemunitdir); \
+ echo "installing slapd.service in $(systemdsystemunitdir)"; \
+ echo "$(INSTALL) $(INSTALLFLAGS) -m 644 slapd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/slapd.service"; \
+ $(INSTALL) $(INSTALLFLAGS) -m 644 slapd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/slapd.service; \
+ fi
+
+install-db-config: FORCE
+ @-$(MKDIR) $(DESTDIR)$(localstatedir) $(DESTDIR)$(sysconfdir)
+ @-$(INSTALL) -m 700 -d $(DESTDIR)$(localstatedir)/openldap-data
+ $(INSTALL) $(INSTALLFLAGS) -m 600 $(srcdir)/DB_CONFIG \
+ $(DESTDIR)$(localstatedir)/openldap-data/DB_CONFIG.example
+ $(INSTALL) $(INSTALLFLAGS) -m 600 $(srcdir)/DB_CONFIG \
+ $(DESTDIR)$(sysconfdir)/DB_CONFIG.example
+
+install-tools: FORCE
+ -$(MKDIR) $(DESTDIR)$(sbindir)
+ for i in $(SLAPTOOLS); do \
+ $(RM) $(DESTDIR)$(sbindir)/$$i$(EXEEXT); \
+ $(LN_S) -f $(DESTDIR)$(libexecdir)/slapd$(EXEEXT) $(DESTDIR)$(sbindir)/$$i$(EXEEXT); \
+ done
+
diff --git a/servers/slapd/abandon.c b/servers/slapd/abandon.c
new file mode 100644
index 0000000..bec95e2
--- /dev/null
+++ b/servers/slapd/abandon.c
@@ -0,0 +1,139 @@
+/* abandon.c - decode and handle an ldap abandon operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+int
+do_abandon( Operation *op, SlapReply *rs )
+{
+ ber_int_t id;
+ Operation *o;
+ const char *msg;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_abandon\n",
+ op->o_log_prefix );
+
+ /*
+ * Parse the abandon request. It looks like this:
+ *
+ * AbandonRequest := MessageID
+ */
+
+ if ( ber_scanf( op->o_ber, "i", &id ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_abandon: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s ABANDON msg=%ld\n",
+ op->o_log_prefix, (long) id );
+
+ if( get_ctrls( op, rs, 0 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_abandon: get_ctrls failed\n",
+ op->o_log_prefix );
+ return rs->sr_err;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "%s do_abandon: id=%ld\n",
+ op->o_log_prefix, (long) id );
+
+ if( id <= 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_abandon: bad msgid %ld\n",
+ op->o_log_prefix, (long) id );
+ return LDAP_SUCCESS;
+ }
+
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ /* Find the operation being abandoned. */
+ LDAP_STAILQ_FOREACH( o, &op->o_conn->c_ops, o_next ) {
+ if ( o->o_msgid == id ) {
+ break;
+ }
+ }
+
+ if ( o == NULL ) {
+ msg = "not found";
+ /* The operation is not active. Just discard it if found. */
+ LDAP_STAILQ_FOREACH( o, &op->o_conn->c_pending_ops, o_next ) {
+ if ( o->o_msgid == id ) {
+ msg = "discarded";
+ /* FIXME: This traverses c_pending_ops yet again. */
+ LDAP_STAILQ_REMOVE( &op->o_conn->c_pending_ops,
+ o, Operation, o_next );
+ LDAP_STAILQ_NEXT(o, o_next) = NULL;
+ op->o_conn->c_n_ops_pending--;
+ slap_op_free( o, NULL );
+ break;
+ }
+ }
+
+ } else if ( o->o_tag == LDAP_REQ_BIND
+ || o->o_tag == LDAP_REQ_UNBIND
+ || o->o_tag == LDAP_REQ_ABANDON ) {
+ msg = "cannot be abandoned";
+
+ } else if ( o->o_abandon ) {
+ msg = "already being abandoned";
+
+ } else {
+ msg = "found";
+ /* Set the o_abandon flag in the to-be-abandoned operation.
+ * The backend can periodically check this flag and abort the
+ * operation at a convenient time. However it should "send"
+ * the response anyway, with result code SLAPD_ABANDON.
+ * The functions in result.c will intercept the message.
+ */
+ o->o_abandon = 1;
+ op->orn_msgid = id;
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_abandon( op, rs );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_abandon: op=%ld %s\n",
+ op->o_log_prefix, (long) id, msg );
+ return rs->sr_err;
+}
+
+int
+fe_op_abandon( Operation *op, SlapReply *rs )
+{
+ LDAP_STAILQ_FOREACH( op->o_bd, &backendDB, be_next ) {
+ if ( op->o_bd->be_abandon ) {
+ (void)op->o_bd->be_abandon( op, rs );
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/aci.c b/servers/slapd/aci.c
new file mode 100644
index 0000000..c8cc41d
--- /dev/null
+++ b/servers/slapd/aci.c
@@ -0,0 +1,1836 @@
+/* aci.c - routines to parse and check acl's */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_ACI_ENABLED
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/regex.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+/* use most appropriate size */
+#define ACI_BUF_SIZE 1024
+
+/* move to "stable" when no longer experimental */
+#define SLAPD_ACI_SYNTAX "1.3.6.1.4.1.4203.666.2.1"
+
+/* change this to "OpenLDAPset" */
+#define SLAPD_ACI_SET_ATTR "template"
+
+typedef enum slap_aci_scope_t {
+ SLAP_ACI_SCOPE_ENTRY = 0x1,
+ SLAP_ACI_SCOPE_CHILDREN = 0x2,
+ SLAP_ACI_SCOPE_SUBTREE = ( SLAP_ACI_SCOPE_ENTRY | SLAP_ACI_SCOPE_CHILDREN )
+} slap_aci_scope_t;
+
+enum {
+ ACI_BV_ENTRY,
+ ACI_BV_CHILDREN,
+ ACI_BV_ONELEVEL,
+ ACI_BV_SUBTREE,
+
+ ACI_BV_BR_ENTRY,
+ ACI_BV_BR_CHILDREN,
+ ACI_BV_BR_ALL,
+
+ ACI_BV_ACCESS_ID,
+ ACI_BV_PUBLIC,
+ ACI_BV_USERS,
+ ACI_BV_SELF,
+ ACI_BV_DNATTR,
+ ACI_BV_GROUP,
+ ACI_BV_ROLE,
+ ACI_BV_SET,
+ ACI_BV_SET_REF,
+
+ ACI_BV_GRANT,
+ ACI_BV_DENY,
+
+ ACI_BV_GROUP_CLASS,
+ ACI_BV_GROUP_ATTR,
+ ACI_BV_ROLE_CLASS,
+ ACI_BV_ROLE_ATTR,
+
+ ACI_BV_SET_ATTR,
+
+ ACI_BV_LAST
+};
+
+static const struct berval aci_bv[] = {
+ /* scope */
+ BER_BVC("entry"),
+ BER_BVC("children"),
+ BER_BVC("onelevel"),
+ BER_BVC("subtree"),
+
+ /* */
+ BER_BVC("[entry]"),
+ BER_BVC("[children]"),
+ BER_BVC("[all]"),
+
+ /* type */
+ BER_BVC("access-id"),
+ BER_BVC("public"),
+ BER_BVC("users"),
+ BER_BVC("self"),
+ BER_BVC("dnattr"),
+ BER_BVC("group"),
+ BER_BVC("role"),
+ BER_BVC("set"),
+ BER_BVC("set-ref"),
+
+ /* actions */
+ BER_BVC("grant"),
+ BER_BVC("deny"),
+
+ /* schema */
+ BER_BVC(SLAPD_GROUP_CLASS),
+ BER_BVC(SLAPD_GROUP_ATTR),
+ BER_BVC(SLAPD_ROLE_CLASS),
+ BER_BVC(SLAPD_ROLE_ATTR),
+
+ BER_BVC(SLAPD_ACI_SET_ATTR),
+
+ BER_BVNULL
+};
+
+static AttributeDescription *slap_ad_aci;
+
+static int
+OpenLDAPaciValidate(
+ Syntax *syntax,
+ struct berval *val );
+
+static int
+OpenLDAPaciPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx );
+
+static int
+OpenLDAPaciNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx );
+
+#define OpenLDAPaciMatch octetStringMatch
+
+static int
+aci_list_map_rights(
+ struct berval *list )
+{
+ struct berval bv;
+ slap_access_t mask;
+ int i;
+
+ ACL_INIT( mask );
+ for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
+ if ( bv.bv_len <= 0 ) {
+ continue;
+ }
+
+ switch ( *bv.bv_val ) {
+ case 'x':
+ /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt does not
+ * define any equivalent to the AUTH right, so I've just used
+ * 'x' for now.
+ */
+ ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
+ break;
+ case 'd':
+ /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
+ * the right 'd' to mean "delete"; we hijack it to mean
+ * "disclose" for consistency wuith the rest of slapd.
+ */
+ ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
+ break;
+ case 'c':
+ ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
+ break;
+ case 's':
+ /* **** NOTE: draft-ietf-ldapext-aci-model-0.3.txt defines
+ * the right 's' to mean "set", but in the examples states
+ * that the right 's' means "search". The latter definition
+ * is used here.
+ */
+ ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
+ break;
+ case 'r':
+ ACL_PRIV_SET(mask, ACL_PRIV_READ);
+ break;
+ case 'w':
+ ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ return mask;
+}
+
+static int
+aci_list_has_attr(
+ struct berval *list,
+ const struct berval *attr,
+ struct berval *val )
+{
+ struct berval bv, left, right;
+ int i;
+
+ for ( i = 0; acl_get_part( list, i, ',', &bv ) >= 0; i++ ) {
+ if ( acl_get_part(&bv, 0, '=', &left ) < 0
+ || acl_get_part( &bv, 1, '=', &right ) < 0 )
+ {
+ if ( ber_bvstrcasecmp( attr, &bv ) == 0 ) {
+ return(1);
+ }
+
+ } else if ( val == NULL ) {
+ if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
+ return(1);
+ }
+
+ } else {
+ if ( ber_bvstrcasecmp( attr, &left ) == 0 ) {
+ /* FIXME: this is also totally undocumented! */
+ /* this is experimental code that implements a
+ * simple (prefix) match of the attribute value.
+ * the ACI draft does not provide for aci's that
+ * apply to specific values, but it would be
+ * nice to have. If the <attr> part of an aci's
+ * rights list is of the form <attr>=<value>,
+ * that means the aci applies only to attrs with
+ * the given value. Furthermore, if the attr is
+ * of the form <attr>=<value>*, then <value> is
+ * treated as a prefix, and the aci applies to
+ * any value with that prefix.
+ *
+ * Ideally, this would allow r.e. matches.
+ */
+ if ( acl_get_part( &right, 0, '*', &left ) < 0
+ || right.bv_len <= left.bv_len )
+ {
+ if ( ber_bvstrcasecmp( val, &right ) == 0 ) {
+ return 1;
+ }
+
+ } else if ( val->bv_len >= left.bv_len ) {
+ if ( strncasecmp( val->bv_val, left.bv_val, left.bv_len ) == 0 ) {
+ return(1);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static slap_access_t
+aci_list_get_attr_rights(
+ struct berval *list,
+ const struct berval *attr,
+ struct berval *val )
+{
+ struct berval bv;
+ slap_access_t mask;
+ int i;
+
+ /* loop through each rights/attr pair, skip first part (action) */
+ ACL_INIT(mask);
+ for ( i = 1; acl_get_part( list, i + 1, ';', &bv ) >= 0; i += 2 ) {
+ if ( aci_list_has_attr( &bv, attr, val ) == 0 ) {
+ Debug( LDAP_DEBUG_ACL,
+ " <= aci_list_get_attr_rights "
+ "test %s for %s -> failed\n",
+ bv.bv_val, attr->bv_val );
+ continue;
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ " <= aci_list_get_attr_rights "
+ "test %s for %s -> ok\n",
+ bv.bv_val, attr->bv_val );
+
+ if ( acl_get_part( list, i, ';', &bv ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL,
+ " <= aci_list_get_attr_rights "
+ "test no rights\n" );
+ continue;
+ }
+
+ mask |= aci_list_map_rights( &bv );
+ Debug( LDAP_DEBUG_ACL,
+ " <= aci_list_get_attr_rights "
+ "rights %s to mask 0x%x\n",
+ bv.bv_val, mask );
+ }
+
+ return mask;
+}
+
+static int
+aci_list_get_rights(
+ struct berval *list,
+ struct berval *attr,
+ struct berval *val,
+ slap_access_t *grant,
+ slap_access_t *deny )
+{
+ struct berval perm, actn, baseattr;
+ slap_access_t *mask;
+ int i, found;
+
+ if ( attr == NULL || BER_BVISEMPTY( attr ) ) {
+ attr = (struct berval *)&aci_bv[ ACI_BV_ENTRY ];
+
+ } else if ( acl_get_part( attr, 0, ';', &baseattr ) > 0 ) {
+ attr = &baseattr;
+ }
+ found = 0;
+ ACL_INIT(*grant);
+ ACL_INIT(*deny);
+ /* loop through each permissions clause */
+ for ( i = 0; acl_get_part( list, i, '$', &perm ) >= 0; i++ ) {
+ if ( acl_get_part( &perm, 0, ';', &actn ) < 0 ) {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_GRANT ], &actn ) == 0 ) {
+ mask = grant;
+
+ } else if ( ber_bvstrcasecmp( &aci_bv[ ACI_BV_DENY ], &actn ) == 0 ) {
+ mask = deny;
+
+ } else {
+ continue;
+ }
+
+ *mask |= aci_list_get_attr_rights( &perm, attr, val );
+ *mask |= aci_list_get_attr_rights( &perm, &aci_bv[ ACI_BV_BR_ALL ], NULL );
+
+ if ( *mask != ACL_PRIV_NONE ) {
+ found = 1;
+ }
+ }
+
+ return found;
+}
+
+static int
+aci_group_member (
+ struct berval *subj,
+ const struct berval *defgrpoc,
+ const struct berval *defgrpat,
+ Operation *op,
+ Entry *e,
+ int nmatch,
+ regmatch_t *matches
+)
+{
+ struct berval subjdn;
+ struct berval grpoc;
+ struct berval grpat;
+ ObjectClass *grp_oc = NULL;
+ AttributeDescription *grp_ad = NULL;
+ const char *text;
+ int rc;
+
+ /* format of string is "{group|role}/objectClassValue/groupAttrName" */
+ if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) {
+ return 0;
+ }
+
+ if ( acl_get_part( subj, 1, '/', &grpoc ) < 0 ) {
+ grpoc = *defgrpoc;
+ }
+
+ if ( acl_get_part( subj, 2, '/', &grpat ) < 0 ) {
+ grpat = *defgrpat;
+ }
+
+ rc = slap_bv2ad( &grpat, &grp_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = 0;
+ goto done;
+ }
+ rc = 0;
+
+ grp_oc = oc_bvfind( &grpoc );
+
+ if ( grp_oc != NULL && grp_ad != NULL ) {
+ char buf[ ACI_BUF_SIZE ];
+ struct berval bv, ndn;
+ AclRegexMatches amatches = { 0 };
+
+ amatches.dn_count = nmatch;
+ AC_MEMCPY( amatches.dn_data, matches, sizeof( amatches.dn_data ) );
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = (char *)&buf;
+ if ( acl_string_expand( &bv, &subjdn,
+ &e->e_nname, NULL, &amatches ) )
+ {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( dnNormalize( 0, NULL, NULL, &bv, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS )
+ {
+ rc = ( backend_group( op, e, &ndn, &op->o_ndn,
+ grp_oc, grp_ad ) == 0 );
+ slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+done:
+ return rc;
+}
+
+static int
+aci_mask(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ struct berval *aci,
+ int nmatch,
+ regmatch_t *matches,
+ slap_access_t *grant,
+ slap_access_t *deny,
+ slap_aci_scope_t asserted_scope )
+{
+ struct berval bv,
+ scope,
+ perms,
+ type,
+ opts,
+ sdn;
+ int rc;
+
+ ACL_INIT( *grant );
+ ACL_INIT( *deny );
+
+ assert( !BER_BVISNULL( &desc->ad_cname ) );
+
+ /* parse an aci of the form:
+ oid # scope # action;rights;attr;rights;attr
+ $ action;rights;attr;rights;attr # type # subject
+
+ [NOTE: the following comment is very outdated,
+ as the draft version it refers to (Ando, 2004-11-20)].
+
+ See draft-ietf-ldapext-aci-model-04.txt section 9.1 for
+ a full description of the format for this attribute.
+ Differences: "this" in the draft is "self" here, and
+ "self" and "public" is in the position of type.
+
+ <scope> = {entry|children|subtree}
+ <type> = {public|users|access-id|subtree|onelevel|children|
+ self|dnattr|group|role|set|set-ref}
+
+ This routine now supports scope={ENTRY,CHILDREN}
+ with the semantics:
+ - ENTRY applies to "entry" and "subtree";
+ - CHILDREN applies to "children" and "subtree"
+ */
+
+ /* check that the aci has all 5 components */
+ if ( acl_get_part( aci, 4, '#', NULL ) < 0 ) {
+ return 0;
+ }
+
+ /* check that the aci family is supported */
+ /* FIXME: the OID is ignored? */
+ if ( acl_get_part( aci, 0, '#', &bv ) < 0 ) {
+ return 0;
+ }
+
+ /* check that the scope matches */
+ if ( acl_get_part( aci, 1, '#', &scope ) < 0 ) {
+ return 0;
+ }
+
+ /* note: scope can be either ENTRY or CHILDREN;
+ * they respectively match "entry" and "children" in bv
+ * both match "subtree" */
+ switch ( asserted_scope ) {
+ case SLAP_ACI_SCOPE_ENTRY:
+ if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_ENTRY ] ) != 0
+ && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
+ {
+ return 0;
+ }
+ break;
+
+ case SLAP_ACI_SCOPE_CHILDREN:
+ if ( ber_bvcmp( &scope, &aci_bv[ ACI_BV_CHILDREN ] ) != 0
+ && ber_bvstrcasecmp( &scope, &aci_bv[ ACI_BV_SUBTREE ] ) != 0 )
+ {
+ return 0;
+ }
+ break;
+
+ case SLAP_ACI_SCOPE_SUBTREE:
+ /* TODO: add assertion? */
+ return 0;
+ }
+
+ /* get the list of permissions clauses, bail if empty */
+ if ( acl_get_part( aci, 2, '#', &perms ) <= 0 ) {
+ assert( 0 );
+ return 0;
+ }
+
+ /* check if any permissions allow desired access */
+ if ( aci_list_get_rights( &perms, &desc->ad_cname, val, grant, deny ) == 0 ) {
+ return 0;
+ }
+
+ /* see if we have a DN match */
+ if ( acl_get_part( aci, 3, '#', &type ) < 0 ) {
+ assert( 0 );
+ return 0;
+ }
+
+ /* see if we have a public (i.e. anonymous) access */
+ if ( ber_bvcmp( &aci_bv[ ACI_BV_PUBLIC ], &type ) == 0 ) {
+ return 1;
+ }
+
+ /* otherwise require an identity */
+ if ( BER_BVISNULL( &op->o_ndn ) || BER_BVISEMPTY( &op->o_ndn ) ) {
+ return 0;
+ }
+
+ /* see if we have a users access */
+ if ( ber_bvcmp( &aci_bv[ ACI_BV_USERS ], &type ) == 0 ) {
+ return 1;
+ }
+
+ /* NOTE: this may fail if a DN contains a valid '#' (unescaped);
+ * just grab all the berval up to its end (ITS#3303).
+ * NOTE: the problem could be solved by providing the DN with
+ * the embedded '#' encoded as hexpairs: "cn=Foo#Bar" would
+ * become "cn=Foo\23Bar" and be safely used by aci_mask(). */
+#if 0
+ if ( acl_get_part( aci, 4, '#', &sdn ) < 0 ) {
+ return 0;
+ }
+#endif
+ sdn.bv_val = type.bv_val + type.bv_len + STRLENOF( "#" );
+ sdn.bv_len = aci->bv_len - ( sdn.bv_val - aci->bv_val );
+
+ /* get the type options, if any */
+ if ( acl_get_part( &type, 1, '/', &opts ) > 0 ) {
+ opts.bv_len = type.bv_len - ( opts.bv_val - type.bv_val );
+ type.bv_len = opts.bv_val - type.bv_val - 1;
+
+ } else {
+ BER_BVZERO( &opts );
+ }
+
+ if ( ber_bvcmp( &aci_bv[ ACI_BV_ACCESS_ID ], &type ) == 0 ) {
+ return dn_match( &op->o_ndn, &sdn );
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SUBTREE ], &type ) == 0 ) {
+ return dnIsSuffix( &op->o_ndn, &sdn );
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ONELEVEL ], &type ) == 0 ) {
+ struct berval pdn;
+
+ dnParent( &sdn, &pdn );
+
+ return dn_match( &op->o_ndn, &pdn );
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_CHILDREN ], &type ) == 0 ) {
+ return ( !dn_match( &op->o_ndn, &sdn ) && dnIsSuffix( &op->o_ndn, &sdn ) );
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SELF ], &type ) == 0 ) {
+ return dn_match( &op->o_ndn, &e->e_nname );
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_DNATTR ], &type ) == 0 ) {
+ Attribute *at;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ rc = slap_bv2ad( &sdn, &ad, &text );
+ assert( rc == LDAP_SUCCESS );
+
+ rc = 0;
+ for ( at = attrs_find( e->e_attrs, ad );
+ at != NULL;
+ at = attrs_find( at->a_next, ad ) )
+ {
+ if ( attr_valfind( at,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->o_ndn, NULL, op->o_tmpmemctx ) == 0 )
+ {
+ rc = 1;
+ break;
+ }
+ }
+
+ return rc;
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_GROUP ], &type ) == 0 ) {
+ struct berval oc,
+ at;
+
+ if ( BER_BVISNULL( &opts ) ) {
+ oc = aci_bv[ ACI_BV_GROUP_CLASS ];
+ at = aci_bv[ ACI_BV_GROUP_ATTR ];
+
+ } else {
+ if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
+ assert( 0 );
+ }
+
+ if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
+ at = aci_bv[ ACI_BV_GROUP_ATTR ];
+ }
+ }
+
+ if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
+ {
+ return 1;
+ }
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_ROLE ], &type ) == 0 ) {
+ struct berval oc,
+ at;
+
+ if ( BER_BVISNULL( &opts ) ) {
+ oc = aci_bv[ ACI_BV_ROLE_CLASS ];
+ at = aci_bv[ ACI_BV_ROLE_ATTR ];
+
+ } else {
+ if ( acl_get_part( &opts, 0, '/', &oc ) < 0 ) {
+ assert( 0 );
+ }
+
+ if ( acl_get_part( &opts, 1, '/', &at ) < 0 ) {
+ at = aci_bv[ ACI_BV_ROLE_ATTR ];
+ }
+ }
+
+ if ( aci_group_member( &sdn, &oc, &at, op, e, nmatch, matches ) )
+ {
+ return 1;
+ }
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET ], &type ) == 0 ) {
+ if ( acl_match_set( &sdn, op, e, NULL ) ) {
+ return 1;
+ }
+
+ } else if ( ber_bvcmp( &aci_bv[ ACI_BV_SET_REF ], &type ) == 0 ) {
+ if ( acl_match_set( &sdn, op, e, (struct berval *)&aci_bv[ ACI_BV_SET_ATTR ] ) ) {
+ return 1;
+ }
+
+ } else {
+ /* it passed normalization! */
+ assert( 0 );
+ }
+
+ return 0;
+}
+
+static int
+aci_init( void )
+{
+ /* OpenLDAP eXperimental Syntax */
+ static slap_syntax_defs_rec aci_syntax_def = {
+ "( 1.3.6.1.4.1.4203.666.2.1 DESC 'OpenLDAP Experimental ACI' )",
+ SLAP_SYNTAX_HIDE,
+ NULL,
+ OpenLDAPaciValidate,
+ OpenLDAPaciPretty
+ };
+ static slap_mrule_defs_rec aci_mr_def = {
+ "( 1.3.6.1.4.1.4203.666.4.2 NAME 'OpenLDAPaciMatch' "
+ "SYNTAX 1.3.6.1.4.1.4203.666.2.1 )",
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
+ NULL, OpenLDAPaciNormalize, OpenLDAPaciMatch,
+ NULL, NULL,
+ NULL
+ };
+ static struct {
+ char *name;
+ char *desc;
+ slap_mask_t flags;
+ AttributeDescription **ad;
+ } aci_at = {
+ "OpenLDAPaci", "( 1.3.6.1.4.1.4203.666.1.5 "
+ "NAME 'OpenLDAPaci' "
+ "DESC 'OpenLDAP access control information (experimental)' "
+ "EQUALITY OpenLDAPaciMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.2.1 "
+ "USAGE directoryOperation )",
+ SLAP_AT_HIDE,
+ &slap_ad_aci
+ };
+
+ int rc;
+
+ /* ACI syntax */
+ rc = register_syntax( &aci_syntax_def );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ /* ACI equality rule */
+ rc = register_matching_rule( &aci_mr_def );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ /* ACI attribute */
+ rc = register_at( aci_at.desc, aci_at.ad, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "aci_init: at_register failed\n" );
+ return rc;
+ }
+
+ /* install flags */
+ (*aci_at.ad)->ad_type->sat_flags |= aci_at.flags;
+
+ return rc;
+}
+
+static int
+dynacl_aci_parse(
+ ConfigArgs *c,
+ const char *opts,
+ slap_style_t sty,
+ const char *right,
+ void **privp )
+{
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in \"aci\" by clause",
+ style_strings[sty] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+
+ if ( right != NULL && *right != '\0' ) {
+ if ( slap_str2ad( right, &ad, &text ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "aci \"%s\": %s",
+ right, text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+
+ } else {
+ ad = slap_ad_aci;
+ }
+
+ if ( !is_at_syntax( ad->ad_type, SLAPD_ACI_SYNTAX) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "aci \"%s\": inappropriate syntax: %s",
+ right, ad->ad_type->sat_syntax_oid );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+
+ *privp = (void *)ad;
+
+ return 0;
+}
+
+static int
+dynacl_aci_unparse( void *priv, struct berval *bv )
+{
+ AttributeDescription *ad = ( AttributeDescription * )priv;
+ char *ptr;
+
+ assert( ad != NULL );
+
+ bv->bv_val = ch_malloc( STRLENOF(" aci=") + ad->ad_cname.bv_len + 1 );
+ ptr = lutil_strcopy( bv->bv_val, " aci=" );
+ ptr = lutil_strcopy( ptr, ad->ad_cname.bv_val );
+ bv->bv_len = ptr - bv->bv_val;
+
+ return 0;
+}
+
+static int
+dynacl_aci_mask(
+ void *priv,
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ int nmatch,
+ regmatch_t *matches,
+ slap_access_t *grantp,
+ slap_access_t *denyp )
+{
+ AttributeDescription *ad = ( AttributeDescription * )priv;
+ Attribute *at;
+ slap_access_t tgrant, tdeny, grant, deny;
+#ifdef LDAP_DEBUG
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+ char accessmaskbuf1[ACCESSMASK_MAXLEN];
+#endif /* LDAP_DEBUG */
+
+ if ( BER_BVISEMPTY( &e->e_nname ) ) {
+ /* no ACIs in the root DSE */
+ return -1;
+ }
+
+ /* start out with nothing granted, nothing denied */
+ ACL_INIT(tgrant);
+ ACL_INIT(tdeny);
+
+ /* get the aci attribute */
+ at = attr_find( e->e_attrs, ad );
+ if ( at != NULL ) {
+ int i;
+
+ /* the aci is an multi-valued attribute. The
+ * rights are determined by OR'ing the individual
+ * rights given by the acis.
+ */
+ for ( i = 0; !BER_BVISNULL( &at->a_nvals[i] ); i++ ) {
+ if ( aci_mask( op, e, desc, val, &at->a_nvals[i],
+ nmatch, matches, &grant, &deny,
+ SLAP_ACI_SCOPE_ENTRY ) != 0 )
+ {
+ tgrant |= grant;
+ tdeny |= deny;
+ }
+ }
+
+ Debug( LDAP_DEBUG_ACL, " <= aci_mask grant %s deny %s\n",
+ accessmask2str( tgrant, accessmaskbuf, 1 ),
+ accessmask2str( tdeny, accessmaskbuf1, 1 ) );
+ }
+
+ /* If the entry level aci didn't contain anything valid for the
+ * current operation, climb up the tree and evaluate the
+ * acis with scope set to subtree
+ */
+ if ( tgrant == ACL_PRIV_NONE && tdeny == ACL_PRIV_NONE ) {
+ struct berval parent_ndn;
+
+ dnParent( &e->e_nname, &parent_ndn );
+ while ( !BER_BVISEMPTY( &parent_ndn ) ){
+ int i;
+ BerVarray bvals = NULL;
+ int ret, stop;
+
+ /* to solve the chicken'n'egg problem of accessing
+ * the OpenLDAPaci attribute, the direct access
+ * to the entry's attribute is unchecked; however,
+ * further accesses to OpenLDAPaci values in the
+ * ancestors occur through backend_attribute(), i.e.
+ * with the identity of the operation, requiring
+ * further access checking. For uniformity, this
+ * makes further requests occur as the rootdn, if
+ * any, i.e. searching for the OpenLDAPaci attribute
+ * is considered an internal search. If this is not
+ * acceptable, then the same check needs be performed
+ * when accessing the entry's attribute. */
+ struct berval save_o_dn, save_o_ndn;
+
+ if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
+ save_o_dn = op->o_dn;
+ save_o_ndn = op->o_ndn;
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ }
+
+ Debug( LDAP_DEBUG_ACL, " checking ACI of \"%s\"\n", parent_ndn.bv_val );
+ ret = backend_attribute( op, NULL, &parent_ndn, ad, &bvals, ACL_AUTH );
+
+ if ( !BER_BVISNULL( &op->o_bd->be_rootndn ) ) {
+ op->o_dn = save_o_dn;
+ op->o_ndn = save_o_ndn;
+ }
+
+ switch ( ret ) {
+ case LDAP_SUCCESS :
+ stop = 0;
+ if ( !bvals ) {
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &bvals[i] ); i++ ) {
+ if ( aci_mask( op, e, desc, val,
+ &bvals[i],
+ nmatch, matches,
+ &grant, &deny,
+ SLAP_ACI_SCOPE_CHILDREN ) != 0 )
+ {
+ tgrant |= grant;
+ tdeny |= deny;
+ /* evaluation stops as soon as either a "deny" or a
+ * "grant" directive matches.
+ */
+ if ( tgrant != ACL_PRIV_NONE || tdeny != ACL_PRIV_NONE ) {
+ stop = 1;
+ }
+ }
+ Debug( LDAP_DEBUG_ACL, "<= aci_mask grant %s deny %s\n",
+ accessmask2str( tgrant, accessmaskbuf, 1 ),
+ accessmask2str( tdeny, accessmaskbuf1, 1 ) );
+ }
+ break;
+
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ /* just go on if the aci-Attribute is not present in
+ * the current entry
+ */
+ Debug( LDAP_DEBUG_ACL, "no such attribute\n" );
+ stop = 0;
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* We have reached the base object */
+ Debug( LDAP_DEBUG_ACL, "no such object\n" );
+ stop = 1;
+ break;
+
+ default:
+ stop = 1;
+ break;
+ }
+
+ if ( stop ) {
+ break;
+ }
+ dnParent( &parent_ndn, &parent_ndn );
+ }
+ }
+
+ *grantp = tgrant;
+ *denyp = tdeny;
+
+ return 0;
+}
+
+/* need to register this at some point */
+static slap_dynacl_t dynacl_aci = {
+ "aci",
+ dynacl_aci_parse,
+ dynacl_aci_unparse,
+ dynacl_aci_mask,
+ NULL,
+ NULL,
+ NULL
+};
+
+int
+dynacl_aci_init( void )
+{
+ int rc;
+
+ rc = aci_init();
+
+ if ( rc == 0 ) {
+ rc = slap_dynacl_register( &dynacl_aci );
+ }
+
+ return rc;
+}
+
+
+/* ACI syntax validation */
+
+/*
+ * Matches given berval to array of bervals
+ * Returns:
+ * >=0 if one if the array elements equals to this berval
+ * -1 if string was not found in array
+ */
+static int
+bv_getcaseidx(
+ struct berval *bv,
+ const struct berval *arr[] )
+{
+ int i;
+
+ if ( BER_BVISEMPTY( bv ) ) {
+ return -1;
+ }
+
+ for ( i = 0; arr[ i ] != NULL ; i++ ) {
+ if ( ber_bvstrcasecmp( bv, arr[ i ] ) == 0 ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+/* Returns what have left in input berval after current sub */
+static void
+bv_get_tail(
+ struct berval *val,
+ struct berval *sub,
+ struct berval *tail )
+{
+ int head_len;
+
+ tail->bv_val = sub->bv_val + sub->bv_len;
+ head_len = (unsigned long) tail->bv_val - (unsigned long) val->bv_val;
+ tail->bv_len = val->bv_len - head_len;
+}
+
+
+/*
+ * aci is accepted in following form:
+ * oid#scope#rights#type#subject
+ * Where:
+ * oid := numeric OID (currently ignored)
+ * scope := entry|children|subtree
+ * rights := right[[$right]...]
+ * right := (grant|deny);action
+ * action := perms;attrs[[;perms;attrs]...]
+ * perms := perm[[,perm]...]
+ * perm := c|s|r|w|x
+ * attrs := attribute[[,attribute]..]|"[all]"
+ * attribute := attributeType|attributeType=attributeValue|attributeType=attributeValuePrefix*
+ * type := public|users|self|dnattr|group|role|set|set-ref|
+ * access_id|subtree|onelevel|children
+ */
+static int
+OpenLDAPaciValidatePerms(
+ struct berval *perms )
+{
+ ber_len_t i;
+
+ for ( i = 0; i < perms->bv_len; ) {
+ switch ( perms->bv_val[ i ] ) {
+ case 'x':
+ case 'd':
+ case 'c':
+ case 's':
+ case 'r':
+ case 'w':
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ACL, "aciValidatePerms: perms needs to be one of x,d,c,s,r,w in '%s'\n", perms->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( ++i == perms->bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ while ( i < perms->bv_len && perms->bv_val[ i ] == ' ' )
+ i++;
+
+ assert( i != perms->bv_len );
+
+ if ( perms->bv_val[ i ] != ',' ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidatePerms: missing comma in '%s'\n", perms->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ do {
+ i++;
+ } while ( perms->bv_val[ i ] == ' ' );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static const struct berval *ACIgrantdeny[] = {
+ &aci_bv[ ACI_BV_GRANT ],
+ &aci_bv[ ACI_BV_DENY ],
+ NULL
+};
+
+static int
+OpenLDAPaciValidateRight(
+ struct berval *action )
+{
+ struct berval bv = BER_BVNULL;
+ int i;
+
+ /* grant|deny */
+ if ( acl_get_part( action, 0, ';', &bv ) < 0 ||
+ bv_getcaseidx( &bv, ACIgrantdeny ) == -1 )
+ {
+ Debug( LDAP_DEBUG_ACL, "aciValidateRight: '%s' must be either 'grant' or 'deny'\n", bv.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i = 0; acl_get_part( action, i + 1, ';', &bv ) >= 0; i++ ) {
+ if ( i & 1 ) {
+ /* perms */
+ if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else {
+ /* attr */
+ AttributeDescription *ad;
+ const char *text;
+ struct berval attr, left, right;
+ int j;
+
+ /* could be "[all]" or an attribute description */
+ if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
+ continue;
+ }
+
+
+ for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ )
+ {
+ ad = NULL;
+ text = NULL;
+ if ( acl_get_part( &attr, 0, '=', &left ) < 0
+ || acl_get_part( &attr, 1, '=', &right ) < 0 )
+ {
+ if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", attr.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ } else {
+ if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ACL, "aciValidateRight: unknown attribute: '%s'\n", left.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ }
+ }
+ }
+
+ /* "perms;attr" go in pairs */
+ if ( i > 0 && ( i & 1 ) == 0 ) {
+ return LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_ACL, "aciValidateRight: perms:attr need to be pairs in '%s'\n", action->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+OpenLDAPaciNormalizeRight(
+ struct berval *action,
+ struct berval *naction,
+ void *ctx )
+{
+ struct berval grantdeny,
+ perms = BER_BVNULL,
+ bv = BER_BVNULL;
+ int idx,
+ i;
+
+ /* grant|deny */
+ if ( acl_get_part( action, 0, ';', &grantdeny ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: missing ';' in '%s'\n", action->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ idx = bv_getcaseidx( &grantdeny, ACIgrantdeny );
+ if ( idx == -1 ) {
+ Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: '%s' must be grant or deny\n", grantdeny.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ber_dupbv_x( naction, (struct berval *)ACIgrantdeny[ idx ], ctx );
+
+ for ( i = 1; acl_get_part( action, i, ';', &bv ) >= 0; i++ ) {
+ struct berval nattrs = BER_BVNULL;
+ int freenattrs = 1;
+ if ( i & 1 ) {
+ /* perms */
+ if ( OpenLDAPaciValidatePerms( &bv ) != LDAP_SUCCESS )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+ perms = bv;
+
+ } else {
+ /* attr */
+ char *ptr;
+
+ /* could be "[all]" or an attribute description */
+ if ( ber_bvstrcasecmp( &bv, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
+ nattrs = aci_bv[ ACI_BV_BR_ALL ];
+ freenattrs = 0;
+
+ } else {
+ AttributeDescription *ad = NULL;
+ AttributeDescription adstatic= { 0 };
+ const char *text = NULL;
+ struct berval attr, left, right;
+ int j;
+ int len;
+
+ for ( j = 0; acl_get_part( &bv, j, ',', &attr ) >= 0; j++ )
+ {
+ ad = NULL;
+ text = NULL;
+ /* openldap 2.1 aci compatibility [entry] -> entry */
+ if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ENTRY ] ) == 0 ) {
+ ad = &adstatic;
+ adstatic.ad_cname = aci_bv[ ACI_BV_ENTRY ];
+
+ /* openldap 2.1 aci compatibility [children] -> children */
+ } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_CHILDREN ] ) == 0 ) {
+ ad = &adstatic;
+ adstatic.ad_cname = aci_bv[ ACI_BV_CHILDREN ];
+
+ /* openldap 2.1 aci compatibility [all] -> only [all] */
+ } else if ( ber_bvstrcasecmp( &attr, &aci_bv[ ACI_BV_BR_ALL ] ) == 0 ) {
+ ber_memfree_x( nattrs.bv_val, ctx );
+ nattrs = aci_bv[ ACI_BV_BR_ALL ];
+ freenattrs = 0;
+ break;
+
+ } else if ( acl_get_part( &attr, 0, '=', &left ) < 0
+ || acl_get_part( &attr, 1, '=', &right ) < 0 )
+ {
+ if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS )
+ {
+ ber_memfree_x( nattrs.bv_val, ctx );
+ Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", attr.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else {
+ if ( slap_bv2ad( &left, &ad, &text ) != LDAP_SUCCESS )
+ {
+ ber_memfree_x( nattrs.bv_val, ctx );
+ Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: unknown attribute: '%s'\n", left.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+
+ len = nattrs.bv_len + ( !BER_BVISEMPTY( &nattrs ) ? STRLENOF( "," ) : 0 )
+ + ad->ad_cname.bv_len;
+ nattrs.bv_val = slap_sl_realloc( nattrs.bv_val, len + 1, ctx );
+ ptr = &nattrs.bv_val[ nattrs.bv_len ];
+ if ( !BER_BVISEMPTY( &nattrs ) ) {
+ *ptr++ = ',';
+ }
+ ptr = lutil_strncopy( ptr, ad->ad_cname.bv_val, ad->ad_cname.bv_len );
+ ptr[ 0 ] = '\0';
+ nattrs.bv_len = len;
+ }
+
+ }
+
+ naction->bv_val = slap_sl_realloc( naction->bv_val,
+ naction->bv_len + STRLENOF( ";" )
+ + perms.bv_len + STRLENOF( ";" )
+ + nattrs.bv_len + 1,
+ ctx );
+
+ ptr = &naction->bv_val[ naction->bv_len ];
+ ptr[ 0 ] = ';';
+ ptr++;
+ ptr = lutil_strncopy( ptr, perms.bv_val, perms.bv_len );
+ ptr[ 0 ] = ';';
+ ptr++;
+ ptr = lutil_strncopy( ptr, nattrs.bv_val, nattrs.bv_len );
+ ptr[ 0 ] = '\0';
+ naction->bv_len += STRLENOF( ";" ) + perms.bv_len
+ + STRLENOF( ";" ) + nattrs.bv_len;
+ if ( freenattrs ) {
+ ber_memfree_x( nattrs.bv_val, ctx );
+ }
+ }
+ }
+
+ /* perms;attr go in pairs */
+ if ( i > 1 && ( i & 1 ) ) {
+ return LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_ACL, "aciNormalizeRight: perms:attr need to be pairs in '%s'\n", action->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+}
+
+static int
+OpenLDAPaciValidateRights(
+ struct berval *actions )
+
+{
+ struct berval bv = BER_BVNULL;
+ int i;
+
+ for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
+ if ( OpenLDAPaciValidateRight( &bv ) != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+OpenLDAPaciNormalizeRights(
+ struct berval *actions,
+ struct berval *nactions,
+ void *ctx )
+
+{
+ struct berval bv = BER_BVNULL;
+ int i;
+
+ BER_BVZERO( nactions );
+ for ( i = 0; acl_get_part( actions, i, '$', &bv ) >= 0; i++ ) {
+ int rc;
+ struct berval nbv;
+
+ rc = OpenLDAPaciNormalizeRight( &bv, &nbv, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ ber_memfree_x( nactions->bv_val, ctx );
+ BER_BVZERO( nactions );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( i == 0 ) {
+ *nactions = nbv;
+
+ } else {
+ nactions->bv_val = slap_sl_realloc( nactions->bv_val,
+ nactions->bv_len + STRLENOF( "$" )
+ + nbv.bv_len + 1,
+ ctx );
+ nactions->bv_val[ nactions->bv_len ] = '$';
+ AC_MEMCPY( &nactions->bv_val[ nactions->bv_len + 1 ],
+ nbv.bv_val, nbv.bv_len + 1 );
+ ber_memfree_x( nbv.bv_val, ctx );
+ nactions->bv_len += STRLENOF( "$" ) + nbv.bv_len;
+ }
+ BER_BVZERO( &nbv );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static const struct berval *OpenLDAPaciscopes[] = {
+ &aci_bv[ ACI_BV_ENTRY ],
+ &aci_bv[ ACI_BV_CHILDREN ],
+ &aci_bv[ ACI_BV_SUBTREE ],
+
+ NULL
+};
+
+static const struct berval *OpenLDAPacitypes[] = {
+ /* DN-valued */
+ &aci_bv[ ACI_BV_GROUP ],
+ &aci_bv[ ACI_BV_ROLE ],
+
+/* set to one past the last DN-valued type with options (/) */
+#define LAST_OPTIONAL 2
+
+ &aci_bv[ ACI_BV_ACCESS_ID ],
+ &aci_bv[ ACI_BV_SUBTREE ],
+ &aci_bv[ ACI_BV_ONELEVEL ],
+ &aci_bv[ ACI_BV_CHILDREN ],
+
+/* set to one past the last DN-valued type */
+#define LAST_DNVALUED 6
+
+ /* non DN-valued */
+ &aci_bv[ ACI_BV_DNATTR ],
+ &aci_bv[ ACI_BV_PUBLIC ],
+ &aci_bv[ ACI_BV_USERS ],
+ &aci_bv[ ACI_BV_SELF ],
+ &aci_bv[ ACI_BV_SET ],
+ &aci_bv[ ACI_BV_SET_REF ],
+
+ NULL
+};
+
+static int
+OpenLDAPaciValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ struct berval oid = BER_BVNULL,
+ scope = BER_BVNULL,
+ rights = BER_BVNULL,
+ type = BER_BVNULL,
+ subject = BER_BVNULL;
+ int idx;
+ int rc;
+
+ if ( BER_BVISEMPTY( val ) ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidatet: value is empty\n" );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* oid */
+ if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
+ numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
+ {
+ /* NOTE: the numericoidValidate() is rather pedantic;
+ * I'd replace it with X-ORDERED VALUES so that
+ * it's guaranteed values are maintained and used
+ * in the desired order */
+ Debug( LDAP_DEBUG_ACL, "aciValidate: invalid oid '%s'\n", oid.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* scope */
+ if ( acl_get_part( val, 1, '#', &scope ) < 0 ||
+ bv_getcaseidx( &scope, OpenLDAPaciscopes ) == -1 )
+ {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: invalid scope '%s'\n", scope.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* rights */
+ if ( acl_get_part( val, 2, '#', &rights ) < 0 ||
+ OpenLDAPaciValidateRights( &rights ) != LDAP_SUCCESS )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* type */
+ if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: missing type in '%s'\n", val->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ idx = bv_getcaseidx( &type, OpenLDAPacitypes );
+ if ( idx == -1 ) {
+ struct berval isgr;
+
+ if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", type.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
+ if ( idx == -1 || idx >= LAST_OPTIONAL ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: invalid type '%s'\n", isgr.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ /* subject */
+ bv_get_tail( val, &type, &subject );
+ if ( subject.bv_val[ 0 ] != '#' ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: missing subject in '%s'\n", val->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( idx >= LAST_DNVALUED ) {
+ if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ rc = slap_bv2ad( &subject, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: unknown dn attribute '%s'\n", subject.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
+ /* FIXME: allow nameAndOptionalUID? */
+ Debug( LDAP_DEBUG_ACL, "aciValidate: wrong syntax for dn attribute '%s'\n", subject.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ /* not a DN */
+ return LDAP_SUCCESS;
+
+ } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
+ || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
+ {
+ /* do {group|role}/oc/at check */
+ struct berval ocbv = BER_BVNULL,
+ atbv = BER_BVNULL;
+
+ ocbv.bv_val = ber_bvchr( &type, '/' );
+ if ( ocbv.bv_val != NULL ) {
+ ocbv.bv_val++;
+ ocbv.bv_len = type.bv_len
+ - ( ocbv.bv_val - type.bv_val );
+
+ atbv.bv_val = ber_bvchr( &ocbv, '/' );
+ if ( atbv.bv_val != NULL ) {
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+ int rc;
+
+ atbv.bv_val++;
+ atbv.bv_len = type.bv_len
+ - ( atbv.bv_val - type.bv_val );
+ ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
+
+ rc = slap_bv2ad( &atbv, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group attribute '%s'\n", atbv.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( oc_bvfind( &ocbv ) == NULL ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: unknown group '%s'\n", ocbv.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ }
+
+ if ( BER_BVISEMPTY( &subject ) ) {
+ /* empty DN invalid */
+ Debug( LDAP_DEBUG_ACL, "aciValidate: missing dn in '%s'\n", val->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ subject.bv_val++;
+ subject.bv_len--;
+
+ /* FIXME: pass DN syntax? */
+ rc = dnValidate( NULL, &subject );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ACL, "aciValidate: invalid dn '%s'\n", subject.bv_val );
+ }
+ return rc;
+}
+
+static int
+OpenLDAPaciPrettyNormal(
+ struct berval *val,
+ struct berval *out,
+ void *ctx,
+ int normalize )
+{
+ struct berval oid = BER_BVNULL,
+ scope = BER_BVNULL,
+ rights = BER_BVNULL,
+ nrights = BER_BVNULL,
+ type = BER_BVNULL,
+ ntype = BER_BVNULL,
+ subject = BER_BVNULL,
+ nsubject = BER_BVNULL;
+ int idx,
+ rc = LDAP_SUCCESS,
+ freesubject = 0,
+ freetype = 0;
+ char *ptr;
+
+ BER_BVZERO( out );
+
+ if ( BER_BVISEMPTY( val ) ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: value is empty\n" );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* oid: if valid, it's already normalized */
+ if ( acl_get_part( val, 0, '#', &oid ) < 0 ||
+ numericoidValidate( NULL, &oid ) != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid oid '%s'\n", oid.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* scope: normalize by replacing with OpenLDAPaciscopes */
+ if ( acl_get_part( val, 1, '#', &scope ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing scope in '%s'\n", val->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ idx = bv_getcaseidx( &scope, OpenLDAPaciscopes );
+ if ( idx == -1 ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid scope '%s'\n", scope.bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ scope = *OpenLDAPaciscopes[ idx ];
+
+ /* rights */
+ if ( acl_get_part( val, 2, '#', &rights ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing rights in '%s'\n", val->bv_val );
+ return LDAP_INVALID_SYNTAX;
+ }
+ if ( OpenLDAPaciNormalizeRights( &rights, &nrights, ctx )
+ != LDAP_SUCCESS )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* type */
+ if ( acl_get_part( val, 3, '#', &type ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing type in '%s'\n", val->bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+ idx = bv_getcaseidx( &type, OpenLDAPacitypes );
+ if ( idx == -1 ) {
+ struct berval isgr;
+
+ if ( acl_get_part( &type, 0, '/', &isgr ) < 0 ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", type.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ idx = bv_getcaseidx( &isgr, OpenLDAPacitypes );
+ if ( idx == -1 || idx >= LAST_OPTIONAL ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid type '%s'\n", isgr.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+ }
+ ntype = *OpenLDAPacitypes[ idx ];
+
+ /* subject */
+ bv_get_tail( val, &type, &subject );
+
+ if ( BER_BVISEMPTY( &subject ) || subject.bv_val[ 0 ] != '#' ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: missing subject in '%s'\n", val->bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ subject.bv_val++;
+ subject.bv_len--;
+
+ if ( idx < LAST_DNVALUED ) {
+ /* FIXME: pass DN syntax? */
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL,
+ &subject, &nsubject, ctx );
+ } else {
+ rc = dnPretty( NULL, &subject, &nsubject, ctx );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ freesubject = 1;
+
+ } else {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid subject dn '%s'\n", subject.bv_val );
+ goto cleanup;
+ }
+
+ if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_GROUP ]
+ || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_ROLE ] )
+ {
+ /* do {group|role}/oc/at check */
+ struct berval ocbv = BER_BVNULL,
+ atbv = BER_BVNULL;
+
+ ocbv.bv_val = ber_bvchr( &type, '/' );
+ if ( ocbv.bv_val != NULL ) {
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+ int rc;
+ struct berval bv;
+
+ bv.bv_len = ntype.bv_len;
+
+ ocbv.bv_val++;
+ ocbv.bv_len = type.bv_len - ( ocbv.bv_val - type.bv_val );
+
+ atbv.bv_val = ber_bvchr( &ocbv, '/' );
+ if ( atbv.bv_val != NULL ) {
+ atbv.bv_val++;
+ atbv.bv_len = type.bv_len
+ - ( atbv.bv_val - type.bv_val );
+ ocbv.bv_len = atbv.bv_val - ocbv.bv_val - 1;
+
+ rc = slap_bv2ad( &atbv, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown group attribute '%s'\n", atbv.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ bv.bv_len += STRLENOF( "/" ) + ad->ad_cname.bv_len;
+ }
+
+ oc = oc_bvfind( &ocbv );
+ if ( oc == NULL ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: invalid group '%s'\n", ocbv.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ bv.bv_len += STRLENOF( "/" ) + oc->soc_cname.bv_len;
+ bv.bv_val = slap_sl_malloc( bv.bv_len + 1, ctx );
+
+ ptr = bv.bv_val;
+ ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
+ ptr[ 0 ] = '/';
+ ptr++;
+ ptr = lutil_strncopy( ptr,
+ oc->soc_cname.bv_val,
+ oc->soc_cname.bv_len );
+ if ( ad != NULL ) {
+ ptr[ 0 ] = '/';
+ ptr++;
+ ptr = lutil_strncopy( ptr,
+ ad->ad_cname.bv_val,
+ ad->ad_cname.bv_len );
+ }
+ ptr[ 0 ] = '\0';
+
+ ntype = bv;
+ freetype = 1;
+ }
+ }
+
+ } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_DNATTR ] ) {
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+ int rc;
+
+ rc = slap_bv2ad( &subject, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: unknown dn attribute '%s'\n", subject.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
+ /* FIXME: allow nameAndOptionalUID? */
+ Debug( LDAP_DEBUG_ACL, "aciPrettyNormal: wrong syntax for dn attribute '%s'\n", subject.bv_val );
+ rc = LDAP_INVALID_SYNTAX;
+ goto cleanup;
+ }
+
+ nsubject = ad->ad_cname;
+
+ } else if ( OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET ]
+ || OpenLDAPacitypes[ idx ] == &aci_bv[ ACI_BV_SET_REF ] )
+ {
+ /* NOTE: dunno how to normalize it... */
+ nsubject = subject;
+ }
+
+
+ out->bv_len =
+ oid.bv_len + STRLENOF( "#" )
+ + scope.bv_len + STRLENOF( "#" )
+ + nrights.bv_len + STRLENOF( "#" )
+ + ntype.bv_len + STRLENOF( "#" )
+ + nsubject.bv_len;
+
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+ ptr = lutil_strncopy( out->bv_val, oid.bv_val, oid.bv_len );
+ ptr[ 0 ] = '#';
+ ptr++;
+ ptr = lutil_strncopy( ptr, scope.bv_val, scope.bv_len );
+ ptr[ 0 ] = '#';
+ ptr++;
+ ptr = lutil_strncopy( ptr, nrights.bv_val, nrights.bv_len );
+ ptr[ 0 ] = '#';
+ ptr++;
+ ptr = lutil_strncopy( ptr, ntype.bv_val, ntype.bv_len );
+ ptr[ 0 ] = '#';
+ ptr++;
+ if ( !BER_BVISNULL( &nsubject ) ) {
+ ptr = lutil_strncopy( ptr, nsubject.bv_val, nsubject.bv_len );
+ }
+ ptr[ 0 ] = '\0';
+
+cleanup:;
+ if ( freesubject ) {
+ ber_memfree_x( nsubject.bv_val, ctx );
+ }
+
+ if ( freetype ) {
+ ber_memfree_x( ntype.bv_val, ctx );
+ }
+
+ if ( !BER_BVISNULL( &nrights ) ) {
+ ber_memfree_x( nrights.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+static int
+OpenLDAPaciPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ return OpenLDAPaciPrettyNormal( val, out, ctx, 0 );
+}
+
+static int
+OpenLDAPaciNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ return OpenLDAPaciPrettyNormal( val, out, ctx, 1 );
+}
+
+#if SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC
+/*
+ * FIXME: need config and Makefile.am code to ease building
+ * as dynamic module
+ */
+int
+init_module( int argc, char *argv[] )
+{
+ return dynacl_aci_init();
+}
+#endif /* SLAPD_ACI_ENABLED == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_ACI_ENABLED */
+
diff --git a/servers/slapd/acl.c b/servers/slapd/acl.c
new file mode 100644
index 0000000..4cfa172
--- /dev/null
+++ b/servers/slapd/acl.c
@@ -0,0 +1,2687 @@
+/* acl.c - routines to parse and check acl's */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/regex.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "sets.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+
+#define ACL_BUF_SIZE 1024 /* use most appropriate size */
+
+static const struct berval acl_bv_ip_eq = BER_BVC( "IP=" );
+#ifdef LDAP_PF_INET6
+static const struct berval acl_bv_ipv6_eq = BER_BVC( "IP=[" );
+#endif /* LDAP_PF_INET6 */
+#ifdef LDAP_PF_LOCAL
+static const struct berval acl_bv_path_eq = BER_BVC("PATH=");
+#endif /* LDAP_PF_LOCAL */
+
+static AccessControl * slap_acl_get(
+ AccessControl *ac, int *count,
+ Operation *op, Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ AclRegexMatches *matches,
+ slap_mask_t *mask,
+ AccessControlState *state );
+
+static slap_control_t slap_acl_mask(
+ AccessControl *ac,
+ AccessControl *prev,
+ slap_mask_t *mask,
+ Operation *op, Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ AclRegexMatches *matches,
+ int count,
+ AccessControlState *state,
+ slap_access_t access );
+
+static int regex_matches(
+ struct berval *pat, char *str,
+ struct berval *dn_matches, struct berval *val_matches,
+ AclRegexMatches *matches);
+
+typedef struct AclSetCookie {
+ SetCookie asc_cookie;
+#define asc_op asc_cookie.set_op
+ Entry *asc_e;
+} AclSetCookie;
+
+
+SLAP_SET_GATHER acl_set_gather;
+SLAP_SET_GATHER acl_set_gather2;
+
+/*
+ * access_allowed - check whether op->o_ndn is allowed the requested access
+ * to entry e, attribute attr, value val. if val is null, access to
+ * the whole attribute is assumed (all values).
+ *
+ * This routine loops through all access controls and calls
+ * slap_acl_mask() on each applicable access control.
+ * The loop exits when a definitive answer is reached or
+ * or no more controls remain.
+ *
+ * returns:
+ * 0 access denied
+ * 1 access granted
+ *
+ * Notes:
+ * - can be legally called with op == NULL
+ * - can be legally called with op->o_bd == NULL
+ */
+
+int
+slap_access_always_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ assert( maskp != NULL );
+
+ /* assign all */
+ ACL_LVL_ASSIGN_MANAGE( *maskp );
+
+ return 1;
+}
+
+#define MATCHES_DNMAXCOUNT(m) \
+ ( sizeof ( (m)->dn_data ) / sizeof( *(m)->dn_data ) )
+#define MATCHES_VALMAXCOUNT(m) \
+ ( sizeof ( (m)->val_data ) / sizeof( *(m)->val_data ) )
+#define MATCHES_MEMSET(m) do { \
+ memset( (m)->dn_data, '\0', sizeof( (m)->dn_data ) ); \
+ memset( (m)->val_data, '\0', sizeof( (m)->val_data ) ); \
+ (m)->dn_count = MATCHES_DNMAXCOUNT( (m) ); \
+ (m)->val_count = MATCHES_VALMAXCOUNT( (m) ); \
+} while ( 0 /* CONSTCOND */ )
+
+int
+slap_access_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ int ret = 1;
+ int count;
+ AccessControl *a, *prev;
+
+#ifdef LDAP_DEBUG
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+#endif
+ slap_mask_t mask;
+ slap_control_t control;
+ slap_access_t access_level;
+ const char *attr;
+ AclRegexMatches matches;
+ AccessControlState acl_state = ACL_STATE_INIT;
+ static AccessControlState state_init = ACL_STATE_INIT;
+
+ assert( op != NULL );
+ assert( e != NULL );
+ assert( desc != NULL );
+ assert( maskp != NULL );
+
+ access_level = ACL_LEVEL( access );
+ attr = desc->ad_cname.bv_val;
+
+ assert( attr != NULL );
+
+ ACL_INIT( mask );
+
+ /* grant database root access */
+ if ( be_isroot( op ) ) {
+ Debug( LDAP_DEBUG_ACL, "<= root access granted\n" );
+ mask = ACL_LVL_MANAGE;
+ goto done;
+ }
+
+ /*
+ * no-user-modification operational attributes are ignored
+ * by ACL_WRITE checking as any found here are not provided
+ * by the user
+ *
+ * NOTE: but they are not ignored for ACL_MANAGE, because
+ * if we get here it means a non-root user is trying to
+ * manage data, so we need to check its privileges.
+ */
+ if ( access_level == ACL_WRITE_
+ && is_at_no_user_mod( desc->ad_type )
+ && desc != slap_schema.si_ad_entry
+ && desc != slap_schema.si_ad_children )
+ {
+ Debug( LDAP_DEBUG_ACL, "NoUserMod Operational attribute:"
+ " %s access granted\n",
+ attr );
+ goto done;
+ }
+
+ /* use backend default access if no backend acls */
+ if ( op->o_bd->be_acl == NULL && frontendDB->be_acl == NULL ) {
+ int i;
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> slap_access_allowed: backend default %s "
+ "access %s to \"%s\"\n",
+ access2str( access ),
+ op->o_bd->be_dfltaccess >= access_level ? "granted" : "denied",
+ op->o_dn.bv_val ? op->o_dn.bv_val : "(anonymous)" );
+ ret = op->o_bd->be_dfltaccess >= access_level;
+
+ mask = ACL_PRIV_LEVEL;
+ for ( i = ACL_NONE; i <= op->o_bd->be_dfltaccess; i++ ) {
+ ACL_PRIV_SET( mask, ACL_ACCESS2PRIV( i ) );
+ }
+
+ goto done;
+ }
+
+ ret = 0;
+ control = ACL_BREAK;
+
+ if ( state == NULL )
+ state = &acl_state;
+ if ( state->as_desc == desc &&
+ state->as_access == access &&
+ state->as_vd_acl_present )
+ {
+ a = state->as_vd_acl;
+ count = state->as_vd_acl_count;
+ if ( state->as_fe_done )
+ state->as_fe_done--;
+ ACL_PRIV_ASSIGN( mask, state->as_vd_mask );
+ } else {
+ *state = state_init;
+
+ a = NULL;
+ count = 0;
+ ACL_PRIV_ASSIGN( mask, *maskp );
+ }
+
+ MATCHES_MEMSET( &matches );
+ prev = a;
+
+ while ( ( a = slap_acl_get( a, &count, op, e, desc, val,
+ &matches, &mask, state ) ) != NULL )
+ {
+ int i;
+ int dnmaxcount = MATCHES_DNMAXCOUNT( &matches );
+ int valmaxcount = MATCHES_VALMAXCOUNT( &matches );
+ regmatch_t *dn_data = matches.dn_data;
+ regmatch_t *val_data = matches.val_data;
+
+ /* DN matches */
+ for ( i = 0; i < dnmaxcount && dn_data[i].rm_eo > 0; i++ ) {
+ char *data = e->e_ndn;
+
+ Debug( LDAP_DEBUG_ACL, "=> match[dn%d]: %d %d ", i,
+ (int)dn_data[i].rm_so,
+ (int)dn_data[i].rm_eo );
+ if ( dn_data[i].rm_so <= dn_data[0].rm_eo ) {
+ int n;
+ for ( n = dn_data[i].rm_so;
+ n < dn_data[i].rm_eo; n++ ) {
+ Debug( LDAP_DEBUG_ACL, "%c",
+ data[n] );
+ }
+ }
+ Debug( LDAP_DEBUG_ACL, "\n" );
+ }
+
+ /* val matches */
+ for ( i = 0; i < valmaxcount && val_data[i].rm_eo > 0; i++ ) {
+ char *data = val->bv_val;
+
+ Debug( LDAP_DEBUG_ACL, "=> match[val%d]: %d %d ", i,
+ (int)val_data[i].rm_so,
+ (int)val_data[i].rm_eo );
+ if ( val_data[i].rm_so <= val_data[0].rm_eo ) {
+ int n;
+ for ( n = val_data[i].rm_so;
+ n < val_data[i].rm_eo; n++ ) {
+ Debug( LDAP_DEBUG_ACL, "%c",
+ data[n] );
+ }
+ }
+ Debug( LDAP_DEBUG_ACL, "\n" );
+ }
+
+ control = slap_acl_mask( a, prev, &mask, op,
+ e, desc, val, &matches, count, state, access );
+
+ if ( control != ACL_BREAK ) {
+ break;
+ }
+
+ MATCHES_MEMSET( &matches );
+ prev = a;
+ }
+
+ if ( ACL_IS_INVALID( mask ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "=> slap_access_allowed: \"%s\" (%s) invalid!\n",
+ e->e_dn, attr );
+ ACL_PRIV_ASSIGN( mask, *maskp );
+
+ } else if ( control == ACL_BREAK ) {
+ Debug( LDAP_DEBUG_ACL,
+ "=> slap_access_allowed: no more rules\n" );
+
+ goto done;
+ }
+
+ ret = ACL_GRANT( mask, access );
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> slap_access_allowed: %s access %s by %s\n",
+ access2str( access ), ret ? "granted" : "denied",
+ accessmask2str( mask, accessmaskbuf, 1 ) );
+
+done:
+ ACL_PRIV_ASSIGN( *maskp, mask );
+ return ret;
+}
+
+int
+fe_access_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ BackendDB *be_orig;
+ int rc;
+
+ /*
+ * NOTE: control gets here if FIXME
+ * if an appropriate backend cannot be selected for the operation,
+ * we assume that the frontend should handle this
+ * FIXME: should select_backend() take care of this,
+ * and return frontendDB instead of NULL? maybe for some value
+ * of the flags?
+ */
+ be_orig = op->o_bd;
+
+ if ( op->o_bd == NULL ) {
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL )
+ op->o_bd = frontendDB;
+ }
+ rc = slap_access_allowed( op, e, desc, val, access, state, maskp );
+ op->o_bd = be_orig;
+
+ return rc;
+}
+
+int
+access_allowed_mask(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ int ret = 1;
+ int be_null = 0;
+
+#ifdef LDAP_DEBUG
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+#endif
+ slap_mask_t mask;
+ slap_access_t access_level;
+ const char *attr;
+
+ assert( e != NULL );
+ assert( desc != NULL );
+
+ access_level = ACL_LEVEL( access );
+
+ assert( access_level > ACL_NONE );
+
+ ACL_INIT( mask );
+ if ( maskp ) ACL_INVALIDATE( *maskp );
+
+ attr = desc->ad_cname.bv_val;
+
+ assert( attr != NULL );
+
+ if ( op ) {
+ if ( op->o_acl_priv != ACL_NONE ) {
+ access = op->o_acl_priv;
+
+ } else if ( op->o_is_auth_check &&
+ ( access_level == ACL_SEARCH || access_level == ACL_READ ) )
+ {
+ access = ACL_AUTH;
+
+ } else if ( get_relax( op ) && access_level == ACL_WRITE_ &&
+ desc == slap_schema.si_ad_entry )
+ {
+ access = ACL_MANAGE;
+ }
+ }
+
+ if ( state != NULL ) {
+ if ( state->as_desc == desc &&
+ state->as_access == access &&
+ state->as_result != -1 &&
+ !state->as_vd_acl_present )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: result was in cache (%s)\n",
+ attr );
+ return state->as_result;
+ } else {
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: result not in cache (%s)\n",
+ attr );
+ }
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: %s access to \"%s\" \"%s\" requested\n",
+ access2str( access ), e->e_dn, attr );
+
+ if ( op == NULL ) {
+ /* no-op call */
+ goto done;
+ }
+
+ if ( op->o_bd == NULL ) {
+ op->o_bd = LDAP_STAILQ_FIRST( &backendDB );
+ be_null = 1;
+
+ /* FIXME: experimental; use first backend rules
+ * iff there is no global_acl (ITS#3100)
+ */
+ if ( frontendDB->be_acl != NULL ) {
+ op->o_bd = frontendDB;
+ }
+ }
+ assert( op->o_bd != NULL );
+
+ /* this is enforced in backend_add() */
+ if ( op->o_bd->bd_info->bi_access_allowed ) {
+ /* delegate to backend */
+ ret = op->o_bd->bd_info->bi_access_allowed( op, e,
+ desc, val, access, state, &mask );
+
+ } else {
+ /* use default (but pass through frontend
+ * for global ACL overlays) */
+ ret = frontendDB->bd_info->bi_access_allowed( op, e,
+ desc, val, access, state, &mask );
+ }
+
+ if ( !ret ) {
+ if ( ACL_IS_INVALID( mask ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: \"%s\" (%s) invalid!\n",
+ e->e_dn, attr );
+ ACL_INIT( mask );
+
+ } else {
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: no more rules\n" );
+
+ goto done;
+ }
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: %s access %s by %s\n",
+ access2str( access ), ret ? "granted" : "denied",
+ accessmask2str( mask, accessmaskbuf, 1 ) );
+
+done:
+ if ( state != NULL ) {
+ state->as_access = access;
+ state->as_result = ret;
+ state->as_desc = desc;
+ }
+ if ( be_null ) op->o_bd = NULL;
+ if ( maskp ) ACL_PRIV_ASSIGN( *maskp, mask );
+ return ret;
+}
+
+
+/*
+ * slap_acl_get - return the acl applicable to entry e, attribute
+ * attr. the acl returned is suitable for use in subsequent calls to
+ * acl_access_allowed().
+ */
+
+static AccessControl *
+slap_acl_get(
+ AccessControl *a,
+ int *count,
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ AclRegexMatches *matches,
+ slap_mask_t *mask,
+ AccessControlState *state )
+{
+ const char *attr;
+ ber_len_t dnlen;
+ AccessControl *prev;
+
+ assert( e != NULL );
+ assert( count != NULL );
+ assert( desc != NULL );
+ assert( state != NULL );
+
+ attr = desc->ad_cname.bv_val;
+
+ assert( attr != NULL );
+
+ if( a == NULL ) {
+ if( op->o_bd == NULL || op->o_bd->be_acl == NULL ) {
+ a = frontendDB->be_acl;
+ } else {
+ a = op->o_bd->be_acl;
+ }
+ prev = NULL;
+
+ assert( a != NULL );
+ if ( a == frontendDB->be_acl )
+ state->as_fe_done = 1;
+ } else {
+ prev = a;
+ a = a->acl_next;
+ }
+
+ dnlen = e->e_nname.bv_len;
+
+ retry:
+ for ( ; a != NULL; prev = a, a = a->acl_next ) {
+ (*count) ++;
+
+ if ( a != frontendDB->be_acl && state->as_fe_done )
+ state->as_fe_done++;
+
+ if ( a->acl_dn_pat.bv_len || ( a->acl_dn_style != ACL_STYLE_REGEX )) {
+ if ( a->acl_dn_style == ACL_STYLE_REGEX ) {
+ Debug( LDAP_DEBUG_ACL, "=> dnpat: [%d] %s nsub: %d\n",
+ *count, a->acl_dn_pat.bv_val, (int) a->acl_dn_re.re_nsub );
+ if ( regexec ( &a->acl_dn_re,
+ e->e_ndn,
+ matches->dn_count,
+ matches->dn_data, 0 ) )
+ continue;
+
+ } else {
+ ber_len_t patlen;
+
+ Debug( LDAP_DEBUG_ACL, "=> dn: [%d] %s\n",
+ *count, a->acl_dn_pat.bv_val );
+ patlen = a->acl_dn_pat.bv_len;
+ if ( dnlen < patlen )
+ continue;
+
+ if ( a->acl_dn_style == ACL_STYLE_BASE ) {
+ /* base dn -- entire object DN must match */
+ if ( dnlen != patlen )
+ continue;
+
+ } else if ( a->acl_dn_style == ACL_STYLE_ONE ) {
+ ber_len_t rdnlen = 0;
+ ber_len_t sep = 0;
+
+ if ( dnlen <= patlen )
+ continue;
+
+ if ( patlen > 0 ) {
+ if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) )
+ continue;
+ sep = 1;
+ }
+
+ rdnlen = dn_rdnlen( NULL, &e->e_nname );
+ if ( rdnlen + patlen + sep != dnlen )
+ continue;
+
+ } else if ( a->acl_dn_style == ACL_STYLE_SUBTREE ) {
+ if ( dnlen > patlen && !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) )
+ continue;
+
+ } else if ( a->acl_dn_style == ACL_STYLE_CHILDREN ) {
+ if ( dnlen <= patlen )
+ continue;
+ if ( !DN_SEPARATOR( e->e_ndn[dnlen - patlen - 1] ) )
+ continue;
+ }
+
+ if ( strcmp( a->acl_dn_pat.bv_val, e->e_ndn + dnlen - patlen ) != 0 )
+ continue;
+ }
+
+ Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] matched\n",
+ *count );
+ }
+
+ if ( a->acl_attrs && !ad_inlist( desc, a->acl_attrs ) ) {
+ matches->dn_data[0].rm_so = -1;
+ matches->dn_data[0].rm_eo = -1;
+ matches->val_data[0].rm_so = -1;
+ matches->val_data[0].rm_eo = -1;
+ continue;
+ }
+
+ /* Is this ACL only for a specific value? */
+ if ( a->acl_attrval.bv_val ) {
+ if ( val == NULL ) {
+ continue;
+ }
+
+ if ( !state->as_vd_acl_present ) {
+ state->as_vd_acl_present = 1;
+ state->as_vd_acl = prev;
+ state->as_vd_acl_count = *count - 1;
+ ACL_PRIV_ASSIGN ( state->as_vd_mask, *mask );
+ }
+
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
+ Debug( LDAP_DEBUG_ACL,
+ "acl_get: valpat %s\n",
+ a->acl_attrval.bv_val );
+ if ( regexec ( &a->acl_attrval_re,
+ val->bv_val,
+ matches->val_count,
+ matches->val_data, 0 ) )
+ {
+ continue;
+ }
+
+ } else {
+ int match = 0;
+ const char *text;
+ Debug( LDAP_DEBUG_ACL,
+ "acl_get: val %s\n",
+ a->acl_attrval.bv_val );
+
+ if ( a->acl_attrs[0].an_desc->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) {
+ if (value_match( &match, desc,
+ a->acl_attrval_mr, 0,
+ val, &a->acl_attrval, &text ) != LDAP_SUCCESS ||
+ match )
+ continue;
+
+ } else {
+ ber_len_t patlen, vdnlen;
+
+ patlen = a->acl_attrval.bv_len;
+ vdnlen = val->bv_len;
+
+ if ( vdnlen < patlen )
+ continue;
+
+ if ( a->acl_attrval_style == ACL_STYLE_BASE ) {
+ if ( vdnlen > patlen )
+ continue;
+
+ } else if ( a->acl_attrval_style == ACL_STYLE_ONE ) {
+ ber_len_t rdnlen = 0;
+
+ if ( !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) )
+ continue;
+
+ rdnlen = dn_rdnlen( NULL, val );
+ if ( rdnlen + patlen + 1 != vdnlen )
+ continue;
+
+ } else if ( a->acl_attrval_style == ACL_STYLE_SUBTREE ) {
+ if ( vdnlen > patlen && !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) )
+ continue;
+
+ } else if ( a->acl_attrval_style == ACL_STYLE_CHILDREN ) {
+ if ( vdnlen <= patlen )
+ continue;
+
+ if ( !DN_SEPARATOR( val->bv_val[vdnlen - patlen - 1] ) )
+ continue;
+ }
+
+ if ( strcmp( a->acl_attrval.bv_val, val->bv_val + vdnlen - patlen ) )
+ continue;
+ }
+ }
+ }
+
+ if ( a->acl_filter != NULL ) {
+ ber_int_t rc = test_filter( NULL, e, a->acl_filter );
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ continue;
+ }
+ }
+
+ Debug( LDAP_DEBUG_ACL, "=> acl_get: [%d] attr %s\n",
+ *count, attr );
+ return a;
+ }
+
+ if ( !state->as_fe_done ) {
+ state->as_fe_done = 1;
+ a = frontendDB->be_acl;
+ goto retry;
+ }
+
+ Debug( LDAP_DEBUG_ACL, "<= acl_get: done.\n" );
+ return( NULL );
+}
+
+/*
+ * Record value-dependent access control state
+ */
+#define ACL_RECORD_VALUE_STATE do { \
+ if( state && !state->as_vd_acl_present ) { \
+ state->as_vd_acl_present = 1; \
+ state->as_vd_acl = prev; \
+ state->as_vd_acl_count = count - 1; \
+ ACL_PRIV_ASSIGN( state->as_vd_mask, *mask ); \
+ } \
+ } while( 0 )
+
+static int
+acl_mask_dn(
+ Operation *op,
+ Entry *e,
+ struct berval *val,
+ AccessControl *a,
+ AclRegexMatches *matches,
+ slap_dn_access *bdn,
+ struct berval *opndn )
+{
+ /*
+ * if access applies to the entry itself, and the
+ * user is bound as somebody in the same namespace as
+ * the entry, OR the given dn matches the dn pattern
+ */
+ /*
+ * NOTE: styles "anonymous", "users" and "self"
+ * have been moved to enum slap_style_t, whose
+ * value is set in a_dn_style; however, the string
+ * is maintained in a_dn_pat.
+ */
+
+ if ( bdn->a_style == ACL_STYLE_ANONYMOUS ) {
+ if ( !BER_BVISEMPTY( opndn ) ) {
+ return 1;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_USERS ) {
+ if ( BER_BVISEMPTY( opndn ) ) {
+ return 1;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_SELF ) {
+ struct berval ndn, selfndn;
+ int level;
+
+ if ( BER_BVISEMPTY( opndn ) || BER_BVISNULL( &e->e_nname ) ) {
+ return 1;
+ }
+
+ level = bdn->a_self_level;
+ if ( level < 0 ) {
+ selfndn = *opndn;
+ ndn = e->e_nname;
+ level = -level;
+
+ } else {
+ ndn = *opndn;
+ selfndn = e->e_nname;
+ }
+
+ for ( ; level > 0; level-- ) {
+ if ( BER_BVISEMPTY( &ndn ) ) {
+ break;
+ }
+ dnParent( &ndn, &ndn );
+ }
+
+ if ( BER_BVISEMPTY( &ndn ) || !dn_match( &ndn, &selfndn ) )
+ {
+ return 1;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_REGEX ) {
+ if ( !ber_bvccmp( &bdn->a_pat, '*' ) ) {
+ AclRegexMatches tmp_matches,
+ *tmp_matchesp = &tmp_matches;
+ int rc = 0;
+ regmatch_t *tmp_data;
+
+ MATCHES_MEMSET( &tmp_matches );
+ tmp_data = &tmp_matches.dn_data[0];
+
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX )
+ tmp_matchesp = matches;
+ else switch ( a->acl_dn_style ) {
+ case ACL_STYLE_REGEX:
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ tmp_matchesp = matches;
+ break;
+ }
+ /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */
+
+ case ACL_STYLE_BASE:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 1;
+ break;
+
+ case ACL_STYLE_ONE:
+ case ACL_STYLE_SUBTREE:
+ case ACL_STYLE_CHILDREN:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len;
+ tmp_data[1].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 2;
+ break;
+
+ default:
+ /* error */
+ rc = 1;
+ break;
+ }
+
+ if ( rc ) {
+ return 1;
+ }
+
+ if ( !regex_matches( &bdn->a_pat, opndn->bv_val,
+ &e->e_nname, NULL, tmp_matchesp ) )
+ {
+ return 1;
+ }
+ }
+
+ } else {
+ struct berval pat;
+ ber_len_t patlen, odnlen;
+ int got_match = 0;
+
+ if ( e->e_dn == NULL )
+ return 1;
+
+ if ( bdn->a_expand ) {
+ struct berval bv;
+ char buf[ACL_BUF_SIZE];
+
+ AclRegexMatches tmp_matches,
+ *tmp_matchesp = &tmp_matches;
+ int rc = 0;
+ regmatch_t *tmp_data;
+
+ MATCHES_MEMSET( &tmp_matches );
+ tmp_data = &tmp_matches.dn_data[0];
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = buf;
+
+ /* Expand value regex */
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX )
+ tmp_matchesp = matches;
+ else switch ( a->acl_dn_style ) {
+ case ACL_STYLE_REGEX:
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ tmp_matchesp = matches;
+ break;
+ }
+ /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */
+
+ case ACL_STYLE_BASE:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 1;
+ break;
+
+ case ACL_STYLE_ONE:
+ case ACL_STYLE_SUBTREE:
+ case ACL_STYLE_CHILDREN:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len;
+ tmp_data[1].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 2;
+ break;
+
+ default:
+ /* error */
+ rc = 1;
+ break;
+ }
+
+ if ( rc ) {
+ return 1;
+ }
+
+ if ( acl_string_expand( &bv, &bdn->a_pat,
+ &e->e_nname,
+ val, tmp_matchesp ) )
+ {
+ return 1;
+ }
+
+ if ( dnNormalize(0, NULL, NULL, &bv,
+ &pat, op->o_tmpmemctx )
+ != LDAP_SUCCESS )
+ {
+ /* did not expand to a valid dn */
+ return 1;
+ }
+
+ } else {
+ pat = bdn->a_pat;
+ }
+
+ patlen = pat.bv_len;
+ odnlen = opndn->bv_len;
+ if ( odnlen < patlen ) {
+ goto dn_match_cleanup;
+
+ }
+
+ if ( bdn->a_style == ACL_STYLE_BASE ) {
+ /* base dn -- entire object DN must match */
+ if ( odnlen != patlen ) {
+ goto dn_match_cleanup;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_ONE ) {
+ ber_len_t rdnlen = 0;
+
+ if ( odnlen <= patlen ) {
+ goto dn_match_cleanup;
+ }
+
+ if ( !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) {
+ goto dn_match_cleanup;
+ }
+
+ rdnlen = dn_rdnlen( NULL, opndn );
+ if ( rdnlen - ( odnlen - patlen - 1 ) != 0 ) {
+ goto dn_match_cleanup;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_SUBTREE ) {
+ if ( odnlen > patlen && !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) {
+ goto dn_match_cleanup;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_CHILDREN ) {
+ if ( odnlen <= patlen ) {
+ goto dn_match_cleanup;
+ }
+
+ if ( !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) ) {
+ goto dn_match_cleanup;
+ }
+
+ } else if ( bdn->a_style == ACL_STYLE_LEVEL ) {
+ int level = bdn->a_level;
+ struct berval ndn;
+
+ if ( odnlen <= patlen ) {
+ goto dn_match_cleanup;
+ }
+
+ if ( level > 0 && !DN_SEPARATOR( opndn->bv_val[odnlen - patlen - 1] ) )
+ {
+ goto dn_match_cleanup;
+ }
+
+ ndn = *opndn;
+ for ( ; level > 0; level-- ) {
+ if ( BER_BVISEMPTY( &ndn ) ) {
+ goto dn_match_cleanup;
+ }
+ dnParent( &ndn, &ndn );
+ if ( ndn.bv_len < patlen ) {
+ goto dn_match_cleanup;
+ }
+ }
+
+ if ( ndn.bv_len != patlen ) {
+ goto dn_match_cleanup;
+ }
+ }
+
+ got_match = !strcmp( pat.bv_val, &opndn->bv_val[ odnlen - patlen ] );
+
+dn_match_cleanup:;
+ if ( pat.bv_val != bdn->a_pat.bv_val ) {
+ slap_sl_free( pat.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( !got_match ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+acl_mask_dnattr(
+ Operation *op,
+ Entry *e,
+ struct berval *val,
+ AccessControl *a,
+ int count,
+ AccessControlState *state,
+ slap_mask_t *mask,
+ slap_dn_access *bdn,
+ struct berval *opndn )
+{
+ Attribute *at;
+ struct berval bv;
+ int rc, match = 0;
+ const char *text;
+ const char *attr = bdn->a_at->ad_cname.bv_val;
+
+ assert( attr != NULL );
+
+ if ( BER_BVISEMPTY( opndn ) ) {
+ return 1;
+ }
+
+ Debug( LDAP_DEBUG_ACL, "<= check a_dn_at: %s\n", attr );
+ bv = *opndn;
+
+ /* see if asker is listed in dnattr */
+ for ( at = attrs_find( e->e_attrs, bdn->a_at );
+ at != NULL;
+ at = attrs_find( at->a_next, bdn->a_at ) )
+ {
+ if ( attr_valfind( at,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &bv, NULL, op->o_tmpmemctx ) == 0 )
+ {
+ /* found it */
+ match = 1;
+ break;
+ }
+ }
+
+ if ( match ) {
+ /* have a dnattr match. if this is a self clause then
+ * the target must also match the op dn.
+ */
+ if ( bdn->a_self ) {
+ /* check if the target is an attribute. */
+ if ( val == NULL ) return 1;
+
+ /* target is attribute, check if the attribute value
+ * is the op dn.
+ */
+ rc = value_match( &match, bdn->a_at,
+ bdn->a_at->ad_type->sat_equality, 0,
+ val, &bv, &text );
+ /* on match error or no match, fail the ACL clause */
+ if ( rc != LDAP_SUCCESS || match != 0 )
+ return 1;
+ }
+
+ } else {
+ /* no dnattr match, check if this is a self clause */
+ if ( ! bdn->a_self )
+ return 1;
+
+ /* this is a self clause, check if the target is an
+ * attribute.
+ */
+ if ( val == NULL )
+ return 1;
+
+ /* target is attribute, check if the attribute value
+ * is the op dn.
+ */
+ rc = value_match( &match, bdn->a_at,
+ bdn->a_at->ad_type->sat_equality, 0,
+ val, &bv, &text );
+
+ /* on match error or no match, fail the ACL clause */
+ if ( rc != LDAP_SUCCESS || match != 0 )
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * slap_acl_mask - modifies mask based upon the given acl and the
+ * requested access to entry e, attribute attr, value val. if val
+ * is null, access to the whole attribute is assumed (all values).
+ *
+ * returns 0 access NOT allowed
+ * 1 access allowed
+ */
+
+static slap_control_t
+slap_acl_mask(
+ AccessControl *a,
+ AccessControl *prev,
+ slap_mask_t *mask,
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ AclRegexMatches *matches,
+ int count,
+ AccessControlState *state,
+ slap_access_t access )
+{
+ int i;
+ Access *b;
+#ifdef LDAP_DEBUG
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+#endif /* DEBUG */
+ const char *attr;
+#ifdef SLAP_DYNACL
+ slap_mask_t a2pmask = ACL_ACCESS2PRIV( access );
+#endif /* SLAP_DYNACL */
+
+ assert( a != NULL );
+ assert( mask != NULL );
+ assert( desc != NULL );
+
+ attr = desc->ad_cname.bv_val;
+
+ assert( attr != NULL );
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> acl_mask: access to entry \"%s\", attr \"%s\" requested\n",
+ e->e_dn, attr );
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> acl_mask: to %s by \"%s\", (%s) \n",
+ val ? "value" : "all values",
+ op->o_ndn.bv_val ? op->o_ndn.bv_val : "",
+ accessmask2str( *mask, accessmaskbuf, 1 ) );
+
+
+ b = a->acl_access;
+ i = 1;
+
+ for ( ; b != NULL; b = b->a_next, i++ ) {
+ slap_mask_t oldmask, modmask;
+
+ ACL_INVALIDATE( modmask );
+
+ /* check for the "self" modifier in the <access> field */
+ if ( b->a_dn.a_self ) {
+ const char *dummy;
+ int rc, match = 0;
+
+ ACL_RECORD_VALUE_STATE;
+
+ /* must have DN syntax */
+ if ( desc->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName &&
+ !is_at_syntax( desc->ad_type, SLAPD_NAMEUID_SYNTAX )) continue;
+
+ /* check if the target is an attribute. */
+ if ( val == NULL ) continue;
+
+ /* a DN must be present */
+ if ( BER_BVISEMPTY( &op->o_ndn ) ) {
+ continue;
+ }
+
+ /* target is attribute, check if the attribute value
+ * is the op dn.
+ */
+ rc = value_match( &match, desc,
+ desc->ad_type->sat_equality, 0,
+ val, &op->o_ndn, &dummy );
+ /* on match error or no match, fail the ACL clause */
+ if ( rc != LDAP_SUCCESS || match != 0 )
+ continue;
+ }
+
+ /* AND <who> clauses */
+ if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) {
+ Debug( LDAP_DEBUG_ACL, "<= check a_dn_pat: %s\n",
+ b->a_dn_pat.bv_val );
+ /*
+ * if access applies to the entry itself, and the
+ * user is bound as somebody in the same namespace as
+ * the entry, OR the given dn matches the dn pattern
+ */
+ /*
+ * NOTE: styles "anonymous", "users" and "self"
+ * have been moved to enum slap_style_t, whose
+ * value is set in a_dn_style; however, the string
+ * is maintained in a_dn_pat.
+ */
+
+ if ( acl_mask_dn( op, e, val, a, matches,
+ &b->a_dn, &op->o_ndn ) )
+ {
+ continue;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) {
+ struct berval ndn;
+
+ Debug( LDAP_DEBUG_ACL, "<= check a_realdn_pat: %s\n",
+ b->a_realdn_pat.bv_val );
+ /*
+ * if access applies to the entry itself, and the
+ * user is bound as somebody in the same namespace as
+ * the entry, OR the given dn matches the dn pattern
+ */
+ /*
+ * NOTE: styles "anonymous", "users" and "self"
+ * have been moved to enum slap_style_t, whose
+ * value is set in a_dn_style; however, the string
+ * is maintained in a_dn_pat.
+ */
+
+ if ( op->o_conn && !BER_BVISNULL( &op->o_conn->c_ndn ) )
+ {
+ ndn = op->o_conn->c_ndn;
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( acl_mask_dn( op, e, val, a, matches,
+ &b->a_realdn, &ndn ) )
+ {
+ continue;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
+ if ( ! op->o_conn->c_listener ) {
+ continue;
+ }
+ Debug( LDAP_DEBUG_ACL, "<= check a_sockurl_pat: %s\n",
+ b->a_sockurl_pat.bv_val );
+
+ if ( !ber_bvccmp( &b->a_sockurl_pat, '*' ) ) {
+ if ( b->a_sockurl_style == ACL_STYLE_REGEX) {
+ if ( !regex_matches( &b->a_sockurl_pat, op->o_conn->c_listener_url.bv_val,
+ &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ } else if ( b->a_sockurl_style == ACL_STYLE_EXPAND ) {
+ struct berval bv;
+ char buf[ACL_BUF_SIZE];
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = buf;
+ if ( acl_string_expand( &bv, &b->a_sockurl_pat, &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_listener_url ) != 0 )
+ {
+ continue;
+ }
+
+ } else {
+ if ( ber_bvstrcasecmp( &b->a_sockurl_pat, &op->o_conn->c_listener_url ) != 0 )
+ {
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
+ if ( !op->o_conn->c_peer_domain.bv_val ) {
+ continue;
+ }
+ Debug( LDAP_DEBUG_ACL, "<= check a_domain_pat: %s\n",
+ b->a_domain_pat.bv_val );
+ if ( !ber_bvccmp( &b->a_domain_pat, '*' ) ) {
+ if ( b->a_domain_style == ACL_STYLE_REGEX) {
+ if ( !regex_matches( &b->a_domain_pat, op->o_conn->c_peer_domain.bv_val,
+ &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+ } else {
+ char buf[ACL_BUF_SIZE];
+
+ struct berval cmp = op->o_conn->c_peer_domain;
+ struct berval pat = b->a_domain_pat;
+
+ if ( b->a_domain_expand ) {
+ struct berval bv;
+
+ bv.bv_len = sizeof(buf) - 1;
+ bv.bv_val = buf;
+
+ if ( acl_string_expand(&bv, &b->a_domain_pat, &e->e_nname, val, matches) )
+ {
+ continue;
+ }
+ pat = bv;
+ }
+
+ if ( b->a_domain_style == ACL_STYLE_SUBTREE ) {
+ int offset = cmp.bv_len - pat.bv_len;
+ if ( offset < 0 ) {
+ continue;
+ }
+
+ if ( offset == 1 || ( offset > 1 && cmp.bv_val[ offset - 1 ] != '.' ) ) {
+ continue;
+ }
+
+ /* trim the domain */
+ cmp.bv_val = &cmp.bv_val[ offset ];
+ cmp.bv_len -= offset;
+ }
+
+ if ( ber_bvstrcasecmp( &pat, &cmp ) != 0 ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
+ if ( !op->o_conn->c_peer_name.bv_val ) {
+ continue;
+ }
+ Debug( LDAP_DEBUG_ACL, "<= check a_peername_path: %s\n",
+ b->a_peername_pat.bv_val );
+ if ( !ber_bvccmp( &b->a_peername_pat, '*' ) ) {
+ if ( b->a_peername_style == ACL_STYLE_REGEX ) {
+ if ( !regex_matches( &b->a_peername_pat, op->o_conn->c_peer_name.bv_val,
+ &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ } else {
+ /* try exact match */
+ if ( b->a_peername_style == ACL_STYLE_BASE ) {
+ if ( ber_bvstrcasecmp( &b->a_peername_pat, &op->o_conn->c_peer_name ) != 0 ) {
+ continue;
+ }
+
+ } else if ( b->a_peername_style == ACL_STYLE_EXPAND ) {
+ struct berval bv;
+ char buf[ACL_BUF_SIZE];
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = buf;
+ if ( acl_string_expand( &bv, &b->a_peername_pat, &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_peer_name ) != 0 ) {
+ continue;
+ }
+
+ /* extract IP and try exact match */
+ } else if ( b->a_peername_style == ACL_STYLE_IP ) {
+ char *port;
+ char buf[STRLENOF("255.255.255.255") + 1];
+ struct berval ip;
+ unsigned long addr;
+ int port_number = -1;
+
+ if ( strncasecmp( op->o_conn->c_peer_name.bv_val,
+ acl_bv_ip_eq.bv_val,
+ acl_bv_ip_eq.bv_len ) != 0 )
+ continue;
+
+ ip.bv_val = op->o_conn->c_peer_name.bv_val + acl_bv_ip_eq.bv_len;
+ ip.bv_len = op->o_conn->c_peer_name.bv_len - acl_bv_ip_eq.bv_len;
+
+ port = strrchr( ip.bv_val, ':' );
+ if ( port ) {
+ ip.bv_len = port - ip.bv_val;
+ ++port;
+ if ( lutil_atoi( &port_number, port ) != 0 )
+ continue;
+ }
+
+ /* the port check can be anticipated here */
+ if ( b->a_peername_port != -1 && port_number != b->a_peername_port )
+ continue;
+
+ /* address longer than expected? */
+ if ( ip.bv_len >= sizeof(buf) )
+ continue;
+
+ AC_MEMCPY( buf, ip.bv_val, ip.bv_len );
+ buf[ ip.bv_len ] = '\0';
+
+ addr = inet_addr( buf );
+
+ /* unable to convert? */
+ if ( addr == (unsigned long)(-1) )
+ continue;
+
+ if ( (addr & b->a_peername_mask) != b->a_peername_addr )
+ continue;
+
+#ifdef LDAP_PF_INET6
+ /* extract IPv6 and try exact match */
+ } else if ( b->a_peername_style == ACL_STYLE_IPV6 ) {
+ char *port;
+ char buf[STRLENOF("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF") + 1];
+ struct berval ip;
+ struct in6_addr addr;
+ int port_number = -1;
+
+ if ( strncasecmp( op->o_conn->c_peer_name.bv_val,
+ acl_bv_ipv6_eq.bv_val,
+ acl_bv_ipv6_eq.bv_len ) != 0 )
+ continue;
+
+ ip.bv_val = op->o_conn->c_peer_name.bv_val + acl_bv_ipv6_eq.bv_len;
+ ip.bv_len = op->o_conn->c_peer_name.bv_len - acl_bv_ipv6_eq.bv_len;
+
+ port = strrchr( ip.bv_val, ']' );
+ if ( port ) {
+ ip.bv_len = port - ip.bv_val;
+ ++port;
+ if ( port[0] == ':' && lutil_atoi( &port_number, ++port ) != 0 )
+ continue;
+ }
+
+ /* the port check can be anticipated here */
+ if ( b->a_peername_port != -1 && port_number != b->a_peername_port )
+ continue;
+
+ /* address longer than expected? */
+ if ( ip.bv_len >= sizeof(buf) )
+ continue;
+
+ AC_MEMCPY( buf, ip.bv_val, ip.bv_len );
+ buf[ ip.bv_len ] = '\0';
+
+ if ( inet_pton( AF_INET6, buf, &addr ) != 1 )
+ continue;
+
+ /* check mask */
+ if ( !slap_addr6_mask( &addr, &b->a_peername_mask6, &b->a_peername_addr6 ) )
+ continue;
+#endif /* LDAP_PF_INET6 */
+
+#ifdef LDAP_PF_LOCAL
+ /* extract path and try exact match */
+ } else if ( b->a_peername_style == ACL_STYLE_PATH ) {
+ struct berval path;
+
+ if ( strncmp( op->o_conn->c_peer_name.bv_val,
+ acl_bv_path_eq.bv_val,
+ acl_bv_path_eq.bv_len ) != 0 )
+ continue;
+
+ path.bv_val = op->o_conn->c_peer_name.bv_val
+ + acl_bv_path_eq.bv_len;
+ path.bv_len = op->o_conn->c_peer_name.bv_len
+ - acl_bv_path_eq.bv_len;
+
+ if ( ber_bvcmp( &b->a_peername_pat, &path ) != 0 )
+ continue;
+
+#endif /* LDAP_PF_LOCAL */
+
+ /* exact match (very unlikely...) */
+ } else if ( ber_bvcmp( &op->o_conn->c_peer_name, &b->a_peername_pat ) != 0 ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) {
+ if ( BER_BVISNULL( &op->o_conn->c_sock_name ) ) {
+ continue;
+ }
+ Debug( LDAP_DEBUG_ACL, "<= check a_sockname_path: %s\n",
+ b->a_sockname_pat.bv_val );
+ if ( !ber_bvccmp( &b->a_sockname_pat, '*' ) ) {
+ if ( b->a_sockname_style == ACL_STYLE_REGEX) {
+ if ( !regex_matches( &b->a_sockname_pat, op->o_conn->c_sock_name.bv_val,
+ &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ } else if ( b->a_sockname_style == ACL_STYLE_EXPAND ) {
+ struct berval bv;
+ char buf[ACL_BUF_SIZE];
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = buf;
+ if ( acl_string_expand( &bv, &b->a_sockname_pat, &e->e_nname, val, matches ) )
+ {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &bv, &op->o_conn->c_sock_name ) != 0 ) {
+ continue;
+ }
+
+ } else {
+ if ( ber_bvstrcasecmp( &b->a_sockname_pat, &op->o_conn->c_sock_name ) != 0 ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( b->a_dn_at != NULL ) {
+ if ( acl_mask_dnattr( op, e, val, a,
+ count, state, mask,
+ &b->a_dn, &op->o_ndn ) )
+ {
+ continue;
+ }
+ }
+
+ if ( b->a_realdn_at != NULL ) {
+ struct berval ndn;
+
+ if ( op->o_conn && !BER_BVISNULL( &op->o_conn->c_ndn ) )
+ {
+ ndn = op->o_conn->c_ndn;
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( acl_mask_dnattr( op, e, val, a,
+ count, state, mask,
+ &b->a_realdn, &ndn ) )
+ {
+ continue;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
+ struct berval bv;
+ struct berval ndn = BER_BVNULL;
+ int rc;
+
+ if ( op->o_ndn.bv_len == 0 ) {
+ continue;
+ }
+
+ Debug( LDAP_DEBUG_ACL, "<= check a_group_pat: %s\n",
+ b->a_group_pat.bv_val );
+
+ /* b->a_group is an unexpanded entry name, expanded it should be an
+ * entry with objectclass group* and we test to see if odn is one of
+ * the values in the attribute group
+ */
+ /* see if asker is listed in dnattr */
+ if ( b->a_group_style == ACL_STYLE_EXPAND ) {
+ char buf[ACL_BUF_SIZE];
+ AclRegexMatches tmp_matches,
+ *tmp_matchesp = &tmp_matches;
+ regmatch_t *tmp_data;
+
+ MATCHES_MEMSET( &tmp_matches );
+ tmp_data = &tmp_matches.dn_data[0];
+
+ bv.bv_len = sizeof(buf) - 1;
+ bv.bv_val = buf;
+
+ rc = 0;
+
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX )
+ tmp_matchesp = matches;
+ else switch ( a->acl_dn_style ) {
+ case ACL_STYLE_REGEX:
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ tmp_matchesp = matches;
+ break;
+ }
+
+ /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */
+ case ACL_STYLE_BASE:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 1;
+ break;
+
+ case ACL_STYLE_ONE:
+ case ACL_STYLE_SUBTREE:
+ case ACL_STYLE_CHILDREN:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+
+ tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len;
+ tmp_data[1].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 2;
+ break;
+
+ default:
+ /* error */
+ rc = 1;
+ break;
+ }
+
+ if ( rc ) {
+ continue;
+ }
+
+ if ( acl_string_expand( &bv, &b->a_group_pat,
+ &e->e_nname, val,
+ tmp_matchesp ) )
+ {
+ continue;
+ }
+
+ if ( dnNormalize( 0, NULL, NULL, &bv, &ndn,
+ op->o_tmpmemctx ) != LDAP_SUCCESS )
+ {
+ /* did not expand to a valid dn */
+ continue;
+ }
+
+ bv = ndn;
+
+ } else {
+ bv = b->a_group_pat;
+ }
+
+ rc = backend_group( op, e, &bv, &op->o_ndn,
+ b->a_group_oc, b->a_group_at );
+
+ if ( ndn.bv_val ) {
+ slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( rc != 0 ) {
+ continue;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
+ struct berval bv;
+ char buf[ACL_BUF_SIZE];
+
+ Debug( LDAP_DEBUG_ACL, "<= check a_set_pat: %s\n",
+ b->a_set_pat.bv_val );
+
+ if ( b->a_set_style == ACL_STYLE_EXPAND ) {
+ AclRegexMatches tmp_matches,
+ *tmp_matchesp = &tmp_matches;
+ int rc = 0;
+ regmatch_t *tmp_data;
+
+ MATCHES_MEMSET( &tmp_matches );
+ tmp_data = &tmp_matches.dn_data[0];
+
+ bv.bv_len = sizeof( buf ) - 1;
+ bv.bv_val = buf;
+
+ rc = 0;
+
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX )
+ tmp_matchesp = matches;
+ else switch ( a->acl_dn_style ) {
+ case ACL_STYLE_REGEX:
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ tmp_matchesp = matches;
+ break;
+ }
+
+ /* FALLTHRU: applies also to ACL_STYLE_REGEX when pattern is "*" */
+ case ACL_STYLE_BASE:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_matches.dn_count = 1;
+ break;
+
+ case ACL_STYLE_ONE:
+ case ACL_STYLE_SUBTREE:
+ case ACL_STYLE_CHILDREN:
+ tmp_data[0].rm_so = 0;
+ tmp_data[0].rm_eo = e->e_nname.bv_len;
+ tmp_data[1].rm_so = e->e_nname.bv_len - a->acl_dn_pat.bv_len;
+ tmp_data[1].rm_eo = e->e_nname.bv_len; tmp_matches.dn_count = 2;
+ break;
+
+ default:
+ /* error */
+ rc = 1;
+ break;
+ }
+
+ if ( rc ) {
+ continue;
+ }
+
+ if ( acl_string_expand( &bv, &b->a_set_pat,
+ &e->e_nname, val,
+ tmp_matchesp ) )
+ {
+ continue;
+ }
+
+ } else {
+ bv = b->a_set_pat;
+ }
+
+ if ( acl_match_set( &bv, op, e, NULL ) == 0 ) {
+ continue;
+ }
+ }
+
+ if ( b->a_authz.sai_ssf ) {
+ Debug( LDAP_DEBUG_ACL, "<= check a_authz.sai_ssf: ACL %u > OP %u\n",
+ b->a_authz.sai_ssf, op->o_ssf );
+ if ( b->a_authz.sai_ssf > op->o_ssf ) {
+ continue;
+ }
+ }
+
+ if ( b->a_authz.sai_transport_ssf ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= check a_authz.sai_transport_ssf: ACL %u > OP %u\n",
+ b->a_authz.sai_transport_ssf, op->o_transport_ssf );
+ if ( b->a_authz.sai_transport_ssf > op->o_transport_ssf ) {
+ continue;
+ }
+ }
+
+ if ( b->a_authz.sai_tls_ssf ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= check a_authz.sai_tls_ssf: ACL %u > OP %u\n",
+ b->a_authz.sai_tls_ssf, op->o_tls_ssf );
+ if ( b->a_authz.sai_tls_ssf > op->o_tls_ssf ) {
+ continue;
+ }
+ }
+
+ if ( b->a_authz.sai_sasl_ssf ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= check a_authz.sai_sasl_ssf: ACL %u > OP %u\n",
+ b->a_authz.sai_sasl_ssf, op->o_sasl_ssf );
+ if ( b->a_authz.sai_sasl_ssf > op->o_sasl_ssf ) {
+ continue;
+ }
+ }
+
+#ifdef SLAP_DYNACL
+ if ( b->a_dynacl ) {
+ slap_dynacl_t *da;
+ slap_access_t tgrant, tdeny;
+
+ Debug( LDAP_DEBUG_ACL, "<= check a_dynacl\n" );
+
+ /* this case works different from the others above.
+ * since dynamic ACL's themselves give permissions, we need
+ * to first check b->a_access_mask, the ACL's access level.
+ */
+ /* first check if the right being requested
+ * is allowed by the ACL clause.
+ */
+ if ( ! ACL_PRIV_ISSET( b->a_access_mask, a2pmask ) ) {
+ continue;
+ }
+
+ /* start out with nothing granted, nothing denied */
+ ACL_INVALIDATE(tgrant);
+ ACL_INVALIDATE(tdeny);
+
+ for ( da = b->a_dynacl; da; da = da->da_next ) {
+ slap_access_t grant,
+ deny;
+
+ ACL_INVALIDATE(grant);
+ ACL_INVALIDATE(deny);
+
+ Debug( LDAP_DEBUG_ACL, " <= check a_dynacl: %s\n",
+ da->da_name );
+
+ /*
+ * XXXmanu Only DN matches are supplied
+ * sending attribute values matches require
+ * an API update
+ */
+ (void)da->da_mask( da->da_private, op, e, desc,
+ val, matches->dn_count, matches->dn_data,
+ &grant, &deny );
+
+ tgrant |= grant;
+ tdeny |= deny;
+ }
+
+ /* remove anything that the ACL clause does not allow */
+ tgrant &= b->a_access_mask & ACL_PRIV_MASK;
+ tdeny &= ACL_PRIV_MASK;
+
+ /* see if we have anything to contribute */
+ if( ACL_IS_INVALID(tgrant) && ACL_IS_INVALID(tdeny) ) {
+ continue;
+ }
+
+ /* this could be improved by changing slap_acl_mask so that it can deal with
+ * by clauses that return grant/deny pairs. Right now, it does either
+ * additive or subtractive rights, but not both at the same time. So,
+ * we need to combine the grant/deny pair into a single rights mask in
+ * a smart way: if either grant or deny is "empty", then we use the
+ * opposite as is, otherwise we remove any denied rights from the grant
+ * rights mask and construct an additive mask.
+ */
+ if (ACL_IS_INVALID(tdeny)) {
+ modmask = tgrant | ACL_PRIV_ADDITIVE;
+
+ } else if (ACL_IS_INVALID(tgrant)) {
+ modmask = tdeny | ACL_PRIV_SUBSTRACTIVE;
+
+ } else {
+ modmask = (tgrant & ~tdeny) | ACL_PRIV_ADDITIVE;
+ }
+
+ } else
+#endif /* SLAP_DYNACL */
+ {
+ modmask = b->a_access_mask;
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "<= acl_mask: [%d] applying %s (%s)\n",
+ i, accessmask2str( modmask, accessmaskbuf, 1 ),
+ b->a_type == ACL_CONTINUE
+ ? "continue"
+ : b->a_type == ACL_BREAK
+ ? "break"
+ : "stop" );
+ /* save old mask */
+ oldmask = *mask;
+
+ if( ACL_IS_ADDITIVE(modmask) ) {
+ /* add privs */
+ ACL_PRIV_SET( *mask, modmask );
+
+ /* cleanup */
+ ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK );
+
+ } else if( ACL_IS_SUBTRACTIVE(modmask) ) {
+ /* subtract privs */
+ ACL_PRIV_CLR( *mask, modmask );
+
+ /* cleanup */
+ ACL_PRIV_CLR( *mask, ~ACL_PRIV_MASK );
+
+ } else {
+ /* assign privs */
+ *mask = modmask;
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "<= acl_mask: [%d] mask: %s\n",
+ i, accessmask2str(*mask, accessmaskbuf, 1) );
+
+ if( b->a_type == ACL_CONTINUE ) {
+ continue;
+
+ } else if ( b->a_type == ACL_BREAK ) {
+ return ACL_BREAK;
+
+ } else {
+ return ACL_STOP;
+ }
+ }
+
+ /* implicit "by * none" clause */
+ ACL_INIT(*mask);
+
+ Debug( LDAP_DEBUG_ACL,
+ "<= acl_mask: no more <who> clauses, returning %s (stop)\n",
+ accessmask2str(*mask, accessmaskbuf, 1) );
+ return ACL_STOP;
+}
+
+/*
+ * acl_check_modlist - check access control on the given entry to see if
+ * it allows the given modifications by the user associated with op.
+ * returns 1 if mods allowed ok
+ * 0 mods not allowed
+ */
+
+int
+acl_check_modlist(
+ Operation *op,
+ Entry *e,
+ Modifications *mlist )
+{
+ struct berval *bv;
+ AccessControlState state = ACL_STATE_INIT;
+ Backend *be;
+ int be_null = 0;
+ int ret = 1; /* default is access allowed */
+
+ be = op->o_bd;
+ if ( be == NULL ) {
+ be = LDAP_STAILQ_FIRST(&backendDB);
+ be_null = 1;
+ op->o_bd = be;
+ }
+ assert( be != NULL );
+
+ /* If ADD attribute checking is not enabled, just allow it */
+ if ( op->o_tag == LDAP_REQ_ADD && !SLAP_DBACL_ADD( be ))
+ return 1;
+
+ /* short circuit root database access */
+ if ( be_isroot( op ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= acl_access_allowed: granted to database root\n" );
+ goto done;
+ }
+
+ /* use backend default access if no backend acls */
+ if( op->o_bd != NULL && op->o_bd->be_acl == NULL && frontendDB->be_acl == NULL ) {
+ Debug( LDAP_DEBUG_ACL,
+ "=> access_allowed: backend default %s access %s to \"%s\"\n",
+ access2str( ACL_WRITE ),
+ op->o_bd->be_dfltaccess >= ACL_WRITE
+ ? "granted" : "denied",
+ op->o_dn.bv_val );
+ ret = (op->o_bd->be_dfltaccess >= ACL_WRITE);
+ goto done;
+ }
+
+ for ( ; mlist != NULL; mlist = mlist->sml_next ) {
+ /*
+ * Internal mods are ignored by ACL_WRITE checking
+ */
+ if ( mlist->sml_flags & SLAP_MOD_INTERNAL ) {
+ Debug( LDAP_DEBUG_ACL, "acl: internal mod %s:"
+ " modify access granted\n",
+ mlist->sml_desc->ad_cname.bv_val );
+ continue;
+ }
+
+ /*
+ * no-user-modification operational attributes are ignored
+ * by ACL_WRITE checking as any found here are not provided
+ * by the user
+ */
+ if ( is_at_no_user_mod( mlist->sml_desc->ad_type )
+ && ! ( mlist->sml_flags & SLAP_MOD_MANAGING ) )
+ {
+ Debug( LDAP_DEBUG_ACL, "acl: no-user-mod %s:"
+ " modify access granted\n",
+ mlist->sml_desc->ad_cname.bv_val );
+ continue;
+ }
+
+ switch ( mlist->sml_op ) {
+ case LDAP_MOD_REPLACE:
+ case LDAP_MOD_INCREMENT:
+ /*
+ * We must check both permission to delete the whole
+ * attribute and permission to add the specific attributes.
+ * This prevents abuse from selfwriters.
+ */
+ if ( ! access_allowed( op, e,
+ mlist->sml_desc, NULL,
+ ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL,
+ &state ) )
+ {
+ ret = 0;
+ goto done;
+ }
+
+ if ( mlist->sml_values == NULL ) break;
+
+ /* fall thru to check value to add */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ assert( mlist->sml_values != NULL );
+
+ if ( mlist->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT
+ && attr_find( e->e_attrs, mlist->sml_desc ) )
+ {
+ break;
+ }
+
+ for ( bv = mlist->sml_nvalues
+ ? mlist->sml_nvalues : mlist->sml_values;
+ bv->bv_val != NULL; bv++ )
+ {
+ if ( ! access_allowed( op, e,
+ mlist->sml_desc, bv,
+ ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WADD,
+ &state ) )
+ {
+ ret = 0;
+ goto done;
+ }
+ }
+ break;
+
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL:
+ if ( mlist->sml_values == NULL ) {
+ if ( ! access_allowed( op, e,
+ mlist->sml_desc, NULL,
+ ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL,
+ &state ) )
+ {
+ ret = 0;
+ goto done;
+ }
+ break;
+ }
+ for ( bv = mlist->sml_nvalues
+ ? mlist->sml_nvalues : mlist->sml_values;
+ bv->bv_val != NULL; bv++ )
+ {
+ if ( ! access_allowed( op, e,
+ mlist->sml_desc, bv,
+ ( mlist->sml_flags & SLAP_MOD_MANAGING ) ? ACL_MANAGE : ACL_WDEL,
+ &state ) )
+ {
+ ret = 0;
+ goto done;
+ }
+ }
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ /* allow adding attribute via modrdn thru */
+ break;
+
+ default:
+ assert( 0 );
+ /* not reached */
+ ret = 0;
+ break;
+ }
+ }
+
+done:
+ if (be_null) op->o_bd = NULL;
+ return( ret );
+}
+
+int
+acl_get_part(
+ struct berval *list,
+ int ix,
+ char sep,
+ struct berval *bv )
+{
+ int len;
+ char *p;
+
+ if ( bv ) {
+ BER_BVZERO( bv );
+ }
+ len = list->bv_len;
+ p = list->bv_val;
+ while ( len >= 0 && --ix >= 0 ) {
+ while ( --len >= 0 && *p++ != sep )
+ ;
+ }
+ while ( len >= 0 && *p == ' ' ) {
+ len--;
+ p++;
+ }
+ if ( len < 0 ) {
+ return -1;
+ }
+
+ if ( !bv ) {
+ return 0;
+ }
+
+ bv->bv_val = p;
+ while ( --len >= 0 && *p != sep ) {
+ bv->bv_len++;
+ p++;
+ }
+ while ( bv->bv_len > 0 && *--p == ' ' ) {
+ bv->bv_len--;
+ }
+
+ return bv->bv_len;
+}
+
+typedef struct acl_set_gather_t {
+ SetCookie *cookie;
+ BerVarray bvals;
+} acl_set_gather_t;
+
+static int
+acl_set_cb_gather( Operation *op, SlapReply *rs )
+{
+ acl_set_gather_t *p = (acl_set_gather_t *)op->o_callback->sc_private;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ BerValue bvals[ 2 ];
+ BerVarray bvalsp = NULL;
+ int j;
+
+ for ( j = 0; !BER_BVISNULL( &rs->sr_attrs[ j ].an_name ); j++ ) {
+ AttributeDescription *desc = rs->sr_attrs[ j ].an_desc;
+
+ if ( desc == NULL ) {
+ continue;
+ }
+
+ if ( desc == slap_schema.si_ad_entryDN ) {
+ bvalsp = bvals;
+ bvals[ 0 ] = rs->sr_entry->e_nname;
+ BER_BVZERO( &bvals[ 1 ] );
+
+ } else {
+ Attribute *a;
+
+ a = attr_find( rs->sr_entry->e_attrs, desc );
+ if ( a != NULL ) {
+ bvalsp = a->a_nvals;
+ }
+ }
+
+ if ( bvalsp ) {
+ p->bvals = slap_set_join( p->cookie, p->bvals,
+ ( '|' | SLAP_SET_RREF ), bvalsp );
+ }
+ }
+
+ } else {
+ switch ( rs->sr_type ) {
+ case REP_SEARCHREF:
+ case REP_INTERMEDIATE:
+ /* ignore */
+ break;
+
+ default:
+ assert( rs->sr_type == REP_RESULT );
+ break;
+ }
+ }
+
+ return 0;
+}
+
+BerVarray
+acl_set_gather( SetCookie *cookie, struct berval *name, AttributeDescription *desc )
+{
+ AclSetCookie *cp = (AclSetCookie *)cookie;
+ int rc = 0;
+ LDAPURLDesc *ludp = NULL;
+ Operation op2 = { 0 };
+ SlapReply rs = {REP_RESULT};
+ AttributeName anlist[ 2 ], *anlistp = NULL;
+ int nattrs = 0;
+ slap_callback cb = { NULL, acl_set_cb_gather, NULL, NULL };
+ acl_set_gather_t p = { 0 };
+
+ /* this routine needs to return the bervals instead of
+ * plain strings, since syntax is not known. It should
+ * also return the syntax or some "comparison cookie".
+ */
+ if ( strncasecmp( name->bv_val, "ldap:///", STRLENOF( "ldap:///" ) ) != 0 ) {
+ return acl_set_gather2( cookie, name, desc );
+ }
+
+ rc = ldap_url_parse( name->bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s acl_set_gather: unable to parse URL=\"%s\"\n",
+ cp->asc_op->o_log_prefix, name->bv_val );
+
+ rc = LDAP_PROTOCOL_ERROR;
+ goto url_done;
+ }
+
+ if ( ( ludp->lud_host && ludp->lud_host[0] ) || ludp->lud_exts )
+ {
+ /* host part must be empty */
+ /* extensions parts must be empty */
+ Debug( LDAP_DEBUG_TRACE,
+ "%s acl_set_gather: host/exts must be absent in URL=\"%s\"\n",
+ cp->asc_op->o_log_prefix, name->bv_val );
+
+ rc = LDAP_PROTOCOL_ERROR;
+ goto url_done;
+ }
+
+ /* Grab the searchbase and see if an appropriate database can be found */
+ ber_str2bv( ludp->lud_dn, 0, 0, &op2.o_req_dn );
+ rc = dnNormalize( 0, NULL, NULL, &op2.o_req_dn,
+ &op2.o_req_ndn, cp->asc_op->o_tmpmemctx );
+ BER_BVZERO( &op2.o_req_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s acl_set_gather: DN=\"%s\" normalize failed\n",
+ cp->asc_op->o_log_prefix, ludp->lud_dn );
+
+ goto url_done;
+ }
+
+ op2.o_bd = select_backend( &op2.o_req_ndn, 1 );
+ if ( ( op2.o_bd == NULL ) || ( op2.o_bd->be_search == NULL ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s acl_set_gather: no database could be selected for DN=\"%s\"\n",
+ cp->asc_op->o_log_prefix, op2.o_req_ndn.bv_val );
+
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto url_done;
+ }
+
+ /* Grab the filter */
+ if ( ludp->lud_filter ) {
+ ber_str2bv_x( ludp->lud_filter, 0, 0, &op2.ors_filterstr,
+ cp->asc_op->o_tmpmemctx );
+ op2.ors_filter = str2filter_x( cp->asc_op, op2.ors_filterstr.bv_val );
+ if ( op2.ors_filter == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s acl_set_gather: unable to parse filter=\"%s\"\n",
+ cp->asc_op->o_log_prefix, op2.ors_filterstr.bv_val );
+
+ rc = LDAP_PROTOCOL_ERROR;
+ goto url_done;
+ }
+
+ } else {
+ op2.ors_filterstr = *slap_filterstr_objectClass_pres;
+ op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
+ }
+
+
+ /* Grab the scope */
+ op2.ors_scope = ludp->lud_scope;
+
+ /* Grap the attributes */
+ if ( ludp->lud_attrs ) {
+ int i;
+
+ for ( ; ludp->lud_attrs[ nattrs ]; nattrs++ )
+ ;
+
+ anlistp = slap_sl_calloc( sizeof( AttributeName ), nattrs + 2,
+ cp->asc_op->o_tmpmemctx );
+
+ for ( i = 0, nattrs = 0; ludp->lud_attrs[ i ]; i++ ) {
+ struct berval name;
+ AttributeDescription *desc = NULL;
+ const char *text = NULL;
+
+ ber_str2bv( ludp->lud_attrs[ i ], 0, 0, &name );
+ rc = slap_bv2ad( &name, &desc, &text );
+ if ( rc == LDAP_SUCCESS ) {
+ anlistp[ nattrs ].an_name = name;
+ anlistp[ nattrs ].an_desc = desc;
+ nattrs++;
+ }
+ }
+
+ } else {
+ anlistp = anlist;
+ }
+
+ anlistp[ nattrs ].an_name = desc->ad_cname;
+ anlistp[ nattrs ].an_desc = desc;
+
+ BER_BVZERO( &anlistp[ nattrs + 1 ].an_name );
+
+ p.cookie = cookie;
+
+ op2.o_hdr = cp->asc_op->o_hdr;
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_ndn = op2.o_bd->be_rootndn;
+ op2.o_callback = &cb;
+ slap_op_time( &op2.o_time, &op2.o_tincr );
+ op2.o_do_not_cache = 1;
+ op2.o_is_auth_check = 0;
+ ber_dupbv_x( &op2.o_req_dn, &op2.o_req_ndn, cp->asc_op->o_tmpmemctx );
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_attrs = anlistp;
+ op2.ors_attrsonly = 0;
+ op2.o_private = cp->asc_op->o_private;
+ op2.o_extra = cp->asc_op->o_extra;
+
+ cb.sc_private = &p;
+
+ rc = op2.o_bd->be_search( &op2, &rs );
+ if ( rc != 0 ) {
+ goto url_done;
+ }
+
+url_done:;
+ if ( op2.ors_filter && op2.ors_filter != slap_filter_objectClass_pres ) {
+ filter_free_x( cp->asc_op, op2.ors_filter, 1 );
+ }
+ if ( !BER_BVISNULL( &op2.o_req_ndn ) ) {
+ slap_sl_free( op2.o_req_ndn.bv_val, cp->asc_op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op2.o_req_dn ) ) {
+ slap_sl_free( op2.o_req_dn.bv_val, cp->asc_op->o_tmpmemctx );
+ }
+ if ( ludp ) {
+ ldap_free_urldesc( ludp );
+ }
+ if ( anlistp && anlistp != anlist ) {
+ slap_sl_free( anlistp, cp->asc_op->o_tmpmemctx );
+ }
+
+ return p.bvals;
+}
+
+BerVarray
+acl_set_gather2( SetCookie *cookie, struct berval *name, AttributeDescription *desc )
+{
+ AclSetCookie *cp = (AclSetCookie *)cookie;
+ BerVarray bvals = NULL;
+ struct berval ndn;
+ int rc = 0;
+
+ /* this routine needs to return the bervals instead of
+ * plain strings, since syntax is not known. It should
+ * also return the syntax or some "comparison cookie".
+ */
+ rc = dnNormalize( 0, NULL, NULL, name, &ndn, cp->asc_op->o_tmpmemctx );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( desc == slap_schema.si_ad_entryDN ) {
+ bvals = (BerVarray)slap_sl_malloc( sizeof( BerValue ) * 2,
+ cp->asc_op->o_tmpmemctx );
+ bvals[ 0 ] = ndn;
+ BER_BVZERO( &bvals[ 1 ] );
+ BER_BVZERO( &ndn );
+
+ } else {
+ backend_attribute( cp->asc_op,
+ cp->asc_e, &ndn, desc, &bvals, ACL_NONE );
+ }
+
+ if ( !BER_BVISNULL( &ndn ) ) {
+ slap_sl_free( ndn.bv_val, cp->asc_op->o_tmpmemctx );
+ }
+ }
+
+ return bvals;
+}
+
+int
+acl_match_set (
+ struct berval *subj,
+ Operation *op,
+ Entry *e,
+ struct berval *default_set_attribute )
+{
+ struct berval set = BER_BVNULL;
+ int rc = 0;
+ AclSetCookie cookie;
+
+ if ( default_set_attribute == NULL ) {
+ set = *subj;
+
+ } else {
+ struct berval subjdn, ndn = BER_BVNULL;
+ struct berval setat;
+ BerVarray bvals = NULL;
+ const char *text;
+ AttributeDescription *desc = NULL;
+
+ /* format of string is "entry/setAttrName" */
+ if ( acl_get_part( subj, 0, '/', &subjdn ) < 0 ) {
+ return 0;
+ }
+
+ if ( acl_get_part( subj, 1, '/', &setat ) < 0 ) {
+ setat = *default_set_attribute;
+ }
+
+ /*
+ * NOTE: dnNormalize honors the ber_len field
+ * as the length of the dn to be normalized
+ */
+ if ( slap_bv2ad( &setat, &desc, &text ) == LDAP_SUCCESS ) {
+ if ( dnNormalize( 0, NULL, NULL, &subjdn, &ndn, op->o_tmpmemctx ) == LDAP_SUCCESS )
+ {
+ backend_attribute( op, e, &ndn, desc, &bvals, ACL_NONE );
+ if ( bvals != NULL && !BER_BVISNULL( &bvals[0] ) ) {
+ int i;
+
+ set = bvals[0];
+ BER_BVZERO( &bvals[0] );
+ for ( i = 1; !BER_BVISNULL( &bvals[i] ); i++ )
+ /* count */ ;
+ bvals[0].bv_val = bvals[i-1].bv_val;
+ BER_BVZERO( &bvals[i-1] );
+ }
+ ber_bvarray_free_x( bvals, op->o_tmpmemctx );
+ slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &set ) ) {
+ cookie.asc_op = op;
+ cookie.asc_e = e;
+ rc = ( slap_set_filter(
+ acl_set_gather,
+ (SetCookie *)&cookie, &set,
+ &op->o_ndn, &e->e_nname, NULL ) > 0 );
+ if ( set.bv_val != subj->bv_val ) {
+ slap_sl_free( set.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ return(rc);
+}
+
+#ifdef SLAP_DYNACL
+
+/*
+ * dynamic ACL infrastructure
+ */
+static slap_dynacl_t *da_list = NULL;
+
+int
+slap_dynacl_register( slap_dynacl_t *da )
+{
+ slap_dynacl_t *tmp;
+
+ for ( tmp = da_list; tmp; tmp = tmp->da_next ) {
+ if ( strcasecmp( da->da_name, tmp->da_name ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( tmp != NULL ) {
+ return -1;
+ }
+
+ if ( da->da_mask == NULL ) {
+ return -1;
+ }
+
+ da->da_private = NULL;
+ da->da_next = da_list;
+ da_list = da;
+
+ return 0;
+}
+
+static slap_dynacl_t *
+slap_dynacl_next( slap_dynacl_t *da )
+{
+ if ( da ) {
+ return da->da_next;
+ }
+ return da_list;
+}
+
+slap_dynacl_t *
+slap_dynacl_get( const char *name )
+{
+ slap_dynacl_t *da;
+
+ for ( da = slap_dynacl_next( NULL ); da; da = slap_dynacl_next( da ) ) {
+ if ( strcasecmp( da->da_name, name ) == 0 ) {
+ break;
+ }
+ }
+
+ return da;
+}
+#endif /* SLAP_DYNACL */
+
+/*
+ * statically built-in dynamic ACL initialization
+ */
+static int (*acl_init_func[])( void ) = {
+#ifdef SLAP_DYNACL
+ /* TODO: remove when ACI will only be dynamic */
+#if SLAPD_ACI_ENABLED == SLAPD_MOD_STATIC
+ dynacl_aci_init,
+#endif /* SLAPD_ACI_ENABLED */
+#endif /* SLAP_DYNACL */
+
+ NULL
+};
+
+int
+acl_init( void )
+{
+ int i, rc;
+
+ for ( i = 0; acl_init_func[ i ] != NULL; i++ ) {
+ rc = (*(acl_init_func[ i ]))();
+ if ( rc != 0 ) {
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int
+acl_string_expand(
+ struct berval *bv,
+ struct berval *pat,
+ struct berval *dn_matches,
+ struct berval *val_matches,
+ AclRegexMatches *matches)
+{
+ ber_len_t size;
+ char *sp;
+ char *dp;
+ int flag;
+ enum { DN_FLAG, VAL_FLAG } tflag;
+
+ size = 0;
+ bv->bv_val[0] = '\0';
+ bv->bv_len--; /* leave space for lone $ */
+
+ flag = 0;
+ tflag = DN_FLAG;
+ for ( dp = bv->bv_val, sp = pat->bv_val; size < bv->bv_len &&
+ sp < pat->bv_val + pat->bv_len ; sp++ )
+ {
+ /* did we previously see a $ */
+ if ( flag ) {
+ if ( flag == 1 && *sp == '$' ) {
+ *dp++ = '$';
+ size++;
+ flag = 0;
+ tflag = DN_FLAG;
+
+ } else if ( flag == 2 && *sp == 'v' /*'}'*/) {
+ tflag = VAL_FLAG;
+
+ } else if ( flag == 2 && *sp == 'd' /*'}'*/) {
+ tflag = DN_FLAG;
+
+ } else if ( flag == 1 && *sp == '{' /*'}'*/) {
+ flag = 2;
+
+ } else if ( *sp >= '0' && *sp <= '9' ) {
+ int nm;
+ regmatch_t *m;
+ char *data;
+ int n;
+ int i;
+ int l;
+
+ n = *sp - '0';
+
+ if ( flag == 2 ) {
+ for ( sp++; *sp != '\0' && *sp != /*'{'*/ '}'; sp++ ) {
+ if ( *sp >= '0' && *sp <= '9' ) {
+ n = 10*n + ( *sp - '0' );
+ }
+ }
+
+ if ( *sp != /*'{'*/ '}' ) {
+ /* FIXME: error */
+ return 1;
+ }
+ }
+
+ switch (tflag) {
+ case DN_FLAG:
+ nm = matches->dn_count;
+ m = matches->dn_data;
+ data = dn_matches ? dn_matches->bv_val : NULL;
+ break;
+ case VAL_FLAG:
+ nm = matches->val_count;
+ m = matches->val_data;
+ data = val_matches ? val_matches->bv_val : NULL;
+ break;
+ default:
+ assert( 0 );
+ }
+ if ( n >= nm ) {
+ /* FIXME: error */
+ return 1;
+ }
+ if ( data == NULL ) {
+ /* FIXME: error */
+ return 1;
+ }
+
+ *dp = '\0';
+ i = m[n].rm_so;
+ l = m[n].rm_eo;
+
+ for ( ; size < bv->bv_len && i < l; size++, i++ ) {
+ *dp++ = data[i];
+ }
+ *dp = '\0';
+
+ flag = 0;
+ tflag = DN_FLAG;
+ }
+ } else {
+ if (*sp == '$') {
+ flag = 1;
+ } else {
+ *dp++ = *sp;
+ size++;
+ }
+ }
+ }
+
+ if ( flag ) {
+ /* must have ended with a single $ */
+ *dp++ = '$';
+ size++;
+ }
+
+ *dp = '\0';
+ bv->bv_len = size;
+
+ Debug( LDAP_DEBUG_ACL, "=> acl_string_expand: pattern: %.*s\n", (int)pat->bv_len, pat->bv_val );
+ Debug( LDAP_DEBUG_ACL, "=> acl_string_expand: expanded: %s\n", bv->bv_val );
+
+ return 0;
+}
+
+static int
+regex_matches(
+ struct berval *pat, /* pattern to expand and match against */
+ char *str, /* string to match against pattern */
+ struct berval *dn_matches, /* buffer with $N expansion variables from DN */
+ struct berval *val_matches, /* buffer with $N expansion variables from val */
+ AclRegexMatches *matches /* offsets in buffer for $N expansion variables */
+)
+{
+ regex_t re;
+ char newbuf[ACL_BUF_SIZE];
+ struct berval bv;
+ int rc;
+
+ bv.bv_len = sizeof( newbuf ) - 1;
+ bv.bv_val = newbuf;
+
+ if (str == NULL) {
+ str = "";
+ };
+
+ if ( acl_string_expand( &bv, pat, dn_matches, val_matches, matches )) {
+ Debug( LDAP_DEBUG_TRACE,
+ "expand( \"%s\", \"%s\") failed\n",
+ pat->bv_val, str );
+ return( 0 );
+ }
+ rc = regcomp( &re, newbuf, REG_EXTENDED|REG_ICASE );
+ if ( rc ) {
+ char error[ACL_BUF_SIZE];
+ regerror( rc, &re, error, sizeof( error ) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "compile( \"%s\", \"%s\") failed %s\n",
+ pat->bv_val, str, error );
+ return( 0 );
+ }
+
+ rc = regexec( &re, str, 0, NULL, 0 );
+ regfree( &re );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> regex_matches: string: %s\n", str );
+ Debug( LDAP_DEBUG_TRACE,
+ "=> regex_matches: rc: %d %s\n",
+ rc, !rc ? "matches" : "no matches" );
+ return( !rc );
+}
+
diff --git a/servers/slapd/aclparse.c b/servers/slapd/aclparse.c
new file mode 100644
index 0000000..f08de6a
--- /dev/null
+++ b/servers/slapd/aclparse.c
@@ -0,0 +1,2783 @@
+/* aclparse.c - routines to parse and check acl's */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/regex.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+static const char style_base[] = "base";
+const char *style_strings[] = {
+ "regex",
+ "expand",
+ "exact",
+ "one",
+ "subtree",
+ "children",
+ "level",
+ "attrof",
+ "anonymous",
+ "users",
+ "self",
+ "ip",
+ "ipv6",
+ "path",
+ NULL
+};
+
+#define ACLBUF_CHUNKSIZE 8192
+static struct berval aclbuf;
+
+static void split(char *line, int splitchar, char **left, char **right);
+static void access_append(Access **l, Access *a);
+static void access_free( Access *a );
+static int acl_usage(void);
+
+static void acl_regex_normalized_dn(const char *src, struct berval *pat);
+
+#ifdef LDAP_DEBUG
+static void print_acl(Backend *be, AccessControl *a);
+#endif
+
+static int check_scope( BackendDB *be, AccessControl *a );
+
+#ifdef SLAP_DYNACL
+static int
+slap_dynacl_config(
+ struct config_args_s *c,
+ Access *b,
+ const char *name,
+ const char *opts,
+ slap_style_t sty,
+ const char *right )
+{
+ slap_dynacl_t *da, *tmp;
+ int rc = 0;
+
+ for ( da = b->a_dynacl; da; da = da->da_next ) {
+ if ( strcasecmp( da->da_name, name ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dynacl \"%s\" already specified",
+ name );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return acl_usage();
+ }
+ }
+
+ da = slap_dynacl_get( name );
+ if ( da == NULL ) {
+ return -1;
+ }
+
+ tmp = ch_malloc( sizeof( slap_dynacl_t ) );
+ *tmp = *da;
+
+ if ( tmp->da_parse ) {
+ rc = ( *tmp->da_parse )( c, opts, sty, right, &tmp->da_private );
+ if ( rc ) {
+ ch_free( tmp );
+ return rc;
+ }
+ }
+
+ tmp->da_next = b->a_dynacl;
+ b->a_dynacl = tmp;
+
+ return 0;
+}
+#endif /* SLAP_DYNACL */
+
+static int
+regtest(struct config_args_s *c, char *pat) {
+ int e;
+ regex_t re;
+
+ char buf[ SLAP_TEXT_BUFLEN ];
+ unsigned size;
+
+ char *sp;
+ char *dp;
+ int flag;
+
+ sp = pat;
+ dp = buf;
+ size = 0;
+ buf[0] = '\0';
+
+ for (size = 0, flag = 0; (size < sizeof(buf)) && *sp; sp++) {
+ if (flag) {
+ if (*sp == '$'|| (*sp >= '0' && *sp <= '9')) {
+ *dp++ = *sp;
+ size++;
+ }
+ flag = 0;
+
+ } else {
+ if (*sp == '$') {
+ flag = 1;
+ } else {
+ *dp++ = *sp;
+ size++;
+ }
+ }
+ }
+
+ *dp = '\0';
+ if ( size >= (sizeof(buf) - 1) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "regular expression too large \"%s\"", pat);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ (void)acl_usage();
+ return -1;
+ }
+
+ if ( (e = regcomp(&re, buf, REG_EXTENDED|REG_ICASE)) ) {
+ char error[ SLAP_TEXT_BUFLEN ];
+
+ regerror(e, &re, error, sizeof(error));
+
+ snprintf( c->cr_msg, sizeof ( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s", pat, error);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ acl_usage();
+ regfree(&re);
+ return -1;
+ }
+ regfree(&re);
+ return 0;
+}
+
+/*
+ * Experimental
+ *
+ * Check if the pattern of an ACL, if any, matches the scope
+ * of the backend it is defined within.
+ */
+#define ACL_SCOPE_UNKNOWN (-2)
+#define ACL_SCOPE_ERR (-1)
+#define ACL_SCOPE_OK (0)
+#define ACL_SCOPE_PARTIAL (1)
+#define ACL_SCOPE_WARN (2)
+
+static int
+check_scope( BackendDB *be, AccessControl *a )
+{
+ ber_len_t patlen;
+ struct berval dn;
+
+ dn = be->be_nsuffix[0];
+
+ if ( BER_BVISEMPTY( &dn ) ) {
+ return ACL_SCOPE_OK;
+ }
+
+ if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
+ a->acl_dn_style != ACL_STYLE_REGEX )
+ {
+ slap_style_t style = a->acl_dn_style;
+
+ if ( style == ACL_STYLE_REGEX ) {
+ char dnbuf[SLAP_LDAPDN_MAXLEN + 2];
+ char rebuf[SLAP_LDAPDN_MAXLEN + 1];
+ ber_len_t rebuflen;
+ regex_t re;
+ int rc;
+
+ /* add trailing '$' to database suffix to form
+ * a simple trial regex pattern "<suffix>$" */
+ AC_MEMCPY( dnbuf, be->be_nsuffix[0].bv_val,
+ be->be_nsuffix[0].bv_len );
+ dnbuf[be->be_nsuffix[0].bv_len] = '$';
+ dnbuf[be->be_nsuffix[0].bv_len + 1] = '\0';
+
+ if ( regcomp( &re, dnbuf, REG_EXTENDED|REG_ICASE ) ) {
+ return ACL_SCOPE_WARN;
+ }
+
+ /* remove trailing ')$', if any, from original
+ * regex pattern */
+ rebuflen = a->acl_dn_pat.bv_len;
+ AC_MEMCPY( rebuf, a->acl_dn_pat.bv_val, rebuflen + 1 );
+ if ( rebuf[rebuflen - 1] == '$' ) {
+ rebuf[--rebuflen] = '\0';
+ }
+ while ( rebuflen > be->be_nsuffix[0].bv_len && rebuf[rebuflen - 1] == ')' ) {
+ rebuf[--rebuflen] = '\0';
+ }
+ if ( rebuflen == be->be_nsuffix[0].bv_len ) {
+ rc = ACL_SCOPE_WARN;
+ goto regex_done;
+ }
+
+ /* not a clear indication of scoping error, though */
+ rc = regexec( &re, rebuf, 0, NULL, 0 )
+ ? ACL_SCOPE_WARN : ACL_SCOPE_OK;
+
+regex_done:;
+ regfree( &re );
+ return rc;
+ }
+
+ patlen = a->acl_dn_pat.bv_len;
+ /* If backend suffix is longer than pattern,
+ * it is a potential mismatch (in the sense
+ * that a superior naming context could
+ * match */
+ if ( dn.bv_len > patlen ) {
+ /* base is blatantly wrong */
+ if ( style == ACL_STYLE_BASE ) return ACL_SCOPE_ERR;
+
+ /* a style of one can be wrong if there is
+ * more than one level between the suffix
+ * and the pattern */
+ if ( style == ACL_STYLE_ONE ) {
+ ber_len_t rdnlen = 0;
+ int sep = 0;
+
+ if ( patlen > 0 ) {
+ if ( !DN_SEPARATOR( dn.bv_val[dn.bv_len - patlen - 1] )) {
+ return ACL_SCOPE_ERR;
+ }
+ sep = 1;
+ }
+
+ rdnlen = dn_rdnlen( NULL, &dn );
+ if ( rdnlen != dn.bv_len - patlen - sep )
+ return ACL_SCOPE_ERR;
+ }
+
+ /* if the trailing part doesn't match,
+ * then it's an error */
+ if ( strcmp( a->acl_dn_pat.bv_val,
+ &dn.bv_val[dn.bv_len - patlen] ) != 0 )
+ {
+ return ACL_SCOPE_ERR;
+ }
+
+ return ACL_SCOPE_PARTIAL;
+ }
+
+ switch ( style ) {
+ case ACL_STYLE_BASE:
+ case ACL_STYLE_ONE:
+ case ACL_STYLE_CHILDREN:
+ case ACL_STYLE_SUBTREE:
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ if ( dn.bv_len < patlen &&
+ !DN_SEPARATOR( a->acl_dn_pat.bv_val[patlen - dn.bv_len - 1] ))
+ {
+ return ACL_SCOPE_ERR;
+ }
+
+ if ( strcmp( &a->acl_dn_pat.bv_val[patlen - dn.bv_len], dn.bv_val )
+ != 0 )
+ {
+ return ACL_SCOPE_ERR;
+ }
+
+ return ACL_SCOPE_OK;
+ }
+
+ return ACL_SCOPE_UNKNOWN;
+}
+
+int
+parse_acl(
+ struct config_args_s *c,
+ int pos )
+{
+ int i;
+ char *left, *right, *style;
+ struct berval bv;
+ AccessControl *a = NULL;
+ Access *b = NULL;
+ int rc;
+ const char *text;
+ Backend *be = c->be;
+ const char *fname = c->fname;
+ int lineno = c->lineno;
+ int argc = c->argc;
+ char **argv = c->argv;
+
+ for ( i = 1; i < argc; i++ ) {
+ /* to clause - select which entries are protected */
+ if ( strcasecmp( argv[i], "to" ) == 0 ) {
+ if ( a != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "only one to clause allowed in access line" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ a = (AccessControl *) ch_calloc( 1, sizeof(AccessControl) );
+ a->acl_attrval_style = ACL_STYLE_NONE;
+ for ( ++i; i < argc; i++ ) {
+ if ( strcasecmp( argv[i], "by" ) == 0 ) {
+ i--;
+ break;
+ }
+
+ if ( strcasecmp( argv[i], "*" ) == 0 ) {
+ if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
+ a->acl_dn_style != ACL_STYLE_REGEX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn pattern already specified in to clause." );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
+ continue;
+ }
+
+ split( argv[i], '=', &left, &right );
+ split( left, '.', &left, &style );
+
+ if ( right == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in \"%s\" in to clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( strcasecmp( left, "dn" ) == 0 ) {
+ if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
+ a->acl_dn_style != ACL_STYLE_REGEX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "dn pattern already specified in to clause" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( style == NULL || *style == '\0' ||
+ strcasecmp( style, "baseObject" ) == 0 ||
+ strcasecmp( style, "base" ) == 0 ||
+ strcasecmp( style, "exact" ) == 0 )
+ {
+ a->acl_dn_style = ACL_STYLE_BASE;
+ ber_str2bv( right, 0, 1, &a->acl_dn_pat );
+
+ } else if ( strcasecmp( style, "oneLevel" ) == 0 ||
+ strcasecmp( style, "one" ) == 0 )
+ {
+ a->acl_dn_style = ACL_STYLE_ONE;
+ ber_str2bv( right, 0, 1, &a->acl_dn_pat );
+
+ } else if ( strcasecmp( style, "subtree" ) == 0 ||
+ strcasecmp( style, "sub" ) == 0 )
+ {
+ if( *right == '\0' ) {
+ ber_str2bv( "*", STRLENOF( "*" ), 1, &a->acl_dn_pat );
+
+ } else {
+ a->acl_dn_style = ACL_STYLE_SUBTREE;
+ ber_str2bv( right, 0, 1, &a->acl_dn_pat );
+ }
+
+ } else if ( strcasecmp( style, "children" ) == 0 ) {
+ a->acl_dn_style = ACL_STYLE_CHILDREN;
+ ber_str2bv( right, 0, 1, &a->acl_dn_pat );
+
+ } else if ( strcasecmp( style, "regex" ) == 0 ) {
+ a->acl_dn_style = ACL_STYLE_REGEX;
+
+ if ( *right == '\0' ) {
+ /* empty regex should match empty DN */
+ a->acl_dn_style = ACL_STYLE_BASE;
+ ber_str2bv( right, 0, 1, &a->acl_dn_pat );
+
+ } else if ( strcmp(right, "*") == 0
+ || strcmp(right, ".*") == 0
+ || strcmp(right, ".*$") == 0
+ || strcmp(right, "^.*") == 0
+ || strcmp(right, "^.*$") == 0
+ || strcmp(right, ".*$$") == 0
+ || strcmp(right, "^.*$$") == 0 )
+ {
+ ber_str2bv( "*", STRLENOF("*"), 1, &a->acl_dn_pat );
+
+ } else {
+ acl_regex_normalized_dn( right, &a->acl_dn_pat );
+ }
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unknown dn style \"%s\" in to clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ continue;
+ }
+
+ if ( strcasecmp( left, "filter" ) == 0 ) {
+ if ( (a->acl_filter = str2filter( right )) == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "bad filter \"%s\" in to clause", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ } else if ( strcasecmp( left, "attr" ) == 0 /* TOLERATED */
+ || strcasecmp( left, "attrs" ) == 0 ) /* DOCUMENTED */
+ {
+ if ( strcasecmp( left, "attr" ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"attr\" is deprecated (and undocumented); "
+ "use \"attrs\" instead");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+
+ a->acl_attrs = str2anlist( a->acl_attrs,
+ right, "," );
+ if ( a->acl_attrs == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unknown attr \"%s\" in to clause", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ } else if ( strncasecmp( left, "val", 3 ) == 0 ) {
+ struct berval bv;
+ char *mr;
+
+ if ( !BER_BVISEMPTY( &a->acl_attrval ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "attr val already specified in to clause" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ if ( a->acl_attrs == NULL || !BER_BVISEMPTY( &a->acl_attrs[1].an_name ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "attr val requires a single attribute");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ ber_str2bv( right, 0, 0, &bv );
+ a->acl_attrval_style = ACL_STYLE_BASE;
+
+ mr = strchr( left, '/' );
+ if ( mr != NULL ) {
+ mr[ 0 ] = '\0';
+ mr++;
+
+ a->acl_attrval_mr = mr_find( mr );
+ if ( a->acl_attrval_mr == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid matching rule \"%s\"", mr);
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if( !mr_usable_with_at( a->acl_attrval_mr, a->acl_attrs[ 0 ].an_desc->ad_type ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "matching rule \"%s\" use " "with attr \"%s\" not appropriate",
+ mr,
+ a->acl_attrs[0].an_name.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c-> log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ if ( style != NULL ) {
+ if ( strcasecmp( style, "regex" ) == 0 ) {
+ int e = regcomp( &a->acl_attrval_re, bv.bv_val,
+ REG_EXTENDED | REG_ICASE );
+ if ( e ) {
+ char err[SLAP_TEXT_BUFLEN];
+
+ regerror( e, &a->acl_attrval_re, err, sizeof( err ) );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ right, err );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ a->acl_attrval_style = ACL_STYLE_REGEX;
+
+ } else {
+ /* FIXME: if the attribute has DN syntax, we might
+ * allow one, subtree and children styles as well */
+ if ( !strcasecmp( style, "base" ) ||
+ !strcasecmp( style, "exact" ) ) {
+ a->acl_attrval_style = ACL_STYLE_BASE;
+
+ } else if ( a->acl_attrs[0].an_desc->ad_type->
+ sat_syntax == slap_schema.si_syn_distinguishedName )
+ {
+ if ( !strcasecmp( style, "baseObject" ) ||
+ !strcasecmp( style, "base" ) )
+ {
+ a->acl_attrval_style = ACL_STYLE_BASE;
+ } else if ( !strcasecmp( style, "onelevel" ) ||
+ !strcasecmp( style, "one" ) )
+ {
+ a->acl_attrval_style = ACL_STYLE_ONE;
+ } else if ( !strcasecmp( style, "subtree" ) ||
+ !strcasecmp( style, "sub" ) )
+ {
+ a->acl_attrval_style = ACL_STYLE_SUBTREE;
+ } else if ( !strcasecmp( style, "children" ) ) {
+ a->acl_attrval_style = ACL_STYLE_CHILDREN;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unknown val.<style> \"%s\" for attributeType \"%s\" " "with DN syntax",
+ style,
+ a->acl_attrs[0].an_desc->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &bv, &a->acl_attrval, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to normalize DN \"%s\" " "for attributeType \"%s\" (%d)",
+ bv.bv_val,
+ a->acl_attrs[0].an_desc->ad_cname.bv_val,
+ rc );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unknown val.<style> \"%s\" for attributeType \"%s\"",
+ fname,
+ a->acl_attrs[0].an_desc->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+ }
+
+ /* Check for appropriate matching rule */
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
+ ber_dupbv( &a->acl_attrval, &bv );
+
+ } else if ( BER_BVISNULL( &a->acl_attrval ) ) {
+ int rc;
+ const char *text;
+
+ if ( a->acl_attrval_mr == NULL ) {
+ a->acl_attrval_mr = a->acl_attrs[ 0 ].an_desc->ad_type->sat_equality;
+ }
+
+ if ( a->acl_attrval_mr == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "attr \"%s\" does not have an EQUALITY matching rule",
+ a->acl_attrs[ 0 ].an_name.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ rc = asserted_value_validate_normalize(
+ a->acl_attrs[ 0 ].an_desc,
+ a->acl_attrval_mr,
+ SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &bv,
+ &a->acl_attrval,
+ &text,
+ NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "attr \"%s\" normalization failed (%d: %s).\n",
+ a->acl_attrs[0].an_name.bv_val,
+ rc, text );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "expecting <what> got \"%s\"",
+ left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) &&
+ ber_bvccmp( &a->acl_dn_pat, '*' ) )
+ {
+ free( a->acl_dn_pat.bv_val );
+ BER_BVZERO( &a->acl_dn_pat );
+ a->acl_dn_style = ACL_STYLE_REGEX;
+ }
+
+ if ( !BER_BVISEMPTY( &a->acl_dn_pat ) ||
+ a->acl_dn_style != ACL_STYLE_REGEX )
+ {
+ if ( a->acl_dn_style != ACL_STYLE_REGEX ) {
+ struct berval bv;
+ rc = dnNormalize( 0, NULL, NULL, &a->acl_dn_pat, &bv, NULL);
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg ),
+ "bad DN \"%s\" in to DN clause",
+ a->acl_dn_pat.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ free( a->acl_dn_pat.bv_val );
+ a->acl_dn_pat = bv;
+
+ } else {
+ int e = regcomp( &a->acl_dn_re, a->acl_dn_pat.bv_val,
+ REG_EXTENDED | REG_ICASE );
+ if ( e ) {
+ char err[ SLAP_TEXT_BUFLEN ];
+
+ regerror( e, &a->acl_dn_re, err, sizeof( err ) );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ right, err );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+ }
+
+ /* by clause - select who has what access to entries */
+ } else if ( strcasecmp( argv[i], "by" ) == 0 ) {
+ if ( a == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "to clause required before by clause in access line");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ /*
+ * by clause consists of <who> and <access>
+ */
+
+ if ( ++i == argc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "premature EOL: expecting <who>");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b = (Access *) ch_calloc( 1, sizeof(Access) );
+
+ ACL_INVALIDATE( b->a_access_mask );
+
+ /* get <who> */
+ for ( ; i < argc; i++ ) {
+ slap_style_t sty = ACL_STYLE_REGEX;
+ char *style_modifier = NULL;
+ char *style_level = NULL;
+ int level = 0;
+ int expand = 0;
+ slap_dn_access *bdn = &b->a_dn;
+ int is_realdn = 0;
+
+ split( argv[i], '=', &left, &right );
+ split( left, '.', &left, &style );
+ if ( style ) {
+ split( style, ',', &style, &style_modifier );
+
+ if ( strncasecmp( style, "level", STRLENOF( "level" ) ) == 0 ) {
+ split( style, '{', &style, &style_level );
+ if ( style_level != NULL ) {
+ char *p = strchr( style_level, '}' );
+ if ( p == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "premature eol: expecting closing '}' in \"level{n}\"");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ } else if ( p == style_level ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "empty level in \"level{n}\"");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ p[0] = '\0';
+ }
+ }
+ }
+
+ if ( style == NULL || *style == '\0' ||
+ strcasecmp( style, "exact" ) == 0 ||
+ strcasecmp( style, "baseObject" ) == 0 ||
+ strcasecmp( style, "base" ) == 0 )
+ {
+ sty = ACL_STYLE_BASE;
+
+ } else if ( strcasecmp( style, "onelevel" ) == 0 ||
+ strcasecmp( style, "one" ) == 0 )
+ {
+ sty = ACL_STYLE_ONE;
+
+ } else if ( strcasecmp( style, "subtree" ) == 0 ||
+ strcasecmp( style, "sub" ) == 0 )
+ {
+ sty = ACL_STYLE_SUBTREE;
+
+ } else if ( strcasecmp( style, "children" ) == 0 ) {
+ sty = ACL_STYLE_CHILDREN;
+
+ } else if ( strcasecmp( style, "level" ) == 0 )
+ {
+ if ( lutil_atoi( &level, style_level ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse level in \"level{n}\"");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ sty = ACL_STYLE_LEVEL;
+
+ } else if ( strcasecmp( style, "regex" ) == 0 ) {
+ sty = ACL_STYLE_REGEX;
+
+ } else if ( strcasecmp( style, "expand" ) == 0 ) {
+ sty = ACL_STYLE_EXPAND;
+
+ } else if ( strcasecmp( style, "ip" ) == 0 ) {
+ sty = ACL_STYLE_IP;
+
+ } else if ( strcasecmp( style, "ipv6" ) == 0 ) {
+#ifndef LDAP_PF_INET6
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "IPv6 not supported");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+#endif /* ! LDAP_PF_INET6 */
+ sty = ACL_STYLE_IPV6;
+
+ } else if ( strcasecmp( style, "path" ) == 0 ) {
+ sty = ACL_STYLE_PATH;
+#ifndef LDAP_PF_LOCAL
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "\"path\" style modifier is useless without local");
+ Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+#endif /* LDAP_PF_LOCAL */
+
+ } else {
+ snprintf( c->cr_msg, sizeof ( c->cr_msg ),
+ "unknown style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( style_modifier &&
+ strcasecmp( style_modifier, "expand" ) == 0 )
+ {
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"regex\" style implies \"expand\" modifier" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ break;
+
+ case ACL_STYLE_EXPAND:
+ break;
+
+ default:
+ /* we'll see later if it's pertinent */
+ expand = 1;
+ break;
+ }
+ }
+
+ if ( strncasecmp( left, "real", STRLENOF( "real" ) ) == 0 ) {
+ is_realdn = 1;
+ bdn = &b->a_realdn;
+ left += STRLENOF( "real" );
+ }
+
+ if ( strcasecmp( left, "*" ) == 0 ) {
+ if ( is_realdn ) {
+ goto fail;
+ }
+
+ ber_str2bv( "*", STRLENOF( "*" ), 1, &bv );
+ sty = ACL_STYLE_REGEX;
+
+ } else if ( strcasecmp( left, "anonymous" ) == 0 ) {
+ ber_str2bv("anonymous", STRLENOF( "anonymous" ), 1, &bv);
+ sty = ACL_STYLE_ANONYMOUS;
+
+ } else if ( strcasecmp( left, "users" ) == 0 ) {
+ ber_str2bv("users", STRLENOF( "users" ), 1, &bv);
+ sty = ACL_STYLE_USERS;
+
+ } else if ( strcasecmp( left, "self" ) == 0 ) {
+ ber_str2bv("self", STRLENOF( "self" ), 1, &bv);
+ sty = ACL_STYLE_SELF;
+
+ } else if ( strcasecmp( left, "dn" ) == 0 ) {
+ if ( sty == ACL_STYLE_REGEX ) {
+ bdn->a_style = ACL_STYLE_REGEX;
+ if ( right == NULL ) {
+ /* no '=' */
+ ber_str2bv("users",
+ STRLENOF( "users" ),
+ 1, &bv);
+ bdn->a_style = ACL_STYLE_USERS;
+
+ } else if (*right == '\0' ) {
+ /* dn="" */
+ ber_str2bv("anonymous",
+ STRLENOF( "anonymous" ),
+ 1, &bv);
+ bdn->a_style = ACL_STYLE_ANONYMOUS;
+
+ } else if ( strcmp( right, "*" ) == 0 ) {
+ /* dn=* */
+ /* any or users? users for now */
+ ber_str2bv("users",
+ STRLENOF( "users" ),
+ 1, &bv);
+ bdn->a_style = ACL_STYLE_USERS;
+
+ } else if ( strcmp( right, ".+" ) == 0
+ || strcmp( right, "^.+" ) == 0
+ || strcmp( right, ".+$" ) == 0
+ || strcmp( right, "^.+$" ) == 0
+ || strcmp( right, ".+$$" ) == 0
+ || strcmp( right, "^.+$$" ) == 0 )
+ {
+ ber_str2bv("users",
+ STRLENOF( "users" ),
+ 1, &bv);
+ bdn->a_style = ACL_STYLE_USERS;
+
+ } else if ( strcmp( right, ".*" ) == 0
+ || strcmp( right, "^.*" ) == 0
+ || strcmp( right, ".*$" ) == 0
+ || strcmp( right, "^.*$" ) == 0
+ || strcmp( right, ".*$$" ) == 0
+ || strcmp( right, "^.*$$" ) == 0 )
+ {
+ ber_str2bv("*",
+ STRLENOF( "*" ),
+ 1, &bv);
+
+ } else {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ }
+
+ } else if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+
+ } else {
+ ber_str2bv( right, 0, 1, &bv );
+ }
+
+ } else {
+ BER_BVZERO( &bv );
+ }
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ if ( !BER_BVISEMPTY( &bdn->a_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( sty != ACL_STYLE_REGEX &&
+ sty != ACL_STYLE_ANONYMOUS &&
+ sty != ACL_STYLE_USERS &&
+ sty != ACL_STYLE_SELF &&
+ expand == 0 )
+ {
+ rc = dnNormalize(0, NULL, NULL,
+ &bv, &bdn->a_pat, NULL);
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "bad DN \"%s\" in by DN clause", bv.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ free( bv.bv_val );
+ if ( sty == ACL_STYLE_BASE
+ && be != NULL
+ && !BER_BVISNULL( &be->be_rootndn )
+ && dn_match( &bdn->a_pat, &be->be_rootndn ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "rootdn is always granted unlimited privileges" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+
+ } else {
+ bdn->a_pat = bv;
+ }
+ bdn->a_style = sty;
+ if ( expand ) {
+ char *exp;
+ int gotit = 0;
+
+ for ( exp = strchr( bdn->a_pat.bv_val, '$' );
+ exp && (ber_len_t)(exp - bdn->a_pat.bv_val)
+ < bdn->a_pat.bv_len;
+ exp = strchr( exp, '$' ) )
+ {
+ if ( ( isdigit( (unsigned char) exp[ 1 ] ) ||
+ exp[ 1 ] == '{' ) ) {
+ gotit = 1;
+ break;
+ }
+ }
+
+ if ( gotit == 1 ) {
+ bdn->a_expand = expand;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"expand\" used with no expansions in \"pattern\"");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+ if ( sty == ACL_STYLE_SELF ) {
+ bdn->a_self_level = level;
+
+ } else {
+ if ( level < 0 ) {
+ snprintf( c->cr_msg, sizeof( c ->cr_msg ),
+ "bad negative level \"%d\" in by DN clause", level );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ } else if ( level == 1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"onelevel\" should be used instead of \"level{1}\" in by DN clause" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ } else if ( level == 0 && sty == ACL_STYLE_LEVEL ) {
+ snprintf ( c->cr_msg, sizeof( c->cr_msg ),
+ "\"base\" should be used instead of \"level{0}\" in by DN clause" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+
+ bdn->a_level = level;
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "dnattr" ) == 0 ) {
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if( bdn->a_at != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dnattr already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ rc = slap_str2ad( right, &bdn->a_at, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dnattr \"%s\": %s", right, text );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+
+ if( !is_at_syntax( bdn->a_at->ad_type,
+ SLAPD_DN_SYNTAX ) &&
+ !is_at_syntax( bdn->a_at->ad_type,
+ SLAPD_NAMEUID_SYNTAX ))
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dnattr \"%s\": " "inappropriate syntax: %s",
+ right, bdn->a_at->ad_type->sat_syntax_oid );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if( bdn->a_at->ad_type->sat_equality == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dnattr \"%s\": inappropriate matching (no EQUALITY)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ continue;
+ }
+
+ if ( strncasecmp( left, "group", STRLENOF( "group" ) ) == 0 ) {
+ char *name = NULL;
+ char *value = NULL;
+ char *attr_name = SLAPD_GROUP_ATTR;
+
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ /* legacy, tolerated */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "deprecated group style \"regex\"; use \"expand\" instead" );
+ Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ sty = ACL_STYLE_EXPAND;
+ break;
+
+ case ACL_STYLE_BASE:
+ /* legal, traditional */
+ case ACL_STYLE_EXPAND:
+ /* legal, substring expansion; supersedes regex */
+ break;
+
+ default:
+ /* unknown */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ /* format of string is
+ "group/objectClassValue/groupAttrName" */
+ if ( ( value = strchr(left, '/') ) != NULL ) {
+ *value++ = '\0';
+ if ( *value && ( name = strchr( value, '/' ) ) != NULL ) {
+ *name++ = '\0';
+ }
+ }
+
+ b->a_group_style = sty;
+ if ( sty == ACL_STYLE_EXPAND ) {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ b->a_group_pat = bv;
+
+ } else {
+ ber_str2bv( right, 0, 0, &bv );
+ rc = dnNormalize( 0, NULL, NULL, &bv,
+ &b->a_group_pat, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "bad DN \"%s\"", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ if ( value && *value ) {
+ b->a_group_oc = oc_find( value );
+ *--value = '/';
+
+ if ( b->a_group_oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group objectclass \"%s\" unknown", value );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ } else {
+ b->a_group_oc = oc_find( SLAPD_GROUP_CLASS );
+
+ if( b->a_group_oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group default objectclass \"%s\" unknown", SLAPD_GROUP_CLASS );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ if ( is_object_subclass( slap_schema.si_oc_referral,
+ b->a_group_oc ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group objectclass \"%s\" is subclass of referral", value );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( is_object_subclass( slap_schema.si_oc_alias,
+ b->a_group_oc ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group objectclass \"%s\" is subclass of alias", value );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( name && *name ) {
+ attr_name = name;
+ *--name = '/';
+
+ }
+
+ rc = slap_str2ad( attr_name, &b->a_group_at, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group \"%s\": %s", right, text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !is_at_syntax( b->a_group_at->ad_type,
+ SLAPD_DN_SYNTAX ) /* e.g. "member" */
+ && !is_at_syntax( b->a_group_at->ad_type,
+ SLAPD_NAMEUID_SYNTAX ) /* e.g. memberUID */
+ && !is_at_subtype( b->a_group_at->ad_type,
+ slap_schema.si_ad_labeledURI->ad_type ) /* e.g. memberURL */ )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group \"%s\" attr \"%s\": inappropriate syntax: %s; " "must be " SLAPD_DN_SYNTAX " (DN), " SLAPD_NAMEUID_SYNTAX " (NameUID) " "or a subtype of labeledURI",
+ right, attr_name, at_syntax(b->a_group_at->ad_type) );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+
+ {
+ int rc;
+ ObjectClass *ocs[2];
+
+ ocs[0] = b->a_group_oc;
+ ocs[1] = NULL;
+
+ rc = oc_check_allowed( b->a_group_at->ad_type,
+ ocs, NULL );
+
+ if( rc != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "group: \"%s\" not allowed by \"%s\".\n",
+ b->a_group_at->ad_cname.bv_val,
+ b->a_group_oc->soc_oid );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "peername" ) == 0 ) {
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ case ACL_STYLE_BASE:
+ /* legal, traditional */
+ case ACL_STYLE_EXPAND:
+ /* cheap replacement to regex for simple expansion */
+ case ACL_STYLE_IP:
+ case ACL_STYLE_IPV6:
+ case ACL_STYLE_PATH:
+ /* legal, peername specific */
+ break;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "peername pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_peername_style = sty;
+ if ( sty == ACL_STYLE_REGEX ) {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ b->a_peername_pat = bv;
+
+ } else {
+ ber_str2bv( right, 0, 1, &b->a_peername_pat );
+
+ if ( sty == ACL_STYLE_IP ) {
+ char *addr = NULL,
+ *mask = NULL,
+ *port = NULL;
+
+ split( right, '{', &addr, &port );
+ split( addr, '%', &addr, &mask );
+
+ b->a_peername_addr = inet_addr( addr );
+ if ( b->a_peername_addr == (unsigned long)(-1) ) {
+ /* illegal address */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername address \"%s\"", addr );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_peername_mask = (unsigned long)(-1);
+ if ( mask != NULL ) {
+ b->a_peername_mask = inet_addr( mask );
+ if ( b->a_peername_mask ==
+ (unsigned long)(-1) )
+ {
+ /* illegal mask */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername address mask \"%s\"", mask );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ b->a_peername_port = -1;
+ if ( port ) {
+ char *end = NULL;
+
+ b->a_peername_port = strtol( port, &end, 10 );
+ if ( end == port || end[0] != '}' ) {
+ /* illegal port */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername port specification \"{%s}\"", port );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+#ifdef LDAP_PF_INET6
+ } else if ( sty == ACL_STYLE_IPV6 ) {
+ char *addr = NULL,
+ *mask = NULL,
+ *port = NULL;
+
+ split( right, '{', &addr, &port );
+ split( addr, '%', &addr, &mask );
+
+ if ( inet_pton( AF_INET6, addr, &b->a_peername_addr6 ) != 1 ) {
+ /* illegal address */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername address \"%s\"", addr );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( mask == NULL ) {
+ mask = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF";
+ }
+
+ if ( inet_pton( AF_INET6, mask, &b->a_peername_mask6 ) != 1 ) {
+ /* illegal mask */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername address mask \"%s\"", mask );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_peername_port = -1;
+ if ( port ) {
+ char *end = NULL;
+
+ b->a_peername_port = strtol( port, &end, 10 );
+ if ( end == port || end[0] != '}' ) {
+ /* illegal port */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal peername port specification \"{%s}\"", port );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+#endif /* LDAP_PF_INET6 */
+ }
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "sockname" ) == 0 ) {
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ case ACL_STYLE_BASE:
+ /* legal, traditional */
+ case ACL_STYLE_EXPAND:
+ /* cheap replacement to regex for simple expansion */
+ break;
+
+ default:
+ /* unknown */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISNULL( &b->a_sockname_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "sockname pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_sockname_style = sty;
+ if ( sty == ACL_STYLE_REGEX ) {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ b->a_sockname_pat = bv;
+
+ } else {
+ ber_str2bv( right, 0, 1, &b->a_sockname_pat );
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "domain" ) == 0 ) {
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ case ACL_STYLE_BASE:
+ case ACL_STYLE_SUBTREE:
+ /* legal, traditional */
+ break;
+
+ case ACL_STYLE_EXPAND:
+ /* tolerated: means exact,expand */
+ if ( expand ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"expand\" modifier with \"expand\" style" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ sty = ACL_STYLE_BASE;
+ expand = 1;
+ break;
+
+ default:
+ /* unknown */
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "domain pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_domain_style = sty;
+ b->a_domain_expand = expand;
+ if ( sty == ACL_STYLE_REGEX ) {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ b->a_domain_pat = bv;
+
+ } else {
+ ber_str2bv( right, 0, 1, &b->a_domain_pat );
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "sockurl" ) == 0 ) {
+ switch ( sty ) {
+ case ACL_STYLE_REGEX:
+ case ACL_STYLE_BASE:
+ /* legal, traditional */
+ case ACL_STYLE_EXPAND:
+ /* cheap replacement to regex for simple expansion */
+ break;
+
+ default:
+ /* unknown */
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || right[0] == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing \"=\" in (or value after) \"%s\" in by clause", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "sockurl pattern already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_sockurl_style = sty;
+ if ( sty == ACL_STYLE_REGEX ) {
+ acl_regex_normalized_dn( right, &bv );
+ if ( !ber_bvccmp( &bv, '*' ) ) {
+ if ( regtest( c, bv.bv_val ) != 0)
+ goto fail;
+ }
+ b->a_sockurl_pat = bv;
+
+ } else {
+ ber_str2bv( right, 0, 1, &b->a_sockurl_pat );
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "set" ) == 0 ) {
+ switch ( sty ) {
+ /* deprecated */
+ case ACL_STYLE_REGEX:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "deprecated set style "
+ "\"regex\" in <by> clause; "
+ "use \"expand\" instead" );
+ Debug( LDAP_DEBUG_CONFIG | LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ sty = ACL_STYLE_EXPAND;
+ /* FALLTHRU */
+
+ case ACL_STYLE_BASE:
+ case ACL_STYLE_EXPAND:
+ break;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "set attribute already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no set is defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_set_style = sty;
+ ber_str2bv( right, 0, 1, &b->a_set_pat );
+
+ continue;
+ }
+
+#ifdef SLAP_DYNACL
+ {
+ char *name = NULL,
+ *opts = NULL;
+
+#if 1 /* tolerate legacy "aci" <who> */
+ if ( strcasecmp( left, "aci" ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "undocumented deprecated \"aci\" directive "
+ "is superseded by \"dynacl/aci\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ name = "aci";
+
+ } else
+#endif /* tolerate legacy "aci" <who> */
+ if ( strncasecmp( left, "dynacl/", STRLENOF( "dynacl/" ) ) == 0 ) {
+ name = &left[ STRLENOF( "dynacl/" ) ];
+ opts = strchr( name, '/' );
+ if ( opts ) {
+ opts[ 0 ] = '\0';
+ opts++;
+ }
+ }
+
+ if ( name ) {
+ if ( slap_dynacl_config( c, b, name, opts, sty, right ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to configure dynacl \"%s\"", name );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ continue;
+ }
+ }
+#endif /* SLAP_DYNACL */
+
+ if ( strcasecmp( left, "ssf" ) == 0 ) {
+ if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( b->a_authz.sai_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "ssf attribute already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no ssf is defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( lutil_atou( &b->a_authz.sai_ssf, right ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !b->a_authz.sai_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "transport_ssf" ) == 0 ) {
+ if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( b->a_authz.sai_transport_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "transport_ssf attribute already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no transport_ssf is defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( lutil_atou( &b->a_authz.sai_transport_ssf, right ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse transport_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !b->a_authz.sai_transport_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid transport_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "tls_ssf" ) == 0 ) {
+ if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( b->a_authz.sai_tls_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "tls_ssf attribute already specified" );
+ goto fail;
+ }
+
+ if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no tls_ssf is defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( lutil_atou( &b->a_authz.sai_tls_ssf, right ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse tls_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !b->a_authz.sai_tls_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid tls_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ continue;
+ }
+
+ if ( strcasecmp( left, "sasl_ssf" ) == 0 ) {
+ if ( sty != ACL_STYLE_REGEX && sty != ACL_STYLE_BASE ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "inappropriate style \"%s\" in by clause", style );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( b->a_authz.sai_sasl_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "sasl_ssf attribute already specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( right == NULL || *right == '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no sasl_ssf is defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( lutil_atou( &b->a_authz.sai_sasl_ssf, right ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse sasl_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( !b->a_authz.sai_sasl_ssf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid sasl_ssf value (%s)", right );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ continue;
+ }
+
+ if ( right != NULL ) {
+ /* unsplit */
+ right[-1] = '=';
+ }
+ break;
+ }
+
+ if ( i == argc || ( strcasecmp( left, "stop" ) == 0 ) ) {
+ /* out of arguments or plain stop */
+
+ ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
+ ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
+ b->a_type = ACL_STOP;
+
+ access_append( &a->acl_access, b );
+ continue;
+ }
+
+ if ( strcasecmp( left, "continue" ) == 0 ) {
+ /* plain continue */
+
+ ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
+ ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
+ b->a_type = ACL_CONTINUE;
+
+ access_append( &a->acl_access, b );
+ continue;
+ }
+
+ if ( strcasecmp( left, "break" ) == 0 ) {
+ /* plain continue */
+
+ ACL_PRIV_ASSIGN(b->a_access_mask, ACL_PRIV_ADDITIVE);
+ ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
+ b->a_type = ACL_BREAK;
+
+ access_append( &a->acl_access, b );
+ continue;
+ }
+
+ if ( strcasecmp( left, "by" ) == 0 ) {
+ /* we've gone too far */
+ --i;
+ ACL_PRIV_ASSIGN( b->a_access_mask, ACL_PRIV_ADDITIVE );
+ ACL_PRIV_SET( b->a_access_mask, ACL_PRIV_NONE);
+ b->a_type = ACL_STOP;
+
+ access_append( &a->acl_access, b );
+ continue;
+ }
+
+ /* get <access> */
+ {
+ char *lleft = left;
+
+ if ( strncasecmp( left, "self", STRLENOF( "self" ) ) == 0 ) {
+ b->a_dn_self = 1;
+ lleft = &left[ STRLENOF( "self" ) ];
+
+ } else if ( strncasecmp( left, "realself", STRLENOF( "realself" ) ) == 0 ) {
+ b->a_realdn_self = 1;
+ lleft = &left[ STRLENOF( "realself" ) ];
+ }
+
+ ACL_PRIV_ASSIGN( b->a_access_mask, str2accessmask( lleft ) );
+ }
+
+ if ( ACL_IS_INVALID( b->a_access_mask ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "expecting <access> got \"%s\"", left );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ b->a_type = ACL_STOP;
+
+ if ( ++i == argc ) {
+ /* out of arguments or plain stop */
+ access_append( &a->acl_access, b );
+ continue;
+ }
+
+ if ( strcasecmp( argv[i], "continue" ) == 0 ) {
+ /* plain continue */
+ b->a_type = ACL_CONTINUE;
+
+ } else if ( strcasecmp( argv[i], "break" ) == 0 ) {
+ /* plain continue */
+ b->a_type = ACL_BREAK;
+
+ } else if ( strcasecmp( argv[i], "stop" ) != 0 ) {
+ /* gone to far */
+ i--;
+ }
+
+ access_append( &a->acl_access, b );
+ b = NULL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "expecting \"to\" or \"by\" got \"%s\"", argv[i] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+ }
+
+ /* if we have no real access clause, complain and do nothing */
+ if ( a == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: no access clause(s) specified in access line");
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+
+ } else {
+#ifdef LDAP_DEBUG
+ if ( slap_debug & LDAP_DEBUG_ACL ) {
+ print_acl( be, a );
+ }
+#endif
+
+ if ( a->acl_access == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: no by clause(s) specified in access line" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto fail;
+ }
+
+ if ( be != NULL ) {
+ if ( be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: scope checking needs suffix before ACLs" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ /* go ahead, since checking is not authoritative */
+ } else if ( !BER_BVISNULL( &be->be_nsuffix[ 1 ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: scope checking only applies to single-valued suffix databases" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ /* go ahead, since checking is not authoritative */
+ } else {
+ switch ( check_scope( be, a ) ) {
+ case ACL_SCOPE_UNKNOWN:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: cannot assess the validity of the ACL scope within backend naming context" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ break;
+
+ case ACL_SCOPE_WARN:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: ACL could be out of scope within backend naming context" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ break;
+
+ case ACL_SCOPE_PARTIAL:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: ACL appears to be partially out of scope within backend naming context" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ break;
+
+ case ACL_SCOPE_ERR:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: ACL appears to be out of scope within backend naming context" );
+ Debug( LDAP_DEBUG_ACL, "%s: %s.\n", c->log, c->cr_msg );
+ break;
+
+ default:
+ break;
+ }
+ }
+ acl_append( &be->be_acl, a, pos );
+
+ } else {
+ acl_append( &frontendDB->be_acl, a, pos );
+ }
+ }
+
+ return 0;
+
+fail:
+ if ( b ) access_free( b );
+ if ( a ) acl_free( a );
+ return acl_usage();
+}
+
+char *
+accessmask2str( slap_mask_t mask, char *buf, int debug )
+{
+ int none = 1;
+ char *ptr = buf;
+
+ assert( buf != NULL );
+
+ if ( ACL_IS_INVALID( mask ) ) {
+ return "invalid";
+ }
+
+ buf[0] = '\0';
+
+ if ( ACL_IS_LEVEL( mask ) ) {
+ if ( ACL_LVL_IS_NONE(mask) ) {
+ ptr = lutil_strcopy( ptr, "none" );
+
+ } else if ( ACL_LVL_IS_DISCLOSE(mask) ) {
+ ptr = lutil_strcopy( ptr, "disclose" );
+
+ } else if ( ACL_LVL_IS_AUTH(mask) ) {
+ ptr = lutil_strcopy( ptr, "auth" );
+
+ } else if ( ACL_LVL_IS_COMPARE(mask) ) {
+ ptr = lutil_strcopy( ptr, "compare" );
+
+ } else if ( ACL_LVL_IS_SEARCH(mask) ) {
+ ptr = lutil_strcopy( ptr, "search" );
+
+ } else if ( ACL_LVL_IS_READ(mask) ) {
+ ptr = lutil_strcopy( ptr, "read" );
+
+ } else if ( ACL_LVL_IS_WRITE(mask) ) {
+ ptr = lutil_strcopy( ptr, "write" );
+
+ } else if ( ACL_LVL_IS_WADD(mask) ) {
+ ptr = lutil_strcopy( ptr, "add" );
+
+ } else if ( ACL_LVL_IS_WDEL(mask) ) {
+ ptr = lutil_strcopy( ptr, "delete" );
+
+ } else if ( ACL_LVL_IS_MANAGE(mask) ) {
+ ptr = lutil_strcopy( ptr, "manage" );
+
+ } else {
+ ptr = lutil_strcopy( ptr, "unknown" );
+ }
+
+ if ( !debug ) {
+ *ptr = '\0';
+ return buf;
+ }
+ *ptr++ = '(';
+ }
+
+ if( ACL_IS_ADDITIVE( mask ) ) {
+ *ptr++ = '+';
+
+ } else if( ACL_IS_SUBTRACTIVE( mask ) ) {
+ *ptr++ = '-';
+
+ } else {
+ *ptr++ = '=';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_MANAGE) ) {
+ none = 0;
+ *ptr++ = 'm';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WRITE) ) {
+ none = 0;
+ *ptr++ = 'w';
+
+ } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WADD) ) {
+ none = 0;
+ *ptr++ = 'a';
+
+ } else if ( ACL_PRIV_ISSET(mask, ACL_PRIV_WDEL) ) {
+ none = 0;
+ *ptr++ = 'z';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_READ) ) {
+ none = 0;
+ *ptr++ = 'r';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_SEARCH) ) {
+ none = 0;
+ *ptr++ = 's';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_COMPARE) ) {
+ none = 0;
+ *ptr++ = 'c';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_AUTH) ) {
+ none = 0;
+ *ptr++ = 'x';
+ }
+
+ if ( ACL_PRIV_ISSET(mask, ACL_PRIV_DISCLOSE) ) {
+ none = 0;
+ *ptr++ = 'd';
+ }
+
+ if ( none && ACL_PRIV_ISSET(mask, ACL_PRIV_NONE) ) {
+ none = 0;
+ *ptr++ = '0';
+ }
+
+ if ( none ) {
+ ptr = buf;
+ }
+
+ if ( ACL_IS_LEVEL( mask ) ) {
+ *ptr++ = ')';
+ }
+
+ *ptr = '\0';
+
+ return buf;
+}
+
+slap_mask_t
+str2accessmask( const char *str )
+{
+ slap_mask_t mask;
+
+ if( !ASCII_ALPHA(str[0]) ) {
+ int i;
+
+ if ( str[0] == '=' ) {
+ ACL_INIT(mask);
+
+ } else if( str[0] == '+' ) {
+ ACL_PRIV_ASSIGN(mask, ACL_PRIV_ADDITIVE);
+
+ } else if( str[0] == '-' ) {
+ ACL_PRIV_ASSIGN(mask, ACL_PRIV_SUBSTRACTIVE);
+
+ } else {
+ ACL_INVALIDATE(mask);
+ return mask;
+ }
+
+ for( i=1; str[i] != '\0'; i++ ) {
+ if( TOLOWER((unsigned char) str[i]) == 'm' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_MANAGE);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'w' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_WRITE);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'a' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_WADD);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'z' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_WDEL);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'r' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_READ);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 's' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_SEARCH);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'c' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_COMPARE);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'x' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_AUTH);
+
+ } else if( TOLOWER((unsigned char) str[i]) == 'd' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_DISCLOSE);
+
+ } else if( str[i] == '0' ) {
+ ACL_PRIV_SET(mask, ACL_PRIV_NONE);
+
+ } else {
+ ACL_INVALIDATE(mask);
+ return mask;
+ }
+ }
+
+ return mask;
+ }
+
+ if ( strcasecmp( str, "none" ) == 0 ) {
+ ACL_LVL_ASSIGN_NONE(mask);
+
+ } else if ( strcasecmp( str, "disclose" ) == 0 ) {
+ ACL_LVL_ASSIGN_DISCLOSE(mask);
+
+ } else if ( strcasecmp( str, "auth" ) == 0 ) {
+ ACL_LVL_ASSIGN_AUTH(mask);
+
+ } else if ( strcasecmp( str, "compare" ) == 0 ) {
+ ACL_LVL_ASSIGN_COMPARE(mask);
+
+ } else if ( strcasecmp( str, "search" ) == 0 ) {
+ ACL_LVL_ASSIGN_SEARCH(mask);
+
+ } else if ( strcasecmp( str, "read" ) == 0 ) {
+ ACL_LVL_ASSIGN_READ(mask);
+
+ } else if ( strcasecmp( str, "add" ) == 0 ) {
+ ACL_LVL_ASSIGN_WADD(mask);
+
+ } else if ( strcasecmp( str, "delete" ) == 0 ) {
+ ACL_LVL_ASSIGN_WDEL(mask);
+
+ } else if ( strcasecmp( str, "write" ) == 0 ) {
+ ACL_LVL_ASSIGN_WRITE(mask);
+
+ } else if ( strcasecmp( str, "manage" ) == 0 ) {
+ ACL_LVL_ASSIGN_MANAGE(mask);
+
+ } else {
+ ACL_INVALIDATE( mask );
+ }
+
+ return mask;
+}
+
+static int
+acl_usage(void)
+{
+ char *access =
+ "<access clause> ::= access to <what> "
+ "[ by <who> [ <access> ] [ <control> ] ]+ \n";
+ char *what =
+ "<what> ::= * | dn[.<dnstyle>=<DN>] [filter=<filter>] [attrs=<attrspec>]\n"
+ "<attrspec> ::= <attrname> [val[/<matchingRule>][.<attrstyle>]=<value>] | <attrlist>\n"
+ "<attrlist> ::= <attr> [ , <attrlist> ]\n"
+ "<attr> ::= <attrname> | @<objectClass> | !<objectClass> | entry | children\n";
+
+ char *who =
+ "<who> ::= [ * | anonymous | users | self | dn[.<dnstyle>]=<DN> ]\n"
+ "\t[ realanonymous | realusers | realself | realdn[.<dnstyle>]=<DN> ]\n"
+ "\t[dnattr=<attrname>]\n"
+ "\t[realdnattr=<attrname>]\n"
+ "\t[group[/<objectclass>[/<attrname>]][.<style>]=<group>]\n"
+ "\t[peername[.<peernamestyle>]=<peer>] [sockname[.<style>]=<name>]\n"
+ "\t[domain[.<domainstyle>]=<domain>] [sockurl[.<style>]=<url>]\n"
+#ifdef SLAP_DYNACL
+ "\t[dynacl/<name>[/<options>][.<dynstyle>][=<pattern>]]\n"
+#endif /* SLAP_DYNACL */
+ "\t[ssf=<n>] [transport_ssf=<n>] [tls_ssf=<n>] [sasl_ssf=<n>]\n"
+ "<style> ::= exact | regex | base(Object)\n"
+ "<dnstyle> ::= base(Object) | one(level) | sub(tree) | children | "
+ "exact | regex\n"
+ "<attrstyle> ::= exact | regex | base(Object) | one(level) | "
+ "sub(tree) | children\n"
+ "<peernamestyle> ::= exact | regex | ip | ipv6 | path\n"
+ "<domainstyle> ::= exact | regex | base(Object) | sub(tree)\n"
+ "<access> ::= [[real]self]{<level>|<priv>}\n"
+ "<level> ::= none|disclose|auth|compare|search|read|{write|add|delete}|manage\n"
+ "<priv> ::= {=|+|-}{0|d|x|c|s|r|{w|a|z}|m}+\n"
+ "<control> ::= [ stop | continue | break ]\n"
+#ifdef SLAP_DYNACL
+#ifdef SLAPD_ACI_ENABLED
+ "dynacl:\n"
+ "\t<name>=ACI\t<pattern>=<attrname>\n"
+#endif /* SLAPD_ACI_ENABLED */
+#endif /* ! SLAP_DYNACL */
+ "";
+
+ Debug( LDAP_DEBUG_ANY, "%s%s%s\n", access, what, who );
+
+ return 1;
+}
+
+/*
+ * Set pattern to a "normalized" DN from src.
+ * At present, it simply eats the (optional) space after
+ * a RDN separator (,)
+ * Eventually will evolve in a more complete normalization
+ */
+static void
+acl_regex_normalized_dn(
+ const char *src,
+ struct berval *pattern )
+{
+ char *str, *p;
+ ber_len_t len;
+
+ str = ch_strdup( src );
+ len = strlen( src );
+
+ for ( p = str; p && p[0]; p++ ) {
+ /* escape */
+ if ( p[0] == '\\' && p[1] ) {
+ /*
+ * if escaping a hex pair we should
+ * increment p twice; however, in that
+ * case the second hex number does
+ * no harm
+ */
+ p++;
+ }
+
+ if ( p[0] == ',' && p[1] == ' ' ) {
+ char *q;
+
+ /*
+ * too much space should be an error if we are pedantic
+ */
+ for ( q = &p[2]; q[0] == ' '; q++ ) {
+ /* DO NOTHING */ ;
+ }
+ AC_MEMCPY( p+1, q, len-(q-str)+1);
+ }
+ }
+ pattern->bv_val = str;
+ pattern->bv_len = p - str;
+
+ return;
+}
+
+static void
+split(
+ char *line,
+ int splitchar,
+ char **left,
+ char **right )
+{
+ *left = line;
+ if ( (*right = strchr( line, splitchar )) != NULL ) {
+ *((*right)++) = '\0';
+ }
+}
+
+static void
+access_append( Access **l, Access *a )
+{
+ for ( ; *l != NULL; l = &(*l)->a_next ) {
+ ; /* Empty */
+ }
+
+ *l = a;
+}
+
+void
+acl_append( AccessControl **l, AccessControl *a, int pos )
+{
+ int i;
+
+ for (i=0 ; i != pos && *l != NULL; l = &(*l)->acl_next, i++ ) {
+ ; /* Empty */
+ }
+ if ( *l && a )
+ a->acl_next = *l;
+ *l = a;
+}
+
+static void
+access_free( Access *a )
+{
+ if ( !BER_BVISNULL( &a->a_dn_pat ) ) {
+ free( a->a_dn_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_realdn_pat ) ) {
+ free( a->a_realdn_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_peername_pat ) ) {
+ free( a->a_peername_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_sockname_pat ) ) {
+ free( a->a_sockname_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_domain_pat ) ) {
+ free( a->a_domain_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_sockurl_pat ) ) {
+ free( a->a_sockurl_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_set_pat ) ) {
+ free( a->a_set_pat.bv_val );
+ }
+ if ( !BER_BVISNULL( &a->a_group_pat ) ) {
+ free( a->a_group_pat.bv_val );
+ }
+#ifdef SLAP_DYNACL
+ if ( a->a_dynacl != NULL ) {
+ slap_dynacl_t *da;
+ for ( da = a->a_dynacl; da; ) {
+ slap_dynacl_t *tmp = da;
+
+ da = da->da_next;
+
+ if ( tmp->da_destroy ) {
+ tmp->da_destroy( tmp->da_private );
+ }
+
+ ch_free( tmp );
+ }
+ }
+#endif /* SLAP_DYNACL */
+ free( a );
+}
+
+void
+acl_free( AccessControl *a )
+{
+ Access *n;
+ AttributeName *an;
+
+ if ( a->acl_filter ) {
+ filter_free( a->acl_filter );
+ }
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ if ( a->acl_dn_style == ACL_STYLE_REGEX ) {
+ regfree( &a->acl_dn_re );
+ }
+ free ( a->acl_dn_pat.bv_val );
+ }
+ if ( a->acl_attrs ) {
+ for ( an = a->acl_attrs; !BER_BVISNULL( &an->an_name ); an++ ) {
+ free( an->an_name.bv_val );
+ }
+ free( a->acl_attrs );
+
+ if ( a->acl_attrval_style == ACL_STYLE_REGEX ) {
+ regfree( &a->acl_attrval_re );
+ }
+
+ if ( !BER_BVISNULL( &a->acl_attrval ) ) {
+ ber_memfree( a->acl_attrval.bv_val );
+ }
+ }
+ for ( ; a->acl_access; a->acl_access = n ) {
+ n = a->acl_access->a_next;
+ access_free( a->acl_access );
+ }
+ free( a );
+}
+
+void
+acl_destroy( AccessControl *a )
+{
+ AccessControl *n;
+
+ for ( ; a; a = n ) {
+ n = a->acl_next;
+ acl_free( a );
+ }
+
+ if ( !BER_BVISNULL( &aclbuf ) ) {
+ ch_free( aclbuf.bv_val );
+ BER_BVZERO( &aclbuf );
+ }
+}
+
+char *
+access2str( slap_access_t access )
+{
+ if ( access == ACL_NONE ) {
+ return "none";
+
+ } else if ( access == ACL_DISCLOSE ) {
+ return "disclose";
+
+ } else if ( access == ACL_AUTH ) {
+ return "auth";
+
+ } else if ( access == ACL_COMPARE ) {
+ return "compare";
+
+ } else if ( access == ACL_SEARCH ) {
+ return "search";
+
+ } else if ( access == ACL_READ ) {
+ return "read";
+
+ } else if ( access == ACL_WRITE ) {
+ return "write";
+
+ } else if ( access == ACL_WADD ) {
+ return "add";
+
+ } else if ( access == ACL_WDEL ) {
+ return "delete";
+
+ } else if ( access == ACL_MANAGE ) {
+ return "manage";
+
+ }
+
+ return "unknown";
+}
+
+slap_access_t
+str2access( const char *str )
+{
+ if ( strcasecmp( str, "none" ) == 0 ) {
+ return ACL_NONE;
+
+ } else if ( strcasecmp( str, "disclose" ) == 0 ) {
+ return ACL_DISCLOSE;
+
+ } else if ( strcasecmp( str, "auth" ) == 0 ) {
+ return ACL_AUTH;
+
+ } else if ( strcasecmp( str, "compare" ) == 0 ) {
+ return ACL_COMPARE;
+
+ } else if ( strcasecmp( str, "search" ) == 0 ) {
+ return ACL_SEARCH;
+
+ } else if ( strcasecmp( str, "read" ) == 0 ) {
+ return ACL_READ;
+
+ } else if ( strcasecmp( str, "write" ) == 0 ) {
+ return ACL_WRITE;
+
+ } else if ( strcasecmp( str, "add" ) == 0 ) {
+ return ACL_WADD;
+
+ } else if ( strcasecmp( str, "delete" ) == 0 ) {
+ return ACL_WDEL;
+
+ } else if ( strcasecmp( str, "manage" ) == 0 ) {
+ return ACL_MANAGE;
+ }
+
+ return( ACL_INVALID_ACCESS );
+}
+
+static char *
+safe_strncopy( char *ptr, const char *src, size_t n, struct berval *buf )
+{
+ while ( ptr + n >= buf->bv_val + buf->bv_len ) {
+ char *tmp = ch_realloc( buf->bv_val, 2*buf->bv_len );
+ if ( tmp == NULL ) {
+ return NULL;
+ }
+ ptr = tmp + (ptr - buf->bv_val);
+ buf->bv_val = tmp;
+ buf->bv_len *= 2;
+ }
+
+ return lutil_strncopy( ptr, src, n );
+}
+
+static char *
+safe_strcopy( char *ptr, const char *s, struct berval *buf )
+{
+ size_t n = strlen( s );
+
+ return safe_strncopy( ptr, s, n, buf );
+}
+
+static char *
+safe_strbvcopy( char *ptr, const struct berval *bv, struct berval *buf )
+{
+ return safe_strncopy( ptr, bv->bv_val, bv->bv_len, buf );
+}
+
+#define acl_safe_strcopy( ptr, s ) safe_strcopy( (ptr), (s), &aclbuf )
+#define acl_safe_strncopy( ptr, s, n ) safe_strncopy( (ptr), (s), (n), &aclbuf )
+#define acl_safe_strbvcopy( ptr, bv ) safe_strbvcopy( (ptr), (bv), &aclbuf )
+
+static char *
+dnaccess2text( slap_dn_access *bdn, char *ptr, int is_realdn )
+{
+ *ptr++ = ' ';
+
+ if ( is_realdn ) {
+ ptr = acl_safe_strcopy( ptr, "real" );
+ }
+
+ if ( ber_bvccmp( &bdn->a_pat, '*' ) ||
+ bdn->a_style == ACL_STYLE_ANONYMOUS ||
+ bdn->a_style == ACL_STYLE_USERS ||
+ bdn->a_style == ACL_STYLE_SELF )
+ {
+ if ( is_realdn ) {
+ assert( ! ber_bvccmp( &bdn->a_pat, '*' ) );
+ }
+
+ ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat );
+ if ( bdn->a_style == ACL_STYLE_SELF && bdn->a_self_level != 0 ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), ".level{%d}", bdn->a_self_level );
+ if ( n > 0 ) {
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ } /* else ? */
+ }
+
+ } else {
+ ptr = acl_safe_strcopy( ptr, "dn." );
+ if ( bdn->a_style == ACL_STYLE_BASE )
+ ptr = acl_safe_strcopy( ptr, style_base );
+ else
+ ptr = acl_safe_strcopy( ptr, style_strings[bdn->a_style] );
+ if ( bdn->a_style == ACL_STYLE_LEVEL ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), "{%d}", bdn->a_level );
+ if ( n > 0 ) {
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ } /* else ? */
+ }
+ if ( bdn->a_expand ) {
+ ptr = acl_safe_strcopy( ptr, ",expand" );
+ }
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &bdn->a_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+ return ptr;
+}
+
+static char *
+access2text( Access *b, char *ptr )
+{
+ char maskbuf[ACCESSMASK_MAXLEN];
+
+ ptr = acl_safe_strcopy( ptr, "\tby" );
+
+ if ( !BER_BVISEMPTY( &b->a_dn_pat ) ) {
+ ptr = dnaccess2text( &b->a_dn, ptr, 0 );
+ }
+ if ( b->a_dn_at ) {
+ ptr = acl_safe_strcopy( ptr, " dnattr=" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_dn_at->ad_cname );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_realdn_pat ) ) {
+ ptr = dnaccess2text( &b->a_realdn, ptr, 1 );
+ }
+ if ( b->a_realdn_at ) {
+ ptr = acl_safe_strcopy( ptr, " realdnattr=" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_realdn_at->ad_cname );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_group_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " group/" );
+ ptr = acl_safe_strcopy( ptr, b->a_group_oc ?
+ b->a_group_oc->soc_cname.bv_val : SLAPD_GROUP_CLASS );
+ ptr = acl_safe_strcopy( ptr, "/" );
+ ptr = acl_safe_strcopy( ptr, b->a_group_at ?
+ b->a_group_at->ad_cname.bv_val : SLAPD_GROUP_ATTR );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_group_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_group_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_peername_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " peername" );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_peername_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_peername_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_sockname_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " sockname" );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockname_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_sockname_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_domain_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " domain" );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_domain_style] );
+ if ( b->a_domain_expand ) {
+ ptr = acl_safe_strcopy( ptr, ",expand" );
+ }
+ ptr = acl_safe_strcopy( ptr, "=" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_domain_pat );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_sockurl_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " sockurl" );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_sockurl_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_sockurl_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+
+ if ( !BER_BVISEMPTY( &b->a_set_pat ) ) {
+ ptr = acl_safe_strcopy( ptr, " set" );
+ ptr = acl_safe_strcopy( ptr, "." );
+ ptr = acl_safe_strcopy( ptr, style_strings[b->a_set_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &b->a_set_pat );
+ ptr = acl_safe_strcopy( ptr, "\"" );
+ }
+
+#ifdef SLAP_DYNACL
+ if ( b->a_dynacl ) {
+ slap_dynacl_t *da;
+
+ for ( da = b->a_dynacl; da; da = da->da_next ) {
+ if ( da->da_unparse ) {
+ struct berval bv = BER_BVNULL;
+ (void)( *da->da_unparse )( da->da_private, &bv );
+ assert( !BER_BVISNULL( &bv ) );
+ ptr = acl_safe_strbvcopy( ptr, &bv );
+ ch_free( bv.bv_val );
+ }
+ }
+ }
+#endif /* SLAP_DYNACL */
+
+ /* Security Strength Factors */
+ if ( b->a_authz.sai_ssf ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), " ssf=%u",
+ b->a_authz.sai_ssf );
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ }
+ if ( b->a_authz.sai_transport_ssf ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), " transport_ssf=%u",
+ b->a_authz.sai_transport_ssf );
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ }
+ if ( b->a_authz.sai_tls_ssf ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), " tls_ssf=%u",
+ b->a_authz.sai_tls_ssf );
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ }
+ if ( b->a_authz.sai_sasl_ssf ) {
+ char buf[SLAP_TEXT_BUFLEN];
+ int n = snprintf( buf, sizeof(buf), " sasl_ssf=%u",
+ b->a_authz.sai_sasl_ssf );
+ ptr = acl_safe_strncopy( ptr, buf, n );
+ }
+
+ ptr = acl_safe_strcopy( ptr, " " );
+ if ( b->a_dn_self ) {
+ ptr = acl_safe_strcopy( ptr, "self" );
+ } else if ( b->a_realdn_self ) {
+ ptr = acl_safe_strcopy( ptr, "realself" );
+ }
+ ptr = acl_safe_strcopy( ptr, accessmask2str( b->a_access_mask, maskbuf, 0 ));
+ if ( !maskbuf[0] ) ptr--;
+
+ if( b->a_type == ACL_BREAK ) {
+ ptr = acl_safe_strcopy( ptr, " break" );
+
+ } else if( b->a_type == ACL_CONTINUE ) {
+ ptr = acl_safe_strcopy( ptr, " continue" );
+
+ } else if( b->a_type != ACL_STOP ) {
+ ptr = acl_safe_strcopy( ptr, " unknown-control" );
+ } else {
+ if ( !maskbuf[0] ) ptr = acl_safe_strcopy( ptr, " stop" );
+ }
+ ptr = acl_safe_strcopy( ptr, "\n" );
+
+ return ptr;
+}
+
+void
+acl_unparse( AccessControl *a, struct berval *bv )
+{
+ Access *b;
+ char *ptr;
+ int to = 0;
+
+ if ( BER_BVISNULL( &aclbuf ) ) {
+ aclbuf.bv_val = ch_malloc( ACLBUF_CHUNKSIZE );
+ aclbuf.bv_len = ACLBUF_CHUNKSIZE;
+ }
+
+ bv->bv_len = 0;
+
+ ptr = aclbuf.bv_val;
+
+ ptr = acl_safe_strcopy( ptr, "to" );
+ if ( !BER_BVISNULL( &a->acl_dn_pat ) ) {
+ to++;
+ ptr = acl_safe_strcopy( ptr, " dn." );
+ if ( a->acl_dn_style == ACL_STYLE_BASE )
+ ptr = acl_safe_strcopy( ptr, style_base );
+ else
+ ptr = acl_safe_strcopy( ptr, style_strings[a->acl_dn_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &a->acl_dn_pat );
+ ptr = acl_safe_strcopy( ptr, "\"\n" );
+ }
+
+ if ( a->acl_filter != NULL ) {
+ struct berval fbv = BER_BVNULL;
+
+ to++;
+ filter2bv( a->acl_filter, &fbv );
+ ptr = acl_safe_strcopy( ptr, " filter=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &fbv );
+ ptr = acl_safe_strcopy( ptr, "\"\n" );
+ ch_free( fbv.bv_val );
+ }
+
+ if ( a->acl_attrs != NULL ) {
+ int first = 1;
+ AttributeName *an;
+ to++;
+
+ ptr = acl_safe_strcopy( ptr, " attrs=" );
+ for ( an = a->acl_attrs; an && !BER_BVISNULL( &an->an_name ); an++ ) {
+ if ( ! first ) ptr = acl_safe_strcopy( ptr, ",");
+ if (an->an_oc) {
+ ptr = acl_safe_strcopy( ptr, ( an->an_flags & SLAP_AN_OCEXCLUDE ) ? "!" : "@" );
+ ptr = acl_safe_strbvcopy( ptr, &an->an_oc->soc_cname );
+
+ } else {
+ ptr = acl_safe_strbvcopy( ptr, &an->an_name );
+ }
+ first = 0;
+ }
+ ptr = acl_safe_strcopy( ptr, "\n" );
+ }
+
+ if ( !BER_BVISNULL( &a->acl_attrval ) ) {
+ to++;
+ ptr = acl_safe_strcopy( ptr, " val." );
+ if ( a->acl_attrval_style == ACL_STYLE_BASE &&
+ a->acl_attrs[0].an_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ ptr = acl_safe_strcopy( ptr, style_base );
+ else
+ ptr = acl_safe_strcopy( ptr, style_strings[a->acl_attrval_style] );
+ ptr = acl_safe_strcopy( ptr, "=\"" );
+ ptr = acl_safe_strbvcopy( ptr, &a->acl_attrval );
+ ptr = acl_safe_strcopy( ptr, "\"\n" );
+ }
+
+ if ( !to ) {
+ ptr = acl_safe_strcopy( ptr, " *\n" );
+ }
+
+ for ( b = a->acl_access; b != NULL; b = b->a_next ) {
+ ptr = access2text( b, ptr );
+ }
+ *ptr = '\0';
+ bv->bv_val = aclbuf.bv_val;
+ bv->bv_len = ptr - bv->bv_val;
+}
+
+#ifdef LDAP_DEBUG
+static void
+print_acl( Backend *be, AccessControl *a )
+{
+ struct berval bv;
+
+ acl_unparse( a, &bv );
+ fprintf( stderr, "%s ACL: access %s\n",
+ be == NULL ? "Global" : "Backend", bv.bv_val );
+}
+#endif /* LDAP_DEBUG */
diff --git a/servers/slapd/ad.c b/servers/slapd/ad.c
new file mode 100644
index 0000000..6b643f5
--- /dev/null
+++ b/servers/slapd/ad.c
@@ -0,0 +1,1313 @@
+/* ad.c - routines for dealing with attribute descriptions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+static struct berval bv_no_attrs = BER_BVC( LDAP_NO_ATTRS );
+static struct berval bv_all_user_attrs = BER_BVC( "*" );
+static struct berval bv_all_operational_attrs = BER_BVC( "+" );
+
+static AttributeName anlist_no_attrs[] = {
+ { BER_BVC( LDAP_NO_ATTRS ), NULL, 0, NULL },
+ { BER_BVNULL, NULL, 0, NULL }
+};
+
+static AttributeName anlist_all_user_attributes[] = {
+ { BER_BVC( LDAP_ALL_USER_ATTRIBUTES ), NULL, 0, NULL },
+ { BER_BVNULL, NULL, 0, NULL }
+};
+
+static AttributeName anlist_all_operational_attributes[] = {
+ { BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ), NULL, 0, NULL },
+ { BER_BVNULL, NULL, 0, NULL }
+};
+
+static AttributeName anlist_all_attributes[] = {
+ { BER_BVC( LDAP_ALL_USER_ATTRIBUTES ), NULL, 0, NULL },
+ { BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ), NULL, 0, NULL },
+ { BER_BVNULL, NULL, 0, NULL }
+};
+
+AttributeName *slap_anlist_no_attrs = anlist_no_attrs;
+AttributeName *slap_anlist_all_user_attributes = anlist_all_user_attributes;
+AttributeName *slap_anlist_all_operational_attributes = anlist_all_operational_attributes;
+AttributeName *slap_anlist_all_attributes = anlist_all_attributes;
+
+struct berval * slap_bv_no_attrs = &bv_no_attrs;
+struct berval * slap_bv_all_user_attrs = &bv_all_user_attrs;
+struct berval * slap_bv_all_operational_attrs = &bv_all_operational_attrs;
+
+typedef struct Attr_option {
+ struct berval name; /* option name or prefix */
+ int prefix; /* NAME is a tag and range prefix */
+} Attr_option;
+
+static Attr_option lang_option = { BER_BVC("lang-"), 1 };
+
+/* Options sorted by name, and number of options */
+static Attr_option *options = &lang_option;
+static int option_count = 1;
+
+static int msad_range_hack = 0;
+
+static int ad_count;
+
+static Attr_option *ad_find_option_definition( const char *opt, int optlen );
+
+int ad_keystring(
+ struct berval *bv )
+{
+ ber_len_t i;
+
+ if( !AD_LEADCHAR( bv->bv_val[0] ) ) {
+ return 1;
+ }
+
+ for( i=1; i<bv->bv_len; i++ ) {
+ if( !AD_CHAR( bv->bv_val[i] )) {
+ if ( msad_range_hack && bv->bv_val[i] == '=' )
+ continue;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void ad_destroy( AttributeDescription *ad )
+{
+ AttributeDescription *n;
+
+ for (; ad != NULL; ad = n) {
+ n = ad->ad_next;
+ ldap_memfree( ad );
+ }
+}
+
+/* Is there an AttributeDescription for this type that uses these tags? */
+AttributeDescription * ad_find_tags(
+ AttributeType *type,
+ struct berval *tags )
+{
+ AttributeDescription *ad;
+
+ ldap_pvt_thread_mutex_lock( &type->sat_ad_mutex );
+ for (ad = type->sat_ad; ad; ad=ad->ad_next)
+ {
+ if (ad->ad_tags.bv_len == tags->bv_len &&
+ !strcasecmp(ad->ad_tags.bv_val, tags->bv_val))
+ break;
+ }
+ ldap_pvt_thread_mutex_unlock( &type->sat_ad_mutex );
+ return ad;
+}
+
+int slap_str2ad(
+ const char *str,
+ AttributeDescription **ad,
+ const char **text )
+{
+ struct berval bv;
+ bv.bv_val = (char *) str;
+ bv.bv_len = strlen( str );
+
+ return slap_bv2ad( &bv, ad, text );
+}
+
+static char *strchrlen(
+ const char *beg,
+ const char *end,
+ const char ch,
+ int *len )
+{
+ const char *p;
+
+ for( p=beg; p < end && *p; p++ ) {
+ if( *p == ch ) {
+ *len = p - beg;
+ return (char *) p;
+ }
+ }
+
+ *len = p - beg;
+ return NULL;
+}
+
+int slap_bv2ad(
+ struct berval *bv,
+ AttributeDescription **ad,
+ const char **text )
+{
+ int rtn = LDAP_UNDEFINED_TYPE;
+ AttributeDescription desc, *d2;
+ char *name, *options, *optn;
+ char *opt, *next;
+ int ntags;
+ int tagslen;
+
+ /* hardcoded limits for speed */
+#define MAX_TAGGING_OPTIONS 128
+ struct berval tags[MAX_TAGGING_OPTIONS+1];
+#define MAX_TAGS_LEN 1024
+ char tagbuf[MAX_TAGS_LEN];
+
+ assert( ad != NULL );
+ assert( *ad == NULL ); /* temporary */
+
+ if( bv == NULL || BER_BVISNULL( bv ) || BER_BVISEMPTY( bv ) ) {
+ *text = "empty AttributeDescription";
+ return rtn;
+ }
+
+ /* make sure description is IA5 */
+ if( ad_keystring( bv ) ) {
+ *text = "AttributeDescription contains inappropriate characters";
+ return rtn;
+ }
+
+ /* find valid base attribute type; parse in place */
+ desc.ad_cname = *bv;
+ desc.ad_flags = 0;
+ BER_BVZERO( &desc.ad_tags );
+ name = bv->bv_val;
+ options = ber_bvchr( bv, ';' );
+ if ( options != NULL && (unsigned) ( options - name ) < bv->bv_len ) {
+ /* don't go past the end of the berval! */
+ desc.ad_cname.bv_len = options - name;
+ } else {
+ options = NULL;
+ }
+ desc.ad_type = at_bvfind( &desc.ad_cname );
+ if( desc.ad_type == NULL ) {
+ *text = "attribute type undefined";
+ return rtn;
+ }
+
+ if( is_at_operational( desc.ad_type ) && options != NULL ) {
+ *text = "operational attribute with options undefined";
+ return rtn;
+ }
+
+ /*
+ * parse options in place
+ */
+ ntags = 0;
+ tagslen = 0;
+ optn = bv->bv_val + bv->bv_len;
+
+ for( opt=options; opt != NULL; opt=next ) {
+ Attr_option *aopt;
+ int optlen;
+ opt++;
+ next = strchrlen( opt, optn, ';', &optlen );
+
+ if( optlen == 0 ) {
+ *text = "zero length option is invalid";
+ return rtn;
+
+ } else if ( optlen == STRLENOF("binary") &&
+ strncasecmp( opt, "binary", STRLENOF("binary") ) == 0 )
+ {
+ /* binary option */
+ if( slap_ad_is_binary( &desc ) ) {
+ *text = "option \"binary\" specified multiple times";
+ return rtn;
+ }
+
+ if( !slap_syntax_is_binary( desc.ad_type->sat_syntax )) {
+ /* not stored in binary, disallow option */
+ *text = "option \"binary\" not supported with type";
+ return rtn;
+ }
+
+ desc.ad_flags |= SLAP_DESC_BINARY;
+ continue;
+
+ } else if (( aopt = ad_find_option_definition( opt, optlen )) ) {
+ int i;
+
+ if( opt[optlen-1] == '-' ||
+ ( aopt->name.bv_val[aopt->name.bv_len-1] == '=' && msad_range_hack )) {
+ desc.ad_flags |= SLAP_DESC_TAG_RANGE;
+ }
+
+ if( ntags >= MAX_TAGGING_OPTIONS ) {
+ *text = "too many tagging options";
+ return rtn;
+ }
+
+ /*
+ * tags should be presented in sorted order,
+ * so run the array in reverse.
+ */
+ for( i=ntags-1; i>=0; i-- ) {
+ int rc;
+
+ rc = strncasecmp( opt, tags[i].bv_val,
+ (unsigned) optlen < tags[i].bv_len
+ ? (unsigned) optlen : tags[i].bv_len );
+
+ if( rc == 0 && (unsigned)optlen == tags[i].bv_len ) {
+ /* duplicate (ignore) */
+ ntags--;
+ goto done;
+
+ } else if ( rc > 0 ||
+ ( rc == 0 && (unsigned)optlen > tags[i].bv_len ))
+ {
+ AC_MEMCPY( &tags[i+2], &tags[i+1],
+ (ntags-i-1)*sizeof(struct berval) );
+ tags[i+1].bv_val = opt;
+ tags[i+1].bv_len = optlen;
+ goto done;
+ }
+ }
+
+ if( ntags ) {
+ AC_MEMCPY( &tags[1], &tags[0],
+ ntags*sizeof(struct berval) );
+ }
+ tags[0].bv_val = opt;
+ tags[0].bv_len = optlen;
+
+done:;
+ tagslen += optlen + 1;
+ ntags++;
+
+ } else {
+ *text = "unrecognized option";
+ return rtn;
+ }
+ }
+
+ if( ntags > 0 ) {
+ int i;
+
+ if( tagslen > MAX_TAGS_LEN ) {
+ *text = "tagging options too long";
+ return rtn;
+ }
+
+ desc.ad_tags.bv_val = tagbuf;
+ tagslen = 0;
+
+ for( i=0; i<ntags; i++ ) {
+ AC_MEMCPY( &desc.ad_tags.bv_val[tagslen],
+ tags[i].bv_val, tags[i].bv_len );
+
+ tagslen += tags[i].bv_len;
+ desc.ad_tags.bv_val[tagslen++] = ';';
+ }
+
+ desc.ad_tags.bv_val[--tagslen] = '\0';
+ desc.ad_tags.bv_len = tagslen;
+ }
+
+ /* see if a matching description is already cached */
+ for (d2 = desc.ad_type->sat_ad; d2; d2=d2->ad_next) {
+ if( d2->ad_flags != desc.ad_flags ) {
+ continue;
+ }
+ if( d2->ad_tags.bv_len != desc.ad_tags.bv_len ) {
+ continue;
+ }
+ if( d2->ad_tags.bv_len == 0 ) {
+ break;
+ }
+ if( strncasecmp( d2->ad_tags.bv_val, desc.ad_tags.bv_val,
+ desc.ad_tags.bv_len ) == 0 )
+ {
+ break;
+ }
+ }
+
+ /* Not found, add new one */
+ while (d2 == NULL) {
+ size_t dlen = 0;
+ ldap_pvt_thread_mutex_lock( &desc.ad_type->sat_ad_mutex );
+ /* check again now that we've locked */
+ for (d2 = desc.ad_type->sat_ad; d2; d2=d2->ad_next) {
+ if (d2->ad_flags != desc.ad_flags)
+ continue;
+ if (d2->ad_tags.bv_len != desc.ad_tags.bv_len)
+ continue;
+ if (d2->ad_tags.bv_len == 0)
+ break;
+ if (strncasecmp(d2->ad_tags.bv_val, desc.ad_tags.bv_val,
+ desc.ad_tags.bv_len) == 0)
+ break;
+ }
+ if (d2) {
+ ldap_pvt_thread_mutex_unlock( &desc.ad_type->sat_ad_mutex );
+ break;
+ }
+
+ /* Allocate a single contiguous block. If there are no
+ * options, we just need space for the AttrDesc structure.
+ * Otherwise, we need to tack on the full name length +
+ * options length, + maybe tagging options length again.
+ */
+ if (desc.ad_tags.bv_len || desc.ad_flags != SLAP_DESC_NONE) {
+ dlen = desc.ad_type->sat_cname.bv_len + 1;
+ if (desc.ad_tags.bv_len) {
+ dlen += 1 + desc.ad_tags.bv_len;
+ }
+ if ( slap_ad_is_binary( &desc ) ) {
+ dlen += 1 + STRLENOF(";binary") + desc.ad_tags.bv_len;
+ }
+ }
+
+ d2 = ch_malloc(sizeof(AttributeDescription) + dlen);
+ d2->ad_next = NULL;
+ d2->ad_type = desc.ad_type;
+ d2->ad_flags = desc.ad_flags;
+ d2->ad_cname.bv_len = desc.ad_type->sat_cname.bv_len;
+ d2->ad_tags.bv_len = desc.ad_tags.bv_len;
+ ldap_pvt_thread_mutex_lock( &ad_index_mutex );
+ d2->ad_index = ++ad_count;
+ ldap_pvt_thread_mutex_unlock( &ad_index_mutex );
+
+ if (dlen == 0) {
+ d2->ad_cname.bv_val = d2->ad_type->sat_cname.bv_val;
+ d2->ad_tags.bv_val = NULL;
+ } else {
+ char *cp, *op, *lp;
+ int j;
+ d2->ad_cname.bv_val = (char *)(d2+1);
+ strcpy(d2->ad_cname.bv_val, d2->ad_type->sat_cname.bv_val);
+ cp = d2->ad_cname.bv_val + d2->ad_cname.bv_len;
+ if( slap_ad_is_binary( &desc ) ) {
+ op = cp;
+ lp = NULL;
+ if( desc.ad_tags.bv_len ) {
+ lp = desc.ad_tags.bv_val;
+ while( strncasecmp(lp, "binary", STRLENOF("binary")) < 0
+ && (lp = strchr( lp, ';' )) != NULL )
+ ++lp;
+ if( lp != desc.ad_tags.bv_val ) {
+ *cp++ = ';';
+ j = (lp
+ ? (unsigned) (lp - desc.ad_tags.bv_val - 1)
+ : strlen( desc.ad_tags.bv_val ));
+ cp = lutil_strncopy(cp, desc.ad_tags.bv_val, j);
+ }
+ }
+ cp = lutil_strcopy(cp, ";binary");
+ if( lp != NULL ) {
+ *cp++ = ';';
+ cp = lutil_strcopy(cp, lp);
+ }
+ d2->ad_cname.bv_len = cp - d2->ad_cname.bv_val;
+ if( desc.ad_tags.bv_len )
+ ldap_pvt_str2lower(op);
+ j = 1;
+ } else {
+ j = 0;
+ }
+ if( desc.ad_tags.bv_len ) {
+ lp = d2->ad_cname.bv_val + d2->ad_cname.bv_len + j;
+ if ( j == 0 )
+ *lp++ = ';';
+ d2->ad_tags.bv_val = lp;
+ strcpy(lp, desc.ad_tags.bv_val);
+ ldap_pvt_str2lower(lp);
+ if( j == 0 )
+ d2->ad_cname.bv_len += 1 + desc.ad_tags.bv_len;
+ }
+ }
+ /* Add new desc to list. We always want the bare Desc with
+ * no options to stay at the head of the list, assuming
+ * that one will be used most frequently.
+ */
+ if (desc.ad_type->sat_ad == NULL || dlen == 0) {
+ d2->ad_next = desc.ad_type->sat_ad;
+ desc.ad_type->sat_ad = d2;
+ } else {
+ d2->ad_next = desc.ad_type->sat_ad->ad_next;
+ desc.ad_type->sat_ad->ad_next = d2;
+ }
+ ldap_pvt_thread_mutex_unlock( &desc.ad_type->sat_ad_mutex );
+ }
+
+ if( *ad == NULL ) {
+ *ad = d2;
+ } else {
+ **ad = *d2;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int is_ad_subtags(
+ struct berval *subtagsbv,
+ struct berval *suptagsbv )
+{
+ const char *suptags, *supp, *supdelimp, *supn;
+ const char *subtags, *subp, *subdelimp, *subn;
+ int suplen, sublen;
+
+ subtags =subtagsbv->bv_val;
+ suptags =suptagsbv->bv_val;
+ subn = subtags + subtagsbv->bv_len;
+ supn = suptags + suptagsbv->bv_len;
+
+ for( supp=suptags ; supp; supp=supdelimp ) {
+ supdelimp = strchrlen( supp, supn, ';', &suplen );
+ if( supdelimp ) supdelimp++;
+
+ for( subp=subtags ; subp; subp=subdelimp ) {
+ subdelimp = strchrlen( subp, subn, ';', &sublen );
+ if( subdelimp ) subdelimp++;
+
+ if ( suplen > sublen
+ ? ( suplen-1 == sublen && supp[suplen-1] == '-'
+ && strncmp( supp, subp, sublen ) == 0 )
+ : ( ( suplen == sublen || supp[suplen-1] == '-' )
+ && strncmp( supp, subp, suplen ) == 0 ) )
+ {
+ goto match;
+ }
+ }
+
+ return 0;
+match:;
+ }
+ return 1;
+}
+
+int is_ad_subtype(
+ AttributeDescription *sub,
+ AttributeDescription *super
+)
+{
+ AttributeType *a;
+ int lr;
+
+ for ( a = sub->ad_type; a; a=a->sat_sup ) {
+ if ( a == super->ad_type ) break;
+ }
+ if( !a ) {
+ return 0;
+ }
+
+ /* ensure sub does support all flags of super */
+ lr = sub->ad_tags.bv_len ? SLAP_DESC_TAG_RANGE : 0;
+ if(( super->ad_flags & ( sub->ad_flags | lr )) != super->ad_flags ) {
+ return 0;
+ }
+
+ /* check for tagging options */
+ if ( super->ad_tags.bv_len == 0 )
+ return 1;
+ if ( sub->ad_tags.bv_len == 0 )
+ return 0;
+
+ return is_ad_subtags( &sub->ad_tags, &super->ad_tags );
+}
+
+int ad_inlist(
+ AttributeDescription *desc,
+ AttributeName *attrs )
+{
+ if (! attrs ) return 0;
+
+ for( ; attrs->an_name.bv_val; attrs++ ) {
+ AttributeType *a;
+ ObjectClass *oc;
+
+ if ( attrs->an_desc ) {
+ int lr;
+
+ if ( desc == attrs->an_desc ) {
+ return 1;
+ }
+
+ /*
+ * EXTENSION: if requested description is preceded by
+ * a '-' character, do not match on subtypes.
+ */
+ if ( attrs->an_name.bv_val[0] == '-' ) {
+ continue;
+ }
+
+ /* Is this a subtype of the requested attr? */
+ for (a = desc->ad_type; a; a=a->sat_sup) {
+ if ( a == attrs->an_desc->ad_type )
+ break;
+ }
+ if ( !a ) {
+ continue;
+ }
+ /* Does desc support all the requested flags? */
+ lr = desc->ad_tags.bv_len ? SLAP_DESC_TAG_RANGE : 0;
+ if(( attrs->an_desc->ad_flags & (desc->ad_flags | lr))
+ != attrs->an_desc->ad_flags ) {
+ continue;
+ }
+ /* Do the descs have compatible tags? */
+ if ( attrs->an_desc->ad_tags.bv_len == 0 ) {
+ return 1;
+ }
+ if ( desc->ad_tags.bv_len == 0) {
+ continue;
+ }
+ if ( is_ad_subtags( &desc->ad_tags,
+ &attrs->an_desc->ad_tags ) ) {
+ return 1;
+ }
+ continue;
+ }
+
+ if ( ber_bvccmp( &attrs->an_name, '*' ) ) {
+ if ( !is_at_operational( desc->ad_type ) ) {
+ return 1;
+ }
+ continue;
+ }
+
+ if ( ber_bvccmp( &attrs->an_name, '+' ) ) {
+ if ( is_at_operational( desc->ad_type ) ) {
+ return 1;
+ }
+ continue;
+ }
+
+ /*
+ * EXTENSION: see if requested description is @objectClass
+ * if so, return attributes which the class requires/allows
+ * else if requested description is !objectClass, return
+ * attributes which the class does not require/allow
+ */
+ if ( !( attrs->an_flags & SLAP_AN_OCINITED )) {
+ if( attrs->an_name.bv_val ) {
+ switch( attrs->an_name.bv_val[0] ) {
+ case '@': /* @objectClass */
+ case '+': /* +objectClass (deprecated) */
+ case '!': { /* exclude */
+ struct berval ocname;
+ ocname.bv_len = attrs->an_name.bv_len - 1;
+ ocname.bv_val = &attrs->an_name.bv_val[1];
+ oc = oc_bvfind( &ocname );
+ if ( oc && attrs->an_name.bv_val[0] == '!' ) {
+ attrs->an_flags |= SLAP_AN_OCEXCLUDE;
+ } else {
+ attrs->an_flags &= ~SLAP_AN_OCEXCLUDE;
+ }
+ } break;
+
+ default: /* old (deprecated) way */
+ oc = oc_bvfind( &attrs->an_name );
+ }
+ attrs->an_oc = oc;
+ }
+ attrs->an_flags |= SLAP_AN_OCINITED;
+ }
+ oc = attrs->an_oc;
+ if( oc != NULL ) {
+ if ( attrs->an_flags & SLAP_AN_OCEXCLUDE ) {
+ if ( oc == slap_schema.si_oc_extensibleObject ) {
+ /* extensibleObject allows the return of anything */
+ return 0;
+ }
+
+ if( oc->soc_required ) {
+ /* allow return of required attributes */
+ int i;
+
+ for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
+ for (a = desc->ad_type; a; a=a->sat_sup) {
+ if ( a == oc->soc_required[i] ) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ if( oc->soc_allowed ) {
+ /* allow return of allowed attributes */
+ int i;
+ for ( i = 0; oc->soc_allowed[i] != NULL; i++ ) {
+ for (a = desc->ad_type; a; a=a->sat_sup) {
+ if ( a == oc->soc_allowed[i] ) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
+ }
+
+ if ( oc == slap_schema.si_oc_extensibleObject ) {
+ /* extensibleObject allows the return of anything */
+ return 1;
+ }
+
+ if( oc->soc_required ) {
+ /* allow return of required attributes */
+ int i;
+
+ for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
+ for (a = desc->ad_type; a; a=a->sat_sup) {
+ if ( a == oc->soc_required[i] ) {
+ return 1;
+ }
+ }
+ }
+ }
+
+ if( oc->soc_allowed ) {
+ /* allow return of allowed attributes */
+ int i;
+ for ( i = 0; oc->soc_allowed[i] != NULL; i++ ) {
+ for (a = desc->ad_type; a; a=a->sat_sup) {
+ if ( a == oc->soc_allowed[i] ) {
+ return 1;
+ }
+ }
+ }
+ }
+
+ } else {
+ const char *text;
+
+ /* give it a chance of being retrieved by a proxy... */
+ (void)slap_bv2undef_ad( &attrs->an_name,
+ &attrs->an_desc, &text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+ }
+ }
+
+ return 0;
+}
+
+
+int slap_str2undef_ad(
+ const char *str,
+ AttributeDescription **ad,
+ const char **text,
+ unsigned flags )
+{
+ struct berval bv;
+ bv.bv_val = (char *) str;
+ bv.bv_len = strlen( str );
+
+ return slap_bv2undef_ad( &bv, ad, text, flags );
+}
+
+int slap_bv2undef_ad(
+ struct berval *bv,
+ AttributeDescription **ad,
+ const char **text,
+ unsigned flags )
+{
+ AttributeDescription *desc;
+ AttributeType *at;
+
+ assert( ad != NULL );
+
+ if( bv == NULL || bv->bv_len == 0 ) {
+ *text = "empty AttributeDescription";
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ /* make sure description is IA5 */
+ if( ad_keystring( bv ) ) {
+ *text = "AttributeDescription contains inappropriate characters";
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ /* use the appropriate type */
+ if ( flags & SLAP_AD_PROXIED ) {
+ at = slap_schema.si_at_proxied;
+
+ } else {
+ at = slap_schema.si_at_undefined;
+ }
+
+ for( desc = at->sat_ad; desc; desc=desc->ad_next ) {
+ if( desc->ad_cname.bv_len == bv->bv_len &&
+ !strcasecmp( desc->ad_cname.bv_val, bv->bv_val ) )
+ {
+ break;
+ }
+ }
+
+ if( !desc ) {
+ if ( flags & SLAP_AD_NOINSERT ) {
+ *text = NULL;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ desc = ch_malloc(sizeof(AttributeDescription) + 1 +
+ bv->bv_len);
+
+ desc->ad_flags = SLAP_DESC_NONE;
+ BER_BVZERO( &desc->ad_tags );
+
+ desc->ad_cname.bv_len = bv->bv_len;
+ desc->ad_cname.bv_val = (char *)(desc+1);
+ strncpy(desc->ad_cname.bv_val, bv->bv_val, bv->bv_len);
+ desc->ad_cname.bv_val[bv->bv_len] = '\0';
+
+ /* canonical to upper case */
+ ldap_pvt_str2upper( desc->ad_cname.bv_val );
+
+ /* shouldn't we protect this for concurrency? */
+ desc->ad_type = at;
+ desc->ad_index = 0;
+ ldap_pvt_thread_mutex_lock( &ad_undef_mutex );
+ desc->ad_next = desc->ad_type->sat_ad;
+ desc->ad_type->sat_ad = desc;
+ ldap_pvt_thread_mutex_unlock( &ad_undef_mutex );
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s attributeDescription \"%s\" inserted.\n",
+ ( flags & SLAP_AD_PROXIED ) ? "PROXIED" : "UNKNOWN",
+ desc->ad_cname.bv_val );
+ }
+
+ if( !*ad ) {
+ *ad = desc;
+ } else {
+ **ad = *desc;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+AttributeDescription *
+slap_bv2tmp_ad(
+ struct berval *bv,
+ void *memctx )
+{
+ AttributeDescription *ad =
+ slap_sl_mfuncs.bmf_malloc( sizeof(AttributeDescription) +
+ bv->bv_len + 1, memctx );
+
+ ad->ad_cname.bv_val = (char *)(ad+1);
+ strncpy( ad->ad_cname.bv_val, bv->bv_val, bv->bv_len+1 );
+ ad->ad_cname.bv_len = bv->bv_len;
+ ad->ad_flags = SLAP_DESC_TEMPORARY;
+ ad->ad_type = slap_schema.si_at_undefined;
+
+ return ad;
+}
+
+static int
+undef_promote(
+ AttributeType *at,
+ char *name,
+ AttributeType *nat )
+{
+ AttributeDescription **u_ad, **n_ad;
+
+ /* Get to last ad on the new type */
+ for ( n_ad = &nat->sat_ad; *n_ad; n_ad = &(*n_ad)->ad_next ) ;
+
+ for ( u_ad = &at->sat_ad; *u_ad; ) {
+ struct berval bv;
+
+ ber_str2bv( name, 0, 0, &bv );
+
+ /* remove iff undef == name or undef == name;tag */
+ if ( (*u_ad)->ad_cname.bv_len >= bv.bv_len
+ && strncasecmp( (*u_ad)->ad_cname.bv_val, bv.bv_val, bv.bv_len ) == 0
+ && ( (*u_ad)->ad_cname.bv_val[ bv.bv_len ] == '\0'
+ || (*u_ad)->ad_cname.bv_val[ bv.bv_len ] == ';' ) )
+ {
+ AttributeDescription *tmp = *u_ad;
+
+ *u_ad = (*u_ad)->ad_next;
+
+ tmp->ad_type = nat;
+ tmp->ad_next = NULL;
+ /* ad_cname was contiguous, no leak here */
+ tmp->ad_cname = nat->sat_cname;
+ ldap_pvt_thread_mutex_lock( &ad_index_mutex );
+ tmp->ad_index = ++ad_count;
+ ldap_pvt_thread_mutex_unlock( &ad_index_mutex );
+ *n_ad = tmp;
+ n_ad = &tmp->ad_next;
+ } else {
+ u_ad = &(*u_ad)->ad_next;
+ }
+ }
+
+ return 0;
+}
+
+int
+slap_ad_undef_promote(
+ char *name,
+ AttributeType *at )
+{
+ int rc;
+
+ ldap_pvt_thread_mutex_lock( &ad_undef_mutex );
+
+ rc = undef_promote( slap_schema.si_at_undefined, name, at );
+ if ( rc == 0 ) {
+ rc = undef_promote( slap_schema.si_at_proxied, name, at );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &ad_undef_mutex );
+
+ return rc;
+}
+
+int
+an_find(
+ AttributeName *a,
+ struct berval *s
+)
+{
+ if( a == NULL ) return 0;
+
+ for ( ; a->an_name.bv_val; a++ ) {
+ if ( a->an_name.bv_len != s->bv_len) continue;
+ if ( strcasecmp( s->bv_val, a->an_name.bv_val ) == 0 ) {
+ return( 1 );
+ }
+ }
+
+ return( 0 );
+}
+
+/*
+ * Convert a delimited string into a list of AttributeNames; add
+ * on to an existing list if it was given. If the string is not
+ * a valid attribute name, if a '-' is prepended it is skipped
+ * and the remaining name is tried again; if a '@' (or '+') is
+ * prepended, an objectclass name is searched instead; if a '!'
+ * is prepended, the objectclass name is negated.
+ *
+ * NOTE: currently, if a valid attribute name is not found, the
+ * same string is also checked as valid objectclass name; however,
+ * this behavior is deprecated.
+ */
+AttributeName *
+str2anlist( AttributeName *an, char *in, const char *brkstr )
+{
+ char *str;
+ char *s;
+ char *lasts;
+ int i, j;
+ const char *text;
+ AttributeName *anew;
+
+ /* find last element in list */
+ i = 0;
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ) ; i++)
+ ;
+ }
+
+ /* protect the input string from strtok */
+ str = ch_strdup( in );
+
+ /* Count words in string */
+ j = 1;
+ for ( s = str; *s; s++ ) {
+ if ( strchr( brkstr, *s ) != NULL ) {
+ j++;
+ }
+ }
+
+ an = ch_realloc( an, ( i + j + 1 ) * sizeof( AttributeName ) );
+ anew = an + i;
+ for ( s = ldap_pvt_strtok( str, brkstr, &lasts );
+ s != NULL;
+ s = ldap_pvt_strtok( NULL, brkstr, &lasts ) )
+ {
+ /* put a stop mark */
+ BER_BVZERO( &anew[1].an_name );
+
+ anew->an_desc = NULL;
+ anew->an_oc = NULL;
+ anew->an_flags = 0;
+ ber_str2bv(s, 0, 1, &anew->an_name);
+ slap_bv2ad(&anew->an_name, &anew->an_desc, &text);
+ if ( !anew->an_desc ) {
+ switch( anew->an_name.bv_val[0] ) {
+ case '-': {
+ struct berval adname;
+ adname.bv_len = anew->an_name.bv_len - 1;
+ adname.bv_val = &anew->an_name.bv_val[1];
+ slap_bv2ad(&adname, &anew->an_desc, &text);
+ if ( !anew->an_desc ) {
+ goto reterr;
+ }
+ } break;
+
+ case '@':
+ case '+': /* (deprecated) */
+ case '!': {
+ struct berval ocname;
+ ocname.bv_len = anew->an_name.bv_len - 1;
+ ocname.bv_val = &anew->an_name.bv_val[1];
+ anew->an_oc = oc_bvfind( &ocname );
+ if ( !anew->an_oc ) {
+ goto reterr;
+ }
+
+ if ( anew->an_name.bv_val[0] == '!' ) {
+ anew->an_flags |= SLAP_AN_OCEXCLUDE;
+ }
+ } break;
+
+ default:
+ /* old (deprecated) way */
+ anew->an_oc = oc_bvfind( &anew->an_name );
+ if ( !anew->an_oc ) {
+ goto reterr;
+ }
+ }
+ }
+ anew->an_flags |= SLAP_AN_OCINITED;
+ anew++;
+ }
+
+ BER_BVZERO( &anew->an_name );
+ free( str );
+ return( an );
+
+reterr:
+ anlist_free( an, 1, NULL );
+
+ /*
+ * overwrites input string
+ * on error!
+ */
+ strcpy( in, s );
+ free( str );
+ return NULL;
+}
+
+void
+anlist_free( AttributeName *an, int freename, void *ctx )
+{
+ if ( an == NULL ) {
+ return;
+ }
+
+ if ( freename ) {
+ int i;
+
+ for ( i = 0; an[i].an_name.bv_val; i++ ) {
+ ber_memfree_x( an[i].an_name.bv_val, ctx );
+ }
+ }
+
+ ber_memfree_x( an, ctx );
+}
+
+char **anlist2charray_x( AttributeName *an, int dup, void *ctx )
+{
+ char **attrs;
+ int i;
+
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ )
+ ;
+ attrs = (char **) slap_sl_malloc( (i + 1) * sizeof(char *), ctx );
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ if ( dup )
+ attrs[i] = ch_strdup( an[i].an_name.bv_val );
+ else
+ attrs[i] = an[i].an_name.bv_val;
+ }
+ attrs[i] = NULL;
+ } else {
+ attrs = NULL;
+ }
+
+ return attrs;
+}
+
+char **anlist2charray( AttributeName *an, int dup )
+{
+ return anlist2charray_x( an, dup, NULL );
+}
+
+char**
+anlist2attrs( AttributeName * anlist )
+{
+ int i, j, k = 0;
+ int n;
+ char **attrs;
+ ObjectClass *oc;
+
+ if ( anlist == NULL )
+ return NULL;
+
+ for ( i = 0; anlist[i].an_name.bv_val; i++ ) {
+ if ( ( oc = anlist[i].an_oc ) ) {
+ for ( j = 0; oc->soc_required && oc->soc_required[j]; j++ ) ;
+ k += j;
+ for ( j = 0; oc->soc_allowed && oc->soc_allowed[j]; j++ ) ;
+ k += j;
+ }
+ }
+
+ if ( i == 0 )
+ return NULL;
+
+ attrs = anlist2charray( anlist, 1 );
+
+ n = i;
+
+ if ( k )
+ attrs = (char **) ch_realloc( attrs, (i + k + 1) * sizeof( char * ));
+
+ for ( i = 0; anlist[i].an_name.bv_val; i++ ) {
+ if ( ( oc = anlist[i].an_oc ) ) {
+ for ( j = 0; oc->soc_required && oc->soc_required[j]; j++ ) {
+ attrs[n++] = ch_strdup(
+ oc->soc_required[j]->sat_cname.bv_val );
+ }
+ for ( j = 0; oc->soc_allowed && oc->soc_allowed[j]; j++ ) {
+ attrs[n++] = ch_strdup(
+ oc->soc_allowed[j]->sat_cname.bv_val );
+ }
+ }
+ }
+
+ if ( attrs )
+ attrs[n] = NULL;
+
+ i = 0;
+ while ( attrs && attrs[i] ) {
+ if ( *attrs[i] == '@' ) {
+ ch_free( attrs[i] );
+ for ( j = i; attrs[j]; j++ ) {
+ attrs[j] = attrs[j+1];
+ }
+ } else {
+ i++;
+ }
+ }
+
+ for ( i = 0; attrs && attrs[i]; i++ ) {
+ j = i + 1;
+ while ( attrs && attrs[j] ) {
+ if ( !strcmp( attrs[i], attrs[j] )) {
+ ch_free( attrs[j] );
+ for ( k = j; attrs && attrs[k]; k++ ) {
+ attrs[k] = attrs[k+1];
+ }
+ } else {
+ j++;
+ }
+ }
+ }
+
+ if ( i != n )
+ attrs = (char **) ch_realloc( attrs, (i+1) * sizeof( char * ));
+
+ return attrs;
+}
+
+#define LBUFSIZ 80
+AttributeName*
+file2anlist( AttributeName *an, const char *fname, const char *brkstr )
+{
+ FILE *fp;
+ char *line = NULL;
+ char *lcur = NULL;
+ char *c;
+ size_t lmax = LBUFSIZ;
+
+ fp = fopen( fname, "r" );
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_ANY,
+ "get_attrs_from_file: failed to open attribute list file "
+ "\"%s\": %s\n", fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ) );
+ return NULL;
+ }
+
+ lcur = line = (char *) ch_malloc( lmax );
+ if ( !line ) {
+ Debug( LDAP_DEBUG_ANY,
+ "get_attrs_from_file: could not allocate memory\n" );
+ fclose(fp);
+ return NULL;
+ }
+
+ while ( fgets( lcur, LBUFSIZ, fp ) != NULL ) {
+ if ( ( c = strchr( lcur, '\n' ) ) ) {
+ if ( c == line ) {
+ *c = '\0';
+ } else if ( *(c-1) == '\r' ) {
+ *(c-1) = '\0';
+ } else {
+ *c = '\0';
+ }
+ } else {
+ lmax += LBUFSIZ;
+ line = (char *) ch_realloc( line, lmax );
+ if ( !line ) {
+ Debug( LDAP_DEBUG_ANY,
+ "get_attrs_from_file: could not allocate memory\n" );
+ fclose(fp);
+ return NULL;
+ }
+ lcur = line + strlen( line );
+ continue;
+ }
+ an = str2anlist( an, line, brkstr );
+ if ( an == NULL )
+ break;
+ lcur = line;
+ }
+ ch_free( line );
+ fclose(fp);
+ return an;
+}
+#undef LBUFSIZ
+
+/* Define an attribute option. */
+int
+ad_define_option( const char *name, const char *fname, int lineno )
+{
+ int i;
+ unsigned int optlen;
+
+ if ( options == &lang_option ) {
+ options = NULL;
+ option_count = 0;
+ }
+ if ( name == NULL )
+ return 0;
+
+ optlen = 0;
+ do {
+ if ( !DESC_CHAR( name[optlen] ) ) {
+ /* allow trailing '=', same as '-' */
+ if ( name[optlen] == '=' && !name[optlen+1] ) {
+ msad_range_hack = 1;
+ continue;
+ }
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: illegal option name \"%s\"\n",
+ fname, lineno, name );
+ return 1;
+ }
+ } while ( name[++optlen] );
+
+ options = ch_realloc( options,
+ (option_count+1) * sizeof(Attr_option) );
+
+ if ( strcasecmp( name, "binary" ) == 0
+ || ad_find_option_definition( name, optlen ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: option \"%s\" is already defined\n",
+ fname, lineno, name );
+ return 1;
+ }
+
+ for ( i = option_count; i; --i ) {
+ if ( strcasecmp( name, options[i-1].name.bv_val ) >= 0 )
+ break;
+ options[i] = options[i-1];
+ }
+
+ options[i].name.bv_val = ch_strdup( name );
+ options[i].name.bv_len = optlen;
+ options[i].prefix = (name[optlen-1] == '-') ||
+ (name[optlen-1] == '=');
+
+ if ( i != option_count &&
+ options[i].prefix &&
+ optlen < options[i+1].name.bv_len &&
+ strncasecmp( name, options[i+1].name.bv_val, optlen ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: option \"%s\" overrides previous option\n",
+ fname, lineno, name );
+ return 1;
+ }
+
+ option_count++;
+ return 0;
+}
+
+void
+ad_unparse_options( BerVarray *res )
+{
+ int i;
+ for ( i = 0; i < option_count; i++ ) {
+ value_add_one( res, &options[i].name );
+ }
+}
+
+/* Find the definition of the option name or prefix matching the arguments */
+static Attr_option *
+ad_find_option_definition( const char *opt, int optlen )
+{
+ int top = 0, bot = option_count;
+ while ( top < bot ) {
+ int mid = (top + bot) / 2;
+ int mlen = options[mid].name.bv_len;
+ char *mname = options[mid].name.bv_val;
+ int j;
+ if ( optlen < mlen ) {
+ j = strncasecmp( opt, mname, optlen ) - 1;
+ } else {
+ j = strncasecmp( opt, mname, mlen );
+ if ( j==0 && (optlen==mlen || options[mid].prefix) )
+ return &options[mid];
+ }
+ if ( j < 0 )
+ bot = mid;
+ else
+ top = mid + 1;
+ }
+ return NULL;
+}
+
+MatchingRule *ad_mr(
+ AttributeDescription *ad,
+ unsigned usage )
+{
+ switch( usage & SLAP_MR_TYPE_MASK ) {
+ case SLAP_MR_NONE:
+ case SLAP_MR_EQUALITY:
+ return ad->ad_type->sat_equality;
+ break;
+ case SLAP_MR_ORDERING:
+ return ad->ad_type->sat_ordering;
+ break;
+ case SLAP_MR_SUBSTR:
+ return ad->ad_type->sat_substr;
+ break;
+ case SLAP_MR_EXT:
+ default:
+ assert( 0 /* ad_mr: bad usage */);
+ }
+ return NULL;
+}
diff --git a/servers/slapd/add.c b/servers/slapd/add.c
new file mode 100644
index 0000000..7934d3e
--- /dev/null
+++ b/servers/slapd/add.c
@@ -0,0 +1,693 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+
+int
+do_add( Operation *op, SlapReply *rs )
+{
+ BerElement *ber = op->o_ber;
+ char *last;
+ struct berval dn = BER_BVNULL;
+ ber_len_t len;
+ ber_tag_t tag;
+ Modifications *modlist = NULL;
+ Modifications **modtail = &modlist;
+ Modifications tmp;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+ int rc = 0;
+ int freevals = 1;
+ OpExtraDB *oex;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_add\n",
+ op->o_log_prefix );
+
+ /*
+ * Parse the add request. It looks like this:
+ *
+ * AddRequest := [APPLICATION 14] SEQUENCE {
+ * name DistinguishedName,
+ * attrs SEQUENCE OF SEQUENCE {
+ * type AttributeType,
+ * values SET OF AttributeValue
+ * }
+ * }
+ */
+
+ /* get the name */
+ if ( ber_scanf( ber, "{m", /*}*/ &dn ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "%s do_add: dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+
+ /* get the attrs */
+ for ( tag = ber_first_element( ber, &len, &last ); tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ Modifications *mod;
+ ber_tag_t rtag;
+
+ tmp.sml_nvalues = NULL;
+
+ rtag = ber_scanf( ber, "{m{W}}", &tmp.sml_type, &tmp.sml_values );
+
+ if ( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: decoding error\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto done;
+ }
+
+ if ( tmp.sml_values == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: no values for type %s\n",
+ op->o_log_prefix, tmp.sml_type.bv_val );
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
+ "no values for attribute type" );
+ goto done;
+ }
+
+ mod = (Modifications *) ch_malloc( sizeof(Modifications) );
+ mod->sml_op = LDAP_MOD_ADD;
+ mod->sml_flags = 0;
+ mod->sml_next = NULL;
+ mod->sml_desc = NULL;
+ mod->sml_type = tmp.sml_type;
+ mod->sml_values = tmp.sml_values;
+ mod->sml_nvalues = NULL;
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( ber_scanf( ber, /*{*/ "}") == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto done;
+ }
+
+ if ( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: get_ctrls failed\n",
+ op->o_log_prefix );
+ goto done;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_add: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto done;
+ }
+
+ op->ora_e = entry_alloc();
+ ber_dupbv( &op->ora_e->e_name, &op->o_req_dn );
+ ber_dupbv( &op->ora_e->e_nname, &op->o_req_ndn );
+
+ Debug( LDAP_DEBUG_STATS, "%s ADD dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ if ( modlist == NULL ) {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
+ "no attributes provided" );
+ goto done;
+ }
+
+ if ( dn_match( &op->ora_e->e_nname, &slap_empty_bv ) ) {
+ /* protocolError may be a more appropriate error */
+ send_ldap_error( op, rs, LDAP_ALREADY_EXISTS,
+ "root DSE already exists" );
+ goto done;
+
+ } else if ( dn_match( &op->ora_e->e_nname, &frontendDB->be_schemandn ) ) {
+ send_ldap_error( op, rs, LDAP_ALREADY_EXISTS,
+ "subschema subentry already exists" );
+ goto done;
+ }
+
+ rs->sr_err = slap_mods_check( op, modlist, &rs->sr_text,
+ textbuf, textlen, NULL );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* temporary; remove if not invoking backend function */
+ op->ora_modlist = modlist;
+
+ /* call this so global overlays/SLAPI have access to ora_e */
+ rs->sr_err = slap_mods2entry( op->ora_modlist, &op->ora_e,
+ 1, 0, &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ freevals = 0;
+ oex = op->o_tmpalloc( sizeof(OpExtraDB), op->o_tmpmemctx );
+ oex->oe.oe_key = (void *)do_add;
+ oex->oe_db = NULL;
+ LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex->oe, oe_next);
+
+ op->o_bd = frontendDB;
+ rc = frontendDB->be_add( op, rs );
+
+ if ( rc == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rc;
+ }
+
+ if ( rc == LDAP_TXN_SPECIFY_OKAY ) {
+ /* skip cleanup */
+ return rc;
+ } else if ( rc == 0 ) {
+ if ( op->ora_e != NULL && oex->oe_db != NULL ) {
+ BackendDB *bd = op->o_bd;
+
+ op->o_bd = oex->oe_db;
+
+ be_entry_release_w( op, op->ora_e );
+
+ op->ora_e = NULL;
+ op->o_bd = bd;
+ }
+ }
+ LDAP_SLIST_REMOVE(&op->o_extra, &oex->oe, OpExtra, oe_next);
+ op->o_tmpfree( oex, op->o_tmpmemctx );
+
+done:;
+ if ( modlist != NULL ) {
+ /* in case of error, free the values as well */
+ slap_mods_free( modlist, freevals );
+ }
+
+ if ( op->ora_e != NULL ) {
+ entry_free( op->ora_e );
+ }
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+
+ return rc;
+}
+
+int
+fe_op_add( Operation *op, SlapReply *rs )
+{
+ Modifications **modtail = &op->ora_modlist;
+ int rc = 0;
+ BackendDB *op_be, *bd = op->o_bd;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+ op->o_bd = select_backend( &op->ora_e->e_nname, 1 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd;
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT );
+ if ( !rs->sr_ref ) rs->sr_ref = default_referral;
+ if ( rs->sr_ref ) {
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_ref != default_referral ) {
+ ber_bvarray_free( rs->sr_ref );
+ }
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "no global superior knowledge" );
+ }
+ goto done;
+ }
+
+ /* If we've got a glued backend, check the real backend */
+ op_be = op->o_bd;
+ if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
+ op->o_bd = select_backend( &op->ora_e->e_nname, 0 );
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* check for referrals */
+ if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rs->sr_err = slap_mods_obsolete_check( op, op->ora_modlist,
+ &rs->sr_text, textbuf, textlen );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /*
+ * do the add if 1 && (2 || 3)
+ * 1) there is an add function implemented in this backend;
+ * 2) this backend is the provider for what it holds;
+ * 3) it's a replica and the dn supplied is the updatedn.
+ */
+ if ( op->o_bd->be_add ) {
+ /* do the update here */
+ int repl_user = be_isupdate( op );
+ if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) {
+ int update = !BER_BVISEMPTY( &op->o_bd->be_update_ndn );
+
+ if ( !update ) {
+ rs->sr_err = slap_mods_no_user_mod_check( op, op->ora_modlist,
+ &rs->sr_text, textbuf, textlen );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( !repl_user ) {
+ /* go to the last mod */
+ for ( modtail = &op->ora_modlist;
+ *modtail != NULL;
+ modtail = &(*modtail)->sml_next )
+ {
+ assert( (*modtail)->sml_op == LDAP_MOD_ADD );
+ assert( (*modtail)->sml_desc != NULL );
+ }
+
+
+ /* check for unmodifiable attributes */
+ rs->sr_err = slap_mods_no_repl_user_mod_check( op,
+ op->ora_modlist, &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( op->o_txnSpec ) {
+ rc = txn_preop( op, rs );
+ goto done;
+ }
+
+ op->o_bd = op_be;
+ rc = op->o_bd->be_add( op, rs );
+ if ( rc == LDAP_SUCCESS ) {
+ OpExtra *oex;
+ /* NOTE: be_entry_release_w() is
+ * called by do_add(), so that global
+ * overlays on the way back can
+ * at least read the entry */
+ LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) {
+ if ( oex->oe_key == (void *)do_add ) {
+ ((OpExtraDB *)oex)->oe_db = op->o_bd;
+ break;
+ }
+ }
+ }
+
+ } else {
+ BerVarray defref = NULL;
+
+ defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if ( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( defref,
+ NULL, &op->ora_e->e_name, LDAP_SCOPE_DEFAULT );
+ if ( rs->sr_ref == NULL ) rs->sr_ref = defref;
+ rs->sr_err = LDAP_REFERRAL;
+ if (!rs->sr_ref) rs->sr_ref = default_referral;
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_ref != default_referral ) {
+ ber_bvarray_free( rs->sr_ref );
+ }
+ } else {
+ send_ldap_error( op, rs,
+ LDAP_UNWILLING_TO_PERFORM,
+ "shadow context; no update referral" );
+ }
+ }
+ } else {
+ Debug( LDAP_DEBUG_ARGS, "do_add: no backend support\n" );
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within namingContext" );
+ }
+
+done:;
+ op->o_bd = bd;
+ return rc;
+}
+
+int
+slap_mods2entry(
+ Modifications *mods,
+ Entry **e,
+ int initial,
+ int dup,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ Attribute **tail;
+ int i;
+
+ if ( initial ) {
+ assert( (*e)->e_attrs == NULL );
+ }
+
+ for ( tail = &(*e)->e_attrs; *tail != NULL; tail = &(*tail)->a_next )
+ ;
+
+ *text = textbuf;
+
+ for( ; mods != NULL; mods = mods->sml_next ) {
+ Attribute *attr;
+
+ assert( mods->sml_desc != NULL );
+
+ attr = attr_find( (*e)->e_attrs, mods->sml_desc );
+
+ if( attr != NULL ) {
+#define SLURPD_FRIENDLY
+#ifdef SLURPD_FRIENDLY
+ int j;
+
+ if ( !initial ) {
+ /*
+ * This check allows overlays to override operational
+ * attributes by setting them directly in the entry.
+ * We assume slap_mods_no_user_mod_check() was called
+ * with the user modifications.
+ */
+ *text = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ i = attr->a_numvals;
+ j = mods->sml_numvals;
+ attr->a_numvals += j;
+ j++; /* NULL */
+
+ attr->a_vals = ch_realloc( attr->a_vals,
+ sizeof( struct berval ) * (i+j) );
+
+ /* checked for duplicates in slap_mods_check */
+
+ if ( dup ) {
+ for ( j = 0; mods->sml_values[j].bv_val; j++ ) {
+ ber_dupbv( &attr->a_vals[i+j], &mods->sml_values[j] );
+ }
+ BER_BVZERO( &attr->a_vals[i+j] );
+ j++;
+ } else {
+ AC_MEMCPY( &attr->a_vals[i], mods->sml_values,
+ sizeof( struct berval ) * j );
+ }
+
+ if( mods->sml_nvalues ) {
+ attr->a_nvals = ch_realloc( attr->a_nvals,
+ sizeof( struct berval ) * (i+j) );
+ if ( dup ) {
+ for ( j = 0; mods->sml_nvalues[j].bv_val; j++ ) {
+ ber_dupbv( &attr->a_nvals[i+j], &mods->sml_nvalues[j] );
+ }
+ BER_BVZERO( &attr->a_nvals[i+j] );
+ } else {
+ AC_MEMCPY( &attr->a_nvals[i], mods->sml_nvalues,
+ sizeof( struct berval ) * j );
+ }
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ continue;
+#else
+ snprintf( textbuf, textlen,
+ "attribute '%s' provided more than once",
+ mods->sml_desc->ad_cname.bv_val );
+ *text = textbuf;
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+#endif
+ }
+
+ attr = attr_alloc( mods->sml_desc );
+
+ /* move values to attr structure */
+ i = mods->sml_numvals;
+ attr->a_numvals = mods->sml_numvals;
+ if ( dup ) {
+ attr->a_vals = (BerVarray) ch_calloc( i+1, sizeof( BerValue ));
+ for ( i = 0; mods->sml_values[i].bv_val; i++ ) {
+ ber_dupbv( &attr->a_vals[i], &mods->sml_values[i] );
+ }
+ BER_BVZERO( &attr->a_vals[i] );
+ } else {
+ attr->a_vals = mods->sml_values;
+ }
+
+ if ( mods->sml_nvalues ) {
+ if ( dup ) {
+ i = mods->sml_numvals;
+ attr->a_nvals = (BerVarray) ch_calloc( i+1, sizeof( BerValue ));
+ for ( i = 0; mods->sml_nvalues[i].bv_val; i++ ) {
+ ber_dupbv( &attr->a_nvals[i], &mods->sml_nvalues[i] );
+ }
+ BER_BVZERO( &attr->a_nvals[i] );
+ } else {
+ attr->a_nvals = mods->sml_nvalues;
+ }
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ *tail = attr;
+ tail = &attr->a_next;
+ }
+
+ *text = NULL;
+
+ return LDAP_SUCCESS;
+}
+
+int
+slap_entry2mods(
+ Entry *e,
+ Modifications **mods,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ Modifications *modhead = NULL;
+ Modifications *mod;
+ Modifications **modtail = &modhead;
+ Attribute *a_new;
+ AttributeDescription *a_new_desc;
+ int i, count;
+
+ a_new = e->e_attrs;
+
+ while ( a_new != NULL ) {
+ a_new_desc = a_new->a_desc;
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ));
+
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = 0;
+
+ mod->sml_type = a_new_desc->ad_cname;
+
+ count = a_new->a_numvals;
+ mod->sml_numvals = a_new->a_numvals;
+
+ mod->sml_values = (struct berval*) ch_malloc(
+ (count+1) * sizeof( struct berval) );
+
+ /* see slap_mods_check() comments...
+ * if a_vals == a_nvals, there is no normalizer.
+ * in this case, mod->sml_nvalues must be left NULL.
+ */
+ if ( a_new->a_vals != a_new->a_nvals ) {
+ mod->sml_nvalues = (struct berval*) ch_malloc(
+ (count+1) * sizeof( struct berval) );
+ } else {
+ mod->sml_nvalues = NULL;
+ }
+
+ for ( i = 0; i < count; i++ ) {
+ ber_dupbv(mod->sml_values+i, a_new->a_vals+i);
+ if ( mod->sml_nvalues ) {
+ ber_dupbv( mod->sml_nvalues+i, a_new->a_nvals+i );
+ }
+ }
+
+ mod->sml_values[count].bv_val = NULL;
+ mod->sml_values[count].bv_len = 0;
+
+ if ( mod->sml_nvalues ) {
+ mod->sml_nvalues[count].bv_val = NULL;
+ mod->sml_nvalues[count].bv_len = 0;
+ }
+
+ mod->sml_desc = a_new_desc;
+ mod->sml_next =NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ a_new = a_new->a_next;
+ }
+
+ *mods = modhead;
+
+ return LDAP_SUCCESS;
+}
+
+int slap_add_opattrs(
+ Operation *op,
+ const char **text,
+ char *textbuf,
+ size_t textlen,
+ int manage_ctxcsn )
+{
+ struct berval name, timestamp, csn = BER_BVNULL;
+ struct berval nname, tmp;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ Attribute *a;
+
+ if ( SLAP_LASTMOD( op->o_bd ) ) {
+ char *ptr;
+ int gotcsn = 0;
+
+ timestamp.bv_val = timebuf;
+ a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ gotcsn = 1;
+ csn = a->a_vals[0];
+ }
+ if ( BER_BVISEMPTY( &op->o_csn )) {
+ if ( !gotcsn ) {
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof(csnbuf);
+ slap_get_csn( op, &csn, manage_ctxcsn );
+ } else {
+ if ( manage_ctxcsn )
+ slap_queue_csn( op, &csn );
+ }
+ } else {
+ csn = op->o_csn;
+ }
+ ptr = ber_bvchr( &csn, '#' );
+ if ( ptr ) {
+ timestamp.bv_len = STRLENOF("YYYYMMDDHHMMSSZ");
+ AC_MEMCPY( timebuf, csn.bv_val, timestamp.bv_len );
+ timebuf[timestamp.bv_len-1] = 'Z';
+ timebuf[timestamp.bv_len] = '\0';
+ } else {
+ time_t now = slap_get_time();
+
+ timestamp.bv_len = sizeof(timebuf);
+
+ slap_timestamp( &now, &timestamp );
+ }
+
+ if ( BER_BVISEMPTY( &op->o_dn ) ) {
+ BER_BVSTR( &name, SLAPD_ANONYMOUS );
+ nname = name;
+ } else {
+ name = op->o_dn;
+ nname = op->o_ndn;
+ }
+
+ a = attr_find( op->ora_e->e_attrs,
+ slap_schema.si_ad_entryUUID );
+ if ( !a ) {
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+
+ tmp.bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) );
+ tmp.bv_val = uuidbuf;
+
+ attr_merge_normalize_one( op->ora_e,
+ slap_schema.si_ad_entryUUID, &tmp, op->o_tmpmemctx );
+ }
+
+ a = attr_find( op->ora_e->e_attrs,
+ slap_schema.si_ad_creatorsName );
+ if ( !a ) {
+ attr_merge_one( op->ora_e,
+ slap_schema.si_ad_creatorsName, &name, &nname );
+ }
+
+ a = attr_find( op->ora_e->e_attrs,
+ slap_schema.si_ad_createTimestamp );
+ if ( !a ) {
+ attr_merge_one( op->ora_e,
+ slap_schema.si_ad_createTimestamp, &timestamp, NULL );
+ }
+
+ if ( !gotcsn ) {
+ attr_merge_one( op->ora_e,
+ slap_schema.si_ad_entryCSN, &csn, NULL );
+ }
+
+ a = attr_find( op->ora_e->e_attrs,
+ slap_schema.si_ad_modifiersName );
+ if ( !a ) {
+ attr_merge_one( op->ora_e,
+ slap_schema.si_ad_modifiersName, &name, &nname );
+ }
+
+ a = attr_find( op->ora_e->e_attrs,
+ slap_schema.si_ad_modifyTimestamp );
+ if ( !a ) {
+ attr_merge_one( op->ora_e,
+ slap_schema.si_ad_modifyTimestamp, &timestamp, NULL );
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/at.c b/servers/slapd/at.c
new file mode 100644
index 0000000..353a641
--- /dev/null
+++ b/servers/slapd/at.c
@@ -0,0 +1,1123 @@
+/* at.c - routines for dealing with attribute types */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+
+
+const char *
+at_syntax(
+ AttributeType *at )
+{
+ for ( ; at != NULL; at = at->sat_sup ) {
+ if ( at->sat_syntax_oid ) {
+ return at->sat_syntax_oid;
+ }
+ }
+
+ assert( 0 );
+
+ return NULL;
+}
+
+int
+is_at_syntax(
+ AttributeType *at,
+ const char *oid )
+{
+ const char *syn_oid = at_syntax( at );
+
+ if ( syn_oid ) {
+ return strcmp( syn_oid, oid ) == 0;
+ }
+
+ return 0;
+}
+
+int is_at_subtype(
+ AttributeType *sub,
+ AttributeType *sup )
+{
+ for( ; sub != NULL; sub = sub->sat_sup ) {
+ if( sub == sup ) return 1;
+ }
+
+ return 0;
+}
+
+struct aindexrec {
+ struct berval air_name;
+ AttributeType *air_at;
+};
+
+static Avlnode *attr_index = NULL;
+static Avlnode *attr_cache = NULL;
+static LDAP_STAILQ_HEAD(ATList, AttributeType) attr_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(attr_list);
+
+/* Last hardcoded attribute registered */
+AttributeType *at_sys_tail;
+
+int at_oc_cache;
+
+static int
+attr_index_cmp(
+ const void *v_air1,
+ const void *v_air2 )
+{
+ const struct aindexrec *air1 = v_air1;
+ const struct aindexrec *air2 = v_air2;
+ int i = air1->air_name.bv_len - air2->air_name.bv_len;
+ if (i) return i;
+ return (strcasecmp( air1->air_name.bv_val, air2->air_name.bv_val ));
+}
+
+static int
+attr_index_name_cmp(
+ const void *v_type,
+ const void *v_air )
+{
+ const struct berval *type = v_type;
+ const struct aindexrec *air = v_air;
+ int i = type->bv_len - air->air_name.bv_len;
+ if (i) return i;
+ return (strncasecmp( type->bv_val, air->air_name.bv_val, type->bv_len ));
+}
+
+AttributeType *
+at_find( const char *name )
+{
+ struct berval bv;
+
+ bv.bv_val = (char *)name;
+ bv.bv_len = strlen( name );
+
+ return at_bvfind( &bv );
+}
+
+AttributeType *
+at_bvfind( struct berval *name )
+{
+ struct aindexrec *air;
+
+ if ( attr_cache ) {
+ air = ldap_avl_find( attr_cache, name, attr_index_name_cmp );
+ if ( air ) return air->air_at;
+ }
+
+ air = ldap_avl_find( attr_index, name, attr_index_name_cmp );
+
+ if ( air ) {
+ if ( air->air_at->sat_flags & SLAP_AT_DELETED ) {
+ air = NULL;
+ } else if (( slapMode & SLAP_TOOL_MODE ) && at_oc_cache ) {
+ ldap_avl_insert( &attr_cache, (caddr_t) air,
+ attr_index_cmp, ldap_avl_dup_error );
+ }
+ }
+
+ return air != NULL ? air->air_at : NULL;
+}
+
+int
+at_append_to_list(
+ AttributeType *sat,
+ AttributeType ***listp )
+{
+ AttributeType **list;
+ AttributeType **list1;
+ int size;
+
+ list = *listp;
+ if ( !list ) {
+ size = 2;
+ list = ch_calloc(size, sizeof(AttributeType *));
+ if ( !list ) {
+ return -1;
+ }
+ } else {
+ size = 0;
+ list1 = *listp;
+ while ( *list1 ) {
+ size++;
+ list1++;
+ }
+ size += 2;
+ list1 = ch_realloc(list, size*sizeof(AttributeType *));
+ if ( !list1 ) {
+ return -1;
+ }
+ list = list1;
+ }
+ list[size-2] = sat;
+ list[size-1] = NULL;
+ *listp = list;
+ return 0;
+}
+
+int
+at_delete_from_list(
+ int pos,
+ AttributeType ***listp )
+{
+ AttributeType **list;
+ AttributeType **list1;
+ int i;
+ int j;
+
+ if ( pos < 0 ) {
+ return -2;
+ }
+ list = *listp;
+ for ( i=0; list[i]; i++ )
+ ;
+ if ( pos >= i ) {
+ return -2;
+ }
+ for ( i=pos, j=pos+1; list[j]; i++, j++ ) {
+ list[i] = list[j];
+ }
+ list[i] = NULL;
+ /* Tell the runtime this can be shrunk */
+ list1 = ch_realloc(list, (i+1)*sizeof(AttributeType **));
+ if ( !list1 ) {
+ return -1;
+ }
+ *listp = list1;
+ return 0;
+}
+
+int
+at_find_in_list(
+ AttributeType *sat,
+ AttributeType **list )
+{
+ int i;
+
+ if ( !list ) {
+ return -1;
+ }
+ for ( i=0; list[i]; i++ ) {
+ if ( sat == list[i] ) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static void
+at_delete_names( AttributeType *at )
+{
+ char **names = at->sat_names;
+
+ if (!names) return;
+
+ while (*names) {
+ struct aindexrec tmpair, *air;
+
+ ber_str2bv( *names, 0, 0, &tmpair.air_name );
+ tmpair.air_at = at;
+ air = (struct aindexrec *)ldap_avl_delete( &attr_index,
+ (caddr_t)&tmpair, attr_index_cmp );
+ assert( air != NULL );
+ ldap_memfree( air );
+ names++;
+ }
+}
+
+/* Mark the attribute as deleted, remove from list, and remove all its
+ * names from the AVL tree. Leave the OID in the tree.
+ */
+void
+at_delete( AttributeType *at )
+{
+ at->sat_flags |= SLAP_AT_DELETED;
+
+ LDAP_STAILQ_REMOVE(&attr_list, at, AttributeType, sat_next);
+
+ at_delete_names( at );
+}
+
+static void
+at_clean( AttributeType *a )
+{
+ if ( a->sat_equality ) {
+ MatchingRule *mr;
+
+ mr = mr_find( a->sat_equality->smr_oid );
+ assert( mr != NULL );
+ if ( mr != a->sat_equality ) {
+ ch_free( a->sat_equality );
+ a->sat_equality = NULL;
+ }
+ }
+
+ if ( a->sat_ordering ) {
+ MatchingRule *mr;
+
+ mr = mr_find( a->sat_ordering->smr_oid );
+ assert( mr != NULL );
+ if ( mr != a->sat_ordering ) {
+ ch_free( a->sat_ordering );
+ a->sat_ordering = NULL;
+ }
+ }
+
+ assert( a->sat_syntax != NULL );
+ if ( a->sat_syntax != NULL ) {
+ Syntax *syn;
+
+ syn = syn_find( a->sat_syntax->ssyn_oid );
+ assert( syn != NULL );
+ if ( syn != a->sat_syntax ) {
+ ch_free( a->sat_syntax );
+ a->sat_syntax = NULL;
+ }
+ }
+
+ if ( a->sat_oidmacro ) {
+ ldap_memfree( a->sat_oidmacro );
+ a->sat_oidmacro = NULL;
+ }
+ if ( a->sat_soidmacro ) {
+ ldap_memfree( a->sat_soidmacro );
+ a->sat_soidmacro = NULL;
+ }
+ if ( a->sat_subtypes ) {
+ ldap_memfree( a->sat_subtypes );
+ a->sat_subtypes = NULL;
+ }
+}
+
+static void
+at_destroy_one( void *v )
+{
+ struct aindexrec *air = v;
+ AttributeType *a = air->air_at;
+
+ at_clean( a );
+ ad_destroy(a->sat_ad);
+ ldap_pvt_thread_mutex_destroy(&a->sat_ad_mutex);
+ ldap_attributetype_free((LDAPAttributeType *)a);
+ ldap_memfree(air);
+}
+
+void
+at_destroy( void )
+{
+ AttributeType *a;
+
+ while( !LDAP_STAILQ_EMPTY(&attr_list) ) {
+ a = LDAP_STAILQ_FIRST(&attr_list);
+ LDAP_STAILQ_REMOVE_HEAD(&attr_list, sat_next);
+
+ at_delete_names( a );
+ }
+
+ ldap_avl_free(attr_index, at_destroy_one);
+
+ if ( slap_schema.si_at_undefined ) {
+ ad_destroy(slap_schema.si_at_undefined->sat_ad);
+ }
+
+ if ( slap_schema.si_at_proxied ) {
+ ad_destroy(slap_schema.si_at_proxied->sat_ad);
+ }
+}
+
+int
+at_start( AttributeType **at )
+{
+ assert( at != NULL );
+
+ *at = LDAP_STAILQ_FIRST(&attr_list);
+
+ return (*at != NULL);
+}
+
+int
+at_next( AttributeType **at )
+{
+ assert( at != NULL );
+
+#if 0 /* pedantic check: don't use this */
+ {
+ AttributeType *tmp = NULL;
+
+ LDAP_STAILQ_FOREACH(tmp,&attr_list,sat_next) {
+ if ( tmp == *at ) {
+ break;
+ }
+ }
+
+ assert( tmp != NULL );
+ }
+#endif
+
+ if ( *at == NULL ) {
+ return 0;
+ }
+
+ *at = LDAP_STAILQ_NEXT(*at,sat_next);
+
+ return (*at != NULL);
+}
+
+/*
+ * check whether the two attributeTypes actually __are__ identical,
+ * or rather inconsistent
+ */
+static int
+at_check_dup(
+ AttributeType *sat,
+ AttributeType *new_sat )
+{
+ if ( new_sat->sat_oid != NULL ) {
+ if ( sat->sat_oid == NULL ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+
+ if ( strcmp( sat->sat_oid, new_sat->sat_oid ) != 0 ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+
+ } else {
+ if ( sat->sat_oid != NULL ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+ }
+
+ if ( new_sat->sat_names ) {
+ int i;
+
+ if ( sat->sat_names == NULL ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+
+ for ( i = 0; new_sat->sat_names[ i ]; i++ ) {
+ if ( sat->sat_names[ i ] == NULL ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+
+ if ( strcasecmp( sat->sat_names[ i ],
+ new_sat->sat_names[ i ] ) != 0 )
+ {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+ }
+ } else {
+ if ( sat->sat_names != NULL ) {
+ return SLAP_SCHERR_ATTR_INCONSISTENT;
+ }
+ }
+
+ return SLAP_SCHERR_ATTR_DUP;
+}
+
+static struct aindexrec *air_old;
+
+static int
+at_dup_error( void *left, void *right )
+{
+ air_old = left;
+ return -1;
+}
+
+static int
+at_insert(
+ AttributeType **rat,
+ AttributeType *prev,
+ const char **err )
+{
+ struct aindexrec *air;
+ char **names = NULL;
+ AttributeType *sat = *rat;
+
+ if ( sat->sat_oid ) {
+ air = (struct aindexrec *)
+ ch_calloc( 1, sizeof(struct aindexrec) );
+ ber_str2bv( sat->sat_oid, 0, 0, &air->air_name );
+ air->air_at = sat;
+ air_old = NULL;
+
+ if ( ldap_avl_insert( &attr_index, (caddr_t) air,
+ attr_index_cmp, at_dup_error ) )
+ {
+ AttributeType *old_sat;
+ int rc;
+
+ *err = sat->sat_oid;
+
+ assert( air_old != NULL );
+ old_sat = air_old->air_at;
+
+ /* replacing a deleted definition? */
+ if ( old_sat->sat_flags & SLAP_AT_DELETED ) {
+ AttributeType tmp;
+ AttributeDescription *ad;
+
+ /* Keep old oid, free new oid;
+ * Keep old ads, free new ads;
+ * Keep old ad_mutex, free new ad_mutex;
+ * Keep new everything else, free old
+ */
+ tmp = *old_sat;
+ *old_sat = *sat;
+ old_sat->sat_oid = tmp.sat_oid;
+ tmp.sat_oid = sat->sat_oid;
+ old_sat->sat_ad = tmp.sat_ad;
+ tmp.sat_ad = sat->sat_ad;
+ old_sat->sat_ad_mutex = tmp.sat_ad_mutex;
+ tmp.sat_ad_mutex = sat->sat_ad_mutex;
+ *sat = tmp;
+
+ /* Check for basic ad pointing at old cname */
+ for ( ad = old_sat->sat_ad; ad; ad=ad->ad_next ) {
+ if ( ad->ad_cname.bv_val == sat->sat_cname.bv_val ) {
+ ad->ad_cname = old_sat->sat_cname;
+ break;
+ }
+ }
+
+ at_clean( sat );
+ at_destroy_one( air );
+
+ air = air_old;
+ sat = old_sat;
+ *rat = sat;
+ } else {
+ ldap_memfree( air );
+
+ rc = at_check_dup( old_sat, sat );
+
+ return rc;
+ }
+ }
+ /* FIX: temporal consistency check */
+ at_bvfind( &air->air_name );
+ }
+
+ names = sat->sat_names;
+ if ( names ) {
+ while ( *names ) {
+ air = (struct aindexrec *)
+ ch_calloc( 1, sizeof(struct aindexrec) );
+ ber_str2bv( *names, 0, 0, &air->air_name );
+ air->air_at = sat;
+ if ( ldap_avl_insert( &attr_index, (caddr_t) air,
+ attr_index_cmp, ldap_avl_dup_error ) )
+ {
+ AttributeType *old_sat;
+ int rc;
+
+ *err = *names;
+
+ old_sat = at_bvfind( &air->air_name );
+ assert( old_sat != NULL );
+ rc = at_check_dup( old_sat, sat );
+
+ ldap_memfree(air);
+
+ while ( names > sat->sat_names ) {
+ struct aindexrec tmpair;
+
+ names--;
+ ber_str2bv( *names, 0, 0, &tmpair.air_name );
+ tmpair.air_at = sat;
+ air = (struct aindexrec *)ldap_avl_delete( &attr_index,
+ (caddr_t)&tmpair, attr_index_cmp );
+ assert( air != NULL );
+ ldap_memfree( air );
+ }
+
+ if ( sat->sat_oid ) {
+ struct aindexrec tmpair;
+
+ ber_str2bv( sat->sat_oid, 0, 0, &tmpair.air_name );
+ tmpair.air_at = sat;
+ air = (struct aindexrec *)ldap_avl_delete( &attr_index,
+ (caddr_t)&tmpair, attr_index_cmp );
+ assert( air != NULL );
+ ldap_memfree( air );
+ }
+
+ return rc;
+ }
+ /* FIX: temporal consistency check */
+ at_bvfind(&air->air_name);
+ names++;
+ }
+ }
+
+ if ( sat->sat_oid ) {
+ slap_ad_undef_promote( sat->sat_oid, sat );
+ }
+
+ names = sat->sat_names;
+ if ( names ) {
+ while ( *names ) {
+ slap_ad_undef_promote( *names, sat );
+ names++;
+ }
+ }
+
+ if ( sat->sat_flags & SLAP_AT_HARDCODE ) {
+ prev = at_sys_tail;
+ at_sys_tail = sat;
+ }
+ if ( prev ) {
+ LDAP_STAILQ_INSERT_AFTER( &attr_list, prev, sat, sat_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &attr_list, sat, sat_next );
+ }
+
+ return 0;
+}
+
+int
+at_add(
+ LDAPAttributeType *at,
+ int user,
+ AttributeType **rsat,
+ AttributeType *prev,
+ const char **err )
+{
+ AttributeType *sat = NULL;
+ MatchingRule *mr = NULL;
+ Syntax *syn = NULL;
+ int i;
+ int code = LDAP_SUCCESS;
+ char *cname = NULL;
+ char *oidm = NULL;
+ char *soidm = NULL;
+
+ if ( !at->at_oid ) {
+ *err = "";
+ return SLAP_SCHERR_ATTR_INCOMPLETE;
+ }
+
+ if ( !OID_LEADCHAR( at->at_oid[0] )) {
+ char *oid;
+
+ /* Expand OID macros */
+ oid = oidm_find( at->at_oid );
+ if ( !oid ) {
+ *err = at->at_oid;
+ return SLAP_SCHERR_OIDM;
+ }
+ if ( oid != at->at_oid ) {
+ oidm = at->at_oid;
+ at->at_oid = oid;
+ }
+ }
+
+ if ( at->at_syntax_oid && !OID_LEADCHAR( at->at_syntax_oid[0] )) {
+ char *oid;
+
+ /* Expand OID macros */
+ oid = oidm_find( at->at_syntax_oid );
+ if ( !oid ) {
+ *err = at->at_syntax_oid;
+ code = SLAP_SCHERR_OIDM;
+ goto error_return;
+ }
+ if ( oid != at->at_syntax_oid ) {
+ soidm = at->at_syntax_oid;
+ at->at_syntax_oid = oid;
+ }
+ }
+
+ if ( at->at_names && at->at_names[0] ) {
+ int i;
+
+ for( i=0; at->at_names[i]; i++ ) {
+ if( !slap_valid_descr( at->at_names[i] ) ) {
+ *err = at->at_names[i];
+ code = SLAP_SCHERR_BAD_DESCR;
+ goto error_return;
+ }
+ }
+
+ cname = at->at_names[0];
+
+ } else {
+ cname = at->at_oid;
+
+ }
+
+ *err = cname;
+
+ if ( !at->at_usage && at->at_no_user_mod ) {
+ /* user attribute must be modifiable */
+ code = SLAP_SCHERR_ATTR_BAD_USAGE;
+ goto error_return;
+ }
+
+ if ( at->at_collective ) {
+ if( at->at_usage ) {
+ /* collective attributes cannot be operational */
+ code = SLAP_SCHERR_ATTR_BAD_USAGE;
+ goto error_return;
+ }
+
+ if( at->at_single_value ) {
+ /* collective attributes cannot be single-valued */
+ code = SLAP_SCHERR_ATTR_BAD_USAGE;
+ goto error_return;
+ }
+ }
+
+ sat = (AttributeType *) ch_calloc( 1, sizeof(AttributeType) );
+ AC_MEMCPY( &sat->sat_atype, at, sizeof(LDAPAttributeType));
+
+ sat->sat_cname.bv_val = cname;
+ sat->sat_cname.bv_len = strlen( cname );
+ sat->sat_oidmacro = oidm;
+ sat->sat_soidmacro = soidm;
+ ldap_pvt_thread_mutex_init(&sat->sat_ad_mutex);
+
+ if ( at->at_sup_oid ) {
+ AttributeType *supsat = at_find(at->at_sup_oid);
+
+ if ( supsat == NULL ) {
+ *err = at->at_sup_oid;
+ code = SLAP_SCHERR_ATTR_NOT_FOUND;
+ goto error_return;
+ }
+
+ sat->sat_sup = supsat;
+
+ if ( at_append_to_list(sat, &supsat->sat_subtypes) ) {
+ code = SLAP_SCHERR_OUTOFMEM;
+ goto error_return;
+ }
+
+ if ( sat->sat_usage != supsat->sat_usage ) {
+ /* subtypes must have same usage as their SUP */
+ code = SLAP_SCHERR_ATTR_BAD_USAGE;
+ goto error_return;
+ }
+
+ if ( supsat->sat_obsolete && !sat->sat_obsolete ) {
+ /* subtypes must be obsolete if super is */
+ code = SLAP_SCHERR_ATTR_BAD_SUP;
+ goto error_return;
+ }
+
+ if ( sat->sat_flags & SLAP_AT_FINAL ) {
+ /* cannot subtype a "final" attribute type */
+ code = SLAP_SCHERR_ATTR_BAD_SUP;
+ goto error_return;
+ }
+ }
+
+ /*
+ * Inherit definitions from superiors. We only check the
+ * direct superior since that one has already inherited from
+ * its own superiors
+ */
+ if ( sat->sat_sup ) {
+ Syntax *syn = syn_find(sat->sat_sup->sat_syntax->ssyn_oid);
+ if ( syn != sat->sat_sup->sat_syntax ) {
+ sat->sat_syntax = ch_malloc( sizeof( Syntax ));
+ *sat->sat_syntax = *sat->sat_sup->sat_syntax;
+ } else {
+ sat->sat_syntax = sat->sat_sup->sat_syntax;
+ }
+ if ( sat->sat_sup->sat_equality ) {
+ MatchingRule *mr = mr_find( sat->sat_sup->sat_equality->smr_oid );
+ if ( mr != sat->sat_sup->sat_equality ) {
+ sat->sat_equality = ch_malloc( sizeof( MatchingRule ));
+ *sat->sat_equality = *sat->sat_sup->sat_equality;
+ } else {
+ sat->sat_equality = sat->sat_sup->sat_equality;
+ }
+ }
+ sat->sat_approx = sat->sat_sup->sat_approx;
+ sat->sat_ordering = sat->sat_sup->sat_ordering;
+ sat->sat_substr = sat->sat_sup->sat_substr;
+ }
+
+ /*
+ * check for X-ORDERED attributes
+ */
+ if ( sat->sat_extensions ) {
+ for (i=0; sat->sat_extensions[i]; i++) {
+ if (!strcasecmp( sat->sat_extensions[i]->lsei_name,
+ "X-ORDERED" ) && sat->sat_extensions[i]->lsei_values ) {
+ if ( !strcasecmp( sat->sat_extensions[i]->lsei_values[0],
+ "VALUES" )) {
+ sat->sat_flags |= SLAP_AT_ORDERED_VAL;
+ break;
+ } else if ( !strcasecmp( sat->sat_extensions[i]->lsei_values[0],
+ "SIBLINGS" )) {
+ sat->sat_flags |= SLAP_AT_ORDERED_SIB;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !user )
+ sat->sat_flags |= SLAP_AT_HARDCODE;
+
+ if ( at->at_syntax_oid ) {
+ syn = syn_find(sat->sat_syntax_oid);
+ if ( syn == NULL ) {
+ *err = sat->sat_syntax_oid;
+ code = SLAP_SCHERR_SYN_NOT_FOUND;
+ goto error_return;
+ }
+
+ if ( sat->sat_syntax != NULL && sat->sat_syntax != syn ) {
+ /* BEWARE: no loop detection! */
+ if ( syn_is_sup( sat->sat_syntax, syn ) ) {
+ code = SLAP_SCHERR_ATTR_BAD_SUP;
+ goto error_return;
+ }
+ }
+
+ sat->sat_syntax = syn;
+
+ } else if ( sat->sat_syntax == NULL ) {
+ code = SLAP_SCHERR_ATTR_INCOMPLETE;
+ goto error_return;
+ }
+
+ if ( sat->sat_equality_oid ) {
+ mr = mr_find(sat->sat_equality_oid);
+
+ if( mr == NULL ) {
+ *err = sat->sat_equality_oid;
+ code = SLAP_SCHERR_MR_NOT_FOUND;
+ goto error_return;
+ }
+
+ if(( mr->smr_usage & SLAP_MR_EQUALITY ) != SLAP_MR_EQUALITY ) {
+ *err = sat->sat_equality_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ if( sat->sat_syntax != mr->smr_syntax ) {
+ if( mr->smr_compat_syntaxes == NULL ) {
+ *err = sat->sat_equality_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ for(i=0; mr->smr_compat_syntaxes[i]; i++) {
+ if( sat->sat_syntax == mr->smr_compat_syntaxes[i] ) {
+ i = -1;
+ break;
+ }
+ }
+
+ if( i >= 0 ) {
+ *err = sat->sat_equality_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+ }
+
+ sat->sat_equality = mr;
+ sat->sat_approx = mr->smr_associated;
+ }
+
+ if ( sat->sat_ordering_oid ) {
+ if( !sat->sat_equality ) {
+ *err = sat->sat_ordering_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ mr = mr_find(sat->sat_ordering_oid);
+
+ if( mr == NULL ) {
+ *err = sat->sat_ordering_oid;
+ code = SLAP_SCHERR_MR_NOT_FOUND;
+ goto error_return;
+ }
+
+ if(( mr->smr_usage & SLAP_MR_ORDERING ) != SLAP_MR_ORDERING ) {
+ *err = sat->sat_ordering_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ if( sat->sat_syntax != mr->smr_syntax ) {
+ if( mr->smr_compat_syntaxes == NULL ) {
+ *err = sat->sat_ordering_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ for(i=0; mr->smr_compat_syntaxes[i]; i++) {
+ if( sat->sat_syntax == mr->smr_compat_syntaxes[i] ) {
+ i = -1;
+ break;
+ }
+ }
+
+ if( i >= 0 ) {
+ *err = sat->sat_ordering_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+ }
+
+ sat->sat_ordering = mr;
+ }
+
+ if ( sat->sat_substr_oid ) {
+ if( !sat->sat_equality ) {
+ *err = sat->sat_substr_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ mr = mr_find(sat->sat_substr_oid);
+
+ if( mr == NULL ) {
+ *err = sat->sat_substr_oid;
+ code = SLAP_SCHERR_MR_NOT_FOUND;
+ goto error_return;
+ }
+
+ if(( mr->smr_usage & SLAP_MR_SUBSTR ) != SLAP_MR_SUBSTR ) {
+ *err = sat->sat_substr_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ /* due to funky LDAP builtin substring rules,
+ * we check against the equality rule assertion
+ * syntax and compat syntaxes instead of those
+ * associated with the substrings rule.
+ */
+ if( sat->sat_syntax != sat->sat_equality->smr_syntax ) {
+ if( sat->sat_equality->smr_compat_syntaxes == NULL ) {
+ *err = sat->sat_substr_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+
+ for(i=0; sat->sat_equality->smr_compat_syntaxes[i]; i++) {
+ if( sat->sat_syntax ==
+ sat->sat_equality->smr_compat_syntaxes[i] )
+ {
+ i = -1;
+ break;
+ }
+ }
+
+ if( i >= 0 ) {
+ *err = sat->sat_substr_oid;
+ code = SLAP_SCHERR_ATTR_BAD_MR;
+ goto error_return;
+ }
+ }
+
+ sat->sat_substr = mr;
+ }
+
+ code = at_insert( &sat, prev, err );
+ if ( code != 0 ) {
+error_return:;
+ if ( sat ) {
+ ldap_pvt_thread_mutex_destroy( &sat->sat_ad_mutex );
+ ch_free( sat );
+ }
+
+ if ( oidm ) {
+ if ( *err == at->at_oid )
+ *err = oidm;
+ SLAP_FREE( at->at_oid );
+ at->at_oid = oidm;
+ }
+
+ if ( soidm ) {
+ if ( *err == at->at_syntax_oid )
+ *err = soidm;
+ SLAP_FREE( at->at_syntax_oid );
+ at->at_syntax_oid = soidm;
+ }
+
+ } else if ( rsat ) {
+ *rsat = sat;
+ }
+
+ return code;
+}
+
+#ifdef LDAP_DEBUG
+#ifdef SLAPD_UNUSED
+static int
+at_index_printnode( void *v_air, void *ignore )
+{
+ struct aindexrec *air = v_air;
+ printf("%s = %s\n",
+ air->air_name.bv_val,
+ ldap_attributetype2str(&air->air_at->sat_atype) );
+ return( 0 );
+}
+
+static void
+at_index_print( void )
+{
+ printf("Printing attribute type index:\n");
+ (void) ldap_avl_apply( attr_index, at_index_printnode, 0, -1, AVL_INORDER );
+}
+#endif
+#endif
+
+void
+at_unparse( BerVarray *res, AttributeType *start, AttributeType *end, int sys )
+{
+ AttributeType *at;
+ int i, num;
+ struct berval bv, *bva = NULL, idx;
+ char ibuf[32];
+
+ if ( !start )
+ start = LDAP_STAILQ_FIRST( &attr_list );
+
+ /* count the result size */
+ i = 0;
+ for ( at=start; at; at=LDAP_STAILQ_NEXT(at, sat_next)) {
+ if ( sys && !(at->sat_flags & SLAP_AT_HARDCODE)) break;
+ i++;
+ if ( at == end ) break;
+ }
+ if (!i) return;
+
+ num = i;
+ bva = ch_malloc( (num+1) * sizeof(struct berval) );
+ BER_BVZERO( bva );
+ idx.bv_val = ibuf;
+ if ( sys ) {
+ idx.bv_len = 0;
+ ibuf[0] = '\0';
+ }
+ i = 0;
+ for ( at=start; at; at=LDAP_STAILQ_NEXT(at, sat_next)) {
+ LDAPAttributeType lat, *latp;
+ if ( sys && !(at->sat_flags & SLAP_AT_HARDCODE)) break;
+ if ( at->sat_oidmacro || at->sat_soidmacro ) {
+ lat = at->sat_atype;
+ if ( at->sat_oidmacro )
+ lat.at_oid = at->sat_oidmacro;
+ if ( at->sat_soidmacro )
+ lat.at_syntax_oid = at->sat_soidmacro;
+ latp = &lat;
+ } else {
+ latp = &at->sat_atype;
+ }
+ if ( ldap_attributetype2bv( latp, &bv ) == NULL ) {
+ ber_bvarray_free( bva );
+ }
+ if ( !sys ) {
+ idx.bv_len = sprintf(idx.bv_val, "{%d}", i);
+ }
+ bva[i].bv_len = idx.bv_len + bv.bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ strcpy( bva[i].bv_val, ibuf );
+ strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val );
+ i++;
+ bva[i].bv_val = NULL;
+ ldap_memfree( bv.bv_val );
+ if ( at == end ) break;
+ }
+ *res = bva;
+}
+
+int
+at_schema_info( Entry *e )
+{
+ AttributeDescription *ad_attributeTypes = slap_schema.si_ad_attributeTypes;
+ AttributeType *at;
+ struct berval val;
+ struct berval nval;
+
+ LDAP_STAILQ_FOREACH(at,&attr_list,sat_next) {
+ if( at->sat_flags & SLAP_AT_HIDE ) continue;
+
+ if ( ldap_attributetype2bv( &at->sat_atype, &val ) == NULL ) {
+ return -1;
+ }
+
+ ber_str2bv( at->sat_oid, 0, 0, &nval );
+
+ if( attr_merge_one( e, ad_attributeTypes, &val, &nval ) )
+ {
+ return -1;
+ }
+ ldap_memfree( val.bv_val );
+ }
+ return 0;
+}
+
+int
+register_at( const char *def, AttributeDescription **rad, int dupok )
+{
+ LDAPAttributeType *at;
+ int code, freeit = 0;
+ const char *err;
+ AttributeDescription *ad = NULL;
+
+ at = ldap_str2attributetype( def, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !at ) {
+ Debug( LDAP_DEBUG_ANY,
+ "register_at: AttributeType \"%s\": %s, %s\n",
+ def, ldap_scherr2str(code), err );
+ return code;
+ }
+
+ code = at_add( at, 0, NULL, NULL, &err );
+ if ( code ) {
+ if ( code == SLAP_SCHERR_ATTR_DUP && dupok ) {
+ freeit = 1;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "register_at: AttributeType \"%s\": %s, %s\n",
+ def, scherr2str(code), err );
+ ldap_attributetype_free( at );
+ return code;
+ }
+ }
+ code = slap_str2ad( at->at_names[0], &ad, &err );
+ if ( freeit || code ) {
+ ldap_attributetype_free( at );
+ } else {
+ ldap_memfree( at );
+ }
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY, "register_at: AttributeType \"%s\": %s\n",
+ def, err );
+ }
+ if ( rad ) *rad = ad;
+ return code;
+}
diff --git a/servers/slapd/attr.c b/servers/slapd/attr.c
new file mode 100644
index 0000000..0828a76
--- /dev/null
+++ b/servers/slapd/attr.c
@@ -0,0 +1,722 @@
+/* attr.c - routines for dealing with attributes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+
+/*
+ * Allocate in chunks, minimum of 1000 at a time.
+ */
+#define CHUNK_SIZE 1000
+typedef struct slap_list {
+ struct slap_list *next;
+} slap_list;
+static slap_list *attr_chunks;
+static Attribute *attrs_list;
+static ldap_pvt_thread_mutex_t attr_mutex;
+
+int
+attr_prealloc( int num )
+{
+ Attribute *a;
+ slap_list *s;
+
+ if (!num) return 0;
+
+ s = ch_calloc( 1, sizeof(slap_list) + num * sizeof(Attribute));
+ s->next = attr_chunks;
+ attr_chunks = s;
+
+ a = (Attribute *)(s+1);
+ for ( ;num>1; num--) {
+ a->a_next = a+1;
+ a++;
+ }
+ a->a_next = attrs_list;
+ attrs_list = (Attribute *)(s+1);
+
+ return 0;
+}
+
+Attribute *
+attr_alloc( AttributeDescription *ad )
+{
+ Attribute *a;
+
+ ldap_pvt_thread_mutex_lock( &attr_mutex );
+ if ( !attrs_list )
+ attr_prealloc( CHUNK_SIZE );
+ a = attrs_list;
+ attrs_list = a->a_next;
+ a->a_next = NULL;
+ ldap_pvt_thread_mutex_unlock( &attr_mutex );
+
+ a->a_desc = ad;
+ if ( ad && ( ad->ad_type->sat_flags & SLAP_AT_SORTED_VAL ))
+ a->a_flags |= SLAP_ATTR_SORTED_VALS;
+
+ return a;
+}
+
+/* Return a list of num attrs */
+Attribute *
+attrs_alloc( int num )
+{
+ Attribute *head = NULL;
+ Attribute **a;
+
+ ldap_pvt_thread_mutex_lock( &attr_mutex );
+ for ( a = &attrs_list; *a && num > 0; a = &(*a)->a_next ) {
+ if ( !head )
+ head = *a;
+ num--;
+ }
+ attrs_list = *a;
+ if ( num > 0 ) {
+ attr_prealloc( num > CHUNK_SIZE ? num : CHUNK_SIZE );
+ *a = attrs_list;
+ for ( ; *a && num > 0; a = &(*a)->a_next ) {
+ if ( !head )
+ head = *a;
+ num--;
+ }
+ attrs_list = *a;
+ }
+ *a = NULL;
+ ldap_pvt_thread_mutex_unlock( &attr_mutex );
+
+ return head;
+}
+
+
+void
+attr_clean( Attribute *a )
+{
+ if ( a->a_nvals && a->a_nvals != a->a_vals &&
+ !( a->a_flags & SLAP_ATTR_DONT_FREE_VALS )) {
+ if ( a->a_flags & SLAP_ATTR_DONT_FREE_DATA ) {
+ free( a->a_nvals );
+ } else {
+ ber_bvarray_free( a->a_nvals );
+ }
+ }
+ /* a_vals may be equal to slap_dummy_bv, a static empty berval;
+ * this is used as a placeholder for attributes that do not carry
+ * values, e.g. when proxying search entries with the "attrsonly"
+ * bit set. */
+ if ( a->a_vals != &slap_dummy_bv &&
+ !( a->a_flags & SLAP_ATTR_DONT_FREE_VALS )) {
+ if ( a->a_flags & SLAP_ATTR_DONT_FREE_DATA ) {
+ free( a->a_vals );
+ } else {
+ ber_bvarray_free( a->a_vals );
+ }
+ }
+ a->a_desc = NULL;
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+#ifdef LDAP_COMP_MATCH
+ a->a_comp_data = NULL;
+#endif
+ a->a_flags = 0;
+ a->a_numvals = 0;
+}
+
+void
+attr_free( Attribute *a )
+{
+ attr_clean( a );
+ ldap_pvt_thread_mutex_lock( &attr_mutex );
+ a->a_next = attrs_list;
+ attrs_list = a;
+ ldap_pvt_thread_mutex_unlock( &attr_mutex );
+}
+
+#ifdef LDAP_COMP_MATCH
+void
+comp_tree_free( Attribute *a )
+{
+ Attribute *next;
+
+ for( ; a != NULL ; a = next ) {
+ next = a->a_next;
+ if ( component_destructor && a->a_comp_data ) {
+ if ( a->a_comp_data->cd_mem_op )
+ component_destructor( a->a_comp_data->cd_mem_op );
+ free ( a->a_comp_data );
+ }
+ }
+}
+#endif
+
+void
+attrs_free( Attribute *a )
+{
+ if ( a ) {
+ Attribute *b = (Attribute *)0xBAD, *tail, *next;
+
+ /* save tail */
+ tail = a;
+ do {
+ next = a->a_next;
+ attr_clean( a );
+ a->a_next = b;
+ b = a;
+ a = next;
+ } while ( next );
+
+ ldap_pvt_thread_mutex_lock( &attr_mutex );
+ /* replace NULL with current attr list and let attr list
+ * start from last attribute returned to list */
+ tail->a_next = attrs_list;
+ attrs_list = b;
+ ldap_pvt_thread_mutex_unlock( &attr_mutex );
+ }
+}
+
+static void
+attr_dup2( Attribute *tmp, Attribute *a )
+{
+ tmp->a_flags = a->a_flags & SLAP_ATTR_PERSISTENT_FLAGS;
+ if ( a->a_vals != NULL ) {
+ unsigned i, j;
+
+ tmp->a_numvals = a->a_numvals;
+ tmp->a_vals = ch_malloc( (tmp->a_numvals + 1) * sizeof(struct berval) );
+ for ( i = 0; i < tmp->a_numvals; i++ ) {
+ ber_dupbv( &tmp->a_vals[i], &a->a_vals[i] );
+ if ( BER_BVISNULL( &tmp->a_vals[i] ) ) break;
+ /* FIXME: error? */
+ }
+ BER_BVZERO( &tmp->a_vals[i] );
+
+ /* a_nvals must be non null; it may be equal to a_vals */
+ assert( a->a_nvals != NULL );
+
+ if ( a->a_nvals != a->a_vals ) {
+
+ tmp->a_nvals = ch_malloc( (tmp->a_numvals + 1) * sizeof(struct berval) );
+ j = 0;
+ if ( i ) {
+ for ( ; !BER_BVISNULL( &a->a_nvals[j] ); j++ ) {
+ assert( j < i );
+ ber_dupbv( &tmp->a_nvals[j], &a->a_nvals[j] );
+ if ( BER_BVISNULL( &tmp->a_nvals[j] ) ) break;
+ /* FIXME: error? */
+ }
+ assert( j == i );
+ }
+ BER_BVZERO( &tmp->a_nvals[j] );
+
+ } else {
+ tmp->a_nvals = tmp->a_vals;
+ }
+ }
+}
+
+Attribute *
+attr_dup( Attribute *a )
+{
+ Attribute *tmp;
+
+ if ( a == NULL) return NULL;
+
+ tmp = attr_alloc( a->a_desc );
+ attr_dup2( tmp, a );
+ return tmp;
+}
+
+Attribute *
+attrs_dup( Attribute *a )
+{
+ int i;
+ Attribute *tmp, *anew;
+
+ if( a == NULL ) return NULL;
+
+ /* count them */
+ for( tmp=a,i=0; tmp; tmp=tmp->a_next ) {
+ i++;
+ }
+
+ anew = attrs_alloc( i );
+
+ for( tmp=anew; a; a=a->a_next ) {
+ tmp->a_desc = a->a_desc;
+ attr_dup2( tmp, a );
+ tmp=tmp->a_next;
+ }
+
+ return anew;
+}
+
+int
+attr_valfind(
+ Attribute *a,
+ unsigned flags,
+ struct berval *val,
+ unsigned *slot,
+ void *ctx )
+{
+ struct berval nval = BER_BVNULL, *cval;
+ MatchingRule *mr;
+ const char *text;
+ int match = -1, rc;
+ unsigned i, n;
+
+ if ( flags & SLAP_MR_ORDERING )
+ mr = a->a_desc->ad_type->sat_ordering;
+ else
+ mr = a->a_desc->ad_type->sat_equality;
+
+ if( !SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( flags ) &&
+ mr->smr_normalize )
+ {
+ rc = (mr->smr_normalize)(
+ flags & (SLAP_MR_TYPE_MASK|SLAP_MR_SUBTYPE_MASK|SLAP_MR_VALUE_OF_SYNTAX),
+ a->a_desc->ad_type->sat_syntax,
+ mr, val, &nval, ctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ cval = &nval;
+ } else {
+ cval = val;
+ }
+
+ n = a->a_numvals;
+ if ( (a->a_flags & SLAP_ATTR_SORTED_VALS) && n ) {
+ /* Binary search */
+ unsigned base = 0;
+
+ do {
+ unsigned pivot = n >> 1;
+ i = base + pivot;
+ rc = value_match( &match, a->a_desc, mr, flags,
+ &a->a_nvals[i], cval, &text );
+ if ( rc == LDAP_SUCCESS && match == 0 )
+ break;
+ if ( match < 0 ) {
+ base = i+1;
+ n -= pivot+1;
+ } else {
+ n = pivot;
+ }
+ } while ( n );
+ if ( match < 0 )
+ i++;
+ } else {
+ /* Linear search */
+ for ( i = 0; i < n; i++ ) {
+ const char *text;
+
+ rc = ordered_value_match( &match, a->a_desc, mr, flags,
+ &a->a_nvals[i], cval, &text );
+ if ( rc == LDAP_SUCCESS && match == 0 )
+ break;
+ }
+ }
+ if ( match )
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ if ( slot )
+ *slot = i;
+ if ( nval.bv_val )
+ slap_sl_free( nval.bv_val, ctx );
+
+ return rc;
+}
+
+int
+attr_valadd(
+ Attribute *a,
+ BerVarray vals,
+ BerVarray nvals,
+ int nn )
+{
+ int i;
+ BerVarray v2;
+
+ v2 = (BerVarray) SLAP_REALLOC( (char *) a->a_vals,
+ (a->a_numvals + nn + 1) * sizeof(struct berval) );
+ if( v2 == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "attr_valadd: SLAP_REALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ a->a_vals = v2;
+ if ( nvals ) {
+ v2 = (BerVarray) SLAP_REALLOC( (char *) a->a_nvals,
+ (a->a_numvals + nn + 1) * sizeof(struct berval) );
+ if( v2 == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "attr_valadd: SLAP_REALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ a->a_nvals = v2;
+ } else {
+ a->a_nvals = a->a_vals;
+ }
+
+ /* If sorted and old vals exist, must insert */
+ if (( a->a_flags & SLAP_ATTR_SORTED_VALS ) && a->a_numvals ) {
+ unsigned slot;
+ int j, rc;
+ v2 = nvals ? nvals : vals;
+ for ( i = 0; i < nn; i++ ) {
+ rc = attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &v2[i], &slot, NULL );
+ if ( rc != LDAP_NO_SUCH_ATTRIBUTE ) {
+ /* should never happen */
+ if ( rc == LDAP_SUCCESS )
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ return rc;
+ }
+ for ( j = a->a_numvals; j >= (int)slot; j-- ) {
+ a->a_vals[j+1] = a->a_vals[j];
+ if ( nvals )
+ a->a_nvals[j+1] = a->a_nvals[j];
+ }
+ ber_dupbv( &a->a_nvals[slot], &v2[i] );
+ if ( nvals )
+ ber_dupbv( &a->a_vals[slot], &vals[i] );
+ a->a_numvals++;
+ }
+ BER_BVZERO( &a->a_vals[a->a_numvals] );
+ if ( a->a_vals != a->a_nvals )
+ BER_BVZERO( &a->a_nvals[a->a_numvals] );
+ } else {
+ v2 = &a->a_vals[a->a_numvals];
+ for ( i = 0 ; i < nn; i++ ) {
+ ber_dupbv( &v2[i], &vals[i] );
+ if ( BER_BVISNULL( &v2[i] ) ) break;
+ }
+ BER_BVZERO( &v2[i] );
+
+ if ( nvals ) {
+ v2 = &a->a_nvals[a->a_numvals];
+ for ( i = 0 ; i < nn; i++ ) {
+ ber_dupbv( &v2[i], &nvals[i] );
+ if ( BER_BVISNULL( &v2[i] ) ) break;
+ }
+ BER_BVZERO( &v2[i] );
+ }
+ a->a_numvals += i;
+ }
+ return 0;
+}
+
+/*
+ * attr_merge - merge the given type and value with the list of
+ * attributes in attrs.
+ *
+ * nvals must be NULL if the attribute has no normalizer.
+ * In this case, a->a_nvals will be set equal to a->a_vals.
+ *
+ * returns 0 everything went ok
+ * -1 trouble
+ */
+
+int
+attr_merge(
+ Entry *e,
+ AttributeDescription *desc,
+ BerVarray vals,
+ BerVarray nvals )
+{
+ int i = 0;
+
+ Attribute **a;
+
+ for ( a = &e->e_attrs; *a != NULL; a = &(*a)->a_next ) {
+ if ( (*a)->a_desc == desc ) {
+ break;
+ }
+ }
+
+ if ( *a == NULL ) {
+ *a = attr_alloc( desc );
+ } else {
+ /*
+ * FIXME: if the attribute already exists, the presence
+ * of nvals and the value of (*a)->a_nvals must be consistent
+ */
+ assert( ( nvals == NULL && (*a)->a_nvals == (*a)->a_vals )
+ || ( nvals != NULL && (
+ ( (*a)->a_vals == NULL && (*a)->a_nvals == NULL )
+ || ( (*a)->a_nvals != (*a)->a_vals ) ) ) );
+ }
+
+ if ( vals != NULL ) {
+ for ( ; !BER_BVISNULL( &vals[i] ); i++ ) ;
+ }
+ return attr_valadd( *a, vals, nvals, i );
+}
+
+/*
+ * if a normalization function is defined for the equality matchingRule
+ * of desc, the value is normalized and stored in nval; otherwise nval
+ * is NULL
+ */
+int
+attr_normalize(
+ AttributeDescription *desc,
+ BerVarray vals,
+ BerVarray *nvalsp,
+ void *memctx )
+{
+ int rc = LDAP_SUCCESS;
+ BerVarray nvals = NULL;
+
+ *nvalsp = NULL;
+
+ if ( desc->ad_type->sat_equality &&
+ desc->ad_type->sat_equality->smr_normalize )
+ {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ );
+
+ nvals = slap_sl_calloc( sizeof(struct berval), i + 1, memctx );
+ for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ ) {
+ rc = desc->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ &vals[i], &nvals[i], memctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ BER_BVZERO( &nvals[i + 1] );
+ break;
+ }
+ }
+ BER_BVZERO( &nvals[i] );
+ *nvalsp = nvals;
+ }
+
+ if ( rc != LDAP_SUCCESS && nvals != NULL ) {
+ ber_bvarray_free_x( nvals, memctx );
+ }
+
+ return rc;
+}
+
+int
+attr_merge_normalize(
+ Entry *e,
+ AttributeDescription *desc,
+ BerVarray vals,
+ void *memctx )
+{
+ BerVarray nvals = NULL;
+ int rc;
+
+ rc = attr_normalize( desc, vals, &nvals, memctx );
+ if ( rc == LDAP_SUCCESS ) {
+ rc = attr_merge( e, desc, vals, nvals );
+ if ( nvals != NULL ) {
+ ber_bvarray_free_x( nvals, memctx );
+ }
+ }
+
+ return rc;
+}
+
+int
+attr_merge_one(
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ struct berval *nval )
+{
+ Attribute **a;
+
+ for ( a = &e->e_attrs; *a != NULL; a = &(*a)->a_next ) {
+ if ( (*a)->a_desc == desc ) {
+ break;
+ }
+ }
+
+ if ( *a == NULL ) {
+ *a = attr_alloc( desc );
+ }
+
+ return attr_valadd( *a, val, nval, 1 );
+}
+
+/*
+ * if a normalization function is defined for the equality matchingRule
+ * of desc, the value is normalized and stored in nval; otherwise nval
+ * is NULL
+ */
+int
+attr_normalize_one(
+ AttributeDescription *desc,
+ struct berval *val,
+ struct berval *nval,
+ void *memctx )
+{
+ int rc = LDAP_SUCCESS;
+
+ BER_BVZERO( nval );
+
+ if ( desc->ad_type->sat_equality &&
+ desc->ad_type->sat_equality->smr_normalize )
+ {
+ rc = desc->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ val, nval, memctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+int
+attr_merge_normalize_one(
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ void *memctx )
+{
+ struct berval nval = BER_BVNULL;
+ struct berval *nvalp = NULL;
+ int rc;
+
+ rc = attr_normalize_one( desc, val, &nval, memctx );
+ if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &nval ) ) {
+ nvalp = &nval;
+ }
+
+ rc = attr_merge_one( e, desc, val, nvalp );
+ if ( nvalp != NULL ) {
+ slap_sl_free( nval.bv_val, memctx );
+ }
+ return rc;
+}
+
+/*
+ * attrs_find - find attribute(s) by AttributeDescription
+ * returns next attribute which is subtype of provided description.
+ */
+
+Attribute *
+attrs_find(
+ Attribute *a,
+ AttributeDescription *desc )
+{
+ for ( ; a != NULL; a = a->a_next ) {
+ if ( is_ad_subtype( a->a_desc, desc ) ) {
+ return( a );
+ }
+ }
+
+ return( NULL );
+}
+
+/*
+ * attr_find - find attribute by type
+ */
+
+Attribute *
+attr_find(
+ Attribute *a,
+ AttributeDescription *desc )
+{
+ for ( ; a != NULL; a = a->a_next ) {
+ if ( a->a_desc == desc ) {
+ return( a );
+ }
+ }
+
+ return( NULL );
+}
+
+/*
+ * attr_delete - delete the attribute type in list pointed to by attrs
+ * return 0 deleted ok
+ * 1 not found in list a
+ * -1 something bad happened
+ */
+
+int
+attr_delete(
+ Attribute **attrs,
+ AttributeDescription *desc )
+{
+ Attribute **a;
+
+ for ( a = attrs; *a != NULL; a = &(*a)->a_next ) {
+ if ( (*a)->a_desc == desc ) {
+ Attribute *save = *a;
+ *a = (*a)->a_next;
+ attr_free( save );
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_NO_SUCH_ATTRIBUTE;
+}
+
+int
+attr_init( void )
+{
+ ldap_pvt_thread_mutex_init( &attr_mutex );
+ return 0;
+}
+
+int
+attr_destroy( void )
+{
+ slap_list *a;
+
+ for ( a=attr_chunks; a; a=attr_chunks ) {
+ attr_chunks = a->next;
+ free( a );
+ }
+ ldap_pvt_thread_mutex_destroy( &attr_mutex );
+ return 0;
+}
diff --git a/servers/slapd/ava.c b/servers/slapd/ava.c
new file mode 100644
index 0000000..ae9351d
--- /dev/null
+++ b/servers/slapd/ava.c
@@ -0,0 +1,149 @@
+/* ava.c - routines for dealing with attribute value assertions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#ifdef LDAP_COMP_MATCH
+#include "component.h"
+#endif
+
+void
+ava_free(
+ Operation *op,
+ AttributeAssertion *ava,
+ int freeit )
+{
+#ifdef LDAP_COMP_MATCH
+ if ( ava->aa_cf && ava->aa_cf->cf_ca->ca_comp_data.cd_mem_op )
+ nibble_mem_free ( ava->aa_cf->cf_ca->ca_comp_data.cd_mem_op );
+#endif
+ op->o_tmpfree( ava->aa_value.bv_val, op->o_tmpmemctx );
+ if ( ava->aa_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ op->o_tmpfree( ava->aa_desc, op->o_tmpmemctx );
+ if ( freeit ) op->o_tmpfree( (char *) ava, op->o_tmpmemctx );
+}
+
+AttributeAssertion *
+ava_dup(
+ AttributeAssertion *ava,
+ void *memctx )
+{
+ BerMemoryFunctions *mf = &slap_sl_mfuncs;
+ AttributeAssertion *nava;
+
+ nava = mf->bmf_malloc( sizeof(AttributeAssertion), memctx );
+ *nava = *ava;
+ if ( ava->aa_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ nava->aa_desc = slap_bv2tmp_ad( &ava->aa_desc->ad_cname, memctx );
+ ber_dupbv_x( &nava->aa_value, &ava->aa_value, memctx );
+ return nava;
+}
+
+int
+get_ava(
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ unsigned usage,
+ const char **text )
+{
+ int rc;
+ ber_tag_t rtag;
+ struct berval type, value;
+ AttributeAssertion *aa;
+#ifdef LDAP_COMP_MATCH
+ AttributeAliasing* a_alias = NULL;
+#endif
+
+ rtag = ber_scanf( ber, "{mm}", &type, &value );
+
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_ava ber_scanf\n" );
+ *text = "Error decoding attribute value assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ aa = op->o_tmpalloc( sizeof( AttributeAssertion ), op->o_tmpmemctx );
+ aa->aa_desc = NULL;
+ aa->aa_value.bv_val = NULL;
+#ifdef LDAP_COMP_MATCH
+ aa->aa_cf = NULL;
+#endif
+
+ rc = slap_bv2ad( &type, &aa->aa_desc, text );
+
+ if( rc != LDAP_SUCCESS ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ *text = NULL;
+ rc = slap_bv2undef_ad( &type, &aa->aa_desc, text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "get_ava: unknown attributeType %s\n", type.bv_val );
+ aa->aa_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx );
+ ber_dupbv_x( &aa->aa_value, &value, op->o_tmpmemctx );
+ f->f_ava = aa;
+ return LDAP_SUCCESS;
+ }
+ }
+
+ rc = asserted_value_validate_normalize(
+ aa->aa_desc, ad_mr(aa->aa_desc, usage),
+ usage, &value, &aa->aa_value, text, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ Debug( LDAP_DEBUG_FILTER,
+ "get_ava: illegal value for attributeType %s\n", type.bv_val );
+ ber_dupbv_x( &aa->aa_value, &value, op->o_tmpmemctx );
+ *text = NULL;
+ rc = LDAP_SUCCESS;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if( is_aliased_attribute ) {
+ a_alias = is_aliased_attribute ( aa->aa_desc );
+ if ( a_alias ) {
+ rc = get_aliased_filter_aa ( op, aa, a_alias, text );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "get_ava: Invalid Attribute Aliasing\n" );
+ return rc;
+ }
+ }
+ }
+#endif
+ f->f_ava = aa;
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/back-asyncmeta/Makefile.in b/servers/slapd/back-asyncmeta/Makefile.in
new file mode 100644
index 0000000..c609458
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/Makefile.in
@@ -0,0 +1,50 @@
+## Makefile.in for back-asyncmeta
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 The OpenLDAP Foundation.
+## Portions Copyright 2016 Symas Corporation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was developed by Symas Corporation
+## based on back-meta module for inclusion in OpenLDAP Software.
+## This work was sponsored by Ericsson
+
+SRCS = init.c config.c search.c message_queue.c bind.c add.c compare.c \
+ delete.c modify.c modrdn.c map.c \
+ conn.c candidates.c dncache.c meta_result.c
+OBJS = init.lo config.lo search.lo message_queue.lo bind.lo add.lo compare.lo \
+ delete.lo modify.lo modrdn.lo map.lo \
+ conn.lo candidates.lo dncache.lo meta_result.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-asyncmeta"
+BUILD_MOD = @BUILD_ASYNCMETA@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_ASYNCMETA@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(LIBS) $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_asyncmeta
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
diff --git a/servers/slapd/back-asyncmeta/add.c b/servers/slapd/back-asyncmeta/add.c
new file mode 100644
index 0000000..55277ee
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/add.c
@@ -0,0 +1,370 @@
+/* add.c - add request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+
+
+int
+asyncmeta_error_cleanup(Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate)
+{
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ if (asyncmeta_bc_in_queue(mc,bc) == NULL || bc->bc_active > 1) {
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ return LDAP_SUCCESS;
+ }
+ asyncmeta_drop_bc(mc, bc);
+ slap_sl_mem_setctx(op->o_threadctx, op->o_tmpmemctx);
+ operation_counter_init( op, op->o_threadctx );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+ return LDAP_SUCCESS;
+}
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ int isupdate;
+ Attribute *a;
+ int i;
+ LDAPMod **attrs;
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = {0, NULL};
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+ int rc;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* Count number of attributes in entry ( +1 ) */
+ for ( i = 1, a = op->ora_e->e_attrs; a; i++, a = a->a_next );
+
+ /* Create array of LDAPMods for ldap_add() */
+ attrs = op->o_tmpalloc(sizeof( LDAPMod * )*i, op->o_tmpmemctx);
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) {
+ int j;
+
+ if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ attrs[ i ] = op->o_tmpalloc( sizeof( LDAPMod ), op->o_tmpmemctx );
+ if ( attrs[ i ] == NULL ) {
+ continue;
+ }
+ attrs[ i ]->mod_op = LDAP_MOD_BVALUES;
+ attrs[ i ]->mod_type = a->a_desc->ad_cname.bv_val;
+ j = a->a_numvals;
+ attrs[ i ]->mod_bvalues = op->o_tmpalloc( ( j + 1 ) * sizeof( struct berval * ), op->o_tmpmemctx );
+ for (j=0; j<a->a_numvals; j++) {
+ attrs[ i ]->mod_bvalues[ j ] = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ if ( a->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &a->a_vals[ j ], attrs[ i ]->mod_bvalues[ j ] );
+ else
+ *attrs[ i ]->mod_bvalues[ j ] = a->a_vals[ j ];
+ }
+
+ attrs[ i ]->mod_bvalues[ j ] = NULL;
+ i++;
+ }
+ attrs[ i ] = NULL;
+
+ asyncmeta_set_msc_time(msc);
+
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_add_req( msc->msc_ld, mdn.bv_val, attrs, ctrls, NULL, &msgid);
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_add_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_ADD,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ ldap_pvt_thread_yield();
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send add request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_add_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+
+int
+asyncmeta_back_add( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_TRACE, "==> asyncmeta_back_add: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_add[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_ADD ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ current_time = slap_get_time();
+ if (bc->timeout && bc->stoptime < current_time) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug(LDAP_DEBUG_TRACE , "%s asyncmeta_back_add: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_add_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/back-asyncmeta.h b/servers/slapd/back-asyncmeta/back-asyncmeta.h
new file mode 100644
index 0000000..a5ae6cd
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/back-asyncmeta.h
@@ -0,0 +1,788 @@
+/* back-asyncmeta.h - main header file for back-asyncmeta module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef SLAPD_LDAP_H
+#error "include servers/slapd/back-ldap/back-ldap.h before this file!"
+#endif /* SLAPD_LDAP_H */
+
+#ifndef SLAPD_ASYNCMETA_H
+#define SLAPD_ASYNCMETA_H
+
+#ifdef LDAP_DEVEL
+#define SLAPD_META_CLIENT_PR 1
+#endif /* LDAP_DEVEL */
+
+#include "proto-asyncmeta.h"
+
+#include "ldap_rq.h"
+
+LDAP_BEGIN_DECL
+
+/*
+ * Set META_BACK_PRINT_CONNTREE larger than 0 to dump the connection tree (debug only)
+ */
+#ifndef META_BACK_PRINT_CONNTREE
+#define META_BACK_PRINT_CONNTREE 0
+#endif /* !META_BACK_PRINT_CONNTREE */
+
+/*
+ * A a_metasingleconn_t can be in the following, mutually exclusive states:
+ *
+ * - none (0x0U)
+ * - creating META_BACK_FCONN_CREATING
+ * - initialized META_BACK_FCONN_INITED
+ * - binding LDAP_BACK_FCONN_BINDING
+ * - bound/anonymous LDAP_BACK_FCONN_ISBOUND/LDAP_BACK_FCONN_ISANON
+ *
+ * possible modifiers are:
+ *
+ * - privileged LDAP_BACK_FCONN_ISPRIV
+ * - privileged, TLS LDAP_BACK_FCONN_ISTLS
+ * - subjected to idassert LDAP_BACK_FCONN_ISIDASR
+ * - tainted LDAP_BACK_FCONN_TAINTED
+ */
+
+#define META_BACK_FCONN_INITED (0x00100000U)
+#define META_BACK_FCONN_CREATING (0x00200000U)
+#define META_BACK_FCONN_INVALID (0x00400000U)
+
+#define META_BACK_CONN_INITED(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_INITED, (mlc))
+#define META_BACK_CONN_CREATING(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_CREATING, (mlc))
+#define META_BACK_CONN_INVALID(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INVALID)
+#define META_BACK_CONN_INVALID_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INVALID)
+#define META_BACK_CONN_INVALID_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INVALID)
+
+struct a_metainfo_t;
+struct a_metaconn_t;
+struct a_metatarget_t;
+#define META_NOT_CANDIDATE ((ber_tag_t)0x0)
+#define META_CANDIDATE ((ber_tag_t)0x1)
+#define META_BINDING ((ber_tag_t)0x2)
+#define META_RETRYING ((ber_tag_t)0x4)
+
+typedef struct bm_context_t {
+ LDAP_STAILQ_ENTRY(bm_context_t) bc_next;
+ struct a_metaconn_t *bc_mc;
+ time_t timeout;
+ time_t stoptime;
+ ldap_back_send_t sendok;
+ ldap_back_send_t retrying;
+ int candidate_match;
+ volatile int bc_active;
+ int searchtime; /* stoptime is a search timelimit */
+ int is_ok;
+ int is_root;
+ volatile sig_atomic_t bc_invalid;
+ SlapReply rs;
+ Operation *op;
+ Operation copy_op;
+ LDAPControl **ctrls;
+ int *msgids;
+ int *nretries; /* number of times to retry a failed send on an msc */
+ struct berval c_peer_name; /* peer name of original op->o_conn*/
+ SlapReply *candidates;
+} bm_context_t;
+
+typedef struct a_metasingleconn_t {
+#define META_CND_ISSET(rs,f) ( ( (rs)->sr_tag & (f) ) == (f) )
+#define META_CND_SET(rs,f) ( (rs)->sr_tag |= (f) )
+#define META_CND_CLEAR(rs,f) ( (rs)->sr_tag &= ~(f) )
+
+#define META_CANDIDATE_RESET(rs) ( (rs)->sr_tag = 0 )
+#define META_IS_CANDIDATE(rs) META_CND_ISSET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_SET(rs) META_CND_SET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_CLEAR(rs) META_CND_CLEAR( (rs), META_CANDIDATE )
+#define META_IS_BINDING(rs) META_CND_ISSET( (rs), META_BINDING )
+#define META_BINDING_SET(rs) META_CND_SET( (rs), META_BINDING )
+#define META_BINDING_CLEAR(rs) META_CND_CLEAR( (rs), META_BINDING )
+#define META_IS_RETRYING(rs) META_CND_ISSET( (rs), META_RETRYING )
+#define META_RETRYING_SET(rs) META_CND_SET( (rs), META_RETRYING )
+#define META_RETRYING_CLEAR(rs) META_CND_CLEAR( (rs), META_RETRYING )
+
+ LDAP *msc_ld;
+ LDAP *msc_ldr;
+ time_t msc_time;
+ time_t msc_binding_time;
+ time_t msc_result_time;
+ struct berval msc_bound_ndn;
+ struct berval msc_cred;
+ unsigned msc_mscflags;
+
+ /* NOTE: lc_lcflags is redefined to msc_mscflags to reuse the macros
+ * defined for back-ldap */
+#define lc_lcflags msc_mscflags
+ volatile int msc_active;
+ /* Connection for the select */
+ Connection *conn;
+} a_metasingleconn_t;
+
+typedef struct a_metaconn_t {
+ ldapconn_base_t lc_base;
+#define mc_base lc_base
+//#define mc_conn mc_base.lcb_conn
+//#define mc_local_ndn mc_base.lcb_local_ndn
+//#define mc_refcnt mc_base.lcb_refcnt
+//#define mc_create_time mc_base.lcb_create_time
+//#define mc_time mc_base.lcb_time
+
+ LDAP_TAILQ_ENTRY(a_metaconn_t) mc_q;
+
+ /* NOTE: msc_mscflags is used to recycle the #define
+ * in metasingleconn_t */
+ unsigned msc_mscflags;
+ int mc_active;
+
+ /*
+ * means that the connection is bound;
+ * of course only one target actually is ...
+ */
+ int mc_authz_target;
+#define META_BOUND_NONE (-1)
+#define META_BOUND_ALL (-2)
+
+ struct a_metainfo_t *mc_info;
+
+ int pending_ops;
+ ldap_pvt_thread_mutex_t mc_om_mutex;
+ /* queue for pending operations */
+ LDAP_STAILQ_HEAD(BCList, bm_context_t) mc_om_list;
+ /* supersedes the connection stuff */
+ a_metasingleconn_t *mc_conns;
+} a_metaconn_t;
+
+typedef enum meta_st_t {
+#if 0 /* todo */
+ META_ST_EXACT = LDAP_SCOPE_BASE,
+#endif
+ META_ST_SUBTREE = LDAP_SCOPE_SUBTREE,
+ META_ST_SUBORDINATE = LDAP_SCOPE_SUBORDINATE,
+ META_ST_REGEX /* last + 1 */
+} meta_st_t;
+
+typedef struct a_metasubtree_t {
+ meta_st_t ms_type;
+ union {
+ struct berval msu_dn;
+ struct {
+ struct berval msr_regex_pattern;
+ regex_t msr_regex;
+ } msu_regex;
+ } ms_un;
+#define ms_dn ms_un.msu_dn
+#define ms_regex ms_un.msu_regex.msr_regex
+#define ms_regex_pattern ms_un.msu_regex.msr_regex_pattern
+
+ struct a_metasubtree_t *ms_next;
+} a_metasubtree_t;
+
+typedef struct metafilter_t {
+ struct metafilter_t *mf_next;
+ struct berval mf_regex_pattern;
+ regex_t mf_regex;
+} metafilter_t;
+
+typedef struct a_metacommon_t {
+ int mc_version;
+ int mc_nretries;
+#define META_RETRY_UNDEFINED (-2)
+#define META_RETRY_FOREVER (-1)
+#define META_RETRY_NEVER (0)
+#define META_RETRY_DEFAULT (2)
+
+ unsigned mc_flags;
+#define META_BACK_CMN_ISSET(mc,f) ( ( (mc)->mc_flags & (f) ) == (f) )
+#define META_BACK_CMN_QUARANTINE(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_QUARANTINE )
+#define META_BACK_CMN_CHASE_REFERRALS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_CHASE_REFERRALS )
+#define META_BACK_CMN_NOREFS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOREFS )
+#define META_BACK_CMN_NOUNDEFFILTER(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOUNDEFFILTER )
+#define META_BACK_CMN_SAVECRED(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_SAVECRED )
+#define META_BACK_CMN_ST_REQUEST(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_ST_REQUEST )
+
+#ifdef SLAPD_META_CLIENT_PR
+ /*
+ * client-side paged results:
+ * -1: accept unsolicited paged results responses
+ * 0: off
+ * >0: always request paged results with size == mt_ps
+ */
+#define META_CLIENT_PR_DISABLE (0)
+#define META_CLIENT_PR_ACCEPT_UNSOLICITED (-1)
+ ber_int_t mc_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ slap_retry_info_t mc_quarantine;
+ time_t mc_network_timeout;
+ struct timeval mc_bind_timeout;
+#define META_BIND_TIMEOUT LDAP_BACK_RESULT_UTIMEOUT
+ time_t mc_timeout[ SLAP_OP_LAST ];
+} a_metacommon_t;
+
+typedef struct a_metatarget_t {
+ char *mt_uri;
+ ldap_pvt_thread_mutex_t mt_uri_mutex;
+
+ /* TODO: we might want to enable different strategies
+ * for different targets */
+ LDAP_REBIND_PROC *mt_rebind_f;
+ LDAP_URLLIST_PROC *mt_urllist_f;
+ void *mt_urllist_p;
+
+ metafilter_t *mt_filter;
+ a_metasubtree_t *mt_subtree;
+ /* F: subtree-include; T: subtree-exclude */
+ int mt_subtree_exclude;
+
+ int mt_scope;
+
+ struct berval mt_psuffix; /* pretty suffix */
+ struct berval mt_nsuffix; /* normalized suffix */
+
+ struct berval mt_lsuffixm; /* local suffix for massage */
+ struct berval mt_rsuffixm; /* remote suffix for massage */
+
+ struct berval mt_binddn;
+ struct berval mt_bindpw;
+
+ /* we only care about the TLS options here */
+ slap_bindconf mt_tls;
+
+ slap_idassert_t mt_idassert;
+#define mt_idassert_mode mt_idassert.si_mode
+#define mt_idassert_authcID mt_idassert.si_bc.sb_authcId
+#define mt_idassert_authcDN mt_idassert.si_bc.sb_binddn
+#define mt_idassert_passwd mt_idassert.si_bc.sb_cred
+#define mt_idassert_authzID mt_idassert.si_bc.sb_authzId
+#define mt_idassert_authmethod mt_idassert.si_bc.sb_method
+#define mt_idassert_sasl_mech mt_idassert.si_bc.sb_saslmech
+#define mt_idassert_sasl_realm mt_idassert.si_bc.sb_realm
+#define mt_idassert_secprops mt_idassert.si_bc.sb_secprops
+#define mt_idassert_tls mt_idassert.si_bc.sb_tls
+#define mt_idassert_flags mt_idassert.si_flags
+#define mt_idassert_authz mt_idassert.si_authz
+
+ sig_atomic_t mt_isquarantined;
+ ldap_pvt_thread_mutex_t mt_quarantine_mutex;
+
+ a_metacommon_t mt_mc;
+#define mt_nretries mt_mc.mc_nretries
+#define mt_flags mt_mc.mc_flags
+#define mt_version mt_mc.mc_version
+#define mt_ps mt_mc.mc_ps
+#define mt_network_timeout mt_mc.mc_network_timeout
+#define mt_bind_timeout mt_mc.mc_bind_timeout
+#define mt_timeout mt_mc.mc_timeout
+#define mt_quarantine mt_mc.mc_quarantine
+
+#define META_BACK_TGT_ISSET(mt,f) ( ( (mt)->mt_flags & (f) ) == (f) )
+#define META_BACK_TGT_ISMASK(mt,m,f) ( ( (mt)->mt_flags & (m) ) == (f) )
+
+#define META_BACK_TGT_SAVECRED(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_SAVECRED )
+
+#define META_BACK_TGT_USE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_USE_TLS )
+#define META_BACK_TGT_PROPAGATE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_PROPAGATE_TLS )
+#define META_BACK_TGT_TLS_CRITICAL(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_TLS_CRITICAL )
+
+#define META_BACK_TGT_CHASE_REFERRALS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_CHASE_REFERRALS )
+
+#define META_BACK_TGT_T_F(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F )
+#define META_BACK_TGT_T_F_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER )
+
+#define META_BACK_TGT_ABANDON(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON )
+#define META_BACK_TGT_IGNORE(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE )
+#define META_BACK_TGT_CANCEL(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP )
+#define META_BACK_TGT_CANCEL_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER )
+#define META_BACK_TGT_QUARANTINE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_QUARANTINE )
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define META_BACK_TGT_ST_REQUEST(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_REQUEST )
+#define META_BACK_TGT_ST_RESPONSE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_RESPONSE )
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define META_BACK_TGT_NOREFS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOREFS )
+#define META_BACK_TGT_NOUNDEFFILTER(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOUNDEFFILTER )
+
+#define META_BACK_CFG_MAX_PENDING_OPS 0x80
+#define META_BACK_CFG_MAX_TARGET_CONNS 0xFF
+#define META_BACK_CFG_DEFAULT_OPS_TIMEOUT 0x02
+
+/* the interval of the timeout checking loop in microseconds
+ * possibly make this configurable? */
+#define META_BACK_CFG_MAX_TIMEOUT_LOOP 0x70000
+ slap_mask_t mt_rep_flags;
+ int mt_timeout_ops;
+} a_metatarget_t;
+
+typedef struct a_metadncache_t {
+ ldap_pvt_thread_mutex_t mutex;
+ Avlnode *tree;
+
+#define META_DNCACHE_DISABLED (0)
+#define META_DNCACHE_FOREVER ((time_t)(-1))
+ time_t ttl; /* seconds; 0: no cache, -1: no expiry */
+} a_metadncache_t;
+
+typedef struct a_metacandidates_t {
+ int mc_ntargets;
+ SlapReply *mc_candidates;
+} a_metacandidates_t;
+
+/*
+ * Hook to allow mucking with a_metainfo_t/a_metatarget_t when quarantine is over
+ */
+typedef int (*asyncmeta_quarantine_f)( struct a_metainfo_t *, int target, void * );
+
+struct meta_out_message_t;
+
+typedef struct a_metainfo_t {
+ int mi_ntargets;
+ int mi_defaulttarget;
+#define META_DEFAULT_TARGET_NONE (-1)
+
+#define mi_nretries mi_mc.mc_nretries
+#define mi_flags mi_mc.mc_flags
+#define mi_version mi_mc.mc_version
+#define mi_ps mi_mc.mc_ps
+#define mi_network_timeout mi_mc.mc_network_timeout
+#define mi_bind_timeout mi_mc.mc_bind_timeout
+#define mi_timeout mi_mc.mc_timeout
+#define mi_quarantine mi_mc.mc_quarantine
+
+ a_metatarget_t **mi_targets;
+ a_metacandidates_t *mi_candidates;
+
+ LDAP_REBIND_PROC *mi_rebind_f;
+ LDAP_URLLIST_PROC *mi_urllist_f;
+
+ a_metadncache_t mi_cache;
+
+ struct {
+ int mic_num;
+ LDAP_TAILQ_HEAD(mc_conn_priv_q, a_metaconn_t) mic_priv;
+ } mi_conn_priv[ LDAP_BACK_PCONN_LAST ];
+ int mi_conn_priv_max;
+
+ /* NOTE: quarantine uses the connection mutex */
+ asyncmeta_quarantine_f mi_quarantine_f;
+ void *mi_quarantine_p;
+
+#define li_flags mi_flags
+/* uses flags as defined in <back-ldap/back-ldap.h> */
+#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP
+#define META_BACK_F_ONERR_REPORT (0x02000000U)
+#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT)
+#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U)
+#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */
+
+#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP )
+#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT )
+#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) )
+
+#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND )
+#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS )
+#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON )
+#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON )
+
+#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE )
+
+ time_t mi_idle_timeout;
+ struct re_s *mi_task;
+
+ a_metacommon_t mi_mc;
+ ldap_extra_t *mi_ldap_extra;
+
+ int mi_max_timeout_ops;
+ int mi_max_pending_ops;
+ int mi_max_target_conns;
+ /* mutex for access to the connection structures */
+ ldap_pvt_thread_mutex_t mi_mc_mutex;
+ int mi_num_conns;
+ int mi_next_conn;
+ a_metaconn_t *mi_conns;
+
+ struct berval mi_suffix;
+} a_metainfo_t;
+
+typedef enum meta_op_type {
+ META_OP_ALLOW_MULTIPLE = 0,
+ META_OP_REQUIRE_SINGLE,
+ META_OP_REQUIRE_ALL
+} meta_op_type;
+
+/* Whatever context asyncmeta_dn_massage needs... */
+typedef struct a_dncookie {
+ Operation *op;
+ struct a_metatarget_t *target;
+ void *memctx;
+ int to_from;
+} a_dncookie;
+
+
+#define MASSAGE_REQ 0
+#define MASSAGE_REP 1
+
+extern void
+asyncmeta_dn_massage(a_dncookie *dc, struct berval *dn,
+ struct berval *res);
+
+extern void
+asyncmeta_filter_map_rewrite(
+ a_dncookie *dc,
+ Filter *f,
+ struct berval *fstr );
+
+extern void
+asyncmeta_back_referral_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals );
+
+extern a_metaconn_t *
+asyncmeta_getconn(
+ Operation *op,
+ SlapReply *rs,
+ SlapReply *candidates,
+ int *candidate,
+ ldap_back_send_t sendok,
+ int alloc_new);
+
+
+extern int
+asyncmeta_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock );
+
+extern void
+asyncmeta_quarantine(
+ Operation *op,
+ a_metainfo_t *mi,
+ SlapReply *rs,
+ int candidate );
+
+extern int
+asyncmeta_proxy_authz_cred(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method );
+
+extern int
+asyncmeta_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int isroot,
+ LDAPControl ***pctrls );
+
+extern int
+asyncmeta_LTX_init_module(
+ int argc,
+ char *argv[] );
+
+/*
+ * Candidate stuff
+ */
+extern int
+asyncmeta_is_candidate(
+ a_metatarget_t *mt,
+ struct berval *ndn,
+ int scope );
+
+extern int
+asyncmeta_select_unique_candidate(
+ a_metainfo_t *mi,
+ struct berval *ndn );
+
+extern int
+asyncmeta_clear_unused_candidates(
+ Operation *op,
+ int candidate,
+ a_metaconn_t *mc,
+ SlapReply *candidates);
+
+/*
+ * Dn cache stuff (experimental)
+ */
+extern int
+asyncmeta_dncache_cmp(
+ const void *c1,
+ const void *c2 );
+
+extern int
+asyncmeta_dncache_dup(
+ void *c1,
+ void *c2 );
+
+#define META_TARGET_NONE (-1)
+#define META_TARGET_MULTIPLE (-2)
+extern int
+asyncmeta_dncache_get_target(
+ a_metadncache_t *cache,
+ struct berval *ndn );
+
+extern int
+meta_dncache_update_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn,
+ int target );
+
+extern int
+asyncmeta_dncache_delete_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn );
+
+extern void
+asyncmeta_dncache_free( void *entry );
+
+extern int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms );
+
+extern void
+asyncmeta_filter_destroy( metafilter_t *mf );
+
+extern int
+asyncmeta_target_finish( a_metainfo_t *mi, a_metatarget_t *mt,
+ const char *log, char *msg, size_t msize
+);
+
+
+extern LDAP_REBIND_PROC asyncmeta_back_default_rebind;
+extern LDAP_URLLIST_PROC asyncmeta_back_default_urllist;
+
+/* IGNORE means that target does not (no longer) participate
+ * in the search;
+ * NOTREADY means the search on that target has not been initialized yet
+ */
+#define META_MSGID_IGNORE (-1)
+#define META_MSGID_NEED_BIND (-2)
+#define META_MSGID_CONNECTING (-3)
+#define META_MSGID_UNDEFINED (-4)
+#define META_MSGID_GOT_BIND (-5)
+
+typedef enum meta_search_candidate_t {
+ META_SEARCH_UNDEFINED = -2,
+ META_SEARCH_ERR = -1,
+ META_SEARCH_NOT_CANDIDATE,
+ META_SEARCH_CANDIDATE,
+ META_SEARCH_BINDING,
+ META_SEARCH_NEED_BIND,
+ META_SEARCH_CONNECTING
+} meta_search_candidate_t;
+
+Operation* asyncmeta_copy_op(Operation *op);
+void asyncmeta_clear_bm_context(bm_context_t *bc);
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc);
+void asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc);
+void asyncmeta_drop_bc_from_fconn(bm_context_t *bc);
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate);
+
+void* asyncmeta_op_handle_result(void *ctx, void *arg);
+int asyncmeta_back_cleanup( Operation *op, SlapReply *rs, bm_context_t *bm );
+
+int
+asyncmeta_clear_one_msc(
+ Operation *op,
+ a_metaconn_t *msc,
+ int candidate,
+ int unbind,
+ const char * caller);
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi );
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg);
+
+int
+asyncmeta_start_timeout_loop(a_metatarget_t *mt, a_metainfo_t *mi);
+
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc);
+
+int asyncmeta_back_cancel(
+ a_metaconn_t *mc,
+ Operation *op,
+ ber_int_t msgid,
+ int candidate );
+
+void
+asyncmeta_send_result(bm_context_t* bc, int error, char *text);
+
+int asyncmeta_new_bm_context(Operation *op,
+ SlapReply *rs,
+ bm_context_t **new_bc,
+ int ntargets,
+ a_metainfo_t *mi);
+
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc);
+int asyncmeta_start_one_listener(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc, int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_search_start(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ struct berval *prcookie,
+ ber_int_t prsize,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_dobind_init(
+ Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(
+ Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+bm_context_t *
+asyncmeta_bc_in_queue(a_metaconn_t *mc,
+ bm_context_t *bc);
+
+int
+asyncmeta_error_cleanup(Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+int
+asyncmeta_reset_msc(Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller);
+
+
+void
+asyncmeta_back_conn_free(
+ void *v_mc );
+
+void asyncmeta_log_msc(a_metasingleconn_t *msc);
+void asyncmeta_log_conns(a_metainfo_t *mi);
+
+void asyncmeta_get_timestamp(char *buf);
+
+int
+asyncmeta_dncache_update_entry(a_metadncache_t *cache,
+ struct berval *ndn,
+ int target );
+
+void
+asyncmeta_dnattr_result_rewrite(a_dncookie *dc,
+ BerVarray a_vals);
+
+void
+asyncmeta_referral_result_rewrite(a_dncookie *dc,
+ BerVarray a_vals);
+
+meta_search_candidate_t
+asyncmeta_send_all_pending_ops(a_metaconn_t *mc,
+ int candidate,
+ void *ctx,
+ int dolock);
+meta_search_candidate_t
+asyncmeta_return_bind_errors(a_metaconn_t *mc,
+ int candidate,
+ SlapReply *bind_result,
+ void *ctx,
+ int dolock);
+
+int
+asyncmeta_db_has_pending_ops(a_metainfo_t *mi);
+
+int
+asyncmeta_db_has_mscs(a_metainfo_t *mi);
+
+/* The the maximum time in seconds after a result has been received on a connection,
+ * after which it can be reset if a sender error occurs. Should this be configurable? */
+#define META_BACK_RESULT_INTERVAL (2)
+
+extern int asyncmeta_debug;
+
+LDAP_END_DECL
+
+#endif /* SLAPD_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/bind.c b/servers/slapd/back-asyncmeta/bind.c
new file mode 100644
index 0000000..78c0e57
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/bind.c
@@ -0,0 +1,1739 @@
+/* bind.c - bind request handler functions for binding
+ * to remote targets for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+#define AVL_INTERNAL
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "lutil_ldap.h"
+
+#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12"
+
+static int
+asyncmeta_proxy_authz_bind(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock );
+
+static int
+asyncmeta_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate );
+
+int
+asyncmeta_back_bind( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metaconn_t *mc = NULL;
+
+ int rc = LDAP_OTHER,
+ i,
+ gotit = 0,
+ isroot = 0;
+
+ SlapReply *candidates = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS, "%s asyncmeta_back_bind: dn=\"%s\".\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ /* the test on the bind method should be superfluous */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case LDAP_SUCCESS:
+ if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) {
+ /* frontend will return success */
+ return rs->sr_err;
+ }
+
+ isroot = 1;
+ /* fallthru */
+
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* be_rootdn_bind() sent result */
+ return rs->sr_err;
+ }
+
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = op->o_tmpcalloc(mi->mi_ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+
+ /* we need asyncmeta_getconn() not send result even on error,
+ * because we want to intercept the error and make it
+ * invalidCredentials */
+ mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_BIND_DONTSEND, 1 );
+ if ( !mc ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_back_bind: no target " "for dn \"%s\" (%d%s%s).\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ rs->sr_err, rs->sr_text ? ". " : "",
+ rs->sr_text ? rs->sr_text : "" );
+
+ /* FIXME: there might be cases where we don't want
+ * to map the error onto invalidCredentials */
+ switch ( rs->sr_err ) {
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_UNWILLING_TO_PERFORM:
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ rs->sr_text = NULL;
+ break;
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ /*
+ * Each target is scanned ...
+ */
+ mc->mc_authz_target = META_BOUND_NONE;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+ int lerr;
+
+ /*
+ * Skip non-candidates
+ */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( gotit == 0 ) {
+ /* set rc to LDAP_SUCCESS only if at least
+ * one candidate has been tried */
+ rc = LDAP_SUCCESS;
+ gotit = 1;
+
+ } else if ( !isroot ) {
+ /*
+ * A bind operation is expected to have
+ * ONE CANDIDATE ONLY!
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_back_bind: more than one"
+ " candidate selected...\n",
+ op->o_log_prefix );
+ }
+
+ if ( isroot ) {
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE
+ || BER_BVISNULL( &mt->mt_idassert_authcDN ) )
+ {
+ a_metasingleconn_t *msc = &mc->mc_conns[ i ];
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ continue;
+ }
+
+
+ (void)asyncmeta_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 );
+ lerr = rs->sr_err;
+
+ } else {
+ lerr = asyncmeta_single_bind( op, rs, mc, i );
+ }
+
+ if ( lerr != LDAP_SUCCESS ) {
+ rc = rs->sr_err = lerr;
+
+ /* FIXME: in some cases (e.g. unavailable)
+ * do not assume it's not candidate; rather
+ * mark this as an error to be eventually
+ * reported to client */
+ META_CANDIDATE_CLEAR( &candidates[ i ] );
+ break;
+ }
+ }
+
+ if ( mc != NULL ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metasingleconn_t *msc = &mc->mc_conns[ i ];
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ }
+ }
+ asyncmeta_back_conn_free( mc );
+ }
+
+ /*
+ * rc is LDAP_SUCCESS if at least one bind succeeded,
+ * err is the last error that occurred during a bind;
+ * if at least (and at most?) one bind succeeds, fine.
+ */
+ if ( rc != LDAP_SUCCESS ) {
+
+ /*
+ * deal with bind failure ...
+ */
+
+ /*
+ * no target was found within the naming context,
+ * so bind must fail with invalid credentials
+ */
+ if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ } else {
+ rs->sr_err = slap_map_api2result( rs );
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_bind_op_result(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int msgid,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ LDAPMessage *res;
+ struct timeval tv;
+ int rc;
+ int nretries = mt->mt_nretries;
+
+ Debug( LDAP_DEBUG_TRACE,
+ ">>> %s asyncmeta_bind_op_result[%d]\n",
+ op->o_log_prefix, candidate );
+
+ /* make sure this is clean */
+ assert( rs->sr_ctrls == NULL );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ time_t stoptime = (time_t)(-1),
+ timeout;
+ int timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ const char *timeout_text = "Operation timed out";
+ slap_op_t opidx = slap_req2op( op->o_tag );
+
+ /* since timeout is not specified, compute and use
+ * the one specific to the ongoing operation */
+ if ( opidx == LDAP_REQ_SEARCH ) {
+ if ( op->ors_tlimit <= 0 ) {
+ timeout = 0;
+
+ } else {
+ timeout = op->ors_tlimit;
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ }
+
+ } else {
+ timeout = mt->mt_timeout[ opidx ];
+ }
+
+ /* better than nothing :) */
+ if ( timeout == 0 ) {
+ if ( mi->mi_idle_timeout ) {
+ timeout = mi->mi_idle_timeout;
+
+ }
+ }
+
+ if ( timeout ) {
+ stoptime = op->o_time + timeout;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+
+ /*
+ * handle response!!!
+ */
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ if ( nretries != META_RETRY_NEVER
+ || ( timeout && slap_get_time() <= stoptime ) )
+ {
+ ldap_pvt_thread_yield();
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ tv = mt->mt_bind_timeout;
+ goto retry;
+ }
+
+ /* don't let anyone else use this handler,
+ * because there's a pending bind that will not
+ * be acknowledged */
+ assert( LDAP_BACK_CONN_BINDING( msc ) );
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_bind_op_result ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ break;
+
+ case -1:
+ ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER,
+ &rs->sr_err );
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_bind_op_result[%d]: err=%d (%s) nretries=%d.\n",
+ op->o_log_prefix, candidate, rs->sr_err,
+ ldap_err2string(rs->sr_err), nretries );
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+ msc->msc_time = op->o_time;
+ }
+
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+ NULL, NULL, NULL, NULL, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+ break;
+ }
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+ Debug( LDAP_DEBUG_TRACE,
+ "<<< %s asyncmeta_bind_op_result[%d] err=%d\n",
+ op->o_log_prefix, candidate, rs->sr_err );
+
+ return rs->sr_err;
+}
+
+/*
+ * asyncmeta_single_bind
+ *
+ * attempts to perform a bind with creds
+ */
+static int
+asyncmeta_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int msgid;
+ a_dncookie dc;
+ struct berval save_o_dn;
+ int save_o_do_not_cache;
+ LDAPControl **ctrls = NULL;
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* don't add proxyAuthz; set the bindDN */
+ save_o_dn = op->o_dn;
+ save_o_do_not_cache = op->o_do_not_cache;
+ op->o_do_not_cache = 1;
+ op->o_dn = op->o_req_dn;
+
+ ctrls = op->o_ctrls;
+ rs->sr_err = asyncmeta_controls_add( op, rs, mc, candidate, be_isroot(op), &ctrls );
+ op->o_dn = save_o_dn;
+ op->o_do_not_cache = save_o_do_not_cache;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* FIXME: this fixes the bind problem right now; we need
+ * to use the asynchronous version to get the "matched"
+ * and more in case of failure ... */
+ /* FIXME: should we check if at least some of the op->o_ctrls
+ * can/should be passed? */
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val,
+ LDAP_SASL_SIMPLE, &op->orb_cred,
+ ctrls, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* If defined, proxyAuthz will be used also when
+ * back-ldap is the authorizing backend; for this
+ * purpose, a successful bind is followed by a
+ * bind with the configured identity assertion */
+ /* NOTE: use with care */
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ asyncmeta_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 );
+ if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ goto return_results;
+ }
+ goto cache_refresh;
+ }
+
+ ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn );
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ mc->mc_authz_target = candidate;
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &op->orb_cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+
+cache_refresh:;
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED
+ && !BER_BVISEMPTY( &op->o_req_ndn ) )
+ {
+ ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+ &op->o_req_ndn, candidate );
+ }
+
+return_results:;
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ asyncmeta_quarantine( op, mi, rs, candidate );
+ }
+ ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+ msc->msc_ld = NULL;
+ ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+ msc->msc_ldr = NULL;
+ return rs->sr_err;
+}
+
+
+/*
+ * asyncmeta_back_default_rebind
+ *
+ * This is a callback used for chasing referrals using the same
+ * credentials as the original user on this session.
+ */
+int
+asyncmeta_back_default_rebind(
+ LDAP *ld,
+ LDAP_CONST char *url,
+ ber_tag_t request,
+ ber_int_t msgid,
+ void *params )
+{
+ a_metasingleconn_t *msc = ( a_metasingleconn_t * )params;
+
+ return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val,
+ LDAP_SASL_SIMPLE, &msc->msc_cred,
+ NULL, NULL, NULL );
+}
+
+/*
+ * meta_back_default_urllist
+ *
+ * This is a callback used for mucking with the urllist
+ */
+int
+asyncmeta_back_default_urllist(
+ LDAP *ld,
+ LDAPURLDesc **urllist,
+ LDAPURLDesc **url,
+ void *params )
+{
+ a_metatarget_t *mt = (a_metatarget_t *)params;
+ LDAPURLDesc **urltail;
+
+ if ( urllist == url ) {
+ return LDAP_SUCCESS;
+ }
+
+ for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next )
+ /* count */ ;
+
+ *urltail = *urllist;
+ *urllist = *url;
+ *url = NULL;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ }
+
+ ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+ return LDAP_SUCCESS;
+}
+
+int
+asyncmeta_back_cancel(
+ a_metaconn_t *mc,
+ Operation *op,
+ ber_int_t msgid,
+ int candidate )
+{
+
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ int rc = LDAP_OTHER;
+ struct timeval tv = { 0, 0 };
+ ber_socket_t s;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n",
+ op->o_log_prefix, candidate, msgid );
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n already reset",
+ op->o_log_prefix, candidate, msgid );
+ return LDAP_SUCCESS;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ return rc;
+ }
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ rc = LDAP_SERVER_DOWN;
+ return rc;
+ }
+ /* default behavior */
+ if ( META_BACK_TGT_ABANDON( mt ) ) {
+ rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+ } else if ( META_BACK_TGT_IGNORE( mt ) ) {
+ rc = ldap_pvt_discard( msc->msc_ld, msgid );
+
+ } else if ( META_BACK_TGT_CANCEL( mt ) ) {
+ rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL );
+
+ } else {
+ assert( 0 );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_cancel[%d] err=%d\n",
+ op->o_log_prefix, candidate, rc );
+
+ return rc;
+}
+
+
+
+/*
+ * asyncmeta_back_proxy_authz_cred()
+ *
+ * prepares credentials & method for meta_back_proxy_authz_bind();
+ * or, if method is SASL, performs the SASL bind directly.
+ */
+int
+asyncmeta_back_proxy_authz_cred(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval ndn;
+ int dobind = 0;
+ struct timeval old_tv = {0, 0};
+ struct timeval bind_tv = { mt->mt_timeout[ SLAP_OP_BIND ], 0};
+ /* don't proxyAuthz if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+
+ } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+
+ } else {
+ ndn = op->o_ndn;
+ }
+ rs->sr_err = LDAP_SUCCESS;
+
+ /*
+ * FIXME: we need to let clients use proxyAuthz
+ * otherwise we cannot do symmetric pools of servers;
+ * we have to live with the fact that a user can
+ * authorize itself as any ID that is allowed
+ * by the authzTo directive of the "proxyauthzdn".
+ */
+ /*
+ * NOTE: current Proxy Authorization specification
+ * and implementation do not allow proxy authorization
+ * control to be provided with Bind requests
+ */
+ /*
+ * if no bind took place yet, but the connection is bound
+ * and the "proxyauthzdn" is set, then bind as
+ * "proxyauthzdn" and explicitly add the proxyAuthz
+ * control to every operation with the dn bound
+ * to the connection as control value.
+ */
+
+ /* bind as proxyauthzdn only if no idassert mode
+ * is requested, or if the client's identity
+ * is authorized */
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) )
+ {
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ }
+ }
+ break;
+
+ default:
+ /* NOTE: rootdn can always idassert */
+ if ( BER_BVISNULL( &ndn )
+ && mt->mt_idassert_authz == NULL
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+
+ } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz,
+ &authcDN, &authcDN );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+ }
+ }
+
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ break;
+ }
+
+ if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults = NULL;
+ struct berval authzID = BER_BVNULL;
+ int freeauthz = 0;
+
+ /* if SASL supports native authz, prepare for it */
+ if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ authzID = mt->mt_idassert_authzID;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+
+ case LDAP_BACK_IDASSERT_SELF:
+ if ( BER_BVISNULL( &ndn ) ) {
+ /* connection is not authc'd, so don't idassert */
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+ }
+ authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len;
+ authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx );
+ AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ),
+ ndn.bv_val, ndn.bv_len + 1 );
+ freeauthz = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_secprops != NULL ) {
+ rs->sr_err = ldap_set_option( msc->msc_ld,
+ LDAP_OPT_X_SASL_SECPROPS,
+ (void *)mt->mt_idassert_secprops );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv);
+
+ if (mt->mt_timeout[ SLAP_OP_BIND ] > 0 ) {
+ rs->sr_err = ldap_set_option( msc->msc_ld,
+ LDAP_OPT_TIMEOUT,
+ (void *)&bind_tv );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+ }
+ defaults = lutil_sasl_defaults( msc->msc_ld,
+ mt->mt_idassert_sasl_mech.bv_val,
+ mt->mt_idassert_sasl_realm.bv_val,
+ mt->mt_idassert_authcID.bv_val,
+ mt->mt_idassert_passwd.bv_val,
+ authzID.bv_val );
+ if ( defaults == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+ rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val,
+ mt->mt_idassert_sasl_mech.bv_val, NULL, NULL,
+ LDAP_SASL_QUIET, lutil_sasl_interact,
+ defaults );
+
+ /* restore the old timeout just in case */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv );
+
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_back_proxy_authz_cred failed bind msc: %p\n",
+ time_buf, msc );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+
+ } else {
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+
+ lutil_sasl_freedefs( defaults );
+ if ( freeauthz ) {
+ slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+ }
+
+ goto done;
+#endif /* HAVE_CYRUS_SASL */
+ }
+
+ *method = mt->mt_idassert_authmethod;
+ switch ( mt->mt_idassert_authmethod ) {
+ case LDAP_AUTH_NONE:
+ BER_BVSTR( binddn, "" );
+ BER_BVSTR( bindcred, "" );
+ /* fallthru */
+
+ case LDAP_AUTH_SIMPLE:
+ break;
+
+ default:
+ /* unsupported! */
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ break;
+ }
+
+done:;
+
+ if ( !BER_BVISEMPTY( binddn ) ) {
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+asyncmeta_proxy_authz_bind(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval binddn = BER_BVC( "" ),
+ cred = BER_BVC( "" );
+ int method = LDAP_AUTH_NONE,
+ rc;
+
+ rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method );
+ if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ int msgid;
+
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+ binddn.bv_val, LDAP_SASL_SIMPLE,
+ &cred, NULL, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ rc = asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+ if ( rc == LDAP_SUCCESS ) {
+ /* set rebind stuff in case of successful proxyAuthz bind,
+ * so that referral chasing is attempted using the right
+ * identity */
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+ }
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ return LDAP_BACK_CONN_ISBOUND( msc );
+}
+
+
+static int
+asyncmeta_back_proxy_authz_ctrl(Operation *op,
+ SlapReply *rs,
+ struct berval *bound_ndn,
+ int version,
+ int isroot,
+ slap_idassert_t *si,
+ LDAPControl *ctrl )
+{
+ slap_idassert_mode_t mode;
+ struct berval assertedID,
+ ndn;
+
+ rs->sr_err = SLAP_CB_CONTINUE;
+
+ /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID,
+ * but if it is not set this test fails. We need a different
+ * means to detect if idassert is enabled */
+ if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) )
+ && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) )
+ && BER_BVISNULL( &si->si_bc.sb_saslmech ) )
+ {
+ goto done;
+ }
+
+ if ( !op->o_conn || op->o_do_not_cache || ( isroot ) ) {
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+
+#if 0
+ } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+#endif
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) {
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we do not want to perform proxyAuthz
+ * on behalf of the client, because this would
+ * be performed with "proxyauthzdn" privileges.
+ *
+ * This might actually be too strict, since
+ * the "proxyauthzdn" authzTo, and each entry's
+ * authzFrom attributes may be crafted
+ * to avoid unwanted proxyAuthz to take place.
+ */
+#if 0
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+#endif
+ goto done;
+ }
+
+ if ( !BER_BVISNULL( bound_ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) {
+ goto done;
+ }
+
+ } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) {
+ if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ /* already asserted in SASL via native authz */
+ goto done;
+ }
+
+ } else if ( si->si_authz && !isroot ) {
+ int rc;
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+ } else {
+ authcDN = ndn;
+ }
+ rc = slap_sasl_matches( op, si->si_authz,
+ &authcDN, &authcDN );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ /* ndn is not authorized
+ * to use idassert */
+ rs->sr_err = rc;
+ }
+ goto done;
+ }
+ }
+
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we can:
+ * 1) ignore the already set proxyAuthz control
+ * 2) leave it in place, and don't set ours
+ * 3) add both
+ * 4) reject the operation
+ *
+ * option (4) is very drastic
+ * option (3) will make the remote server reject
+ * the operation, thus being equivalent to (4)
+ * option (2) will likely break the idassert
+ * assumptions, so we cannot accept it;
+ * option (1) means that we are contradicting
+ * the client's request.
+ *
+ * I think (4) is the only correct choice.
+ */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+ }
+
+ if ( op->o_is_auth_check ) {
+ mode = LDAP_BACK_IDASSERT_NOASSERT;
+
+ } else {
+ mode = si->si_mode;
+ }
+
+ switch ( mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ /* original behavior:
+ * assert the client's identity */
+ case LDAP_BACK_IDASSERT_SELF:
+ assertedID = ndn;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ /* assert "anonymous" */
+ assertedID = slap_empty_bv;
+ break;
+
+ case LDAP_BACK_IDASSERT_NOASSERT:
+ /* don't assert; bind as proxyauthzdn */
+ goto done;
+
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ /* assert idassert DN */
+ assertedID = si->si_bc.sb_authzId;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ /* if we got here, "" is allowed to proxyAuthz */
+ if ( BER_BVISNULL( &assertedID ) ) {
+ assertedID = slap_empty_bv;
+ }
+
+ /* don't idassert the bound DN (ITS#4497) */
+ if ( dn_match( &assertedID, bound_ndn ) ) {
+ goto done;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL );
+
+ switch ( si->si_mode ) {
+ /* already in u:ID or dn:DN form */
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ /* needs the dn: prefix */
+ default:
+ ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" );
+ ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1,
+ op->o_tmpmemctx );
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ],
+ assertedID.bv_val, assertedID.bv_len + 1 );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+
+ /* Older versions of <draft-weltman-ldapv3-proxy> required
+ * to encode the value of the authzID (and called it proxyDN);
+ * this hack provides compatibility with those DSAs that
+ * implement it this way */
+ if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ struct berval authzID = ctrl->ldctl_value;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ tag = ber_printf( ber, "O", &authzID );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ } else if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ struct berval authzID = ctrl->ldctl_value,
+ tmp;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ if ( strncasecmp( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ) != 0 ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ tmp = authzID;
+ tmp.bv_val += STRLENOF( "dn:" );
+ tmp.bv_len -= STRLENOF( "dn:" );
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* apparently, Mozilla API encodes this
+ * as "SEQUENCE { LDAPDN }" */
+ tag = ber_printf( ber, "{O}", &tmp );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ;
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber2:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+done:;
+
+ return rs->sr_err;
+}
+
+/*
+ * Add controls;
+ *
+ * if any needs to be added, it is prepended to existing ones,
+ * in a newly allocated array. The companion function
+ * mi->mi_ldap_extra->controls_free() must be used to restore the original
+ * status of op->o_ctrls.
+ */
+int
+asyncmeta_controls_add( Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int isroot,
+ LDAPControl ***pctrls )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ LDAPControl **ctrls = NULL;
+ /* set to the maximum number of controls this backend can add */
+ LDAPControl c[ 2 ] = {{ 0 }};
+ int n = 0, i, j1 = 0, j2 = 0, skipped = 0;
+
+ *pctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* don't add controls if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __before__ existing ones here */
+
+ /* proxyAuthz for identity assertion */
+ switch ( asyncmeta_back_proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn,
+ mt->mt_version, isroot, &mt->mt_idassert, &c[ j1 ] ) )
+ {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j1++;
+ break;
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ /* session tracking */
+ if ( META_BACK_TGT_ST_REQUEST( mt ) ) {
+ switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j2++;
+ break;
+
+ default:
+ goto done;
+ }
+ }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ /* if nothing to do, just bail out */
+ if ( j1 == 0 && j2 == 0 ) {
+ goto done;
+ }
+
+ assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) );
+
+ if ( op->o_ctrls ) {
+ for ( n = 0; op->o_ctrls[ n ]; n++ )
+ /* just count ctrls */ ;
+ }
+
+ ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ),
+ op->o_tmpmemctx );
+ if ( j1 ) {
+ ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+ *ctrls[ 0 ] = c[ 0 ];
+ for ( i = 1; i < j1; i++ ) {
+ ctrls[ i ] = &ctrls[ 0 ][ i ];
+ *ctrls[ i ] = c[ i ];
+ }
+ }
+
+ i = 0;
+ if ( op->o_ctrls ) {
+ LDAPControl *proxyauthz = ldap_control_find(
+ LDAP_CONTROL_PROXY_AUTHZ, op->o_ctrls, NULL );
+
+ for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+ /* Only replace it if we generated one */
+ if ( j1 && proxyauthz && proxyauthz == op->o_ctrls[ i ] ) {
+ /* Frontend has already checked only one is present */
+ assert( skipped == 0 );
+ skipped++;
+ continue;
+ }
+ ctrls[ i + j1 - skipped ] = op->o_ctrls[ i ];
+ }
+ }
+
+ n += j1 - skipped;
+ if ( j2 ) {
+ ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+ *ctrls[ n ] = c[ j1 ];
+ for ( i = 1; i < j2; i++ ) {
+ ctrls[ n + i ] = &ctrls[ n ][ i ];
+ *ctrls[ n + i ] = c[ i ];
+ }
+ }
+
+ ctrls[ n + j2 ] = NULL;
+
+done:;
+ if ( ctrls == NULL ) {
+ ctrls = op->o_ctrls;
+ }
+
+ *pctrls = ctrls;
+
+ return rs->sr_err;
+}
+
+
+/*
+ * asyncmeta_dobind_init()
+ *
+ * initiates bind for a candidate target
+ */
+meta_search_candidate_t
+asyncmeta_dobind_init(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+ SlapReply *candidates = bc->candidates;
+ a_metainfo_t *mi = ( a_metainfo_t * )mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval binddn = msc->msc_bound_ndn,
+ cred = msc->msc_cred;
+ int method;
+
+ int rc;
+ ber_int_t msgid;
+
+ meta_search_candidate_t retcode;
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_dobind_init[%d] msc %p\n",
+ op->o_log_prefix, candidate, msc );
+
+ if ( mc->mc_authz_target == META_BOUND_ALL ) {
+ return META_SEARCH_CANDIDATE;
+ }
+
+ if ( slapd_shutdown ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ return META_SEARCH_ERR;
+ }
+
+ retcode = META_SEARCH_BINDING;
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+ /* already bound (or anonymous) */
+
+#ifdef DEBUG_205
+ char buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+ int bound = 0;
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ bound = 1;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p%s DN=\"%s\"\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld, bound ? " bound" : " anonymous",
+ bound == 0 ? "" : msc->msc_bound_ndn.bv_val );
+#endif /* DEBUG_205 */
+
+ retcode = META_SEARCH_CANDIDATE;
+
+ } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) {
+ /* another thread is binding the target for this conn; wait */
+
+#ifdef DEBUG_205
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p needbind\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+ retcode = META_SEARCH_NEED_BIND;
+ } else {
+ /* we'll need to bind the target for this conn */
+
+#ifdef DEBUG_205
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p binding\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ if ( msc->msc_ld == NULL ) {
+ /* for some reason (e.g. because formerly in "binding"
+ * state, with eventual connection expiration or invalidation)
+ * it was not initialized as expected */
+
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p ld=NULL\n",
+ op->o_log_prefix, candidate, (void *)mc );
+
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( msc->msc_ld != NULL );
+ break;
+
+ case LDAP_SERVER_DOWN:
+ case LDAP_UNAVAILABLE:
+ goto down;
+
+ default:
+ goto other;
+ }
+ }
+
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ }
+
+ if ( retcode != META_SEARCH_BINDING ) {
+ return retcode;
+ }
+
+ if ( op->o_conn != NULL &&
+ !op->o_do_not_cache &&
+ ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+ BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+ {
+ rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, LDAP_BACK_DONTSEND, &binddn, &cred, &method );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+ case LDAP_UNAVAILABLE:
+ goto down;
+ default:
+ goto other;
+ }
+
+ /* NOTE: we copy things here, even if bind didn't succeed yet,
+ * because the connection is not shared until bind is over */
+ if ( !BER_BVISNULL( &binddn ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+ if ( META_BACK_TGT_SAVECRED( mt ) && !BER_BVISNULL( &cred ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ }
+ }
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ /* apparently, idassert was configured with SASL bind,
+ * so bind occurred inside meta_back_proxy_authz_cred() */
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ return META_SEARCH_CANDIDATE;
+ }
+
+ /* paranoid */
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+ /* do a simple bind with binddn, cred */
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ assert( msc->msc_ld != NULL );
+
+ if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) {
+ /* bind anonymously? */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p: "
+ "non-empty dn with empty cred; binding anonymously\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ cred = slap_empty_bv;
+
+ } else if ( BER_BVISEMPTY( &binddn ) && !BER_BVISEMPTY( &cred ) ) {
+ /* error */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p: "
+ "empty dn with non-empty cred: error\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ rc = LDAP_OTHER;
+ goto other;
+ }
+retry_bind:
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init sending bind msc: %p\n",
+ time_buf, msc );
+ }
+ rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred,
+ NULL, NULL, &msgid );
+ ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rc );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init rc=%d msc: %p\n",
+ time_buf, rc, msc );
+ }
+ if ( LogTest( LDAP_DEBUG_TRACE )) {
+ ber_socket_t s;
+ char sockname[LDAP_IPADDRLEN];
+ struct berval sockbv = BER_BVC( sockname );
+ Sockaddr addr;
+ socklen_t len = sizeof( addr );
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ getsockname( s, &addr.sa_addr, &len );
+ ldap_pvt_sockaddrstr( &addr, &sockbv );
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_dobind_init msc %p ld %p ldr %p fd %d addr %s\n",
+ op->o_log_prefix, msc, msc->msc_ld, msc->msc_ldr, s, sockname );
+ }
+
+ if (rc == LDAP_SERVER_DOWN ) {
+ goto down;
+ } else if (rc == LDAP_BUSY) {
+ if (rs->sr_text == NULL) {
+ rs->sr_text = "Unable to establish LDAP connection to target within the specified network timeout.";
+ }
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ goto other;
+ }
+ /* mark as need bind so it gets send when the bind response is received */
+ candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+ asyncmeta_set_msc_time(msc);
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p rc=%d\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)mc->mc_conns[candidate].msc_ld, rc );
+#endif /* DEBUG_205 */
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( msgid >= 0 );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init sending bind success msc: %p\n",
+ time_buf, msc );
+ }
+ META_BINDING_SET( &candidates[ candidate ] );
+ rs->sr_err = LDAP_SUCCESS;
+ msc->msc_binding_time = slap_get_time();
+ return META_SEARCH_BINDING;
+
+ case LDAP_X_CONNECTING:
+ /* must retry, same conn */
+ candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ goto retry_bind;
+
+ case LDAP_SERVER_DOWN:
+down:;
+ retcode = META_SEARCH_ERR;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ if (rs->sr_text == NULL) {
+ rs->sr_text = "Unable to bind to remote target - target down or unavailable";
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ break;
+
+ /* fall thru */
+
+ default:
+other:;
+ rs->sr_err = rc;
+ rc = slap_map_api2result( rs );
+ candidates[ candidate ].sr_err = rc;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ retcode = META_SEARCH_ERR;
+
+ } else {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ break;
+ }
+
+ return retcode;
+}
+
+
+
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+
+ int rc;
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+
+ if (META_BACK_CONN_INVALID(msc) || (LDAP_BACK_CONN_BINDING( msc ) && msc->msc_binding_time > 0
+ && (msc->msc_binding_time + mt->mt_timeout[ SLAP_OP_BIND ]) < slap_get_time())) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ), "called from %s:%d", __FILE__, __LINE__ );
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, buf);
+
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ }
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+ if ( mc->pending_ops > 1 ) {
+ asyncmeta_send_all_pending_ops( mc, candidate, op->o_threadctx, 1 );
+ }
+ return META_SEARCH_CANDIDATE;
+ }
+
+retry_dobind:
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+ if (rs->sr_err != LDAP_UNAVAILABLE && rs->sr_err != LDAP_BUSY) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+ } else if (bc->nretries[candidate] == 0) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ), "called from %s:%d", __FILE__, __LINE__ );
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, buf);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+ }
+ /* need to retry */
+ bc->nretries[candidate]--;
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ /* this lock is required; however,
+ * it's invoked only when logging is on */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_dobind_init_with_retry[%d]: retrying URI=\"%s\" DN=\"%s\".\n",
+ op->o_log_prefix, candidate, mt->mt_uri,
+ BER_BVISNULL(&msc->msc_bound_ndn) ? "" : msc->msc_bound_ndn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ }
+
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+ if (rs->sr_err != LDAP_SUCCESS) {
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return META_SEARCH_ERR;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ goto retry_dobind;
+ return rc;
+}
diff --git a/servers/slapd/back-asyncmeta/candidates.c b/servers/slapd/back-asyncmeta/candidates.c
new file mode 100644
index 0000000..5f3b228
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/candidates.c
@@ -0,0 +1,239 @@
+/* candidates.c - candidate targets selection and processing for
+ * back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The meta-directory has one suffix, called <suffix>.
+ * It handles a pool of target servers, each with a branch suffix
+ * of the form <branch X>,<suffix>, where <branch X> may be empty.
+ *
+ * When the meta-directory receives a request with a request DN that belongs
+ * to a branch, the corresponding target is invoked. When the request DN
+ * does not belong to a specific branch, all the targets that
+ * are compatible with the request DN are selected as candidates, and
+ * the request is spawned to all the candidate targets
+ *
+ * A request is characterized by a request DN. The following cases are
+ * handled:
+ * - the request DN is the suffix: <dn> == <suffix>,
+ * all the targets are candidates (search ...)
+ * - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or
+ * - the request DN is a subtree of a branch suffix:
+ * <dn> == <rdn>,<branch X>,<suffix>,
+ * the target is the only candidate.
+ *
+ * A possible extension will include the handling of multiple suffixes
+ */
+
+static a_metasubtree_t *
+asyncmeta_subtree_match( a_metatarget_t *mt, struct berval *ndn, int scope )
+{
+ a_metasubtree_t *ms = mt->mt_subtree;
+
+ for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) ) {
+ return ms;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) &&
+ ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) )
+ {
+ return ms;
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* NOTE: cannot handle scope */
+ if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
+ return ms;
+ }
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * returns 1 if suffix is candidate for dn, otherwise 0
+ *
+ * Note: this function should never be called if dn is the <suffix>.
+ */
+int
+asyncmeta_is_candidate(
+ a_metatarget_t *mt,
+ struct berval *ndn,
+ int scope )
+{
+ struct berval rdn;
+ int d = ndn->bv_len - mt->mt_nsuffix.bv_len;
+
+ if ( d >= 0 ) {
+ if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ /*
+ * | match | exclude |
+ * +---------+---------+-------------------+
+ * | T | T | not candidate |
+ * | F | T | continue checking |
+ * +---------+---------+-------------------+
+ * | T | F | candidate |
+ * | F | F | not candidate |
+ * +---------+---------+-------------------+
+ */
+
+ if ( mt->mt_subtree ) {
+ int match = ( asyncmeta_subtree_match( mt, ndn, scope ) != NULL );
+
+ if ( !mt->mt_subtree_exclude ) {
+ return match ? META_CANDIDATE : META_NOT_CANDIDATE;
+ }
+
+ if ( match /* && mt->mt_subtree_exclude */ ) {
+ return META_NOT_CANDIDATE;
+ }
+ }
+
+ switch ( mt->mt_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ default:
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( d > 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_ONELEVEL:
+ if ( d > 0 ) {
+ rdn.bv_val = ndn->bv_val;
+ rdn.bv_len = (ber_len_t)d - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_BASE:
+ if ( d == 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+
+ } else /* if ( d < 0 ) */ {
+ if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ switch ( scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ /*
+ * suffix longer than dn, but common part matches
+ */
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_ONELEVEL:
+ rdn.bv_val = mt->mt_nsuffix.bv_val;
+ rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+ }
+
+ return META_NOT_CANDIDATE;
+}
+
+/*
+ * meta_back_select_unique_candidate
+ *
+ * returns the index of the candidate in case it is unique, otherwise
+ * META_TARGET_NONE if none matches, or
+ * META_TARGET_MULTIPLE if more than one matches
+ * Note: ndn MUST be normalized.
+ */
+int
+asyncmeta_select_unique_candidate(
+ a_metainfo_t *mi,
+ struct berval *ndn )
+{
+ int i, candidate = META_TARGET_NONE;
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( asyncmeta_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) {
+ if ( candidate == META_TARGET_NONE ) {
+ candidate = i;
+
+ }
+ }
+ }
+
+ return candidate;
+}
+
+/*
+ * asyncmeta_clear_unused_candidates
+ *
+ * clears all candidates except candidate
+ */
+int
+asyncmeta_clear_unused_candidates(
+ Operation *op,
+ int candidate,
+ a_metaconn_t *mc,
+ SlapReply *candidates)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int i;
+
+ for ( i = 0; i < mi->mi_ntargets; ++i ) {
+ if ( i == candidate ) {
+ continue;
+ }
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/compare.c b/servers/slapd/back-asyncmeta/compare.c
new file mode 100644
index 0000000..8f56719
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/compare.c
@@ -0,0 +1,312 @@
+/* compare.c - compare exop handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval c_attr = op->orc_ava->aa_desc->ad_cname;
+ struct berval mdn = BER_BVNULL;
+ struct berval mapped_value = op->orc_ava->aa_value;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value );
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root,&ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_compare_req( msc->msc_ld, mdn.bv_val, c_attr.bv_val, &mapped_value,
+ ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_compare_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_COMPARE,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send compare request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) {
+ op->o_tmpfree( mapped_value.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_compare_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_compare( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_compare: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug( asyncmeta_debug, "==> asyncmeta_back_compare[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_COMPARE ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_compare_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/config.c b/servers/slapd/back-asyncmeta/config.c
new file mode 100644
index 0000000..e4cc5ea
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/config.c
@@ -0,0 +1,2536 @@
+/* config.c - configuration parsing for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldif.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+#ifdef LDAP_DEVEL
+#define SLAP_AUTH_DN 1
+#endif
+
+static ConfigDriver asyncmeta_back_cf_gen;
+static ConfigLDAPadd asyncmeta_ldadd;
+static ConfigCfAdd asyncmeta_cfadd;
+
+/* Three sets of enums:
+ * 1) attrs that are only valid in the base config
+ * 2) attrs that are valid in base or target
+ * 3) attrs that are only valid in a target
+ */
+
+/* Base attrs */
+enum {
+ LDAP_BACK_CFG_DNCACHE_TTL = 1,
+ LDAP_BACK_CFG_IDLE_TIMEOUT,
+ LDAP_BACK_CFG_ONERR,
+ LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ LDAP_BACK_CFG_CONNPOOLMAX,
+ LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+ LDAP_BACK_CFG_MAX_PENDING_OPS,
+ LDAP_BACK_CFG_MAX_TARGET_CONNS,
+ LDAP_BACK_CFG_LAST_BASE,
+};
+
+/* Base or target */
+enum {
+ LDAP_BACK_CFG_BIND_TIMEOUT = LDAP_BACK_CFG_LAST_BASE,
+ LDAP_BACK_CFG_CANCEL,
+ LDAP_BACK_CFG_CHASE,
+ LDAP_BACK_CFG_CLIENT_PR,
+ LDAP_BACK_CFG_DEFAULT_T,
+ LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ LDAP_BACK_CFG_NOREFS,
+ LDAP_BACK_CFG_NOUNDEFFILTER,
+ LDAP_BACK_CFG_NRETRIES,
+ LDAP_BACK_CFG_QUARANTINE,
+ LDAP_BACK_CFG_REBIND,
+ LDAP_BACK_CFG_TIMEOUT,
+ LDAP_BACK_CFG_VERSION,
+ LDAP_BACK_CFG_ST_REQUEST,
+ LDAP_BACK_CFG_T_F,
+ LDAP_BACK_CFG_TLS,
+ LDAP_BACK_CFG_LAST_BOTH
+};
+
+/* Target attrs */
+enum {
+ LDAP_BACK_CFG_URI = LDAP_BACK_CFG_LAST_BOTH,
+ LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ LDAP_BACK_CFG_IDASSERT_BIND,
+ LDAP_BACK_CFG_SUFFIXM,
+ LDAP_BACK_CFG_SUBTREE_EX,
+ LDAP_BACK_CFG_SUBTREE_IN,
+ LDAP_BACK_CFG_KEEPALIVE,
+ LDAP_BACK_CFG_FILTER,
+ LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ LDAP_BACK_CFG_LAST
+};
+
+static ConfigTable a_metacfg[] = {
+ { "uri", "uri", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_URI,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:0.14 "
+ "NAME 'olcDbURI' "
+ "DESC 'URI (list) for remote DSA' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tls", "what", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TLS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.1 "
+ "NAME 'olcDbStartTLS' "
+ "DESC 'StartTLS' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-bind", "args", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.7 "
+ "NAME 'olcDbIDAssertBind' "
+ "DESC 'Remote Identity Assertion administrative identity auth bind configuration' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-authzFrom", "authzRule", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.9 "
+ "NAME 'olcDbIDAssertAuthzFrom' "
+ "DESC 'Remote Identity Assertion authz rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "rebind-as-user", "true|FALSE", 1, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.10 "
+ "NAME 'olcDbRebindAsUser' "
+ "DESC 'Rebind as user' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "chase-referrals", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.11 "
+ "NAME 'olcDbChaseReferrals' "
+ "DESC 'Chase referrals' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "t-f-support", "true|FALSE|discover", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_T_F,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.12 "
+ "NAME 'olcDbTFSupport' "
+ "DESC 'Absolute filters support' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "timeout", "timeout(list)", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.14 "
+ "NAME 'olcDbTimeout' "
+ "DESC 'Per-operation timeouts' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idle-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.15 "
+ "NAME 'olcDbIdleTimeout' "
+ "DESC 'connection idle timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "network-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.17 "
+ "NAME 'olcDbNetworkTimeout' "
+ "DESC 'connection network timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "protocol-version", "version", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.18 "
+ "NAME 'olcDbProtocolVersion' "
+ "DESC 'protocol version' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "cancel", "ABANDON|ignore|exop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CANCEL,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.20 "
+ "NAME 'olcDbCancel' "
+ "DESC 'abandon/ignore/exop operations when appropriate' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "quarantine", "retrylist", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.21 "
+ "NAME 'olcDbQuarantine' "
+ "DESC 'Quarantine database if connection fails and retry according to rule' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "conn-pool-max", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.23 "
+ "NAME 'olcDbConnectionPoolMax' "
+ "DESC 'Max size of privileged connections pool' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ { "session-tracking-request", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.24 "
+ "NAME 'olcDbSessionTrackingRequest' "
+ "DESC 'Add session tracking control to proxied requests' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+ { "norefs", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.25 "
+ "NAME 'olcDbNoRefs' "
+ "DESC 'Do not return search reference responses' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "noundeffilter", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.26 "
+ "NAME 'olcDbNoUndefFilter' "
+ "DESC 'Do not propagate undefined search filters' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "suffixmassage", "local> <remote", 2, 3, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.117 "
+ "NAME 'olcDbSuffixMassage' "
+ "DESC 'DN suffix massage' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "subtree-exclude", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.103 "
+ "NAME 'olcDbSubtreeExclude' "
+ "DESC 'DN of subtree to exclude from target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "subtree-include", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.104 "
+ "NAME 'olcDbSubtreeInclude' "
+ "DESC 'DN of subtree to include in target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "default-target", "[none|<target ID>]", 1, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.105 "
+ "NAME 'olcDbDefaultTarget' "
+ "DESC 'Specify the default target' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "dncache-ttl", "ttl", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DNCACHE_TTL,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.106 "
+ "NAME 'olcDbDnCacheTtl' "
+ "DESC 'dncache ttl' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "bind-timeout", "microseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_ULONG|LDAP_BACK_CFG_BIND_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.107 "
+ "NAME 'olcDbBindTimeout' "
+ "DESC 'bind timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "onerr", "CONTINUE|report|stop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_ONERR,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.108 "
+ "NAME 'olcDbOnErr' "
+ "DESC 'error handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "pseudoroot-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.109 "
+ "NAME 'olcDbPseudoRootBindDefer' "
+ "DESC 'error handling' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "root-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ asyncmeta_back_cf_gen, NULL, NULL, NULL },
+ { "nretries", "NEVER|forever|<number>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NRETRIES,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.110 "
+ "NAME 'olcDbNretries' "
+ "DESC 'retry handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.111 "
+ "NAME 'olcDbClientPr' "
+ "DESC 'PagedResults handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "", "", 0, 0, 0, ARG_IGNORED,
+ NULL, "( OLcfgDbAt:3.116 NAME 'olcAsyncMetaSub' "
+ "DESC 'Placeholder to name a Target entry' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+
+ { "keepalive", "keepalive", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.29 "
+ "NAME 'olcDbKeepalive' "
+ "DESC 'TCP keepalive' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "tcp-user-timeout", "milliseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.30 "
+ "NAME 'olcDbTcpUserTimeout' "
+ "DESC 'TCP User Timeout' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "filter", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_FILTER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.112 "
+ "NAME 'olcDbFilter' "
+ "DESC 'Filter regex pattern to include in target' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+
+ { "max-pending-ops", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_PENDING_OPS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.113 "
+ "NAME 'olcDbMaxPendingOps' "
+ "DESC 'Maximum number of pending operations' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "max-target-conns", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TARGET_CONNS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.114 "
+ "NAME 'olcDbMaxTargetConns' "
+ "DESC 'Maximum number of open connections per target' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "max-timeout-ops", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.115 "
+ "NAME 'olcDbMaxTimeoutOps' "
+ "DESC 'Maximum number of consecutive timeout operations after which the connection is reset' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define ST_ATTR "$ olcDbSessionTrackingRequest "
+#else
+#define ST_ATTR ""
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define COMMON_ATTRS \
+ "$ olcDbBindTimeout " \
+ "$ olcDbCancel " \
+ "$ olcDbChaseReferrals " \
+ "$ olcDbClientPr " \
+ "$ olcDbDefaultTarget " \
+ "$ olcDbNetworkTimeout " \
+ "$ olcDbNoRefs " \
+ "$ olcDbNoUndefFilter " \
+ "$ olcDbNretries " \
+ "$ olcDbProtocolVersion " \
+ "$ olcDbQuarantine " \
+ "$ olcDbRebindAsUser " \
+ ST_ATTR \
+ "$ olcDbStartTLS " \
+ "$ olcDbTFSupport "
+
+static ConfigOCs a_metaocs[] = {
+ { "( OLcfgDbOc:3.4 "
+ "NAME 'olcAsyncMetaConfig' "
+ "DESC 'Asyncmeta backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcDbDnCacheTtl "
+ "$ olcDbIdleTimeout "
+ "$ olcDbOnErr "
+ "$ olcDbPseudoRootBindDefer "
+ "$ olcDbConnectionPoolMax "
+ "$ olcDbMaxTimeoutOps"
+ "$ olcDbMaxPendingOps "
+ "$ olcDbMaxTargetConns"
+ /* defaults, may be overridden per-target */
+ COMMON_ATTRS
+ ") )",
+ Cft_Database, a_metacfg, NULL, asyncmeta_cfadd },
+ { "( OLcfgDbOc:3.5 "
+ "NAME 'olcAsyncMetaTargetConfig' "
+ "DESC 'Asyncmeta target configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( olcAsyncMetaSub $ olcDbURI ) "
+ "MAY ( olcDbIDAssertAuthzFrom "
+ "$ olcDbIDAssertBind "
+ "$ olcDbSuffixMassage "
+ "$ olcDbSubtreeExclude "
+ "$ olcDbSubtreeInclude "
+ "$ olcDbTimeout "
+ "$ olcDbKeepalive "
+ "$ olcDbFilter "
+ "$ olcDbTcpUserTimeout "
+
+ /* defaults may be inherited */
+ COMMON_ATTRS
+ ") )",
+ Cft_Misc, a_metacfg, asyncmeta_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int
+asyncmeta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c )
+{
+ a_metainfo_t *mi;
+
+ if ( p->ce_type != Cft_Database || !p->ce_be ||
+ p->ce_be->be_cf_ocs != a_metaocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ c->be = p->ce_be;
+ mi = ( a_metainfo_t * )c->be->be_private;
+
+ if ( asyncmeta_db_has_pending_ops ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify a working database" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ struct berval bv;
+ int i;
+
+ bv.bv_val = c->cr_msg;
+ for ( i=0; i<mi->mi_ntargets; i++ ) {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "olcAsyncMetaSub=" SLAP_X_ORDERED_FMT "uri", i );
+ c->ca_private = mi->mi_targets[i];
+ c->valx = i;
+ config_build_entry( op, rs, p->e_private, c,
+ &bv, &a_metaocs[1], NULL );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_back_new_target(
+ a_metatarget_t **mtp,
+ a_metainfo_t *mi )
+{
+ a_metatarget_t *mt;
+
+ *mtp = NULL;
+ int i;
+
+ assert ( mi != NULL );
+ mt = ch_calloc( sizeof( a_metatarget_t ), 1 );
+
+ ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex );
+
+ mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY;
+ mt->mt_idassert_authmethod = LDAP_AUTH_NONE;
+ mt->mt_idassert_tls = SB_TLS_DEFAULT;
+ /* by default, use proxyAuthz control on each operation */
+ mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+ *mtp = mt;
+
+ for ( i = 0; i < mi->mi_num_conns; i++ ) {
+ a_metaconn_t *mc = &mi->mi_conns[i];
+ mc->mc_conns = ch_realloc( mc->mc_conns, sizeof( a_metasingleconn_t ) * mi->mi_ntargets);
+ memset( &(mc->mc_conns[mi->mi_ntargets-1]), 0, sizeof( a_metasingleconn_t ) );
+ }
+ /* If this is the first target, start the timeout loop */
+ if ( mi->mi_ntargets == 1 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 1,
+ asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", mi->mi_suffix.bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ return 0;
+}
+
+/* suffixmassage config */
+static int
+asyncmeta_suffixm_config(
+ ConfigArgs *c,
+ int argc,
+ char **argv,
+ a_metatarget_t *mt
+)
+{
+ BackendDB *tmp_bd;
+ struct berval dn, nvnc, pvnc, nrnc, prnc;
+ int j;
+
+ /*
+ * syntax:
+ *
+ * suffixmassage <local suffix> <remote suffix>
+ *
+ * the <local suffix> field must be defined as a valid suffix
+ * (or suffixAlias?) for the current database;
+ * the <remote suffix> shouldn't have already been
+ * defined as a valid suffix or suffixAlias for the
+ * current server
+ */
+
+ ber_str2bv( argv[ 1 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" is invalid",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &nvnc, &c->be->be_nsuffix[ 0 ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" must be within the database naming context",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ ber_str2bv( argv[ 2 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "massaged suffix \"%s\" is invalid",
+ argv[2] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ tmp_bd = select_backend( &nrnc, 0 );
+ if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning: <massaged suffix> \"%s\" resolves to this database, in "
+ "\"suffixMassage <suffix> <massaged suffix>\"\n",
+ c->log, prnc.bv_val );
+ }
+
+ mt->mt_lsuffixm = pvnc;
+ mt->mt_rsuffixm = prnc;
+
+ free( nvnc.bv_val );
+ free( nrnc.bv_val );
+
+ return 0;
+}
+
+int
+asyncmeta_subtree_free( a_metasubtree_t *ms )
+{
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ber_memfree( ms->ms_dn.bv_val );
+ break;
+
+ case META_ST_REGEX:
+ regfree( &ms->ms_regex );
+ ber_memfree( ms->ms_regex_pattern.bv_val );
+ break;
+
+ default:
+ return -1;
+ }
+
+ ch_free( ms );
+ return 0;
+}
+
+int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms )
+{
+ if ( ms->ms_next ) {
+ asyncmeta_subtree_destroy( ms->ms_next );
+ }
+
+ return asyncmeta_subtree_free( ms );
+}
+
+static void
+asyncmeta_filter_free( metafilter_t *mf )
+{
+ regfree( &mf->mf_regex );
+ ber_memfree( mf->mf_regex_pattern.bv_val );
+ ch_free( mf );
+}
+
+void
+asyncmeta_filter_destroy( metafilter_t *mf )
+{
+ if ( mf->mf_next )
+ asyncmeta_filter_destroy( mf->mf_next );
+ asyncmeta_filter_free( mf );
+}
+
+static struct berval st_styles[] = {
+ BER_BVC("subtree"),
+ BER_BVC("children"),
+ BER_BVC("regex")
+};
+
+static int
+asyncmeta_subtree_unparse(
+ ConfigArgs *c,
+ a_metatarget_t *mt )
+{
+ a_metasubtree_t *ms;
+ struct berval bv, *style;
+
+ if ( !mt->mt_subtree )
+ return 1;
+
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude )
+ return 1;
+
+ bv.bv_val = c->cr_msg;
+ for ( ms=mt->mt_subtree; ms; ms=ms->ms_next ) {
+ if (ms->ms_type == META_ST_SUBTREE)
+ style = &st_styles[0];
+ else if ( ms->ms_type == META_ST_SUBORDINATE )
+ style = &st_styles[1];
+ else if ( ms->ms_type == META_ST_REGEX )
+ style = &st_styles[2];
+ else {
+ assert(0);
+ continue;
+ }
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "dn.%s:%s", style->bv_val, ms->ms_dn.bv_val );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ return 0;
+}
+
+static int
+asyncmeta_subtree_config(
+ a_metatarget_t *mt,
+ ConfigArgs *c )
+{
+ meta_st_t type = META_ST_SUBTREE;
+ char *pattern;
+ struct berval ndn = BER_BVNULL;
+ a_metasubtree_t *ms = NULL;
+
+ if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) {
+ if ( mt->mt_subtree && !mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" );
+ return 1;
+ }
+
+ mt->mt_subtree_exclude = 1;
+
+ } else {
+ if ( mt->mt_subtree && mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" );
+ return 1;
+ }
+ }
+
+ pattern = c->argv[1];
+ if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) {
+ char *style;
+
+ pattern = &pattern[STRLENOF( "dn")];
+
+ if ( pattern[0] == '.' ) {
+ style = &pattern[1];
+
+ if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "subtree" )];
+
+ } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) {
+ type = META_ST_SUBORDINATE;
+ pattern = &style[STRLENOF( "children" )];
+
+ } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "sub" )];
+
+ } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) {
+ type = META_ST_REGEX;
+ pattern = &style[STRLENOF( "regex" )];
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" );
+ return 1;
+ }
+ }
+
+ if ( pattern[0] != ':' ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" );
+ return 1;
+ }
+ pattern++;
+ }
+
+ switch ( type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE: {
+ struct berval dn;
+
+ ber_str2bv( pattern, 0, 0, &dn );
+ if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL )
+ != LDAP_SUCCESS )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern );
+ return 1;
+ }
+
+ if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "DN=\"%s\" is not a subtree of target \"%s\"",
+ pattern, mt->mt_nsuffix.bv_val );
+ ber_memfree( ndn.bv_val );
+ return( 1 );
+ }
+ } break;
+
+ default:
+ /* silence warnings */
+ break;
+ }
+
+ ms = ch_calloc( sizeof( a_metasubtree_t ), 1 );
+ ms->ms_type = type;
+
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ms->ms_dn = ndn;
+ break;
+
+ case META_ST_REGEX: {
+ int rc;
+
+ rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE );
+ if ( rc != 0 ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+
+ regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) );
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ pattern, regerr );
+ ch_free( ms );
+ return 1;
+ }
+ ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern );
+ } break;
+ }
+
+ if ( mt->mt_subtree == NULL ) {
+ mt->mt_subtree = ms;
+
+ } else {
+ a_metasubtree_t **msp;
+
+ for ( msp = &mt->mt_subtree; *msp; ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_REGEX:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n",
+ c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val );
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* no check possible */
+ break;
+ }
+ break;
+ }
+
+ msp = &(*msp)->ms_next;
+ }
+
+ *msp = ms;
+ }
+
+ return 0;
+}
+
+static slap_verbmasks idassert_mode[] = {
+ { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF },
+ { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS },
+ { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT },
+ { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks tls_mode[] = {
+ { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK },
+ { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS },
+ { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK },
+ { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS },
+ { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS },
+ { BER_BVC( "none" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "yes" ), LDAP_BACK_F_T_F },
+ { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER },
+ { BER_BVC( "no" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks cancel_mode[] = {
+ { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE },
+ { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP },
+ { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER },
+ { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks onerr_mode[] = {
+ { BER_BVC( "stop" ), META_BACK_F_ONERR_STOP },
+ { BER_BVC( "report" ), META_BACK_F_ONERR_REPORT },
+ { BER_BVC( "continue" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+/* see enum in slap.h */
+static slap_cf_aux_table timeout_table[] = {
+ { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL },
+ /* unbind makes no sense */
+ { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL },
+ /* abandon makes little sense */
+#if 0 /* not implemented yet */
+ { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL },
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+static int
+asyncmeta_cf_cleanup( ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ a_metatarget_t *mt = c->ca_private;
+
+ return asyncmeta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg ));
+}
+
+static int
+asyncmeta_back_cf_gen( ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ a_metatarget_t *mt = NULL;
+ a_metacommon_t *mc = NULL;
+
+ int i, rc = 0;
+
+ assert( mi != NULL );
+
+ if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) {
+ if ( !mi )
+ return 1;
+
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ mc = &mt->mt_mc;
+ }
+ }
+
+ if ( c->op != SLAP_CONFIG_EMIT && asyncmeta_db_has_pending_ops ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify a working database" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch( c->type ) {
+ /* Base attrs */
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) {
+ return 1;
+ } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ if ( mi->mi_idle_timeout == 0 ) {
+ return 1;
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv );
+ if ( BER_BVISNULL( &bv )) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi );
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ c->value_int = mi->mi_conn_priv_max;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ if ( mc->mc_bind_timeout.tv_sec == 0 &&
+ mc->mc_bind_timeout.tv_usec == 0 ) {
+ return 1;
+ } else {
+ c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL +
+ mc->mc_bind_timeout.tv_usec;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CANCEL: {
+ slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2;
+
+ if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ mask &= ~LDAP_BACK_F_CANCEL_EXOP;
+ }
+ enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_CHASE:
+ c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc);
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) {
+ return 1;
+ } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) {
+ BER_BVSTR( &bv, "accept-unsolicited" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE )
+ return 1;
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ if ( mc->mc_network_timeout == 0 ) {
+ return 1;
+ }
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%ld",
+ mc->mc_network_timeout );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ c->value_int = META_BACK_CMN_NOREFS(mc);
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc);
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ if ( mc->mc_nretries == META_RETRY_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else if ( mc->mc_nretries == META_RETRY_NEVER ) {
+ BER_BVSTR( &bv, "never" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d",
+ mc->mc_nretries );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( !META_BACK_CMN_QUARANTINE( mc )) {
+ rc = 1;
+ break;
+ }
+ rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv );
+ if ( rc == 0 ) {
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ c->value_int = META_BACK_CMN_SAVECRED(mc);
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( mc->mc_timeout[ i ] != META_BACK_CFG_DEFAULT_OPS_TIMEOUT ) {
+ break;
+ }
+ }
+
+ if ( i == SLAP_OP_LAST ) {
+ return 1;
+ }
+
+ BER_BVZERO( &bv );
+ slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table );
+
+ if ( BER_BVISNULL( &bv ) ) {
+ return 1;
+ }
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( mc->mc_version == 0 )
+ return 1;
+ c->value_int = mc->mc_version;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ c->value_int = META_BACK_CMN_ST_REQUEST( mc );
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS: {
+ struct berval bc = BER_BVNULL, bv2;
+
+ if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) {
+ rc = 1;
+ break;
+ }
+ enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv );
+ assert( !BER_BVISNULL( &bv ) );
+
+ if ( mt ) {
+ bindconf_tls_unparse( &mt->mt_tls, &bc );
+ }
+
+ if ( !BER_BVISEMPTY( &bc )) {
+ bv2.bv_len = bv.bv_len + bc.bv_len + 1;
+ bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+ strcpy( bv2.bv_val, bv.bv_val );
+ bv2.bv_val[bv.bv_len] = ' ';
+ strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val );
+ ber_memfree( bc.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv2 );
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI: {
+ char *p2, *p1 = strchr( mt->mt_uri, ' ' );
+ bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ p2 = bv.bv_val;
+ *p2++ = '"';
+ if ( p1 ) {
+ p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri );
+ } else {
+ p2 = lutil_strcopy( p2, mt->mt_uri );
+ }
+ *p2++ = '/';
+ p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val );
+ *p2++ = '"';
+ if ( p1 ) {
+ strcpy( p2, p1 );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+ int i;
+ struct berval bv = BER_BVNULL;
+ char buf[SLAP_TEXT_BUFLEN];
+
+ bvp = &mt->mt_idassert_authz;
+ if ( *bvp == NULL ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ {
+ BER_BVSTR( &bv, "*" );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) {
+ char *ptr;
+ int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len = ((*bvp)[ i ]).bv_len + len;
+ bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ if ( bv.bv_val ) {
+ ber_memfree( bv.bv_val );
+ }
+ break;
+ }
+
+ case LDAP_BACK_CFG_IDASSERT_BIND: {
+ int i;
+ struct berval bc = BER_BVNULL;
+ char *ptr;
+
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) {
+ return 1;
+ } else {
+ ber_len_t len;
+
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ break;
+
+ default: {
+ struct berval mode = BER_BVNULL;
+
+ enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode );
+ if ( BER_BVISNULL( &mode ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ ptr = lutil_strcopy( bv.bv_val, "mode=" );
+ ptr = lutil_strcopy( ptr, mode.bv_val );
+ }
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) {
+ len = bv.bv_len + STRLENOF( "authz=native" );
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ (void)lutil_strcopy( ptr, "authz=native" );
+ }
+
+ len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" );
+ /* flags */
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ ptr = lutil_strcopy( ptr, "flags=" );
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ ptr = lutil_strcopy( ptr, "prescriptive" );
+ } else {
+ ptr = lutil_strcopy( ptr, "non-prescriptive" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ ptr = lutil_strcopy( ptr, ",override" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" );
+
+ } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-critical" );
+
+ } else {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" );
+ }
+
+#ifdef SLAP_AUTH_DN
+ switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) {
+ case LDAP_BACK_AUTH_DN_AUTHZID:
+ ptr = lutil_strcopy( ptr, ",dn-authzid" );
+ break;
+
+ case LDAP_BACK_AUTH_DN_WHOAMI:
+ ptr = lutil_strcopy( ptr, ",dn-whoami" );
+ break;
+
+ default:
+#if 0 /* implicit */
+ ptr = lutil_strcopy( ptr, ",dn-none" );
+#endif
+ break;
+ }
+#endif
+
+ bv.bv_len = ( ptr - bv.bv_val );
+ /* end-of-flags */
+ }
+
+ bindconf_unparse( &mt->mt_idassert.si_bc, &bc );
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ ber_len_t len = bv.bv_len + bc.bv_len;
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ assert( bc.bv_val[ 0 ] == ' ' );
+
+ ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val );
+ free( bc.bv_val );
+ bv.bv_len = ptr - bv.bv_val;
+
+ } else {
+ for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bc.bv_len -= i;
+ AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 );
+ }
+
+ bv = bc;
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+
+ break;
+ }
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ if ( mt->mt_lsuffixm.bv_val ) {
+ struct berval bv;
+ char *ptr;
+ bv.bv_len = mt->mt_lsuffixm.bv_len + 2 + 1 + mt->mt_rsuffixm.bv_len + 2;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy(ptr, mt->mt_lsuffixm.bv_val);
+ ptr = lutil_strcopy(ptr, "\" \"");
+ ptr = lutil_strcopy(ptr, mt->mt_rsuffixm.bv_val);
+ *ptr++ = '"';
+ *ptr = '\0';
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ } else
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ rc = asyncmeta_subtree_unparse( c, mt );
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( mt->mt_filter == NULL ) {
+ rc = 1;
+ } else {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next )
+ value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern );
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ c->value_int = mi->mi_max_pending_ops;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ c->value_int = mi->mi_max_target_conns;
+ break;
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ c->value_int = mi->mi_max_timeout_ops;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ char buf[AC_LINE_MAX];
+ bv.bv_len = AC_LINE_MAX;
+ bv.bv_val = &buf[0];
+ slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1);
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ }
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ c->value_uint = mt->mt_tls.sb_tcp_user_timeout;
+ break;
+
+ default:
+ rc = 1;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+
+ switch( c->type ) {
+ /* Base attrs */
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ mi->mi_idle_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_bind_timeout.tv_sec = 0;
+ mc->mc_bind_timeout.tv_usec = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_network_timeout = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ mc->mc_nretries = META_RETRY_DEFAULT;
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( META_BACK_CMN_QUARANTINE( mc )) {
+ mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine );
+ mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE;
+ if ( mc == &mt->mt_mc ) {
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ mt->mt_isquarantined = 0;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ mc->mc_timeout[ i ] = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_version = 0;
+ }
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ if ( mt )
+ bindconf_free( &mt->mt_tls );
+ break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ mt->mt_uri = NULL;
+ }
+ }
+ /* FIXME: should have a way to close all cached
+ * connections associated with this target.
+ */
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+
+ bvp = &mt->mt_idassert_authz;
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( c->valx < 0 ) {
+ if ( *bvp != NULL ) {
+ ber_bvarray_free( *bvp );
+ *bvp = NULL;
+ }
+
+ } else {
+ if ( *bvp == NULL ) {
+ rc = 1;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ )
+ ;
+
+ if ( i >= c->valx ) {
+ rc = 1;
+ break;
+ }
+ ber_memfree( ((*bvp)[ c->valx ]).bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) {
+ (*bvp)[ i ] = (*bvp)[ i + 1 ];
+ }
+ BER_BVZERO( &((*bvp)[ i ]) );
+ }
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ bindconf_free( &mt->mt_idassert.si_bc );
+ memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) );
+ }
+ break;
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( mt->mt_lsuffixm.bv_val ) {
+ ch_free( mt->mt_lsuffixm.bv_val );
+ ch_free( mt->mt_rsuffixm.bv_val );
+ BER_BVZERO( &mt->mt_lsuffixm );
+ BER_BVZERO( &mt->mt_rsuffixm );
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) {
+ rc = 1;
+ break;
+ }
+ if ( c->valx < 0 ) {
+ asyncmeta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ } else {
+ a_metasubtree_t *ms, **mprev;
+ for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = ms->ms_next;
+ asyncmeta_subtree_free( ms );
+ break;
+ }
+ i++;
+ mprev = &ms->ms_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( c->valx < 0 ) {
+ asyncmeta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ } else {
+ metafilter_t *mf, **mprev;
+ for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = mf->mf_next;
+ asyncmeta_filter_free( mf );
+ break;
+ }
+ i++;
+ mprev = &mf->mf_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ mi->mi_max_pending_ops = 0;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-target-conns cannot be modified at runtime" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ mi->mi_max_timeout_ops = 0;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mt->mt_tls.sb_keepalive.sk_idle = 0;
+ mt->mt_tls.sb_keepalive.sk_probes = 0;
+ mt->mt_tls.sb_keepalive.sk_interval = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = 0;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ if ( c->op == SLAP_CONFIG_ADD ) {
+ if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) {
+ /* exclude CFG_URI from this check */
+ if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) {
+ if ( !mi->mi_ntargets ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ if ( mi->mi_ntargets ) {
+ mt = mi->mi_targets[ mi->mi_ntargets-1 ];
+ mc = &mt->mt_mc;
+ } else {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ }
+ }
+ } else {
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ if ( mt )
+ mc = &mt->mt_mc;
+ else
+ mc = NULL;
+ }
+ }
+
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI: {
+ LDAPURLDesc *ludp;
+ struct berval dn;
+ int j;
+
+ char **uris = NULL;
+
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "the suffix must be defined before any target" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ i = mi->mi_ntargets++;
+
+ mi->mi_targets = ( a_metatarget_t ** )ch_realloc( mi->mi_targets,
+ sizeof( a_metatarget_t * ) * mi->mi_ntargets );
+ if ( mi->mi_targets == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "out of memory while storing server name"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( asyncmeta_back_new_target( &mi->mi_targets[ i ], mi ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to init server"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mt = mi->mi_targets[ i ];
+
+ mt->mt_rebind_f = mi->mi_rebind_f;
+ mt->mt_urllist_f = mi->mi_urllist_f;
+ mt->mt_urllist_p = mt;
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ mt->mt_mc = mi->mi_mc;
+
+ for ( j = 1; j < c->argc; j++ ) {
+ char **tmpuris = ldap_str2charray( c->argv[ j ], "\t" );
+
+ if ( tmpuris == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URIs #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( j == 1 ) {
+ uris = tmpuris;
+
+ } else {
+ ldap_charray_merge( &uris, tmpuris );
+ ldap_charray_free( tmpuris );
+ }
+ }
+
+ for ( j = 0; uris[ j ] != NULL; j++ ) {
+ char *tmpuri = NULL;
+
+ /*
+ * uri MUST be legal!
+ */
+ if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t",
+ LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS
+ || ludp->lud_next != NULL )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URI #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ if ( j == 0 ) {
+
+ /*
+ * uri MUST have the <dn> part!
+ */
+ if ( ludp->lud_dn == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing <naming context> "
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ /*
+ * copies and stores uri and suffix
+ */
+ ber_str2bv( ludp->lud_dn, 0, 0, &dn );
+ rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix,
+ &mt->mt_nsuffix, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "target DN is invalid \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ ludp->lud_dn[ 0 ] = '\0';
+
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_DEFAULT:
+ mt->mt_scope = LDAP_SCOPE_SUBTREE;
+ break;
+
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ mt->mt_scope = ludp->lud_scope;
+ break;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid scope for target \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ } else {
+ /* check all, to apply the scope check on the first one */
+ if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "multiple URIs must have no DN part" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+
+ }
+ }
+
+ tmpuri = ldap_url_list2urls( ludp );
+ ldap_free_urllist( ludp );
+ if ( tmpuri == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+ ldap_memfree( uris[ j ] );
+ uris[ j ] = tmpuri;
+ }
+
+ mt->mt_uri = ldap_charray2str( uris, " " );
+ ldap_charray_free( uris );
+ if ( mt->mt_uri == NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ /*
+ * uri MUST be a branch of suffix!
+ */
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<naming context> of URI must be within the naming context of this database." );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ c->ca_private = mt;
+ config_push_cleanup( c, asyncmeta_cf_cleanup );
+ } break;
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* subtree-exclude */
+ if ( asyncmeta_subtree_config( mt, c )) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER: {
+ metafilter_t *mf, **m2;
+ mf = ch_malloc( sizeof( metafilter_t ));
+ rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED );
+ if ( rc ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+ regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ c->argv[1], regerr );
+ ch_free( mf );
+ return 1;
+ }
+ ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern );
+ for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next )
+ ;
+ *m2 = mf;
+ } break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-pending-ops invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_pending_ops = c->value_int;
+ break;
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ {
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-target-conns invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_target_conns = c->value_int;
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-timeout-ops invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_timeout_ops = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ /* default target directive */
+ i = mi->mi_ntargets - 1;
+
+ if ( c->argc == 1 ) {
+ if ( i < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" alone must be inside a \"uri\" directive",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_defaulttarget = i;
+
+ } else {
+ if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) {
+ if ( i >= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s none\" should go before uri definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+
+ } else {
+
+ if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0
+ || mi->mi_defaulttarget < 0
+ || mi->mi_defaulttarget >= i - 1 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal target number %d",
+ mi->mi_defaulttarget );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ /* ttl of dn cache */
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+
+ } else {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse dncache ttl \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_cache.ttl = (time_t)t;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+ /* network timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse network timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_network_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+ /* idle timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse idle timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+
+ }
+ mi->mi_idle_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ /* bind timeout when connecting to ldap servers */
+ mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000;
+ mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000;
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ /* save bind creds for referral rebinds? */
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_SAVECRED;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ i = verb_to_mask( c->argv[1], tls_mode );
+ if ( BER_BVISNULL( &tls_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ mc->mc_flags |= tls_mode[i].mask;
+
+ if ( c->argc > 2 ) {
+ if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( i = 2; i < c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls ))
+ return 1;
+ }
+ bindconf_tls_defaults( &mt->mt_tls );
+ }
+ break;
+
+ case LDAP_BACK_CFG_T_F:
+ i = verb_to_mask( c->argv[1], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ mc->mc_flags |= t_f_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ /* onerr? */
+ i = verb_to_mask( c->argv[1], onerr_mode );
+ if ( BER_BVISNULL( &onerr_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ mi->mi_flags |= onerr_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ /* bind-defer? */
+ if ( c->argc == 1 || c->value_int ) {
+ mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND;
+ } else {
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ /* privileged connections pool max size ? */
+ if ( mi->mi_ntargets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" must appear before target definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN
+ || c->value_int > LDAP_BACK_CONN_PRIV_MAX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid max size " "of privileged "
+ "connections pool \"%s\" "
+ "in \"conn-pool-max <n> "
+ "(must be between %d and %d)\"",
+ c->argv[ 1 ],
+ LDAP_BACK_CONN_PRIV_MIN,
+ LDAP_BACK_CONN_PRIV_MAX );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_conn_priv_max = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ i = verb_to_mask( c->argv[1], cancel_mode );
+ if ( BER_BVISNULL( &cancel_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ mc->mc_flags |= cancel_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) {
+ int j;
+ unsigned u;
+
+ if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; j < SLAP_OP_LAST; j++ ) {
+ mc->mc_timeout[ j ] = u;
+ }
+
+ continue;
+ }
+
+ if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ /* idassert-bind */
+ rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ /* idassert-authzFrom */
+ rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ /* quarantine */
+ if ( META_BACK_CMN_QUARANTINE( mc ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "quarantine already defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( mt ) {
+ mc->mc_quarantine.ri_interval = NULL;
+ mc->mc_quarantine.ri_num = NULL;
+ if ( !META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ }
+
+ if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mc->mc_flags |= LDAP_BACK_F_QUARANTINE;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ /* session tracking request */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_ST_REQUEST;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ }
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ rc = asyncmeta_suffixm_config( c, c->argc, c->argv, mt );
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES: {
+ int nretries = META_RETRY_UNDEFINED;
+
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ nretries = META_RETRY_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) {
+ nretries = META_RETRY_NEVER;
+
+ } else {
+ if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse nretries {never|forever|<retries>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+
+ mc->mc_nretries = nretries;
+ } break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unsupported protocol version \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_version = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ /* do not return search references */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOREFS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ /* do not propagate undefined search filters */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ }
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+
+ } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ ber_str2bv( c->argv[1], 0, 1, &bv );
+ slap_keepalive_parse( &bv, &mt->mt_tls.sb_keepalive, 0, 0, 0 );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = c->value_uint;
+ break;
+
+ /* anything else */
+ default:
+ return SLAP_CONF_UNKNOWN;
+ }
+
+ return rc;
+}
+
+int
+asyncmeta_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( LDAP_BACK_CFG_LAST );
+
+ bi->bi_cf_ocs = a_metaocs;
+
+ rc = config_register_schema( a_metacfg, a_metaocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/conn.c b/servers/slapd/back-asyncmeta/conn.c
new file mode 100644
index 0000000..19c100e
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/conn.c
@@ -0,0 +1,1222 @@
+/* conn.c - handles connections to remote targets */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * asyncmeta_conn_alloc
+ *
+ * Allocates a connection structure, making room for all the referenced targets
+ */
+static a_metaconn_t *
+asyncmeta_conn_alloc(
+ a_metainfo_t *mi)
+{
+ a_metaconn_t *mc;
+ int ntargets = mi->mi_ntargets;
+
+ assert( ntargets > 0 );
+
+ /* malloc all in one */
+ mc = ( a_metaconn_t * )ch_calloc( 1, sizeof( a_metaconn_t ) + ntargets * sizeof( a_metasingleconn_t ));
+ if ( mc == NULL ) {
+ return NULL;
+ }
+
+ mc->mc_info = mi;
+ ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+ mc->mc_authz_target = META_BOUND_NONE;
+ mc->mc_conns = (a_metasingleconn_t *)(mc+1);
+ return mc;
+}
+
+/*
+ * asyncmeta_init_one_conn
+ *
+ * Initializes one connection
+ */
+int
+asyncmeta_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = NULL;
+ int version;
+ a_dncookie dc;
+ int isauthz = ( candidate == mc->mc_authz_target );
+ int do_return = 0;
+#ifdef HAVE_TLS
+ int is_ldaps = 0;
+ int do_start_tls = 0;
+#endif /* HAVE_TLS */
+
+ /* if the server is quarantined, and
+ * - the current interval did not expire yet, or
+ * - no more retries should occur,
+ * don't return the connection */
+ if ( mt->mt_isquarantined ) {
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+ int dont_retry = 0;
+
+ if ( mt->mt_quarantine.ri_interval ) {
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+ dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO );
+ if ( dont_retry ) {
+ dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL
+ || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] );
+ if ( !dont_retry ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_init_one_conn[%d]: quarantine " "retry block #%d try #%d.\n",
+ op->o_log_prefix,
+ candidate, ri->ri_idx,
+ ri->ri_count );
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING;
+ }
+
+ }
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+ }
+
+ if ( dont_retry ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Target is quarantined";
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: Target is quarantined\n",
+ op->o_log_prefix );
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ send_ldap_result( op, rs );
+ }
+ return rs->sr_err;
+ }
+ }
+ msc = &mc->mc_conns[candidate];
+ /*
+ * Already init'ed
+ */
+ if ( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc ) )
+ {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_CREATING( msc )
+ || LDAP_BACK_CONN_BINDING( msc ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_INITED( msc ) ) {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else {
+ /*
+ * creating...
+ */
+ META_BACK_CONN_CREATING_SET( msc );
+ }
+
+ if ( do_return ) {
+ if ( rs->sr_err != LDAP_SUCCESS
+ && op->o_conn
+ && ( sendok & LDAP_BACK_SENDERR ) )
+ {
+ send_ldap_result( op, rs );
+ }
+
+ return rs->sr_err;
+ }
+
+ assert( msc->msc_ld == NULL );
+
+ /*
+ * Attempts to initialize the connection to the target ds
+ */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+
+ rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri );
+#ifdef HAVE_TLS
+ is_ldaps = ldap_is_ldaps_url( mt->mt_uri );
+#endif /* HAVE_TLS */
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: ldap_initialize failed err=%d\n",
+ op->o_log_prefix, rs->sr_err );
+ goto error_return;
+ }
+
+ ldap_set_option( msc->msc_ld, LDAP_OPT_KEEPCONN, LDAP_OPT_ON);
+
+ msc->msc_ldr = ldap_dup(msc->msc_ld);
+ if (!msc->msc_ldr) {
+ ldap_ld_free(msc->msc_ld, 0, NULL, NULL);
+ rs->sr_err = LDAP_NO_MEMORY;
+ goto error_return;
+ }
+
+ /*
+ * Set LDAP version. This will always succeed: If the client
+ * bound with a particular version, then so can we.
+ */
+ if ( mt->mt_version != 0 ) {
+ version = mt->mt_version;
+
+ } else if ( op->o_conn->c_protocol != 0 ) {
+ version = op->o_conn->c_protocol;
+
+ } else {
+ version = LDAP_VERSION3;
+ }
+ ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+ ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p );
+
+ /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS,
+ META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF );
+
+ slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive);
+
+ if ( mt->mt_tls.sb_tcp_user_timeout > 0 ) {
+ ldap_set_option( msc->msc_ld, LDAP_OPT_TCP_USER_TIMEOUT,
+ &mt->mt_tls.sb_tcp_user_timeout );
+ }
+
+#ifdef HAVE_TLS
+ {
+ slap_bindconf *sb = NULL;
+
+ if ( ispriv ) {
+ sb = &mt->mt_idassert.si_bc;
+ } else {
+ sb = &mt->mt_tls;
+ }
+
+ bindconf_tls_set( sb, msc->msc_ld );
+
+ if ( !is_ldaps ) {
+ if ( META_BACK_TGT_USE_TLS( mt )
+ || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) )
+ {
+ do_start_tls = 1;
+ }
+ }
+ }
+
+ /* start TLS ("tls [try-]{start|propagate}" statement) */
+ if ( do_start_tls ) {
+#ifdef SLAP_STARTTLS_ASYNCHRONOUS
+ /*
+ * use asynchronous StartTLS; in case, chase referral
+ * FIXME: OpenLDAP does not return referral on StartTLS yet
+ */
+ int msgid;
+
+ rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ LDAPMessage *res = NULL;
+ int rc, nretries = mt->mt_nretries;
+ struct timeval tv;
+
+ LDAP_BACK_TV_SET( &tv );
+
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case -1:
+ rs->sr_err = LDAP_OTHER;
+ break;
+
+ case 0:
+ if ( nretries != 0 ) {
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ LDAP_BACK_TV_SET( &tv );
+ goto retry;
+ }
+ rs->sr_err = LDAP_OTHER;
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+ break;
+ }
+
+ if ( rc == LDAP_RES_EXTENDED ) {
+ struct berval *data = NULL;
+
+ /* NOTE: right now, data is unused, so don't get it */
+ rs->sr_err = ldap_parse_extended_result( msc->msc_ld,
+ res, NULL, NULL /* &data */ , 0 );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int err;
+
+ /* FIXME: matched? referrals? response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ld,
+ res, &err, NULL, NULL, NULL, NULL, 1 );
+ res = NULL;
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+
+ rs->sr_err = err;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* FIXME: in case a referral
+ * is returned, should we try
+ * using it instead of the
+ * configured URI? */
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = ldap_install_tls( msc->msc_ld );
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ /* FIXME: LDAP_OPERATIONS_ERROR? */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Unwilling to chase referral "
+ "returned by Start TLS exop";
+ }
+
+ if ( data ) {
+ ber_bvfree( data );
+ }
+ }
+
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ }
+#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ /*
+ * use synchronous StartTLS
+ */
+ rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL );
+#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ if (rs->sr_err != LDAP_SUCCESS) {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: ldap_start_tls_s failed err=%d\n",
+ op->o_log_prefix, rs->sr_err );
+ }
+ /* if StartTLS is requested, only attempt it if the URL
+ * is not "ldaps://"; this may occur not only in case
+ * of misconfiguration, but also when used in the chain
+ * overlay, where the "uri" can be parsed out of a referral */
+ if ( rs->sr_err == LDAP_SERVER_DOWN
+ || ( rs->sr_err != LDAP_SUCCESS
+ && META_BACK_TGT_TLS_CRITICAL( mt ) ) )
+ {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_init_one_conn(TLS) "
+ "ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ /* need to trash a failed Start TLS */
+ asyncmeta_clear_one_msc( op, mc, candidate, 1, __FUNCTION__ );
+ goto error_return;
+ }
+ }
+#endif /* HAVE_TLS */
+ /*
+ * Set the network timeout if set
+ */
+ if ( mt->mt_network_timeout != 0 ) {
+ struct timeval network_timeout;
+ network_timeout.tv_sec = 0;
+ network_timeout.tv_usec = mt->mt_network_timeout*1000;
+
+ ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT,
+ (void *)&network_timeout );
+ }
+
+ /*
+ * If the connection DN is not null, an attempt to rewrite it is made
+ */
+
+ if ( ispriv ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN );
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd );
+ }
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+
+ } else {
+ ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv );
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ && isauthz )
+ {
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = NULL;
+ dc.to_from = MASSAGE_REQ;
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ asyncmeta_dn_massage( &dc, &op->o_conn->c_dn, &msc->msc_bound_ndn );
+
+ /* copy the DN if needed */
+ if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) {
+ ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn );
+ }
+ } else {
+ ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv );
+ }
+ }
+ assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+error_return:;
+
+ if (msc != NULL) {
+ META_BACK_CONN_CREATING_CLEAR( msc );
+ }
+ if ( rs->sr_err == LDAP_SUCCESS && msc != NULL) {
+ META_BACK_CONN_INITED_SET( msc );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ }
+ return rs->sr_err;
+}
+
+
+static int
+asyncmeta_get_candidate(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ long candidate;
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ candidate = asyncmeta_select_unique_candidate( mi, ndn );
+
+ /*
+ * if any is found, inits the connection
+ */
+ if ( candidate == META_TARGET_NONE ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "No suitable candidate target found";
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ return candidate;
+}
+
+
+/*
+ * asyncmeta_getconn
+ *
+ * Prepares the connection structure
+ *
+ * RATIONALE:
+ *
+ * - determine what DN is being requested:
+ *
+ * op requires candidate checks
+ *
+ * add unique parent of o_req_ndn
+ * bind unique^*[/all] o_req_ndn [no check]
+ * compare unique^+ o_req_ndn
+ * delete unique o_req_ndn
+ * modify unique o_req_ndn
+ * search any o_req_ndn
+ * modrdn unique[, unique] o_req_ndn[, orr_nnewSup]
+ *
+ * - for ops that require the candidate to be unique, in case of multiple
+ * occurrences an internal search with sizeLimit=1 is performed
+ * if a unique candidate can actually be determined. If none is found,
+ * the operation aborts; if multiple are found, the default target
+ * is used if defined and candidate; otherwise the operation aborts.
+ *
+ * *^note: actually, the bind operation is handled much like a search;
+ * i.e. the bind is broadcast to all candidate targets.
+ *
+ * +^note: actually, the compare operation is handled much like a search;
+ * i.e. the compare is broadcast to all candidate targets, while checking
+ * that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is
+ * returned.
+ */
+a_metaconn_t *
+asyncmeta_getconn(
+ Operation *op,
+ SlapReply *rs,
+ SlapReply *candidates,
+ int *candidate,
+ ldap_back_send_t sendok,
+ int alloc_new)
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metaconn_t *mc = NULL,
+ mc_curr = {{ 0 }};
+ int cached = META_TARGET_NONE,
+ i = META_TARGET_NONE,
+ err = LDAP_SUCCESS,
+ new_conn = 0,
+ ncandidates = 0;
+
+
+ meta_op_type op_type = META_OP_REQUIRE_SINGLE;
+ enum {
+ META_DNTYPE_ENTRY,
+ META_DNTYPE_PARENT,
+ META_DNTYPE_NEWPARENT
+ } dn_type = META_DNTYPE_ENTRY;
+ struct berval ndn = op->o_req_ndn,
+ pndn;
+
+ if (alloc_new > 0) {
+ mc = asyncmeta_conn_alloc(mi);
+ new_conn = 0;
+ } else {
+ mc = asyncmeta_get_next_mc(mi);
+ }
+
+ ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+ /* Internal searches are privileged and shared. So is root. */
+ if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) )
+ || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) )
+ || op->o_do_not_cache || be_isroot( op ) )
+ {
+ LDAP_BACK_CONN_ISPRIV_SET( &mc_curr );
+ LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op );
+
+ } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+
+ } else {
+ /* Explicit binds must not be shared */
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ || op->o_tag == LDAP_REQ_BIND
+ || SLAP_IS_AUTHZ_BACKEND( op ) )
+ {
+ //mc_curr.mc_conn = op->o_conn;
+
+ } else {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+ }
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ /* if we go to selection, the entry must not exist,
+ * and we must be able to resolve the parent */
+ dn_type = META_DNTYPE_PARENT;
+ dnParent( &ndn, &pndn );
+ break;
+
+ case LDAP_REQ_MODRDN:
+ /* if nnewSuperior is not NULL, it must resolve
+ * to the same candidate as the req_ndn */
+ if ( op->orr_nnewSup ) {
+ dn_type = META_DNTYPE_NEWPARENT;
+ }
+ break;
+
+ case LDAP_REQ_BIND:
+ /* if bound as rootdn, the backend must bind to all targets
+ * with the administrative identity
+ * (unless pseoudoroot-bind-defer is TRUE) */
+ if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) {
+ op_type = META_OP_REQUIRE_ALL;
+ }
+ break;
+
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODIFY:
+ /* just a unique candidate */
+ break;
+
+ case LDAP_REQ_SEARCH:
+ /* allow multiple candidates for the searchBase */
+ op_type = META_OP_ALLOW_MULTIPLE;
+ break;
+
+ default:
+ /* right now, just break (exop?) */
+ break;
+ }
+
+ /*
+ * require all connections ...
+ */
+ if ( op_type == META_OP_REQUIRE_ALL ) {
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ candidates[ i ].sr_err = asyncmeta_init_one_conn( op,
+ rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) {
+ LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] );
+ }
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ err = candidates[ i ].sr_err;
+ continue;
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( alloc_new > 0) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ goto done;
+ }
+
+ /*
+ * looks in cache, if any
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ cached = i = asyncmeta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn );
+ }
+
+ if ( op_type == META_OP_REQUIRE_SINGLE ) {
+ int j;
+
+ for ( j = 0; j < mi->mi_ntargets; j++ ) {
+ META_CANDIDATE_RESET( &candidates[ j ] );
+ }
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ if ( i == META_TARGET_NONE ) {
+ i = asyncmeta_get_candidate( op, rs, &ndn );
+
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) {
+ i = asyncmeta_get_candidate( op, rs, &pndn );
+ }
+
+ if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+ }
+
+ if ( dn_type == META_DNTYPE_NEWPARENT && asyncmeta_get_candidate( op, rs, op->orr_nnewSup ) != i )
+ {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Cross-target rename not supported";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "==>asyncmeta__getconn: got target=%d for ndn=\"%s\" from cache\n",
+ i, op->o_req_ndn.bv_val );
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ /*
+ * Clear all other candidates
+ */
+ ( void )asyncmeta_clear_unused_candidates( op, i , mc, candidates);
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd. In case of error, asyncmeta_init_one_conn
+ * sends the appropriate result.
+ */
+ err = asyncmeta_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn );
+ if ( err != LDAP_SUCCESS ) {
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ candidates[ i ].sr_err = LDAP_SUCCESS;
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ if ( candidate ) {
+ *candidate = i;
+ }
+
+ /*
+ * if no unique candidate ...
+ */
+ } else {
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ META_CANDIDATE_RESET( &candidates[ i ] );
+
+ if ( i == cached
+ || asyncmeta_is_candidate( mt, &op->o_req_ndn,
+ op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) )
+ {
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ int lerr = asyncmeta_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ candidates[ i ].sr_err = lerr;
+ if ( lerr == LDAP_SUCCESS ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d]\n",
+ op->o_log_prefix, i );
+
+ } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] %s\n",
+ op->o_log_prefix, i,
+ mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" );
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ /* leave the target candidate, but record the error for later use */
+ err = lerr;
+
+ if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] quarantined err=%d\n",
+ op->o_log_prefix, i, lerr );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: asyncmeta_getconn[%d] failed err=%d\n",
+ op->o_log_prefix, i, lerr );
+ }
+
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+
+ }
+ return NULL;
+ }
+
+ continue;
+ }
+
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+ }
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ if ( alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ return NULL;
+ }
+ }
+
+done:;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+
+ if ( new_conn ) {
+ if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ /*
+ * Err could be -1 in case a duplicate metaconn is inserted
+ */
+ switch ( err ) {
+ case 0:
+ break;
+ default:
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_getconn: candidates=%d conn=%s insert failed\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ asyncmeta_back_conn_free( mc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Proxy bind collision";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ return NULL;
+ }
+ }
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_getconn: candidates=%d conn=%s inserted\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ } else {
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_getconn: candidates=%d conn=%s fetched\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ return mc;
+}
+
+void
+asyncmeta_quarantine(
+ Operation *op,
+ a_metainfo_t *mi,
+ SlapReply *rs,
+ int candidate )
+{
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ time_t new_last = slap_get_time();
+
+ switch ( mt->mt_isquarantined ) {
+ case LDAP_BACK_FQ_NO:
+ if ( ri->ri_last == new_last ) {
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: enter.\n",
+ op->o_log_prefix, candidate );
+
+ ri->ri_idx = 0;
+ ri->ri_count = 0;
+ break;
+
+ case LDAP_BACK_FQ_RETRYING:
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: block #%d try #%d failed.\n",
+ op->o_log_prefix, candidate, ri->ri_idx,
+ ri->ri_count );
+
+ ++ri->ri_count;
+ if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER
+ && ri->ri_count == ri->ri_num[ ri->ri_idx ] )
+ {
+ ri->ri_count = 0;
+ ++ri->ri_idx;
+ }
+ break;
+
+ default:
+ goto done;
+ }
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_YES;
+ ri->ri_last = new_last;
+
+ } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: exit.\n",
+ op->o_log_prefix, candidate );
+
+ if ( mi->mi_quarantine_f ) {
+ (void)mi->mi_quarantine_f( mi, candidate,
+ mi->mi_quarantine_p );
+ }
+
+ ri->ri_count = 0;
+ ri->ri_idx = 0;
+ mt->mt_isquarantined = LDAP_BACK_FQ_NO;
+ mt->mt_timeout_ops = 0;
+ }
+
+done:;
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+}
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi )
+{
+ a_metaconn_t *mc = NULL;
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ if (mi->mi_next_conn >= mi->mi_num_conns-1) {
+ mi->mi_next_conn = 0;
+ } else {
+ mi->mi_next_conn++;
+ }
+
+ mc = &mi->mi_conns[mi->mi_next_conn];
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ return mc;
+}
+
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc)
+{
+ int i;
+ for (i = 0; i < mc->mc_info->mi_ntargets; i++) {
+ asyncmeta_start_one_listener(mc, candidates, bc, i);
+ }
+ return LDAP_SUCCESS;
+}
+
+int asyncmeta_start_one_listener(a_metaconn_t *mc,
+ SlapReply *candidates,
+ bm_context_t *bc,
+ int candidate)
+{
+ a_metasingleconn_t *msc;
+ ber_socket_t s;
+
+ msc = &mc->mc_conns[candidate];
+ if ( slapd_shutdown || !META_BACK_CONN_INITED( msc ) || msc->msc_ld == NULL
+ || META_BACK_CONN_INVALID(msc) || !META_IS_CANDIDATE( &candidates[ candidate ] )) {
+ return LDAP_SUCCESS;
+ }
+ bc->msgids[candidate] = candidates[candidate].sr_msgid;
+ if ( msc->conn == NULL) {
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ /* Todo a meaningful log pls */
+ return LDAP_OTHER;
+ }
+ msc->conn = connection_client_setup( s, asyncmeta_op_handle_result, mc );
+ }
+ connection_client_enable( msc->conn );
+ return LDAP_SUCCESS;
+}
+
+int
+asyncmeta_clear_one_msc(
+ Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller)
+{
+ a_metasingleconn_t *msc;
+ if (mc == NULL) {
+ return 0;
+ }
+ msc = &mc->mc_conns[candidate];
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] Resetting msc: %p, msc_ld: %p, "
+ "msc_bound_ndn: %s, msc->conn: %p, %s \n",
+ time_buf, msc, msc->msc_ld, msc->msc_bound_ndn.bv_val,
+ msc->conn, caller ? caller : "" );
+ }
+ msc->msc_mscflags = 0;
+ if (msc->conn) {
+ connection_client_stop( msc->conn );
+ msc->conn = NULL;
+ }
+
+ if ( msc->msc_ld != NULL ) {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_clear_one_msc ldap_unbind_ext[%d] ld=%p\n",
+ op ? op->o_log_prefix : "", candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+ msc->msc_ld = NULL;
+ ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+ msc->msc_ldr = NULL;
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ msc->msc_time = 0;
+ msc->msc_binding_time = 0;
+ msc->msc_result_time = 0;
+ return 0;
+}
+
+void asyncmeta_get_timestamp(char *buf)
+{
+ struct timespec tp;
+ struct tm *ttm;
+ clock_gettime(CLOCK_REALTIME, &tp);
+ ttm = gmtime(&tp.tv_sec);
+ sprintf(buf, "%d:%d:%d.%ld", ttm->tm_hour, ttm->tm_min, ttm->tm_sec, tp.tv_nsec/1000);
+}
+
+int
+asyncmeta_reset_msc(
+ Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller)
+{
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug(asyncmeta_debug, "[%x] Will attempt to reset [%s] msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x %s\n",
+ (unsigned int)slap_get_time(), time_buf, msc,
+ (unsigned int)msc->msc_binding_time, msc->msc_mscflags, caller );
+ }
+ if (msc->msc_active <= 1 && mc->mc_active < 1) {
+ bm_context_t *om;
+ asyncmeta_clear_one_msc(NULL, mc, candidate, 0, caller);
+ /* set whatever's in the queue to invalid, so the timeout loop cleans it up,
+ * but do not invalidate the current op*/
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om->candidates[candidate].sr_msgid >= 0 && (om->op != op)) {
+ om->bc_invalid = 1;
+ }
+ }
+ return LDAP_SUCCESS;
+ } else {
+ META_BACK_CONN_INVALID_SET(msc);
+ Debug( asyncmeta_debug, "[%x] Failed to reset msc %p, msc_active=%d, mc_active=%d, %s\n",
+ (unsigned int)slap_get_time(), msc, msc->msc_active, mc->mc_active, caller );
+ }
+ return LDAP_OTHER;
+}
+
+
+void asyncmeta_log_msc(a_metasingleconn_t *msc)
+{
+ ber_socket_t s = 0;
+ if (msc->msc_ld) {
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ }
+ Debug( asyncmeta_debug, "msc: %p, msc_ld: %p, msc_ld socket: %d, "
+ "msc_bound_ndn: %s, msc->conn: %p\n", msc, msc->msc_ld,
+ (int)s, msc->msc_bound_ndn.bv_val, msc->conn );
+}
+
+void asyncmeta_log_conns(a_metainfo_t *mi)
+{
+ a_metaconn_t *mc;
+ int i, j;
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ mc = &mi->mi_conns[i];
+ Debug(asyncmeta_debug, "mc: %p, mc->pending_ops: %d\n", mc, mc->pending_ops);
+ for (j = 0; j < mi->mi_ntargets; j++ ) {
+ asyncmeta_log_msc(&mc->mc_conns[j]);
+ }
+
+ }
+}
+
+int
+asyncmeta_db_has_pending_ops(a_metainfo_t *mi)
+{
+ int i;
+ if (mi->mi_ntargets == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ if (mi->mi_conns[i].pending_ops > 0) {
+ return mi->mi_conns[i].pending_ops;
+ }
+ }
+
+ return 0;
+}
+
+
+int
+asyncmeta_db_has_mscs(a_metainfo_t *mi)
+{
+ int i, j;
+ if (mi->mi_ntargets == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ for (j = 0; j < mi->mi_ntargets; j++) {
+ if (mi->mi_conns[i].mc_conns[j].msc_ld != NULL ||
+ mi->mi_conns[i].mc_conns[j].msc_ldr != NULL ) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/delete.c b/servers/slapd/back-asyncmeta/delete.c
new file mode 100644
index 0000000..b85d463
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/delete.c
@@ -0,0 +1,304 @@
+/* delete.c - delete request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL;
+ a_dncookie dc;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ber = ldap_build_delete_req( msc->msc_ld, mdn.bv_val, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_delete_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_DELETE,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send delete request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_delete_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_delete( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_TRACE, "==> asyncmeta_back_delete: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_delete[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_DELETE ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_delete_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/dncache.c b/servers/slapd/back-asyncmeta/dncache.c
new file mode 100644
index 0000000..a588290
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/dncache.c
@@ -0,0 +1,228 @@
+/* dncache.c - dn caching for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The dncache, at present, maps an entry to the target that holds it.
+ */
+
+typedef struct metadncacheentry_t {
+ struct berval dn;
+ int target;
+
+ time_t lastupdated;
+} metadncacheentry_t;
+
+/*
+ * asyncmeta_dncache_cmp
+ *
+ * compares two struct metadncacheentry; used by avl stuff
+ * FIXME: modify avl stuff to delete an entry based on cmp
+ * (e.g. when ttl expired?)
+ */
+int
+asyncmeta_dncache_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ber_bvcmp( &cc1->dn, &cc2->dn);
+}
+
+/*
+ * asyncmeta_dncache_dup
+ *
+ * returns -1 in case a duplicate struct metadncacheentry has been inserted;
+ * used by avl stuff
+ */
+int
+asyncmeta_dncache_dup(
+ void *c1,
+ void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0;
+}
+
+/*
+ * asyncmeta_dncache_get_target
+ *
+ * returns the target a dn belongs to, or -1 in case the dn is not
+ * in the cache
+ */
+int
+asyncmeta_dncache_get_target(
+ a_metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t tmp_entry,
+ *entry;
+ int target = META_TARGET_NONE;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+ if ( entry != NULL ) {
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl < 0 ) {
+ target = entry->target;
+
+ } else {
+ if ( entry->lastupdated+cache->ttl > slap_get_time() ) {
+ target = entry->target;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return target;
+}
+
+/*
+ * asyncmeta_dncache_update_entry
+ *
+ * updates target and lastupdated of a struct metadncacheentry if exists,
+ * otherwise it gets created; returns -1 in case of error
+ */
+int
+asyncmeta_dncache_update_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn,
+ int target )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+ time_t curr_time = 0L;
+ int err = 0;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl > 0 ) {
+ curr_time = slap_get_time();
+ }
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+ if ( entry != NULL ) {
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ } else {
+ entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 );
+ if ( entry == NULL ) {
+ err = -1;
+ goto error_return;
+ }
+
+ entry->dn.bv_len = ndn->bv_len;
+ entry->dn.bv_val = (char *)&entry[ 1 ];
+ AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len );
+ entry->dn.bv_val[ ndn->bv_len ] = '\0';
+
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ err = ldap_avl_insert( &cache->tree, ( caddr_t )entry,
+ asyncmeta_dncache_cmp, asyncmeta_dncache_dup );
+ }
+
+error_return:;
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return err;
+}
+
+int
+asyncmeta_dncache_delete_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ldap_avl_delete( &cache->tree, ( caddr_t )&tmp_entry,
+ asyncmeta_dncache_cmp );
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ if ( entry != NULL ) {
+ asyncmeta_dncache_free( ( void * )entry );
+ }
+
+ return 0;
+}
+
+/*
+ * meta_dncache_free
+ *
+ * frees an entry
+ *
+ */
+void
+asyncmeta_dncache_free(
+ void *e )
+{
+ free( e );
+}
diff --git a/servers/slapd/back-asyncmeta/init.c b/servers/slapd/back-asyncmeta/init.c
new file mode 100644
index 0000000..2b43958
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/init.c
@@ -0,0 +1,475 @@
+/* init.c - initialization of a back-asyncmeta database */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int asyncmeta_debug;
+
+int
+asyncmeta_back_open(
+ BackendInfo *bi )
+{
+ /* FIXME: need to remove the pagedResults, and likely more... */
+ bi->bi_controls = slap_known_controls;
+
+ return 0;
+}
+
+int
+asyncmeta_back_initialize(
+ BackendInfo *bi )
+{
+ int rc;
+ struct berval debugbv = BER_BVC("asyncmeta");
+
+ rc = slap_loglevel_get( &debugbv, &asyncmeta_debug );
+ if ( rc ) {
+ return rc;
+ }
+
+ bi->bi_flags =
+#if 0
+ /* this is not (yet) set essentially because back-meta does not
+ * directly support extended operations... */
+#ifdef LDAP_DYNAMIC_OBJECTS
+ /* this is set because all the support a proxy has to provide
+ * is the capability to forward the refresh exop, and to
+ * pass thru entries that contain the dynamicObject class
+ * and the entryTtl attribute */
+ SLAP_BFLAG_DYNAMIC |
+#endif /* LDAP_DYNAMIC_OBJECTS */
+#endif
+
+ /* back-meta recognizes RFC4525 increment;
+ * let the remote server complain, if needed (ITS#5912) */
+ SLAP_BFLAG_INCREMENT;
+
+ bi->bi_open = asyncmeta_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = asyncmeta_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = asyncmeta_back_db_open;
+ bi->bi_db_close = asyncmeta_back_db_close;
+ bi->bi_db_destroy = asyncmeta_back_db_destroy;
+
+ bi->bi_op_bind = asyncmeta_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = asyncmeta_back_search;
+ bi->bi_op_compare = asyncmeta_back_compare;
+ bi->bi_op_modify = asyncmeta_back_modify;
+ bi->bi_op_modrdn = asyncmeta_back_modrdn;
+ bi->bi_op_add = asyncmeta_back_add;
+ bi->bi_op_delete = asyncmeta_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0 /* asyncmeta_back_conn_destroy */;
+
+ return asyncmeta_back_init_cf( bi );
+}
+
+int
+asyncmeta_back_db_init(
+ Backend *be,
+ ConfigReply *cr)
+{
+ a_metainfo_t *mi;
+ int i;
+ BackendInfo *bi;
+
+ bi = backend_info( "ldap" );
+ if ( !bi || !bi->bi_extra ) {
+ Debug( LDAP_DEBUG_ANY,
+ "asyncmeta_back_db_init: needs back-ldap\n" );
+ return 1;
+ }
+
+ mi = ch_calloc( 1, sizeof( a_metainfo_t ) );
+ if ( mi == NULL ) {
+ return -1;
+ }
+
+ /* set default flags */
+ mi->mi_flags =
+ META_BACK_F_DEFER_ROOTDN_BIND
+ | META_BACK_F_PROXYAUTHZ_ALWAYS
+ | META_BACK_F_PROXYAUTHZ_ANON
+ | META_BACK_F_PROXYAUTHZ_NOANON;
+
+ /*
+ * At present the default is no default target;
+ * this may change
+ */
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ mi->mi_bind_timeout.tv_sec = 0;
+ mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT;
+
+ mi->mi_rebind_f = asyncmeta_back_default_rebind;
+ mi->mi_urllist_f = asyncmeta_back_default_urllist;
+
+ ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex );
+
+ /* safe default */
+ mi->mi_nretries = META_RETRY_DEFAULT;
+ mi->mi_version = LDAP_VERSION3;
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ mi->mi_timeout[ i ] = META_BACK_CFG_DEFAULT_OPS_TIMEOUT;
+ }
+
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ mi->mi_conn_priv[ i ].mic_num = 0;
+ LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv );
+ }
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT;
+
+ mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra;
+ ldap_pvt_thread_mutex_init( &mi->mi_mc_mutex);
+
+ be->be_private = mi;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return 0;
+}
+
+int
+asyncmeta_target_finish(
+ a_metainfo_t *mi,
+ a_metatarget_t *mt,
+ const char *log,
+ char *msg,
+ size_t msize
+)
+{
+ slap_bindconf sb = { BER_BVNULL };
+ int rc;
+
+ ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = mt->mt_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+ LDAP_FEATURE_ABSOLUTE_FILTERS );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_T_F;
+ }
+ }
+
+ if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+ LDAP_EXOP_CANCEL );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP;
+ }
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )
+ || mt->mt_idassert_authz != NULL )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: inconsistent idassert configuration " "(likely authz=\"*\" used with \"non-prescriptive\" flag) (target %s)\n",
+ log, mt->mt_uri );
+ return 1;
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON;
+ }
+
+ return 0;
+}
+
+int
+asyncmeta_back_db_open(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi = (a_metainfo_t *)be->be_private;
+ char msg[SLAP_TEXT_BUFLEN];
+ int i;
+
+ if ( mi->mi_ntargets == 0 ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "asyncmeta_back_db_open: no targets defined\n" );
+ }
+
+ mi->mi_num_conns = 0;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+ if ( asyncmeta_target_finish( mi, mt,
+ "asyncmeta_back_db_open", msg, sizeof( msg ))) {
+ return 1;
+ }
+ }
+
+ mi->mi_num_conns = (mi->mi_max_target_conns == 0) ? META_BACK_CFG_MAX_TARGET_CONNS : mi->mi_max_target_conns;
+ assert(mi->mi_num_conns > 0);
+ mi->mi_conns = ch_calloc( mi->mi_num_conns, sizeof( a_metaconn_t ));
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ a_metaconn_t *mc = &mi->mi_conns[i];
+ ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+ mc->mc_authz_target = META_BOUND_NONE;
+
+ if ( mi->mi_ntargets > 0 ) {
+ mc->mc_conns = ch_calloc( mi->mi_ntargets, sizeof( a_metasingleconn_t ));
+ } else {
+ mc->mc_conns = NULL;
+ }
+
+ mc->mc_info = mi;
+ LDAP_STAILQ_INIT( &mc->mc_om_list );
+ }
+
+ ber_dupbv ( &mi->mi_suffix, &be->be_suffix[0] );
+
+ if ( mi->mi_ntargets > 0 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 1,
+ asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", mi->mi_suffix.bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ return 0;
+}
+
+/*
+ * asyncmeta_back_conn_free()
+ *
+ * actually frees a connection; the reference count must be 0,
+ * and it must not (or no longer) be in the cache.
+ */
+void
+asyncmeta_back_conn_free(
+ void *v_mc )
+{
+ a_metaconn_t *mc = v_mc;
+
+ assert( mc != NULL );
+ ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+ free( mc );
+}
+
+static void
+asyncmeta_back_stop_miconns( a_metainfo_t *mi )
+{
+
+ /*Todo do any other mc cleanup here if necessary*/
+}
+
+static void
+asyncmeta_back_clear_miconns( a_metainfo_t *mi )
+{
+ int i, j;
+ a_metaconn_t *mc;
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ mc = &mi->mi_conns[i];
+ /* todo clear the message queue */
+ for (j = 0; j < mi->mi_ntargets; j ++) {
+ asyncmeta_clear_one_msc(NULL, mc, j, 1, __FUNCTION__);
+ }
+ free(mc->mc_conns);
+ ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+ }
+ free(mi->mi_conns);
+}
+
+static void
+asyncmeta_target_free(
+ a_metatarget_t *mt )
+{
+ if ( mt->mt_uri ) {
+ free( mt->mt_uri );
+ ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex );
+ }
+ if ( mt->mt_subtree ) {
+ asyncmeta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ }
+ if ( mt->mt_filter ) {
+ asyncmeta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ }
+ if ( !BER_BVISNULL( &mt->mt_psuffix ) ) {
+ free( mt->mt_psuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) {
+ free( mt->mt_nsuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_binddn ) ) {
+ free( mt->mt_binddn.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_bindpw ) ) {
+ free( mt->mt_bindpw.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) {
+ ch_free( mt->mt_idassert_authcID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ch_free( mt->mt_idassert_authcDN.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ ch_free( mt->mt_idassert_passwd.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) {
+ ch_free( mt->mt_idassert_authzID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) {
+ ch_free( mt->mt_idassert_sasl_mech.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) {
+ ch_free( mt->mt_idassert_sasl_realm.bv_val );
+ }
+ if ( mt->mt_idassert_authz != NULL ) {
+ ber_bvarray_free( mt->mt_idassert_authz );
+ }
+ if ( !BER_BVISNULL( &mt->mt_lsuffixm )) {
+ ch_free( mt->mt_lsuffixm.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_rsuffixm )) {
+ ch_free( mt->mt_rsuffixm.bv_val );
+ }
+ free( mt );
+}
+
+int
+asyncmeta_back_db_close(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi;
+
+ if ( be->be_private ) {
+ mi = ( a_metainfo_t * )be->be_private;
+ if ( mi->mi_task != NULL ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, mi->mi_task )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, mi->mi_task);
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ mi->mi_task = NULL;
+ }
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ asyncmeta_back_stop_miconns( mi );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ }
+ return 0;
+}
+
+int
+asyncmeta_back_db_destroy(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi;
+
+ if ( be->be_private ) {
+ int i;
+
+ mi = ( a_metainfo_t * )be->be_private;
+ /*
+ * Destroy the per-target stuff (assuming there's at
+ * least one ...)
+ */
+ if ( mi->mi_targets != NULL ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num )
+ {
+ mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ }
+
+ asyncmeta_target_free( mt );
+ }
+
+ free( mi->mi_targets );
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex );
+ if ( mi->mi_cache.tree ) {
+ ldap_avl_free( mi->mi_cache.tree, asyncmeta_dncache_free );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex );
+
+ if ( mi->mi_candidates != NULL ) {
+ ber_memfree_x( mi->mi_candidates, NULL );
+ }
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine );
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ asyncmeta_back_clear_miconns(mi);
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_mc_mutex );
+
+ free( be->be_private );
+ }
+ return 0;
+}
+
+#if SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( asyncmeta )
+
+#endif /* SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-asyncmeta/map.c b/servers/slapd/back-asyncmeta/map.c
new file mode 100644
index 0000000..66bb8b0
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/map.c
@@ -0,0 +1,224 @@
+/* map.c - ldap backend mapping routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+/* This is an altered version */
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources,
+ * credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users
+ * ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ *
+ *
+ *
+ * Copyright 2016, Symas Corporation
+ *
+ * This is based on the back-meta/map.c version by Pierangelo Masarati.
+ * The previously reported conditions apply to the modified code as well.
+ * Changes in the original code are highlighted where required.
+ * Credits for the original code go to the author, Howard Chu.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+void
+asyncmeta_referral_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals
+)
+{
+ int i, last;
+
+ assert( dc != NULL );
+ assert( a_vals != NULL );
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ )
+ ;
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) {
+ struct berval dn,
+ olddn = BER_BVNULL;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ asyncmeta_dn_massage( dc, &olddn, &dn );
+ /* leave attr untouched if massage did nothing */
+ if ( olddn.bv_val != dn.bv_val )
+ {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ dc->op->o_tmpfree( dn.bv_val, dc->memctx );
+ if ( newurl )
+ {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+
+ ber_memfree_x( a_vals[ i ].bv_val, dc->op->o_tmpmemctx );
+ ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], dc->memctx );
+ ber_memfree( newurl );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ }
+ ldap_free_urldesc( ludp );
+ }
+}
+
+void
+asyncmeta_dnattr_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals
+)
+{
+ struct berval bv;
+ int i;
+
+ assert( a_vals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ asyncmeta_dn_massage( dc, &a_vals[i], &bv );
+ if ( bv.bv_val != a_vals[i].bv_val ) {
+ ber_memfree_x( a_vals[i].bv_val, dc->memctx );
+ a_vals[i] = bv;
+ }
+ }
+}
+
+/*
+ * asyncmeta_dn_massage
+ *
+ * Aliases the suffix.
+ */
+void
+asyncmeta_dn_massage(
+ a_dncookie *dc,
+ struct berval *odn,
+ struct berval *res
+)
+{
+ struct berval pretty = {0,NULL}, *dn = odn;
+ struct berval *osuff, *nsuff;
+ int diff;
+
+ assert( res );
+
+ BER_BVZERO(res);
+ if ( dn == NULL )
+ return;
+
+ /* no suffix massage configured */
+ if ( !dc->target->mt_lsuffixm.bv_val ) {
+ *res = *dn;
+ return;
+ }
+
+ if ( dc->to_from == MASSAGE_REQ ) {
+ osuff = &dc->target->mt_lsuffixm;
+ nsuff = &dc->target->mt_rsuffixm;
+ } else {
+ osuff = &dc->target->mt_rsuffixm;
+ nsuff = &dc->target->mt_lsuffixm;
+ /* DN from remote server may be in arbitrary form.
+ * Pretty it so we can parse reliably.
+ */
+ dnPretty( NULL, dn, &pretty, dc->op->o_tmpmemctx );
+ if (pretty.bv_val) dn = &pretty;
+ }
+
+ diff = dn->bv_len - osuff->bv_len;
+ /* DN is shorter than suffix - ignore */
+ if ( diff < 0 ) {
+ignore:
+ *res = *odn;
+ if (pretty.bv_val)
+ dc->op->o_tmpfree( pretty.bv_val, dc->op->o_tmpmemctx );
+ return;
+ }
+
+ /* DN longer than our suffix and doesn't match */
+ if ( osuff->bv_len != 0 && diff > 0 && !DN_SEPARATOR(dn->bv_val[diff-1]) )
+ goto ignore;
+
+ /* suffix is same length as ours, but doesn't match */
+ if ( strcasecmp( osuff->bv_val, &dn->bv_val[diff] ))
+ goto ignore;
+
+ /* if remote suffix is empty, remove or add the dn separator*/
+ if ( nsuff->bv_len == 0 ) {
+ diff--;
+ } else if ( osuff->bv_len == 0 ) {
+ diff++;
+ }
+
+
+ res->bv_len = diff + nsuff->bv_len;
+ res->bv_val = dc->op->o_tmpalloc( res->bv_len + 1, dc->memctx );
+ strncpy( res->bv_val, dn->bv_val, diff );
+ if ( osuff->bv_len == 0 )
+ res->bv_val[diff-1] = ',';
+ strcpy( &res->bv_val[diff], nsuff->bv_val );
+
+ if (pretty.bv_val)
+ dc->op->o_tmpfree( pretty.bv_val, dc->op->o_tmpmemctx );
+}
diff --git a/servers/slapd/back-asyncmeta/message_queue.c b/servers/slapd/back-asyncmeta/message_queue.c
new file mode 100644
index 0000000..29087c5
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/message_queue.c
@@ -0,0 +1,236 @@
+/* message_queue.c - routines to maintain the per-connection lists
+ * of pending operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "lutil.h"
+
+
+typedef struct listptr {
+ void *reserved;
+ struct listptr *next;
+} listptr;
+
+typedef struct listhead {
+ struct listptr *list;
+ int cnt;
+} listhead;
+
+#ifndef LH_MAX
+#define LH_MAX 16
+#endif
+
+static void asyncmeta_memctx_put(void *threadctx, void *memctx)
+{
+ slap_sl_mem_setctx(threadctx, NULL);
+ slap_sl_mem_destroy((void *)1, memctx);
+}
+
+int asyncmeta_new_bm_context(Operation *op,
+ SlapReply *rs,
+ bm_context_t **new_bc,
+ int ntargets,
+ a_metainfo_t *mi)
+{
+ int i;
+ *new_bc = op->o_tmpcalloc( 1, sizeof( bm_context_t ), op->o_tmpmemctx );
+
+ (*new_bc)->op = op;
+ (*new_bc)->copy_op = *op;
+ (*new_bc)->candidates = op->o_tmpcalloc(ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+ (*new_bc)->msgids = op->o_tmpcalloc(ntargets, sizeof(int),op->o_tmpmemctx);
+ (*new_bc)->nretries = op->o_tmpcalloc(ntargets, sizeof(int),op->o_tmpmemctx);
+ (*new_bc)->c_peer_name = op->o_conn->c_peer_name;
+ (*new_bc)->is_root = be_isroot( op );
+
+ switch(op->o_tag) {
+ case LDAP_REQ_COMPARE:
+ {
+ AttributeAssertion *ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ *ava = *op->orc_ava;
+ op->orc_ava = ava;
+ }
+ break;
+ case LDAP_REQ_MODRDN:
+ if (op->orr_newSup != NULL) {
+ struct berval *bv = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ *bv = *op->orr_newSup;
+ op->orr_newSup = bv;
+ }
+
+ if (op->orr_nnewSup != NULL) {
+ struct berval *bv = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ *bv = *op->orr_nnewSup;
+ op->orr_nnewSup = bv;
+ }
+ break;
+ default:
+ break;
+ }
+ for (i = 0; i < ntargets; i++) {
+ (*new_bc)->msgids[i] = META_MSGID_UNDEFINED;
+ }
+ for (i = 0; i < ntargets; i++) {
+ (*new_bc)->nretries[i] = mi->mi_targets[i]->mt_nretries;
+ }
+ return LDAP_SUCCESS;
+}
+
+void asyncmeta_free_op(Operation *op)
+{
+ assert (op != NULL);
+ switch (op->o_tag) {
+ case LDAP_REQ_SEARCH:
+ break;
+ case LDAP_REQ_ADD:
+ if ( op->ora_modlist != NULL ) {
+ slap_mods_free(op->ora_modlist, 0 );
+ }
+
+ if ( op->ora_e != NULL ) {
+ entry_free( op->ora_e );
+ }
+
+ break;
+ case LDAP_REQ_MODIFY:
+ if ( op->orm_modlist != NULL ) {
+ slap_mods_free(op->orm_modlist, 1 );
+ }
+ break;
+ case LDAP_REQ_MODRDN:
+ if ( op->orr_modlist != NULL ) {
+ slap_mods_free(op->orr_modlist, 1 );
+ }
+ break;
+ case LDAP_REQ_COMPARE:
+ break;
+ case LDAP_REQ_DELETE:
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "==> asyncmeta_free_op : other message type" );
+ }
+
+ connection_op_finish( op, 1 );
+ slap_op_free( op, op->o_threadctx );
+}
+
+
+
+
+void asyncmeta_clear_bm_context(bm_context_t *bc)
+{
+
+ Operation *op = bc->op;
+ void *thrctx, *memctx;
+ int i;
+
+ if ( bc->bc_mc && bc->bc_mc->mc_info ) {
+ for (i = 0; i < bc->bc_mc->mc_info->mi_ntargets; i++) {
+ if (bc->candidates[ i ].sr_text != NULL) {
+ ch_free( (char *)bc->candidates[ i ].sr_text );
+ bc->candidates[ i ].sr_text = NULL;
+ }
+ }
+ }
+
+ if (op->o_conn->c_conn_idx == -1)
+ return;
+ memctx = op->o_tmpmemctx;
+ thrctx = op->o_threadctx;
+ while (op->o_bd == bc->copy_op.o_bd)
+ ldap_pvt_thread_yield();
+ asyncmeta_free_op(op);
+ asyncmeta_memctx_put(thrctx, memctx);
+}
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug( LDAP_DEBUG_TRACE, "add_message_queue: mc %p, pending_ops %d, max_pending %d\n",
+ mc, mc->pending_ops, max_pending_ops );
+
+ assert(bc->bc_mc == NULL);
+ if (mc->pending_ops >= max_pending_ops) {
+ return LDAP_BUSY;
+ }
+ bc->bc_mc = mc;
+
+ slap_sl_mem_setctx(bc->op->o_threadctx, NULL);
+ LDAP_STAILQ_INSERT_TAIL( &mc->mc_om_list, bc, bc_next);
+ mc->pending_ops++;
+ return LDAP_SUCCESS;
+}
+
+
+void
+asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om == bc) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, om, bm_context_t, bc_next);
+ mc->pending_ops--;
+ break;
+ }
+ }
+ assert(om == bc);
+ assert(bc->bc_mc == mc);
+}
+
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om->candidates[candidate].sr_msgid == msgid && !om->bc_invalid) {
+ break;
+ }
+ }
+ return om;
+}
+
+bm_context_t *
+asyncmeta_bc_in_queue(a_metaconn_t *mc, bm_context_t *bc)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om == bc) {
+ return bc;
+ }
+ }
+ return NULL;
+}
diff --git a/servers/slapd/back-asyncmeta/meta_result.c b/servers/slapd/back-asyncmeta/meta_result.c
new file mode 100644
index 0000000..0ce279a
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/meta_result.c
@@ -0,0 +1,1825 @@
+/* meta_result.c - target responses processing */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+static void
+asyncmeta_send_ldap_result(bm_context_t *bc, Operation *op, SlapReply *rs)
+{
+ if (bc->c_peer_name.bv_val == op->o_conn->c_peer_name.bv_val && !bc->op->o_abandon ) {
+ send_ldap_result(&bc->copy_op, rs);
+ bc->op->o_callback = bc->copy_op.o_callback;
+ bc->op->o_extra = bc->copy_op.o_extra;
+ bc->op->o_ctrls = bc->copy_op.o_ctrls;
+ }
+}
+
+static int
+asyncmeta_is_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int i;
+ SlapReply *candidates = bc->candidates;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+ if (candidates[ i ].sr_msgid != META_MSGID_IGNORE ||
+ candidates[ i ].sr_type != REP_RESULT) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+meta_search_candidate_t
+asyncmeta_dobind_result(
+ a_metaconn_t *mc,
+ int candidate,
+ SlapReply *bind_result,
+ LDAPMessage *res )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE;
+ int rc;
+
+ assert( msc->msc_ldr != NULL );
+
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%x] [%s] asyncmeta_dobind_result msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x\n ",
+ (unsigned int)slap_get_time(), time_buf, msc,
+ (unsigned int)msc->msc_binding_time, msc->msc_mscflags );
+ }
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ldr, res,
+ &(bind_result->sr_err),
+ (char **)&(bind_result->sr_matched),
+ (char **)&(bind_result->sr_text),
+ NULL, NULL, 0 );
+
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug,
+ "[%s] asyncmeta_dobind_result error=%d msc: %p\n",
+ time_buf,bind_result->sr_err, msc );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ bind_result->sr_err = rc;
+ }
+ rc = slap_map_api2result( bind_result );
+
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ if ( rc != LDAP_SUCCESS ) {
+ bind_result->sr_err = rc;
+ } else {
+ /* FIXME: check if bound as idassert authcDN! */
+ if ( BER_BVISNULL( &msc->msc_bound_ndn )
+ || BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( msc );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_result anonymous msc: %p\n",
+ time_buf, msc );
+ }
+
+ } else {
+ if ( META_BACK_TGT_SAVECRED( mt ) &&
+ !BER_BVISNULL( &msc->msc_cred ) &&
+ !BER_BVISEMPTY( &msc->msc_cred ) )
+ {
+ ldap_set_rebind_proc( msc->msc_ldr, mt->mt_rebind_f, msc );
+ }
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_result success msc: %p\n",
+ time_buf, msc );
+ }
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+ retcode = META_SEARCH_CANDIDATE;
+ }
+ return retcode;
+}
+
+static int
+asyncmeta_send_entry(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int target,
+ LDAPMessage *e )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ struct berval a, mapped = BER_BVNULL;
+ int check_sorted_attrs = 0;
+ Entry ent = {0};
+ BerElement ber = *ldap_get_message_ber( e );
+ Attribute *attr, **attrp;
+ struct berval bdn,
+ dn = BER_BVNULL;
+ const char *text;
+ a_dncookie dc;
+ ber_len_t len;
+ int rc;
+ void *mem_mark;
+
+ mem_mark = slap_sl_mark( op->o_tmpmemctx );
+ ber_set_option( &ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) {
+ return LDAP_OTHER;
+ }
+
+ if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ /*
+ * Rewrite the dn of the result, if needed
+ */
+ dc.op = op;
+ dc.target = mi->mi_targets[ target ];
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REP;
+ asyncmeta_dn_massage( &dc, &bdn, &dn );
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ *
+ * FIXME: should we log anything, or delegate to dnNormalize?
+ */
+ rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname,
+ op->o_tmpmemctx );
+ if ( dn.bv_val != bdn.bv_val ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ }
+ BER_BVZERO( &dn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ "invalid DN syntax\n",
+ op->o_log_prefix, ent.e_name.bv_val );
+ rc = LDAP_INVALID_DN_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * cache dn
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+ &ent.e_nname, target );
+ }
+
+ attrp = &ent.e_attrs;
+
+ while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) {
+ int last = 0;
+ slap_syntax_validate_func *validate;
+ slap_syntax_transform_func *pretty;
+
+ if ( ber_pvt_ber_remaining( &ber ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ "unable to parse attr \"%s\".\n",
+ op->o_log_prefix, ent.e_name.bv_val, a.bv_val );
+
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( ber_pvt_ber_remaining( &ber ) == 0 ) {
+ break;
+ }
+
+ attr = op->o_tmpcalloc( 1, sizeof(Attribute), op->o_tmpmemctx );
+ if ( slap_bv2ad( &a, &attr->a_desc, &text )
+ != LDAP_SUCCESS) {
+ if ( slap_bv2undef_ad( &a, &attr->a_desc, &text,
+ SLAP_AD_PROXIED ) != LDAP_SUCCESS )
+ {
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_send_entry(\"%s\"): " "slap_bv2undef_ad(%s): %s\n",
+ op->o_log_prefix, ent.e_name.bv_val,
+ mapped.bv_val, text );
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ continue;
+ }
+ }
+
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
+ check_sorted_attrs = 1;
+
+ /* no subschemaSubentry */
+ if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry
+ || attr->a_desc == slap_schema.si_ad_entryDN )
+ {
+
+ /*
+ * We eat target's subschemaSubentry because
+ * a search for this value is likely not
+ * to resolve to the appropriate backend;
+ * later, the local subschemaSubentry is
+ * added.
+ *
+ * We also eat entryDN because the frontend
+ * will reattach it without checking if already
+ * present...
+ */
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ continue;
+ }
+
+ if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR
+ || attr->a_vals == NULL )
+ {
+ attr->a_vals = (struct berval *)&slap_dummy_bv;
+
+ } else {
+ for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last )
+ ;
+ }
+ attr->a_numvals = last;
+
+ validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate;
+ pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( !validate && !pretty ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+
+ /*
+ * It is necessary to try to rewrite attributes with
+ * dn syntax because they might be used in ACLs as
+ * members of groups; since ACLs are applied to the
+ * rewritten stuff, no dn-based subecj clause could
+ * be used at the ldap backend side (see
+ * http://www.OpenLDAP.org/faq/data/cache/452.html)
+ * The problem can be overcome by moving the dn-based
+ * ACLs to the target directory server, and letting
+ * everything pass thru the ldap backend.
+ */
+ {
+ int i;
+
+ if ( attr->a_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ {
+ asyncmeta_dnattr_result_rewrite( &dc, attr->a_vals );
+
+ } else if ( attr->a_desc == slap_schema.si_ad_ref ) {
+ asyncmeta_referral_result_rewrite( &dc, attr->a_vals );
+
+ }
+
+ for ( i = 0; i < last; i++ ) {
+ struct berval pval;
+ int rc;
+
+ if ( pretty ) {
+ rc = ordered_value_pretty( attr->a_desc,
+ &attr->a_vals[i], &pval, op->o_tmpmemctx );
+
+ } else {
+ rc = ordered_value_validate( attr->a_desc,
+ &attr->a_vals[i], 0 );
+ }
+
+ if ( rc ) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ continue;
+ }
+
+ if ( pretty ) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ attr->a_vals[i] = pval;
+ }
+ }
+
+ if ( last == 0 && attr->a_vals != &slap_dummy_bv ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+ }
+
+ if ( last && attr->a_desc->ad_type->sat_equality &&
+ attr->a_desc->ad_type->sat_equality->smr_normalize )
+ {
+ int i;
+
+ attr->a_nvals = op->o_tmpalloc( ( last + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ for ( i = 0; i<last; i++ ) {
+ /* if normalizer fails, drop this value */
+ if ( ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ attr->a_desc,
+ attr->a_desc->ad_type->sat_equality,
+ &attr->a_vals[i], &attr->a_nvals[i],
+ op->o_tmpmemctx )) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ }
+ }
+ BER_BVZERO( &attr->a_nvals[i] );
+ if ( last == 0 ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( attr->a_nvals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ attr->a_numvals = last;
+ *attrp = attr;
+ attrp = &attr->a_next;
+next_attr:;
+ }
+
+ /* Check for sorted attributes */
+ if ( check_sorted_attrs ) {
+ for ( attr = ent.e_attrs; attr; attr = attr->a_next ) {
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ while ( attr->a_numvals > 1 ) {
+ int i;
+ int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx );
+ if ( rc != LDAP_TYPE_OR_VALUE_EXISTS )
+ break;
+
+ /* Strip duplicate values */
+ if ( attr->a_nvals != attr->a_vals )
+ ber_memfree_x( attr->a_nvals[i].bv_val, op->o_tmpmemctx );
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ attr->a_numvals--;
+ if ( (unsigned)i < attr->a_numvals ) {
+ attr->a_vals[i] = attr->a_vals[attr->a_numvals];
+ if ( attr->a_nvals != attr->a_vals )
+ attr->a_nvals[i] = attr->a_nvals[attr->a_numvals];
+ }
+ BER_BVZERO(&attr->a_vals[attr->a_numvals]);
+ if ( attr->a_nvals != attr->a_vals )
+ BER_BVZERO(&attr->a_nvals[attr->a_numvals]);
+ }
+ attr->a_flags |= SLAP_ATTR_SORTED_VALS;
+ }
+ }
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ ".\n",
+ op->o_log_prefix, ent.e_name.bv_val );
+ ldap_get_entry_controls( mc->mc_conns[target].msc_ldr,
+ e, &rs->sr_ctrls );
+ rs->sr_entry = &ent;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags;
+ rs->sr_err = LDAP_SUCCESS;
+ rc = send_search_entry( op, rs );
+ switch ( rc ) {
+ case LDAP_UNAVAILABLE:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+done:;
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+#if 0
+ while ( ent.e_attrs ) {
+ attr = ent.e_attrs;
+ ent.e_attrs = attr->a_next;
+ if ( attr->a_nvals != attr->a_vals )
+ ber_bvarray_free_x( attr->a_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ }
+ if (ent.e_name.bv_val != NULL) {
+ op->o_tmpfree( ent.e_name.bv_val, op->o_tmpmemctx );
+ }
+
+ if (ent.e_nname.bv_val != NULL) {
+ op->o_tmpfree( ent.e_nname.bv_val, op->o_tmpmemctx );
+ }
+ if (rs->sr_entry && rs->sr_entry != &ent) {
+ entry_free( rs->sr_entry );
+ }
+#endif
+ slap_sl_release( mem_mark, op->o_tmpmemctx );
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ return rc;
+}
+
+static void
+asyncmeta_search_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate, int sres)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ Operation *op = bc->op;
+ SlapReply *rs = &bc->rs;
+ int i;
+ SlapReply *candidates = bc->candidates;
+ char *matched = NULL;
+
+ if ( bc->candidate_match > 0 ) {
+ struct berval pmatched = BER_BVNULL;
+
+ /* we use the first one */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_matched != NULL )
+ {
+ struct berval bv, pbv;
+ int rc;
+
+ /* if we got success, and this target
+ * returned noSuchObject, and its suffix
+ * is a superior of the searchBase,
+ * ignore the matchedDN */
+ if ( sres == LDAP_SUCCESS
+ && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT
+ && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len )
+ {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ continue;
+ }
+
+ ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv );
+ rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx );
+
+ if ( rc == LDAP_SUCCESS ) {
+
+ /* NOTE: if they all are superiors
+ * of the baseDN, the shorter is also
+ * superior of the longer... */
+ if ( pbv.bv_len > pmatched.bv_len ) {
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx );
+ }
+ pmatched = pbv;
+
+ } else {
+ op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( candidates[ i ].sr_matched != NULL ) {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ matched = pmatched.bv_val;
+ }
+
+ } else if ( sres == LDAP_NO_SUCH_OBJECT ) {
+ matched = mi->mi_suffix.bv_val;
+ }
+
+ /*
+ * In case we returned at least one entry, we return LDAP_SUCCESS
+ * otherwise, the latter error code we got
+ */
+
+ if ( sres == LDAP_SUCCESS ) {
+ if ( rs->sr_v2ref ) {
+ sres = LDAP_REFERRAL;
+ }
+
+ if ( META_BACK_ONERR_REPORT( mi ) ) {
+ /*
+ * Report errors, if any
+ *
+ * FIXME: we should handle error codes and return the more
+ * important/reasonable
+ */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( candidates[ i ].sr_err != LDAP_SUCCESS
+ && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT )
+ {
+ sres = candidates[ i ].sr_err;
+ break;
+ }
+ }
+ }
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_search_last_result(\"%d\"): "
+ ".\n",
+ op->o_log_prefix, candidate );
+ rs->sr_err = sres;
+ rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched );
+ rs->sr_text = ( sres == LDAP_SUCCESS ? NULL : candidates[candidate].sr_text );
+ rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL );
+ asyncmeta_send_ldap_result(bc, op, rs);
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+}
+
+static meta_search_candidate_t
+asyncmeta_send_pending_op(bm_context_t *bc, int candidate)
+{
+ meta_search_candidate_t retcode;
+ switch (bc->op->o_tag) {
+ case LDAP_REQ_SEARCH:
+ retcode = asyncmeta_back_search_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, NULL, 0 , 0);
+ break;
+ case LDAP_REQ_ADD:
+ retcode = asyncmeta_back_add_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_MODIFY:
+ retcode = asyncmeta_back_modify_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_MODRDN:
+ retcode = asyncmeta_back_modrdn_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_COMPARE:
+ retcode = asyncmeta_back_compare_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_DELETE:
+ retcode = asyncmeta_back_delete_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ default:
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+ return retcode;
+}
+
+
+meta_search_candidate_t
+asyncmeta_send_all_pending_ops(a_metaconn_t *mc, int candidate, void *ctx, int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ bm_context_t *bc, *onext;
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+
+ msc->msc_active++;
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ meta_search_candidate_t ret;
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->candidates[candidate].sr_msgid == META_MSGID_NEED_BIND)
+ bc->candidates[candidate].sr_msgid = META_MSGID_GOT_BIND;
+ if (bc->candidates[candidate].sr_msgid != META_MSGID_GOT_BIND || bc->bc_active > 0 || bc->op->o_abandon > 0) {
+ continue;
+ }
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ bc->bc_active++;
+ ret = asyncmeta_send_pending_op(bc, candidate);
+ if (ret != META_SEARCH_CANDIDATE) {
+ bc->candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ bc->candidates[ candidate ].sr_type = REP_RESULT;
+ bc->candidates[ candidate ].sr_err = bc->rs.sr_err;
+ if (bc->op->o_tag != LDAP_REQ_SEARCH || (META_BACK_ONERR_STOP( mi )) ||
+ (asyncmeta_is_last_result(mc, bc, candidate) == 0)) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ asyncmeta_send_ldap_result(bc, bc->op, &bc->rs);
+ asyncmeta_clear_bm_context(bc);
+ }
+ } else {
+ bc->bc_active--;
+ }
+ }
+ msc->msc_active--;
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return META_SEARCH_CANDIDATE;
+}
+
+meta_search_candidate_t
+asyncmeta_return_bind_errors(a_metaconn_t *mc, int candidate, SlapReply *bind_result, void *ctx, int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ bm_context_t *bc, *onext;
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->candidates[candidate].sr_msgid != META_MSGID_NEED_BIND
+ || bc->bc_active > 0 || bc->op->o_abandon > 0) {
+ continue;
+ }
+ bc->candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ bc->candidates[ candidate ].sr_type = REP_RESULT;
+ bc->candidates[ candidate ].sr_err = bind_result->sr_err;
+ if (bc->op->o_tag != LDAP_REQ_SEARCH || (META_BACK_ONERR_STOP( mi )) ||
+ (asyncmeta_is_last_result(mc, bc, candidate) == 0)) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ bc->rs.sr_err = bind_result->sr_err;
+ bc->rs.sr_text = bind_result->sr_text;
+ mc->pending_ops--;
+ asyncmeta_send_ldap_result(bc, bc->op, &bc->rs);
+ asyncmeta_clear_bm_context(bc);
+ }
+ }
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return META_SEARCH_CANDIDATE;
+}
+
+static meta_search_candidate_t
+asyncmeta_handle_bind_result(LDAPMessage *msg, a_metaconn_t *mc, int candidate, void *ctx)
+{
+ meta_search_candidate_t retcode;
+ SlapReply bind_result = {0};
+ /* could modify the msc, safer to lock it */
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ retcode = asyncmeta_dobind_result( mc, candidate, &bind_result, msg );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if ( retcode == META_SEARCH_CANDIDATE ) {
+ /* send the remaining pending ops */
+ asyncmeta_send_all_pending_ops(mc, candidate, ctx, 1);
+ } else {
+ asyncmeta_return_bind_errors(mc, candidate, &bind_result, ctx, 1);
+ }
+ return retcode;
+}
+
+int
+asyncmeta_handle_search_msg(LDAPMessage *res, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi;
+ a_metatarget_t *mt;
+ a_metasingleconn_t *msc;
+ Operation *op = bc->op;
+ SlapReply *rs;
+ int i, rc = LDAP_SUCCESS, sres;
+ SlapReply *candidates;
+ char **references = NULL;
+ LDAPControl **ctrls = NULL;
+ a_dncookie dc;
+ LDAPMessage *msg;
+ ber_int_t id;
+
+ rs = &bc->rs;
+ mi = mc->mc_info;
+ mt = mi->mi_targets[ candidate ];
+ msc = &mc->mc_conns[ candidate ];
+ dc.op = op;
+ dc.target = mt;
+ dc.to_from = MASSAGE_REP;
+ id = ldap_msgid(res);
+
+
+ candidates = bc->candidates;
+ i = candidate;
+
+ while (res && !META_BACK_CONN_INVALID(msc)) {
+ for (msg = ldap_first_message(msc->msc_ldr, res); msg; msg = ldap_next_message(msc->msc_ldr, msg)) {
+ switch(ldap_msgtype(msg)) {
+ case LDAP_RES_SEARCH_ENTRY:
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p entry\n",
+ op->o_log_prefix, msc );
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ /* count entries returned by target */
+ candidates[ i ].sr_nentries++;
+ if (bc->c_peer_name.bv_val == op->o_conn->c_peer_name.bv_val && !op->o_abandon) {
+ rs->sr_err = asyncmeta_send_entry( &bc->copy_op, rs, mc, i, msg );
+ } else {
+ goto err_cleanup;
+ }
+ switch ( rs->sr_err ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ asyncmeta_send_ldap_result(bc, op, rs);
+ rs->sr_err = LDAP_SUCCESS;
+ goto err_cleanup;
+ case LDAP_UNAVAILABLE:
+ rs->sr_err = LDAP_OTHER;
+ break;
+ default:
+ break;
+ }
+ bc->is_ok++;
+ break;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+ if ( META_BACK_TGT_NOREFS( mt ) ) {
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ bc->is_ok++;
+ rc = ldap_parse_reference( msc->msc_ldr, msg,
+ &references, &rs->sr_ctrls, 0 );
+
+ if ( rc != LDAP_SUCCESS || references == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+
+ /* FIXME: merge all and return at the end */
+
+ {
+ int cnt;
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &rs->sr_ref[ cnt ] );
+ }
+
+ {
+ dc.memctx = op->o_tmpmemctx;
+ ( void )asyncmeta_referral_result_rewrite( &dc, rs->sr_ref );
+ }
+
+ if ( rs->sr_ref != NULL ) {
+ if (!BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) {
+ /* ignore return value by now */
+ ( void )send_search_reference( op, rs );
+ }
+
+ ber_bvarray_free_x( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+
+ /* cleanup */
+ if ( references ) {
+ ber_memvfree( (void **)references );
+ }
+
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ break;
+
+ case LDAP_RES_INTERMEDIATE:
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ bc->is_ok++;
+
+ /* FIXME: response controls
+ * are passed without checks */
+ rs->sr_err = ldap_parse_intermediate( msc->msc_ldr,
+ msg,
+ (char **)&rs->sr_rspoid,
+ &rs->sr_rspdata,
+ &rs->sr_ctrls,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_type = REP_RESULT;
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+
+ slap_send_ldap_intermediate( op, rs );
+
+ if ( rs->sr_rspoid != NULL ) {
+ ber_memfree( (char *)rs->sr_rspoid );
+ rs->sr_rspoid = NULL;
+ }
+
+ if ( rs->sr_rspdata != NULL ) {
+ ber_bvfree( rs->sr_rspdata );
+ rs->sr_rspdata = NULL;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p result\n",
+ op->o_log_prefix, msc );
+ candidates[ i ].sr_type = REP_RESULT;
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ /* NOTE: ignores response controls
+ * (and intermediate response controls
+ * as well, except for those with search
+ * references); this may not be correct,
+ * but if they're not ignored then
+ * back-meta would need to merge them
+ * consistently (think of pagedResults...)
+ */
+ /* FIXME: response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ldr,
+ msg,
+ &candidates[ i ].sr_err,
+ (char **)&candidates[ i ].sr_matched,
+ (char **)&candidates[ i ].sr_text,
+ &references,
+ &ctrls /* &candidates[ i ].sr_ctrls (unused) */ ,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_err = rs->sr_err;
+ sres = slap_map_api2result( &candidates[ i ] );
+ candidates[ i ].sr_type = REP_RESULT;
+ goto finish;
+ }
+
+ rs->sr_err = candidates[ i ].sr_err;
+
+ /* massage matchedDN if need be */
+ if ( candidates[ i ].sr_matched != NULL ) {
+ struct berval match, mmatch;
+
+ ber_str2bv( candidates[ i ].sr_matched,
+ 0, 0, &match );
+ candidates[ i ].sr_matched = NULL;
+
+ dc.memctx = NULL;
+ asyncmeta_dn_massage( &dc, &match, &mmatch );
+ if ( mmatch.bv_val == match.bv_val ) {
+ candidates[ i ].sr_matched
+ = ch_strdup( mmatch.bv_val );
+
+ } else {
+ candidates[ i ].sr_matched = mmatch.bv_val;
+ }
+
+ bc->candidate_match++;
+ ldap_memfree( match.bv_val );
+ }
+
+ /* add references to array */
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( references != NULL
+ && references[ 0 ] != NULL
+ && references[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asncmeta_search_result[%d]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ } else {
+ BerVarray sr_ref;
+ int cnt;
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &sr_ref[ cnt ] );
+
+ dc.memctx = op->o_tmpmemctx;
+ ( void )asyncmeta_referral_result_rewrite( &dc, sr_ref );
+
+ if ( rs->sr_v2ref == NULL ) {
+ rs->sr_v2ref = sr_ref;
+
+ } else {
+ for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) {
+ ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ ber_memfree_x( sr_ref, op->o_tmpmemctx );
+ }
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_search_result[%d]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ /* cleanup */
+ ber_memvfree( (void **)references );
+
+ sres = slap_map_api2result( rs );
+
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_search_result[%d] "
+ "match=\"%s\" err=%ld\n",
+ op->o_log_prefix, i,
+ candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+ (long) candidates[ i ].sr_err );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_search_result[%d] "
+ "match=\"%s\" err=%ld (%s)\n",
+ op->o_log_prefix, i,
+ candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+ (long) candidates[ i ].sr_err, ldap_err2string( candidates[ i ].sr_err ) );
+ }
+
+ switch ( sres ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* is_ok is touched any time a valid
+ * (even intermediate) result is
+ * returned; as a consequence, if
+ * a candidate returns noSuchObject
+ * it is ignored and the candidate
+ * is simply demoted. */
+ if ( bc->is_ok ) {
+ sres = LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_SUCCESS:
+ if ( ctrls != NULL && ctrls[0] != NULL ) {
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl *pr_c;
+
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL );
+ if ( pr_c != NULL ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_int_t prsize;
+ struct berval prcookie;
+
+ /* unsolicited, do not accept */
+ if ( mt->mt_ps == 0 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER );
+
+ tag = ber_scanf( ber, "{im}", &prsize, &prcookie );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ /* more pages? new search request */
+ if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) {
+ if ( mt->mt_ps > 0 ) {
+ /* ignore size if specified */
+ prsize = 0;
+
+ } else if ( prsize == 0 ) {
+ /* guess the page size from the entries returned so far */
+ prsize = candidates[ i ].sr_nentries;
+ }
+
+ candidates[ i ].sr_nentries = 0;
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_INTERMEDIATE;
+
+ assert( candidates[ i ].sr_matched == NULL );
+ assert( candidates[ i ].sr_text == NULL );
+ assert( candidates[ i ].sr_ref == NULL );
+
+ switch ( asyncmeta_back_search_start( &bc->copy_op, rs, mc, bc, i, &prcookie, prsize, 1 ) )
+ {
+ case META_SEARCH_CANDIDATE:
+ assert( candidates[ i ].sr_msgid >= 0 );
+ ldap_controls_free( ctrls );
+ // goto free_message;
+
+ case META_SEARCH_ERR:
+ case META_SEARCH_NEED_BIND:
+err_pr:;
+ candidates[ i ].sr_err = rs->sr_err;
+ candidates[ i ].sr_type = REP_RESULT;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_send_ldap_result(bc, op, rs);
+ ldap_controls_free( ctrls );
+ goto err_cleanup;
+ }
+ /* fallthru */
+
+ case META_SEARCH_NOT_CANDIDATE:
+ /* means that asyncmeta_back_search_start()
+ * failed but onerr == continue */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_RESULT;
+ break;
+
+ default:
+ /* impossible */
+ assert( 0 );
+ break;
+ }
+ break;
+ }
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ ldap_controls_free( ctrls );
+ }
+ /* fallthru */
+
+ case LDAP_REFERRAL:
+ bc->is_ok++;
+ break;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ /* if a target returned sizelimitExceeded
+ * and the entry count is equal to the
+ * proxy's limit, the target would have
+ * returned more, and the error must be
+ * propagated to the client; otherwise,
+ * the target enforced a limit lower
+ * than what requested by the proxy;
+ * ignore it */
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( rs->sr_nentries == op->ors_slimit
+ || META_BACK_ONERR_STOP( mi ) )
+ {
+ const char *save_text;
+got_err:
+ save_text = rs->sr_text;
+ rs->sr_text = candidates[ i ].sr_text;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ if (candidates[ i ].sr_text != NULL) {
+ ch_free( (char *)candidates[ i ].sr_text );
+ candidates[ i ].sr_text = NULL;
+ }
+ rs->sr_text = save_text;
+ ldap_controls_free( ctrls );
+ goto err_cleanup;
+ }
+ break;
+
+ default:
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ goto got_err;
+ }
+ break;
+ }
+ /* if this is the last result we will ever receive, send it back */
+ rc = rs->sr_err;
+ if (asyncmeta_is_last_result(mc, bc, i) == 0) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p last result\n",
+ op->o_log_prefix, msc );
+ asyncmeta_search_last_result(mc, bc, i, sres);
+err_cleanup:
+ rc = rs->sr_err;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ ldap_msgfree(res);
+ return rc;
+ }
+finish:
+ break;
+
+ default:
+ continue;
+ }
+ }
+ ldap_msgfree(res);
+ res = NULL;
+ if (candidates[ i ].sr_type != REP_RESULT) {
+ struct timeval tv = {0};
+ rc = ldap_result( msc->msc_ldr, id, LDAP_MSG_RECEIVED, &tv, &res );
+ if (res != NULL) {
+ msc->msc_result_time = slap_get_time();
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return rc;
+}
+
+/* handles the received result for add, modify, modrdn, compare and delete ops */
+
+int asyncmeta_handle_common_result(LDAPMessage *msg, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi;
+ a_metatarget_t *mt;
+ a_metasingleconn_t *msc;
+ const char *save_text = NULL,
+ *save_matched = NULL;
+ BerVarray save_ref = NULL;
+ LDAPControl **save_ctrls = NULL;
+ void *matched_ctx = NULL;
+
+ char *matched = NULL;
+ char *text = NULL;
+ char **refs = NULL;
+ LDAPControl **ctrls = NULL;
+ Operation *op;
+ SlapReply *rs;
+ int rc;
+
+ mi = mc->mc_info;
+ mt = mi->mi_targets[ candidate ];
+ msc = &mc->mc_conns[ candidate ];
+
+ op = bc->op;
+ rs = &bc->rs;
+ save_text = rs->sr_text,
+ save_matched = rs->sr_matched;
+ save_ref = rs->sr_ref;
+ save_ctrls = rs->sr_ctrls;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+ rs->sr_ctrls = NULL;
+
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+
+ rc = ldap_parse_result( msc->msc_ldr, msg, &rs->sr_err,
+ &matched, &text, &refs, &ctrls, 0 );
+
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_text = text;
+ } else {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( refs != NULL
+ && refs[ 0 ] != NULL
+ && refs[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_handle_common_result[%d]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ } else {
+ int i;
+
+ for ( i = 0; refs[ i ] != NULL; i++ )
+ /* count */ ;
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0; refs[ i ] != NULL; i++ ) {
+ ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ i ] );
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_handle_common_result[%d]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( ctrls != NULL ) {
+ rs->sr_ctrls = ctrls;
+ }
+
+ /* if the error in the reply structure is not
+ * LDAP_SUCCESS, try to map it from client
+ * to server error */
+ if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* internal ops ( op->o_conn == NULL )
+ * must not reply to client */
+ if ( op->o_conn && !op->o_do_not_cache && matched ) {
+
+ /* record the (massaged) matched
+ * DN into the reply structure */
+ rs->sr_matched = matched;
+ }
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ asyncmeta_quarantine( op, mi, rs, candidate );
+ }
+
+ if ( matched != NULL ) {
+ struct berval dn, pdn;
+
+ ber_str2bv( matched, 0, 0, &dn );
+ if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+ ldap_memfree( matched );
+ matched_ctx = op->o_tmpmemctx;
+ matched = pdn.bv_val;
+ }
+ rs->sr_matched = matched;
+ }
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE || rs->sr_err == LDAP_SERVER_DOWN ) {
+ if ( rs->sr_text == NULL ) {
+ rs->sr_text = "Target is unavailable";
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ if ( op->o_conn ) {
+ asyncmeta_send_ldap_result(bc, op, rs);
+ }
+
+ if ( matched ) {
+ op->o_tmpfree( (char *)rs->sr_matched, matched_ctx );
+ }
+ if ( text ) {
+ ldap_memfree( text );
+ }
+ if ( rs->sr_ref ) {
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+ if ( refs ) {
+ ber_memvfree( (void **)refs );
+ }
+ if ( ctrls ) {
+ assert( rs->sr_ctrls != NULL );
+ ldap_controls_free( ctrls );
+ }
+
+ rs->sr_text = save_text;
+ rs->sr_matched = save_matched;
+ rs->sr_ref = save_ref;
+ rs->sr_ctrls = save_ctrls;
+ rc = (LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+}
+
+/* This takes care to clean out the outbound queue in case we have a read error
+ * sending back responses to the client */
+int
+asyncmeta_op_read_error(a_metaconn_t *mc, int candidate, int error, void* ctx)
+{
+ bm_context_t *bc, *onext;
+ int cleanup;
+ Operation *op;
+ SlapReply *rs;
+ SlapReply *candidates;
+
+ /* no outstanding ops, nothing to do but log */
+ Debug( LDAP_DEBUG_TRACE,
+ "asyncmeta_op_read_error: ldr=%p, err=%d\n",
+ mc->mc_conns[candidate].msc_ldr, error );
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ /*someone may be trying to write */
+ if (mc->mc_conns[candidate].msc_active <= 1) {
+ asyncmeta_clear_one_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ } else {
+ META_BACK_CONN_INVALID_SET(&mc->mc_conns[candidate]);
+ }
+
+ if (mc->pending_ops <= 0) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return LDAP_SUCCESS;
+ }
+
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ cleanup = 0;
+ candidates = bc->candidates;
+ /* was this op affected? */
+ if ( !META_IS_CANDIDATE( &candidates[ candidate ] ) )
+ continue;
+
+ if (bc->op->o_abandon) {
+ bc->bc_invalid = 1;
+ continue;
+ }
+
+ if (bc->bc_active > 0) {
+ bc->bc_invalid = 1;
+ continue;
+ }
+
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+
+ op = bc->op;
+ rs = &bc->rs;
+ switch (op->o_tag) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_DELETE:
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Read error on connection to target";
+ asyncmeta_send_ldap_result( bc, op, rs );
+ cleanup = 1;
+ break;
+ case LDAP_REQ_SEARCH:
+ {
+ a_metainfo_t *mi = mc->mc_info;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Read error on connection to target";
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ candidate ].sr_type = REP_RESULT;
+ if ( (META_BACK_ONERR_STOP( mi ) ||
+ asyncmeta_is_last_result(mc, bc, candidate)) && op->o_conn) {
+ asyncmeta_send_ldap_result( bc, op, rs );
+ cleanup = 1;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (cleanup) {
+ int j;
+ a_metainfo_t *mi = mc->mc_info;
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (j != candidate && bc->candidates[j].sr_msgid >= 0
+ && mc->mc_conns[j].msc_ld != NULL) {
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ }
+ }
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ asyncmeta_clear_bm_context(bc);
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return LDAP_SUCCESS;
+}
+
+void *
+asyncmeta_op_handle_result(void *ctx, void *arg)
+{
+ a_metaconn_t *mc = arg;
+ int i, j, rc, ntargets;
+ struct timeval tv = {0};
+ LDAPMessage *msg;
+ a_metasingleconn_t *msc;
+ bm_context_t *bc;
+ void *oldctx;
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = ++mc->mc_active;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (rc > 1)
+ return NULL;
+
+ ntargets = mc->mc_info->mi_ntargets;
+ i = ntargets;
+ oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0); /* get existing memctx */
+
+again:
+ for (j=0; j<ntargets; j++) {
+ i++;
+ if (i >= ntargets) i = 0;
+ msc = &mc->mc_conns[i];
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ if (!mc->mc_conns[i].msc_ldr ||
+ META_BACK_CONN_CREATING( &mc->mc_conns[i] ) ||
+ META_BACK_CONN_INVALID(&mc->mc_conns[i])) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ continue;
+ }
+
+ msc->msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ rc = ldap_result( mc->mc_conns[i].msc_ldr, LDAP_RES_ANY, LDAP_MSG_RECEIVED, &tv, &msg );
+ if (rc < 1) {
+ if (rc < 0) {
+ ldap_get_option( mc->mc_conns[i].msc_ldr, LDAP_OPT_ERROR_NUMBER, &rc);
+ META_BACK_CONN_INVALID_SET(&mc->mc_conns[i]);
+ asyncmeta_op_read_error(mc, i, rc, ctx);
+ }
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ continue;
+ }
+ rc = ldap_msgtype( msg );
+ if (rc == LDAP_RES_BIND) {
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_op_handle_result received bind msgid=%d msc: %p\n",
+ time_buf, ldap_msgid(msg), msc );
+ }
+ asyncmeta_handle_bind_result(msg, mc, i, ctx);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ msc->msc_result_time = slap_get_time();
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (msg)
+ ldap_msgfree(msg);
+
+ continue;
+ }
+retry_bc:
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ bc = asyncmeta_find_message(ldap_msgid(msg), mc, i);
+/* The sender might not be yet done with the context. On error it might also remove it
+ * so it's best to try and find it again after a wait */
+ if (bc && bc->bc_active > 0) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ ldap_pvt_thread_yield();
+ goto retry_bc;
+ }
+ if (bc) {
+ bc->bc_active++;
+ }
+
+ msc->msc_result_time = slap_get_time();
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (!bc) {
+ Debug( asyncmeta_debug,
+ "asyncmeta_op_handle_result: Unable to find bc for msguid %d, msc: %p\n", ldap_msgid(msg), msc );
+ ldap_msgfree(msg);
+ continue;
+ }
+
+ /* set our memctx */
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ if (bc->op->o_abandon) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ if ( bc->op->o_tag == LDAP_REQ_SEARCH ) {
+ int j;
+ for (j=0; j<ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *tmp_msc = &mc->mc_conns[j];
+ tmp_msc->msc_active++;
+ asyncmeta_back_cancel( mc, bc->op,
+ bc->candidates[ j ].sr_msgid, j );
+ tmp_msc->msc_active--;
+ }
+ }
+ }
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (msg)
+ ldap_msgfree(msg);
+ continue;
+ }
+
+ switch (rc) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ case LDAP_RES_SEARCH_RESULT:
+ case LDAP_RES_INTERMEDIATE:
+ asyncmeta_handle_search_msg(msg, mc, bc, i);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ msg = NULL;
+ break;
+ case LDAP_RES_ADD:
+ case LDAP_RES_DELETE:
+ case LDAP_RES_MODDN:
+ case LDAP_RES_COMPARE:
+ case LDAP_RES_MODIFY:
+ rc = asyncmeta_handle_common_result(msg, mc, bc, i);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ break;
+ default:
+ {
+ Debug( asyncmeta_debug,
+ "asyncmeta_op_handle_result: "
+ "unrecognized response message tag=%d\n",
+ rc );
+
+ }
+ }
+ if (msg)
+ ldap_msgfree(msg);
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = --mc->mc_active;
+ if (rc) {
+ i++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ goto again;
+ }
+ slap_sl_mem_setctx(ctx, oldctx);
+ if (mc->mc_conns) {
+ for (i=0; i<ntargets; i++) {
+ if (!slapd_shutdown && !META_BACK_CONN_INVALID(msc)
+ && mc->mc_conns[i].msc_ldr && mc->mc_conns[i].conn) {
+ connection_client_enable(mc->mc_conns[i].conn);
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return NULL;
+}
+
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc)
+{
+ msc->msc_time = slap_get_time();
+}
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg)
+{
+ struct re_s* rtask = arg;
+ a_metainfo_t *mi = rtask->arg;
+ bm_context_t *bc, *onext;
+ time_t current_time = slap_get_time();
+ int i, j;
+ LDAP_STAILQ_HEAD(BCList, bm_context_t) timeout_list;
+ LDAP_STAILQ_INIT( &timeout_list );
+
+ Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] start at [%ld] \n", rtask, current_time );
+ void *oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0);
+ for (i=0; i<mi->mi_num_conns; i++) {
+ a_metaconn_t * mc= &mi->mi_conns[i];
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->bc_active > 0) {
+ continue;
+ }
+
+ if (bc->op->o_abandon ) {
+ Operation *op = bc->op;
+
+ /* set our memctx */
+ op->o_threadctx = ctx;
+ op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, op->o_tmpmemctx);
+ operation_counter_init( op, ctx );
+
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ msc->msc_active++;
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ msc->msc_active--;
+ }
+ }
+ }
+ asyncmeta_clear_bm_context(bc);
+ continue;
+ }
+ if (bc->bc_invalid) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ LDAP_STAILQ_INSERT_TAIL( &timeout_list, bc, bc_next);
+ continue;
+ }
+
+ if (bc->timeout && bc->stoptime < current_time) {
+ Operation *op = bc->op;
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ LDAP_STAILQ_INSERT_TAIL( &timeout_list, bc, bc_next);
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ asyncmeta_set_msc_time(msc);
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ msc->msc_active++;
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ msc->msc_active--;
+ }
+ }
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ for (bc = LDAP_STAILQ_FIRST(&timeout_list); bc; bc = onext) {
+ Operation *op = bc->op;
+ SlapReply *rs = &bc->rs;
+ int timeout_err;
+ const char *timeout_text;
+
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ LDAP_STAILQ_REMOVE(&timeout_list, bc, bm_context_t, bc_next);
+ /* set our memctx */
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+
+ if (bc->searchtime) {
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ } else {
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ }
+
+ if ( bc->bc_invalid ) {
+ timeout_text = "Operation is invalid - target connection has been reset";
+ } else {
+ a_metasingleconn_t *log_msc = &mc->mc_conns[0];
+ Debug( asyncmeta_debug,
+ "asyncmeta_timeout_loop:Timeout op %s loop[%p], "
+ "current_time:%ld, op->o_time:%ld msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x \n",
+ bc->op->o_log_prefix, rtask, current_time, bc->op->o_time,
+ log_msc, (unsigned int)log_msc->msc_binding_time, log_msc->msc_mscflags );
+
+ if (bc->searchtime) {
+ timeout_text = NULL;
+ } else {
+ timeout_text = "Operation timed out";
+ }
+
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metatarget_t *mt = mi->mi_targets[j];
+ if (!META_BACK_TGT_QUARANTINE( mt ) ||
+ bc->candidates[j].sr_type == REP_RESULT) {
+ continue;
+ }
+
+ if (mt->mt_isquarantined > LDAP_BACK_FQ_NO) {
+ timeout_err = LDAP_UNAVAILABLE;
+ } else {
+ mt->mt_timeout_ops++;
+ if ((mi->mi_max_timeout_ops > 0) &&
+ (mt->mt_timeout_ops > mi->mi_max_timeout_ops)) {
+ timeout_err = LDAP_UNAVAILABLE;
+ rs->sr_err = timeout_err;
+ if (mt->mt_isquarantined == LDAP_BACK_FQ_NO)
+ asyncmeta_quarantine(op, mi, rs, j);
+ }
+ }
+ }
+ }
+ }
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ if (!bc->op->o_abandon ) {
+ asyncmeta_send_ldap_result( bc, bc->op, &bc->rs );
+ }
+ asyncmeta_clear_bm_context(bc);
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ if (mi->mi_idle_timeout) {
+ for (j=0; j<mi->mi_ntargets; j++) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ if ( msc->msc_active > 0 ) {
+ continue;
+ }
+ if (mc->pending_ops > 0) {
+ continue;
+ }
+ current_time = slap_get_time();
+ if (msc->msc_ld && msc->msc_time > 0 && msc->msc_time + mi->mi_idle_timeout < current_time) {
+ asyncmeta_clear_one_msc(NULL, mc, j, 1, __FUNCTION__);
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ }
+
+ slap_sl_mem_setctx(ctx, oldctx);
+ current_time = slap_get_time();
+ Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] stop at [%ld] \n", rtask, current_time );
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
diff --git a/servers/slapd/back-asyncmeta/modify.c b/servers/slapd/back-asyncmeta/modify.c
new file mode 100644
index 0000000..14105ae
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/modify.c
@@ -0,0 +1,360 @@
+/* modify.c - modify request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ int i, isupdate, rc = 0;
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ LDAPMod **modv = NULL;
+ LDAPMod *mods = NULL;
+ struct berval mdn;
+ Modifications *ml;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+
+ /*
+ * Rewrite the modify dn, if needed
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next )
+ ;
+
+ modv = op->o_tmpalloc( ( i + 1 )*sizeof( LDAPMod * ) + i*sizeof( LDAPMod ),
+ op->o_tmpmemctx );
+ if ( modv == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ retcode = META_SEARCH_ERR;
+ goto doreturn;
+ }
+ mods = (LDAPMod *)&modv[ i + 1 ];
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ int j;
+
+ if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ modv[ i ] = &mods[ i ];
+ mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES;
+ mods[ i ].mod_type = ml->sml_desc->ad_cname.bv_val;
+
+ if ( ml->sml_values != NULL ) {
+ j = ml->sml_numvals;
+ mods[ i ].mod_bvalues =(struct berval **)op->o_tmpalloc( ( j + 1 ) *sizeof( struct berval * ), op->o_tmpmemctx );
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+ mods[ i ].mod_bvalues[ j ] = op->o_tmpalloc(sizeof( struct berval ), op->o_tmpmemctx );
+ if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &ml->sml_values[ j ], mods[ i ].mod_bvalues[ j ] );
+ else
+ *mods[ i ].mod_bvalues[ j ] = ml->sml_values[ j ];
+ }
+ mods[ i ].mod_bvalues[ j ] = NULL;
+
+ } else {
+ mods[ i ].mod_bvalues = NULL;
+ }
+
+ i++;
+ }
+ modv[ i ] = 0;
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ /* someone reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug , "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_modify_req( msc->msc_ld, mdn.bv_val, modv, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_modify_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODIFY,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ ldap_pvt_thread_yield();
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send modify request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ op->o_tmpfree( modv, op->o_tmpmemctx );
+
+doreturn:;
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modify_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_modify( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modify: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_modify[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_MODIFY ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_modify_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/modrdn.c b/servers/slapd/back-asyncmeta/modrdn.c
new file mode 100644
index 0000000..6793686
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/modrdn.c
@@ -0,0 +1,375 @@
+/* modrdn.c - modrdn request handler for back-syncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL,
+ mnewSuperior = BER_BVNULL,
+ newrdn = BER_BVNULL;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ if ( op->orr_newSup ) {
+
+ /*
+ * NOTE: the newParent, if defined, must be on the
+ * same target as the entry to be renamed. This check
+ * has been anticipated in meta_back_getconn()
+ */
+ /*
+ * FIXME: one possibility is to delete the entry
+ * from one target and add it to the other;
+ * unfortunately we'd need write access to both,
+ * which is nearly impossible; for administration
+ * needs, the rootdn of the metadirectory could
+ * be mapped to an administrative account on each
+ * target (the binddn?); we'll see.
+ */
+ /*
+ * NOTE: we need to port the identity assertion
+ * feature from back-ldap
+ */
+
+ /* needs LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ /* op->o_protocol cannot be anything but LDAPv3,
+ * otherwise wouldn't be here */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ /*
+ * Rewrite the new superior, if defined and required
+ */
+ asyncmeta_dn_massage( &dc, op->orr_newSup, &mnewSuperior );
+ }
+
+ /*
+ * Rewrite the modrdn dn, if required
+ */
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* NOTE: we need to copy the newRDN in case it was formed
+ * from a DN by simply changing the length (ITS#5397) */
+ newrdn = op->orr_newrdn;
+ if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) {
+ ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+ }
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ber = ldap_build_moddn_req( msc->msc_ld, mdn.bv_val, newrdn.bv_val,
+ mnewSuperior.bv_val, op->orr_deleteoldrdn, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_modrdn_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODRDN,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__ );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send modrdn request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &mnewSuperior )
+ && mnewSuperior.bv_val != op->orr_newSup->bv_val )
+ {
+ op->o_tmpfree( mnewSuperior.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( newrdn.bv_val != op->orr_newrdn.bv_val ) {
+ op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modrdn_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_modrdn( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modrdn: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_modrdn[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_MODRDN ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_modrdn_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/proto-asyncmeta.h b/servers/slapd/back-asyncmeta/proto-asyncmeta.h
new file mode 100644
index 0000000..54041fa
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/proto-asyncmeta.h
@@ -0,0 +1,53 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef PROTO_ASYNCMETA_H
+#define PROTO_ASYNCMETA_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init asyncmeta_back_initialize;
+
+extern BI_open asyncmeta_back_open;
+extern BI_close asyncmeta_back_close;
+extern BI_destroy asyncmeta_back_destroy;
+
+extern BI_db_init asyncmeta_back_db_init;
+extern BI_db_open asyncmeta_back_db_open;
+extern BI_db_destroy asyncmeta_back_db_destroy;
+extern BI_db_close asyncmeta_back_db_close;
+extern BI_db_config asyncmeta_back_db_config;
+
+extern BI_op_bind asyncmeta_back_bind;
+extern BI_op_search asyncmeta_back_search;
+extern BI_op_compare asyncmeta_back_compare;
+extern BI_op_modify asyncmeta_back_modify;
+extern BI_op_modrdn asyncmeta_back_modrdn;
+extern BI_op_add asyncmeta_back_add;
+extern BI_op_delete asyncmeta_back_delete;
+
+extern BI_connection_destroy asyncmeta_back_conn_destroy;
+
+int asyncmeta_back_init_cf( BackendInfo *bi );
+
+LDAP_END_DECL
+
+#endif /* PROTO_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/search.c b/servers/slapd/back-asyncmeta/search.c
new file mode 100644
index 0000000..ab32c48
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/search.c
@@ -0,0 +1,970 @@
+/* search.c - search request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+static void
+asyncmeta_handle_onerr_stop(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int j;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ if (asyncmeta_bc_in_queue(mc,bc) == NULL || bc->bc_active > 1) {
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ return;
+ }
+ asyncmeta_drop_bc(mc, bc);
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (j != candidate && bc->candidates[j].sr_msgid >= 0
+ && mc->mc_conns[j].msc_ld != NULL && !META_BACK_CONN_CREATING( &mc->mc_conns[j] )) {
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ }
+ }
+ slap_sl_mem_setctx(op->o_threadctx, op->o_tmpmemctx);
+ operation_counter_init( op, op->o_threadctx );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+}
+
+static int
+asyncmeta_int_filter2bv( a_dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int i;
+ Filter *p;
+ struct berval atmp,
+ vtmp,
+ ntmp,
+ *tmp;
+ static struct berval
+ /* better than nothing... */
+ ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+ ber_bvtf_false = BER_BVC( "(|)" ),
+ /* better than nothing... */
+ ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+ ber_bvtf_true = BER_BVC( "(&)" ),
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" );
+ ber_len_t len;
+ void *memctx = dc->memctx;
+
+ assert( fstr != NULL );
+ BER_BVZERO( fstr );
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, memctx );
+ return LDAP_OTHER;
+ }
+
+ switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) {
+ case LDAP_FILTER_EQUALITY:
+ if ( f->f_av_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ asyncmeta_dn_massage( dc, &f->f_av_value, &vtmp );
+ } else {
+ vtmp = f->f_av_value;
+ }
+
+ filter_escape_value_x( &vtmp, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(>=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(<=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(~=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ fstr->bv_len = f->f_sub_desc->ad_cname.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ f->f_sub_desc->ad_cname.bv_val );
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 2], ntmp.bv_len + 3,
+ /* "(attr=" */ "%s*)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i], &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len + 1;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 1], ntmp.bv_len + 3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 1], ntmp.bv_len + 3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ fstr->bv_len = f->f_desc->ad_cname.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ f->f_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF( "(%)" );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 128, memctx ); /* FIXME: why 128? */
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ int rc;
+
+ len = fstr->bv_len;
+
+ rc = asyncmeta_int_filter2bv( dc, p, &vtmp );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+ /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT:
+ if ( f->f_mr_desc ) {
+ atmp = f->f_mr_desc->ad_cname;
+
+ } else {
+ BER_BVSTR( &atmp, "" );
+ }
+ filter_escape_value_x( &f->f_mr_value, &ntmp, memctx );
+
+ /* FIXME: cleanup (less ?: operators...) */
+ fstr->bv_len = atmp.bv_len +
+ ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+ ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+ ntmp.bv_len + ( STRLENOF( "(:=)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ atmp.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ /* FIXME: treat UNDEFINED as FALSE */
+ case SLAPD_COMPARE_UNDEFINED:
+ if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) {
+ return LDAP_COMPARE_FALSE;
+ }
+ /* fallthru */
+
+ case LDAP_COMPARE_FALSE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_false;
+ break;
+ }
+ tmp = &ber_bvfalse;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_true;
+ break;
+ }
+
+ tmp = &ber_bvtrue;
+ break;
+
+ default:
+ tmp = &ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, tmp, memctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, memctx );
+ break;
+ }
+
+ return 0;
+}
+meta_search_candidate_t
+asyncmeta_back_search_start(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ struct berval *prcookie,
+ ber_int_t prsize,
+ int do_lock)
+{
+ SlapReply *candidates = bc->candidates;
+ a_metainfo_t *mi = ( a_metainfo_t * )mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ a_dncookie dc;
+ struct berval realbase = op->o_req_dn;
+ char **attrs;
+ int realscope = op->ors_scope;
+ struct berval mbase = BER_BVNULL;
+ int rc;
+ struct berval filterbv = BER_BVNULL;
+ meta_search_candidate_t retcode;
+ int timelimit;
+ LDAPControl **ctrls = NULL;
+ BerElement *ber = NULL;
+ ber_int_t msgid;
+ ber_socket_t s = -1;
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl **save_ctrls = NULL;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ /* this should not happen; just in case... */
+ if ( msc->msc_ld == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: asyncmeta_back_search_start candidate=%d ld=NULL%s.\n",
+ op->o_log_prefix, candidate,
+ META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" );
+ candidates[ candidate ].sr_err = LDAP_OTHER;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ return META_SEARCH_ERR;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ return META_SEARCH_NOT_CANDIDATE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_back_search_start: dn=%s filter=%s\n",
+ op->o_log_prefix, op->o_req_dn.bv_val, op->ors_filterstr.bv_val );
+ /*
+ * modifies the base according to the scope, if required
+ */
+ if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) {
+ switch ( op->ors_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ /*
+ * make the target suffix the new base
+ * FIXME: this is very forgiving, because
+ * "illegal" searchBases may be turned
+ * into the suffix of the target; however,
+ * the requested searchBase already passed
+ * thru the candidate analyzer...
+ */
+ if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) {
+ realbase = mt->mt_nsuffix;
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ }
+
+ } else {
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_ONELEVEL:
+ {
+ struct berval rdn = mt->mt_nsuffix;
+ rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn )
+ && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) )
+ {
+ /*
+ * if there is exactly one level,
+ * make the target suffix the new
+ * base, and make scope "base"
+ */
+ realbase = mt->mt_nsuffix;
+ if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) {
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ } else {
+ realscope = LDAP_SCOPE_SUBTREE;
+ }
+ } else {
+ realscope = LDAP_SCOPE_BASE;
+ }
+ break;
+ } /* else continue with the next case */
+ }
+
+ case LDAP_SCOPE_BASE:
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /* check filter expression */
+ if ( mt->mt_filter ) {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) {
+ if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 )
+ break;
+ }
+ /* nothing matched, this target is no longer a candidate */
+ if ( !mf ) {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /*
+ * Rewrite the search base, if required
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+ asyncmeta_dn_massage( &dc, &realbase, &mbase );
+
+ attrs = anlist2charray_x( op->ors_attrs, 0, op->o_tmpmemctx );
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ timelimit = op->ors_tlimit > 0 ? op->ors_tlimit : 1;
+ } else {
+ timelimit = -1; /* no limit */
+ }
+
+#ifdef SLAPD_META_CLIENT_PR
+ save_ctrls = op->o_ctrls;
+ {
+ LDAPControl *pr_c = NULL;
+ int i = 0, nc = 0;
+
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[i] != NULL; i++ );
+ nc = i;
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL );
+ }
+
+ if ( pr_c != NULL ) nc--;
+ if ( mt->mt_ps > 0 || prcookie != NULL ) nc++;
+
+ if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) {
+ int src = 0, dst = 0;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval val = BER_BVNULL;
+ ber_len_t len;
+
+ len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl );
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ struct berval nullcookie = BER_BVNULL;
+ ber_tag_t tag;
+
+ if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps;
+ if ( prcookie == NULL ) prcookie = &nullcookie;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ tag = ber_printf( ber, "{iO}", prsize, prcookie );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ tag = ber_flatten2( ber, &val, 0 );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ len += val.bv_len + 1;
+ }
+
+ op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx );
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[ src ] != NULL; src++ ) {
+ if ( save_ctrls[ src ] != pr_c ) {
+ op->o_ctrls[ dst ] = save_ctrls[ src ];
+ dst++;
+ }
+ }
+ }
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ];
+
+ op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ op->o_ctrls[ dst ]->ldctl_iscritical = 1;
+
+ op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ];
+ AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 );
+ op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len;
+ dst++;
+
+ (void)ber_free_buf( ber );
+ }
+
+ op->o_ctrls[ dst ] = NULL;
+ }
+done_pr:;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls )
+ != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+
+ /*
+ * Starts the search
+ */
+ /* someone reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ rc = asyncmeta_int_filter2bv( &dc, op->ors_filter, &filterbv );
+ if ( rc ) {
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ ber = ldap_build_search_req( msc->msc_ld,
+ mbase.bv_val, realscope, filterbv.bv_val,
+ attrs, op->ors_attrsonly,
+ ctrls, NULL, timelimit, op->ors_slimit, op->ors_deref,
+ &msgid );
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_search_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_SEARCH,
+ mbase.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+
+ default:
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send search request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:;
+#if 0
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+#endif
+#ifdef SLAPD_META_CLIENT_PR
+ if ( save_ctrls != op->o_ctrls ) {
+ op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+ op->o_ctrls = save_ctrls;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ if ( mbase.bv_val != realbase.bv_val ) {
+ op->o_tmpfree( mbase.bv_val, op->o_tmpmemctx );
+ }
+
+doreturn:;
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_search_start[%p] (fd %d)=%d\n", op->o_log_prefix, msc, s, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_search( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ time_t timeout = 0;
+ int rc = 0;
+ int ncandidates = 0, initial_candidates = 0;
+ long i;
+ SlapReply *candidates = NULL;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ a_metaconn_t *mc;
+ int msc_decr = 0;
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+ int check_bind = 0;
+
+ rs_assert_ready( rs );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ /*
+ * controls are set in ldap_back_dobind()
+ *
+ * FIXME: in case of values return filter, we might want
+ * to map attrs and maybe rewrite value
+ */
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ /*
+ * Inits searches
+ */
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /* reset sr_msgid; it is used in most loops
+ * to check if that target is still to be considered */
+ candidates[i].sr_msgid = META_MSGID_UNDEFINED;
+ /* a target is marked as candidate by asyncmeta_getconn();
+ * if for any reason (an error, it's over or so) it is
+ * no longer active, sr_msgid is set to META_MSGID_IGNORE
+ * but it remains candidate, which means it has been active
+ * at some point during the operation. This allows to
+ * use its response code and more to compute the final
+ * response */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ candidates[ i ].sr_matched = NULL;
+ candidates[ i ].sr_text = NULL;
+ candidates[ i ].sr_ref = NULL;
+ candidates[ i ].sr_ctrls = NULL;
+ candidates[ i ].sr_nentries = 0;
+ candidates[ i ].sr_type = -1;
+
+ /* get largest timeout among candidates */
+ if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]
+ && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout )
+ {
+ timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ];
+ }
+ }
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT && (timeout == 0 || op->ors_tlimit < timeout)) {
+ bc->searchtime = 1;
+ bc->timeout = op->ors_tlimit;
+ } else {
+ bc->timeout = timeout;
+ }
+
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active++;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ goto finish;
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] )
+ || candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ continue;
+ }
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time() && META_BACK_ONERR_STOP( mi )) {
+ int timeout_err;
+ const char *timeout_text;
+ if (bc->searchtime) {
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ } else {
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ timeout_text = "Operation timed out before it was sent to target";
+ }
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+
+ }
+
+ if (op->o_abandon) {
+ rs->sr_err = SLAPD_ABANDON;
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, i);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the search request */
+ ncandidates++;
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: IS_CANDIDATE "
+ "cnd=\"%ld\"\n", op->o_log_prefix, i );
+
+ rc = asyncmeta_back_search_start( op, rs, mc, bc, i, NULL, 0 , 1);
+ if (rc == META_SEARCH_ERR) {
+ META_CANDIDATE_CLEAR(&candidates[i]);
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ else {
+ continue;
+ }
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: NOT_CANDIDATE "
+ "cnd=\"%ld\"\n", op->o_log_prefix, i );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ break;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: BINDING "
+ "cnd=\"%ld\" mc %p msc %p\n", op->o_log_prefix, i , mc, &mc->mc_conns[i]);
+ check_bind++;
+ ncandidates++;
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: SEARCH_ERR "
+ "cnd=\"%ldd\"\n", op->o_log_prefix, i );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_RESULT;
+
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ else {
+ continue;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ initial_candidates = ncandidates;
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char cnd[ SLAP_TEXT_BUFLEN ];
+ int c;
+
+ for ( c = 0; c < mi->mi_ntargets; c++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ c ] ) ) {
+ cnd[ c ] = '*';
+ } else {
+ cnd[ c ] = ' ';
+ }
+ }
+ cnd[ c ] = '\0';
+
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: ncandidates=%d "
+ "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd );
+ }
+
+ if ( initial_candidates == 0 ) {
+ /* NOTE: here we are not sending any matchedDN;
+ * this is intended, because if the back-meta
+ * is serving this search request, but no valid
+ * candidate could be looked up, it means that
+ * there is a hole in the mapping of the targets
+ * and thus no knowledge of any remote superior
+ * is available */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_back_search: "
+ "base=\"%s\" scope=%d: "
+ "no candidate could be selected\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ op->ors_scope );
+
+ /* FIXME: we're sending the first error we encounter;
+ * maybe we should pick the worst... */
+ rc = LDAP_NO_SUCH_OBJECT;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ rc = candidates[ i ].sr_err;
+ break;
+ }
+ }
+ rs->sr_err = rc;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_drop_bc(mc, bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+ goto finish;
+ }
+
+ /* If we were processing many targets the result from a pending Bind
+ * on an earlier target may have arrived while we were sending to a
+ * later target. See if we can now send our pending request.
+ */
+ if ( check_bind ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( candidates[ i ].sr_msgid == META_MSGID_GOT_BIND ) {
+ rc = asyncmeta_back_search_start( op, rs, mc, bc, i, NULL, 0, 1 );
+ if ( rc == META_SEARCH_ERR ) {
+ META_CANDIDATE_CLEAR( &candidates[i] );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ }
+ }
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active--;
+ }
+ msc_decr = 1;
+
+ asyncmeta_start_listeners(mc, candidates, bc);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+
+finish:
+ /* we ended up straight here due to error and need to reset the msc_active*/
+ if (msc_decr == 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active--;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-dnssrv/Makefile.in b/servers/slapd/back-dnssrv/Makefile.in
new file mode 100644
index 0000000..cf571e0
--- /dev/null
+++ b/servers/slapd/back-dnssrv/Makefile.in
@@ -0,0 +1,46 @@
+# Makefile.in for back-dnssrv
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## Portions Copyright 1998-2003 Kurt D. Zeilenga.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# ACKNOWLEDGEMENTS:
+# The DNSSRV backend was written by Kurt D. Zeilenga.
+#
+
+SRCS = init.c bind.c search.c config.c referral.c
+OBJS = init.lo bind.lo search.lo config.lo referral.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-dnssrv"
+BUILD_MOD = @BUILD_DNSSRV@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_DNSSRV@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_dnssrv
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-dnssrv/bind.c b/servers/slapd/back-dnssrv/bind.c
new file mode 100644
index 0000000..705c503
--- /dev/null
+++ b/servers/slapd/back-dnssrv/bind.c
@@ -0,0 +1,79 @@
+/* bind.c - DNS SRV backend bind function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "proto-dnssrv.h"
+
+int
+dnssrv_back_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: bind dn=\"%s\" (%d)\n",
+ BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val,
+ op->orb_method );
+
+ /* allow rootdn as a means to auth without the need to actually
+ * contact the proxied DSA */
+ switch ( be_rootdn_bind( op, NULL ) ) {
+ case LDAP_SUCCESS:
+ /* frontend will send result */
+ return rs->sr_err;
+
+ default:
+ /* treat failure and like any other bind, otherwise
+ * it could reveal the DN of the rootdn */
+ break;
+ }
+
+ if ( !BER_BVISNULL( &op->orb_cred ) &&
+ !BER_BVISEMPTY( &op->orb_cred ) )
+ {
+ /* simple bind */
+ Debug( LDAP_DEBUG_STATS,
+ "%s DNSSRV BIND dn=\"%s\" provided cleartext passwd\n",
+ op->o_log_prefix,
+ BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val );
+
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "you shouldn't send strangers your password" );
+
+ } else {
+ /* unauthenticated bind */
+ /* NOTE: we're not going to get here anyway:
+ * unauthenticated bind is dealt with by the frontend */
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: BIND dn=\"%s\"\n",
+ BER_BVISNULL( &op->o_req_dn ) ? "" : op->o_req_dn.bv_val );
+
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "anonymous bind expected" );
+ }
+
+ return 1;
+}
diff --git a/servers/slapd/back-dnssrv/compare.c b/servers/slapd/back-dnssrv/compare.c
new file mode 100644
index 0000000..28a0f6c
--- /dev/null
+++ b/servers/slapd/back-dnssrv/compare.c
@@ -0,0 +1,46 @@
+/* compare.c - DNS SRV backend compare function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "proto-dnssrv.h"
+
+int
+dnssrv_back_compare(
+ Operation *op,
+ SlapReply *rs
+)
+{
+#if 0
+ assert( get_manageDSAit( op ) );
+#endif
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "Operation not supported within naming context" );
+
+ /* not implemented */
+ return 1;
+}
diff --git a/servers/slapd/back-dnssrv/config.c b/servers/slapd/back-dnssrv/config.c
new file mode 100644
index 0000000..32e412e
--- /dev/null
+++ b/servers/slapd/back-dnssrv/config.c
@@ -0,0 +1,54 @@
+/* config.c - DNS SRV backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "proto-dnssrv.h"
+
+#if 0
+int
+dnssrv_back_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+#if 0
+ struct ldapinfo *li = (struct ldapinfo *) be->be_private;
+
+ if ( li == NULL ) {
+ fprintf( stderr, "%s: line %d: DNSSRV backend info is null!\n",
+ fname, lineno );
+ return( 1 );
+ }
+#endif
+
+ /* no configuration options (yet) */
+ return SLAP_CONF_UNKNOWN;
+}
+#endif
diff --git a/servers/slapd/back-dnssrv/init.c b/servers/slapd/back-dnssrv/init.c
new file mode 100644
index 0000000..a253be7
--- /dev/null
+++ b/servers/slapd/back-dnssrv/init.c
@@ -0,0 +1,115 @@
+/* init.c - initialize ldap backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/param.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "proto-dnssrv.h"
+
+int
+dnssrv_back_initialize(
+ BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_MANAGEDSAIT,
+ NULL
+ };
+
+ bi->bi_controls = controls;
+
+ bi->bi_open = dnssrv_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = 0;
+ bi->bi_db_destroy = 0;
+ bi->bi_db_config = 0 /* dnssrv_back_db_config */;
+ bi->bi_db_open = 0;
+ bi->bi_db_close = 0;
+
+ bi->bi_chk_referrals = dnssrv_back_referrals;
+
+ bi->bi_op_bind = dnssrv_back_bind;
+ bi->bi_op_search = dnssrv_back_search;
+ bi->bi_op_compare = 0 /* dnssrv_back_compare */;
+ bi->bi_op_modify = 0;
+ bi->bi_op_modrdn = 0;
+ bi->bi_op_add = 0;
+ bi->bi_op_delete = 0;
+ bi->bi_op_abandon = 0;
+ bi->bi_op_unbind = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ bi->bi_access_allowed = slap_access_always_allowed;
+
+ return 0;
+}
+
+AttributeDescription *ad_dc;
+AttributeDescription *ad_associatedDomain;
+
+int
+dnssrv_back_open(
+ BackendInfo *bi )
+{
+ const char *text;
+
+ (void)slap_str2ad( "dc", &ad_dc, &text );
+ (void)slap_str2ad( "associatedDomain", &ad_associatedDomain, &text );
+
+ return 0;
+}
+
+int
+dnssrv_back_db_init(
+ Backend *be,
+ ConfigReply *cr)
+{
+ return 0;
+}
+
+int
+dnssrv_back_db_destroy(
+ Backend *be,
+ ConfigReply *cr )
+{
+ return 0;
+}
+
+#if SLAPD_DNSSRV == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( dnssrv )
+
+#endif /* SLAPD_DNSSRV == SLAPD_MOD_DYNAMIC */
+
diff --git a/servers/slapd/back-dnssrv/proto-dnssrv.h b/servers/slapd/back-dnssrv/proto-dnssrv.h
new file mode 100644
index 0000000..23b01aa
--- /dev/null
+++ b/servers/slapd/back-dnssrv/proto-dnssrv.h
@@ -0,0 +1,46 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef PROTO_DNSSRV_H
+#define PROTO_DNSSRV_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init dnssrv_back_initialize;
+
+extern BI_open dnssrv_back_open;
+extern BI_close dnssrv_back_close;
+extern BI_destroy dnssrv_back_destroy;
+
+extern BI_db_init dnssrv_back_db_init;
+extern BI_db_destroy dnssrv_back_db_destroy;
+extern BI_db_config dnssrv_back_db_config;
+
+extern BI_op_bind dnssrv_back_bind;
+extern BI_op_search dnssrv_back_search;
+extern BI_op_compare dnssrv_back_compare;
+
+extern BI_chk_referrals dnssrv_back_referrals;
+
+extern AttributeDescription *ad_dc;
+extern AttributeDescription *ad_associatedDomain;
+
+LDAP_END_DECL
+
+#endif /* PROTO_DNSSRV_H */
diff --git a/servers/slapd/back-dnssrv/referral.c b/servers/slapd/back-dnssrv/referral.c
new file mode 100644
index 0000000..c3b801a
--- /dev/null
+++ b/servers/slapd/back-dnssrv/referral.c
@@ -0,0 +1,129 @@
+/* referral.c - DNS SRV backend referral handler */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "proto-dnssrv.h"
+
+int
+dnssrv_back_referrals(
+ Operation *op,
+ SlapReply *rs )
+{
+ int i;
+ int rc = LDAP_OTHER;
+ char *domain = NULL;
+ char *hostlist = NULL;
+ char **hosts = NULL;
+ BerVarray urls = NULL;
+
+ if ( BER_BVISEMPTY( &op->o_req_dn ) ) {
+ /* FIXME: need some means to determine whether the database
+ * is a glue instance */
+ if ( SLAP_GLUE_INSTANCE( op->o_bd ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ rs->sr_text = "DNS SRV operation upon null (empty) DN disallowed";
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ if( get_manageDSAit( op ) ) {
+ if( op->o_tag == LDAP_REQ_SEARCH ) {
+ return LDAP_SUCCESS;
+ }
+
+ rs->sr_text = "DNS SRV problem processing manageDSAit control";
+ return LDAP_OTHER;
+ }
+
+ if( ldap_dn2domain( op->o_req_dn.bv_val, &domain ) || domain == NULL ) {
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_ref = default_referral;
+ send_ldap_result( op, rs );
+ rs->sr_ref = NULL;
+ return LDAP_REFERRAL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> domain=\"%s\"\n",
+ op->o_req_dn.bv_val, domain );
+
+ i = ldap_domain2hostlist( domain, &hostlist );
+ if ( i ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "DNSSRV: domain2hostlist(%s) returned %d\n",
+ domain, i );
+ rs->sr_text = "no DNS SRV RR available for DN";
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+
+ hosts = ldap_str2charray( hostlist, " " );
+
+ if( hosts == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: str2charray error\n" );
+ rs->sr_text = "problem processing DNS SRV records for DN";
+ goto done;
+ }
+
+ for( i=0; hosts[i] != NULL; i++) {
+ struct berval url;
+
+ url.bv_len = STRLENOF( "ldap://" ) + strlen( hosts[i] );
+ url.bv_val = ch_malloc( url.bv_len + 1 );
+
+ strcpy( url.bv_val, "ldap://" );
+ strcpy( &url.bv_val[STRLENOF( "ldap://" )], hosts[i] );
+
+ if ( ber_bvarray_add( &urls, &url ) < 0 ) {
+ free( url.bv_val );
+ rs->sr_text = "problem processing DNS SRV records for DN";
+ goto done;
+ }
+ }
+
+ Debug( LDAP_DEBUG_STATS,
+ "%s DNSSRV p=%d dn=\"%s\" url=\"%s\"\n",
+ op->o_log_prefix, op->o_protocol,
+ op->o_req_dn.bv_val, urls[0].bv_val );
+
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> url=\"%s\"\n",
+ op->o_req_dn.bv_val, urls[0].bv_val );
+
+ rs->sr_ref = urls;
+ send_ldap_error( op, rs, LDAP_REFERRAL,
+ "DNS SRV generated referrals" );
+ rs->sr_ref = NULL;
+ rc = LDAP_REFERRAL;
+
+done:
+ if( domain != NULL ) ch_free( domain );
+ if( hostlist != NULL ) ch_free( hostlist );
+ if( hosts != NULL ) ldap_charray_free( hosts );
+ ber_bvarray_free( urls );
+ return rc;
+}
diff --git a/servers/slapd/back-dnssrv/search.c b/servers/slapd/back-dnssrv/search.c
new file mode 100644
index 0000000..4403248
--- /dev/null
+++ b/servers/slapd/back-dnssrv/search.c
@@ -0,0 +1,239 @@
+/* search.c - DNS SRV backend search function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Kurt D. Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "proto-dnssrv.h"
+
+int
+dnssrv_back_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ int i;
+ int rc;
+ char *domain = NULL;
+ char *hostlist = NULL;
+ char **hosts = NULL;
+ char *refdn;
+ struct berval nrefdn = BER_BVNULL;
+ BerVarray urls = NULL;
+ int manageDSAit;
+
+ rs->sr_ref = NULL;
+
+ if ( BER_BVISEMPTY( &op->o_req_ndn ) ) {
+ /* FIXME: need some means to determine whether the database
+ * is a glue instance; if we got here with empty DN, then
+ * we passed this same test in dnssrv_back_referrals() */
+ if ( !SLAP_GLUE_INSTANCE( op->o_bd ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "DNS SRV operation upon null (empty) DN disallowed";
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ goto done;
+ }
+
+ manageDSAit = get_manageDSAit( op );
+ /*
+ * FIXME: we may return a referral if manageDSAit is not set
+ */
+ if ( !manageDSAit ) {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "manageDSAit must be set" );
+ goto done;
+ }
+
+ if( ldap_dn2domain( op->o_req_dn.bv_val, &domain ) || domain == NULL ) {
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_ref = default_referral;
+ send_ldap_result( op, rs );
+ rs->sr_ref = NULL;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: dn=\"%s\" -> domain=\"%s\"\n",
+ op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", domain );
+
+ if( ( rc = ldap_domain2hostlist( domain, &hostlist ) ) ) {
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: domain2hostlist returned %d\n",
+ rc );
+ send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT,
+ "no DNS SRV RR available for DN" );
+ goto done;
+ }
+
+ hosts = ldap_str2charray( hostlist, " " );
+
+ if( hosts == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "DNSSRV: str2charray error\n" );
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "problem processing DNS SRV records for DN" );
+ goto done;
+ }
+
+ for( i=0; hosts[i] != NULL; i++) {
+ struct berval url;
+
+ url.bv_len = STRLENOF( "ldap://" ) + strlen(hosts[i]);
+ url.bv_val = ch_malloc( url.bv_len + 1 );
+
+ strcpy( url.bv_val, "ldap://" );
+ strcpy( &url.bv_val[STRLENOF( "ldap://" )], hosts[i] );
+
+ if( ber_bvarray_add( &urls, &url ) < 0 ) {
+ free( url.bv_val );
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "problem processing DNS SRV records for DN" );
+ goto done;
+ }
+ }
+
+ Debug( LDAP_DEBUG_STATS,
+ "%s DNSSRV p=%d dn=\"%s\" url=\"%s\"\n",
+ op->o_log_prefix, op->o_protocol,
+ op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "", urls[0].bv_val );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "DNSSRV: ManageDSAit scope=%d dn=\"%s\" -> url=\"%s\"\n",
+ op->oq_search.rs_scope,
+ op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "",
+ urls[0].bv_val );
+
+ rc = ldap_domain2dn(domain, &refdn);
+
+ if( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "DNS SRV problem processing manageDSAit control" );
+ goto done;
+
+ } else {
+ struct berval bv;
+ bv.bv_val = refdn;
+ bv.bv_len = strlen( refdn );
+
+ rc = dnNormalize( 0, NULL, NULL, &bv, &nrefdn, op->o_tmpmemctx );
+ if( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "DNS SRV problem processing manageDSAit control" );
+ goto done;
+ }
+ }
+
+ if( !dn_match( &nrefdn, &op->o_req_ndn ) ) {
+ /* requested dn is subordinate */
+
+ Debug( LDAP_DEBUG_TRACE,
+ "DNSSRV: dn=\"%s\" subordinate to refdn=\"%s\"\n",
+ op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "",
+ refdn == NULL ? "" : refdn );
+
+ rs->sr_matched = refdn;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+
+ } else if ( op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL ) {
+ send_ldap_error( op, rs, LDAP_SUCCESS, NULL );
+
+ } else {
+ Entry e = { 0 };
+ AttributeDescription *ad_objectClass
+ = slap_schema.si_ad_objectClass;
+ AttributeDescription *ad_ref = slap_schema.si_ad_ref;
+ e.e_name.bv_val = ch_strdup( op->o_req_dn.bv_val );
+ e.e_name.bv_len = op->o_req_dn.bv_len;
+ e.e_nname.bv_val = ch_strdup( op->o_req_ndn.bv_val );
+ e.e_nname.bv_len = op->o_req_ndn.bv_len;
+
+ e.e_attrs = NULL;
+ e.e_private = NULL;
+
+ attr_merge_one( &e, ad_objectClass, &slap_schema.si_oc_referral->soc_cname, NULL );
+ attr_merge_one( &e, ad_objectClass, &slap_schema.si_oc_extensibleObject->soc_cname, NULL );
+
+ if ( ad_dc ) {
+ char *p;
+ struct berval bv;
+
+ bv.bv_val = domain;
+
+ p = strchr( bv.bv_val, '.' );
+
+ if ( p == bv.bv_val ) {
+ bv.bv_len = 1;
+
+ } else if ( p != NULL ) {
+ bv.bv_len = p - bv.bv_val;
+
+ } else {
+ bv.bv_len = strlen( bv.bv_val );
+ }
+
+ attr_merge_normalize_one( &e, ad_dc, &bv, NULL );
+ }
+
+ if ( ad_associatedDomain ) {
+ struct berval bv;
+
+ ber_str2bv( domain, 0, 0, &bv );
+ attr_merge_normalize_one( &e, ad_associatedDomain, &bv, NULL );
+ }
+
+ attr_merge_normalize_one( &e, ad_ref, urls, NULL );
+
+ rc = test_filter( op, &e, op->oq_search.rs_filter );
+
+ if( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = &e;
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ send_search_entry( op, rs );
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ rs->sr_flags = 0;
+ }
+
+ entry_clean( &e );
+
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+ }
+
+ free( refdn );
+ if ( nrefdn.bv_val ) free( nrefdn.bv_val );
+
+done:
+ if( domain != NULL ) ch_free( domain );
+ if( hostlist != NULL ) ch_free( hostlist );
+ if( hosts != NULL ) ldap_charray_free( hosts );
+ if( urls != NULL ) ber_bvarray_free( urls );
+ return 0;
+}
diff --git a/servers/slapd/back-ldap/Makefile.in b/servers/slapd/back-ldap/Makefile.in
new file mode 100644
index 0000000..4b10d77
--- /dev/null
+++ b/servers/slapd/back-ldap/Makefile.in
@@ -0,0 +1,45 @@
+# Makefile.in for back-ldap
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \
+ delete.c modify.c modrdn.c extended.c chain.c \
+ distproc.c monitor.c pbind.c
+OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \
+ delete.lo modify.lo modrdn.lo extended.lo chain.lo \
+ distproc.lo monitor.lo pbind.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-ldap"
+BUILD_MOD = @BUILD_LDAP@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_LDAP@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_ldap
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-ldap/TODO.proxy b/servers/slapd/back-ldap/TODO.proxy
new file mode 100644
index 0000000..01406c5
--- /dev/null
+++ b/servers/slapd/back-ldap/TODO.proxy
@@ -0,0 +1,101 @@
+back-proxy
+
+A proxy that handles a pool of URI associated to a unique suffix.
+Each request is spread over the different URIs and results are
+masqueraded to appear as coming from a unique server.
+
+Suppose a company has two branches, whose existing DS have URIs
+
+"ldap://ldap.branch1.com/o=Branch 1, c=US"
+"ldap://ldap.branch2.it/o=Branch 2, c=IT"
+
+and it wants to propose to the outer world as a unique URI
+
+"ldap://ldap.company.net/dc=company, dc=net"
+
+It could do some rewriting to map everything that comes in with a base DN
+of "o=Branch 1, dc=company, dc=net" as the URI of the Branch 1, and
+everything that comes in with a base DN of "o=Branch 2, dc=company, dc=net"
+as the URI of Branch 2, and by rewriting all the DNs back to the new, uniform
+base. Everything that comes in with a base DN of "dc=company, dc=net" should
+be handled locally and propagated to the two branch URIs if a subtree
+(or at least onelevel) search is required.
+
+Operations:
+
+- bind
+- unbind
+- search
+- compare
+- add
+- modify
+- modrdn
+- delete
+- abandon
+
+The input of each operation may be related to:
+
+ exact DN exact parent ancestor
+-------------------------------------------------------------
+bind x
+unbind
+search x x x
+compare x
+add x
+modify x
+modrdn x
+delete x
+abandon
+
+The backend must rely on a DN fetching mechanism. Each operation requires
+to determine as early as possible which URI will be able to satisfy it.
+Apart from searches, which by definition are usually allowed to return
+multiple results, and apart from unbind and abandon, which do not return any
+result, all the remaining operations require the related entry to be unique.
+
+A major problem isposed by the uniqueness of the DNs. As far as the suffixes
+are masqueraded by a common suffix, the DNs are no longer guaranteed to be
+unique. This backend relies on the assumption that the uniqueness of the
+DNs is guaranteed.
+
+Two layers of depth in DN fetching are envisaged.
+The first layer is provided by a backend-side cache made of previously
+retrieved entries. The cache relates each RDN (i.e. the DN apart from the
+common suffix) to the pool of URIs that are expected to contain a subset
+of its children.
+
+The second layer is provided by a fetching function that spawns a search for
+each URI in the pool determined by the cache if the correct URI has not been
+directly determined.
+
+Note that, as the remote servers may have been updated by some direct
+operation, this mechanism does not guarantee the uniqueness of the result.
+So write operations will require to skip the cache search and to perform
+the exhaustive search of all the URIs unless some hint mechanism is provided
+to the backend (e.g. a server is read-only).
+
+Again, the lag between the fetching of the required DN and the actual
+read/write may result in a failure; however, this applies to any LDAP
+operation AFAIK.
+
+- bind
+if updates are to be strictly honored, a bind operation is performed against
+each URI; otherwise, it is performed against the URIs resulting from a
+cache-level DN fetch.
+
+- unbind
+nothing to say; all the open handles related to the connection are reset.
+
+- search
+if updates are to be strictly honored, a search operation is performed against
+each URI. Note that this needs be performed also when the backend suffix
+is used as base. In case the base is stricter, the URI pool may be restricted
+by performing a cache DN fetch of the base first.
+
+- compare
+the same applies to the compare DN.
+
+- add
+this operation is delicate. Unless the DN up to the top-level part excluded
+can be uniquely associated to a URI, and unless its uniqueness can be trusted,
+no add operation should be allowed.
diff --git a/servers/slapd/back-ldap/add.c b/servers/slapd/back-ldap/add.c
new file mode 100644
index 0000000..32ceda2
--- /dev/null
+++ b/servers/slapd/back-ldap/add.c
@@ -0,0 +1,139 @@
+/* add.c - ldap backend add function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_add(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ int i = 0,
+ j = 0;
+ Attribute *a;
+ LDAPMod **attrs = NULL,
+ *attrs2 = NULL;
+ ber_int_t msgid;
+ int isupdate;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS, "==> ldap_back_add(\"%s\")\n",
+ op->o_req_dn.bv_val );
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ lc = NULL;
+ goto cleanup;
+ }
+
+ /* Count number of attributes in entry */
+ for ( i = 1, a = op->oq_add.rs_e->e_attrs; a; i++, a = a->a_next )
+ /* just count attrs */ ;
+
+ /* Create array of LDAPMods for ldap_add() */
+ attrs = (LDAPMod **)ch_malloc( sizeof( LDAPMod * )*i
+ + sizeof( LDAPMod )*( i - 1 ) );
+ attrs2 = ( LDAPMod * )&attrs[ i ];
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, a = op->oq_add.rs_e->e_attrs; a; a = a->a_next ) {
+ if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ attrs[ i ] = &attrs2[ i ];
+ attrs[ i ]->mod_op = LDAP_MOD_BVALUES;
+ attrs[ i ]->mod_type = a->a_desc->ad_cname.bv_val;
+
+ for ( j = 0; a->a_vals[ j ].bv_val; j++ )
+ /* just count vals */ ;
+ attrs[i]->mod_vals.modv_bvals =
+ ch_malloc( ( j + 1 )*sizeof( struct berval * ) );
+ for ( j = 0; a->a_vals[ j ].bv_val; j++ ) {
+ attrs[ i ]->mod_vals.modv_bvals[ j ] = &a->a_vals[ j ];
+ }
+ attrs[ i ]->mod_vals.modv_bvals[ j ] = NULL;
+ i++;
+ }
+ attrs[ i ] = NULL;
+
+retry:
+ ctrls = op->o_ctrls;
+ rs->sr_err = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_add_ext( lc->lc_ld, op->o_req_dn.bv_val, attrs,
+ ctrls, NULL, &msgid );
+ rs->sr_err = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_ADD ],
+ ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_ADD ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+cleanup:
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( attrs ) {
+ for ( --i; i >= 0; --i ) {
+ ch_free( attrs[ i ]->mod_vals.modv_bvals );
+ }
+ ch_free( attrs );
+ }
+
+ if ( lc ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "<== ldap_back_add(\"%s\"): %d\n",
+ op->o_req_dn.bv_val, rs->sr_err );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-ldap/back-ldap.h b/servers/slapd/back-ldap/back-ldap.h
new file mode 100644
index 0000000..96bc6f3
--- /dev/null
+++ b/servers/slapd/back-ldap/back-ldap.h
@@ -0,0 +1,479 @@
+/* back-ldap.h - ldap backend header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef SLAPD_LDAP_H
+#define SLAPD_LDAP_H
+
+#include "../back-monitor/back-monitor.h"
+
+LDAP_BEGIN_DECL
+
+struct ldapinfo_t;
+
+/* stuff required for monitoring */
+typedef struct ldap_monitor_info_t {
+ monitor_subsys_t lmi_mss[2];
+
+ struct berval lmi_ndn;
+ struct berval lmi_conn_rdn;
+ struct berval lmi_ops_rdn;
+} ldap_monitor_info_t;
+
+enum {
+ /* even numbers are connection types */
+ LDAP_BACK_PCONN_FIRST = 0,
+ LDAP_BACK_PCONN_ROOTDN = LDAP_BACK_PCONN_FIRST,
+ LDAP_BACK_PCONN_ANON = 2,
+ LDAP_BACK_PCONN_BIND = 4,
+
+ /* add the TLS bit */
+ LDAP_BACK_PCONN_TLS = 0x1U,
+
+ LDAP_BACK_PCONN_ROOTDN_TLS = (LDAP_BACK_PCONN_ROOTDN|LDAP_BACK_PCONN_TLS),
+ LDAP_BACK_PCONN_ANON_TLS = (LDAP_BACK_PCONN_ANON|LDAP_BACK_PCONN_TLS),
+ LDAP_BACK_PCONN_BIND_TLS = (LDAP_BACK_PCONN_BIND|LDAP_BACK_PCONN_TLS),
+
+ LDAP_BACK_PCONN_LAST
+};
+
+typedef struct ldapconn_base_t {
+ Connection *lcb_conn;
+#define LDAP_BACK_CONN2PRIV(lc) ((unsigned long)(lc)->lc_conn)
+#define LDAP_BACK_PCONN_ISPRIV(lc) (((void *)(lc)->lc_conn) >= ((void *)LDAP_BACK_PCONN_FIRST) \
+ && ((void *)(lc)->lc_conn) < ((void *)LDAP_BACK_PCONN_LAST))
+#define LDAP_BACK_PCONN_ISROOTDN(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \
+ && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_ANON))
+#define LDAP_BACK_PCONN_ISANON(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \
+ && (LDAP_BACK_CONN2PRIV((lc)) < LDAP_BACK_PCONN_BIND) \
+ && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_ANON))
+#define LDAP_BACK_PCONN_ISBIND(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \
+ && (LDAP_BACK_CONN2PRIV((lc)) >= LDAP_BACK_PCONN_BIND))
+#define LDAP_BACK_PCONN_ISTLS(lc) (LDAP_BACK_PCONN_ISPRIV((lc)) \
+ && (LDAP_BACK_CONN2PRIV((lc)) & LDAP_BACK_PCONN_TLS))
+#ifdef HAVE_TLS
+#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \
+ ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ROOTDN_TLS : (void *) LDAP_BACK_PCONN_ROOTDN))
+#define LDAP_BACK_PCONN_ANON_SET(lc, op) \
+ ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_ANON_TLS : (void *) LDAP_BACK_PCONN_ANON))
+#define LDAP_BACK_PCONN_BIND_SET(lc, op) \
+ ((lc)->lc_conn = (void *)((op)->o_conn->c_is_tls ? (void *) LDAP_BACK_PCONN_BIND_TLS : (void *) LDAP_BACK_PCONN_BIND))
+#else /* ! HAVE_TLS */
+#define LDAP_BACK_PCONN_ROOTDN_SET(lc, op) \
+ ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ROOTDN)
+#define LDAP_BACK_PCONN_ANON_SET(lc, op) \
+ ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_ANON)
+#define LDAP_BACK_PCONN_BIND_SET(lc, op) \
+ ((lc)->lc_conn = (void *)LDAP_BACK_PCONN_BIND)
+#endif /* ! HAVE_TLS */
+#define LDAP_BACK_PCONN_SET(lc, op) \
+ (BER_BVISEMPTY(&(op)->o_ndn) ? \
+ LDAP_BACK_PCONN_ANON_SET((lc), (op)) : LDAP_BACK_PCONN_ROOTDN_SET((lc), (op)))
+
+ struct ldapinfo_t *lcb_ldapinfo;
+ struct berval lcb_local_ndn;
+ unsigned lcb_refcnt;
+ time_t lcb_create_time;
+ time_t lcb_time;
+} ldapconn_base_t;
+
+typedef struct ldapconn_t {
+ ldapconn_base_t lc_base;
+#define lc_conn lc_base.lcb_conn
+#define lc_ldapinfo lc_base.lcb_ldapinfo
+#define lc_local_ndn lc_base.lcb_local_ndn
+#define lc_refcnt lc_base.lcb_refcnt
+#define lc_create_time lc_base.lcb_create_time
+#define lc_time lc_base.lcb_time
+
+ LDAP_TAILQ_ENTRY(ldapconn_t) lc_q;
+
+ unsigned lc_lcflags;
+#define LDAP_BACK_CONN_ISSET_F(fp,f) (*(fp) & (f))
+#define LDAP_BACK_CONN_SET_F(fp,f) (*(fp) |= (f))
+#define LDAP_BACK_CONN_CLEAR_F(fp,f) (*(fp) &= ~(f))
+#define LDAP_BACK_CONN_CPY_F(fp,f,mfp) \
+ do { \
+ if ( ((f) & *(mfp)) == (f) ) { \
+ *(fp) |= (f); \
+ } else { \
+ *(fp) &= ~(f); \
+ } \
+ } while ( 0 )
+
+#define LDAP_BACK_CONN_ISSET(lc,f) LDAP_BACK_CONN_ISSET_F(&(lc)->lc_lcflags, (f))
+#define LDAP_BACK_CONN_SET(lc,f) LDAP_BACK_CONN_SET_F(&(lc)->lc_lcflags, (f))
+#define LDAP_BACK_CONN_CLEAR(lc,f) LDAP_BACK_CONN_CLEAR_F(&(lc)->lc_lcflags, (f))
+#define LDAP_BACK_CONN_CPY(lc,f,mlc) LDAP_BACK_CONN_CPY_F(&(lc)->lc_lcflags, (f), &(mlc)->lc_lcflags)
+
+/* 0xFFF00000U are reserved for back-meta */
+
+#define LDAP_BACK_FCONN_ISBOUND (0x00000001U)
+#define LDAP_BACK_FCONN_ISANON (0x00000002U)
+#define LDAP_BACK_FCONN_ISBMASK (LDAP_BACK_FCONN_ISBOUND|LDAP_BACK_FCONN_ISANON)
+#define LDAP_BACK_FCONN_ISPRIV (0x00000004U)
+#define LDAP_BACK_FCONN_ISTLS (0x00000008U)
+#define LDAP_BACK_FCONN_BINDING (0x00000010U)
+#define LDAP_BACK_FCONN_TAINTED (0x00000020U)
+#define LDAP_BACK_FCONN_ABANDON (0x00000040U)
+#define LDAP_BACK_FCONN_ISIDASR (0x00000080U)
+#define LDAP_BACK_FCONN_CACHED (0x00000100U)
+
+#define LDAP_BACK_CONN_ISBOUND(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISBOUND)
+#define LDAP_BACK_CONN_ISBOUND_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISBOUND)
+#define LDAP_BACK_CONN_ISBOUND_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISBMASK)
+#define LDAP_BACK_CONN_ISBOUND_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISBOUND, (mlc))
+#define LDAP_BACK_CONN_ISANON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISANON)
+#define LDAP_BACK_CONN_ISANON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISANON)
+#define LDAP_BACK_CONN_ISANON_CLEAR(lc) LDAP_BACK_CONN_ISBOUND_CLEAR((lc))
+#define LDAP_BACK_CONN_ISANON_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISANON, (mlc))
+#define LDAP_BACK_CONN_ISPRIV(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISPRIV)
+#define LDAP_BACK_CONN_ISPRIV_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISPRIV)
+#define LDAP_BACK_CONN_ISPRIV_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISPRIV)
+#define LDAP_BACK_CONN_ISPRIV_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISPRIV, (mlc))
+#define LDAP_BACK_CONN_ISTLS(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISTLS)
+#define LDAP_BACK_CONN_ISTLS_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISTLS)
+#define LDAP_BACK_CONN_ISTLS_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISTLS)
+#define LDAP_BACK_CONN_ISTLS_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISTLS, (mlc))
+#define LDAP_BACK_CONN_BINDING(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_BINDING)
+#define LDAP_BACK_CONN_BINDING_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_BINDING)
+#define LDAP_BACK_CONN_BINDING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_BINDING)
+#define LDAP_BACK_CONN_TAINTED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_TAINTED)
+#define LDAP_BACK_CONN_TAINTED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_TAINTED)
+#define LDAP_BACK_CONN_TAINTED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_TAINTED)
+#define LDAP_BACK_CONN_ABANDON(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ABANDON)
+#define LDAP_BACK_CONN_ABANDON_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ABANDON)
+#define LDAP_BACK_CONN_ABANDON_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ABANDON)
+#define LDAP_BACK_CONN_ISIDASSERT(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_ISIDASR)
+#define LDAP_BACK_CONN_ISIDASSERT_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_ISIDASR)
+#define LDAP_BACK_CONN_ISIDASSERT_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_ISIDASR)
+#define LDAP_BACK_CONN_ISIDASSERT_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), LDAP_BACK_FCONN_ISIDASR, (mlc))
+#define LDAP_BACK_CONN_CACHED(lc) LDAP_BACK_CONN_ISSET((lc), LDAP_BACK_FCONN_CACHED)
+#define LDAP_BACK_CONN_CACHED_SET(lc) LDAP_BACK_CONN_SET((lc), LDAP_BACK_FCONN_CACHED)
+#define LDAP_BACK_CONN_CACHED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), LDAP_BACK_FCONN_CACHED)
+
+ LDAP *lc_ld;
+ unsigned long lc_connid;
+ struct berval lc_cred;
+ struct berval lc_bound_ndn;
+ unsigned lc_flags;
+} ldapconn_t;
+
+typedef struct ldap_avl_info_t {
+ ldap_pvt_thread_mutex_t lai_mutex;
+ TAvlnode *lai_tree;
+} ldap_avl_info_t;
+
+typedef struct slap_retry_info_t {
+ time_t *ri_interval;
+ int *ri_num;
+ int ri_idx;
+ int ri_count;
+ time_t ri_last;
+
+#define SLAP_RETRYNUM_FOREVER (-1) /* retry forever */
+#define SLAP_RETRYNUM_TAIL (-2) /* end of retrynum array */
+#define SLAP_RETRYNUM_VALID(n) ((n) >= SLAP_RETRYNUM_FOREVER) /* valid retrynum */
+#define SLAP_RETRYNUM_FINITE(n) ((n) > SLAP_RETRYNUM_FOREVER) /* not forever */
+} slap_retry_info_t;
+
+/*
+ * identity assertion modes
+ */
+typedef enum {
+ LDAP_BACK_IDASSERT_LEGACY = 1,
+ LDAP_BACK_IDASSERT_NOASSERT,
+ LDAP_BACK_IDASSERT_ANONYMOUS,
+ LDAP_BACK_IDASSERT_SELF,
+ LDAP_BACK_IDASSERT_OTHERDN,
+ LDAP_BACK_IDASSERT_OTHERID
+} slap_idassert_mode_t;
+
+/* ID assert stuff */
+typedef struct slap_idassert_t {
+ slap_idassert_mode_t si_mode;
+#define li_idassert_mode li_idassert.si_mode
+
+ slap_bindconf si_bc;
+#define li_idassert_authcID li_idassert.si_bc.sb_authcId
+#define li_idassert_authcDN li_idassert.si_bc.sb_binddn
+#define li_idassert_passwd li_idassert.si_bc.sb_cred
+#define li_idassert_authzID li_idassert.si_bc.sb_authzId
+#define li_idassert_authmethod li_idassert.si_bc.sb_method
+#define li_idassert_sasl_mech li_idassert.si_bc.sb_saslmech
+#define li_idassert_sasl_realm li_idassert.si_bc.sb_realm
+#define li_idassert_secprops li_idassert.si_bc.sb_secprops
+#define li_idassert_tls li_idassert.si_bc.sb_tls
+
+ unsigned si_flags;
+#define LDAP_BACK_AUTH_NONE (0x00U)
+#define LDAP_BACK_AUTH_NATIVE_AUTHZ (0x01U)
+#define LDAP_BACK_AUTH_OVERRIDE (0x02U)
+#define LDAP_BACK_AUTH_PRESCRIPTIVE (0x04U)
+#define LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ (0x08U)
+#define LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND (0x10U)
+#define LDAP_BACK_AUTH_AUTHZ_ALL (0x20U)
+#define LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL (0x40U)
+#define LDAP_BACK_AUTH_DN_AUTHZID (0x100U)
+#define LDAP_BACK_AUTH_DN_WHOAMI (0x200U)
+#define LDAP_BACK_AUTH_DN_MASK (LDAP_BACK_AUTH_DN_AUTHZID|LDAP_BACK_AUTH_DN_WHOAMI)
+#define li_idassert_flags li_idassert.si_flags
+
+ BerVarray si_authz;
+#define li_idassert_authz li_idassert.si_authz
+
+ BerVarray si_passthru;
+#define li_idassert_passthru li_idassert.si_passthru
+} slap_idassert_t;
+
+/*
+ * Hook to allow mucking with ldapinfo_t when quarantine is over
+ */
+typedef int (*ldap_back_quarantine_f)( struct ldapinfo_t *, void * );
+
+typedef struct ldapinfo_t {
+ /* li_uri: the string that goes into ldap_initialize()
+ * TODO: use li_acl.sb_uri instead */
+ char *li_uri;
+ /* li_bvuri: an array of each single URI that is equivalent;
+ * to be checked for the presence of a certain item */
+ BerVarray li_bvuri;
+ ldap_pvt_thread_mutex_t li_uri_mutex;
+ /* hack because when TLS is used we need to lock and let
+ * the li_urllist_f function to know it's locked */
+ int li_uri_mutex_do_not_lock;
+
+ LDAP_REBIND_PROC *li_rebind_f;
+ LDAP_URLLIST_PROC *li_urllist_f;
+ void *li_urllist_p;
+
+ /* we only care about the TLS options here */
+ slap_bindconf li_tls;
+
+ slap_bindconf li_acl;
+#define li_acl_authcID li_acl.sb_authcId
+#define li_acl_authcDN li_acl.sb_binddn
+#define li_acl_passwd li_acl.sb_cred
+#define li_acl_authzID li_acl.sb_authzId
+#define li_acl_authmethod li_acl.sb_method
+#define li_acl_sasl_mech li_acl.sb_saslmech
+#define li_acl_sasl_realm li_acl.sb_realm
+#define li_acl_secprops li_acl.sb_secprops
+
+ /* ID assert stuff */
+ slap_idassert_t li_idassert;
+ /* end of ID assert stuff */
+
+ int li_nretries;
+#define LDAP_BACK_RETRY_UNDEFINED (-2)
+#define LDAP_BACK_RETRY_FOREVER (-1)
+#define LDAP_BACK_RETRY_NEVER (0)
+#define LDAP_BACK_RETRY_DEFAULT (3)
+
+ unsigned li_flags;
+
+/* 0xFF000000U are reserved for back-meta */
+
+#define LDAP_BACK_F_NONE (0x00000000U)
+#define LDAP_BACK_F_SAVECRED (0x00000001U)
+#define LDAP_BACK_F_USE_TLS (0x00000002U)
+#define LDAP_BACK_F_PROPAGATE_TLS (0x00000004U)
+#define LDAP_BACK_F_TLS_CRITICAL (0x00000008U)
+#define LDAP_BACK_F_TLS_LDAPS (0x00000010U)
+
+#define LDAP_BACK_F_TLS_USE_MASK (LDAP_BACK_F_USE_TLS|LDAP_BACK_F_TLS_CRITICAL)
+#define LDAP_BACK_F_TLS_PROPAGATE_MASK (LDAP_BACK_F_PROPAGATE_TLS|LDAP_BACK_F_TLS_CRITICAL)
+#define LDAP_BACK_F_TLS_MASK (LDAP_BACK_F_TLS_USE_MASK|LDAP_BACK_F_TLS_PROPAGATE_MASK|LDAP_BACK_F_TLS_LDAPS)
+#define LDAP_BACK_F_CHASE_REFERRALS (0x00000020U)
+#define LDAP_BACK_F_PROXY_WHOAMI (0x00000040U)
+
+#define LDAP_BACK_F_T_F (0x00000080U)
+#define LDAP_BACK_F_T_F_DISCOVER (0x00000100U)
+#define LDAP_BACK_F_T_F_MASK (LDAP_BACK_F_T_F)
+#define LDAP_BACK_F_T_F_MASK2 (LDAP_BACK_F_T_F_MASK|LDAP_BACK_F_T_F_DISCOVER)
+
+#define LDAP_BACK_F_MONITOR (0x00000200U)
+#define LDAP_BACK_F_SINGLECONN (0x00000400U)
+#define LDAP_BACK_F_USE_TEMPORARIES (0x00000800U)
+
+#define LDAP_BACK_F_ISOPEN (0x00001000U)
+
+#define LDAP_BACK_F_CANCEL_ABANDON (0x00000000U)
+#define LDAP_BACK_F_CANCEL_IGNORE (0x00002000U)
+#define LDAP_BACK_F_CANCEL_EXOP (0x00004000U)
+#define LDAP_BACK_F_CANCEL_EXOP_DISCOVER (0x00008000U)
+#define LDAP_BACK_F_CANCEL_MASK (LDAP_BACK_F_CANCEL_IGNORE|LDAP_BACK_F_CANCEL_EXOP)
+#define LDAP_BACK_F_CANCEL_MASK2 (LDAP_BACK_F_CANCEL_MASK|LDAP_BACK_F_CANCEL_EXOP_DISCOVER)
+
+#define LDAP_BACK_F_QUARANTINE (0x00010000U)
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define LDAP_BACK_F_ST_REQUEST (0x00020000U)
+#define LDAP_BACK_F_ST_RESPONSE (0x00040000U)
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define LDAP_BACK_F_NOREFS (0x00080000U)
+#define LDAP_BACK_F_NOUNDEFFILTER (0x00100000U)
+#define LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA (0x00200000U)
+
+#define LDAP_BACK_F_ONERR_STOP (0x00400000U)
+
+#define LDAP_BACK_ISSET_F(ff,f) ( ( (ff) & (f) ) == (f) )
+#define LDAP_BACK_ISMASK_F(ff,m,f) ( ( (ff) & (m) ) == (f) )
+
+#define LDAP_BACK_ISSET(li,f) LDAP_BACK_ISSET_F( (li)->li_flags, (f) )
+#define LDAP_BACK_ISMASK(li,m,f) LDAP_BACK_ISMASK_F( (li)->li_flags, (m), (f) )
+
+#define LDAP_BACK_SAVECRED(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SAVECRED )
+#define LDAP_BACK_USE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TLS )
+#define LDAP_BACK_PROPAGATE_TLS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROPAGATE_TLS )
+#define LDAP_BACK_TLS_CRITICAL(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_TLS_CRITICAL )
+#define LDAP_BACK_CHASE_REFERRALS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_CHASE_REFERRALS )
+#define LDAP_BACK_PROXY_WHOAMI(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_PROXY_WHOAMI )
+
+#define LDAP_BACK_USE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_USE_TLS )
+#define LDAP_BACK_PROPAGATE_TLS_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_PROPAGATE_TLS )
+#define LDAP_BACK_TLS_CRITICAL_F(ff) LDAP_BACK_ISSET_F( (ff), LDAP_BACK_F_TLS_CRITICAL )
+
+#define LDAP_BACK_T_F(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F )
+#define LDAP_BACK_T_F_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER )
+
+#define LDAP_BACK_MONITOR(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_MONITOR )
+#define LDAP_BACK_SINGLECONN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_SINGLECONN )
+#define LDAP_BACK_USE_TEMPORARIES(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_USE_TEMPORARIES)
+
+#define LDAP_BACK_ISOPEN(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ISOPEN )
+
+#define LDAP_BACK_ABANDON(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON )
+#define LDAP_BACK_IGNORE(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE )
+#define LDAP_BACK_CANCEL(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP )
+#define LDAP_BACK_CANCEL_DISCOVER(li) LDAP_BACK_ISMASK( (li), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER )
+
+#define LDAP_BACK_QUARANTINE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_QUARANTINE )
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define LDAP_BACK_ST_REQUEST(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_REQUEST)
+#define LDAP_BACK_ST_RESPONSE(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ST_RESPONSE)
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define LDAP_BACK_NOREFS(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOREFS)
+#define LDAP_BACK_NOUNDEFFILTER(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_NOUNDEFFILTER)
+#define LDAP_BACK_OMIT_UNKNOWN_SCHEMA(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA)
+#define LDAP_BACK_ONERR_STOP(li) LDAP_BACK_ISSET( (li), LDAP_BACK_F_ONERR_STOP)
+
+ int li_version;
+
+ unsigned long li_conn_nextid;
+
+ /* cached connections;
+ * special conns are in tailq rather than in tree */
+ ldap_avl_info_t li_conninfo;
+ struct {
+ int lic_num;
+ LDAP_TAILQ_HEAD(lc_conn_priv_q, ldapconn_t) lic_priv;
+ } li_conn_priv[ LDAP_BACK_PCONN_LAST ];
+ int li_conn_priv_max;
+#define LDAP_BACK_CONN_PRIV_MIN (1)
+#define LDAP_BACK_CONN_PRIV_MAX (256)
+ /* must be between LDAP_BACK_CONN_PRIV_MIN
+ * and LDAP_BACK_CONN_PRIV_MAX ! */
+#define LDAP_BACK_CONN_PRIV_DEFAULT (16)
+
+ ldap_monitor_info_t li_monitor_info;
+
+ sig_atomic_t li_isquarantined;
+#define LDAP_BACK_FQ_NO (0)
+#define LDAP_BACK_FQ_YES (1)
+#define LDAP_BACK_FQ_RETRYING (2)
+
+ slap_retry_info_t li_quarantine;
+ ldap_pvt_thread_mutex_t li_quarantine_mutex;
+ ldap_back_quarantine_f li_quarantine_f;
+ void *li_quarantine_p;
+
+ time_t li_network_timeout;
+ time_t li_conn_ttl;
+ time_t li_idle_timeout;
+ time_t li_timeout[ SLAP_OP_LAST ];
+
+ ldap_pvt_thread_mutex_t li_counter_mutex;
+ ldap_pvt_mp_t li_ops_completed[SLAP_OP_LAST];
+ struct re_s* li_conn_expire_task;
+} ldapinfo_t;
+
+#define LDAP_ERR_OK(err) ((err) == LDAP_SUCCESS || (err) == LDAP_COMPARE_FALSE || (err) == LDAP_COMPARE_TRUE)
+
+typedef enum ldap_back_send_t {
+ LDAP_BACK_DONTSEND = 0x00,
+ LDAP_BACK_SENDOK = 0x01,
+ LDAP_BACK_SENDERR = 0x02,
+ LDAP_BACK_SENDRESULT = (LDAP_BACK_SENDOK|LDAP_BACK_SENDERR),
+ LDAP_BACK_BINDING = 0x04,
+
+ LDAP_BACK_BIND_DONTSEND = (LDAP_BACK_BINDING),
+ LDAP_BACK_BIND_SOK = (LDAP_BACK_BINDING|LDAP_BACK_SENDOK),
+ LDAP_BACK_BIND_SERR = (LDAP_BACK_BINDING|LDAP_BACK_SENDERR),
+ LDAP_BACK_BIND_SRES = (LDAP_BACK_BINDING|LDAP_BACK_SENDRESULT),
+
+ LDAP_BACK_RETRYING = 0x08,
+ LDAP_BACK_RETRY_DONTSEND = (LDAP_BACK_RETRYING),
+ LDAP_BACK_RETRY_SOK = (LDAP_BACK_RETRYING|LDAP_BACK_SENDOK),
+ LDAP_BACK_RETRY_SERR = (LDAP_BACK_RETRYING|LDAP_BACK_SENDERR),
+ LDAP_BACK_RETRY_SRES = (LDAP_BACK_RETRYING|LDAP_BACK_SENDRESULT),
+
+ LDAP_BACK_GETCONN = 0x10
+} ldap_back_send_t;
+
+/* define to use asynchronous StartTLS */
+#define SLAP_STARTTLS_ASYNCHRONOUS
+
+/* timeout to use when calling ldap_result() */
+#define LDAP_BACK_RESULT_TIMEOUT (0)
+#define LDAP_BACK_RESULT_UTIMEOUT (100000)
+#define LDAP_BACK_TV_SET(tv) \
+ do { \
+ (tv)->tv_sec = LDAP_BACK_RESULT_TIMEOUT; \
+ (tv)->tv_usec = LDAP_BACK_RESULT_UTIMEOUT; \
+ } while ( 0 )
+
+#ifndef LDAP_BACK_PRINT_CONNTREE
+#define LDAP_BACK_PRINT_CONNTREE 0
+#endif /* !LDAP_BACK_PRINT_CONNTREE */
+
+typedef struct ldap_extra_t {
+ int (*proxy_authz_ctrl)( Operation *op, SlapReply *rs, struct berval *bound_ndn,
+ int version, slap_idassert_t *si, LDAPControl *ctrl );
+ int (*controls_free)( Operation *op, SlapReply *rs, LDAPControl ***pctrls );
+ int (*idassert_authzfrom_parse)( struct config_args_s *ca, slap_idassert_t *si );
+ int (*idassert_passthru_parse_cf)( const char *fname, int lineno, const char *arg, slap_idassert_t *si );
+ int (*idassert_parse)( struct config_args_s *ca, slap_idassert_t *si );
+ void (*retry_info_destroy)( slap_retry_info_t *ri );
+ int (*retry_info_parse)( char *in, slap_retry_info_t *ri, char *buf, ber_len_t buflen );
+ int (*retry_info_unparse)( slap_retry_info_t *ri, struct berval *bvout );
+ int (*connid2str)( const ldapconn_base_t *lc, char *buf, ber_len_t buflen );
+} ldap_extra_t;
+
+LDAP_END_DECL
+
+#include "proto-ldap.h"
+
+#endif /* SLAPD_LDAP_H */
diff --git a/servers/slapd/back-ldap/bind.c b/servers/slapd/back-ldap/bind.c
new file mode 100644
index 0000000..16a09bf
--- /dev/null
+++ b/servers/slapd/back-ldap/bind.c
@@ -0,0 +1,3230 @@
+/* bind.c - ldap backend bind function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#define AVL_INTERNAL
+#include "slap.h"
+#include "back-ldap.h"
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "ldap_rq.h"
+
+#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12"
+
+#ifdef LDAP_DEVEL
+#define SLAP_AUTH_DN 1
+#endif
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+
+static const struct {
+ slap_mask_t f;
+ char c;
+} flagsmap[] = {
+ { LDAP_BACK_FCONN_ISBOUND, 'B' },
+ { LDAP_BACK_FCONN_ISANON, 'A' },
+ { LDAP_BACK_FCONN_ISPRIV, 'P' },
+ { LDAP_BACK_FCONN_ISTLS, 'T' },
+ { LDAP_BACK_FCONN_BINDING, 'X' },
+ { LDAP_BACK_FCONN_TAINTED, 'E' },
+ { LDAP_BACK_FCONN_ABANDON, 'N' },
+ { LDAP_BACK_FCONN_ISIDASR, 'S' },
+ { LDAP_BACK_FCONN_CACHED, 'C' },
+ { 0, '\0' }
+};
+
+static void
+ldap_back_conn_print( ldapconn_t *lc )
+{
+ char buf[ SLAP_TEXT_BUFLEN ];
+ char fbuf[ sizeof("BAPTIENSC") ];
+ int i;
+
+ ldap_back_conn2str( &lc->lc_base, buf, sizeof( buf ) );
+ for ( i = 0; flagsmap[ i ].c != '\0'; i++ ) {
+ if ( lc->lc_lcflags & flagsmap[i].f ) {
+ fbuf[i] = flagsmap[i].c;
+
+ } else {
+ fbuf[i] = '.';
+ }
+ }
+ fbuf[i] = '\0';
+
+ fprintf( stderr, "lc=%p %s flags=0x%08x (%s)\n",
+ (void *)lc, buf, lc->lc_lcflags, fbuf );
+}
+
+
+static char* priv2str[] = {
+ "privileged",
+ "privileged/TLS",
+ "anonymous",
+ "anonymous/TLS",
+ "bind",
+ "bind/TLS",
+ NULL
+};
+
+void
+ldap_back_print_conntree( ldapinfo_t *li, char *msg )
+{
+ int c;
+
+ fprintf( stderr, "========> %s\n", msg );
+
+ for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) {
+ int i = 0;
+ ldapconn_t *lc;
+
+ fprintf( stderr, " %s[%d]\n", priv2str[ c ], li->li_conn_priv[ c ].lic_num );
+
+ LDAP_TAILQ_FOREACH( lc, &li->li_conn_priv[ c ].lic_priv, lc_q )
+ {
+ fprintf( stderr, " [%d] ", i );
+ ldap_back_conn_print( lc );
+ i++;
+ }
+ }
+
+ if ( li->li_conninfo.lai_tree == 0 ) {
+ fprintf( stderr, "\t(empty)\n" );
+
+ } else {
+ TAvlnode *edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ ldap_back_conn_print( (ldapconn_t *)edge->avl_data );
+ edge = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ }
+ }
+
+ fprintf( stderr, "<======== %s\n", msg );
+}
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+static int
+ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock );
+
+static ldapconn_t *
+ldap_back_getconn( Operation *op, SlapReply *rs, ldap_back_send_t sendok,
+ struct berval *binddn, struct berval *bindcred );
+
+static int
+ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok,
+ struct berval *binddn, struct berval *bindcred );
+
+static int
+ldap_back_proxy_authz_bind( ldapconn_t *lc, Operation *op, SlapReply *rs,
+ ldap_back_send_t sendok, struct berval *binddn, struct berval *bindcred );
+
+static int
+ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs,
+ ldap_back_send_t sendok );
+
+static int
+ldap_back_conndnlc_cmp( const void *c1, const void *c2 );
+
+static void
+ldap_back_conn_prune( ldapinfo_t *li );
+
+static void
+ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc );
+
+ldapconn_t *
+ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc )
+{
+ if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) {
+ if ( LDAP_BACK_CONN_CACHED( lc ) ) {
+ assert( lc->lc_q.tqe_prev != NULL );
+ assert( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num > 0 );
+ li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num--;
+ LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q );
+ LDAP_TAILQ_ENTRY_INIT( lc, lc_q );
+ LDAP_BACK_CONN_CACHED_CLEAR( lc );
+
+ } else {
+ assert( LDAP_BACK_CONN_TAINTED( lc ) );
+ assert( lc->lc_q.tqe_prev == NULL );
+ }
+
+ } else {
+ ldapconn_t *tmplc = NULL;
+
+ if ( LDAP_BACK_CONN_CACHED( lc ) ) {
+ assert( !LDAP_BACK_CONN_TAINTED( lc ) );
+ tmplc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc,
+ ldap_back_conndnlc_cmp );
+ assert( tmplc == lc );
+ LDAP_BACK_CONN_CACHED_CLEAR( lc );
+ }
+
+ assert( LDAP_BACK_CONN_TAINTED( lc ) || tmplc == lc );
+ }
+
+ return lc;
+}
+
+int
+ldap_back_bind( Operation *op, SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+ ldapconn_t *lc;
+
+ LDAPControl **ctrls = NULL;
+ struct berval save_o_dn;
+ int save_o_do_not_cache,
+ rc = 0;
+ ber_int_t msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+
+ /* allow rootdn as a means to auth without the need to actually
+ * contact the proxied DSA */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ return rs->sr_err;
+ }
+
+ lc = ldap_back_getconn( op, rs, LDAP_BACK_BIND_SERR, NULL, NULL );
+ if ( !lc ) {
+ return rs->sr_err;
+ }
+
+ /* we can do (almost) whatever we want with this conn,
+ * because either it's temporary, or it's marked as binding */
+ if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) {
+ ch_free( lc->lc_bound_ndn.bv_val );
+ BER_BVZERO( &lc->lc_bound_ndn );
+ }
+ if ( !BER_BVISNULL( &lc->lc_cred ) ) {
+ memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len );
+ ch_free( lc->lc_cred.bv_val );
+ BER_BVZERO( &lc->lc_cred );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+
+ /* don't add proxyAuthz; set the bindDN */
+ save_o_dn = op->o_dn;
+ save_o_do_not_cache = op->o_do_not_cache;
+ op->o_dn = op->o_req_dn;
+ op->o_do_not_cache = 1;
+
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ op->o_dn = save_o_dn;
+ op->o_do_not_cache = save_o_do_not_cache;
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ ldap_back_release_conn( li, lc );
+ return( rc );
+ }
+
+retry:;
+ /* method is always LDAP_AUTH_SIMPLE if we got here */
+ rs->sr_err = ldap_sasl_bind( lc->lc_ld, op->o_req_dn.bv_val,
+ LDAP_SASL_SIMPLE,
+ &op->orb_cred, ctrls, NULL, &msgid );
+ /* FIXME: should we always retry, or only when piping the bind
+ * in the "override" connection pool? */
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_BIND ],
+ LDAP_BACK_BIND_SERR | retrying );
+ if ( rc == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_BIND_SERR ) ) {
+ goto retry;
+ }
+ if ( !lc )
+ return( rc );
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( rc == LDAP_SUCCESS ) {
+ op->o_conn->c_authz_cookie = op->o_bd->be_private;
+
+ /* If defined, proxyAuthz will be used also when
+ * back-ldap is the authorizing backend; for this
+ * purpose, after a successful bind the connection
+ * is left for further binds, and further operations
+ * on this client connection will use a default
+ * connection with identity assertion */
+ /* NOTE: use with care */
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ ldap_back_release_conn( li, lc );
+ return( rc );
+ }
+
+ /* rebind is now done inside ldap_back_proxy_authz_bind()
+ * in case of success */
+ LDAP_BACK_CONN_ISBOUND_SET( lc );
+ ber_dupbv( &lc->lc_bound_ndn, &op->o_req_ndn );
+
+ if ( !BER_BVISNULL( &lc->lc_cred ) ) {
+ memset( lc->lc_cred.bv_val, 0,
+ lc->lc_cred.bv_len );
+ }
+
+ if ( LDAP_BACK_SAVECRED( li ) ) {
+ ber_bvreplace( &lc->lc_cred, &op->orb_cred );
+ ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc );
+
+ } else {
+ lc->lc_cred.bv_len = 0;
+ }
+ }
+
+ /* must re-insert if local DN changed as result of bind */
+ if ( !LDAP_BACK_CONN_ISBOUND( lc )
+ || ( !dn_match( &op->o_req_ndn, &lc->lc_local_ndn )
+ && !LDAP_BACK_PCONN_ISPRIV( lc ) ) )
+ {
+ int lerr = -1;
+ ldapconn_t *tmplc;
+
+ /* wait for all other ops to release the connection */
+retry_lock:;
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ if ( lc->lc_refcnt > 1 ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ ldap_pvt_thread_yield();
+ goto retry_lock;
+ }
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_bind" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ assert( lc->lc_refcnt == 1 );
+ ldap_back_conn_delete( li, lc );
+
+ /* delete all cached connections with the current connection */
+ if ( LDAP_BACK_SINGLECONN( li ) ) {
+ while ( ( tmplc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, ldap_back_conn_cmp ) ) != NULL )
+ {
+ assert( !LDAP_BACK_PCONN_ISPRIV( lc ) );
+ Debug( LDAP_DEBUG_TRACE,
+ "=>ldap_back_bind: destroying conn %lu (refcnt=%u)\n",
+ lc->lc_conn->c_connid, lc->lc_refcnt );
+
+ if ( tmplc->lc_refcnt != 0 ) {
+ /* taint it */
+ LDAP_BACK_CONN_TAINTED_SET( tmplc );
+ LDAP_BACK_CONN_CACHED_CLEAR( tmplc );
+
+ } else {
+ /*
+ * Needs a test because the handler may be corrupted,
+ * and calling ldap_unbind on a corrupted header results
+ * in a segmentation fault
+ */
+ ldap_back_conn_free( tmplc );
+ }
+ }
+ }
+
+ if ( LDAP_BACK_CONN_ISBOUND( lc ) ) {
+ ber_bvreplace( &lc->lc_local_ndn, &op->o_req_ndn );
+ if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) {
+ LDAP_BACK_PCONN_ROOTDN_SET( lc, op );
+ }
+ lerr = ldap_tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
+ ldap_back_conndn_cmp, ldap_back_conndn_dup );
+ }
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_bind" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ switch ( lerr ) {
+ case 0:
+ LDAP_BACK_CONN_CACHED_SET( lc );
+ break;
+
+ case -1:
+ /* duplicate; someone else successfully bound
+ * on the same connection with the same identity;
+ * we can do this because lc_refcnt == 1 */
+ ldap_back_conn_free( lc );
+ lc = NULL;
+ }
+ }
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return( rc );
+}
+
+/*
+ * ldap_back_conndn_cmp
+ *
+ * compares two ldapconn_t based on the value of the conn pointer
+ * and of the local DN; used by avl stuff for insert, lookup
+ * and direct delete
+ */
+int
+ldap_back_conndn_cmp( const void *c1, const void *c2 )
+{
+ const ldapconn_t *lc1 = (const ldapconn_t *)c1;
+ const ldapconn_t *lc2 = (const ldapconn_t *)c2;
+ int rc;
+
+ /* If local DNs don't match, it is definitely not a match */
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn );
+ if ( rc == 0 ) {
+ rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn );
+ }
+
+ return rc;
+}
+
+/*
+ * ldap_back_conndnlc_cmp
+ *
+ * compares two ldapconn_t based on the value of the conn pointer,
+ * the local DN and the lc pointer; used by avl stuff for insert, lookup
+ * and direct delete
+ */
+static int
+ldap_back_conndnlc_cmp( const void *c1, const void *c2 )
+{
+ const ldapconn_t *lc1 = (const ldapconn_t *)c1;
+ const ldapconn_t *lc2 = (const ldapconn_t *)c2;
+ int rc;
+
+ /* If local DNs don't match, it is definitely not a match */
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ rc = SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn );
+ if ( rc == 0 ) {
+ rc = ber_bvcmp( &lc1->lc_local_ndn, &lc2->lc_local_ndn );
+ if ( rc == 0 ) {
+ rc = SLAP_PTRCMP( lc1, lc2 );
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * ldap_back_conn_cmp
+ *
+ * compares two ldapconn_t based on the value of the conn pointer;
+ * used by avl stuff for delete of all conns with the same connid
+ */
+int
+ldap_back_conn_cmp( const void *c1, const void *c2 )
+{
+ const ldapconn_t *lc1 = (const ldapconn_t *)c1;
+ const ldapconn_t *lc2 = (const ldapconn_t *)c2;
+
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ return SLAP_PTRCMP( lc1->lc_conn, lc2->lc_conn );
+}
+
+/*
+ * ldap_back_conndn_dup
+ *
+ * returns -1 in case a duplicate ldapconn_t has been inserted;
+ * used by avl stuff
+ */
+int
+ldap_back_conndn_dup( void *c1, void *c2 )
+{
+ ldapconn_t *lc1 = (ldapconn_t *)c1;
+ ldapconn_t *lc2 = (ldapconn_t *)c2;
+
+ /* Cannot have more than one shared session with same DN */
+ if ( lc1->lc_conn == lc2->lc_conn &&
+ dn_match( &lc1->lc_local_ndn, &lc2->lc_local_ndn ) )
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+ldap_back_freeconn( ldapinfo_t *li, ldapconn_t *lc, int dolock )
+{
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ }
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_freeconn" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ (void)ldap_back_conn_delete( li, lc );
+
+ if ( lc->lc_refcnt == 0 ) {
+ ldap_back_conn_free( (void *)lc );
+ }
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_freeconn" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+
+ return 0;
+}
+
+#ifdef HAVE_TLS
+static int
+ldap_back_start_tls(
+ LDAP *ld,
+ int protocol,
+ int *is_tls,
+ const char *url,
+ unsigned flags,
+ int timeout,
+ const char **text )
+{
+ int rc = LDAP_SUCCESS;
+
+ /* start TLS ("tls-[try-]{start,propagate}" statements) */
+ if ( ( LDAP_BACK_USE_TLS_F( flags ) || ( *is_tls && LDAP_BACK_PROPAGATE_TLS_F( flags ) ) )
+ && !ldap_is_ldaps_url( url ) )
+ {
+#ifdef SLAP_STARTTLS_ASYNCHRONOUS
+ /*
+ * use asynchronous StartTLS
+ * in case, chase referral (not implemented yet)
+ */
+ int msgid;
+
+ if ( protocol == 0 ) {
+ ldap_get_option( ld, LDAP_OPT_PROTOCOL_VERSION,
+ (void *)&protocol );
+ }
+
+ if ( protocol < LDAP_VERSION3 ) {
+ /* we should rather bail out... */
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ *text = "invalid protocol version";
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = ldap_start_tls( ld, NULL, NULL, &msgid );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ LDAPMessage *res = NULL;
+ struct timeval tv;
+
+ if ( timeout ) {
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ } else {
+ LDAP_BACK_TV_SET( &tv );
+ }
+ rc = ldap_result( ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ if ( rc <= 0 ) {
+ rc = LDAP_UNAVAILABLE;
+
+ } else if ( rc == LDAP_RES_EXTENDED ) {
+ struct berval *data = NULL;
+
+ rc = ldap_parse_extended_result( ld, res,
+ NULL, &data, 0 );
+ if ( rc == LDAP_SUCCESS ) {
+ SlapReply rs;
+ rc = ldap_parse_result( ld, res, &rs.sr_err,
+ NULL, NULL, NULL, NULL, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ rs.sr_err = rc;
+ }
+ rc = slap_map_api2result( &rs );
+ res = NULL;
+
+ /* FIXME: in case a referral
+ * is returned, should we try
+ * using it instead of the
+ * configured URI? */
+ if ( rc == LDAP_SUCCESS ) {
+ rc = ldap_install_tls( ld );
+
+ } else if ( rc == LDAP_REFERRAL ) {
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ *text = "unwilling to chase referral returned by Start TLS exop";
+ }
+
+ if ( data ) {
+ if ( data->bv_val ) {
+ ber_memfree( data->bv_val );
+ }
+ ber_memfree( data );
+ }
+ }
+
+ } else {
+ rc = LDAP_OTHER;
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ }
+#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ /*
+ * use synchronous StartTLS
+ */
+ rc = ldap_start_tls_s( ld, NULL, NULL );
+#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+
+ /* if StartTLS is requested, only attempt it if the URL
+ * is not "ldaps://"; this may occur not only in case
+ * of misconfiguration, but also when used in the chain
+ * overlay, where the "uri" can be parsed out of a referral */
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ *is_tls = 1;
+ break;
+
+ case LDAP_SERVER_DOWN:
+ break;
+
+ default:
+ if ( LDAP_BACK_TLS_CRITICAL_F( flags ) ) {
+ *text = "could not start TLS";
+ break;
+ }
+
+ /* in case Start TLS is not critical */
+ *is_tls = 0;
+ rc = LDAP_SUCCESS;
+ break;
+ }
+
+ } else {
+ *is_tls = 0;
+ }
+
+ return rc;
+}
+#endif /* HAVE_TLS */
+
+static int
+ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs, ldap_back_send_t sendok )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+ int version;
+ LDAP *ld = NULL;
+#ifdef HAVE_TLS
+ int is_tls = op->o_conn->c_is_tls;
+ int flags = li->li_flags;
+ slap_bindconf *sb;
+#endif /* HAVE_TLS */
+
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ rs->sr_err = ldap_initialize( &ld, li->li_uri );
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto error_return;
+ }
+
+ if ( li->li_urllist_f ) {
+ ldap_set_urllist_proc( ld, li->li_urllist_f, li->li_urllist_p );
+ }
+
+ /* Set LDAP version. This will always succeed: If the client
+ * bound with a particular version, then so can we.
+ */
+ if ( li->li_version != 0 ) {
+ version = li->li_version;
+
+ } else if ( op->o_protocol != 0 ) {
+ version = op->o_protocol;
+
+ } else {
+ /* assume it's an internal op; set to LDAPv3 */
+ version = LDAP_VERSION3;
+ }
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, (const void *)&version );
+
+ /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */
+ ldap_set_option( ld, LDAP_OPT_REFERRALS,
+ LDAP_BACK_CHASE_REFERRALS( li ) ? LDAP_OPT_ON : LDAP_OPT_OFF );
+
+ if ( li->li_network_timeout > 0 ) {
+ struct timeval tv;
+
+ tv.tv_sec = li->li_network_timeout;
+ tv.tv_usec = 0;
+ ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, (const void *)&tv );
+ }
+
+ /* turn on network keepalive, if configured so */
+ slap_client_keepalive(ld, &li->li_tls.sb_keepalive);
+
+ if ( li->li_tls.sb_tcp_user_timeout > 0 ) {
+ ldap_set_option( ld, LDAP_OPT_TCP_USER_TIMEOUT,
+ &li->li_tls.sb_tcp_user_timeout );
+ }
+
+#ifdef HAVE_TLS
+ if ( LDAP_BACK_CONN_ISPRIV( lc ) ) {
+ /* See "rationale" comment in ldap_back_getconn() */
+ if ( li->li_acl_authmethod == LDAP_AUTH_NONE &&
+ li->li_idassert_authmethod != LDAP_AUTH_NONE )
+ sb = &li->li_idassert.si_bc;
+ else
+ sb = &li->li_acl;
+
+ } else if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) {
+ sb = &li->li_idassert.si_bc;
+
+ } else {
+ sb = &li->li_tls;
+ }
+
+ bindconf_tls_set( sb, ld );
+
+ /* if required by the bindconf configuration, force TLS */
+ if ( ( sb == &li->li_acl || sb == &li->li_idassert.si_bc ) &&
+ sb->sb_tls_ctx )
+ {
+ flags |= LDAP_BACK_F_USE_TLS;
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ assert( li->li_uri_mutex_do_not_lock == 0 );
+ li->li_uri_mutex_do_not_lock = 1;
+ rs->sr_err = ldap_back_start_tls( ld, op->o_protocol, &is_tls,
+ li->li_uri, flags, li->li_timeout[ SLAP_OP_BIND ], &rs->sr_text );
+ li->li_uri_mutex_do_not_lock = 0;
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ rs->sr_text = "Start TLS failed";
+ goto error_return;
+ }
+#endif /* HAVE_TLS */
+
+ lc->lc_ld = ld;
+ lc->lc_refcnt = 1;
+#ifdef HAVE_TLS
+ if ( is_tls ) {
+ LDAP_BACK_CONN_ISTLS_SET( lc );
+ } else {
+ LDAP_BACK_CONN_ISTLS_CLEAR( lc );
+ }
+#endif /* HAVE_TLS */
+
+error_return:;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_text == NULL ) {
+ rs->sr_text = "Proxy connection initialization failed";
+ }
+ send_ldap_result( op, rs );
+ }
+
+ } else {
+ lc->lc_create_time = op->o_time;
+ lc->lc_time = op->o_time;
+ }
+
+ return rs->sr_err;
+}
+
+static ldapconn_t *
+ldap_back_getconn(
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+ ldapconn_t *lc = NULL,
+ lc_curr = {{ 0 }};
+ int refcnt = 1,
+ lookupconn = !( sendok & LDAP_BACK_BINDING );
+
+ /* if the server is quarantined, and
+ * - the current interval did not expire yet, or
+ * - no more retries should occur,
+ * don't return the connection */
+ if ( li->li_isquarantined ) {
+ slap_retry_info_t *ri = &li->li_quarantine;
+ int dont_retry = 1;
+
+ if ( li->li_quarantine.ri_interval ) {
+ ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex );
+ if ( li->li_isquarantined == LDAP_BACK_FQ_YES ) {
+ dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL
+ || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] );
+ if ( !dont_retry ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: ldap_back_getconn quarantine "
+ "retry block #%d try #%d.\n",
+ op->o_log_prefix, ri->ri_idx, ri->ri_count );
+ li->li_isquarantined = LDAP_BACK_FQ_RETRYING;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex );
+ }
+
+ if ( dont_retry ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ rs->sr_text = "Target is quarantined";
+ send_ldap_result( op, rs );
+ }
+ return NULL;
+ }
+ }
+
+ /* Internal searches are privileged and shared. So is root. */
+ if ( op->o_do_not_cache || be_isroot( op ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( &lc_curr );
+ lc_curr.lc_local_ndn = op->o_bd->be_rootndn;
+ LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op );
+
+ } else {
+ struct berval tmpbinddn,
+ tmpbindcred,
+ save_o_dn,
+ save_o_ndn;
+ int isproxyauthz;
+
+ /* need cleanup */
+ if ( binddn == NULL ) {
+ binddn = &tmpbinddn;
+ }
+ if ( bindcred == NULL ) {
+ bindcred = &tmpbindcred;
+ }
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ save_o_dn = op->o_dn;
+ save_o_ndn = op->o_ndn;
+ op->o_dn = op->o_req_dn;
+ op->o_ndn = op->o_req_ndn;
+ }
+ isproxyauthz = ldap_back_is_proxy_authz( op, rs, sendok, binddn, bindcred );
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ op->o_dn = save_o_dn;
+ op->o_ndn = save_o_ndn;
+ }
+ if ( isproxyauthz == -1 ) {
+ return NULL;
+ }
+
+ lc_curr.lc_local_ndn = op->o_ndn;
+ /* Explicit binds must not be shared;
+ * however, explicit binds are piped in a special connection
+ * when idassert is to occur with "override" set */
+ if ( op->o_tag == LDAP_REQ_BIND && !isproxyauthz ) {
+ lc_curr.lc_conn = op->o_conn;
+
+ } else {
+ if ( isproxyauthz && !( sendok & LDAP_BACK_BINDING ) ) {
+ lc_curr.lc_local_ndn = *binddn;
+ LDAP_BACK_PCONN_ROOTDN_SET( &lc_curr, op );
+ LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr );
+
+ } else if ( isproxyauthz && ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) {
+ lc_curr.lc_local_ndn = slap_empty_bv;
+ LDAP_BACK_PCONN_BIND_SET( &lc_curr, op );
+ LDAP_BACK_CONN_ISIDASSERT_SET( &lc_curr );
+ lookupconn = 1;
+
+ } else if ( SLAP_IS_AUTHZ_BACKEND( op ) ) {
+ lc_curr.lc_conn = op->o_conn;
+
+ } else {
+ LDAP_BACK_PCONN_ANON_SET( &lc_curr, op );
+ }
+ }
+ }
+
+ /* Explicit Bind requests always get their own conn */
+ if ( lookupconn ) {
+retry_lock:
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ if ( LDAP_BACK_PCONN_ISPRIV( &lc_curr ) ) {
+ /* lookup a conn that's not binding */
+ LDAP_TAILQ_FOREACH( lc,
+ &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv,
+ lc_q )
+ {
+ if ( !LDAP_BACK_CONN_BINDING( lc ) && lc->lc_refcnt == 0 ) {
+ break;
+ }
+ }
+
+ if ( lc != NULL ) {
+ if ( lc != LDAP_TAILQ_LAST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv,
+ lc_conn_priv_q ) )
+ {
+ LDAP_TAILQ_REMOVE( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv,
+ lc, lc_q );
+ LDAP_TAILQ_ENTRY_INIT( lc, lc_q );
+ LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv,
+ lc, lc_q );
+ }
+
+ } else if ( !LDAP_BACK_USE_TEMPORARIES( li )
+ && li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_num == li->li_conn_priv_max )
+ {
+ lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( &lc_curr ) ].lic_priv );
+ }
+
+ } else {
+
+ /* Searches for a ldapconn in the avl tree */
+ lc = (ldapconn_t *)ldap_tavl_find( li->li_conninfo.lai_tree,
+ (caddr_t)&lc_curr, ldap_back_conndn_cmp );
+ }
+
+ if ( lc != NULL ) {
+ /* Don't reuse connections while they're still binding */
+ if ( LDAP_BACK_CONN_BINDING( lc ) ) {
+ if ( !LDAP_BACK_USE_TEMPORARIES( li ) ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ ldap_pvt_thread_yield();
+ goto retry_lock;
+ }
+ lc = NULL;
+ }
+
+ if ( lc != NULL ) {
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ /* right now, this is the only possible case */
+ assert( ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) );
+ LDAP_BACK_CONN_BINDING_SET( lc );
+ }
+
+ refcnt = ++lc->lc_refcnt;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+
+ /* Looks like we didn't get a bind. Open a new session... */
+ if ( lc == NULL ) {
+ lc = (ldapconn_t *)ch_calloc( 1, sizeof( ldapconn_t ) );
+ lc->lc_flags = li->li_flags;
+ lc->lc_lcflags = lc_curr.lc_lcflags;
+ lc->lc_ldapinfo = li;
+ if ( ldap_back_prepare_conn( lc, op, rs, sendok ) != LDAP_SUCCESS ) {
+ ch_free( lc );
+ return NULL;
+ }
+
+ if ( sendok & LDAP_BACK_BINDING ) {
+ LDAP_BACK_CONN_BINDING_SET( lc );
+ }
+
+ lc->lc_conn = lc_curr.lc_conn;
+ ber_dupbv( &lc->lc_local_ndn, &lc_curr.lc_local_ndn );
+
+ /*
+ * the rationale is: connections as the rootdn are privileged,
+ * so li_acl is to be used; however, in some cases
+ * one already configured identity assertion with a highly
+ * privileged idassert_authcDN, so if li_acl is not configured
+ * and idassert is, use idassert instead.
+ *
+ * might change in the future, because it's preferable
+ * to make clear what identity is being used, since
+ * the only drawback is that one risks to configure
+ * the same identity twice...
+ */
+ if ( LDAP_BACK_CONN_ISPRIV( &lc_curr ) ) {
+ if ( li->li_acl_authmethod == LDAP_AUTH_NONE &&
+ li->li_idassert_authmethod != LDAP_AUTH_NONE ) {
+ ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN );
+ ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd );
+
+ } else {
+ ber_dupbv( &lc->lc_bound_ndn, &li->li_acl_authcDN );
+ ber_dupbv( &lc->lc_cred, &li->li_acl_passwd );
+ }
+ LDAP_BACK_CONN_ISPRIV_SET( lc );
+
+ } else if ( LDAP_BACK_CONN_ISIDASSERT( &lc_curr ) ) {
+ if ( !LDAP_BACK_PCONN_ISBIND( &lc_curr ) ) {
+ ber_dupbv( &lc->lc_bound_ndn, &li->li_idassert_authcDN );
+ ber_dupbv( &lc->lc_cred, &li->li_idassert_passwd );
+ }
+ LDAP_BACK_CONN_ISIDASSERT_SET( lc );
+
+ } else {
+ BER_BVZERO( &lc->lc_cred );
+ BER_BVZERO( &lc->lc_bound_ndn );
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ && SLAP_IS_AUTHZ_BACKEND( op ) )
+ {
+ ber_dupbv( &lc->lc_bound_ndn, &op->o_ndn );
+ }
+ }
+
+#ifdef HAVE_TLS
+ /* if start TLS failed but it was not mandatory,
+ * check if the non-TLS connection was already
+ * in cache; in case, destroy the newly created
+ * connection and use the existing one */
+ if ( LDAP_BACK_PCONN_ISTLS( lc )
+ && !ldap_tls_inplace( lc->lc_ld ) )
+ {
+ ldapconn_t *tmplc = NULL;
+ int idx = LDAP_BACK_CONN2PRIV( &lc_curr ) - 1;
+
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ LDAP_TAILQ_FOREACH( tmplc,
+ &li->li_conn_priv[ idx ].lic_priv,
+ lc_q )
+ {
+ if ( !LDAP_BACK_CONN_BINDING( tmplc ) ) {
+ break;
+ }
+ }
+
+ if ( tmplc != NULL ) {
+ refcnt = ++tmplc->lc_refcnt;
+ ldap_back_conn_free( lc );
+ lc = tmplc;
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ if ( tmplc != NULL ) {
+ goto done;
+ }
+ }
+#endif /* HAVE_TLS */
+
+ /* Inserts the newly created ldapconn in the avl tree */
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ lc->lc_connid = li->li_conn_nextid++;
+
+ assert( lc->lc_refcnt == 1 );
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_getconn(insert)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ if ( LDAP_BACK_PCONN_ISPRIV( lc ) ) {
+ if ( li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num < li->li_conn_priv_max ) {
+ LDAP_TAILQ_INSERT_TAIL( &li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_priv, lc, lc_q );
+ li->li_conn_priv[ LDAP_BACK_CONN2PRIV( lc ) ].lic_num++;
+ LDAP_BACK_CONN_CACHED_SET( lc );
+
+ } else {
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+ }
+ rs->sr_err = 0;
+
+ } else {
+ rs->sr_err = ldap_tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
+ ldap_back_conndn_cmp, ldap_back_conndn_dup );
+ LDAP_BACK_CONN_CACHED_SET( lc );
+ }
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_getconn(insert)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ Debug(LDAP_DEBUG_TRACE,
+ "=>ldap_back_getconn: %s: lc=%p inserted refcnt=%u rc=%d\n",
+ op->o_log_prefix, (void *)lc, refcnt,
+ rs->sr_err );
+
+ if ( !LDAP_BACK_PCONN_ISPRIV( lc ) ) {
+ /* Err could be -1 in case a duplicate ldapconn is inserted */
+ switch ( rs->sr_err ) {
+ case 0:
+ break;
+
+ case -1:
+ LDAP_BACK_CONN_CACHED_CLEAR( lc );
+ if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( li ) ) {
+ /* duplicate: free and try to get the newly created one */
+ ldap_back_conn_free( lc );
+ lc = NULL;
+ goto retry_lock;
+ }
+
+ /* taint connection, so that it'll be freed when released */
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+ break;
+
+ default:
+ LDAP_BACK_CONN_CACHED_CLEAR( lc );
+ ldap_back_conn_free( lc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Proxy bind collision";
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ send_ldap_result( op, rs );
+ }
+ return NULL;
+ }
+ }
+ ldap_back_schedule_conn_expiry( li, lc );
+
+ } else {
+ int expiring = 0;
+
+ if ( ( li->li_idle_timeout != 0 && op->o_time > lc->lc_time + li->li_idle_timeout )
+ || ( li->li_conn_ttl != 0 && op->o_time > lc->lc_create_time + li->li_conn_ttl ) )
+ {
+ expiring = 1;
+
+ /* let it be used, but taint/delete it so that
+ * no-one else can look it up any further */
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ (void)ldap_back_conn_delete( li, lc );
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "=>ldap_back_getconn: conn %p fetched refcnt=%u%s.\n",
+ (void *)lc, refcnt, expiring ? " expiring" : "" );
+ }
+
+#ifdef HAVE_TLS
+done:;
+#endif /* HAVE_TLS */
+
+ return lc;
+}
+
+void
+ldap_back_release_conn_lock(
+ ldapinfo_t *li,
+ ldapconn_t **lcp,
+ int dolock )
+{
+
+ ldapconn_t *lc = *lcp;
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ }
+ assert( lc->lc_refcnt > 0 );
+ LDAP_BACK_CONN_BINDING_CLEAR( lc );
+ lc->lc_refcnt--;
+ if ( LDAP_BACK_CONN_TAINTED( lc ) ) {
+ ldap_back_freeconn( li, lc, 0 );
+ *lcp = NULL;
+ }
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+}
+
+void
+ldap_back_quarantine(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ slap_retry_info_t *ri = &li->li_quarantine;
+
+ ldap_pvt_thread_mutex_lock( &li->li_quarantine_mutex );
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ time_t new_last = slap_get_time();
+
+ switch ( li->li_isquarantined ) {
+ case LDAP_BACK_FQ_NO:
+ if ( ri->ri_last == new_last ) {
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s: ldap_back_quarantine enter.\n",
+ op->o_log_prefix );
+
+ ri->ri_idx = 0;
+ ri->ri_count = 0;
+ break;
+
+ case LDAP_BACK_FQ_RETRYING:
+ Debug( LDAP_DEBUG_ANY,
+ "%s: ldap_back_quarantine block #%d try #%d failed.\n",
+ op->o_log_prefix, ri->ri_idx, ri->ri_count );
+
+ ++ri->ri_count;
+ if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER
+ && ri->ri_count == ri->ri_num[ ri->ri_idx ] )
+ {
+ ri->ri_count = 0;
+ ++ri->ri_idx;
+ }
+ break;
+
+ default:
+ goto done;
+ }
+
+ li->li_isquarantined = LDAP_BACK_FQ_YES;
+ ri->ri_last = new_last;
+
+ } else if ( li->li_isquarantined != LDAP_BACK_FQ_NO ) {
+ if ( ri->ri_last == slap_get_time() ) {
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s: ldap_back_quarantine exit (%d) err=%d.\n",
+ op->o_log_prefix, li->li_isquarantined, rs->sr_err );
+
+ if ( li->li_quarantine_f ) {
+ (void)li->li_quarantine_f( li, li->li_quarantine_p );
+ }
+
+ ri->ri_count = 0;
+ ri->ri_idx = 0;
+ li->li_isquarantined = LDAP_BACK_FQ_NO;
+ }
+
+done:;
+ ldap_pvt_thread_mutex_unlock( &li->li_quarantine_mutex );
+}
+
+static int
+ldap_back_dobind_cb(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ ber_tag_t *tptr = op->o_callback->sc_private;
+ op->o_tag = *tptr;
+ rs->sr_tag = slap_req2res( op->o_tag );
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * ldap_back_dobind_int
+ *
+ * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not
+ */
+static int
+ldap_back_dobind_int(
+ ldapconn_t **lcp,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int retries,
+ int dolock )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc;
+ struct berval binddn = slap_empty_bv,
+ bindcred = slap_empty_bv;
+
+ int rc = 0,
+ isbound,
+ binding = 0;
+ ber_int_t msgid;
+ ber_tag_t o_tag = op->o_tag;
+ slap_callback cb = {0};
+ char *tmp_dn;
+
+ assert( lcp != NULL );
+ assert( retries >= 0 );
+
+ if ( sendok & LDAP_BACK_GETCONN ) {
+ assert( *lcp == NULL );
+
+ lc = ldap_back_getconn( op, rs, sendok, &binddn, &bindcred );
+ if ( lc == NULL ) {
+ return 0;
+ }
+ *lcp = lc;
+
+ } else {
+ lc = *lcp;
+ }
+
+ assert( lc != NULL );
+
+retry_lock:;
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ }
+
+ if ( binding == 0 ) {
+ /* check if already bound */
+ rc = isbound = LDAP_BACK_CONN_ISBOUND( lc );
+ if ( isbound ) {
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+ return rc;
+ }
+
+ if ( LDAP_BACK_CONN_BINDING( lc ) ) {
+ /* if someone else is about to bind it, give up and retry */
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+ ldap_pvt_thread_yield();
+ goto retry_lock;
+
+ } else {
+ /* otherwise this thread will bind it */
+ LDAP_BACK_CONN_BINDING_SET( lc );
+ binding = 1;
+ }
+ }
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+
+ /*
+ * FIXME: we need to let clients use proxyAuthz
+ * otherwise we cannot do symmetric pools of servers;
+ * we have to live with the fact that a user can
+ * authorize itself as any ID that is allowed
+ * by the authzTo directive of the "proxyauthzdn".
+ */
+ /*
+ * NOTE: current Proxy Authorization specification
+ * and implementation do not allow proxy authorization
+ * control to be provided with Bind requests
+ */
+ /*
+ * if no bind took place yet, but the connection is bound
+ * and the "idassert-authcDN" (or other ID) is set,
+ * then bind as the asserting identity and explicitly
+ * add the proxyAuthz control to every operation with the
+ * dn bound to the connection as control value.
+ * This is done also if this is the authorizing backend,
+ * but the "override" flag is given to idassert.
+ * It allows to use SASL bind and yet proxyAuthz users
+ */
+ op->o_tag = LDAP_REQ_BIND;
+ cb.sc_next = op->o_callback;
+ cb.sc_private = &o_tag;
+ cb.sc_response = ldap_back_dobind_cb;
+ op->o_callback = &cb;
+
+ if ( LDAP_BACK_CONN_ISIDASSERT( lc ) ) {
+ if ( BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &bindcred ) ) {
+ /* if we got here, it shouldn't return result */
+ rc = ldap_back_is_proxy_authz( op, rs,
+ LDAP_BACK_DONTSEND, &binddn, &bindcred );
+ if ( rc != 1 ) {
+ Debug( LDAP_DEBUG_ANY, "Error: ldap_back_is_proxy_authz "
+ "returned %d, misconfigured URI?\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "misconfigured URI?";
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+ }
+ rc = ldap_back_proxy_authz_bind( lc, op, rs, sendok, &binddn, &bindcred );
+ goto done;
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ if ( LDAP_BACK_CONN_ISPRIV( lc )) {
+ slap_bindconf *sb;
+ if ( li->li_acl_authmethod != LDAP_AUTH_NONE )
+ sb = &li->li_acl;
+ else
+ sb = &li->li_idassert.si_bc;
+
+ if ( sb->sb_method == LDAP_AUTH_SASL ) {
+ void *defaults = NULL;
+
+ if ( sb->sb_secprops != NULL ) {
+ rc = ldap_set_option( lc->lc_ld,
+ LDAP_OPT_X_SASL_SECPROPS, sb->sb_secprops );
+
+ if ( rc != LDAP_OPT_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Error: ldap_set_option "
+ "(SECPROPS,\"%s\") failed!\n",
+ sb->sb_secprops );
+ goto done;
+ }
+ }
+
+ defaults = lutil_sasl_defaults( lc->lc_ld,
+ sb->sb_saslmech.bv_val,
+ sb->sb_realm.bv_val,
+ sb->sb_authcId.bv_val,
+ sb->sb_cred.bv_val,
+ sb->sb_authzId.bv_val );
+ if ( defaults == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+ rs->sr_err = ldap_sasl_interactive_bind_s( lc->lc_ld,
+ sb->sb_binddn.bv_val,
+ sb->sb_saslmech.bv_val, NULL, NULL,
+ LDAP_SASL_QUIET, lutil_sasl_interact,
+ defaults );
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ lutil_sasl_freedefs( defaults );
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ LDAP_BACK_CONN_ISBOUND_SET( lc );
+ break;
+
+ case LDAP_LOCAL_ERROR:
+ /* list client API error codes that require
+ * to taint the connection */
+ /* FIXME: should actually retry? */
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+
+ /* fallthru */
+
+ default:
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ break;
+ }
+
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ goto done;
+ }
+ }
+#endif /* HAVE_CYRUS_SASL */
+
+retry:;
+ if ( BER_BVISNULL( &lc->lc_cred ) ) {
+ tmp_dn = "";
+ /*
+ * Bind is requested with DN but without credentials.
+ * This can happen when connection to remote server has been
+ * lost either due to remote server disconnecting it or due to
+ * proxy disconnecting it by itself (idle-timeout, conn-ttl).
+ * See comment in ldap_back_conn_prune().
+ */
+ if ( !BER_BVISNULL( &lc->lc_bound_ndn ) && !BER_BVISEMPTY( &lc->lc_bound_ndn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_dobind_int: DN=\"%s\" connection "
+ "was re-established but cannot rebind without creds\n",
+ op->o_log_prefix, lc->lc_bound_ndn.bv_val );
+ rs->sr_text = "Proxy lost connection to remote server";
+ rs->sr_err = LDAP_UNAVAILABLE;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ rs->sr_err = SLAPD_DISCONNECT;
+ rc = 0;
+ goto done;
+ }
+
+ } else {
+ tmp_dn = lc->lc_bound_ndn.bv_val;
+ }
+ rs->sr_err = ldap_sasl_bind( lc->lc_ld,
+ tmp_dn,
+ LDAP_SASL_SIMPLE, &lc->lc_cred,
+ NULL, NULL, &msgid );
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ if ( rs->sr_err == LDAP_SERVER_DOWN ) {
+ if ( retries != LDAP_BACK_RETRY_NEVER ) {
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+ }
+
+ assert( lc->lc_refcnt > 0 );
+ if ( lc->lc_refcnt == 1 ) {
+ ldap_unbind_ext( lc->lc_ld, NULL, NULL );
+ lc->lc_ld = NULL;
+
+ /* lc here must be the regular lc, reset and ready for init */
+ rs->sr_err = ldap_back_prepare_conn( lc, op, rs, sendok );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ sendok &= ~LDAP_BACK_SENDERR;
+ lc->lc_refcnt = 0;
+ }
+ }
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ if ( retries > 0 ) {
+ retries--;
+ }
+ goto retry;
+ }
+ }
+
+ assert( lc->lc_refcnt == 1 );
+ lc->lc_refcnt = 0;
+ ldap_back_freeconn( li, lc, dolock );
+ *lcp = NULL;
+ rs->sr_err = slap_map_api2result( rs );
+
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS &&
+ ( sendok & LDAP_BACK_SENDERR ) )
+ {
+ if ( op->o_callback == &cb )
+ op->o_callback = cb.sc_next;
+ op->o_tag = o_tag;
+ rs->sr_text = "Proxy can't contact remote server";
+ send_ldap_result( op, rs );
+ /* if we originally bound and wanted rebind-as-user, must drop
+ * the connection now because we just discarded the credentials.
+ * ITS#7464, #8142
+ */
+ if ( LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) )
+ rs->sr_err = SLAPD_DISCONNECT;
+ }
+
+ rc = 0;
+ goto func_leave;
+ }
+
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ -1, ( sendok | LDAP_BACK_BINDING ) );
+ if ( rc == LDAP_SUCCESS ) {
+ LDAP_BACK_CONN_ISBOUND_SET( lc );
+ }
+
+done:;
+ LDAP_BACK_CONN_BINDING_CLEAR( lc );
+ rc = LDAP_BACK_CONN_ISBOUND( lc );
+ if ( !rc ) {
+ ldap_back_release_conn_lock( li, lcp, dolock );
+
+ } else if ( LDAP_BACK_SAVECRED( li ) ) {
+ ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc );
+ }
+
+func_leave:;
+ if ( op->o_callback == &cb )
+ op->o_callback = cb.sc_next;
+ op->o_tag = o_tag;
+
+ return rc;
+}
+
+/*
+ * ldap_back_dobind
+ *
+ * Note: dolock indicates whether li->li_conninfo.lai_mutex must be locked or not
+ */
+int
+ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ return ldap_back_dobind_int( lcp, op, rs,
+ ( sendok | LDAP_BACK_GETCONN ), li->li_nretries, 1 );
+}
+
+/*
+ * ldap_back_default_rebind
+ *
+ * This is a callback used for chasing referrals using the same
+ * credentials as the original user on this session.
+ */
+int
+ldap_back_default_rebind( LDAP *ld, LDAP_CONST char *url, ber_tag_t request,
+ ber_int_t msgid, void *params )
+{
+ ldapconn_t *lc = (ldapconn_t *)params;
+
+#ifdef HAVE_TLS
+ /* ... otherwise we couldn't get here */
+ assert( lc != NULL );
+
+ if ( !ldap_tls_inplace( ld ) ) {
+ int is_tls = LDAP_BACK_CONN_ISTLS( lc ),
+ rc;
+ const char *text = NULL;
+
+ rc = ldap_back_start_tls( ld, 0, &is_tls, url, lc->lc_flags,
+ lc->lc_ldapinfo->li_timeout[ SLAP_OP_BIND ], &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+#endif /* HAVE_TLS */
+
+ /* FIXME: add checks on the URL/identity? */
+ /* TODO: would like to count this bind operation for monitoring
+ * too, but where do we get the ldapinfo_t? */
+
+ return ldap_sasl_bind_s( ld,
+ BER_BVISNULL( &lc->lc_cred ) ? "" : lc->lc_bound_ndn.bv_val,
+ LDAP_SASL_SIMPLE, &lc->lc_cred, NULL, NULL, NULL );
+}
+
+/*
+ * ldap_back_default_urllist
+ */
+int
+ldap_back_default_urllist(
+ LDAP *ld,
+ LDAPURLDesc **urllist,
+ LDAPURLDesc **url,
+ void *params )
+{
+ ldapinfo_t *li = (ldapinfo_t *)params;
+ LDAPURLDesc **urltail;
+
+ if ( urllist == url ) {
+ return LDAP_SUCCESS;
+ }
+
+ for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next )
+ /* count */ ;
+
+ *urltail = *urllist;
+ *urllist = *url;
+ *url = NULL;
+
+ if ( !li->li_uri_mutex_do_not_lock ) {
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ }
+
+ if ( li->li_uri ) {
+ ch_free( li->li_uri );
+ }
+
+ ldap_get_option( ld, LDAP_OPT_URI, (void *)&li->li_uri );
+
+ if ( !li->li_uri_mutex_do_not_lock ) {
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+ldap_back_cancel(
+ ldapconn_t *lc,
+ Operation *op,
+ SlapReply *rs,
+ ber_int_t msgid,
+ ldap_back_send_t sendok )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ /* default behavior */
+ if ( LDAP_BACK_ABANDON( li ) ) {
+ return ldap_abandon_ext( lc->lc_ld, msgid, NULL, NULL );
+ }
+
+ if ( LDAP_BACK_IGNORE( li ) ) {
+ return ldap_pvt_discard( lc->lc_ld, msgid );
+ }
+
+ if ( LDAP_BACK_CANCEL( li ) ) {
+ /* FIXME: asynchronous? */
+ return ldap_cancel_s( lc->lc_ld, msgid, NULL, NULL );
+ }
+
+ assert( 0 );
+
+ return LDAP_OTHER;
+}
+
+int
+ldap_back_op_result(
+ ldapconn_t *lc,
+ Operation *op,
+ SlapReply *rs,
+ ber_int_t msgid,
+ time_t timeout,
+ ldap_back_send_t sendok )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ char *match = NULL;
+ char *text = NULL;
+ char **refs = NULL;
+ LDAPControl **ctrls = NULL;
+
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+ rs->sr_ctrls = NULL;
+
+ /* if the error recorded in the reply corresponds
+ * to a successful state, get the error from the
+ * remote server response */
+ if ( LDAP_ERR_OK( rs->sr_err ) ) {
+ int rc;
+ struct timeval tv;
+ LDAPMessage *res = NULL;
+ time_t stoptime = (time_t)(-1);
+ int timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ const char *timeout_text = "Operation timed out";
+
+ /* if timeout is not specified, compute and use
+ * the one specific to the ongoing operation */
+ if ( timeout == (time_t)(-1) ) {
+ slap_op_t opidx = slap_req2op( op->o_tag );
+
+ if ( opidx == SLAP_OP_SEARCH ) {
+ if ( op->ors_tlimit <= 0 ) {
+ timeout = 0;
+
+ } else {
+ timeout = op->ors_tlimit;
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ }
+
+ } else {
+ timeout = li->li_timeout[ opidx ];
+ }
+ }
+
+ /* better than nothing :) */
+ if ( timeout == 0 ) {
+ if ( li->li_idle_timeout ) {
+ timeout = li->li_idle_timeout;
+
+ } else if ( li->li_conn_ttl ) {
+ timeout = li->li_conn_ttl;
+ }
+ }
+
+ if ( timeout ) {
+ stoptime = op->o_time + timeout;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+
+retry:;
+ /* if result parsing fails, note the failure reason */
+ rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ if ( timeout && slap_get_time() > stoptime ) {
+ if ( sendok & LDAP_BACK_BINDING ) {
+ ldap_unbind_ext( lc->lc_ld, NULL, NULL );
+ lc->lc_ld = NULL;
+
+ /* let it be used, but taint/delete it so that
+ * no-one else can look it up any further */
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_getconn(timeout)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+ (void)ldap_back_conn_delete( li, lc );
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_getconn(timeout)" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ } else {
+ (void)ldap_back_cancel( lc, op, rs, msgid, sendok );
+ }
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ break;
+ }
+
+ /* timeout == 0 */
+ LDAP_BACK_TV_SET( &tv );
+ ldap_pvt_thread_yield();
+ goto retry;
+
+ case -1:
+ ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER,
+ &rs->sr_err );
+ break;
+
+
+ /* otherwise get the result; if it is not
+ * LDAP_SUCCESS, record it in the reply
+ * structure (this includes
+ * LDAP_COMPARE_{TRUE|FALSE}) */
+ default:
+ /* only touch when activity actually took place... */
+ if ( li->li_idle_timeout ) {
+ lc->lc_time = op->o_time;
+ }
+
+ rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
+ &match, &text, &refs, &ctrls, 1 );
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_text = text;
+ } else {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( refs != NULL
+ && refs[ 0 ] != NULL
+ && refs[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_op_result: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ rs->sr_err );
+
+ } else {
+ int i;
+
+ for ( i = 0; refs[ i ] != NULL; i++ )
+ /* count */ ;
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0; refs[ i ] != NULL; i++ ) {
+ ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ i ] );
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_op_result: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( ctrls != NULL ) {
+ rs->sr_ctrls = ctrls;
+ }
+ }
+ }
+
+ /* if the error in the reply structure is not
+ * LDAP_SUCCESS, try to map it from client
+ * to server error */
+ if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* internal ops ( op->o_conn == NULL )
+ * must not reply to client */
+ if ( op->o_conn && !op->o_do_not_cache && match ) {
+
+ /* record the (massaged) matched
+ * DN into the reply structure */
+ rs->sr_matched = match;
+ }
+ }
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ if ( !( sendok & LDAP_BACK_RETRYING ) ) {
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed";
+ send_ldap_result( op, rs );
+ }
+ }
+
+ } else if ( op->o_conn &&
+ ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) )
+ || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) )
+ {
+ send_ldap_result( op, rs );
+ }
+
+ if ( text ) {
+ ldap_memfree( text );
+ }
+ rs->sr_text = NULL;
+
+ /* there can't be refs with a (successful) bind */
+ if ( rs->sr_ref ) {
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+
+ if ( refs ) {
+ ber_memvfree( (void **)refs );
+ }
+
+ /* match should not be possible with a successful bind */
+ if ( match ) {
+ if ( rs->sr_matched != match ) {
+ free( (char *)rs->sr_matched );
+ }
+ rs->sr_matched = NULL;
+ ldap_memfree( match );
+ }
+
+ if ( ctrls != NULL ) {
+ if ( op->o_tag == LDAP_REQ_BIND && rs->sr_err == LDAP_SUCCESS ) {
+ int i;
+
+ for ( i = 0; ctrls[i] != NULL; i++ );
+
+ rs->sr_ctrls = op->o_tmpalloc( sizeof( LDAPControl * )*( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0; ctrls[ i ] != NULL; i++ ) {
+ char *ptr;
+ ber_len_t oidlen = strlen( ctrls[i]->ldctl_oid );
+ ber_len_t size = sizeof( LDAPControl )
+ + oidlen + 1
+ + ctrls[i]->ldctl_value.bv_len + 1;
+
+ rs->sr_ctrls[ i ] = op->o_tmpalloc( size, op->o_tmpmemctx );
+ rs->sr_ctrls[ i ]->ldctl_oid = (char *)&rs->sr_ctrls[ i ][ 1 ];
+ lutil_strcopy( rs->sr_ctrls[ i ]->ldctl_oid, ctrls[i]->ldctl_oid );
+ rs->sr_ctrls[ i ]->ldctl_value.bv_val
+ = (char *)&rs->sr_ctrls[ i ]->ldctl_oid[oidlen + 1];
+ rs->sr_ctrls[ i ]->ldctl_value.bv_len
+ = ctrls[i]->ldctl_value.bv_len;
+ ptr = lutil_memcopy( rs->sr_ctrls[ i ]->ldctl_value.bv_val,
+ ctrls[i]->ldctl_value.bv_val, ctrls[i]->ldctl_value.bv_len );
+ *ptr = '\0';
+ }
+ rs->sr_ctrls[ i ] = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+
+ } else {
+ assert( rs->sr_ctrls != NULL );
+ rs->sr_ctrls = NULL;
+ }
+
+ ldap_controls_free( ctrls );
+ }
+
+ return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err );
+}
+
+/* return true if bound, false if failed */
+int
+ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+ int rc = 0;
+
+ assert( lcp != NULL );
+ assert( *lcp != NULL );
+
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+ if ( (*lcp)->lc_refcnt == 1 ) {
+ int binding = LDAP_BACK_CONN_BINDING( *lcp );
+
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_retry: retrying URI=\"%s\" DN=\"%s\"\n",
+ op->o_log_prefix, li->li_uri,
+ BER_BVISNULL( &(*lcp)->lc_bound_ndn ) ?
+ "" : (*lcp)->lc_bound_ndn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+
+ ldap_unbind_ext( (*lcp)->lc_ld, NULL, NULL );
+ (*lcp)->lc_ld = NULL;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( (*lcp) );
+
+ /* lc here must be the regular lc, reset and ready for init */
+ rc = ldap_back_prepare_conn( *lcp, op, rs, sendok );
+ if ( rc != LDAP_SUCCESS ) {
+ /* freeit, because lc_refcnt == 1 */
+ (*lcp)->lc_refcnt = 0;
+ (void)ldap_back_freeconn( li, *lcp, 0 );
+ *lcp = NULL;
+ rc = 0;
+
+ } else if ( ( sendok & LDAP_BACK_BINDING ) ) {
+ if ( binding ) {
+ LDAP_BACK_CONN_BINDING_SET( *lcp );
+ }
+ rc = 1;
+
+ } else {
+ rc = ldap_back_dobind_int( lcp, op, rs, sendok, 0, 0 );
+ if ( rc == 0 && *lcp != NULL ) {
+ /* freeit, because lc_refcnt == 1 */
+ (*lcp)->lc_refcnt = 0;
+ (void)ldap_back_freeconn( li, *lcp, 0 );
+ *lcp = NULL;
+ }
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_retry: conn %p refcnt=%u unable to retry.\n",
+ (void *)(*lcp), (*lcp)->lc_refcnt );
+
+ LDAP_BACK_CONN_TAINTED_SET( *lcp );
+ ldap_back_release_conn_lock( li, lcp, 0 );
+ assert( *lcp == NULL );
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to retry";
+ send_ldap_result( op, rs );
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ return rc;
+}
+
+static int
+ldap_back_is_proxy_authz( Operation *op, SlapReply *rs, ldap_back_send_t sendok,
+ struct berval *binddn, struct berval *bindcred )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+ struct berval ndn;
+ int dobind = 0;
+
+ if ( op->o_conn == NULL || op->o_do_not_cache ) {
+ goto done;
+ }
+
+ /* don't proxyAuthz if protocol is not LDAPv3 */
+ switch ( li->li_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ dobind = -1;
+ }
+ goto done;
+ }
+
+ /* safe default */
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+
+ if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( !( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )) {
+ if ( op->o_tag == LDAP_REQ_BIND && ( sendok & LDAP_BACK_SENDERR )) {
+ if ( !BER_BVISEMPTY( &ndn )) {
+ dobind = 0;
+ goto done;
+ }
+ } else if ( SLAP_IS_AUTHZ_BACKEND( op )) {
+ dobind = 0;
+ goto done;
+ }
+ }
+
+ switch ( li->li_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) {
+ if ( !BER_BVISNULL( &li->li_idassert_authcDN ) && !BER_BVISEMPTY( &li->li_idassert_authcDN ) )
+ {
+ *binddn = li->li_idassert_authcDN;
+ *bindcred = li->li_idassert_passwd;
+ dobind = 1;
+ }
+ }
+ break;
+
+ default:
+ /* NOTE: rootdn can always idassert */
+ if ( BER_BVISNULL( &ndn )
+ && li->li_idassert_authz == NULL
+ && !( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ dobind = -1;
+ }
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+ }
+
+ goto done;
+
+ } else if ( !be_isroot( op ) ) {
+ if ( li->li_idassert_passthru ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, li->li_idassert_passthru,
+ &authcDN, &authcDN );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ dobind = 0;
+ break;
+ }
+ }
+
+ if ( li->li_idassert_authz ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, li->li_idassert_authz,
+ &authcDN, &authcDN );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ dobind = -1;
+ }
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+ }
+
+ goto done;
+ }
+ }
+ }
+
+ *binddn = li->li_idassert_authcDN;
+ *bindcred = li->li_idassert_passwd;
+ dobind = 1;
+ break;
+ }
+
+done:;
+ return dobind;
+}
+
+static int
+ldap_back_proxy_authz_bind(
+ ldapconn_t *lc,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+ struct berval ndn;
+ int msgid;
+ int rc;
+
+ if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( li->li_idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults = NULL;
+ struct berval authzID = BER_BVNULL;
+ int freeauthz = 0;
+ LDAPControl **ctrlsp = NULL;
+ LDAPMessage *result = NULL;
+ const char *rmech = NULL;
+ const char *save_text = rs->sr_text;
+
+#ifdef SLAP_AUTH_DN
+ LDAPControl ctrl, *ctrls[2];
+ int msgid;
+#endif /* SLAP_AUTH_DN */
+
+ /* if SASL supports native authz, prepare for it */
+ if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+ ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ switch ( li->li_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ authzID = li->li_idassert_authzID;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+
+ case LDAP_BACK_IDASSERT_SELF:
+ if ( BER_BVISNULL( &ndn ) ) {
+ /* connection is not authc'd, so don't idassert */
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+ }
+ authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len;
+ authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx );
+ AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ),
+ ndn.bv_val, ndn.bv_len + 1 );
+ freeauthz = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if ( li->li_idassert_secprops != NULL ) {
+ rs->sr_err = ldap_set_option( lc->lc_ld,
+ LDAP_OPT_X_SASL_SECPROPS,
+ (void *)li->li_idassert_secprops );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ goto done;
+ }
+ }
+
+ defaults = lutil_sasl_defaults( lc->lc_ld,
+ li->li_idassert_sasl_mech.bv_val,
+ li->li_idassert_sasl_realm.bv_val,
+ li->li_idassert_authcID.bv_val,
+ li->li_idassert_passwd.bv_val,
+ authzID.bv_val );
+ if ( defaults == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+#ifdef SLAP_AUTH_DN
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) {
+ assert( BER_BVISNULL( binddn ) );
+
+ ctrl.ldctl_oid = LDAP_CONTROL_AUTHZID_REQUEST;
+ ctrl.ldctl_iscritical = 0;
+ BER_BVZERO( &ctrl.ldctl_value );
+ ctrls[0] = &ctrl;
+ ctrls[1] = NULL;
+ ctrlsp = ctrls;
+ }
+#endif /* SLAP_AUTH_DN */
+
+ do {
+ rs->sr_err = ldap_sasl_interactive_bind( lc->lc_ld, binddn->bv_val,
+ li->li_idassert_sasl_mech.bv_val,
+ ctrlsp, NULL, LDAP_SASL_QUIET, lutil_sasl_interact, defaults,
+ result, &rmech, &msgid );
+
+ if ( rs->sr_err != LDAP_SASL_BIND_IN_PROGRESS )
+ break;
+
+ ldap_msgfree( result );
+
+ if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1 || !result ) {
+ ldap_get_option( lc->lc_ld, LDAP_OPT_RESULT_CODE, (void*)&rs->sr_err );
+ ldap_get_option( lc->lc_ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&rs->sr_text );
+ break;
+ }
+ } while ( rs->sr_err == LDAP_SASL_BIND_IN_PROGRESS );
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+#ifdef SLAP_AUTH_DN
+ /* FIXME: right now, the only reason to check
+ * response controls is RFC 3829 authzid */
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_AUTHZID ) {
+ ctrlsp = NULL;
+ rc = ldap_parse_result( lc->lc_ld, result, NULL, NULL, NULL, NULL,
+ &ctrlsp, 0 );
+ if ( rc == LDAP_SUCCESS && ctrlsp ) {
+ LDAPControl *ctrl;
+
+ ctrl = ldap_control_find( LDAP_CONTROL_AUTHZID_RESPONSE,
+ ctrlsp, NULL );
+ if ( ctrl ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (authzid)\n",
+ op->o_log_prefix, ctrl->ldctl_value.bv_val );
+ if ( ctrl->ldctl_value.bv_len > STRLENOF("dn:") &&
+ strncasecmp( ctrl->ldctl_value.bv_val, "dn:", STRLENOF("dn:") ) == 0 )
+ {
+ struct berval bv;
+ bv.bv_val = &ctrl->ldctl_value.bv_val[STRLENOF("dn:")];
+ bv.bv_len = ctrl->ldctl_value.bv_len - STRLENOF("dn:");
+ ber_bvreplace( &lc->lc_bound_ndn, &bv );
+ }
+ }
+ }
+
+ ldap_controls_free( ctrlsp );
+
+ } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_WHOAMI ) {
+ struct berval *val = NULL;
+ rc = ldap_whoami_s( lc->lc_ld, &val, NULL, NULL );
+ if ( rc == LDAP_SUCCESS && val != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: ldap_back_proxy_authz_bind: authzID=\"%s\" (whoami)\n",
+ op->o_log_prefix, val->bv_val );
+ if ( val->bv_len > STRLENOF("dn:") &&
+ strncasecmp( val->bv_val, "dn:", STRLENOF("dn:") ) == 0 )
+ {
+ struct berval bv;
+ bv.bv_val = &val->bv_val[STRLENOF("dn:")];
+ bv.bv_len = val->bv_len - STRLENOF("dn:");
+ ber_bvreplace( &lc->lc_bound_ndn, &bv );
+ }
+ ber_bvfree( val );
+ }
+ }
+
+ if ( ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) &&
+ BER_BVISNULL( &lc->lc_bound_ndn ) )
+ {
+ /* all in all, we only need it to be non-null */
+ /* FIXME: should this be configurable? */
+ static struct berval bv = BER_BVC("cn=authzdn");
+ ber_bvreplace( &lc->lc_bound_ndn, &bv );
+ }
+#endif /* SLAP_AUTH_DN */
+ LDAP_BACK_CONN_ISBOUND_SET( lc );
+ break;
+
+ case LDAP_LOCAL_ERROR:
+ /* list client API error codes that require
+ * to taint the connection */
+ /* FIXME: should actually retry? */
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+
+ /* fallthru */
+
+ default:
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ break;
+ }
+
+ if ( save_text != rs->sr_text ) {
+ ldap_memfree( (char *)rs->sr_text );
+ rs->sr_text = save_text;
+ }
+
+ ldap_msgfree( result );
+
+ lutil_sasl_freedefs( defaults );
+ if ( freeauthz ) {
+ slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+ }
+
+ goto done;
+#endif /* HAVE_CYRUS_SASL */
+ }
+
+ switch ( li->li_idassert_authmethod ) {
+ case LDAP_AUTH_NONE:
+ /* FIXME: do we really need this? */
+ BER_BVSTR( binddn, "" );
+ BER_BVSTR( bindcred, "" );
+ /* fallthru */
+
+ case LDAP_AUTH_SIMPLE:
+ rs->sr_err = ldap_sasl_bind( lc->lc_ld,
+ binddn->bv_val, LDAP_SASL_SIMPLE,
+ bindcred, NULL, NULL, &msgid );
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ -1, ( sendok | LDAP_BACK_BINDING ) );
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_BIND ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+ break;
+
+ default:
+ /* unsupported! */
+ LDAP_BACK_CONN_ISBOUND_CLEAR( lc );
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ /* set rebind stuff in case of successful proxyAuthz bind,
+ * so that referral chasing is attempted using the right
+ * identity */
+ LDAP_BACK_CONN_ISBOUND_SET( lc );
+ if ( !BER_BVISNULL( binddn ) ) {
+ ber_bvreplace( &lc->lc_bound_ndn, binddn );
+ }
+
+ if ( !BER_BVISNULL( &lc->lc_cred ) ) {
+ memset( lc->lc_cred.bv_val, 0,
+ lc->lc_cred.bv_len );
+ }
+
+ if ( LDAP_BACK_SAVECRED( li ) ) {
+ if ( !BER_BVISNULL( bindcred ) ) {
+ ber_bvreplace( &lc->lc_cred, bindcred );
+ ldap_set_rebind_proc( lc->lc_ld, li->li_rebind_f, lc );
+ }
+
+ } else {
+ lc->lc_cred.bv_len = 0;
+ }
+ }
+
+done:;
+ return LDAP_BACK_CONN_ISBOUND( lc );
+}
+
+/*
+ * ldap_back_proxy_authz_ctrl() prepends a proxyAuthz control
+ * to existing server-side controls if required; if not,
+ * the existing server-side controls are placed in *pctrls.
+ * The caller, after using the controls in client API
+ * operations, if ( *pctrls != op->o_ctrls ), should
+ * free( (*pctrls)[ 0 ] ) and free( *pctrls ).
+ * The function returns success if the control could
+ * be added if required, or if it did nothing; in the future,
+ * it might return some error if it failed.
+ *
+ * if no bind took place yet, but the connection is bound
+ * and the "proxyauthzdn" is set, then bind as "proxyauthzdn"
+ * and explicitly add proxyAuthz the control to every operation
+ * with the dn bound to the connection as control value.
+ *
+ * If no server-side controls are defined for the operation,
+ * simply add the proxyAuthz control; otherwise, if the
+ * proxyAuthz control is not already set, add it as
+ * the first one
+ *
+ * FIXME: is controls order significant for security?
+ * ANSWER: controls ordering and interoperability
+ * must be indicated by the specs of each control; if none
+ * is specified, the order is irrelevant.
+ */
+int
+ldap_back_proxy_authz_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *bound_ndn,
+ int version,
+ slap_idassert_t *si,
+ LDAPControl *ctrl )
+{
+ slap_idassert_mode_t mode;
+ struct berval assertedID,
+ ndn;
+ int isroot = 0;
+
+ rs->sr_err = SLAP_CB_CONTINUE;
+
+ /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID,
+ * but if it is not set this test fails. We need a different
+ * means to detect if idassert is enabled */
+ if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) )
+ && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) )
+ && BER_BVISNULL( &si->si_bc.sb_saslmech ) )
+ {
+ goto done;
+ }
+
+ if ( !op->o_conn || op->o_do_not_cache || ( isroot = be_isroot( op ) ) ) {
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) {
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we do not want to perform proxyAuthz
+ * on behalf of the client, because this would
+ * be performed with "proxyauthzdn" privileges.
+ *
+ * This might actually be too strict, since
+ * the "proxyauthzdn" authzTo, and each entry's
+ * authzFrom attributes may be crafted
+ * to avoid unwanted proxyAuthz to take place.
+ */
+#if 0
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+#endif
+ goto done;
+ }
+
+ if ( !BER_BVISNULL( bound_ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) {
+ goto done;
+ }
+
+ } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) {
+ if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ /* already asserted in SASL via native authz */
+ goto done;
+ }
+
+ } else if ( si->si_authz && !isroot ) {
+ int rc;
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+ } else {
+ authcDN = ndn;
+ }
+ rc = slap_sasl_matches( op, si->si_authz,
+ &authcDN, &authcDN );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ /* ndn is not authorized
+ * to use idassert */
+ rs->sr_err = rc;
+ }
+ goto done;
+ }
+ }
+
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we can:
+ * 1) ignore the already set proxyAuthz control
+ * 2) leave it in place, and don't set ours
+ * 3) add both
+ * 4) reject the operation
+ *
+ * option (4) is very drastic
+ * option (3) will make the remote server reject
+ * the operation, thus being equivalent to (4)
+ * option (2) will likely break the idassert
+ * assumptions, so we cannot accept it;
+ * option (1) means that we are contradicting
+ * the client's request.
+ *
+ * I think (4) is the only correct choice.
+ */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+ }
+
+ if ( op->o_is_auth_check ) {
+ mode = LDAP_BACK_IDASSERT_NOASSERT;
+
+ } else {
+ mode = si->si_mode;
+ }
+
+ switch ( mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ /* original behavior:
+ * assert the client's identity */
+ case LDAP_BACK_IDASSERT_SELF:
+ assertedID = ndn;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ /* assert "anonymous" */
+ assertedID = slap_empty_bv;
+ break;
+
+ case LDAP_BACK_IDASSERT_NOASSERT:
+ /* don't assert; bind as proxyauthzdn */
+ goto done;
+
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ /* assert idassert DN */
+ assertedID = si->si_bc.sb_authzId;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ /* if we got here, "" is allowed to proxyAuthz */
+ if ( BER_BVISNULL( &assertedID ) ) {
+ assertedID = slap_empty_bv;
+ }
+
+ /* don't idassert the bound DN (ITS#4497) */
+ if ( dn_match( &assertedID, bound_ndn ) ) {
+ goto done;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL );
+
+ switch ( si->si_mode ) {
+ /* already in u:ID or dn:DN form */
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ /* needs the dn: prefix */
+ default:
+ ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" );
+ ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1,
+ op->o_tmpmemctx );
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ],
+ assertedID.bv_val, assertedID.bv_len + 1 );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+
+ /* Older versions of <draft-weltman-ldapv3-proxy> required
+ * to encode the value of the authzID (and called it proxyDN);
+ * this hack provides compatibility with those DSAs that
+ * implement it this way */
+ if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ struct berval authzID = ctrl->ldctl_value;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ tag = ber_printf( ber, "O", &authzID );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ } else if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ struct berval authzID = ctrl->ldctl_value,
+ tmp;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ if ( strncasecmp( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ) != 0 ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ tmp = authzID;
+ tmp.bv_val += STRLENOF( "dn:" );
+ tmp.bv_len -= STRLENOF( "dn:" );
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* apparently, Mozilla API encodes this
+ * as "SEQUENCE { LDAPDN }" */
+ tag = ber_printf( ber, "{O}", &tmp );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ;
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber2:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+done:;
+
+ return rs->sr_err;
+}
+
+/*
+ * Add controls;
+ *
+ * if any needs to be added, it is prepended to existing ones,
+ * in a newly allocated array. The companion function
+ * ldap_back_controls_free() must be used to restore the original
+ * status of op->o_ctrls.
+ */
+int
+ldap_back_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ ldapconn_t *lc,
+ LDAPControl ***pctrls )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ LDAPControl **ctrls = NULL;
+ /* set to the maximum number of controls this backend can add */
+ LDAPControl c[ 2 ] = { { 0 } };
+ int n = 0, i, j1 = 0, j2 = 0, skipped = 0;
+
+ *pctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* don't add controls if protocol is not LDAPv3 */
+ switch ( li->li_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __before__ existing ones here */
+
+ /* proxyAuthz for identity assertion */
+ switch ( ldap_back_proxy_authz_ctrl( op, rs, &lc->lc_bound_ndn,
+ li->li_version, &li->li_idassert, &c[ j1 ] ) )
+ {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j1++;
+ break;
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ /* FIXME: according to <draft-wahl-ldap-session>,
+ * the server should check if the control can be added
+ * based on the identity of the client and so */
+
+ /* session tracking */
+ if ( LDAP_BACK_ST_REQUEST( li ) ) {
+ switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j2++;
+ break;
+
+ default:
+ goto done;
+ }
+ }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ /* if nothing to do, just bail out */
+ if ( j1 == 0 && j2 == 0 ) {
+ goto done;
+ }
+
+ assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) );
+
+ if ( op->o_ctrls ) {
+ for ( n = 0; op->o_ctrls[ n ]; n++ )
+ /* just count ctrls */ ;
+ }
+
+ ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ),
+ op->o_tmpmemctx );
+ if ( j1 ) {
+ ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+ *ctrls[ 0 ] = c[ 0 ];
+ for ( i = 1; i < j1; i++ ) {
+ ctrls[ i ] = &ctrls[ 0 ][ i ];
+ *ctrls[ i ] = c[ i ];
+ }
+ }
+
+ i = 0;
+ if ( op->o_ctrls ) {
+ LDAPControl *proxyauthz = ldap_control_find(
+ LDAP_CONTROL_PROXY_AUTHZ, op->o_ctrls, NULL );
+
+ for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+ /* Only replace it if we generated one */
+ if ( j1 && proxyauthz && proxyauthz == op->o_ctrls[ i ] ) {
+ /* Frontend has already checked only one is present */
+ assert( skipped == 0 );
+ skipped++;
+ continue;
+ }
+ ctrls[ i + j1 - skipped ] = op->o_ctrls[ i ];
+ }
+ }
+
+ n += j1 - skipped;
+ if ( j2 ) {
+ ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+ *ctrls[ n ] = c[ j1 ];
+ for ( i = 1; i < j2; i++ ) {
+ ctrls[ n + i ] = &ctrls[ n ][ i ];
+ *ctrls[ n + i ] = c[ i ];
+ }
+ }
+
+ ctrls[ n + j2 ] = NULL;
+
+done:;
+ if ( ctrls == NULL ) {
+ ctrls = op->o_ctrls;
+ }
+
+ *pctrls = ctrls;
+
+ return rs->sr_err;
+}
+
+int
+ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls )
+{
+ LDAPControl **ctrls = *pctrls;
+
+ /* we assume that the controls added by the proxy come first,
+ * so as soon as we find op->o_ctrls[ 0 ] we can stop */
+ if ( ctrls && ctrls != op->o_ctrls ) {
+ int i = 0, n = 0, n_added;
+ LDAPControl *lower, *upper;
+
+ assert( ctrls[ 0 ] != NULL );
+
+ for ( n = 0; ctrls[ n ] != NULL; n++ )
+ /* count 'em */ ;
+
+ if ( op->o_ctrls ) {
+ for ( i = 0; op->o_ctrls[ i ] != NULL; i++ )
+ /* count 'em */ ;
+ }
+
+ n_added = n - i;
+ lower = (LDAPControl *)&ctrls[ n ];
+ upper = &lower[ n_added ];
+
+ for ( i = 0; ctrls[ i ] != NULL; i++ ) {
+ if ( ctrls[ i ] < lower || ctrls[ i ] >= upper ) {
+ /* original; don't touch */
+ continue;
+ }
+
+ if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) {
+ op->o_tmpfree( ctrls[ i ]->ldctl_value.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ op->o_tmpfree( ctrls, op->o_tmpmemctx );
+ }
+
+ *pctrls = NULL;
+
+ return 0;
+}
+
+int
+ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen )
+{
+ char tbuf[ SLAP_TEXT_BUFLEN ];
+ char *ptr = buf, *end = buf + buflen;
+ int len;
+
+ if ( ptr + sizeof("conn=") > end ) return -1;
+ ptr = lutil_strcopy( ptr, "conn=" );
+
+ len = ldap_back_connid2str( lc, ptr, (ber_len_t)(end - ptr) );
+ ptr += len;
+ if ( ptr >= end ) return -1;
+
+ if ( !BER_BVISNULL( &lc->lcb_local_ndn ) ) {
+ if ( ptr + sizeof(" DN=\"\"") + lc->lcb_local_ndn.bv_len > end ) return -1;
+ ptr = lutil_strcopy( ptr, " DN=\"" );
+ ptr = lutil_strncopy( ptr, lc->lcb_local_ndn.bv_val, lc->lcb_local_ndn.bv_len );
+ *ptr++ = '"';
+ }
+
+ if ( lc->lcb_create_time != 0 ) {
+ len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_create_time );
+ if ( ptr + sizeof(" created=") + len >= end ) return -1;
+ ptr = lutil_strcopy( ptr, " created=" );
+ ptr = lutil_strcopy( ptr, tbuf );
+ }
+
+ if ( lc->lcb_time != 0 ) {
+ len = snprintf( tbuf, sizeof(tbuf), "%ld", lc->lcb_time );
+ if ( ptr + sizeof(" modified=") + len >= end ) return -1;
+ ptr = lutil_strcopy( ptr, " modified=" );
+ ptr = lutil_strcopy( ptr, tbuf );
+ }
+
+ len = snprintf( tbuf, sizeof(tbuf), "%u", lc->lcb_refcnt );
+ if ( ptr + sizeof(" refcnt=") + len >= end ) return -1;
+ ptr = lutil_strcopy( ptr, " refcnt=" );
+ ptr = lutil_strcopy( ptr, tbuf );
+
+ return ptr - buf;
+}
+
+int
+ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen )
+{
+ static struct berval conns[] = {
+ BER_BVC("ROOTDN"),
+ BER_BVC("ROOTDN-TLS"),
+ BER_BVC("ANON"),
+ BER_BVC("ANON-TLS"),
+ BER_BVC("BIND"),
+ BER_BVC("BIND-TLS"),
+ BER_BVNULL
+ };
+
+ int len = 0;
+
+ if ( LDAP_BACK_PCONN_ISPRIV( (const ldapconn_t *)lc ) ) {
+ long cid;
+ struct berval *bv;
+
+ cid = (long)lc->lcb_conn;
+ assert( cid >= LDAP_BACK_PCONN_FIRST && cid < LDAP_BACK_PCONN_LAST );
+
+ bv = &conns[ cid ];
+
+ if ( bv->bv_len >= buflen ) {
+ return bv->bv_len + 1;
+ }
+
+ len = bv->bv_len;
+ lutil_strncopy( buf, bv->bv_val, bv->bv_len + 1 );
+
+ } else {
+ len = snprintf( buf, buflen, "%lu", lc->lcb_conn->c_connid );
+ }
+
+ return len;
+}
+
+void *
+ldap_back_conn_expire_fn( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ ldapinfo_t *li = (ldapinfo_t *)rtask->arg;
+ ldap_back_conn_prune( li );
+
+ return NULL;
+}
+
+/* Pick which expires first: connection TTL or idle timeout */
+static time_t
+ldap_back_conn_expire_time( ldapinfo_t *li, ldapconn_t *lc) {
+ if ( li->li_conn_ttl != 0 && li->li_idle_timeout != 0 ) {
+ return ( lc->lc_create_time + li->li_conn_ttl ) < ( lc->lc_time + li->li_idle_timeout ) ?
+ ( lc->lc_create_time + li->li_conn_ttl ) : ( lc->lc_time + li->li_idle_timeout );
+ } else if ( li->li_conn_ttl != 0 ) {
+ return lc->lc_create_time + li->li_conn_ttl;
+ } else if ( li->li_idle_timeout != 0 ) {
+ return lc->lc_time + li->li_idle_timeout;
+ }
+ return -1;
+}
+
+/*
+ * Iterate though connections and close those that are past the expiry time.
+ * Also calculate the time for next connection to expire.
+ *
+ * Note:
+ * When the client sends a request after remote connection is pruned, a new
+ * connection is created but bind cannot be replayed even if "rebind-as-user"
+ * was set to "yes". The client credentials are stored in ldapconn_t and lost
+ * when the connection is freed.
+ *
+ * LDAP_DISCONNECT is sent to signal the client that it needs to reconnect to
+ * the proxy and rebind itself (see "Bind is requested with DN but without
+ * credentials" in ldap_back_dobind_int()). Better implementation would not
+ * free ldapconn_t but instead just close the socket. This is not implemented
+ * currently as it is considerable work for what is assumed to be a corner case.
+ */
+static void
+ldap_back_conn_prune( ldapinfo_t *li )
+{
+ time_t now = slap_get_time();
+ time_t next_timeout = -1; /* -1 means uninitialized */
+ TAvlnode *edge;
+ int c;
+
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+ for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) {
+ ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ c ].lic_priv );
+
+ while ( lc ) {
+ ldapconn_t *next = LDAP_TAILQ_NEXT( lc, lc_q );
+
+ if ( !LDAP_BACK_CONN_TAINTED( lc ) ) {
+ time_t conn_expires = ldap_back_conn_expire_time( li, lc );
+
+ if ( now >= conn_expires ) {
+ if ( lc->lc_refcnt == 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: closing expired connection lc=%p\n",
+ lc );
+ ldap_back_freeconn( li, lc, 0 );
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: tainting expired connection lc=%p\n",
+ lc );
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+ }
+ } else if ( next_timeout == -1 || conn_expires < next_timeout ) {
+ /* next_timeout was not yet initialized or current connection expires sooner */
+ next_timeout = conn_expires;
+ }
+ }
+
+ lc = next;
+ }
+ }
+
+ edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapconn_t *lc = (ldapconn_t *)edge->avl_data;
+
+ if ( !LDAP_BACK_CONN_TAINTED( lc ) ) {
+ time_t conn_expires = ldap_back_conn_expire_time( li, lc );
+
+ if ( now >= conn_expires ) {
+ if ( lc->lc_refcnt == 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: closing expired connection lc=%p\n",
+ lc );
+ ldap_back_freeconn( li, lc, 0 );
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: tainting expired connection lc=%p\n",
+ lc );
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+ }
+ } else if ( next_timeout == -1 || conn_expires < next_timeout ) {
+ next_timeout = conn_expires;
+ }
+ }
+
+ edge = next;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ /* Reschedule for next timeout or cancel the task */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( next_timeout > 0 ) {
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+ }
+ li->li_conn_expire_task->interval.tv_sec = next_timeout - now;
+ ldap_pvt_runqueue_resched( &slapd_rq, li->li_conn_expire_task, 0 );
+
+ /*
+ * The thread that handles runqueue might have already processed all tasks
+ * before we insertered new task or rescheduled the existing task with new
+ * timeout period. Wake it up to ensure that the task will be picked up.
+ */
+ slap_wake_listener();
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n",
+ li->li_conn_expire_task->interval.tv_sec );
+ } else if ( next_timeout == -1 && li->li_conn_expire_task != NULL ) {
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task );
+ li->li_conn_expire_task = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return;
+}
+
+static void
+ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc ) {
+ /* Do nothing if timeouts are not set. */
+ if ( li->li_conn_ttl == 0 && li->li_idle_timeout == 0 ) {
+ return;
+ }
+
+ /*
+ * If connection expire task is not running, create it and schedule for
+ * timeout of this connection.
+ *
+ * If the task is already running, this connection cannot be next one
+ * to expire and therefore timeout does not need to be re-calculated.
+ */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( li->li_conn_expire_task == NULL ) {
+ li->li_conn_expire_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ ldap_back_conn_expire_time( li, lc ) - slap_get_time(),
+ ldap_back_conn_expire_fn, li, "ldap_back_conn_expire_fn",
+ "ldap_back_conn_expire_timer" );
+ slap_wake_listener();
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n",
+ li->li_conn_expire_task->interval.tv_sec );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return;
+}
diff --git a/servers/slapd/back-ldap/chain.c b/servers/slapd/back-ldap/chain.c
new file mode 100644
index 0000000..fece74d
--- /dev/null
+++ b/servers/slapd/back-ldap/chain.c
@@ -0,0 +1,2356 @@
+/* chain.c - chain LDAP operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software.
+ * This work was subsequently modified by Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "back-ldap.h"
+#include "slap-config.h"
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+#define SLAP_CHAINING_DEFAULT LDAP_CHAINING_PREFERRED
+#define SLAP_CH_RESOLVE_SHIFT SLAP_CONTROL_SHIFT
+#define SLAP_CH_RESOLVE_MASK (0x3 << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_RESOLVE_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_RESOLVE_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_RESOLVE_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_RESOLVE_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_RESOLVE_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_RESOLVE_SHIFT)
+#define SLAP_CH_CONTINUATION_SHIFT (SLAP_CH_RESOLVE_SHIFT + 2)
+#define SLAP_CH_CONTINUATION_MASK (0x3 << SLAP_CH_CONTINUATION_SHIFT)
+#define SLAP_CH_CONTINUATION_CHAINING_PREFERRED (LDAP_CHAINING_PREFERRED << SLAP_CH_CONTINUATION_SHIFT)
+#define SLAP_CH_CONTINUATION_CHAINING_REQUIRED (LDAP_CHAINING_REQUIRED << SLAP_CH_CONTINUATION_SHIFT)
+#define SLAP_CH_CONTINUATION_REFERRALS_PREFERRED (LDAP_REFERRALS_PREFERRED << SLAP_CH_CONTINUATION_SHIFT)
+#define SLAP_CH_CONTINUATION_REFERRALS_REQUIRED (LDAP_REFERRALS_REQUIRED << SLAP_CH_CONTINUATION_SHIFT)
+#define SLAP_CH_CONTINUATION_DEFAULT (SLAP_CHAINING_DEFAULT << SLAP_CH_CONTINUATION_SHIFT)
+
+#define o_chaining o_ctrlflag[sc_chainingBehavior]
+#define get_chaining(op) ((op)->o_chaining & SLAP_CONTROL_MASK)
+#define get_chainingBehavior(op) ((op)->o_chaining & (SLAP_CH_RESOLVE_MASK|SLAP_CH_CONTINUATION_MASK))
+#define get_resolveBehavior(op) ((op)->o_chaining & SLAP_CH_RESOLVE_MASK)
+#define get_continuationBehavior(op) ((op)->o_chaining & SLAP_CH_CONTINUATION_MASK)
+
+static int sc_chainingBehavior;
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+typedef enum {
+ LDAP_CH_NONE = 0,
+ LDAP_CH_RES,
+ LDAP_CH_ERR
+} ldap_chain_status_t;
+
+static BackendInfo *lback;
+
+typedef struct ldap_chain_t {
+ /*
+ * A "template" ldapinfo_t gets all common configuration items;
+ * then, for each configured URI, an entry is created in the tree;
+ * all the specific configuration items get in the current URI
+ * structure.
+ *
+ * Then, for each referral, extract the URI and lookup the
+ * related structure. If configured to do so, allow URIs
+ * not found in the structure to create a temporary one
+ * that chains anonymously; maybe it can also be added to
+ * the tree? Should be all configurable.
+ */
+
+ /* "common" configuration info (anything occurring before an "uri") */
+ ldapinfo_t *lc_common_li;
+
+ /* current configuration info */
+ ldapinfo_t *lc_cfg_li;
+
+ /* tree of configured[/generated?] "uri" info */
+ ldap_avl_info_t lc_lai;
+
+ /* max depth in nested referrals chaining */
+ int lc_max_depth;
+
+ unsigned lc_flags;
+#define LDAP_CHAIN_F_NONE (0x00U)
+#define LDAP_CHAIN_F_CHAINING (0x01U)
+#define LDAP_CHAIN_F_CACHE_URI (0x02U)
+#define LDAP_CHAIN_F_RETURN_ERR (0x04U)
+
+#define LDAP_CHAIN_ISSET(lc, f) ( ( (lc)->lc_flags & (f) ) == (f) )
+#define LDAP_CHAIN_CHAINING( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CHAINING )
+#define LDAP_CHAIN_CACHE_URI( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_CACHE_URI )
+#define LDAP_CHAIN_RETURN_ERR( lc ) LDAP_CHAIN_ISSET( (lc), LDAP_CHAIN_F_RETURN_ERR )
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ LDAPControl lc_chaining_ctrl;
+ char lc_chaining_ctrlflag;
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+} ldap_chain_t;
+
+static int ldap_chain_db_init_common( BackendDB *be );
+static int ldap_chain_db_init_one( BackendDB *be );
+static int ldap_chain_db_open_one( BackendDB *be );
+#define ldap_chain_db_close_one(be) (0)
+#define ldap_chain_db_destroy_one(be, rs) (lback)->bi_db_destroy( (be), (rs) )
+
+typedef struct ldap_chain_cb_t {
+ ldap_chain_status_t lb_status;
+ ldap_chain_t *lb_lc;
+ slap_operation_t lb_op_type;
+ char *lb_text;
+ int lb_depth;
+} ldap_chain_cb_t;
+
+static int
+ldap_chain_op(
+ Operation *op,
+ SlapReply *rs,
+ slap_operation_t op_type,
+ BerVarray ref,
+ int depth );
+
+static int
+ldap_chain_search(
+ Operation *op,
+ SlapReply *rs,
+ BerVarray ref,
+ int depth );
+
+static slap_overinst ldapchain;
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+static int
+chaining_control_add(
+ ldap_chain_t *lc,
+ Operation *op,
+ LDAPControl ***oldctrlsp )
+{
+ LDAPControl **ctrls = NULL;
+ int c = 0;
+
+ *oldctrlsp = op->o_ctrls;
+
+ /* default chaining control not defined */
+ if ( !LDAP_CHAIN_CHAINING( lc ) ) {
+ return 0;
+ }
+
+ /* already present */
+ if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
+ return 0;
+ }
+
+ /* FIXME: check other incompatibilities */
+
+ /* add to other controls */
+ if ( op->o_ctrls ) {
+ for ( c = 0; op->o_ctrls[ c ]; c++ )
+ /* count them */ ;
+ }
+
+ ctrls = ch_calloc( sizeof( LDAPControl *), c + 2 );
+ ctrls[ 0 ] = &lc->lc_chaining_ctrl;
+ if ( op->o_ctrls ) {
+ for ( c = 0; op->o_ctrls[ c ]; c++ ) {
+ ctrls[ c + 1 ] = op->o_ctrls[ c ];
+ }
+ }
+ ctrls[ c + 1 ] = NULL;
+
+ op->o_ctrls = ctrls;
+
+ op->o_chaining = lc->lc_chaining_ctrlflag;
+
+ return 0;
+}
+
+static int
+chaining_control_remove(
+ Operation *op,
+ LDAPControl ***oldctrlsp )
+{
+ LDAPControl **oldctrls = *oldctrlsp;
+
+ /* we assume that the first control is the chaining control
+ * added by the chain overlay, so it's the only one we explicitly
+ * free */
+ if ( op->o_ctrls != oldctrls ) {
+ if ( op->o_ctrls != NULL ) {
+ assert( op->o_ctrls[ 0 ] != NULL );
+
+ free( op->o_ctrls );
+
+ op->o_chaining = 0;
+ }
+ op->o_ctrls = oldctrls;
+ }
+
+ *oldctrlsp = NULL;
+
+ return 0;
+}
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+static int
+ldap_chain_uri_cmp( const void *c1, const void *c2 )
+{
+ const ldapinfo_t *li1 = (const ldapinfo_t *)c1;
+ const ldapinfo_t *li2 = (const ldapinfo_t *)c2;
+
+ assert( li1->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) );
+
+ assert( li2->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) );
+
+ return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] );
+}
+
+static int
+ldap_chain_uri_dup( void *c1, void *c2 )
+{
+ ldapinfo_t *li1 = (ldapinfo_t *)c1;
+ ldapinfo_t *li2 = (ldapinfo_t *)c2;
+
+ assert( li1->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) );
+
+ assert( li2->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) );
+
+ if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Search specific response that strips entryDN from entries
+ */
+static int
+ldap_chain_cb_search_response( Operation *op, SlapReply *rs )
+{
+ ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private;
+
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ /* if in error, don't proceed any further */
+ if ( lb->lb_status == LDAP_CH_ERR ) {
+ return 0;
+ }
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ Attribute **ap = &rs->sr_entry->e_attrs;
+
+ for ( ; *ap != NULL; ap = &(*ap)->a_next ) {
+ /* will be generated later by frontend
+ * (a cleaner solution would be that
+ * the frontend checks if it already exists */
+ if ( ad_cmp( (*ap)->a_desc, slap_schema.si_ad_entryDN ) == 0 )
+ {
+ Attribute *a = *ap;
+
+ *ap = (*ap)->a_next;
+ attr_free( a );
+
+ /* there SHOULD be one only! */
+ break;
+ }
+ }
+
+ /* tell the frontend not to add generated
+ * operational attributes */
+ rs->sr_flags |= REP_NO_OPERATIONALS;
+
+ return SLAP_CB_CONTINUE;
+
+ } else if ( rs->sr_type == REP_SEARCHREF ) {
+ /* if we get it here, it means the library was unable
+ * to chase the referral... */
+ if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) {
+ rs->sr_err = ldap_chain_search( op, rs, rs->sr_ref, lb->lb_depth );
+ }
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
+ switch ( get_continuationBehavior( op ) ) {
+ case SLAP_CH_RESOLVE_CHAINING_REQUIRED:
+ lb->lb_status = LDAP_CH_ERR;
+ return rs->sr_err = LDAP_X_CANNOT_CHAIN;
+
+ default:
+ break;
+ }
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ return SLAP_CB_CONTINUE;
+
+ } else if ( rs->sr_type == REP_RESULT ) {
+ if ( rs->sr_err == LDAP_REFERRAL
+ && lb->lb_depth < lb->lb_lc->lc_max_depth
+ && rs->sr_ref != NULL )
+ {
+ rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_type,
+ rs->sr_ref, lb->lb_depth );
+ }
+
+ /* back-ldap tried to send result */
+ lb->lb_status = LDAP_CH_RES;
+ /* don't let other callbacks run, this isn't
+ * the real result for this op.
+ */
+ op->o_callback->sc_next = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Dummy response that simply traces if back-ldap tried to send
+ * anything to the client
+ */
+static int
+ldap_chain_cb_response( Operation *op, SlapReply *rs )
+{
+ ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private;
+
+ /* if in error, don't proceed any further */
+ if ( lb->lb_status == LDAP_CH_ERR ) {
+ return 0;
+ }
+
+ if ( rs->sr_type == REP_RESULT ) {
+retry:;
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ if ( op->o_tag != LDAP_REQ_COMPARE ) {
+ return rs->sr_err;
+ }
+ /* fallthru */
+
+ case LDAP_SUCCESS:
+ lb->lb_status = LDAP_CH_RES;
+ break;
+
+ case LDAP_REFERRAL:
+ if ( lb->lb_depth < lb->lb_lc->lc_max_depth && rs->sr_ref != NULL ) {
+ rs->sr_err = ldap_chain_op( op, rs, lb->lb_op_type,
+ rs->sr_ref, lb->lb_depth );
+ goto retry;
+ }
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ if ( get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
+ switch ( get_continuationBehavior( op ) ) {
+ case SLAP_CH_RESOLVE_CHAINING_REQUIRED:
+ lb->lb_status = LDAP_CH_ERR;
+ return rs->sr_err = LDAP_X_CANNOT_CHAIN;
+
+ default:
+ break;
+ }
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ break;
+
+ default:
+ /* remember the text before it's freed in ldap_back_op_result */
+ if ( lb->lb_text ) {
+ ber_memfree_x( lb->lb_text, op->o_tmpmemctx );
+ }
+ lb->lb_text = ber_strdup_x( rs->sr_text, op->o_tmpmemctx );
+ return rs->sr_err;
+ }
+
+ } else if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH )
+ {
+ /* strip the entryDN attribute, but keep returning results */
+ (void)ldap_chain_cb_search_response( op, rs );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ldap_chain_op(
+ Operation *op,
+ SlapReply *rs,
+ slap_operation_t op_type,
+ BerVarray ref,
+ int depth )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ struct berval odn = op->o_req_dn,
+ ondn = op->o_req_ndn;
+ ldapinfo_t li = { 0 }, *lip = NULL;
+ struct berval bvuri[ 2 ] = { { 0 } };
+
+ /* NOTE: returned if ref is empty... */
+ int rc = LDAP_OTHER,
+ first_rc;
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ LDAPControl **ctrls = NULL;
+
+ (void)chaining_control_add( lc, op, &ctrls );
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ li.li_bvuri = bvuri;
+ first_rc = -1;
+ for ( ; !BER_BVISNULL( ref ); ref++ ) {
+ SlapReply rs2 = { 0 };
+ LDAPURLDesc *srv = NULL;
+ req_search_s save_oq_search = op->oq_search,
+ tmp_oq_search = { 0 };
+ struct berval dn = BER_BVNULL,
+ pdn = odn,
+ ndn = ondn;
+ char *filter = NULL;
+ int temporary = 0;
+ int free_dn = 0;
+
+ /* We're setting the URI of the first referral;
+ * what if there are more?
+
+Document: RFC 4511
+
+4.1.10. Referral
+ ...
+ If the client wishes to progress the operation, it MUST follow the
+ referral by contacting one of the supported services. If multiple
+ URIs are present, the client assumes that any supported URI may be
+ used to progress the operation.
+
+ * so we actually need to follow exactly one,
+ * and we can assume any is fine.
+ */
+
+ /* parse reference and use
+ * proto://[host][:port]/ only */
+ rc = ldap_url_parse_ext( ref->bv_val, &srv, LDAP_PVT_URL_PARSE_NONE );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: unable to parse ref=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val );
+
+ /* try next */
+ rc = LDAP_OTHER;
+ continue;
+ }
+
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) {
+ /* RFC 4511: if scope is present, use it */
+ tmp_oq_search.rs_scope = srv->lud_scope;
+
+ } else {
+ /* RFC 4511: if scope is absent, use original */
+ tmp_oq_search.rs_scope = op->ors_scope;
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+ srv->lud_scope = LDAP_SCOPE_DEFAULT;
+ dn.bv_val = srv->lud_dn;
+ filter = srv->lud_filter;
+
+ /* normalize DN */
+ if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) {
+ if ( srv->lud_dn == NULL ) {
+ srv->lud_dn = "";
+ }
+
+ } else {
+ ber_str2bv( srv->lud_dn, 0, 0, &dn );
+ rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx );
+ if ( rc == LDAP_SUCCESS ) {
+ /* remove DN essentially because later on
+ * ldap_initialize() will parse the URL
+ * as a comma-separated URL list */
+ srv->lud_dn = "";
+ free_dn = 1;
+ }
+ }
+
+ /* prepare filter */
+ if ( rc == LDAP_SUCCESS && op->o_tag == LDAP_REQ_SEARCH ) {
+ /* filter */
+ if ( srv->lud_filter != NULL
+ && srv->lud_filter[0] != '\0'
+ && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 )
+ {
+ /* RFC 4511: if filter is present, use it;
+ * otherwise, use original */
+ tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter );
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": unable to parse filter=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, srv->lud_filter );
+ rc = LDAP_OTHER;
+ }
+ }
+ }
+ srv->lud_filter = NULL;
+
+ if ( rc == LDAP_SUCCESS ) {
+ li.li_uri = ldap_url_desc2str( srv );
+ }
+
+ srv->lud_dn = dn.bv_val;
+ srv->lud_filter = filter;
+ ldap_free_urldesc( srv );
+
+ if ( rc != LDAP_SUCCESS ) {
+ /* try next */
+ rc = LDAP_OTHER;
+ continue;
+ }
+
+ if ( li.li_uri == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to reconstruct URI\n",
+ op->o_log_prefix, ref->bv_val );
+
+ /* try next */
+ rc = LDAP_OTHER;
+ goto further_cleanup;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" -> \"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+
+ op->o_req_dn = pdn;
+ op->o_req_ndn = ndn;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ op->ors_scope = tmp_oq_search.rs_scope;
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ op->ors_filter = tmp_oq_search.rs_filter;
+ op->ors_filterstr = tmp_oq_search.rs_filterstr;
+ }
+ }
+
+ ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] );
+
+ /* Searches for a ldapinfo in the avl tree */
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ lip = (ldapinfo_t *)ldap_tavl_find( lc->lc_lai.lai_tree,
+ (caddr_t)&li, ldap_chain_uri_cmp );
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+
+ if ( lip != NULL ) {
+ op->o_bd->be_private = (void *)lip;
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\": URI=\"%s\" found in cache\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+
+ } else {
+ rc = ldap_chain_db_init_one( op->o_bd );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+ goto cleanup;
+ }
+ lip = (ldapinfo_t *)op->o_bd->be_private;
+ lip->li_uri = li.li_uri;
+ lip->li_bvuri = bvuri;
+ rc = ldap_chain_db_open_one( op->o_bd );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+ lip->li_uri = NULL;
+ lip->li_bvuri = NULL;
+ (void)ldap_chain_db_destroy_one( op->o_bd, NULL);
+ goto cleanup;
+ }
+
+ if ( LDAP_CHAIN_CACHE_URI( lc ) ) {
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ if ( ldap_tavl_insert( &lc->lc_lai.lai_tree,
+ (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
+ {
+ /* someone just inserted another;
+ * don't bother, use this and then
+ * just free it */
+ temporary = 1;
+ }
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+
+ } else {
+ temporary = 1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_op: ref=\"%s\" %s\n",
+ op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" );
+ }
+
+ lb->lb_op_type = op_type;
+ lb->lb_depth = depth + 1;
+
+ rc = (&lback->bi_op_bind)[ op_type ]( op, &rs2 );
+
+ /* note the first error */
+ if ( first_rc == -1 ) {
+ first_rc = rc;
+ }
+
+cleanup:;
+ ldap_memfree( li.li_uri );
+ li.li_uri = NULL;
+
+ if ( temporary ) {
+ lip->li_uri = NULL;
+ lip->li_bvuri = NULL;
+ (void)ldap_chain_db_close_one( op->o_bd );
+ (void)ldap_chain_db_destroy_one( op->o_bd, NULL );
+ }
+
+further_cleanup:;
+ if ( op->o_req_dn.bv_val == pdn.bv_val ) {
+ op->o_req_dn = odn;
+ op->o_req_ndn = ondn;
+ }
+
+ if ( free_dn ) {
+ op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ filter_free_x( op, tmp_oq_search.rs_filter, 1 );
+ }
+
+ if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) {
+ slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx );
+ }
+
+ op->oq_search = save_oq_search;
+ }
+
+ if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) {
+ *rs = rs2;
+ break;
+ }
+
+ rc = rs2.sr_err;
+ }
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ (void)chaining_control_remove( op, &ctrls );
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ if ( rc != LDAP_SUCCESS && first_rc > 0 ) {
+ rc = first_rc;
+ }
+
+ return rc;
+}
+
+static int
+ldap_chain_search(
+ Operation *op,
+ SlapReply *rs,
+ BerVarray ref,
+ int depth )
+
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ ldap_chain_cb_t *lb = (ldap_chain_cb_t *)op->o_callback->sc_private;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ ldapinfo_t li = { 0 }, *lip = NULL;
+ struct berval bvuri[ 2 ] = { { 0 } };
+
+ struct berval odn = op->o_req_dn,
+ ondn = op->o_req_ndn;
+ Entry *save_entry = rs->sr_entry;
+ slap_mask_t save_flags = rs->sr_flags;
+
+ int rc = LDAP_OTHER,
+ first_rc = -1;
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ LDAPControl **ctrls = NULL;
+
+ (void)chaining_control_add( lc, op, &ctrls );
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ assert( rs->sr_type == REP_SEARCHREF );
+
+ rs->sr_type = REP_SEARCH;
+
+ /* if we parse the URI then by no means
+ * we can cache stuff or reuse connections,
+ * because in back-ldap there's no caching
+ * based on the URI value, which is supposed
+ * to be set once for all (correct?) */
+ li.li_bvuri = bvuri;
+ for ( ; !BER_BVISNULL( &ref[0] ); ref++ ) {
+ SlapReply rs2 = { REP_RESULT };
+ LDAPURLDesc *srv;
+ req_search_s save_oq_search = op->oq_search,
+ tmp_oq_search = { 0 };
+ struct berval dn,
+ pdn = op->o_req_dn,
+ ndn = op->o_req_ndn;
+ char *filter = NULL;
+ int temporary = 0;
+ int free_dn = 0;
+
+ /* parse reference and use
+ * proto://[host][:port]/ only */
+ rc = ldap_url_parse_ext( ref[0].bv_val, &srv, LDAP_PVT_URL_PARSE_NONE );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: unable to parse ref=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val );
+
+ /* try next */
+ rs->sr_err = LDAP_OTHER;
+ continue;
+ }
+
+ if ( srv->lud_scope != LDAP_SCOPE_DEFAULT ) {
+ /* RFC 4511: if scope is present, use it */
+ tmp_oq_search.rs_scope = srv->lud_scope;
+
+ } else {
+ /* RFC 4511: if scope is absent, use original */
+ /* Section 4.5.3: if scope is onelevel, use base */
+ if ( op->ors_scope == LDAP_SCOPE_ONELEVEL )
+ tmp_oq_search.rs_scope = LDAP_SCOPE_BASE;
+ else
+ tmp_oq_search.rs_scope = op->ors_scope;
+ }
+
+ rc = LDAP_SUCCESS;
+ srv->lud_scope = LDAP_SCOPE_DEFAULT;
+ dn.bv_val = srv->lud_dn;
+ filter = srv->lud_filter;
+
+ /* normalize DN */
+ if ( srv->lud_dn == NULL || srv->lud_dn[0] == '\0' ) {
+ if ( srv->lud_dn == NULL ) {
+ srv->lud_dn = "";
+ }
+
+ if ( save_entry != NULL ) {
+ /* use the "right" DN, if available */
+ pdn = save_entry->e_name;
+ ndn = save_entry->e_nname;
+ } /* else leave the original req DN in place, if any RFC 4511 */
+
+ } else {
+ /* RFC 4511: if DN is present, use it */
+ ber_str2bv( srv->lud_dn, 0, 0, &dn );
+ rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx );
+ if ( rc == LDAP_SUCCESS ) {
+ /* remove DN essentially because later on
+ * ldap_initialize() will parse the URL
+ * as a comma-separated URL list */
+ srv->lud_dn = "";
+ free_dn = 1;
+ }
+ }
+
+ /* prepare filter */
+ if ( rc == LDAP_SUCCESS ) {
+ /* filter */
+ if ( srv->lud_filter != NULL
+ && srv->lud_filter[0] != '\0'
+ && strcasecmp( srv->lud_filter, "(objectClass=*)" ) != 0 )
+ {
+ /* RFC 4511: if filter is present, use it;
+ * otherwise, use original */
+ tmp_oq_search.rs_filter = str2filter_x( op, srv->lud_filter );
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ filter2bv_x( op, tmp_oq_search.rs_filter, &tmp_oq_search.rs_filterstr );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": unable to parse filter=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, srv->lud_filter );
+ rc = LDAP_OTHER;
+ }
+ }
+ }
+ srv->lud_filter = NULL;
+
+ if ( rc == LDAP_SUCCESS ) {
+ li.li_uri = ldap_url_desc2str( srv );
+ }
+
+ srv->lud_dn = dn.bv_val;
+ srv->lud_filter = filter;
+ ldap_free_urldesc( srv );
+
+ if ( rc != LDAP_SUCCESS || li.li_uri == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to reconstruct URI\n",
+ op->o_log_prefix, ref->bv_val );
+
+ /* try next */
+ rc = LDAP_OTHER;
+ goto further_cleanup;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" -> \"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+
+ op->o_req_dn = pdn;
+ op->o_req_ndn = ndn;
+ op->ors_scope = tmp_oq_search.rs_scope;
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ op->ors_filter = tmp_oq_search.rs_filter;
+ op->ors_filterstr = tmp_oq_search.rs_filterstr;
+ }
+
+ ber_str2bv( li.li_uri, 0, 0, &li.li_bvuri[ 0 ] );
+
+ /* Searches for a ldapinfo in the avl tree */
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ lip = (ldapinfo_t *)ldap_tavl_find( lc->lc_lai.lai_tree,
+ (caddr_t)&li, ldap_chain_uri_cmp );
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+
+ if ( lip != NULL ) {
+ op->o_bd->be_private = (void *)lip;
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\": URI=\"%s\" found in cache\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+
+ } else {
+ /* if none is found, create a temporary... */
+ rc = ldap_chain_db_init_one( op->o_bd );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to init back-ldap for URI=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+ goto cleanup;
+ }
+ lip = (ldapinfo_t *)op->o_bd->be_private;
+ lip->li_uri = li.li_uri;
+ lip->li_bvuri = bvuri;
+ rc = ldap_chain_db_open_one( op->o_bd );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" unable to open back-ldap for URI=\"%s\"\n",
+ op->o_log_prefix, ref->bv_val, li.li_uri );
+ lip->li_uri = NULL;
+ lip->li_bvuri = NULL;
+ (void)ldap_chain_db_destroy_one( op->o_bd, NULL );
+ goto cleanup;
+ }
+
+ if ( LDAP_CHAIN_CACHE_URI( lc ) ) {
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ if ( ldap_tavl_insert( &lc->lc_lai.lai_tree,
+ (caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
+ {
+ /* someone just inserted another;
+ * don't bother, use this and then
+ * just free it */
+ temporary = 1;
+ }
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+
+ } else {
+ temporary = 1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s ldap_chain_search: ref=\"%s\" %s\n",
+ op->o_log_prefix, ref->bv_val, temporary ? "temporary" : "caching" );
+ }
+
+ lb->lb_op_type = op_search;
+ lb->lb_depth = depth + 1;
+
+ /* FIXME: should we also copy filter and scope?
+ * according to RFC3296, no */
+ rc = lback->bi_op_search( op, &rs2 );
+ if ( first_rc == -1 ) {
+ first_rc = rc;
+ }
+
+cleanup:;
+ ldap_memfree( li.li_uri );
+ li.li_uri = NULL;
+
+ if ( temporary ) {
+ lip->li_uri = NULL;
+ lip->li_bvuri = NULL;
+ (void)ldap_chain_db_close_one( op->o_bd );
+ (void)ldap_chain_db_destroy_one( op->o_bd, NULL );
+ }
+
+further_cleanup:;
+ if ( op->o_req_dn.bv_val == pdn.bv_val ) {
+ op->o_req_dn = odn;
+ op->o_req_ndn = ondn;
+ }
+
+ if ( free_dn ) {
+ op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( tmp_oq_search.rs_filter != NULL ) {
+ filter_free_x( op, tmp_oq_search.rs_filter, 1 );
+ }
+
+ if ( !BER_BVISNULL( &tmp_oq_search.rs_filterstr ) ) {
+ slap_sl_free( tmp_oq_search.rs_filterstr.bv_val, op->o_tmpmemctx );
+ }
+
+ op->oq_search = save_oq_search;
+
+ if ( rc == LDAP_SUCCESS && rs2.sr_err == LDAP_SUCCESS ) {
+ *rs = rs2;
+ break;
+ }
+
+ rc = rs2.sr_err;
+ }
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ (void)chaining_control_remove( op, &ctrls );
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ rs->sr_type = REP_SEARCHREF;
+ rs->sr_entry = save_entry;
+ rs->sr_flags = save_flags;
+
+ if ( rc != LDAP_SUCCESS ) {
+ /* couldn't chase any of the referrals */
+ if ( first_rc != -1 ) {
+ rc = first_rc;
+
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+
+ return rc;
+}
+
+static int
+ldap_chain_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ BackendDB db, *bd = op->o_bd;
+ ldap_chain_cb_t lb = { 0 };
+ slap_callback *sc = op->o_callback,
+ sc2 = { 0 };
+ int rc = 0;
+ const char *text = NULL;
+ const char *matched;
+ BerVarray ref;
+ slap_mask_t flags = 0;
+ struct berval ndn = op->o_ndn;
+
+ int sr_err = rs->sr_err;
+ slap_reply_t sr_type = rs->sr_type;
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ slap_mask_t chain_mask = 0;
+ ber_len_t chain_shift = 0;
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ if ( rs->sr_err != LDAP_REFERRAL && rs->sr_type != REP_SEARCHREF ) {
+ return SLAP_CB_CONTINUE;
+ }
+ if ( !rs->sr_ref ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ if ( rs->sr_err == LDAP_REFERRAL && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
+ switch ( get_resolveBehavior( op ) ) {
+ case SLAP_CH_RESOLVE_REFERRALS_PREFERRED:
+ case SLAP_CH_RESOLVE_REFERRALS_REQUIRED:
+ return SLAP_CB_CONTINUE;
+
+ default:
+ chain_mask = SLAP_CH_RESOLVE_MASK;
+ chain_shift = SLAP_CH_RESOLVE_SHIFT;
+ break;
+ }
+
+ } else if ( rs->sr_type == REP_SEARCHREF && get_chaining( op ) > SLAP_CONTROL_IGNORED ) {
+ switch ( get_continuationBehavior( op ) ) {
+ case SLAP_CH_CONTINUATION_REFERRALS_PREFERRED:
+ case SLAP_CH_CONTINUATION_REFERRALS_REQUIRED:
+ return SLAP_CB_CONTINUE;
+
+ default:
+ chain_mask = SLAP_CH_CONTINUATION_MASK;
+ chain_shift = SLAP_CH_CONTINUATION_SHIFT;
+ break;
+ }
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ /*
+ * TODO: add checks on who/when chain operations; e.g.:
+ * a) what identities are authorized
+ * b) what request DN (e.g. only chain requests rooted at <DN>)
+ * c) what referral URIs
+ * d) what protocol scheme (e.g. only ldaps://)
+ * e) what ssf
+ */
+
+ db = *op->o_bd;
+ SLAP_DBFLAGS( &db ) &= ~SLAP_DBFLAG_MONITORING;
+ op->o_bd = &db;
+
+ text = rs->sr_text;
+ rs->sr_text = NULL;
+ matched = rs->sr_matched;
+ rs->sr_matched = NULL;
+ ref = rs->sr_ref;
+ rs->sr_ref = NULL;
+
+ flags = rs->sr_flags & (REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED);
+ rs->sr_flags &= ~flags;
+
+ /* we need this to know if back-ldap returned any result */
+ lb.lb_lc = lc;
+ sc2.sc_next = sc;
+ sc2.sc_private = &lb;
+ sc2.sc_response = ldap_chain_cb_response;
+ op->o_callback = &sc2;
+
+ /* Chaining can be performed by a privileged user on behalf
+ * of normal users, using the ProxyAuthz control, by exploiting
+ * the identity assertion feature of back-ldap; see idassert-*
+ * directives in slapd-ldap(5).
+ *
+ * FIXME: the idassert-authcDN is one, will it be fine regardless
+ * of the URI we obtain from the referral?
+ */
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND: {
+ struct berval rndn = op->o_req_ndn;
+ Connection *conn = op->o_conn;
+
+ /* FIXME: can we really get a referral for binds? */
+ op->o_req_ndn = slap_empty_bv;
+ op->o_conn = NULL;
+ rc = ldap_chain_op( op, rs, op_bind, ref, 0 );
+ op->o_req_ndn = rndn;
+ op->o_conn = conn;
+ }
+ break;
+
+ case LDAP_REQ_ADD:
+ rc = ldap_chain_op( op, rs, op_add, ref, 0 );
+ break;
+
+ case LDAP_REQ_DELETE:
+ rc = ldap_chain_op( op, rs, op_delete, ref, 0 );
+ break;
+
+ case LDAP_REQ_MODRDN:
+ rc = ldap_chain_op( op, rs, op_modrdn, ref, 0 );
+ break;
+
+ case LDAP_REQ_MODIFY:
+ rc = ldap_chain_op( op, rs, op_modify, ref, 0 );
+ break;
+
+ case LDAP_REQ_COMPARE:
+ rc = ldap_chain_op( op, rs, op_compare, ref, 0 );
+ if ( rs->sr_err == LDAP_COMPARE_TRUE || rs->sr_err == LDAP_COMPARE_FALSE ) {
+ rc = LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_REQ_SEARCH:
+ if ( rs->sr_type == REP_SEARCHREF ) {
+ sc2.sc_response = ldap_chain_cb_search_response;
+ rc = ldap_chain_search( op, rs, ref, 0 );
+
+ } else {
+ /* we might get here before any database actually
+ * performed a search; in those cases, we need
+ * to check limits, to make sure safe defaults
+ * are in place */
+ if ( op->ors_limit != NULL || limits_check( op, rs ) == 0 ) {
+ rc = ldap_chain_op( op, rs, op_search, ref, 0 );
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+ break;
+
+ case LDAP_REQ_EXTENDED:
+ rc = ldap_chain_op( op, rs, op_extended, ref, 0 );
+ /* FIXME: ldap_back_extended() by design
+ * doesn't send result; frontend is expected
+ * to send it... */
+ /* FIXME: what about chaining? */
+ if ( rc != SLAPD_ABANDON ) {
+ rs->sr_err = rc;
+ send_ldap_extended( op, rs );
+ rc = LDAP_SUCCESS;
+ }
+ lb.lb_status = LDAP_CH_RES;
+ break;
+
+ default:
+ rc = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ switch ( rc ) {
+ case SLAPD_ABANDON:
+ goto dont_chain;
+
+ case LDAP_SUCCESS:
+ case LDAP_REFERRAL:
+ sr_err = rs->sr_err;
+ /* slapd-ldap sent response */
+ if ( !op->o_abandon && lb.lb_status != LDAP_CH_RES ) {
+ /* FIXME: should we send response? */
+ Debug( LDAP_DEBUG_ANY,
+ "%s: ldap_chain_response: "
+ "overlay should have sent result.\n",
+ op->o_log_prefix );
+ }
+ break;
+
+ default:
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ if ( lb.lb_status == LDAP_CH_ERR && rs->sr_err == LDAP_X_CANNOT_CHAIN ) {
+ goto cannot_chain;
+ }
+
+ switch ( ( get_chainingBehavior( op ) & chain_mask ) >> chain_shift ) {
+ case LDAP_CHAINING_REQUIRED:
+cannot_chain:;
+ op->o_callback = NULL;
+ send_ldap_error( op, rs, LDAP_X_CANNOT_CHAIN,
+ "operation cannot be completed without chaining" );
+ goto dont_chain;
+
+ default:
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ if ( LDAP_CHAIN_RETURN_ERR( lc ) ) {
+ sr_err = rs->sr_err = rc;
+ rs->sr_text = lb.lb_text;
+ rs->sr_type = sr_type;
+
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ rs->sr_err = sr_err;
+ rs->sr_type = sr_type;
+ rs->sr_text = text;
+ rs->sr_matched = matched;
+ rs->sr_ref = ref;
+ rs->sr_flags |= flags;
+ }
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ break;
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ }
+
+ if ( lb.lb_status == LDAP_CH_NONE && rc != SLAPD_ABANDON ) {
+ /* give the remaining callbacks a chance */
+ op->o_callback = sc->sc_next;
+ rc = rs->sr_err = slap_map_api2result( rs );
+ send_ldap_result( op, rs );
+ }
+
+dont_chain:;
+ rs->sr_err = sr_err;
+ rs->sr_type = sr_type;
+ rs->sr_text = text;
+ rs->sr_matched = matched;
+ rs->sr_ref = ref;
+ rs->sr_flags |= flags;
+
+ op->o_bd = bd;
+ op->o_callback = sc;
+ op->o_ndn = ndn;
+
+ if ( rs->sr_text == lb.lb_text ) {
+ rs->sr_text = NULL;
+ }
+ if ( lb.lb_text ) {
+ ber_memfree_x( lb.lb_text, op->o_tmpmemctx );
+ }
+
+ return rc;
+}
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+static int
+ldap_chain_parse_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl );
+
+static int
+str2chain( const char *s )
+{
+ if ( strcasecmp( s, "chainingPreferred" ) == 0 ) {
+ return LDAP_CHAINING_PREFERRED;
+
+ } else if ( strcasecmp( s, "chainingRequired" ) == 0 ) {
+ return LDAP_CHAINING_REQUIRED;
+
+ } else if ( strcasecmp( s, "referralsPreferred" ) == 0 ) {
+ return LDAP_REFERRALS_PREFERRED;
+
+ } else if ( strcasecmp( s, "referralsRequired" ) == 0 ) {
+ return LDAP_REFERRALS_REQUIRED;
+ }
+
+ return -1;
+}
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+/*
+ * configuration...
+ */
+
+enum {
+ CH_CHAINING = 1,
+ CH_CACHE_URI,
+ CH_MAX_DEPTH,
+ CH_RETURN_ERR,
+
+ CH_LAST
+};
+
+static ConfigDriver chain_cf_gen;
+static ConfigCfAdd chain_cfadd;
+static ConfigLDAPadd chain_ldadd;
+#ifdef SLAP_CONFIG_DELETE
+static ConfigLDAPdel chain_lddel;
+#endif
+
+static ConfigTable chaincfg[] = {
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ { "chain-chaining", "args",
+ 2, 4, 0, ARG_MAGIC|ARG_BERVAL|CH_CHAINING, chain_cf_gen,
+ "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' "
+ "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ { "chain-cache-uri", "TRUE/FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_CACHE_URI, chain_cf_gen,
+ "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' "
+ "DESC 'Enables caching of URIs not present in configuration' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "chain-max-depth", "args",
+ 2, 2, 0, ARG_MAGIC|ARG_INT|CH_MAX_DEPTH, chain_cf_gen,
+ "( OLcfgOvAt:3.3 NAME 'olcChainMaxReferralDepth' "
+ "DESC 'max referral depth' "
+ "SYNTAX OMsInteger "
+ "EQUALITY integerMatch "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "chain-return-error", "TRUE/FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|CH_RETURN_ERR, chain_cf_gen,
+ "( OLcfgOvAt:3.4 NAME 'olcChainReturnError' "
+ "DESC 'Errors are returned instead of the original referral' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs chainocs[] = {
+ { "( OLcfgOvOc:3.1 "
+ "NAME 'olcChainConfig' "
+ "DESC 'Chain configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ "olcChainingBehavior $ "
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ "olcChainCacheURI $ "
+ "olcChainMaxReferralDepth $ "
+ "olcChainReturnError "
+ ") )",
+ Cft_Overlay, chaincfg, NULL, chain_cfadd },
+ { "( OLcfgOvOc:3.2 "
+ "NAME 'olcChainDatabase' "
+ "DESC 'Chain remote server configuration' "
+ "AUXILIARY )",
+ Cft_Misc, NULL, chain_ldadd
+#ifdef SLAP_CONFIG_DELETE
+ , NULL, chain_lddel
+#endif
+ },
+ { NULL, 0, NULL }
+};
+
+static int
+chain_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ ldap_chain_t *lc;
+
+ ldapinfo_t *li;
+
+ AttributeDescription *ad = NULL;
+ Attribute *at;
+ const char *text;
+
+ int rc;
+
+ if ( p->ce_type != Cft_Overlay
+ || !p->ce_bi
+ || p->ce_bi->bi_cf_ocs != chainocs )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ on = (slap_overinst *)p->ce_bi;
+ lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ assert( ca->be == NULL );
+ ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) );
+
+ ca->be->bd_info = (BackendInfo *)on;
+
+ rc = slap_str2ad( "olcDbURI", &ad, &text );
+ assert( rc == LDAP_SUCCESS );
+
+ at = attr_find( e->e_attrs, ad );
+#if 0
+ if ( lc->lc_common_li == NULL && at != NULL ) {
+ /* FIXME: we should generate an empty default entry
+ * if none is supplied */
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "first underlying database \"%s\" "
+ "cannot contain attribute \"%s\".\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+
+ } else
+#endif
+ if ( lc->lc_common_li != NULL && lc->lc_common_li != lc->lc_cfg_li && at == NULL ) {
+ /* FIXME: we should generate an empty default entry
+ * if none is supplied */
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "subsequent underlying database \"%s\" "
+ "must contain attribute \"%s\".\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( lc->lc_common_li == NULL ) {
+ rc = ldap_chain_db_init_common( ca->be );
+ if ( rc != 0 )
+ goto fail;
+ li = ca->be->be_private;
+ lc->lc_common_li = lc->lc_cfg_li = li;
+
+ }
+ rc = ldap_chain_db_init_one( ca->be );
+ lc->lc_cfg_li = NULL;
+
+ if ( rc != 0 ) {
+fail:
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "unable to init %sunderlying database \"%s\".\n",
+ lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ li = ca->be->be_private;
+
+ if ( at ) {
+ char **urls;
+
+ urls = ldap_str2charray( at->a_vals[ 0 ].bv_val, ", \t" );
+ if ( !urls || !urls[0] || urls[1] ) {
+ ldap_charray_free( urls );
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "olcDbURI must contain exactly one url, got %s\n",
+ at->a_vals[ 0 ].bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ ldap_charray_free( urls );
+
+ li->li_uri = ch_strdup( at->a_vals[ 0 ].bv_val );
+ value_add_one( &li->li_bvuri, &at->a_vals[ 0 ] );
+ if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
+ ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "database \"%s\" insert failed.\n",
+ e->e_name.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ }
+
+ ca->ca_private = on;
+
+done:;
+ if ( rc != LDAP_SUCCESS ) {
+ (void)ldap_chain_db_destroy_one( ca->be, NULL );
+ ch_free( ca->be );
+ ca->be = NULL;
+ }
+
+ return rc;
+}
+
+static void
+ldap_chain_cfadd_apply(
+ ldapinfo_t *li,
+ Operation *op,
+ SlapReply *rs,
+ Entry *p,
+ ConfigArgs *ca,
+ int count )
+{
+ struct berval bv;
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase={%d}%s", count, lback->bi_type );
+ bv.bv_val = ca->cr_msg;
+
+ ca->be->be_private = (void *)li;
+ config_build_entry( op, rs, p->e_private, ca,
+ &bv, lback->bi_cf_ocs, &chainocs[1] );
+
+ return;
+}
+
+static int
+chain_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ CfEntryInfo *pe = p->e_private;
+ slap_overinst *on = (slap_overinst *)pe->ce_bi;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ void *priv = (void *)ca->be->be_private;
+ TAvlnode *edge;
+ int count = 0;
+
+ if ( lback->bi_cf_ocs ) {
+
+ ldap_chain_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ );
+
+ edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ ldap_chain_cfadd_apply( li, op, rs, p, ca, count++ );
+ edge = next;
+ }
+
+ ca->be->be_private = priv;
+ }
+
+ lc->lc_cfg_li = NULL;
+
+ return 0;
+}
+
+#ifdef SLAP_CONFIG_DELETE
+static int
+chain_lddel( CfEntryInfo *ce, Operation *op )
+{
+ CfEntryInfo *pe = ce->ce_parent;
+ slap_overinst *on = (slap_overinst *)pe->ce_bi;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ ldapinfo_t *li = (ldapinfo_t *) ce->ce_be->be_private;
+
+ if ( li != lc->lc_common_li ) {
+ if (! ldap_tavl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: ldap_avl_delete failed. "
+ "\"%s\" not found.\n", li->li_uri );
+ return -1;
+ }
+ } else if ( lc->lc_lai.lai_tree ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: cannot delete first underlying "
+ "LDAP database when other databases are still present.\n" );
+ return -1;
+ } else {
+ lc->lc_common_li = NULL;
+ }
+
+ ce->ce_be->bd_info = lback;
+
+ if ( ce->ce_be->bd_info->bi_db_close ) {
+ ce->ce_be->bd_info->bi_db_close( ce->ce_be, NULL );
+ }
+ if ( ce->ce_be->bd_info->bi_db_destroy ) {
+ ce->ce_be->bd_info->bi_db_destroy( ce->ce_be, NULL );
+ }
+
+ ch_free(ce->ce_be);
+ ce->ce_be = NULL;
+
+ return LDAP_SUCCESS;
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+static slap_verbmasks chaining_mode[] = {
+ { BER_BVC("referralsRequired"), LDAP_REFERRALS_REQUIRED },
+ { BER_BVC("referralsPreferred"), LDAP_REFERRALS_PREFERRED },
+ { BER_BVC("chainingRequired"), LDAP_CHAINING_REQUIRED },
+ { BER_BVC("chainingPreferred"), LDAP_CHAINING_PREFERRED },
+ { BER_BVNULL, 0 }
+};
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+static int
+chain_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ case CH_CHAINING: {
+ struct berval resolve = BER_BVNULL,
+ continuation = BER_BVNULL;
+
+ if ( !LDAP_CHAIN_CHAINING( lc ) ) {
+ return 1;
+ }
+
+ enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_RESOLVE_MASK ) >> SLAP_CH_RESOLVE_SHIFT ), &resolve );
+ enum_to_verb( chaining_mode, ( ( lc->lc_chaining_ctrlflag & SLAP_CH_CONTINUATION_MASK ) >> SLAP_CH_CONTINUATION_SHIFT ), &continuation );
+
+ c->value_bv.bv_len = STRLENOF( "resolve=" ) + resolve.bv_len
+ + STRLENOF( " " )
+ + STRLENOF( "continuation=" ) + continuation.bv_len;
+ c->value_bv.bv_val = ch_malloc( c->value_bv.bv_len + 1 );
+ snprintf( c->value_bv.bv_val, c->value_bv.bv_len + 1,
+ "resolve=%s continuation=%s",
+ resolve.bv_val, continuation.bv_val );
+
+ if ( lc->lc_chaining_ctrl.ldctl_iscritical ) {
+ c->value_bv.bv_val = ch_realloc( c->value_bv.bv_val,
+ c->value_bv.bv_len + STRLENOF( " critical" ) + 1 );
+ AC_MEMCPY( &c->value_bv.bv_val[ c->value_bv.bv_len ],
+ " critical", STRLENOF( " critical" ) + 1 );
+ c->value_bv.bv_len += STRLENOF( " critical" );
+ }
+
+ break;
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ case CH_CACHE_URI:
+ c->value_int = LDAP_CHAIN_CACHE_URI( lc );
+ break;
+
+ case CH_MAX_DEPTH:
+ c->value_int = lc->lc_max_depth;
+ break;
+
+ case CH_RETURN_ERR:
+ c->value_int = LDAP_CHAIN_RETURN_ERR( lc );
+ break;
+
+ default:
+ assert( 0 );
+ rc = 1;
+ }
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case CH_CHAINING:
+ return 1;
+
+ case CH_CACHE_URI:
+ lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI;
+ break;
+
+ case CH_MAX_DEPTH:
+ c->value_int = 0;
+ break;
+
+ case CH_RETURN_ERR:
+ lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR;
+ break;
+
+ default:
+ return 1;
+ }
+ return rc;
+ }
+
+ switch( c->type ) {
+ case CH_CHAINING: {
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ char **argv = c->argv;
+ int argc = c->argc;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ int resolve = -1,
+ continuation = -1,
+ iscritical = 0;
+ Operation op = { 0 };
+ SlapReply rs = { 0 };
+
+ lc->lc_chaining_ctrlflag = 0;
+
+ for ( argc--, argv++; argc > 0; argc--, argv++ ) {
+ if ( strncasecmp( argv[ 0 ], "resolve=", STRLENOF( "resolve=" ) ) == 0 ) {
+ resolve = str2chain( argv[ 0 ] + STRLENOF( "resolve=" ) );
+ if ( resolve == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "illegal <resolve> value %s "
+ "in \"chain-chaining>\".\n",
+ c->log, argv[ 0 ] );
+ return 1;
+ }
+
+ } else if ( strncasecmp( argv[ 0 ], "continuation=", STRLENOF( "continuation=" ) ) == 0 ) {
+ continuation = str2chain( argv[ 0 ] + STRLENOF( "continuation=" ) );
+ if ( continuation == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "illegal <continuation> value %s "
+ "in \"chain-chaining\".\n",
+ c->log, argv[ 0 ] );
+ return 1;
+ }
+
+ } else if ( strcasecmp( argv[ 0 ], "critical" ) == 0 ) {
+ iscritical = 1;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "unknown option in \"chain-chaining\".\n",
+ c->log );
+ return 1;
+ }
+ }
+
+ if ( resolve != -1 || continuation != -1 ) {
+ int err;
+
+ if ( resolve == -1 ) {
+ /* default */
+ resolve = SLAP_CHAINING_DEFAULT;
+ }
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ err = ber_printf( ber, "{e" /* } */, resolve );
+ if ( err == -1 ) {
+ ber_free( ber, 1 );
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "chaining behavior control encoding error!\n",
+ c->log );
+ return 1;
+ }
+
+ if ( continuation > -1 ) {
+ err = ber_printf( ber, "e", continuation );
+ if ( err == -1 ) {
+ ber_free( ber, 1 );
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "chaining behavior control encoding error!\n",
+ c->log );
+ return 1;
+ }
+ }
+
+ err = ber_printf( ber, /* { */ "N}" );
+ if ( err == -1 ) {
+ ber_free( ber, 1 );
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "chaining behavior control encoding error!\n",
+ c->log );
+ return 1;
+ }
+
+ if ( ber_flatten2( ber, &lc->lc_chaining_ctrl.ldctl_value, 0 ) == -1 ) {
+ exit( EXIT_FAILURE );
+ }
+
+ } else {
+ BER_BVZERO( &lc->lc_chaining_ctrl.ldctl_value );
+ }
+
+ lc->lc_chaining_ctrl.ldctl_oid = LDAP_CONTROL_X_CHAINING_BEHAVIOR;
+ lc->lc_chaining_ctrl.ldctl_iscritical = iscritical;
+
+ if ( ldap_chain_parse_ctrl( &op, &rs, &lc->lc_chaining_ctrl ) != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "unable to parse chaining control%s%s.\n",
+ c->log, rs.sr_text ? ": " : "",
+ rs.sr_text ? rs.sr_text : "" );
+ return 1;
+ }
+
+ lc->lc_chaining_ctrlflag = op.o_chaining;
+
+ lc->lc_flags |= LDAP_CHAIN_F_CHAINING;
+
+ rc = 0;
+#else /* ! LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "\"chaining\" control unsupported (ignored).\n",
+ c->log );
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ } break;
+
+ case CH_CACHE_URI:
+ if ( c->value_int ) {
+ lc->lc_flags |= LDAP_CHAIN_F_CACHE_URI;
+ } else {
+ lc->lc_flags &= ~LDAP_CHAIN_F_CACHE_URI;
+ }
+ break;
+
+ case CH_MAX_DEPTH:
+ if ( c->value_int < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid max referral depth %d",
+ c->argv[0], c->value_int );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ break;
+ }
+ lc->lc_max_depth = c->value_int;
+
+ case CH_RETURN_ERR:
+ if ( c->value_int ) {
+ lc->lc_flags |= LDAP_CHAIN_F_RETURN_ERR;
+ } else {
+ lc->lc_flags &= ~LDAP_CHAIN_F_RETURN_ERR;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+ return rc;
+}
+
+static int
+ldap_chain_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_chain_t *lc = NULL;
+
+ if ( lback == NULL ) {
+ lback = backend_info( "ldap" );
+
+ if ( lback == NULL ) {
+ return 1;
+ }
+ }
+
+ lc = ch_malloc( sizeof( ldap_chain_t ) );
+ if ( lc == NULL ) {
+ return 1;
+ }
+ memset( lc, 0, sizeof( ldap_chain_t ) );
+ lc->lc_max_depth = 1;
+ ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex );
+
+ on->on_bi.bi_private = (void *)lc;
+
+ return 0;
+}
+
+static int
+ldap_chain_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ int rc = SLAP_CONF_UNKNOWN;
+
+ if ( lc->lc_common_li == NULL ) {
+ BackendDB db = *be;
+ ldap_chain_db_init_common( &db );
+ lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)db.be_private;
+ }
+
+ /* Something for the chain database? */
+ if ( strncasecmp( argv[ 0 ], "chain-", STRLENOF( "chain-" ) ) == 0 ) {
+ char *save_argv0 = argv[ 0 ];
+ BackendDB db = *be;
+ static char *allowed_argv[] = {
+ /* special: put URI here, so in the meanwhile
+ * it detects whether a new URI is being provided */
+ "uri",
+ "nretries",
+ "timeout",
+ /* flags */
+ "tls",
+ /* FIXME: maybe rebind-as-user should be allowed
+ * only within known URIs... */
+ "rebind-as-user",
+ "chase-referrals",
+ "t-f-support",
+ "proxy-whoami",
+ NULL
+ };
+ int which_argv = -1;
+
+ argv[ 0 ] += STRLENOF( "chain-" );
+
+ for ( which_argv = 0; allowed_argv[ which_argv ]; which_argv++ ) {
+ if ( strcasecmp( argv[ 0 ], allowed_argv[ which_argv ] ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( allowed_argv[ which_argv ] == NULL ) {
+ which_argv = -1;
+
+ if ( lc->lc_cfg_li == lc->lc_common_li ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "\"%s\" only allowed within a URI directive.\n.",
+ fname, lineno, argv[ 0 ] );
+ return 1;
+ }
+ }
+
+ if ( which_argv == 0 ) {
+ rc = ldap_chain_db_init_one( &db );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "underlying slapd-ldap initialization failed.\n.",
+ fname, lineno );
+ return 1;
+ }
+ lc->lc_cfg_li = db.be_private;
+ }
+
+ /* TODO: add checks on what other slapd-ldap(5) args
+ * should be put in the template; this is not quite
+ * harmful, because attributes that shouldn't don't
+ * get actually used, but the user should at least
+ * be warned.
+ */
+
+ db.bd_info = lback;
+ db.be_private = (void *)lc->lc_cfg_li;
+ db.be_cf_ocs = lback->bi_cf_ocs;
+
+ rc = config_generic_wrapper( &db, fname, lineno, argc, argv );
+
+ argv[ 0 ] = save_argv0;
+
+ if ( which_argv == 0 ) {
+private_destroy:;
+ if ( rc != 0 ) {
+ db.bd_info = lback;
+ db.be_private = (void *)lc->lc_cfg_li;
+ ldap_chain_db_destroy_one( &db, NULL );
+ lc->lc_cfg_li = NULL;
+ } else {
+ if ( lc->lc_cfg_li->li_bvuri == NULL
+ || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] )
+ || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "no URI list allowed in slapo-chain.\n",
+ fname, lineno );
+ rc = 1;
+ goto private_destroy;
+ }
+
+ if ( ldap_tavl_insert( &lc->lc_lai.lai_tree,
+ (caddr_t)lc->lc_cfg_li,
+ ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "duplicate URI in slapo-chain.\n",
+ fname, lineno );
+ rc = 1;
+ goto private_destroy;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+enum db_which {
+ db_open = 0,
+ db_close,
+ db_destroy,
+
+ db_last
+};
+
+static int
+ldap_chain_db_func(
+ BackendDB *be,
+ enum db_which which
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ int rc = 0;
+
+ if ( lc ) {
+ BI_db_func *func = (&lback->bi_db_open)[ which ];
+
+ if ( func != NULL && lc->lc_common_li != NULL ) {
+ BackendDB db = *be;
+
+ db.bd_info = lback;
+ db.be_private = lc->lc_common_li;
+
+ rc = func( &db, NULL );
+
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ if ( lc->lc_lai.lai_tree != NULL ) {
+ TAvlnode *edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ db.be_private = (void *)li;
+ rc = func( &db, NULL );
+ if ( rc == 1 ) {
+ break;
+ }
+ edge = next;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int
+ldap_chain_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ slap_mask_t monitoring;
+ int rc = 0;
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ rc = overlay_register_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
+ if ( rc != 0 ) {
+ return rc;
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ if ( lc->lc_common_li == NULL ) {
+ void *be_private = be->be_private;
+ ldap_chain_db_init_common( be );
+ lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private;
+ be->be_private = be_private;
+ }
+
+ /* filter out and restore monitoring */
+ monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING );
+ SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING;
+ rc = ldap_chain_db_func( be, db_open );
+ SLAP_DBFLAGS( be ) |= monitoring;
+
+ return rc;
+}
+
+static int
+ldap_chain_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_X_CHAINING_BEHAVIOR );
+#endif /* SLAP_CONFIG_DELETE */
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ return ldap_chain_db_func( be, db_close );
+}
+
+static int
+ldap_chain_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ int rc;
+
+ rc = ldap_chain_db_func( be, db_destroy );
+
+ if ( lc ) {
+ ldap_tavl_free( lc->lc_lai.lai_tree, NULL );
+ ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex );
+ ch_free( lc );
+ }
+
+ return rc;
+}
+
+/*
+ * inits one instance of the slapd-ldap backend, and stores
+ * the private info in be_private of the arg
+ */
+static int
+ldap_chain_db_init_common(
+ BackendDB *be )
+{
+ BackendInfo *bi = be->bd_info;
+ ldapinfo_t *li;
+ int rc;
+
+ be->bd_info = lback;
+ be->be_private = NULL;
+ rc = lback->bi_db_init( be, NULL );
+ if ( rc != 0 ) {
+ return rc;
+ }
+ li = (ldapinfo_t *)be->be_private;
+ li->li_urllist_f = NULL;
+ li->li_urllist_p = NULL;
+
+ be->bd_info = bi;
+
+ return 0;
+}
+
+/*
+ * inits one instance of the slapd-ldap backend, stores
+ * the private info in be_private of the arg and fills
+ * selected fields with data from the template.
+ *
+ * NOTE: add checks about the other fields of the template,
+ * which are ignored and SHOULD NOT be configured by the user.
+ */
+static int
+ldap_chain_db_init_one(
+ BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+
+ BackendInfo *bi = be->bd_info;
+ ldapinfo_t *li;
+
+ slap_op_t t;
+
+ be->bd_info = lback;
+ be->be_private = NULL;
+ t = lback->bi_db_init( be, NULL );
+ if ( t != 0 ) {
+ return t;
+ }
+ li = (ldapinfo_t *)be->be_private;
+ li->li_urllist_f = NULL;
+ li->li_urllist_p = NULL;
+
+ /* copy common data */
+ li->li_nretries = lc->lc_common_li->li_nretries;
+ li->li_flags = lc->lc_common_li->li_flags;
+ li->li_version = lc->lc_common_li->li_version;
+ for ( t = 0; t < SLAP_OP_LAST; t++ ) {
+ li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ];
+ }
+ be->bd_info = bi;
+
+ return 0;
+}
+
+static int
+ldap_chain_db_open_one(
+ BackendDB *be )
+{
+ if ( SLAP_DBMONITORING( be ) ) {
+ ldapinfo_t *li = (ldapinfo_t *)be->be_private;
+
+ if ( li->li_uri == NULL ) {
+ ber_str2bv( "cn=Common Connections", 0, 1,
+ &li->li_monitor_info.lmi_conn_rdn );
+ ber_str2bv( "cn=Operations on Common Connections", 0, 1,
+ &li->li_monitor_info.lmi_conn_rdn );
+
+ } else {
+ char *ptr;
+
+ li->li_monitor_info.lmi_conn_rdn.bv_len
+ = STRLENOF( "cn=" ) + strlen( li->li_uri );
+ ptr = li->li_monitor_info.lmi_conn_rdn.bv_val
+ = ch_malloc( li->li_monitor_info.lmi_conn_rdn.bv_len + 1 );
+ ptr = lutil_strcopy( ptr, "cn=" );
+ ptr = lutil_strcopy( ptr, li->li_uri );
+ ptr[ 0 ] = '\0';
+
+ li->li_monitor_info.lmi_ops_rdn.bv_len
+ = STRLENOF( "cn=Operations on " ) + strlen( li->li_uri );
+ ptr = li->li_monitor_info.lmi_ops_rdn.bv_val
+ = ch_malloc( li->li_monitor_info.lmi_ops_rdn.bv_len + 1 );
+ ptr = lutil_strcopy( ptr, "cn=Operations on " );
+ ptr = lutil_strcopy( ptr, li->li_uri );
+ ptr[ 0 ] = '\0';
+ }
+ }
+
+ return lback->bi_db_open( be, NULL );
+}
+
+static int
+ldap_chain_connection_destroy(
+ BackendDB *be,
+ Connection *conn
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ ldap_chain_t *lc = (ldap_chain_t *)on->on_bi.bi_private;
+ void *private = be->be_private;
+ TAvlnode *edge;
+ int rc;
+
+ be->be_private = NULL;
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ be->be_private = (void *)li;
+ rc = lback->bi_connection_destroy( be, conn );
+ if ( rc == 1 ) {
+ break;
+ }
+ edge = next;
+ }
+
+
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+ be->be_private = private;
+
+ return rc;
+}
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+static int
+ldap_chain_parse_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElement *ber;
+ ber_int_t mode,
+ behavior;
+
+ if ( get_chaining( op ) != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Chaining behavior control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Chaining behavior control specified with pagedResults control";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ mode = (SLAP_CH_RESOLVE_DEFAULT|SLAP_CH_CONTINUATION_DEFAULT);
+
+ } else {
+ ber_len_t len;
+
+ /* Parse the control value
+ * ChainingBehavior ::= SEQUENCE {
+ * resolveBehavior Behavior OPTIONAL,
+ * continuationBehavior Behavior OPTIONAL }
+ *
+ * Behavior :: = ENUMERATED {
+ * chainingPreferred (0),
+ * chainingRequired (1),
+ * referralsPreferred (2),
+ * referralsRequired (3) }
+ */
+
+ ber = ber_init( &ctrl->ldctl_value );
+ if( ber == NULL ) {
+ rs->sr_text = "internal error";
+ return LDAP_OTHER;
+ }
+
+ tag = ber_scanf( ber, "{e" /* } */, &behavior );
+ /* FIXME: since the whole SEQUENCE is optional,
+ * should we accept no enumerations at all? */
+ if ( tag != LBER_ENUMERATED ) {
+ rs->sr_text = "Chaining behavior control: resolveBehavior decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ switch ( behavior ) {
+ case LDAP_CHAINING_PREFERRED:
+ mode = SLAP_CH_RESOLVE_CHAINING_PREFERRED;
+ break;
+
+ case LDAP_CHAINING_REQUIRED:
+ mode = SLAP_CH_RESOLVE_CHAINING_REQUIRED;
+ break;
+
+ case LDAP_REFERRALS_PREFERRED:
+ mode = SLAP_CH_RESOLVE_REFERRALS_PREFERRED;
+ break;
+
+ case LDAP_REFERRALS_REQUIRED:
+ mode = SLAP_CH_RESOLVE_REFERRALS_REQUIRED;
+ break;
+
+ default:
+ rs->sr_text = "Chaining behavior control: unknown resolveBehavior";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_ENUMERATED ) {
+ tag = ber_scanf( ber, "e", &behavior );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_text = "Chaining behavior control: continuationBehavior decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+
+ if ( tag == LBER_DEFAULT ) {
+ mode |= SLAP_CH_CONTINUATION_DEFAULT;
+
+ } else {
+ switch ( behavior ) {
+ case LDAP_CHAINING_PREFERRED:
+ mode |= SLAP_CH_CONTINUATION_CHAINING_PREFERRED;
+ break;
+
+ case LDAP_CHAINING_REQUIRED:
+ mode |= SLAP_CH_CONTINUATION_CHAINING_REQUIRED;
+ break;
+
+ case LDAP_REFERRALS_PREFERRED:
+ mode |= SLAP_CH_CONTINUATION_REFERRALS_PREFERRED;
+ break;
+
+ case LDAP_REFERRALS_REQUIRED:
+ mode |= SLAP_CH_CONTINUATION_REFERRALS_REQUIRED;
+ break;
+
+ default:
+ rs->sr_text = "Chaining behavior control: unknown continuationBehavior";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+
+ if ( ( ber_scanf( ber, /* { */ "}") ) == LBER_ERROR ) {
+ rs->sr_text = "Chaining behavior control: decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ (void) ber_free( ber, 1 );
+ }
+
+ op->o_chaining = mode | ( ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL );
+
+ return LDAP_SUCCESS;
+}
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+int
+chain_initialize( void )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( CH_LAST );
+
+ /* olcDatabaseDummy is defined in slapd, and Windows
+ will not let us initialize a struct element with a data pointer
+ from another library, so we have to initialize this element
+ "by hand". */
+ chainocs[1].co_table = olcDatabaseDummy;
+
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ rc = register_supported_control( LDAP_CONTROL_X_CHAINING_BEHAVIOR,
+ /* SLAP_CTRL_GLOBAL| */ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
+ ldap_chain_parse_ctrl, &sc_chainingBehavior );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-chain: "
+ "unable to register chaining behavior control: %d.\n",
+ rc );
+ return rc;
+ }
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+
+ ldapchain.on_bi.bi_type = "chain";
+ ldapchain.on_bi.bi_db_init = ldap_chain_db_init;
+ ldapchain.on_bi.bi_db_config = ldap_chain_db_config;
+ ldapchain.on_bi.bi_db_open = ldap_chain_db_open;
+ ldapchain.on_bi.bi_db_close = ldap_chain_db_close;
+ ldapchain.on_bi.bi_db_destroy = ldap_chain_db_destroy;
+
+ ldapchain.on_bi.bi_connection_destroy = ldap_chain_connection_destroy;
+
+ ldapchain.on_response = ldap_chain_response;
+
+ ldapchain.on_bi.bi_cf_ocs = chainocs;
+
+ rc = config_register_schema( chaincfg, chainocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &ldapchain );
+}
+
diff --git a/servers/slapd/back-ldap/compare.c b/servers/slapd/back-ldap/compare.c
new file mode 100644
index 0000000..1e410db
--- /dev/null
+++ b/servers/slapd/back-ldap/compare.c
@@ -0,0 +1,88 @@
+/* compare.c - ldap backend compare function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ ber_int_t msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+ int rc = LDAP_SUCCESS;
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ lc = NULL;
+ goto cleanup;
+ }
+
+retry:
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_compare_ext( lc->lc_ld, op->o_req_dn.bv_val,
+ op->orc_ava->aa_desc->ad_cname.bv_val,
+ &op->orc_ava->aa_value,
+ ctrls, NULL, &msgid );
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_COMPARE ],
+ ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rc == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_COMPARE ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+cleanup:
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-ldap/config.c b/servers/slapd/back-ldap/config.c
new file mode 100644
index 0000000..fb97e8e
--- /dev/null
+++ b/servers/slapd/back-ldap/config.c
@@ -0,0 +1,2214 @@
+/* config.c - ldap backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "back-ldap.h"
+#include "lutil.h"
+#include "ldif.h"
+
+static SLAP_EXTOP_MAIN_FN ldap_back_exop_whoami;
+
+static ConfigDriver ldap_back_cf_gen;
+static ConfigDriver ldap_pbind_cf_gen;
+
+enum {
+ LDAP_BACK_CFG_URI = 1,
+ LDAP_BACK_CFG_TLS,
+ LDAP_BACK_CFG_ACL_BIND,
+ LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ LDAP_BACK_CFG_IDASSERT_PASSTHRU,
+ LDAP_BACK_CFG_IDASSERT_BIND,
+ LDAP_BACK_CFG_REBIND,
+ LDAP_BACK_CFG_CHASE,
+ LDAP_BACK_CFG_T_F,
+ LDAP_BACK_CFG_WHOAMI,
+ LDAP_BACK_CFG_TIMEOUT,
+ LDAP_BACK_CFG_IDLE_TIMEOUT,
+ LDAP_BACK_CFG_CONN_TTL,
+ LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ LDAP_BACK_CFG_VERSION,
+ LDAP_BACK_CFG_SINGLECONN,
+ LDAP_BACK_CFG_USETEMP,
+ LDAP_BACK_CFG_CONNPOOLMAX,
+ LDAP_BACK_CFG_CANCEL,
+ LDAP_BACK_CFG_QUARANTINE,
+ LDAP_BACK_CFG_ST_REQUEST,
+ LDAP_BACK_CFG_NOREFS,
+ LDAP_BACK_CFG_NOUNDEFFILTER,
+ LDAP_BACK_CFG_ONERR,
+
+ LDAP_BACK_CFG_KEEPALIVE,
+ LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+
+ LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA,
+
+ LDAP_BACK_CFG_LAST
+};
+
+static ConfigTable ldapcfg[] = {
+ { "uri", "uri", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_URI,
+ ldap_back_cf_gen, "( OLcfgDbAt:0.14 "
+ "NAME 'olcDbURI' "
+ "DESC 'URI (list) for remote DSA' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tls", "what", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TLS,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.1 "
+ "NAME 'olcDbStartTLS' "
+ "DESC 'StartTLS' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "acl-bind", "args", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_ACL_BIND,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.4 "
+ "NAME 'olcDbACLBind' "
+ "DESC 'Remote ACL administrative identity auth bind configuration' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-bind", "args", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.7 "
+ "NAME 'olcDbIDAssertBind' "
+ "DESC 'Remote Identity Assertion administrative identity auth bind configuration' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-authzFrom", "authzRule", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.9 "
+ "NAME 'olcDbIDAssertAuthzFrom' "
+ "DESC 'Remote Identity Assertion authz rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "rebind-as-user", "true|FALSE", 1, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.10 "
+ "NAME 'olcDbRebindAsUser' "
+ "DESC 'Rebind as user' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "chase-referrals", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.11 "
+ "NAME 'olcDbChaseReferrals' "
+ "DESC 'Chase referrals' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "t-f-support", "true|FALSE|discover", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_T_F,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.12 "
+ "NAME 'olcDbTFSupport' "
+ "DESC 'Absolute filters support' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "proxy-whoami", "true|FALSE", 1, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_WHOAMI,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.13 "
+ "NAME 'olcDbProxyWhoAmI' "
+ "DESC 'Proxy whoAmI exop' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "timeout", "timeout(list)", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.14 "
+ "NAME 'olcDbTimeout' "
+ "DESC 'Per-operation timeouts' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idle-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.15 "
+ "NAME 'olcDbIdleTimeout' "
+ "DESC 'connection idle timeout' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "conn-ttl", "ttl", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.16 "
+ "NAME 'olcDbConnTtl' "
+ "DESC 'connection ttl' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "network-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.17 "
+ "NAME 'olcDbNetworkTimeout' "
+ "DESC 'connection network timeout' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "protocol-version", "version", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.18 "
+ "NAME 'olcDbProtocolVersion' "
+ "DESC 'protocol version' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "single-conn", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.19 "
+ "NAME 'olcDbSingleConn' "
+ "DESC 'cache a single connection per identity' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "cancel", "ABANDON|ignore|exop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CANCEL,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.20 "
+ "NAME 'olcDbCancel' "
+ "DESC 'abandon/ignore/exop operations when appropriate' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "quarantine", "retrylist", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.21 "
+ "NAME 'olcDbQuarantine' "
+ "DESC 'Quarantine database if connection fails and retry according to rule' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "use-temporary-conn", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.22 "
+ "NAME 'olcDbUseTemporaryConn' "
+ "DESC 'Use temporary connections if the cached one is busy' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "conn-pool-max", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.23 "
+ "NAME 'olcDbConnectionPoolMax' "
+ "DESC 'Max size of privileged connections pool' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ { "session-tracking-request", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.24 "
+ "NAME 'olcDbSessionTrackingRequest' "
+ "DESC 'Add session tracking control to proxied requests' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+ { "norefs", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.25 "
+ "NAME 'olcDbNoRefs' "
+ "DESC 'Do not return search reference responses' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "noundeffilter", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.26 "
+ "NAME 'olcDbNoUndefFilter' "
+ "DESC 'Do not propagate undefined search filters' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "onerr", "CONTINUE|report|stop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_ONERR,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.108 "
+ "NAME 'olcDbOnErr' "
+ "DESC 'error handling' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-passThru", "authzRule", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_PASSTHRU,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.27 "
+ "NAME 'olcDbIDAssertPassThru' "
+ "DESC 'Remote Identity Assertion passthru rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "omit-unknown-schema", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.28 "
+ "NAME 'olcDbRemoveUnknownSchema' "
+ "DESC 'Omit unknown schema when returning search results' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "keepalive", "keepalive", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.29 "
+ "NAME 'olcDbKeepalive' "
+ "DESC 'TCP keepalive' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tcp-user-timeout", "milliseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ ldap_back_cf_gen, "( OLcfgDbAt:3.30 "
+ "NAME 'olcDbTcpUserTimeout' "
+ "DESC 'TCP User Timeout' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs ldapocs[] = {
+ { "( OLcfgDbOc:3.1 "
+ "NAME 'olcLDAPConfig' "
+ "DESC 'LDAP backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcDbURI "
+ "$ olcDbStartTLS "
+ "$ olcDbACLBind "
+ "$ olcDbIDAssertBind "
+ "$ olcDbIDAssertAuthzFrom "
+ "$ olcDbIDAssertPassThru "
+ "$ olcDbRebindAsUser "
+ "$ olcDbChaseReferrals "
+ "$ olcDbTFSupport "
+ "$ olcDbProxyWhoAmI "
+ "$ olcDbTimeout "
+ "$ olcDbIdleTimeout "
+ "$ olcDbConnTtl "
+ "$ olcDbNetworkTimeout "
+ "$ olcDbProtocolVersion "
+ "$ olcDbSingleConn "
+ "$ olcDbCancel "
+ "$ olcDbQuarantine "
+ "$ olcDbUseTemporaryConn "
+ "$ olcDbConnectionPoolMax "
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ "$ olcDbSessionTrackingRequest "
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+ "$ olcDbNoRefs "
+ "$ olcDbNoUndefFilter "
+ "$ olcDbOnErr "
+ "$ olcDbKeepalive "
+ ") )",
+ Cft_Database, ldapcfg},
+ { NULL, 0, NULL }
+};
+
+static ConfigTable pbindcfg[] = {
+ { "uri", "uri", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_URI,
+ ldap_pbind_cf_gen, "( OLcfgDbAt:0.14 "
+ "NAME 'olcDbURI' "
+ "DESC 'URI (list) for remote DSA' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tls", "what", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TLS,
+ ldap_pbind_cf_gen, "( OLcfgDbAt:3.1 "
+ "NAME 'olcDbStartTLS' "
+ "DESC 'StartTLS' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "network-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ ldap_pbind_cf_gen, "( OLcfgDbAt:3.17 "
+ "NAME 'olcDbNetworkTimeout' "
+ "DESC 'connection network timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "quarantine", "retrylist", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+ ldap_pbind_cf_gen, "( OLcfgDbAt:3.21 "
+ "NAME 'olcDbQuarantine' "
+ "DESC 'Quarantine database if connection fails and retry according to rule' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs pbindocs[] = {
+ { "( OLcfgOvOc:3.3 "
+ "NAME 'olcPBindConfig' "
+ "DESC 'Proxy Bind configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcDbURI "
+ "MAY ( olcDbStartTLS "
+ "$ olcDbNetworkTimeout "
+ "$ olcDbQuarantine "
+ ") )",
+ Cft_Overlay, pbindcfg},
+ { NULL, 0, NULL }
+};
+
+static slap_verbmasks idassert_mode[] = {
+ { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF },
+ { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS },
+ { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT },
+ { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks tls_mode[] = {
+ { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK },
+ { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS },
+ { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK },
+ { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS },
+ { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS },
+ { BER_BVC( "none" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "yes" ), LDAP_BACK_F_T_F },
+ { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER },
+ { BER_BVC( "no" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks cancel_mode[] = {
+ { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE },
+ { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP },
+ { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER },
+ { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks onerr_mode[] = {
+ { BER_BVC( "stop" ), LDAP_BACK_F_ONERR_STOP },
+ { BER_BVC( "report" ), LDAP_BACK_F_ONERR_STOP }, /* same behavior */
+ { BER_BVC( "continue" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+/* see enum in slap.h */
+static slap_cf_aux_table timeout_table[] = {
+ { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL },
+ /* unbind makes no sense */
+ { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL },
+ /* abandon makes little sense */
+#if 0 /* not implemented yet */
+ { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL },
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+int
+slap_retry_info_parse(
+ char *in,
+ slap_retry_info_t *ri,
+ char *buf,
+ ber_len_t buflen )
+{
+ char **retrylist = NULL;
+ int rc = 0;
+ int i;
+
+ slap_str2clist( &retrylist, in, " ;" );
+ if ( retrylist == NULL ) {
+ return 1;
+ }
+
+ for ( i = 0; retrylist[ i ] != NULL; i++ )
+ /* count */ ;
+
+ ri->ri_interval = ch_calloc( sizeof( time_t ), i + 1 );
+ ri->ri_num = ch_calloc( sizeof( int ), i + 1 );
+
+ for ( i = 0; retrylist[ i ] != NULL; i++ ) {
+ unsigned long t;
+ char *sep = strchr( retrylist[ i ], ',' );
+
+ if ( sep == NULL ) {
+ snprintf( buf, buflen,
+ "missing comma in retry pattern #%d \"%s\"",
+ i, retrylist[ i ] );
+ rc = 1;
+ goto done;
+ }
+
+ *sep++ = '\0';
+
+ if ( lutil_parse_time( retrylist[ i ], &t ) ) {
+ snprintf( buf, buflen,
+ "unable to parse interval #%d \"%s\"",
+ i, retrylist[ i ] );
+ rc = 1;
+ goto done;
+ }
+ ri->ri_interval[ i ] = (time_t)t;
+
+ if ( strcmp( sep, "+" ) == 0 ) {
+ if ( retrylist[ i + 1 ] != NULL ) {
+ snprintf( buf, buflen,
+ "extra cruft after retry pattern "
+ "#%d \"%s,+\" with \"forever\" mark",
+ i, retrylist[ i ] );
+ rc = 1;
+ goto done;
+ }
+ ri->ri_num[ i ] = SLAP_RETRYNUM_FOREVER;
+
+ } else if ( lutil_atoi( &ri->ri_num[ i ], sep ) ) {
+ snprintf( buf, buflen,
+ "unable to parse retry num #%d \"%s\"",
+ i, sep );
+ rc = 1;
+ goto done;
+ }
+ }
+
+ ri->ri_num[ i ] = SLAP_RETRYNUM_TAIL;
+
+ ri->ri_idx = 0;
+ ri->ri_count = 0;
+ ri->ri_last = (time_t)(-1);
+
+done:;
+ ldap_charray_free( retrylist );
+
+ if ( rc ) {
+ slap_retry_info_destroy( ri );
+ }
+
+ return rc;
+}
+
+int
+slap_retry_info_unparse(
+ slap_retry_info_t *ri,
+ struct berval *bvout )
+{
+ char buf[ BUFSIZ * 2 ],
+ *ptr = buf;
+ int i, len, restlen = (int) sizeof( buf );
+ struct berval bv;
+
+ assert( ri != NULL );
+ assert( bvout != NULL );
+
+ BER_BVZERO( bvout );
+
+ for ( i = 0; ri->ri_num[ i ] != SLAP_RETRYNUM_TAIL; i++ ) {
+ if ( i > 0 ) {
+ if ( --restlen <= 0 ) {
+ return 1;
+ }
+ *ptr++ = ';';
+ }
+
+ if ( lutil_unparse_time( ptr, restlen, ri->ri_interval[i] ) < 0 ) {
+ return 1;
+ }
+ len = (int) strlen( ptr );
+ if ( (restlen -= len + 1) <= 0 ) {
+ return 1;
+ }
+ ptr += len;
+ *ptr++ = ',';
+
+ if ( ri->ri_num[i] == SLAP_RETRYNUM_FOREVER ) {
+ if ( --restlen <= 0 ) {
+ return 1;
+ }
+ *ptr++ = '+';
+
+ } else {
+ len = snprintf( ptr, restlen, "%d", ri->ri_num[i] );
+ if ( (restlen -= len) <= 0 || len < 0 ) {
+ return 1;
+ }
+ ptr += len;
+ }
+ }
+
+ bv.bv_val = buf;
+ bv.bv_len = ptr - buf;
+ ber_dupbv( bvout, &bv );
+
+ return 0;
+}
+
+void
+slap_retry_info_destroy(
+ slap_retry_info_t *ri )
+{
+ assert( ri != NULL );
+
+ assert( ri->ri_interval != NULL );
+ ch_free( ri->ri_interval );
+ ri->ri_interval = NULL;
+
+ assert( ri->ri_num != NULL );
+ ch_free( ri->ri_num );
+ ri->ri_num = NULL;
+}
+
+int
+slap_idassert_authzfrom_parse( ConfigArgs *c, slap_idassert_t *si )
+{
+ struct berval bv;
+ struct berval in;
+ int rc;
+
+ if ( strcmp( c->argv[ 1 ], "*" ) == 0
+ || strcmp( c->argv[ 1 ], "dn:*" ) == 0
+ || strcasecmp( c->argv[ 1 ], "dn.regex:.*" ) == 0 )
+ {
+ if ( si->si_authz != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <authz>\": "
+ "\"%s\" conflicts with existing authz rules",
+ c->argv[ 0 ], c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ si->si_flags |= LDAP_BACK_AUTH_AUTHZ_ALL;
+
+ return 0;
+
+ } else if ( ( si->si_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <authz>\": "
+ "\"<authz>\" conflicts with \"*\"", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ ber_str2bv( c->argv[ 1 ], 0, 0, &in );
+ rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <authz>\": "
+ "invalid syntax", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( c->valx == -1 ) {
+ ber_bvarray_add( &si->si_authz, &bv );
+
+ } else {
+ int i = 0;
+ if ( si->si_authz != NULL ) {
+ for ( ; !BER_BVISNULL( &si->si_authz[ i ] ); i++ )
+ ;
+ }
+
+ if ( i <= c->valx ) {
+ ber_bvarray_add( &si->si_authz, &bv );
+
+ } else {
+ BerVarray tmp = ber_memrealloc( si->si_authz,
+ sizeof( struct berval )*( i + 2 ) );
+ if ( tmp == NULL ) {
+ return -1;
+ }
+ si->si_authz = tmp;
+ for ( ; i > c->valx; i-- ) {
+ si->si_authz[ i ] = si->si_authz[ i - 1 ];
+ }
+ si->si_authz[ c->valx ] = bv;
+ }
+ }
+
+ return 0;
+}
+
+static int
+slap_idassert_passthru_parse( ConfigArgs *c, slap_idassert_t *si )
+{
+ struct berval bv;
+ struct berval in;
+ int rc;
+
+ ber_str2bv( c->argv[ 1 ], 0, 0, &in );
+ rc = authzNormalize( 0, NULL, NULL, &in, &bv, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <authz>\": "
+ "invalid syntax", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( c->valx == -1 ) {
+ ber_bvarray_add( &si->si_passthru, &bv );
+
+ } else {
+ int i = 0;
+ if ( si->si_passthru != NULL ) {
+ for ( ; !BER_BVISNULL( &si->si_passthru[ i ] ); i++ )
+ ;
+ }
+
+ if ( i <= c->valx ) {
+ ber_bvarray_add( &si->si_passthru, &bv );
+
+ } else {
+ BerVarray tmp = ber_memrealloc( si->si_passthru,
+ sizeof( struct berval )*( i + 2 ) );
+ if ( tmp == NULL ) {
+ return -1;
+ }
+ si->si_passthru = tmp;
+ for ( ; i > c->valx; i-- ) {
+ si->si_passthru[ i ] = si->si_passthru[ i - 1 ];
+ }
+ si->si_passthru[ c->valx ] = bv;
+ }
+ }
+
+ return 0;
+}
+
+int
+slap_idassert_parse( ConfigArgs *c, slap_idassert_t *si )
+{
+ int i;
+
+ /* set default */
+ si->si_mode = LDAP_BACK_IDASSERT_LEGACY;
+
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( strncasecmp( c->argv[ i ], "mode=", STRLENOF( "mode=" ) ) == 0 ) {
+ char *argvi = c->argv[ i ] + STRLENOF( "mode=" );
+ int j;
+
+ j = verb_to_mask( argvi, idassert_mode );
+ if ( BER_BVISNULL( &idassert_mode[ j ].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "unknown mode \"%s\"",
+ c->argv[0], argvi );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ si->si_mode = idassert_mode[ j ].mask;
+
+ } else if ( strncasecmp( c->argv[ i ], "authz=", STRLENOF( "authz=" ) ) == 0 ) {
+ char *argvi = c->argv[ i ] + STRLENOF( "authz=" );
+
+ if ( strcasecmp( argvi, "native" ) == 0 ) {
+ if ( si->si_bc.sb_method != LDAP_AUTH_SASL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "authz=\"native\" incompatible "
+ "with auth method", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ si->si_flags |= LDAP_BACK_AUTH_NATIVE_AUTHZ;
+
+ } else if ( strcasecmp( argvi, "proxyAuthz" ) == 0 ) {
+ si->si_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "unknown authz \"%s\"",
+ c->argv[0], argvi );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 ) {
+ char *argvi = c->argv[ i ] + STRLENOF( "flags=" );
+ char **flags = ldap_str2charray( argvi, "," );
+ int j, err = 0;
+
+ if ( flags == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "unable to parse flags \"%s\"",
+ c->argv[0], argvi );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; flags[ j ] != NULL; j++ ) {
+
+ if ( strcasecmp( flags[ j ], "override" ) == 0 ) {
+ si->si_flags |= LDAP_BACK_AUTH_OVERRIDE;
+
+ } else if ( strcasecmp( flags[ j ], "prescriptive" ) == 0 ) {
+ si->si_flags |= LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+ } else if ( strcasecmp( flags[ j ], "non-prescriptive" ) == 0 ) {
+ si->si_flags &= ( ~LDAP_BACK_AUTH_PRESCRIPTIVE );
+
+ } else if ( strcasecmp( flags[ j ], "obsolete-proxy-authz" ) == 0 ) {
+ if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "\"obsolete-proxy-authz\" flag "
+ "incompatible with previously issued \"obsolete-encoding-workaround\" flag.",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ err = 1;
+ break;
+
+ } else {
+ si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ;
+ }
+
+ } else if ( strcasecmp( flags[ j ], "obsolete-encoding-workaround" ) == 0 ) {
+ if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "\"obsolete-encoding-workaround\" flag "
+ "incompatible with previously issued \"obsolete-proxy-authz\" flag.",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ err = 1;
+ break;
+
+ } else {
+ si->si_flags |= LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND;
+ }
+
+ } else if ( strcasecmp( flags[ j ], "proxy-authz-critical" ) == 0 ) {
+ si->si_flags |= LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL;
+
+ } else if ( strcasecmp( flags[ j ], "proxy-authz-non-critical" ) == 0 ) {
+ si->si_flags &= ~LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL;
+
+ } else if ( strcasecmp( flags[ j ], "dn-none" ) == 0 ) {
+ si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK;
+
+ } else if ( strcasecmp( flags[ j ], "dn-authzid" ) == 0 ) {
+ si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK;
+ si->si_flags |= LDAP_BACK_AUTH_DN_AUTHZID;
+
+ } else if ( strcasecmp( flags[ j ], "dn-whoami" ) == 0 ) {
+ si->si_flags &= ~LDAP_BACK_AUTH_DN_MASK;
+ si->si_flags |= LDAP_BACK_AUTH_DN_WHOAMI;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "unknown flag \"%s\"",
+ c->argv[0], flags[ j ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ err = 1;
+ break;
+ }
+ }
+
+ ldap_charray_free( flags );
+ if ( err ) {
+ return 1;
+ }
+
+ } else if ( bindconf_parse( c->argv[ i ], &si->si_bc ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "unable to parse field \"%s\"",
+ c->argv[0], c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+
+ if ( si->si_bc.sb_method == LDAP_AUTH_SIMPLE ) {
+ if ( BER_BVISNULL( &si->si_bc.sb_binddn )
+ || BER_BVISNULL( &si->si_bc.sb_cred ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s <args>\": "
+ "SIMPLE needs \"binddn\" and \"credentials\"", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) {
+ if ( BER_BVISNULL( &si->si_bc.sb_binddn ) &&
+ !(si->si_flags & LDAP_BACK_AUTH_DN_MASK) )
+ {
+ static struct berval authid = BER_BVC("cn=auth");
+ ber_dupbv( &si->si_bc.sb_binddn, &authid );
+ }
+ }
+
+ bindconf_tls_defaults( &si->si_bc );
+#ifdef HAVE_TLS
+ if ( si->si_bc.sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( si->si_bc.sb_tls_ctx );
+ si->si_bc.sb_tls_ctx = NULL;
+ }
+#endif
+
+ return 0;
+}
+
+/* NOTE: temporary, until back-meta is ported to back-config */
+int
+slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si )
+{
+ ConfigArgs c = { 0 };
+ char *argv[ 3 ];
+
+ snprintf( c.log, sizeof( c.log ), "%s: line %d", fname, lineno );
+ c.argc = 2;
+ c.argv = argv;
+ argv[ 0 ] = "idassert-passThru";
+ argv[ 1 ] = (char *)arg;
+ argv[ 2 ] = NULL;
+
+ return slap_idassert_passthru_parse( &c, si );
+}
+
+static int
+ldap_back_cf_gen( ConfigArgs *c )
+{
+ ldapinfo_t *li = ( ldapinfo_t * )c->be->be_private;
+ int rc = 0;
+ int i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ if ( li == NULL ) {
+ return 1;
+ }
+
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI:
+ if ( li->li_uri != NULL ) {
+ struct berval bv, bv2;
+
+ ber_str2bv( li->li_uri, 0, 0, &bv );
+ bv2.bv_len = bv.bv_len + STRLENOF( "\"\"" );
+ bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+ snprintf( bv2.bv_val, bv2.bv_len + 1,
+ "\"%s\"", bv.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv2 );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS: {
+ struct berval bc = BER_BVNULL, bv2;
+ enum_to_verb( tls_mode, ( li->li_flags & LDAP_BACK_F_TLS_MASK ), &bv );
+ assert( !BER_BVISNULL( &bv ) );
+ bindconf_tls_unparse( &li->li_tls, &bc );
+
+ if ( !BER_BVISEMPTY( &bc )) {
+ bv2.bv_len = bv.bv_len + bc.bv_len + 1;
+ bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+ strcpy( bv2.bv_val, bv.bv_val );
+ bv2.bv_val[bv.bv_len] = ' ';
+ strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv2 );
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ ber_memfree( bc.bv_val );
+ }
+ break;
+
+ case LDAP_BACK_CFG_ACL_BIND: {
+ int i;
+
+ if ( li->li_acl_authmethod == LDAP_AUTH_NONE ) {
+ return 1;
+ }
+
+ bindconf_unparse( &li->li_acl, &bv );
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ break;
+ }
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ case LDAP_BACK_CFG_IDASSERT_PASSTHRU: {
+ BerVarray *bvp;
+ int i;
+ struct berval bv = BER_BVNULL;
+ char buf[SLAP_TEXT_BUFLEN];
+
+ switch ( c->type ) {
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break;
+ case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break;
+ default: assert( 0 ); break;
+ }
+
+ if ( *bvp == NULL ) {
+ if ( bvp == &li->li_idassert_authz
+ && ( li->li_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ BER_BVSTR( &bv, "*" );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) {
+ char *ptr;
+ int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len = ((*bvp)[ i ]).bv_len + len;
+ bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ if ( bv.bv_val ) {
+ ber_memfree( bv.bv_val );
+ }
+ break;
+ }
+
+ case LDAP_BACK_CFG_IDASSERT_BIND: {
+ int i;
+ struct berval bc = BER_BVNULL;
+ char *ptr;
+
+ if ( li->li_idassert_authmethod == LDAP_AUTH_NONE ) {
+ return 1;
+ }
+
+ if ( li->li_idassert_authmethod != LDAP_AUTH_NONE ) {
+ ber_len_t len;
+
+ switch ( li->li_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ break;
+
+ default: {
+ struct berval mode = BER_BVNULL;
+
+ enum_to_verb( idassert_mode, li->li_idassert_mode, &mode );
+ if ( BER_BVISNULL( &mode ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ ptr = lutil_strcopy( bv.bv_val, "mode=" );
+ ptr = lutil_strcopy( ptr, mode.bv_val );
+ }
+ break;
+ }
+ }
+
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) {
+ len = bv.bv_len + STRLENOF( "authz=native" );
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ (void)lutil_strcopy( ptr, "authz=native" );
+ }
+
+ len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" );
+ /* flags */
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ ptr = lutil_strcopy( ptr, "flags=" );
+
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ ptr = lutil_strcopy( ptr, "prescriptive" );
+ } else {
+ ptr = lutil_strcopy( ptr, "non-prescriptive" );
+ }
+
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ ptr = lutil_strcopy( ptr, ",override" );
+ }
+
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" );
+
+ } else if ( li->li_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" );
+ }
+
+ if ( li->li_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-critical" );
+
+ } else {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" );
+ }
+
+ switch ( li->li_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) {
+ case LDAP_BACK_AUTH_DN_AUTHZID:
+ ptr = lutil_strcopy( ptr, ",dn-authzid" );
+ break;
+
+ case LDAP_BACK_AUTH_DN_WHOAMI:
+ ptr = lutil_strcopy( ptr, ",dn-whoami" );
+ break;
+
+ default:
+#if 0 /* implicit */
+ ptr = lutil_strcopy( ptr, ",dn-none" );
+#endif
+ break;
+ }
+
+ bv.bv_len = ( ptr - bv.bv_val );
+ /* end-of-flags */
+ }
+
+ bindconf_unparse( &li->li_idassert.si_bc, &bc );
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ ber_len_t len = bv.bv_len + bc.bv_len;
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ assert( bc.bv_val[ 0 ] == ' ' );
+
+ ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val );
+ free( bc.bv_val );
+ bv.bv_len = ptr - bv.bv_val;
+
+ } else {
+ for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bc.bv_len -= i;
+ AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 );
+ }
+
+ bv = bc;
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+
+ break;
+ }
+
+ case LDAP_BACK_CFG_REBIND:
+ c->value_int = LDAP_BACK_SAVECRED( li );
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ c->value_int = LDAP_BACK_CHASE_REFERRALS( li );
+ break;
+
+ case LDAP_BACK_CFG_T_F:
+ enum_to_verb( t_f_mode, (li->li_flags & LDAP_BACK_F_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_WHOAMI:
+ c->value_int = LDAP_BACK_PROXY_WHOAMI( li );
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ BER_BVZERO( &bv );
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( li->li_timeout[ i ] != 0 ) {
+ break;
+ }
+ }
+
+ if ( i == SLAP_OP_LAST ) {
+ return 1;
+ }
+
+ slap_cf_aux_table_unparse( li->li_timeout, &bv, timeout_table );
+
+ if ( BER_BVISNULL( &bv ) ) {
+ return 1;
+ }
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ if ( li->li_idle_timeout == 0 ) {
+ return 1;
+ }
+
+ lutil_unparse_time( buf, sizeof( buf ), li->li_idle_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_CONN_TTL: {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ if ( li->li_conn_ttl == 0 ) {
+ return 1;
+ }
+
+ lutil_unparse_time( buf, sizeof( buf ), li->li_conn_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ if ( li->li_network_timeout == 0 ) {
+ return 1;
+ }
+
+ lutil_unparse_time( buf, sizeof( buf ), li->li_network_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( li->li_version == 0 ) {
+ return 1;
+ }
+
+ c->value_int = li->li_version;
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ c->value_int = LDAP_BACK_SINGLECONN( li );
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ c->value_int = LDAP_BACK_USE_TEMPORARIES( li );
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ c->value_int = li->li_conn_priv_max;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL: {
+ slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2;
+
+ if ( LDAP_BACK_CANCEL_DISCOVER( li ) ) {
+ mask &= ~LDAP_BACK_F_CANCEL_EXOP;
+ }
+ enum_to_verb( cancel_mode, (li->li_flags & mask), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( !LDAP_BACK_QUARANTINE( li ) ) {
+ rc = 1;
+ break;
+ }
+
+ rc = slap_retry_info_unparse( &li->li_quarantine, &bv );
+ if ( rc == 0 ) {
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ c->value_int = LDAP_BACK_ST_REQUEST( li );
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_NOREFS:
+ c->value_int = LDAP_BACK_NOREFS( li );
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ c->value_int = LDAP_BACK_NOUNDEFFILTER( li );
+ break;
+
+ case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA:
+ c->value_int = LDAP_BACK_OMIT_UNKNOWN_SCHEMA( li );
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ enum_to_verb( onerr_mode, li->li_flags & LDAP_BACK_F_ONERR_STOP, &bv );
+ if ( BER_BVISNULL( &bv )) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ char buf[AC_LINE_MAX];
+ bv.bv_len = AC_LINE_MAX;
+ bv.bv_val = &buf[0];
+ slap_keepalive_parse(&bv, &li->li_tls.sb_keepalive, 0, 0, 1);
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ }
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ c->value_uint = li->li_tls.sb_tcp_user_timeout;
+ break;
+
+ default:
+ /* FIXME: we need to handle all... */
+ assert( 0 );
+ break;
+ }
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI:
+ if ( li->li_uri != NULL ) {
+ ch_free( li->li_uri );
+ li->li_uri = NULL;
+
+ assert( li->li_bvuri != NULL );
+ ber_bvarray_free( li->li_bvuri );
+ li->li_bvuri = NULL;
+ }
+
+ /* better cleanup the cached connections... */
+ /* NOTE: don't worry about locking: if we got here,
+ * other threads are suspended. */
+ if ( li->li_conninfo.lai_tree != NULL ) {
+ ldap_tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
+ li->li_conninfo.lai_tree = NULL;
+ }
+
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_ACL_BIND:
+ bindconf_free( &li->li_acl );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ case LDAP_BACK_CFG_IDASSERT_PASSTHRU: {
+ BerVarray *bvp;
+
+ switch ( c->type ) {
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: bvp = &li->li_idassert_authz; break;
+ case LDAP_BACK_CFG_IDASSERT_PASSTHRU: bvp = &li->li_idassert_passthru; break;
+ default: assert( 0 ); break;
+ }
+
+ if ( c->valx < 0 ) {
+ if ( *bvp != NULL ) {
+ ber_bvarray_free( *bvp );
+ *bvp = NULL;
+ }
+
+ } else {
+ int i;
+
+ if ( *bvp == NULL ) {
+ rc = 1;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ )
+ ;
+
+ if ( i >= c->valx ) {
+ rc = 1;
+ break;
+ }
+ ber_memfree( ((*bvp)[ c->valx ]).bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) {
+ (*bvp)[ i ] = (*bvp)[ i + 1 ];
+ }
+ BER_BVZERO( &((*bvp)[ i ]) );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ bindconf_free( &li->li_idassert.si_bc );
+ memset( &li->li_idassert, 0, sizeof( slap_idassert_t ) );
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ case LDAP_BACK_CFG_CHASE:
+ case LDAP_BACK_CFG_T_F:
+ case LDAP_BACK_CFG_WHOAMI:
+ case LDAP_BACK_CFG_CANCEL:
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ li->li_timeout[ i ] = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ li->li_idle_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_CONN_TTL:
+ li->li_conn_ttl = 0;
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ li->li_network_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ li->li_version = 0;
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ li->li_flags &= ~LDAP_BACK_F_SINGLECONN;
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES;
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN;
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( !LDAP_BACK_QUARANTINE( li ) ) {
+ break;
+ }
+
+ slap_retry_info_destroy( &li->li_quarantine );
+ ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex );
+ li->li_isquarantined = 0;
+ li->li_flags &= ~LDAP_BACK_F_QUARANTINE;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ li->li_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_NOREFS:
+ li->li_flags &= ~LDAP_BACK_F_NOREFS;
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ break;
+
+ case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA:
+ li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ li->li_flags &= ~LDAP_BACK_F_ONERR_STOP;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE:
+ li->li_tls.sb_keepalive.sk_idle = 0;
+ li->li_tls.sb_keepalive.sk_probes = 0;
+ li->li_tls.sb_keepalive.sk_interval = 0;
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ li->li_tls.sb_tcp_user_timeout = 0;
+ break;
+
+ default:
+ /* FIXME: we need to handle all... */
+ assert( 0 );
+ break;
+ }
+ return rc;
+
+ }
+
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI: {
+ LDAPURLDesc *tmpludp, *lud;
+ char **urllist = NULL;
+ int urlrc = LDAP_URL_SUCCESS, i;
+
+ if ( li->li_uri != NULL ) {
+ ch_free( li->li_uri );
+ li->li_uri = NULL;
+
+ assert( li->li_bvuri != NULL );
+ ber_bvarray_free( li->li_bvuri );
+ li->li_bvuri = NULL;
+ }
+
+ /* PARANOID: DN and more are not required nor allowed */
+ urlrc = ldap_url_parselist_ext( &lud, c->argv[ 1 ], ", \t", LDAP_PVT_URL_PARSE_NONE );
+ if ( urlrc != LDAP_URL_SUCCESS ) {
+ char *why;
+
+ switch ( urlrc ) {
+ case LDAP_URL_ERR_MEM:
+ why = "no memory";
+ break;
+ case LDAP_URL_ERR_PARAM:
+ why = "parameter is bad";
+ break;
+ case LDAP_URL_ERR_BADSCHEME:
+ why = "URL doesn't begin with \"[c]ldap[si]://\"";
+ break;
+ case LDAP_URL_ERR_BADENCLOSURE:
+ why = "URL is missing trailing \">\"";
+ break;
+ case LDAP_URL_ERR_BADURL:
+ why = "URL is bad";
+ break;
+ case LDAP_URL_ERR_BADHOST:
+ why = "host/port is bad";
+ break;
+ case LDAP_URL_ERR_BADATTRS:
+ why = "bad (or missing) attributes";
+ break;
+ case LDAP_URL_ERR_BADSCOPE:
+ why = "scope string is invalid (or missing)";
+ break;
+ case LDAP_URL_ERR_BADFILTER:
+ why = "bad or missing filter";
+ break;
+ case LDAP_URL_ERR_BADEXTS:
+ why = "bad or missing extensions";
+ break;
+ default:
+ why = "unknown reason";
+ break;
+ }
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse uri \"%s\" "
+ "in \"uri <uri>\" line: %s",
+ c->value_string, why );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ urlrc = 1;
+ goto done_url;
+ }
+
+ for ( i = 0, tmpludp = lud;
+ tmpludp;
+ i++, tmpludp = tmpludp->lud_next )
+ {
+ if ( ( tmpludp->lud_dn != NULL
+ && tmpludp->lud_dn[0] != '\0' )
+ || tmpludp->lud_attrs != NULL
+ /* || tmpludp->lud_scope != LDAP_SCOPE_DEFAULT */
+ || tmpludp->lud_filter != NULL
+ || tmpludp->lud_exts != NULL )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning, only protocol, "
+ "host and port allowed "
+ "in \"uri <uri>\" statement "
+ "for uri #%d of \"%s\"",
+ i, c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ }
+
+ for ( i = 0, tmpludp = lud;
+ tmpludp;
+ i++, tmpludp = tmpludp->lud_next )
+ /* just count */
+ ;
+ urllist = ch_calloc( sizeof( char * ), i + 1 );
+
+ for ( i = 0, tmpludp = lud;
+ tmpludp;
+ i++, tmpludp = tmpludp->lud_next )
+ {
+ LDAPURLDesc tmplud;
+
+ tmplud = *tmpludp;
+ tmplud.lud_dn = "";
+ tmplud.lud_attrs = NULL;
+ tmplud.lud_filter = NULL;
+ if ( !ldap_is_ldapi_url( tmplud.lud_scheme ) ) {
+ tmplud.lud_exts = NULL;
+ tmplud.lud_crit_exts = 0;
+ }
+
+ urllist[ i ] = ldap_url_desc2str( &tmplud );
+
+ if ( urllist[ i ] == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to rebuild uri "
+ "in \"uri <uri>\" statement "
+ "for \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ urlrc = 1;
+ goto done_url;
+ }
+ }
+
+ li->li_uri = ldap_charray2str( urllist, " " );
+ for ( i = 0; urllist[ i ] != NULL; i++ ) {
+ struct berval bv;
+
+ ber_str2bv( urllist[ i ], 0, 0, &bv );
+ ber_bvarray_add( &li->li_bvuri, &bv );
+ urllist[ i ] = NULL;
+ }
+ ldap_memfree( urllist );
+ urllist = NULL;
+
+done_url:;
+ if ( urllist ) {
+ ldap_charray_free( urllist );
+ }
+ if ( lud ) {
+ ldap_free_urllist( lud );
+ }
+ if ( urlrc != LDAP_URL_SUCCESS ) {
+ return 1;
+ }
+ break;
+ }
+
+ case LDAP_BACK_CFG_TLS:
+ i = verb_to_mask( c->argv[1], tls_mode );
+ if ( BER_BVISNULL( &tls_mode[i].word ) ) {
+ return 1;
+ }
+ li->li_flags &= ~LDAP_BACK_F_TLS_MASK;
+ li->li_flags |= tls_mode[i].mask;
+ if ( c->argc > 2 ) {
+ for ( i=2; i<c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &li->li_tls ))
+ return 1;
+ }
+ bindconf_tls_defaults( &li->li_tls );
+ }
+#ifdef HAVE_TLS
+ if ( li->li_tls.sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( li->li_tls.sb_tls_ctx );
+ li->li_tls.sb_tls_ctx = NULL;
+ }
+#endif
+ break;
+
+ case LDAP_BACK_CFG_ACL_BIND:
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( bindconf_parse( c->argv[ i ], &li->li_acl ) ) {
+ return 1;
+ }
+ }
+ bindconf_tls_defaults( &li->li_acl );
+#ifdef HAVE_TLS
+ if ( li->li_acl.sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( li->li_acl.sb_tls_ctx );
+ li->li_acl.sb_tls_ctx = NULL;
+ }
+#endif
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ rc = slap_idassert_authzfrom_parse( c, &li->li_idassert );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_PASSTHRU:
+ rc = slap_idassert_passthru_parse( c, &li->li_idassert );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ rc = slap_idassert_parse( c, &li->li_idassert );
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ if ( c->argc == 1 || c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_SAVECRED;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_SAVECRED;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ if ( c->argc == 1 || c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_CHASE_REFERRALS;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_T_F: {
+ slap_mask_t mask;
+
+ i = verb_to_mask( c->argv[1], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[i].word ) ) {
+ return 1;
+ }
+
+ mask = t_f_mode[i].mask;
+
+ if ( LDAP_BACK_ISOPEN( li )
+ && mask == LDAP_BACK_F_T_F_DISCOVER
+ && !LDAP_BACK_T_F( li ) )
+ {
+ slap_bindconf sb = { BER_BVNULL };
+ int rc;
+
+ if ( li->li_uri == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need URI to discover absolute filters support "
+ "in \"t-f-support discover\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = li->li_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+ LDAP_FEATURE_ABSOLUTE_FILTERS );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mask |= LDAP_BACK_F_T_F;
+ }
+ }
+
+ li->li_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ li->li_flags |= mask;
+ } break;
+
+ case LDAP_BACK_CFG_WHOAMI:
+ if ( c->argc == 1 || c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_PROXY_WHOAMI;
+ load_extop( (struct berval *)&slap_EXOP_WHOAMI,
+ 0, ldap_back_exop_whoami );
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_PROXY_WHOAMI;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) {
+ int j;
+ unsigned u;
+
+ if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; j < SLAP_OP_LAST; j++ ) {
+ li->li_timeout[ j ] = u;
+ }
+
+ continue;
+ }
+
+ if ( slap_cf_aux_table_parse( c->argv[ i ], li->li_timeout, timeout_table, "slapd-ldap timeout" ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse idle timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ li->li_idle_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_CONN_TTL: {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse conn ttl\"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ li->li_conn_ttl = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse network timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ li->li_network_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unsupported version \"%s\" "
+ "in \"protocol-version <version>\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ li->li_version = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_SINGLECONN;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_SINGLECONN;
+ }
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_USE_TEMPORARIES;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_USE_TEMPORARIES;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN
+ || c->value_int > LDAP_BACK_CONN_PRIV_MAX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid max size " "of privileged "
+ "connections pool \"%s\" "
+ "in \"conn-pool-max <n> "
+ "(must be between %d and %d)\"",
+ c->argv[ 1 ],
+ LDAP_BACK_CONN_PRIV_MIN,
+ LDAP_BACK_CONN_PRIV_MAX );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ li->li_conn_priv_max = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL: {
+ slap_mask_t mask;
+
+ i = verb_to_mask( c->argv[1], cancel_mode );
+ if ( BER_BVISNULL( &cancel_mode[i].word ) ) {
+ return 1;
+ }
+
+ mask = cancel_mode[i].mask;
+
+ if ( LDAP_BACK_ISOPEN( li )
+ && mask == LDAP_BACK_F_CANCEL_EXOP_DISCOVER
+ && !LDAP_BACK_CANCEL( li ) )
+ {
+ slap_bindconf sb = { BER_BVNULL };
+ int rc;
+
+ if ( li->li_uri == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need URI to discover \"cancel\" support "
+ "in \"cancel exop-discover\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = li->li_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+ LDAP_EXOP_CANCEL );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mask |= LDAP_BACK_F_CANCEL_EXOP;
+ }
+ }
+
+ li->li_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ li->li_flags |= mask;
+ } break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "quarantine already defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ rc = slap_retry_info_parse( c->argv[1], &li->li_quarantine,
+ c->cr_msg, sizeof( c->cr_msg ) );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+
+ } else {
+ ldap_pvt_thread_mutex_init( &li->li_quarantine_mutex );
+ /* give it a chance to retry if the pattern gets reset
+ * via back-config */
+ li->li_isquarantined = 0;
+ li->li_flags |= LDAP_BACK_F_QUARANTINE;
+ }
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_ST_REQUEST;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ }
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_NOREFS:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_NOREFS;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_NOREFS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_NOUNDEFFILTER;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ }
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ /* onerr? */
+ i = verb_to_mask( c->argv[1], onerr_mode );
+ if ( BER_BVISNULL( &onerr_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ li->li_flags &= ~LDAP_BACK_F_ONERR_STOP;
+ li->li_flags |= onerr_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_OMIT_UNKNOWN_SCHEMA:
+ if ( c->value_int ) {
+ li->li_flags |= LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA;
+
+ } else {
+ li->li_flags &= ~LDAP_BACK_F_OMIT_UNKNOWN_SCHEMA;
+ }
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ ber_str2bv( c->argv[1], 0, 1, &bv );
+ slap_keepalive_parse( &bv, &li->li_tls.sb_keepalive, 0, 0, 0 );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ li->li_tls.sb_tcp_user_timeout = c->value_uint;
+ break;
+
+ default:
+ /* FIXME: try to catch inconsistencies */
+ assert( 0 );
+ break;
+ }
+
+ return rc;
+}
+
+int
+ldap_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( LDAP_BACK_CFG_LAST );
+
+ bi->bi_cf_ocs = ldapocs;
+
+ rc = config_register_schema( ldapcfg, ldapocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+ldap_pbind_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ void *private = c->be->be_private;
+ int rc;
+
+ c->be->be_private = on->on_bi.bi_private;
+ rc = ldap_back_cf_gen( c );
+ c->be->be_private = private;
+ return rc;
+}
+
+int
+ldap_pbind_init_cf( BackendInfo *bi )
+{
+ bi->bi_cf_ocs = pbindocs;
+
+ return config_register_schema( pbindcfg, pbindocs );
+}
+
+static int
+ldap_back_exop_whoami(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval *bv = NULL;
+
+ if ( op->oq_extended.rs_reqdata != NULL ) {
+ /* no request data should be provided */
+ rs->sr_text = "no request data expected";
+ return rs->sr_err = LDAP_PROTOCOL_ERROR;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s WHOAMI\n",
+ op->o_log_prefix );
+
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_WHOAMI );
+ if( rs->sr_err != LDAP_SUCCESS ) return rs->sr_err;
+
+ /* if auth'd by back-ldap and request is proxied, forward it */
+ if ( op->o_conn->c_authz_backend
+ && !strcmp( op->o_conn->c_authz_backend->be_type, "ldap" )
+ && !dn_match( &op->o_ndn, &op->o_conn->c_ndn ) )
+ {
+ ldapconn_t *lc = NULL;
+ LDAPControl c, *ctrls[2] = {NULL, NULL};
+ LDAPMessage *res;
+ Operation op2 = *op;
+ ber_int_t msgid;
+ int doretry = 1;
+ char *ptr;
+
+ ctrls[0] = &c;
+ op2.o_ndn = op->o_conn->c_ndn;
+ if ( !ldap_back_dobind( &lc, &op2, rs, LDAP_BACK_SENDERR ) ) {
+ return -1;
+ }
+ c.ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ c.ldctl_iscritical = 1;
+ c.ldctl_value.bv_val = op->o_tmpalloc(
+ op->o_ndn.bv_len + STRLENOF( "dn:" ) + 1,
+ op->o_tmpmemctx );
+ c.ldctl_value.bv_len = op->o_ndn.bv_len + 3;
+ ptr = c.ldctl_value.bv_val;
+ ptr = lutil_strcopy( ptr, "dn:" );
+ ptr = lutil_strncopy( ptr, op->o_ndn.bv_val, op->o_ndn.bv_len );
+ ptr[ 0 ] = '\0';
+
+retry:
+ rs->sr_err = ldap_whoami( lc->lc_ld, ctrls, NULL, &msgid );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ /* by now, make sure no timeout is used (ITS#6282) */
+ struct timeval tv = { -1, 0 };
+ if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) {
+ ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER,
+ &rs->sr_err );
+ if ( rs->sr_err == LDAP_SERVER_DOWN && doretry ) {
+ doretry = 0;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ goto retry;
+ }
+ }
+
+ } else {
+ /* NOTE: are we sure "bv" will be malloc'ed
+ * with the appropriate memory? */
+ rs->sr_err = ldap_parse_whoami( lc->lc_ld, res, &bv );
+ ldap_msgfree(res);
+ }
+ }
+ op->o_tmpfree( c.ldctl_value.bv_val, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ }
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( (ldapinfo_t *)op2.o_bd->be_private, lc );
+ }
+
+ } else {
+ /* else just do the same as before */
+ bv = (struct berval *) ch_malloc( sizeof( struct berval ) );
+ if ( !BER_BVISEMPTY( &op->o_dn ) ) {
+ bv->bv_len = op->o_dn.bv_len + STRLENOF( "dn:" );
+ bv->bv_val = ch_malloc( bv->bv_len + 1 );
+ AC_MEMCPY( bv->bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &bv->bv_val[ STRLENOF( "dn:" ) ], op->o_dn.bv_val,
+ op->o_dn.bv_len );
+ bv->bv_val[ bv->bv_len ] = '\0';
+
+ } else {
+ bv->bv_len = 0;
+ bv->bv_val = NULL;
+ }
+ }
+
+ rs->sr_rspdata = bv;
+ return rs->sr_err;
+}
+
+
diff --git a/servers/slapd/back-ldap/delete.c b/servers/slapd/back-ldap/delete.c
new file mode 100644
index 0000000..470aa0b
--- /dev/null
+++ b/servers/slapd/back-ldap/delete.c
@@ -0,0 +1,85 @@
+/* delete.c - ldap backend delete function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ int rc = LDAP_SUCCESS;
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+retry:
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_delete_ext( lc->lc_ld, op->o_req_dn.bv_val,
+ ctrls, NULL, &msgid );
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_DELETE ],
+ ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_DELETE ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+cleanup:
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-ldap/distproc.c b/servers/slapd/back-ldap/distproc.c
new file mode 100644
index 0000000..a2417a3
--- /dev/null
+++ b/servers/slapd/back-ldap/distproc.c
@@ -0,0 +1,998 @@
+/* distproc.c - implement distributed procedures */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ * Based on back-ldap and slapo-chain, developed by Howard Chu
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#ifdef SLAP_DISTPROC
+
+#include "back-ldap.h"
+
+#include "slap-config.h"
+
+/*
+ * From <draft-sermersheim-ldap-distproc>
+ *
+
+ ContinuationReference ::= SET {
+ referralURI [0] SET SIZE (1..MAX) OF URI,
+ localReference [2] LDAPDN,
+ referenceType [3] ReferenceType,
+ remainingName [4] RelativeLDAPDN OPTIONAL,
+ searchScope [5] SearchScope OPTIONAL,
+ searchedSubtrees [6] SearchedSubtrees OPTIONAL,
+ failedName [7] LDAPDN OPTIONAL,
+ ... }
+
+ ReferenceType ::= ENUMERATED {
+ superior (0),
+ subordinate (1),
+ cross (2),
+ nonSpecificSubordinate (3),
+ supplier (4),
+ master (5),
+ immediateSuperior (6),
+ self (7),
+ ... }
+
+ SearchScope ::= ENUMERATED {
+ baseObject (0),
+ singleLevel (1),
+ wholeSubtree (2),
+ subordinateSubtree (3),
+ ... }
+
+ SearchedSubtrees ::= SET OF RelativeLDAPDN
+
+ LDAPDN, RelativeLDAPDN, and LDAPString, are defined in [RFC2251].
+
+ */
+
+typedef enum ReferenceType_t {
+ LDAP_DP_RT_UNKNOWN = -1,
+ LDAP_DP_RT_SUPERIOR = 0,
+ LDAP_DP_RT_SUBORDINATE = 1,
+ LDAP_DP_RT_CROSS = 2,
+ LDAP_DP_RT_NONSPECIFICSUBORDINATE = 3,
+ LDAP_DP_RT_SUPPLIER = 4,
+ LDAP_DP_RT_MASTER = 5,
+ LDAP_DP_RT_IMMEDIATESUPERIOR = 6,
+ LDAP_DP_RT_SELF = 7,
+ LDAP_DP_RT_LAST
+} ReferenceType_t;
+
+typedef enum SearchScope_t {
+ LDAP_DP_SS_UNKNOWN = -1,
+ LDAP_DP_SS_BASEOBJECT = 0,
+ LDAP_DP_SS_SINGLELEVEL = 1,
+ LDAP_DP_SS_WHOLESUBTREE = 2,
+ LDAP_DP_SS_SUBORDINATESUBTREE = 3,
+ LDAP_DP_SS_LAST
+} SearchScope_t;
+
+typedef struct ContinuationReference_t {
+ BerVarray cr_referralURI;
+ /* ? [1] ? */
+ struct berval cr_localReference;
+ ReferenceType_t cr_referenceType;
+ struct berval cr_remainingName;
+ SearchScope_t cr_searchScope;
+ BerVarray cr_searchedSubtrees;
+ struct berval cr_failedName;
+} ContinuationReference_t;
+#define CR_INIT { NULL, BER_BVNULL, LDAP_DP_RT_UNKNOWN, BER_BVNULL, LDAP_DP_SS_UNKNOWN, NULL, BER_BVNULL }
+
+#ifdef unused
+static struct berval bv2rt[] = {
+ BER_BVC( "superior" ),
+ BER_BVC( "subordinate" ),
+ BER_BVC( "cross" ),
+ BER_BVC( "nonSpecificSubordinate" ),
+ BER_BVC( "supplier" ),
+ BER_BVC( "master" ),
+ BER_BVC( "immediateSuperior" ),
+ BER_BVC( "self" ),
+ BER_BVNULL
+};
+
+static struct berval bv2ss[] = {
+ BER_BVC( "baseObject" ),
+ BER_BVC( "singleLevel" ),
+ BER_BVC( "wholeSubtree" ),
+ BER_BVC( "subordinateSubtree" ),
+ BER_BVNULL
+};
+
+static struct berval *
+ldap_distproc_rt2bv( ReferenceType_t rt )
+{
+ return &bv2rt[ rt ];
+}
+
+static const char *
+ldap_distproc_rt2str( ReferenceType_t rt )
+{
+ return bv2rt[ rt ].bv_val;
+}
+
+static ReferenceType_t
+ldap_distproc_bv2rt( struct berval *bv )
+{
+ ReferenceType_t rt;
+
+ for ( rt = 0; !BER_BVISNULL( &bv2rt[ rt ] ); rt++ ) {
+ if ( ber_bvstrcasecmp( bv, &bv2rt[ rt ] ) == 0 ) {
+ return rt;
+ }
+ }
+
+ return LDAP_DP_RT_UNKNOWN;
+}
+
+static ReferenceType_t
+ldap_distproc_str2rt( const char *s )
+{
+ struct berval bv;
+
+ ber_str2bv( s, 0, 0, &bv );
+ return ldap_distproc_bv2rt( &bv );
+}
+
+static struct berval *
+ldap_distproc_ss2bv( SearchScope_t ss )
+{
+ return &bv2ss[ ss ];
+}
+
+static const char *
+ldap_distproc_ss2str( SearchScope_t ss )
+{
+ return bv2ss[ ss ].bv_val;
+}
+
+static SearchScope_t
+ldap_distproc_bv2ss( struct berval *bv )
+{
+ ReferenceType_t ss;
+
+ for ( ss = 0; !BER_BVISNULL( &bv2ss[ ss ] ); ss++ ) {
+ if ( ber_bvstrcasecmp( bv, &bv2ss[ ss ] ) == 0 ) {
+ return ss;
+ }
+ }
+
+ return LDAP_DP_SS_UNKNOWN;
+}
+
+static SearchScope_t
+ldap_distproc_str2ss( const char *s )
+{
+ struct berval bv;
+
+ ber_str2bv( s, 0, 0, &bv );
+ return ldap_distproc_bv2ss( &bv );
+}
+#endif /* unused */
+
+/*
+ * NOTE: this overlay assumes that the chainingBehavior control
+ * is registered by the chain overlay; it may move here some time.
+ * This overlay provides support for that control as well.
+ */
+
+
+static int sc_returnContRef;
+#define o_returnContRef o_ctrlflag[sc_returnContRef]
+#define get_returnContRef(op) ((op)->o_returnContRef & SLAP_CONTROL_MASK)
+
+static struct berval slap_EXOP_CHAINEDREQUEST = BER_BVC( LDAP_EXOP_X_CHAINEDREQUEST );
+static struct berval slap_FEATURE_CANCHAINOPS = BER_BVC( LDAP_FEATURE_X_CANCHAINOPS );
+
+static BackendInfo *lback;
+
+typedef struct ldap_distproc_t {
+ /* "common" configuration info (anything occurring before an "uri") */
+ ldapinfo_t *lc_common_li;
+
+ /* current configuration info */
+ ldapinfo_t *lc_cfg_li;
+
+ /* tree of configured[/generated?] "uri" info */
+ ldap_avl_info_t lc_lai;
+
+ unsigned lc_flags;
+#define LDAP_DISTPROC_F_NONE (0x00U)
+#define LDAP_DISTPROC_F_CHAINING (0x01U)
+#define LDAP_DISTPROC_F_CACHE_URI (0x10U)
+
+#define LDAP_DISTPROC_CHAINING( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CHAINING ) == LDAP_DISTPROC_F_CHAINING )
+#define LDAP_DISTPROC_CACHE_URI( lc ) ( ( (lc)->lc_flags & LDAP_DISTPROC_F_CACHE_URI ) == LDAP_DISTPROC_F_CACHE_URI )
+
+} ldap_distproc_t;
+
+static int ldap_distproc_db_init_common( BackendDB *be );
+static int ldap_distproc_db_init_one( BackendDB *be );
+#define ldap_distproc_db_open_one(be) (lback)->bi_db_open( (be) )
+#define ldap_distproc_db_close_one(be) (0)
+#define ldap_distproc_db_destroy_one(be, ca) (lback)->bi_db_destroy( (be), (ca) )
+
+static int
+ldap_distproc_uri_cmp( const void *c1, const void *c2 )
+{
+ const ldapinfo_t *li1 = (const ldapinfo_t *)c1;
+ const ldapinfo_t *li2 = (const ldapinfo_t *)c2;
+
+ assert( li1->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) );
+
+ assert( li2->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) );
+
+ /* If local DNs don't match, it is definitely not a match */
+ return ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] );
+}
+
+static int
+ldap_distproc_uri_dup( void *c1, void *c2 )
+{
+ ldapinfo_t *li1 = (ldapinfo_t *)c1;
+ ldapinfo_t *li2 = (ldapinfo_t *)c2;
+
+ assert( li1->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li1->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li1->li_bvuri[ 1 ] ) );
+
+ assert( li2->li_bvuri != NULL );
+ assert( !BER_BVISNULL( &li2->li_bvuri[ 0 ] ) );
+ assert( BER_BVISNULL( &li2->li_bvuri[ 1 ] ) );
+
+ /* Cannot have more than one shared session with same DN */
+ if ( ber_bvcmp( &li1->li_bvuri[ 0 ], &li2->li_bvuri[ 0 ] ) == 0 ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+ldap_distproc_operational( Operation *op, SlapReply *rs )
+{
+ /* Trap entries generated by back-ldap.
+ *
+ * FIXME: we need a better way to recognize them; a cleaner
+ * solution would be to be able to intercept the response
+ * of be_operational(), so that we can divert only those
+ * calls that fail because operational attributes were
+ * requested for entries that do not belong to the underlying
+ * database. This fix is likely to intercept also entries
+ * generated by back-perl and so. */
+ if ( rs->sr_entry->e_private == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ldap_distproc_response( Operation *op, SlapReply *rs )
+{
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * configuration...
+ */
+
+enum {
+ /* NOTE: the chaining behavior control is registered
+ * by the chain overlay; it may move here some time */
+ DP_CHAINING = 1,
+ DP_CACHE_URI,
+
+ DP_LAST
+};
+
+static ConfigDriver distproc_cfgen;
+static ConfigCfAdd distproc_cfadd;
+static ConfigLDAPadd distproc_ldadd;
+
+static ConfigTable distproc_cfg[] = {
+ { "distproc-chaining", "args",
+ 2, 4, 0, ARG_MAGIC|ARG_BERVAL|DP_CHAINING, distproc_cfgen,
+ /* NOTE: using the same attributeTypes defined
+ * for the "chain" overlay */
+ "( OLcfgOvAt:3.1 NAME 'olcChainingBehavior' "
+ "DESC 'Chaining behavior control parameters (draft-sermersheim-ldap-chaining)' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "distproc-cache-uri", "TRUE/FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DP_CACHE_URI, distproc_cfgen,
+ "( OLcfgOvAt:3.2 NAME 'olcChainCacheURI' "
+ "DESC 'Enables caching of URIs not present in configuration' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs distproc_ocs[] = {
+ { "( OLcfgOvOc:7.1 "
+ "NAME 'olcDistProcConfig' "
+ "DESC 'Distributed procedures <draft-sermersheim-ldap-distproc> configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcChainingBehavior $ "
+ "olcChainCacheURI "
+ ") )",
+ Cft_Overlay, distproc_cfg, NULL, distproc_cfadd },
+ { "( OLcfgOvOc:7.2 "
+ "NAME 'olcDistProcDatabase' "
+ "DESC 'Distributed procedure remote server configuration' "
+ "AUXILIARY )",
+ Cft_Misc, distproc_cfg, distproc_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int
+distproc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ ldap_distproc_t *lc;
+
+ ldapinfo_t *li;
+
+ AttributeDescription *ad = NULL;
+ Attribute *at;
+ const char *text;
+
+ int rc;
+
+ if ( p->ce_type != Cft_Overlay
+ || !p->ce_bi
+ || p->ce_bi->bi_cf_ocs != distproc_ocs )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ on = (slap_overinst *)p->ce_bi;
+ lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ assert( ca->be == NULL );
+ ca->be = (BackendDB *)ch_calloc( 1, sizeof( BackendDB ) );
+
+ ca->be->bd_info = (BackendInfo *)on;
+
+ rc = slap_str2ad( "olcDbURI", &ad, &text );
+ assert( rc == LDAP_SUCCESS );
+
+ at = attr_find( e->e_attrs, ad );
+ if ( lc->lc_common_li == NULL && at != NULL ) {
+ /* FIXME: we should generate an empty default entry
+ * if none is supplied */
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "first underlying database \"%s\" "
+ "cannot contain attribute \"%s\".\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+
+ } else if ( lc->lc_common_li != NULL && at == NULL ) {
+ /* FIXME: we should generate an empty default entry
+ * if none is supplied */
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "subsequent underlying database \"%s\" "
+ "must contain attribute \"%s\".\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( lc->lc_common_li == NULL ) {
+ rc = ldap_distproc_db_init_common( ca->be );
+
+ } else {
+ rc = ldap_distproc_db_init_one( ca->be );
+ }
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "unable to init %sunderlying database \"%s\".\n",
+ lc->lc_common_li == NULL ? "common " : "", e->e_name.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ li = ca->be->be_private;
+
+ if ( lc->lc_common_li == NULL ) {
+ lc->lc_common_li = li;
+
+ } else if ( ldap_tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
+ ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "database \"%s\" insert failed.\n",
+ e->e_name.bv_val );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+done:;
+ if ( rc != LDAP_SUCCESS ) {
+ (void)ldap_distproc_db_destroy_one( ca->be, NULL );
+ ch_free( ca->be );
+ ca->be = NULL;
+ }
+
+ return rc;
+}
+
+typedef struct ldap_distproc_cfadd_apply_t {
+ Operation *op;
+ SlapReply *rs;
+ Entry *p;
+ ConfigArgs *ca;
+ int count;
+} ldap_distproc_cfadd_apply_t;
+
+static void
+ldap_distproc_cfadd_apply(
+ ldapinfo_t *li,
+ Operation *op,
+ SlapReply *rs,
+ Entry *p,
+ ConfigArgs *ca,
+ int count )
+{
+ struct berval bv;
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase={%d}%s", count, lback->bi_type );
+ bv.bv_val = ca->cr_msg;
+
+ ca->be->be_private = (void *)li;
+ config_build_entry( op, rs, p->e_private, ca,
+ &bv, lback->bi_cf_ocs, &distproc_ocs[ 1 ] );
+
+ return;
+}
+
+static int
+distproc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ CfEntryInfo *pe = p->e_private;
+ slap_overinst *on = (slap_overinst *)pe->ce_bi;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+ void *priv = (void *)ca->be->be_private;
+ TAvlnode *edge;
+ int count = 0;
+
+ if ( lback->bi_cf_ocs ) {
+ ldap_distproc_cfadd_apply_t lca = { 0 };
+
+ lca.op = op;
+ lca.rs = rs;
+ lca.p = p;
+ lca.ca = ca;
+ lca.count = 0;
+
+ ldap_distproc_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ );
+
+ edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ ldap_distproc_cfadd_apply( li, op, rs, p, ca, count++ );
+ edge = next;
+ }
+
+ ca->be->be_private = priv;
+ }
+
+ return 0;
+}
+
+static int
+distproc_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case DP_CACHE_URI:
+ c->value_int = LDAP_DISTPROC_CACHE_URI( lc );
+ break;
+
+ default:
+ assert( 0 );
+ rc = 1;
+ }
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case DP_CHAINING:
+ return 1;
+
+ case DP_CACHE_URI:
+ lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI;
+ break;
+
+ default:
+ return 1;
+ }
+ return rc;
+ }
+
+ switch( c->type ) {
+ case DP_CACHE_URI:
+ if ( c->value_int ) {
+ lc->lc_flags |= LDAP_DISTPROC_F_CACHE_URI;
+ } else {
+ lc->lc_flags &= ~LDAP_DISTPROC_F_CACHE_URI;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ return rc;
+}
+
+static int
+ldap_distproc_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_distproc_t *lc = NULL;
+
+ if ( lback == NULL ) {
+ lback = backend_info( "ldap" );
+
+ if ( lback == NULL ) {
+ return 1;
+ }
+ }
+
+ lc = ch_malloc( sizeof( ldap_distproc_t ) );
+ if ( lc == NULL ) {
+ return 1;
+ }
+ memset( lc, 0, sizeof( ldap_distproc_t ) );
+ ldap_pvt_thread_mutex_init( &lc->lc_lai.lai_mutex );
+
+ on->on_bi.bi_private = (void *)lc;
+
+ return 0;
+}
+
+static int
+ldap_distproc_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ int rc = SLAP_CONF_UNKNOWN;
+
+ if ( lc->lc_common_li == NULL ) {
+ void *be_private = be->be_private;
+ ldap_distproc_db_init_common( be );
+ lc->lc_common_li = lc->lc_cfg_li = (ldapinfo_t *)be->be_private;
+ be->be_private = be_private;
+ }
+
+ /* Something for the distproc database? */
+ if ( strncasecmp( argv[ 0 ], "distproc-", STRLENOF( "distproc-" ) ) == 0 ) {
+ char *save_argv0 = argv[ 0 ];
+ BackendInfo *bd_info = be->bd_info;
+ void *be_private = be->be_private;
+ ConfigOCs *be_cf_ocs = be->be_cf_ocs;
+ int is_uri = 0;
+
+ argv[ 0 ] += STRLENOF( "distproc-" );
+
+ if ( strcasecmp( argv[ 0 ], "uri" ) == 0 ) {
+ rc = ldap_distproc_db_init_one( be );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "underlying slapd-ldap initialization failed.\n.",
+ fname, lineno );
+ return 1;
+ }
+ lc->lc_cfg_li = be->be_private;
+ is_uri = 1;
+ }
+
+ /* TODO: add checks on what other slapd-ldap(5) args
+ * should be put in the template; this is not quite
+ * harmful, because attributes that shouldn't don't
+ * get actually used, but the user should at least
+ * be warned.
+ */
+
+ be->bd_info = lback;
+ be->be_private = (void *)lc->lc_cfg_li;
+ be->be_cf_ocs = lback->bi_cf_ocs;
+
+ rc = config_generic_wrapper( be, fname, lineno, argc, argv );
+
+ argv[ 0 ] = save_argv0;
+ be->be_cf_ocs = be_cf_ocs;
+ be->be_private = be_private;
+ be->bd_info = bd_info;
+
+ if ( is_uri ) {
+private_destroy:;
+ if ( rc != 0 ) {
+ BackendDB db = *be;
+
+ db.bd_info = lback;
+ db.be_private = (void *)lc->lc_cfg_li;
+ ldap_distproc_db_destroy_one( &db, NULL );
+ lc->lc_cfg_li = NULL;
+
+ } else {
+ if ( lc->lc_cfg_li->li_bvuri == NULL
+ || BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 0 ] )
+ || !BER_BVISNULL( &lc->lc_cfg_li->li_bvuri[ 1 ] ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "no URI list allowed in slapo-distproc.\n",
+ fname, lineno );
+ rc = 1;
+ goto private_destroy;
+ }
+
+ if ( ldap_tavl_insert( &lc->lc_lai.lai_tree,
+ (caddr_t)lc->lc_cfg_li,
+ ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ "duplicate URI in slapo-distproc.\n",
+ fname, lineno );
+ rc = 1;
+ goto private_destroy;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+enum db_which {
+ db_open = 0,
+ db_close,
+ db_destroy,
+
+ db_last
+};
+
+static int
+ldap_distproc_db_func(
+ BackendDB *be,
+ enum db_which which
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ int rc = 0;
+
+ if ( lc ) {
+ BI_db_func *func = (&lback->bi_db_open)[ which ];
+
+ if ( func != NULL && lc->lc_common_li != NULL ) {
+ BackendDB db = *be;
+
+ db.bd_info = lback;
+ db.be_private = lc->lc_common_li;
+
+ rc = func( &db, NULL );
+
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ if ( lc->lc_lai.lai_tree != NULL ) {
+ TAvlnode *edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ be->be_private = (void *)li;
+ rc = func( &db, NULL );
+ if ( rc == 1 ) {
+ break;
+ }
+ edge = next;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int
+ldap_distproc_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ return ldap_distproc_db_func( be, db_open );
+}
+
+static int
+ldap_distproc_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ return ldap_distproc_db_func( be, db_close );
+}
+
+static int
+ldap_distproc_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ int rc;
+
+ rc = ldap_distproc_db_func( be, db_destroy );
+
+ if ( lc ) {
+ ldap_tavl_free( lc->lc_lai.lai_tree, NULL );
+ ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex );
+ ch_free( lc );
+ }
+
+ return rc;
+}
+
+/*
+ * inits one instance of the slapd-ldap backend, and stores
+ * the private info in be_private of the arg
+ */
+static int
+ldap_distproc_db_init_common(
+ BackendDB *be )
+{
+ BackendInfo *bi = be->bd_info;
+ int t;
+
+ be->bd_info = lback;
+ be->be_private = NULL;
+ t = lback->bi_db_init( be, NULL );
+ if ( t != 0 ) {
+ return t;
+ }
+ be->bd_info = bi;
+
+ return 0;
+}
+
+/*
+ * inits one instance of the slapd-ldap backend, stores
+ * the private info in be_private of the arg and fills
+ * selected fields with data from the template.
+ *
+ * NOTE: add checks about the other fields of the template,
+ * which are ignored and SHOULD NOT be configured by the user.
+ */
+static int
+ldap_distproc_db_init_one(
+ BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+
+ BackendInfo *bi = be->bd_info;
+ ldapinfo_t *li;
+
+ slap_op_t t;
+
+ be->bd_info = lback;
+ be->be_private = NULL;
+ t = lback->bi_db_init( be, NULL );
+ if ( t != 0 ) {
+ return t;
+ }
+ li = (ldapinfo_t *)be->be_private;
+
+ /* copy common data */
+ li->li_nretries = lc->lc_common_li->li_nretries;
+ li->li_flags = lc->lc_common_li->li_flags;
+ li->li_version = lc->lc_common_li->li_version;
+ for ( t = 0; t < SLAP_OP_LAST; t++ ) {
+ li->li_timeout[ t ] = lc->lc_common_li->li_timeout[ t ];
+ }
+ be->bd_info = bi;
+
+ return 0;
+}
+
+static int
+ldap_distproc_connection_destroy(
+ BackendDB *be,
+ Connection *conn
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ ldap_distproc_t *lc = (ldap_distproc_t *)on->on_bi.bi_private;
+ void *private = be->be_private;
+ int rc;
+ TAvlnode *edge;
+
+ be->be_private = NULL;
+ ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
+ edge = ldap_tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+ be->be_private = (void *)li;
+ rc = lback->bi_connection_destroy( be, conn );
+ if ( rc == 1 ) {
+ break;
+ }
+ edge = next;
+ }
+ ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
+ be->be_private = private;
+
+ return rc;
+}
+
+static int
+ldap_distproc_parse_returnContRef_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( get_returnContRef( op ) != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "returnContinuationReference control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "returnContinuationReference control specified with pagedResults control";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "returnContinuationReference control: value must be NULL";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_returnContRef = ctrl->ldctl_iscritical ? SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+ldap_exop_chained_request(
+ Operation *op,
+ SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_STATS, "%s CHAINED REQUEST\n",
+ op->o_log_prefix );
+
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_CHAINEDREQUEST );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ /* by now, just reject requests */
+ rs->sr_text = "under development";
+ return LDAP_UNWILLING_TO_PERFORM;
+}
+
+
+static slap_overinst distproc;
+
+int
+distproc_initialize( void )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( DP_LAST );
+
+ rc = load_extop( (struct berval *)&slap_EXOP_CHAINEDREQUEST,
+ SLAP_EXOP_HIDE, ldap_exop_chained_request );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "unable to register chainedRequest exop: %d.\n",
+ rc );
+ return rc;
+ }
+
+ rc = supported_feature_load( &slap_FEATURE_CANCHAINOPS );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "unable to register canChainOperations supported feature: %d.\n",
+ rc );
+ return rc;
+ }
+
+ rc = register_supported_control( LDAP_CONTROL_X_RETURNCONTREF,
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, NULL,
+ ldap_distproc_parse_returnContRef_ctrl, &sc_returnContRef );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
+ "unable to register returnContinuationReference control: %d.\n",
+ rc );
+ return rc;
+ }
+
+ distproc.on_bi.bi_type = "distproc";
+ distproc.on_bi.bi_db_init = ldap_distproc_db_init;
+ distproc.on_bi.bi_db_config = ldap_distproc_db_config;
+ distproc.on_bi.bi_db_open = ldap_distproc_db_open;
+ distproc.on_bi.bi_db_close = ldap_distproc_db_close;
+ distproc.on_bi.bi_db_destroy = ldap_distproc_db_destroy;
+
+ /* ... otherwise the underlying backend's function would be called,
+ * likely passing an invalid entry; on the contrary, the requested
+ * operational attributes should have been returned while chasing
+ * the referrals. This all in all is a bit messy, because part
+ * of the operational attributes are generated by the backend;
+ * part by the frontend; back-ldap should receive all the available
+ * ones from the remote server, but then, on its own, it strips those
+ * it assumes will be (re)generated by the frontend (e.g.
+ * subschemaSubentry, entryDN, ...) */
+ distproc.on_bi.bi_operational = ldap_distproc_operational;
+
+ distproc.on_bi.bi_connection_destroy = ldap_distproc_connection_destroy;
+
+ distproc.on_response = ldap_distproc_response;
+
+ distproc.on_bi.bi_cf_ocs = distproc_ocs;
+
+ rc = config_register_schema( distproc_cfg, distproc_ocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &distproc );
+}
+
+#endif /* SLAP_DISTPROC */
diff --git a/servers/slapd/back-ldap/extended.c b/servers/slapd/back-ldap/extended.c
new file mode 100644
index 0000000..b6f4ec5
--- /dev/null
+++ b/servers/slapd/back-ldap/extended.c
@@ -0,0 +1,386 @@
+/* extended.c - ldap backend extended routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+#include "lber_pvt.h"
+
+typedef int (ldap_back_exop_f)( Operation *op, SlapReply *rs, ldapconn_t **lc );
+
+static ldap_back_exop_f ldap_back_exop_passwd;
+static ldap_back_exop_f ldap_back_exop_generic;
+
+static struct exop {
+ struct berval oid;
+ ldap_back_exop_f *extended;
+} exop_table[] = {
+ { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), ldap_back_exop_passwd },
+ { BER_BVNULL, NULL }
+};
+
+static int
+ldap_back_extended_one( Operation *op, SlapReply *rs, ldap_back_exop_f exop )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ LDAPControl **ctrls = NULL, **oldctrls = NULL;
+ int rc;
+
+ /* FIXME: this needs to be called here, so it is
+ * called twice; maybe we could avoid the
+ * ldap_back_dobind() call inside each extended()
+ * call ... */
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_DONTSEND ) ) {
+ return rs->sr_err;
+ }
+
+ ctrls = oldctrls = op->o_ctrls;
+ if ( ldap_back_controls_add( op, rs, lc, &ctrls ) )
+ {
+ op->o_ctrls = oldctrls;
+ rc = rs->sr_err;
+ goto done;
+ }
+
+ op->o_ctrls = ctrls;
+ rc = exop( op, rs, &lc );
+
+ op->o_ctrls = oldctrls;
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+done:;
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rc;
+}
+
+int
+ldap_back_extended(
+ Operation *op,
+ SlapReply *rs )
+{
+ int i;
+
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */
+
+ for ( i = 0; exop_table[i].extended != NULL; i++ ) {
+ if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) )
+ {
+ return ldap_back_extended_one( op, rs, exop_table[i].extended );
+ }
+ }
+
+ /* if we get here, the exop is known; the best that we can do
+ * is pass it thru as is */
+ /* FIXME: maybe a list of OIDs to pass thru would be safer */
+ return ldap_back_extended_one( op, rs, ldap_back_exop_generic );
+}
+
+static int
+ldap_back_exop_passwd(
+ Operation *op,
+ SlapReply *rs,
+ ldapconn_t **lcp )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+
+ ldapconn_t *lc = *lcp;
+ req_pwdexop_s *qpw = &op->oq_pwdexop;
+ LDAPMessage *res;
+ ber_int_t msgid;
+ int rc, isproxy, freedn = 0;
+ int do_retry = 1;
+ char *text = NULL;
+ struct berval dn = op->o_req_dn,
+ ndn = op->o_req_ndn;
+
+ assert( lc != NULL );
+ assert( rs->sr_ctrls == NULL );
+
+ if ( BER_BVISNULL( &ndn ) && op->ore_reqdata != NULL ) {
+ /* NOTE: most of this code is mutated
+ * from slap_passwd_parse();
+ * But here we only need
+ * the first berval... */
+
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ struct berval tmpid = BER_BVNULL;
+
+ if ( op->ore_reqdata->bv_len == 0 ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, op->ore_reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+
+ if ( tag == LBER_ERROR ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) {
+ tag = ber_get_stringbv( ber, &tmpid, LBER_BV_NOTERM );
+
+ if ( tag == LBER_ERROR ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &tmpid ) ) {
+ char idNull = tmpid.bv_val[tmpid.bv_len];
+ tmpid.bv_val[tmpid.bv_len] = '\0';
+ rs->sr_err = dnPrettyNormal( NULL, &tmpid, &dn,
+ &ndn, op->o_tmpmemctx );
+ tmpid.bv_val[tmpid.bv_len] = idNull;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ /* should have been successfully parsed earlier! */
+ return rs->sr_err;
+ }
+ freedn = 1;
+
+ } else {
+ dn = op->o_dn;
+ ndn = op->o_ndn;
+ }
+ }
+
+ isproxy = ber_bvcmp( &ndn, &op->o_ndn );
+
+ Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_passwd(\"%s\")%s\n",
+ dn.bv_val, isproxy ? " (proxy)" : "" );
+
+retry:
+ rc = ldap_passwd( lc->lc_ld, &dn,
+ qpw->rs_old.bv_val ? &qpw->rs_old : NULL,
+ qpw->rs_new.bv_val ? &qpw->rs_new : NULL,
+ op->o_ctrls, NULL, &msgid );
+
+ if ( rc == LDAP_SUCCESS ) {
+ /* TODO: set timeout? */
+ /* by now, make sure no timeout is used (ITS#6282) */
+ struct timeval tv = { -1, 0 };
+ if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) {
+ ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc );
+ rs->sr_err = rc;
+
+ } else {
+ /* only touch when activity actually took place... */
+ if ( li->li_idle_timeout ) {
+ lc->lc_time = op->o_time;
+ }
+
+ /* sigh. parse twice, because parse_passwd
+ * doesn't give us the err / match / msg info.
+ */
+ rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
+ (char **)&rs->sr_matched,
+ &text,
+ NULL, &rs->sr_ctrls, 0 );
+
+ if ( rc == LDAP_SUCCESS ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ struct berval newpw;
+
+ /* this never happens because
+ * the frontend is generating
+ * the new password, so when
+ * the passwd exop is proxied,
+ * it never delegates password
+ * generation to the remote server
+ */
+ rc = ldap_parse_passwd( lc->lc_ld, res,
+ &newpw );
+ if ( rc == LDAP_SUCCESS &&
+ !BER_BVISNULL( &newpw ) )
+ {
+ rs->sr_type = REP_EXTENDED;
+ rs->sr_rspdata = slap_passwd_return( &newpw );
+ free( newpw.bv_val );
+ }
+
+ } else {
+ rc = rs->sr_err;
+ }
+ }
+ ldap_msgfree( res );
+ }
+ }
+
+ if ( text ) {
+ /* copy to tmpmem, doesn't need to be freed */
+ rs->sr_text = op->o_tmpalloc( strlen( text ) + 1, op->o_tmpmemctx );
+ strcpy( rs->sr_text, text );
+ ch_free( text );
+ }
+ if ( rs->sr_matched )
+ rs->sr_flags |= REP_MATCHED_MUSTBEFREED;
+ if ( rs->sr_ctrls )
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) {
+ do_retry = 0;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) {
+ goto retry;
+ }
+ }
+
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ } else if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ if ( freedn ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ /* in case, cleanup handler */
+ if ( lc == NULL ) {
+ *lcp = NULL;
+ }
+
+ return rc;
+}
+
+static int
+ldap_back_exop_generic(
+ Operation *op,
+ SlapReply *rs,
+ ldapconn_t **lcp )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+
+ ldapconn_t *lc = *lcp;
+ LDAPMessage *res;
+ ber_int_t msgid;
+ int rc;
+ int do_retry = 1;
+ char *text = NULL;
+
+ Debug( LDAP_DEBUG_ARGS, "==> ldap_back_exop_generic(%s, \"%s\")\n",
+ op->ore_reqoid.bv_val, op->o_req_dn.bv_val );
+ assert( lc != NULL );
+ assert( rs->sr_ctrls == NULL );
+
+retry:
+ rc = ldap_extended_operation( lc->lc_ld,
+ op->ore_reqoid.bv_val, op->ore_reqdata,
+ op->o_ctrls, NULL, &msgid );
+
+ if ( rc == LDAP_SUCCESS ) {
+ /* TODO: set timeout? */
+ /* by now, make sure no timeout is used (ITS#6282) */
+ struct timeval tv = { -1, 0 };
+ if ( ldap_result( lc->lc_ld, msgid, LDAP_MSG_ALL, &tv, &res ) == -1 ) {
+ ldap_get_option( lc->lc_ld, LDAP_OPT_ERROR_NUMBER, &rc );
+ rs->sr_err = rc;
+
+ } else {
+ /* only touch when activity actually took place... */
+ if ( li->li_idle_timeout ) {
+ lc->lc_time = op->o_time;
+ }
+
+ /* sigh. parse twice, because parse_passwd
+ * doesn't give us the err / match / msg info.
+ */
+ rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
+ (char **)&rs->sr_matched,
+ &text,
+ NULL, &rs->sr_ctrls, 0 );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rc = ldap_parse_extended_result( lc->lc_ld, res,
+ (char **)&rs->sr_rspoid, &rs->sr_rspdata, 0 );
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_type = REP_EXTENDED;
+ }
+
+ } else {
+ rc = rs->sr_err;
+ }
+ }
+ ldap_msgfree( res );
+ }
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && do_retry ) {
+ do_retry = 0;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) {
+ goto retry;
+ }
+ }
+
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+ } else if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_EXTENDED ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ if ( text ) {
+ /* copy to tmpmem, doesn't need to be freed */
+ rs->sr_text = op->o_tmpalloc( strlen( text ) + 1, op->o_tmpmemctx );
+ strcpy( rs->sr_text, text );
+ ch_free( text );
+ }
+ if ( rs->sr_matched )
+ rs->sr_flags |= REP_MATCHED_MUSTBEFREED;
+ if ( rs->sr_ctrls )
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+
+ /* in case, cleanup handler */
+ if ( lc == NULL ) {
+ *lcp = NULL;
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/back-ldap/init.c b/servers/slapd/back-ldap/init.c
new file mode 100644
index 0000000..e2db2a0
--- /dev/null
+++ b/servers/slapd/back-ldap/init.c
@@ -0,0 +1,374 @@
+/* init.c - initialize ldap backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "back-ldap.h"
+#include "ldap_rq.h"
+
+static const ldap_extra_t ldap_extra = {
+ ldap_back_proxy_authz_ctrl,
+ ldap_back_controls_free,
+ slap_idassert_authzfrom_parse,
+ slap_idassert_passthru_parse_cf,
+ slap_idassert_parse,
+ slap_retry_info_destroy,
+ slap_retry_info_parse,
+ slap_retry_info_unparse,
+ ldap_back_connid2str
+};
+
+int
+ldap_back_open( BackendInfo *bi )
+{
+ bi->bi_controls = slap_known_controls;
+ return 0;
+}
+
+int
+ldap_back_initialize( BackendInfo *bi )
+{
+ int rc;
+
+ bi->bi_flags =
+#ifdef LDAP_DYNAMIC_OBJECTS
+ /* this is set because all the support a proxy has to provide
+ * is the capability to forward the refresh exop, and to
+ * pass thru entries that contain the dynamicObject class
+ * and the entryTtl attribute */
+ SLAP_BFLAG_DYNAMIC |
+#endif /* LDAP_DYNAMIC_OBJECTS */
+
+ /* back-ldap recognizes RFC4525 increment;
+ * let the remote server complain, if needed (ITS#5912) */
+ SLAP_BFLAG_INCREMENT;
+
+ bi->bi_open = ldap_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = ldap_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = ldap_back_db_open;
+ bi->bi_db_close = ldap_back_db_close;
+ bi->bi_db_destroy = ldap_back_db_destroy;
+
+ bi->bi_op_bind = ldap_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = ldap_back_search;
+ bi->bi_op_compare = ldap_back_compare;
+ bi->bi_op_modify = ldap_back_modify;
+ bi->bi_op_modrdn = ldap_back_modrdn;
+ bi->bi_op_add = ldap_back_add;
+ bi->bi_op_delete = ldap_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = ldap_back_extended;
+
+ bi->bi_chk_referrals = 0;
+ bi->bi_entry_get_rw = ldap_back_entry_get;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = ldap_back_conn_destroy;
+
+ bi->bi_extra = (void *)&ldap_extra;
+
+ rc = ldap_back_init_cf( bi );
+ if ( rc ) {
+ return rc;
+ }
+
+ rc = chain_initialize();
+ if ( rc ) {
+ return rc;
+ }
+
+ rc = pbind_initialize();
+ if ( rc ) {
+ return rc;
+ }
+
+#ifdef SLAP_DISTPROC
+ rc = distproc_initialize();
+ if ( rc ) {
+ return rc;
+ }
+#endif
+ return rc;
+}
+
+int
+ldap_back_db_init( Backend *be, ConfigReply *cr )
+{
+ ldapinfo_t *li;
+ int rc;
+ unsigned i;
+
+ li = (ldapinfo_t *)ch_calloc( 1, sizeof( ldapinfo_t ) );
+ if ( li == NULL ) {
+ return -1;
+ }
+
+ li->li_rebind_f = ldap_back_default_rebind;
+ li->li_urllist_f = ldap_back_default_urllist;
+ li->li_urllist_p = li;
+ ldap_pvt_thread_mutex_init( &li->li_uri_mutex );
+
+ BER_BVZERO( &li->li_acl_authcID );
+ BER_BVZERO( &li->li_acl_authcDN );
+ BER_BVZERO( &li->li_acl_passwd );
+
+ li->li_acl_authmethod = LDAP_AUTH_NONE;
+ BER_BVZERO( &li->li_acl_sasl_mech );
+ li->li_acl.sb_tls = SB_TLS_DEFAULT;
+
+ li->li_idassert_mode = LDAP_BACK_IDASSERT_LEGACY;
+
+ BER_BVZERO( &li->li_idassert_authcID );
+ BER_BVZERO( &li->li_idassert_authcDN );
+ BER_BVZERO( &li->li_idassert_passwd );
+
+ BER_BVZERO( &li->li_idassert_authzID );
+
+ li->li_idassert_authmethod = LDAP_AUTH_NONE;
+ BER_BVZERO( &li->li_idassert_sasl_mech );
+ li->li_idassert_tls = SB_TLS_DEFAULT;
+
+ /* by default, use proxyAuthz control on each operation */
+ li->li_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+ li->li_idassert_authz = NULL;
+
+ /* initialize flags */
+ li->li_flags = LDAP_BACK_F_CHASE_REFERRALS;
+
+ /* initialize version */
+ li->li_version = LDAP_VERSION3;
+
+ ldap_pvt_thread_mutex_init( &li->li_conninfo.lai_mutex );
+
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ li->li_conn_priv[ i ].lic_num = 0;
+ LDAP_TAILQ_INIT( &li->li_conn_priv[ i ].lic_priv );
+ }
+ li->li_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT;
+
+ ldap_pvt_thread_mutex_init( &li->li_counter_mutex );
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ ldap_pvt_mp_init( li->li_ops_completed[ i ] );
+ }
+
+ li->li_conn_expire_task = NULL;
+
+ be->be_private = li;
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_NOLASTMOD;
+
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ rc = ldap_back_monitor_db_init( be );
+ if ( rc != 0 ) {
+ /* ignore, by now */
+ rc = 0;
+ }
+
+ return rc;
+}
+
+int
+ldap_back_db_open( BackendDB *be, ConfigReply *cr )
+{
+ ldapinfo_t *li = (ldapinfo_t *)be->be_private;
+
+ slap_bindconf sb = { BER_BVNULL };
+ int rc = 0;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "ldap_back_db_open: URI=%s\n",
+ li->li_uri != NULL ? li->li_uri : "" );
+
+ /* by default, use proxyAuthz control on each operation */
+ switch ( li->li_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ case LDAP_BACK_IDASSERT_SELF:
+ /* however, since admin connections are pooled and shared,
+ * only static authzIDs can be native */
+ li->li_idassert_flags &= ~LDAP_BACK_AUTH_NATIVE_AUTHZ;
+ break;
+
+ default:
+ break;
+ }
+
+ ber_str2bv( li->li_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = li->li_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ if ( LDAP_BACK_T_F_DISCOVER( li ) && !LDAP_BACK_T_F( li ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+ LDAP_FEATURE_ABSOLUTE_FILTERS );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ li->li_flags |= LDAP_BACK_F_T_F;
+ }
+ }
+
+ if ( LDAP_BACK_CANCEL_DISCOVER( li ) && !LDAP_BACK_CANCEL( li ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+ LDAP_EXOP_CANCEL );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ li->li_flags |= LDAP_BACK_F_CANCEL_EXOP;
+ }
+ }
+
+ /* monitor setup */
+ rc = ldap_back_monitor_db_open( be );
+ if ( rc != 0 ) {
+ /* ignore by now */
+ rc = 0;
+ }
+
+ li->li_flags |= LDAP_BACK_F_ISOPEN;
+
+ return rc;
+}
+
+void
+ldap_back_conn_free( void *v_lc )
+{
+ ldapconn_t *lc = v_lc;
+
+ if ( lc->lc_ld != NULL ) {
+ ldap_unbind_ext( lc->lc_ld, NULL, NULL );
+ }
+ if ( !BER_BVISNULL( &lc->lc_bound_ndn ) ) {
+ ch_free( lc->lc_bound_ndn.bv_val );
+ }
+ if ( !BER_BVISNULL( &lc->lc_cred ) ) {
+ memset( lc->lc_cred.bv_val, 0, lc->lc_cred.bv_len );
+ ch_free( lc->lc_cred.bv_val );
+ }
+ if ( !BER_BVISNULL( &lc->lc_local_ndn ) ) {
+ ch_free( lc->lc_local_ndn.bv_val );
+ }
+ lc->lc_q.tqe_prev = NULL;
+ lc->lc_q.tqe_next = NULL;
+ ch_free( lc );
+}
+
+int
+ldap_back_db_close( Backend *be, ConfigReply *cr )
+{
+ int rc = 0;
+
+ if ( be->be_private ) {
+ rc = ldap_back_monitor_db_close( be );
+ }
+
+ return rc;
+}
+
+int
+ldap_back_db_destroy( Backend *be, ConfigReply *cr )
+{
+ if ( be->be_private ) {
+ ldapinfo_t *li = ( ldapinfo_t * )be->be_private;
+ unsigned i;
+
+ (void)ldap_back_monitor_db_destroy( be );
+
+ /* Stop and remove the task that prunes expired connections */
+ if ( li->li_conn_expire_task != NULL ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+ if ( li->li_uri != NULL ) {
+ ch_free( li->li_uri );
+ li->li_uri = NULL;
+
+ assert( li->li_bvuri != NULL );
+ ber_bvarray_free( li->li_bvuri );
+ li->li_bvuri = NULL;
+ }
+
+ bindconf_free( &li->li_tls );
+ bindconf_free( &li->li_acl );
+ bindconf_free( &li->li_idassert.si_bc );
+
+ if ( li->li_idassert_authz != NULL ) {
+ ber_bvarray_free( li->li_idassert_authz );
+ li->li_idassert_authz = NULL;
+ }
+ if ( li->li_conninfo.lai_tree ) {
+ ldap_tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
+ }
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ while ( !LDAP_TAILQ_EMPTY( &li->li_conn_priv[ i ].lic_priv ) ) {
+ ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ i ].lic_priv );
+
+ LDAP_TAILQ_REMOVE( &li->li_conn_priv[ i ].lic_priv, lc, lc_q );
+ ldap_back_conn_free( lc );
+ }
+ }
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ slap_retry_info_destroy( &li->li_quarantine );
+ ldap_pvt_thread_mutex_destroy( &li->li_quarantine_mutex );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+ ldap_pvt_thread_mutex_destroy( &li->li_conninfo.lai_mutex );
+ ldap_pvt_thread_mutex_destroy( &li->li_uri_mutex );
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ ldap_pvt_mp_clear( li->li_ops_completed[ i ] );
+ }
+ ldap_pvt_thread_mutex_destroy( &li->li_counter_mutex );
+ }
+
+ ch_free( be->be_private );
+
+ return 0;
+}
+
+#if SLAPD_LDAP == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( ldap )
+
+#endif /* SLAPD_LDAP == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-ldap/modify.c b/servers/slapd/back-ldap/modify.c
new file mode 100644
index 0000000..53e8a68
--- /dev/null
+++ b/servers/slapd/back-ldap/modify.c
@@ -0,0 +1,136 @@
+/* modify.c - ldap backend modify function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_modify(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ LDAPMod **modv = NULL,
+ *mods = NULL;
+ Modifications *ml;
+ int i, j, rc;
+ ber_int_t msgid;
+ int isupdate;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ for ( i = 0, ml = op->orm_modlist; ml; i++, ml = ml->sml_next )
+ /* just count mods */ ;
+
+ modv = (LDAPMod **)ch_malloc( ( i + 1 )*sizeof( LDAPMod * )
+ + i*sizeof( LDAPMod ) );
+ if ( modv == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto cleanup;
+ }
+ mods = (LDAPMod *)&modv[ i + 1 ];
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ modv[ i ] = &mods[ i ];
+ mods[ i ].mod_op = ( ml->sml_op | LDAP_MOD_BVALUES );
+ mods[ i ].mod_type = ml->sml_desc->ad_cname.bv_val;
+
+ if ( ml->sml_values != NULL ) {
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+ /* just count mods */ ;
+ mods[ i ].mod_bvalues =
+ (struct berval **)ch_malloc( ( j + 1 )*sizeof( struct berval * ) );
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+ {
+ mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ];
+ }
+ mods[ i ].mod_bvalues[ j ] = NULL;
+
+ } else {
+ mods[ i ].mod_bvalues = NULL;
+ }
+
+ i++;
+ }
+ modv[ i ] = 0;
+
+retry:;
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_modify_ext( lc->lc_ld, op->o_req_dn.bv_val, modv,
+ ctrls, NULL, &msgid );
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_MODIFY ],
+ ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODIFY ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+cleanup:;
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ for ( i = 0; modv[ i ]; i++ ) {
+ ch_free( modv[ i ]->mod_bvalues );
+ }
+ ch_free( modv );
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-ldap/modrdn.c b/servers/slapd/back-ldap/modrdn.c
new file mode 100644
index 0000000..9c441eb
--- /dev/null
+++ b/servers/slapd/back-ldap/modrdn.c
@@ -0,0 +1,123 @@
+/* modrdn.c - ldap backend modrdn function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_modrdn(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *)op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ int rc = LDAP_SUCCESS;
+ char *newSup = NULL;
+ struct berval newrdn = BER_BVNULL;
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ if ( op->orr_newSup ) {
+ /* needs LDAPv3 */
+ switch ( li->li_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ /* op->o_protocol cannot be anything but LDAPv3,
+ * otherwise wouldn't be here */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ newSup = op->orr_newSup->bv_val;
+ }
+
+ /* NOTE: we need to copy the newRDN in case it was formed
+ * from a DN by simply changing the length (ITS#5397) */
+ newrdn = op->orr_newrdn;
+ if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) {
+ ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+ }
+
+retry:
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_rename( lc->lc_ld, op->o_req_dn.bv_val,
+ newrdn.bv_val, newSup,
+ op->orr_deleteoldrdn, ctrls, NULL, &msgid );
+ rc = ldap_back_op_result( lc, op, rs, msgid,
+ li->li_timeout[ SLAP_OP_MODRDN ],
+ ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_MODRDN ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+cleanup:
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( newrdn.bv_val != op->orr_newrdn.bv_val ) {
+ op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-ldap/monitor.c b/servers/slapd/back-ldap/monitor.c
new file mode 100644
index 0000000..16c2d6f
--- /dev/null
+++ b/servers/slapd/back-ldap/monitor.c
@@ -0,0 +1,1074 @@
+/* monitor.c - monitor ldap backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+#include <ac/stdlib.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include "lutil.h"
+#include "back-ldap.h"
+
+#include "slap-config.h"
+
+static ObjectClass *oc_olmLDAPDatabase;
+static ObjectClass *oc_olmLDAPConnection;
+
+static ObjectClass *oc_monitorContainer;
+static ObjectClass *oc_monitorCounterObject;
+
+static AttributeDescription *ad_olmDbURIList;
+static AttributeDescription *ad_olmDbOperations;
+static AttributeDescription *ad_olmDbBoundDN;
+static AttributeDescription *ad_olmDbConnFlags;
+static AttributeDescription *ad_olmDbConnURI;
+static AttributeDescription *ad_olmDbPeerAddress;
+
+/*
+ * Stolen from back-monitor/operations.c
+ * We don't need the normalized rdn's though.
+ */
+struct ldap_back_monitor_ops_t {
+ struct berval rdn;
+} ldap_back_monitor_op[] = {
+ { BER_BVC( "cn=Bind" ) },
+ { BER_BVC( "cn=Unbind" ) },
+ { BER_BVC( "cn=Search" ) },
+ { BER_BVC( "cn=Compare" ) },
+ { BER_BVC( "cn=Modify" ) },
+ { BER_BVC( "cn=Modrdn" ) },
+ { BER_BVC( "cn=Add" ) },
+ { BER_BVC( "cn=Delete" ) },
+ { BER_BVC( "cn=Abandon" ) },
+ { BER_BVC( "cn=Extended" ) },
+
+ { BER_BVNULL }
+};
+
+/* Corresponds to connection flags in back-ldap.h */
+static struct {
+ unsigned flag;
+ struct berval name;
+} s_flag[] = {
+ { LDAP_BACK_FCONN_ISBOUND, BER_BVC( "bound" ) },
+ { LDAP_BACK_FCONN_ISANON, BER_BVC( "anonymous" ) },
+ { LDAP_BACK_FCONN_ISPRIV, BER_BVC( "privileged" ) },
+ { LDAP_BACK_FCONN_ISTLS, BER_BVC( "TLS" ) },
+ { LDAP_BACK_FCONN_BINDING, BER_BVC( "binding" ) },
+ { LDAP_BACK_FCONN_TAINTED, BER_BVC( "tainted" ) },
+ { LDAP_BACK_FCONN_ABANDON, BER_BVC( "abandon" ) },
+ { LDAP_BACK_FCONN_ISIDASR, BER_BVC( "idassert" ) },
+ { LDAP_BACK_FCONN_CACHED, BER_BVC( "cached" ) },
+
+ { 0 }
+};
+
+
+/*
+ * NOTE: there's some confusion in monitor OID arc;
+ * by now, let's consider:
+ *
+ * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0
+ * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1
+ * LDAP database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.2
+ *
+ * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0
+ * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1
+ * LDAP database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.2
+ */
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "olmLDAPAttributes", "olmDatabaseAttributes:2" },
+ { "olmLDAPObjectClasses", "olmDatabaseObjectClasses:2" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **ad;
+} s_at[] = {
+ { "( olmLDAPAttributes:1 "
+ "NAME ( 'olmDbURIList' ) "
+ "DESC 'List of URIs a proxy is serving; can be modified run-time' "
+ "SUP managedInfo )",
+ &ad_olmDbURIList },
+ { "( olmLDAPAttributes:2 "
+ "NAME ( 'olmDbOperation' ) "
+ "DESC 'monitor operations performed' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbOperations },
+ { "( olmLDAPAttributes:3 "
+ "NAME ( 'olmDbBoundDN' ) "
+ "DESC 'monitor connection authorization DN' "
+ "SUP monitorConnectionAuthzDN "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbBoundDN },
+ { "( olmLDAPAttributes:4 "
+ "NAME ( 'olmDbConnFlags' ) "
+ "DESC 'monitor connection flags' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbConnFlags },
+ { "( olmLDAPAttributes:5 "
+ "NAME ( 'olmDbConnURI' ) "
+ "DESC 'monitor connection URI' "
+ "SUP monitorConnectionPeerAddress "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbConnURI },
+ { "( olmLDAPAttributes:6 "
+ "NAME ( 'olmDbConnPeerAddress' ) "
+ "DESC 'monitor connection peer address' "
+ "SUP monitorConnectionPeerAddress "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbPeerAddress },
+
+ { NULL }
+};
+
+static struct {
+ char *name;
+ ObjectClass **oc;
+} s_moc[] = {
+ { "monitorContainer", &oc_monitorContainer },
+ { "monitorCounterObject", &oc_monitorCounterObject },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **oc;
+} s_oc[] = {
+ /* augments an existing object, so it must be AUXILIARY
+ * FIXME: derive from some ABSTRACT "monitoredEntity"? */
+ { "( olmLDAPObjectClasses:1 "
+ "NAME ( 'olmLDAPDatabase' ) "
+ "SUP top AUXILIARY "
+ "MAY ( "
+ "olmDbURIList "
+ ") )",
+ &oc_olmLDAPDatabase },
+ { "( olmLDAPObjectClasses:2 "
+ "NAME ( 'olmLDAPConnection' ) "
+ "SUP monitorConnection STRUCTURAL "
+ "MAY ( "
+ "olmDbBoundDN "
+ "$ olmDbConnFlags "
+ "$ olmDbConnURI "
+ "$ olmDbConnPeerAddress "
+ ") )",
+ &oc_olmLDAPConnection },
+
+ { NULL }
+};
+
+static int
+ldap_back_monitor_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ ldapinfo_t *li = (ldapinfo_t *)priv;
+
+ Attribute *a;
+
+ /* update olmDbURIList */
+ a = attr_find( e->e_attrs, ad_olmDbURIList );
+ if ( a != NULL ) {
+ struct berval bv;
+
+ assert( a->a_vals != NULL );
+ assert( !BER_BVISNULL( &a->a_vals[ 0 ] ) );
+ assert( BER_BVISNULL( &a->a_vals[ 1 ] ) );
+
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ if ( li->li_uri ) {
+ ber_str2bv( li->li_uri, 0, 0, &bv );
+ if ( !bvmatch( &a->a_vals[ 0 ], &bv ) ) {
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ldap_back_monitor_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ ldapinfo_t *li = (ldapinfo_t *) priv;
+
+ Attribute *save_attrs = NULL;
+ Modifications *ml,
+ *ml_olmDbURIList = NULL;
+ struct berval ul = BER_BVNULL;
+ int got = 0;
+
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc == ad_olmDbURIList ) {
+ if ( ml_olmDbURIList != NULL ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "conflicting modifications";
+ goto done;
+ }
+
+ if ( ml->sml_op != LDAP_MOD_REPLACE ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "modification not allowed";
+ goto done;
+ }
+
+ ml_olmDbURIList = ml;
+ got++;
+ continue;
+ }
+ }
+
+ if ( got == 0 ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ save_attrs = attrs_dup( e->e_attrs );
+
+ if ( ml_olmDbURIList != NULL ) {
+ Attribute *a = NULL;
+ LDAPURLDesc *ludlist = NULL;
+ int rc;
+
+ ml = ml_olmDbURIList;
+ assert( ml->sml_nvalues != NULL );
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "no value provided";
+ goto done;
+ }
+
+ if ( !BER_BVISNULL( &ml->sml_nvalues[ 1 ] ) ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "multiple values provided";
+ goto done;
+ }
+
+ rc = ldap_url_parselist_ext( &ludlist,
+ ml->sml_nvalues[ 0 ].bv_val, NULL,
+ LDAP_PVT_URL_PARSE_NOEMPTY_HOST
+ | LDAP_PVT_URL_PARSE_DEF_PORT );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ rs->sr_err = LDAP_INVALID_SYNTAX;
+ rs->sr_text = "unable to parse URI list";
+ goto done;
+ }
+
+ ul.bv_val = ldap_url_list2urls( ludlist );
+ ldap_free_urllist( ludlist );
+ if ( ul.bv_val == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ ul.bv_len = strlen( ul.bv_val );
+
+ a = attr_find( e->e_attrs, ad_olmDbURIList );
+ if ( a != NULL ) {
+ if ( a->a_nvals == a->a_vals ) {
+ a->a_nvals = ch_calloc( sizeof( struct berval ), 2 );
+ }
+
+ ber_bvreplace( &a->a_vals[ 0 ], &ul );
+ ber_bvreplace( &a->a_nvals[ 0 ], &ul );
+
+ } else {
+ attr_merge_normalize_one( e, ad_olmDbURIList, &ul, NULL );
+ }
+ }
+
+ /* apply changes */
+ if ( !BER_BVISNULL( &ul ) ) {
+ ldap_pvt_thread_mutex_lock( &li->li_uri_mutex );
+ if ( li->li_uri ) {
+ ch_free( li->li_uri );
+ }
+ li->li_uri = ul.bv_val;
+ ldap_pvt_thread_mutex_unlock( &li->li_uri_mutex );
+
+ BER_BVZERO( &ul );
+ }
+
+done:;
+ if ( !BER_BVISNULL( &ul ) ) {
+ ldap_memfree( ul.bv_val );
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ attrs_free( save_attrs );
+ return SLAP_CB_CONTINUE;
+ }
+
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+
+ return rs->sr_err;
+}
+
+static int
+ldap_back_monitor_free(
+ Entry *e,
+ void **priv )
+{
+ ldapinfo_t *li = (ldapinfo_t *)(*priv);
+
+ *priv = NULL;
+
+ if ( !slapd_shutdown ) {
+ memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ldap_back_monitor_subsystem_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms)
+{
+ free(ms->mss_dn.bv_val);
+ BER_BVZERO(&ms->mss_dn);
+
+ free(ms->mss_ndn.bv_val);
+ BER_BVZERO(&ms->mss_ndn);
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Connection monitoring subsystem:
+ * Tries to mimic what the cn=connections,cn=monitor subsystem does
+ * by creating volatile entries for each connection and populating them
+ * according to the information attached to the connection.
+ * At this moment the only exposed information is the DN used to bind it.
+ * Also note that the connection IDs are not and probably never will be
+ * stable.
+ */
+
+struct ldap_back_monitor_conn_arg {
+ Operation *op;
+ monitor_subsys_t *ms;
+ Entry **ep;
+};
+
+/* code stolen from daemon.c */
+static int
+ldap_back_monitor_conn_peername(
+ LDAP *ld,
+ struct berval *bv)
+{
+ Sockbuf *sockbuf;
+ ber_socket_t socket;
+ Sockaddr sa;
+ socklen_t salen = sizeof(sa);
+ const char *peeraddr = NULL;
+ /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */
+ char addr[INET6_ADDRSTRLEN];
+#ifdef LDAP_PF_LOCAL
+ char peername[MAXPATHLEN + sizeof("PATH=")];
+#elif defined(LDAP_PF_INET6)
+ char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")];
+#else /* ! LDAP_PF_LOCAL && ! LDAP_PF_INET6 */
+ char peername[sizeof("IP=255.255.255.255:65336")];
+#endif /* LDAP_PF_LOCAL */
+
+ assert( bv != NULL );
+
+ ldap_get_option( ld, LDAP_OPT_SOCKBUF, (void **)&sockbuf );
+ ber_sockbuf_ctrl( sockbuf, LBER_SB_OPT_GET_FD, &socket );
+ getpeername( socket, (struct sockaddr *)&sa, &salen );
+
+ switch ( sa.sa_addr.sa_family ) {
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+ sprintf( peername, "PATH=%s", sa.sa_un_addr.sun_path );
+ break;
+#endif /* LDAP_PF_LOCAL */
+
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+ if ( IN6_IS_ADDR_V4MAPPED(&sa.sa_in6_addr.sin6_addr) ) {
+#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
+ peeraddr = inet_ntop( AF_INET,
+ ((struct in_addr *)&sa.sa_in6_addr.sin6_addr.s6_addr[12]),
+ addr, sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ peeraddr = inet_ntoa( *((struct in_addr *)
+ &sa.sa_in6_addr.sin6_addr.s6_addr[12]) );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN;
+ sprintf( peername, "IP=%s:%d", peeraddr,
+ (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) );
+ } else {
+ peeraddr = inet_ntop( AF_INET6,
+ &sa.sa_in6_addr.sin6_addr,
+ addr, sizeof addr );
+ if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN;
+ sprintf( peername, "IP=[%s]:%d", peeraddr,
+ (unsigned) ntohs( sa.sa_in6_addr.sin6_port ) );
+ }
+ break;
+#endif /* LDAP_PF_INET6 */
+
+ case AF_INET: {
+#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
+ peeraddr = inet_ntop( AF_INET, &sa.sa_in_addr.sin_addr,
+ addr, sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ peeraddr = inet_ntoa( sa.sa_in_addr.sin_addr );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN;
+ sprintf( peername, "IP=%s:%d", peeraddr,
+ (unsigned) ntohs( sa.sa_in_addr.sin_port ) );
+ } break;
+
+ default:
+ sprintf( peername, SLAP_STRING_UNKNOWN );
+ }
+
+ ber_str2bv( peername, 0, 1, bv );
+ return LDAP_SUCCESS;
+}
+
+static int
+ldap_back_monitor_conn_entry(
+ ldapconn_t *lc,
+ struct ldap_back_monitor_conn_arg *arg )
+{
+ Entry *e;
+ monitor_entry_t *mp;
+ monitor_extra_t *mbe = arg->op->o_bd->bd_info->bi_extra;
+ char buf[SLAP_TEXT_BUFLEN];
+ char *ptr;
+ struct berval bv;
+ int i;
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( bv.bv_val, SLAP_TEXT_BUFLEN,
+ "cn=Connection %lu", lc->lc_connid );
+
+ e = mbe->entry_stub( &arg->ms->mss_dn, &arg->ms->mss_ndn, &bv,
+ oc_monitorContainer, NULL, NULL );
+
+ attr_merge_normalize_one( e, ad_olmDbBoundDN, &lc->lc_bound_ndn, NULL );
+
+ for ( i = 0; s_flag[i].flag; i++ )
+ {
+ if ( lc->lc_flags & s_flag[i].flag )
+ {
+ attr_merge_normalize_one( e, ad_olmDbConnFlags, &s_flag[i].name, NULL );
+ }
+ }
+
+ ldap_get_option( lc->lc_ld, LDAP_OPT_URI, &bv.bv_val );
+ ptr = strchr( bv.bv_val, ' ' );
+ bv.bv_len = ptr ? ptr - bv.bv_val : strlen(bv.bv_val);
+ attr_merge_normalize_one( e, ad_olmDbConnURI, &bv, NULL );
+ ch_free( bv.bv_val );
+
+ ldap_back_monitor_conn_peername( lc->lc_ld, &bv );
+ attr_merge_normalize_one( e, ad_olmDbPeerAddress, &bv, NULL );
+ ch_free( bv.bv_val );
+
+ mp = mbe->entrypriv_create();
+ e->e_private = mp;
+ mp->mp_info = arg->ms;
+ mp->mp_flags = MONITOR_F_SUB | MONITOR_F_VOLATILE;
+
+ *arg->ep = e;
+ arg->ep = &mp->mp_next;
+
+ return 0;
+}
+
+static int
+ldap_back_monitor_conn_create(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry *e_parent,
+ Entry **ep )
+{
+ monitor_entry_t *mp_parent;
+ monitor_subsys_t *ms;
+ ldapinfo_t *li;
+ ldapconn_t *lc;
+
+ struct ldap_back_monitor_conn_arg *arg;
+ int conn_type;
+ TAvlnode *edge;
+
+ assert( e_parent->e_private != NULL );
+
+ mp_parent = e_parent->e_private;
+ ms = (monitor_subsys_t *)mp_parent->mp_info;
+ li = (ldapinfo_t *)ms->mss_private;
+
+ arg = ch_calloc( 1, sizeof(struct ldap_back_monitor_conn_arg) );
+ arg->op = op;
+ arg->ep = ep;
+ arg->ms = ms;
+
+ for ( conn_type = LDAP_BACK_PCONN_FIRST;
+ conn_type < LDAP_BACK_PCONN_LAST;
+ conn_type++ )
+ {
+ LDAP_TAILQ_FOREACH( lc,
+ &li->li_conn_priv[ conn_type ].lic_priv,
+ lc_q )
+ {
+ ldap_back_monitor_conn_entry( lc, arg );
+ }
+ }
+
+ edge = ldap_tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+ while ( edge ) {
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ ldapconn_t *lc = (ldapconn_t *)edge->avl_data;
+ ldap_back_monitor_conn_entry( lc, arg );
+ edge = next;
+ }
+
+ ch_free( arg );
+
+ return 0;
+}
+
+static int
+ldap_back_monitor_conn_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ ldapinfo_t *li = (ldapinfo_t *) ms->mss_private;
+ monitor_extra_t *mbe;
+
+ Entry *e;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *) be->bd_info->bi_extra;
+
+ ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn;
+ ms->mss_rdn = li->li_monitor_info.lmi_conn_rdn;
+ ms->mss_create = ldap_back_monitor_conn_create;
+ ms->mss_destroy = ldap_back_monitor_subsystem_destroy;
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn,
+ &ms->mss_rdn, oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_conn_init: "
+ "unable to create entry \"%s,%s\"\n",
+ li->li_monitor_info.lmi_conn_rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, MONITOR_F_VOLATILE_CH );
+
+ /* add labeledURI and special, modifiable URI value */
+ if ( rc == LDAP_SUCCESS && li->li_uri != NULL ) {
+ struct berval bv;
+ Attribute *a;
+ LDAPURLDesc *ludlist = NULL;
+ monitor_callback_t *cb = NULL;
+
+ a = attr_alloc( ad_olmDbURIList );
+
+ ber_str2bv( li->li_uri, 0, 0, &bv );
+ attr_valadd( a, &bv, NULL, 1 );
+ attr_normalize( a->a_desc, a->a_vals, &a->a_nvals, NULL );
+
+ rc = ldap_url_parselist_ext( &ludlist,
+ li->li_uri, NULL,
+ LDAP_PVT_URL_PARSE_NOEMPTY_HOST
+ | LDAP_PVT_URL_PARSE_DEF_PORT );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_db_open: "
+ "unable to parse URI list (ignored)\n" );
+ } else {
+ Attribute *a2 = attr_alloc( slap_schema.si_ad_labeledURI );
+
+ a->a_next = a2;
+
+ for ( ; ludlist != NULL; ) {
+ LDAPURLDesc *next = ludlist->lud_next;
+
+ bv.bv_val = ldap_url_desc2str( ludlist );
+ assert( bv.bv_val != NULL );
+ ldap_free_urldesc( ludlist );
+ bv.bv_len = strlen( bv.bv_val );
+ attr_valadd( a2, &bv, NULL, 1 );
+ ch_free( bv.bv_val );
+
+ ludlist = next;
+ }
+
+ attr_normalize( a2->a_desc, a2->a_vals, &a2->a_nvals, NULL );
+ }
+
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = ldap_back_monitor_update;
+ cb->mc_modify = ldap_back_monitor_modify;
+ cb->mc_free = ldap_back_monitor_free;
+ cb->mc_private = (void *)li;
+
+ rc = mbe->register_entry_attrs( &ms->mss_ndn, a, cb, NULL, -1, NULL );
+
+ attr_free( a->a_next );
+ attr_free( a );
+
+ if ( rc != LDAP_SUCCESS )
+ {
+ ch_free( cb );
+ }
+ }
+
+ entry_free( e );
+
+ return rc;
+}
+
+/*
+ * Operation monitoring subsystem:
+ * Looks a lot like the cn=operations,cn=monitor subsystem except that at this
+ * moment, only completed operations are counted. Each entry has a separate
+ * callback with all the needed information linked there in the structure
+ * below so that the callback need not locate it over and over again.
+ */
+
+struct ldap_back_monitor_op_counter {
+ ldap_pvt_mp_t *data;
+ ldap_pvt_thread_mutex_t *mutex;
+};
+
+static void
+ldap_back_monitor_ops_dispose(
+ void **priv)
+{
+ struct ldap_back_monitor_op_counter *counter = *priv;
+
+ ch_free( counter );
+ counter = NULL;
+}
+
+static int
+ldap_back_monitor_ops_free(
+ Entry *e,
+ void **priv)
+{
+ ldap_back_monitor_ops_dispose( priv );
+ return LDAP_SUCCESS;
+}
+
+static int
+ldap_back_monitor_ops_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ struct ldap_back_monitor_op_counter *counter = priv;
+ Attribute *a;
+
+ /*TODO
+ * what about initiated/completed?
+ */
+ a = attr_find( e->e_attrs, ad_olmDbOperations );
+ assert( a != NULL );
+
+ ldap_pvt_thread_mutex_lock( counter->mutex );
+ UI2BV( &a->a_vals[ 0 ], *counter->data );
+ ldap_pvt_thread_mutex_unlock( counter->mutex );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ldap_back_monitor_ops_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ ldapinfo_t *li = (ldapinfo_t *) ms->mss_private;
+
+ monitor_extra_t *mbe;
+ Entry *e, *parent;
+ int rc;
+ slap_op_t op;
+ struct berval value = BER_BVC( "0" );
+
+ assert( be != NULL );
+
+ mbe = (monitor_extra_t *) be->bd_info->bi_extra;
+
+ ms->mss_dn = ms->mss_ndn = li->li_monitor_info.lmi_ndn;
+ ms->mss_rdn = li->li_monitor_info.lmi_ops_rdn;
+ ms->mss_destroy = ldap_back_monitor_subsystem_destroy;
+
+ parent = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn,
+ &ms->mss_rdn, oc_monitorContainer, NULL, NULL );
+ if ( parent == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ li->li_monitor_info.lmi_ops_rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ ber_dupbv( &ms->mss_dn, &parent->e_name );
+ ber_dupbv( &ms->mss_ndn, &parent->e_nname );
+
+ rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
+ if ( rc != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ parent->e_name.bv_val );
+ goto done;
+ }
+
+ for ( op = 0; op < SLAP_OP_LAST; op++ )
+ {
+ monitor_callback_t *cb;
+ struct ldap_back_monitor_op_counter *counter;
+
+ e = mbe->entry_stub( &parent->e_name, &parent->e_nname,
+ &ldap_back_monitor_op[op].rdn,
+ oc_monitorCounterObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ldap_back_monitor_op[op].rdn.bv_val,
+ parent->e_nname.bv_val );
+ return( -1 );
+ }
+
+ attr_merge_normalize_one( e, ad_olmDbOperations, &value, NULL );
+
+ counter = ch_malloc( sizeof( struct ldap_back_monitor_op_counter ) );
+ counter->data = &li->li_ops_completed[ op ];
+ counter->mutex = &li->li_counter_mutex;
+
+ /*
+ * We cannot share a single callback between entries.
+ *
+ * monitor_cache_destroy() tries to free all callbacks and it's called
+ * before mss_destroy() so we have no chance of handling it ourselves
+ */
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = ldap_back_monitor_ops_update;
+ cb->mc_free = ldap_back_monitor_ops_free;
+ cb->mc_dispose = ldap_back_monitor_ops_dispose;
+ cb->mc_private = (void *)counter;
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+
+ /* TODO: register_entry has stored a duplicate so we might actually reuse it
+ * instead of recreating it every time... */
+ entry_free( e );
+
+ if ( rc != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ ch_free( cb );
+ break;
+ }
+ }
+
+done:
+ entry_free( parent );
+
+ return rc;
+}
+
+/*
+ * call from within ldap_back_initialize()
+ */
+static int
+ldap_back_monitor_initialize( void )
+{
+ int i, code;
+ ConfigArgs c;
+ char *argv[ 3 ];
+
+ static int ldap_back_monitor_initialized = 0;
+
+ /* set to 0 when successfully initialized; otherwise, remember failure */
+ static int ldap_back_monitor_initialized_failure = 1;
+
+ /* register schema here */
+
+ if ( ldap_back_monitor_initialized++ ) {
+ return ldap_back_monitor_initialized_failure;
+ }
+
+ if ( backend_info( "monitor" ) == NULL ) {
+ return -1;
+ }
+
+ argv[ 0 ] = "back-ldap monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+ for ( i = 0; s_oid[ i ].name; i++ ) {
+
+ argv[ 1 ] = s_oid[ i ].name;
+ argv[ 2 ] = s_oid[ i ].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_initialize: unable to add "
+ "objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid );
+ return 2;
+ }
+ }
+
+ for ( i = 0; s_at[ i ].desc != NULL; i++ ) {
+ code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_initialize: register_at failed for attributeType (%s)\n",
+ s_at[ i ].desc );
+ return 3;
+
+ } else {
+ (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ for ( i = 0; s_oc[ i ].desc != NULL; i++ ) {
+ code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_initialize: register_oc failed for objectClass (%s)\n",
+ s_oc[ i ].desc );
+ return 4;
+
+ } else {
+ (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+ }
+
+ for ( i = 0; s_moc[ i ].name != NULL; i++ ) {
+ *s_moc[i].oc = oc_find( s_moc[ i ].name );
+ if ( ! *s_moc[i].oc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_initialize: failed to find objectClass (%s)\n",
+ s_moc[ i ].name );
+ return 5;
+
+ }
+ }
+
+ return ( ldap_back_monitor_initialized_failure = LDAP_SUCCESS );
+}
+
+/*
+ * call from within ldap_back_db_init()
+ */
+int
+ldap_back_monitor_db_init( BackendDB *be )
+{
+ int rc;
+
+ rc = ldap_back_monitor_initialize();
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+#if 0 /* uncomment to turn monitoring on by default */
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING;
+#endif
+
+ return 0;
+}
+
+/*
+ * call from within ldap_back_db_open()
+ */
+int
+ldap_back_monitor_db_open( BackendDB *be )
+{
+ ldapinfo_t *li = (ldapinfo_t *) be->be_private;
+ monitor_subsys_t *mss = li->li_monitor_info.lmi_mss;
+ int rc = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+
+ if ( !SLAP_DBMONITORING( be ) ) {
+ return 0;
+ }
+
+ /* check if monitor is configured and usable */
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING;
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG, "ldap_back_monitor_db_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ return 0;
+ }
+
+ /* caller (e.g. an overlay based on back-ldap) may want to use
+ * a different DN and RDNs... */
+ if ( BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) {
+ rc = mbe->register_database( be, &li->li_monitor_info.lmi_ndn );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "ldap_back_monitor_db_open: "
+ "failed to register the database with back-monitor\n" );
+ }
+ }
+ if ( BER_BVISNULL( &li->li_monitor_info.lmi_conn_rdn ) ) {
+ ber_str2bv( "cn=Connections", 0, 1,
+ &li->li_monitor_info.lmi_conn_rdn );
+ }
+ if ( BER_BVISNULL( &li->li_monitor_info.lmi_ops_rdn ) ) {
+ ber_str2bv( "cn=Operations", 0, 1,
+ &li->li_monitor_info.lmi_ops_rdn );
+ }
+
+ /* set up the subsystems used to create the operation and
+ * volatile connection entries */
+
+ mss->mss_name = "back-ldap connections";
+ mss->mss_flags = MONITOR_F_VOLATILE_CH;
+ mss->mss_open = ldap_back_monitor_conn_init;
+ mss->mss_private = li;
+
+ if ( mbe->register_subsys_late( mss ) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_db_open: "
+ "failed to register connection subsystem" );
+ return -1;
+ }
+
+ mss++;
+
+ mss->mss_name = "back-ldap operations";
+ mss->mss_flags = MONITOR_F_PERSISTENT_CH;
+ mss->mss_open = ldap_back_monitor_ops_init;
+ mss->mss_private = li;
+
+ if ( mbe->register_subsys_late( mss ) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "ldap_back_monitor_db_open: "
+ "failed to register operation subsystem" );
+ return -1;
+ }
+
+ return rc;
+}
+
+/*
+ * call from within ldap_back_db_close()
+ */
+int
+ldap_back_monitor_db_close( BackendDB *be )
+{
+ ldapinfo_t *li = (ldapinfo_t *) be->be_private;
+
+ if ( li && !BER_BVISNULL( &li->li_monitor_info.lmi_ndn ) ) {
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+
+ /* check if monitor is configured and usable */
+ mi = backend_info( "monitor" );
+ if ( mi && mi->bi_extra ) {
+ mbe = mi->bi_extra;
+
+ /*TODO
+ * Unregister all entries our subsystems have created.
+ * Will only really be necessary when
+ * SLAPD_CONFIG_DELETE is enabled.
+ *
+ * Might need a way to unregister subsystems instead.
+ */
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * call from within ldap_back_db_destroy()
+ */
+int
+ldap_back_monitor_db_destroy( BackendDB *be )
+{
+ ldapinfo_t *li = (ldapinfo_t *) be->be_private;
+
+ if ( li ) {
+ memset( &li->li_monitor_info, 0, sizeof( li->li_monitor_info ) );
+ }
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-ldap/pbind.c b/servers/slapd/back-ldap/pbind.c
new file mode 100644
index 0000000..f5841e9
--- /dev/null
+++ b/servers/slapd/back-ldap/pbind.c
@@ -0,0 +1,173 @@
+/* pbind.c - passthru Bind overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003-2010 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "back-ldap.h"
+#include "slap-config.h"
+
+static BackendInfo *lback;
+
+static slap_overinst ldappbind;
+
+static int
+ldap_pbind_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ void *private = op->o_bd->be_private;
+ void *bi = op->o_bd->bd_info;
+ int rc;
+
+ op->o_bd->bd_info = lback;
+ op->o_bd->be_private = on->on_bi.bi_private;
+ rc = lback->bi_op_bind( op, rs );
+ op->o_bd->be_private = private;
+ op->o_bd->bd_info = bi;
+
+ return rc;
+}
+
+static int
+ldap_pbind_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ConfigOCs *be_cf_ocs = be->be_cf_ocs;
+ void *private = be->be_private;
+ int rc;
+
+ if ( lback == NULL ) {
+ lback = backend_info( "ldap" );
+
+ if ( lback == NULL ) {
+ return 1;
+ }
+ }
+
+ rc = lback->bi_db_init( be, cr );
+ on->on_bi.bi_private = be->be_private;
+ be->be_cf_ocs = be_cf_ocs;
+ be->be_private = private;
+
+ return rc;
+}
+
+static int
+ldap_pbind_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ void *private = be->be_private;
+ int rc;
+ int monitoring;
+
+ be->be_private = on->on_bi.bi_private;
+ monitoring = ( SLAP_DBFLAGS( be ) & SLAP_DBFLAG_MONITORING );
+ SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_MONITORING;
+ rc = lback->bi_db_open( be, cr );
+ SLAP_DBFLAGS( be ) |= monitoring;
+ be->be_private = private;
+
+ return rc;
+}
+
+static int
+ldap_pbind_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ void *private = be->be_private;
+ int rc;
+
+ be->be_private = on->on_bi.bi_private;
+ rc = lback->bi_db_close( be, cr );
+ be->be_private = private;
+
+ return rc;
+}
+
+static int
+ldap_pbind_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ void *private = be->be_private;
+ int rc;
+
+ be->be_private = on->on_bi.bi_private;
+ rc = lback->bi_db_close( be, cr );
+ on->on_bi.bi_private = be->be_private;
+ be->be_private = private;
+
+ return rc;
+}
+
+static int
+ldap_pbind_connection_destroy(
+ BackendDB *be,
+ Connection *conn
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ void *private = be->be_private;
+ int rc;
+
+ be->be_private = on->on_bi.bi_private;
+ rc = lback->bi_connection_destroy( be, conn );
+ be->be_private = private;
+
+ return rc;
+}
+
+int
+pbind_initialize( void )
+{
+ int rc;
+
+ ldappbind.on_bi.bi_type = "pbind";
+ ldappbind.on_bi.bi_db_init = ldap_pbind_db_init;
+ ldappbind.on_bi.bi_db_open = ldap_pbind_db_open;
+ ldappbind.on_bi.bi_db_close = ldap_pbind_db_close;
+ ldappbind.on_bi.bi_db_destroy = ldap_pbind_db_destroy;
+
+ ldappbind.on_bi.bi_op_bind = ldap_pbind_bind;
+ ldappbind.on_bi.bi_connection_destroy = ldap_pbind_connection_destroy;
+
+ rc = ldap_pbind_init_cf( &ldappbind.on_bi );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &ldappbind );
+}
diff --git a/servers/slapd/back-ldap/proto-ldap.h b/servers/slapd/back-ldap/proto-ldap.h
new file mode 100644
index 0000000..445d551
--- /dev/null
+++ b/servers/slapd/back-ldap/proto-ldap.h
@@ -0,0 +1,124 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef PROTO_LDAP_H
+#define PROTO_LDAP_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init ldap_back_initialize;
+extern BI_open ldap_back_open;
+
+extern BI_db_init ldap_back_db_init;
+extern BI_db_open ldap_back_db_open;
+extern BI_db_close ldap_back_db_close;
+extern BI_db_destroy ldap_back_db_destroy;
+
+extern BI_op_bind ldap_back_bind;
+extern BI_op_search ldap_back_search;
+extern BI_op_compare ldap_back_compare;
+extern BI_op_modify ldap_back_modify;
+extern BI_op_modrdn ldap_back_modrdn;
+extern BI_op_add ldap_back_add;
+extern BI_op_delete ldap_back_delete;
+extern BI_op_abandon ldap_back_abandon;
+extern BI_op_extended ldap_back_extended;
+
+extern BI_connection_destroy ldap_back_conn_destroy;
+
+extern BI_entry_get_rw ldap_back_entry_get;
+
+void ldap_back_release_conn_lock( ldapinfo_t *li, ldapconn_t **lcp, int dolock );
+#define ldap_back_release_conn(li, lc) ldap_back_release_conn_lock((li), &(lc), 1)
+int ldap_back_dobind( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok );
+int ldap_back_retry( ldapconn_t **lcp, Operation *op, SlapReply *rs, ldap_back_send_t sendok );
+int ldap_back_map_result( SlapReply *rs );
+int ldap_back_op_result( ldapconn_t *lc, Operation *op, SlapReply *rs,
+ ber_int_t msgid, time_t timeout, ldap_back_send_t sendok );
+int ldap_back_cancel( ldapconn_t *lc, Operation *op, SlapReply *rs, ber_int_t msgid, ldap_back_send_t sendok );
+
+int ldap_back_init_cf( BackendInfo *bi );
+int ldap_pbind_init_cf( BackendInfo *bi );
+
+extern int ldap_back_conndn_cmp( const void *c1, const void *c2);
+extern int ldap_back_conn_cmp( const void *c1, const void *c2);
+extern int ldap_back_conndn_dup( void *c1, void *c2 );
+extern void ldap_back_conn_free( void *c );
+
+extern ldapconn_t * ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc );
+
+extern int ldap_back_conn2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen );
+extern int ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen );
+
+extern int
+ldap_back_proxy_authz_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *bound_ndn,
+ int version,
+ slap_idassert_t *si,
+ LDAPControl *ctrl );
+
+extern int
+ldap_back_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ ldapconn_t *lc,
+ LDAPControl ***pctrls );
+
+extern int
+ldap_back_controls_free( Operation *op, SlapReply *rs, LDAPControl ***pctrls );
+
+extern void
+ldap_back_quarantine(
+ Operation *op,
+ SlapReply *rs );
+
+#ifdef LDAP_BACK_PRINT_CONNTREE
+extern void
+ldap_back_print_conntree( ldapinfo_t *li, char *msg );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+
+extern void slap_retry_info_destroy( slap_retry_info_t *ri );
+extern int slap_retry_info_parse( char *in, slap_retry_info_t *ri,
+ char *buf, ber_len_t buflen );
+extern int slap_retry_info_unparse( slap_retry_info_t *ri, struct berval *bvout );
+
+extern int slap_idassert_authzfrom_parse( struct config_args_s *ca, slap_idassert_t *si );
+extern int slap_idassert_passthru_parse_cf( const char *fname, int lineno, const char *arg, slap_idassert_t *si );
+extern int slap_idassert_parse( struct config_args_s *ca, slap_idassert_t *si );
+
+extern int chain_initialize( void );
+extern int pbind_initialize( void );
+#ifdef SLAP_DISTPROC
+extern int distproc_initialize( void );
+#endif
+
+extern int ldap_back_monitor_db_init( BackendDB *be );
+extern int ldap_back_monitor_db_open( BackendDB *be );
+extern int ldap_back_monitor_db_close( BackendDB *be );
+extern int ldap_back_monitor_db_destroy( BackendDB *be );
+
+extern LDAP_REBIND_PROC ldap_back_default_rebind;
+extern LDAP_URLLIST_PROC ldap_back_default_urllist;
+
+LDAP_END_DECL
+
+#endif /* PROTO_LDAP_H */
diff --git a/servers/slapd/back-ldap/search.c b/servers/slapd/back-ldap/search.c
new file mode 100644
index 0000000..90b5b65
--- /dev/null
+++ b/servers/slapd/back-ldap/search.c
@@ -0,0 +1,1042 @@
+/* search.c - ldap backend search function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+#include "lutil.h"
+
+static int
+ldap_build_entry( Operation *op, LDAPMessage *e, Entry *ent,
+ struct berval *bdn, int remove_unknown_schema );
+
+
+static ObjectClass *
+oc_bvfind_undef_ex( struct berval *ocname, int flag )
+{
+ ObjectClass *oc = oc_bvfind( ocname );
+
+ if ( oc || flag ) {
+ /* oc defined or remove-unknown-schema flag set */
+ return oc;
+ }
+
+ return oc_bvfind_undef( ocname );
+}
+
+
+/*
+ * replaces (&) with (objectClass=*) and (|) with (!(objectClass=*))
+ * as the best replacement for RFC 4526 absolute true/absolute false
+ * filters; the only difference (AFAIK) is that they require search
+ * access to objectClass.
+ *
+ * filter->bv_val may be alloc'd on the thread's slab, if equal to
+ * op->ors_filterstr.bv_val, or realloc'd on the thread's slab otherwise.
+ */
+static int
+ldap_back_munge_filter(
+ Operation *op,
+ struct berval *filter )
+{
+ char *ptr;
+ int gotit = 0;
+
+ Debug( LDAP_DEBUG_ARGS, "=> ldap_back_munge_filter \"%s\"\n",
+ filter->bv_val );
+
+ for ( ptr = strchr( filter->bv_val, '(' );
+ ptr;
+ ptr = strchr( ptr, '(' ) )
+ {
+ static struct berval
+ bv_t = BER_BVC( "(&)" ),
+ bv_f = BER_BVC( "(|)" ),
+ bv_T = BER_BVC( "(objectClass=*)" ),
+ bv_F = BER_BVC( "(!(objectClass=*))" );
+ struct berval *oldbv = NULL,
+ *newbv = NULL,
+ oldfilter = BER_BVNULL;
+
+ if ( ptr[2] != ')' ) {
+ ptr++;
+ continue;
+ }
+
+ switch ( ptr[1] ) {
+ case '&':
+ oldbv = &bv_t;
+ newbv = &bv_T;
+ break;
+
+ case '|':
+ oldbv = &bv_f;
+ newbv = &bv_F;
+ break;
+
+ default:
+ /* should be an error */
+ continue;
+ }
+
+ oldfilter = *filter;
+ filter->bv_len += newbv->bv_len - oldbv->bv_len;
+ if ( filter->bv_val == op->ors_filterstr.bv_val ) {
+ filter->bv_val = op->o_tmpalloc( filter->bv_len + 1,
+ op->o_tmpmemctx );
+
+ AC_MEMCPY( filter->bv_val, op->ors_filterstr.bv_val,
+ ptr - oldfilter.bv_val );
+
+ } else {
+ filter->bv_val = op->o_tmprealloc( filter->bv_val,
+ filter->bv_len + 1, op->o_tmpmemctx );
+ }
+
+ ptr = filter->bv_val + ( ptr - oldfilter.bv_val );
+
+ AC_MEMCPY( &ptr[ newbv->bv_len ],
+ &ptr[ oldbv->bv_len ],
+ oldfilter.bv_len - ( ptr - filter->bv_val ) - oldbv->bv_len + 1 );
+ AC_MEMCPY( ptr, newbv->bv_val, newbv->bv_len );
+
+ ptr += newbv->bv_len;
+
+ gotit++;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "<= ldap_back_munge_filter \"%s\" (%d)\n",
+ filter->bv_val, gotit );
+
+ return gotit;
+}
+
+int
+ldap_back_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ struct timeval tv;
+ time_t stoptime = (time_t)(-1);
+ LDAPMessage *res,
+ *e;
+ int rc = 0,
+ msgid;
+ struct berval match = BER_BVNULL,
+ filter = BER_BVNULL;
+ int i, x;
+ char **attrs = NULL;
+ int freetext = 0, filter_undef = 0;
+ int do_retry = 1, dont_retry = 0;
+ LDAPControl **ctrls = NULL;
+ char **references = NULL;
+ int remove_unknown_schema =
+ LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li);
+
+ rs_assert_ready( rs );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */
+
+ if ( !ldap_back_dobind( &lc, op, rs, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ /*
+ * FIXME: in case of values return filter, we might want
+ * to map attrs and maybe rewrite value
+ */
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ tv.tv_sec = op->ors_tlimit;
+ tv.tv_usec = 0;
+ stoptime = op->o_time + op->ors_tlimit;
+
+ } else {
+ LDAP_BACK_TV_SET( &tv );
+ }
+
+ i = 0;
+ if ( op->ors_attrs ) {
+ for ( ; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++ )
+ /* just count attrs */ ;
+ }
+
+ x = 0;
+ if ( op->o_bd->be_extra_anlist ) {
+ for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ )
+ /* just count attrs */ ;
+ }
+
+ if ( i > 0 || x > 0 ) {
+ int j = 0;
+
+ attrs = op->o_tmpalloc( ( i + x + 1 )*sizeof( char * ),
+ op->o_tmpmemctx );
+ if ( attrs == NULL ) {
+ rs->sr_err = LDAP_NO_MEMORY;
+ rc = -1;
+ goto finish;
+ }
+
+ if ( i > 0 ) {
+ for ( i = 0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++, j++ ) {
+ attrs[ j ] = op->ors_attrs[i].an_name.bv_val;
+ }
+ }
+
+ if ( x > 0 ) {
+ for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++, j++ ) {
+ if ( op->o_bd->be_extra_anlist[x].an_desc &&
+ ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, op->ors_attrs ) )
+ {
+ continue;
+ }
+
+ attrs[ j ] = op->o_bd->be_extra_anlist[x].an_name.bv_val;
+ }
+ }
+
+ attrs[ j ] = NULL;
+ }
+
+ ctrls = op->o_ctrls;
+ rc = ldap_back_controls_add( op, rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ goto finish;
+ }
+
+ /* deal with <draft-zeilenga-ldap-t-f> filters */
+ filter = op->ors_filterstr;
+retry:
+ /* this goes after retry because ldap_back_munge_filter()
+ * optionally replaces RFC 4526 T-F filters (&) (|)
+ * if already computed, they will be re-installed
+ * by filter2bv_undef_x() later */
+ if ( !LDAP_BACK_T_F( li ) ) {
+ ldap_back_munge_filter( op, &filter );
+ }
+
+ rs->sr_err = ldap_pvt_search( lc->lc_ld, op->o_req_dn.bv_val,
+ op->ors_scope, filter.bv_val,
+ attrs, op->ors_attrsonly, ctrls, NULL,
+ tv.tv_sec ? &tv : NULL,
+ op->ors_slimit, op->ors_deref, &msgid );
+
+ ldap_pvt_thread_mutex_lock( &li->li_counter_mutex );
+ ldap_pvt_mp_add( li->li_ops_completed[ SLAP_OP_SEARCH ], 1 );
+ ldap_pvt_thread_mutex_unlock( &li->li_counter_mutex );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ switch ( rs->sr_err ) {
+ case LDAP_SERVER_DOWN:
+ if ( do_retry ) {
+ do_retry = 0;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) {
+ goto retry;
+ }
+ }
+
+ if ( lc == NULL ) {
+ /* reset by ldap_back_retry ... */
+ rs->sr_err = slap_map_api2result( rs );
+
+ } else {
+ rc = ldap_back_op_result( lc, op, rs, msgid, 0, LDAP_BACK_DONTSEND );
+ }
+
+ goto finish;
+
+ case LDAP_FILTER_ERROR:
+ /* first try? */
+ if ( !filter_undef &&
+ strstr( filter.bv_val, "(?" ) &&
+ !LDAP_BACK_NOUNDEFFILTER( li ) )
+ {
+ BER_BVZERO( &filter );
+ filter2bv_undef_x( op, op->ors_filter, 1, &filter );
+ filter_undef = 1;
+ goto retry;
+ }
+
+ /* invalid filters return success with no data */
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ goto finish;
+
+ default:
+ rs->sr_err = slap_map_api2result( rs );
+ rs->sr_text = NULL;
+ goto finish;
+ }
+ }
+
+ /* if needed, initialize timeout */
+ if ( li->li_timeout[ SLAP_OP_SEARCH ] ) {
+ if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) {
+ tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ];
+ tv.tv_usec = 0;
+ }
+ }
+
+ /* We pull apart the ber result, stuff it into a slapd entry, and
+ * let send_search_entry stuff it back into ber format. Slow & ugly,
+ * but this is necessary for version matching, and for ACL processing.
+ */
+
+ for ( rc = -2; rc != -1; rc = ldap_result( lc->lc_ld, msgid, LDAP_MSG_ONE, &tv, &res ) )
+ {
+ /* check for abandon */
+ if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( lc ) ) {
+ if ( rc > 0 ) {
+ ldap_msgfree( res );
+ }
+ (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND );
+ rc = SLAPD_ABANDON;
+ goto finish;
+ }
+
+ if ( rc == 0 || rc == -2 ) {
+ ldap_pvt_thread_yield();
+
+ /* check timeout */
+ if ( li->li_timeout[ SLAP_OP_SEARCH ] ) {
+ if ( rc == 0 ) {
+ (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND );
+ rs->sr_text = "Operation timed out";
+ rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ goto finish;
+ }
+
+ } else {
+ LDAP_BACK_TV_SET( &tv );
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND );
+ rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
+ goto finish;
+ }
+ continue;
+
+ } else {
+ /* only touch when activity actually took place... */
+ if ( li->li_idle_timeout ) {
+ lc->lc_time = op->o_time;
+ }
+
+ /* don't retry any more */
+ dont_retry = 1;
+ }
+
+
+ if ( rc == LDAP_RES_SEARCH_ENTRY ) {
+ Entry ent = { 0 };
+ struct berval bdn = BER_BVNULL;
+
+ do_retry = 0;
+
+ e = ldap_first_entry( lc->lc_ld, res );
+ rc = ldap_build_entry( op, e, &ent, &bdn,
+ remove_unknown_schema);
+ if ( rc == LDAP_SUCCESS ) {
+ ldap_get_entry_controls( lc->lc_ld, res, &rs->sr_ctrls );
+ rs->sr_entry = &ent;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_flags = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ rc = rs->sr_err = send_search_entry( op, rs );
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ rs->sr_entry = NULL;
+ rs->sr_flags = 0;
+ if ( !BER_BVISNULL( &ent.e_name ) ) {
+ assert( ent.e_name.bv_val != bdn.bv_val );
+ op->o_tmpfree( ent.e_name.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &ent.e_name );
+ }
+ if ( !BER_BVISNULL( &ent.e_nname ) ) {
+ op->o_tmpfree( ent.e_nname.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &ent.e_nname );
+ }
+ entry_clean( &ent );
+ }
+ ldap_msgfree( res );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ case LDAP_INSUFFICIENT_ACCESS:
+ break;
+
+ default:
+ if ( rc == LDAP_UNAVAILABLE ) {
+ rc = rs->sr_err = LDAP_OTHER;
+ } else {
+ (void)ldap_back_cancel( lc, op, rs, msgid, LDAP_BACK_DONTSEND );
+ }
+ goto finish;
+ }
+
+ } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) {
+ if ( LDAP_BACK_NOREFS( li ) ) {
+ ldap_msgfree( res );
+ continue;
+ }
+
+ do_retry = 0;
+ rc = ldap_parse_reference( lc->lc_ld, res,
+ &references, &rs->sr_ctrls, 1 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ /* FIXME: there MUST be at least one */
+ if ( references && references[ 0 ] && references[ 0 ][ 0 ] ) {
+ int cnt;
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ /* NO OP */ ;
+
+ /* FIXME: there MUST be at least one */
+ rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ cnt ] );
+
+ /* ignore return value by now */
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_entry = NULL;
+ ( void )send_search_reference( op, rs );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_search: "
+ "got SEARCH_REFERENCE "
+ "with no referrals\n",
+ op->o_log_prefix );
+ }
+
+ /* cleanup */
+ if ( references ) {
+ ber_memvfree( (void **)references );
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ references = NULL;
+ }
+
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+
+ } else if ( rc == LDAP_RES_INTERMEDIATE ) {
+ /* FIXME: response controls
+ * are passed without checks */
+ rc = ldap_parse_intermediate( lc->lc_ld,
+ res,
+ (char **)&rs->sr_rspoid,
+ &rs->sr_rspdata,
+ &rs->sr_ctrls,
+ 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ slap_send_ldap_intermediate( op, rs );
+
+ if ( rs->sr_rspoid != NULL ) {
+ ber_memfree( (char *)rs->sr_rspoid );
+ rs->sr_rspoid = NULL;
+ }
+
+ if ( rs->sr_rspdata != NULL ) {
+ ber_bvfree( rs->sr_rspdata );
+ rs->sr_rspdata = NULL;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+
+ } else {
+ char *err = NULL;
+
+ rc = ldap_parse_result( lc->lc_ld, res, &rs->sr_err,
+ &match.bv_val, &err,
+ &references, &rs->sr_ctrls, 1 );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( err ) {
+ rs->sr_text = err;
+ freetext = 1;
+ }
+ } else {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( references
+ && references[ 0 ]
+ && references[ 0 ][ 0 ] )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_search: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ rs->sr_err );
+
+ } else {
+ int cnt;
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ /* NO OP */ ;
+
+ rs->sr_ref = op->o_tmpalloc( ( cnt + 1 ) * sizeof( struct berval ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ /* duplicating ...*/
+ ber_str2bv( references[ cnt ], 0, 0, &rs->sr_ref[ cnt ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ cnt ] );
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_back_search: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( match.bv_val != NULL ) {
+ match.bv_len = strlen( match.bv_val );
+ }
+
+ rc = 0;
+ break;
+ }
+
+ /* if needed, restore timeout */
+ if ( li->li_timeout[ SLAP_OP_SEARCH ] ) {
+ if ( tv.tv_sec == 0 || tv.tv_sec > li->li_timeout[ SLAP_OP_SEARCH ] ) {
+ tv.tv_sec = li->li_timeout[ SLAP_OP_SEARCH ];
+ tv.tv_usec = 0;
+ }
+ }
+ }
+
+ if ( rc == -1 ) {
+ if ( dont_retry == 0 ) {
+ if ( do_retry ) {
+ do_retry = 0;
+ if ( ldap_back_retry( &lc, op, rs, LDAP_BACK_DONTSEND ) ) {
+ goto retry;
+ }
+ }
+
+ rs->sr_err = LDAP_SERVER_DOWN;
+ rs->sr_err = slap_map_api2result( rs );
+ goto finish;
+
+ } else if ( LDAP_BACK_ONERR_STOP( li ) ) {
+ /* if onerr == STOP */
+ rs->sr_err = LDAP_SERVER_DOWN;
+ rs->sr_err = slap_map_api2result( rs );
+ goto finish;
+ }
+ }
+
+ /*
+ * Rewrite the matched portion of the search base, if required
+ */
+ if ( !BER_BVISNULL( &match ) && !BER_BVISEMPTY( &match ) ) {
+ struct berval pmatch;
+
+ if ( dnPretty( NULL, &match, &pmatch, op->o_tmpmemctx ) != LDAP_SUCCESS ) {
+ pmatch.bv_val = match.bv_val;
+ match.bv_val = NULL;
+ }
+ rs->sr_matched = pmatch.bv_val;
+ rs->sr_flags |= REP_MATCHED_MUSTBEFREED;
+ }
+
+finish:;
+ if ( !BER_BVISNULL( &match ) ) {
+ ber_memfree( match.bv_val );
+ }
+
+ if ( rs->sr_v2ref ) {
+ rs->sr_err = LDAP_REFERRAL;
+ }
+
+ if ( LDAP_BACK_QUARANTINE( li ) ) {
+ ldap_back_quarantine( op, rs );
+ }
+
+ if ( filter.bv_val != op->ors_filterstr.bv_val ) {
+ op->o_tmpfree( filter.bv_val, op->o_tmpmemctx );
+ }
+
+#if 0
+ /* let send_ldap_result play cleanup handlers (ITS#4645) */
+ if ( rc != SLAPD_ABANDON )
+#endif
+ {
+ send_ldap_result( op, rs );
+ }
+
+ (void)ldap_back_controls_free( op, rs, &ctrls );
+
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+
+ if ( rs->sr_text ) {
+ if ( freetext ) {
+ ber_memfree( (char *)rs->sr_text );
+ }
+ rs->sr_text = NULL;
+ }
+
+ if ( rs->sr_ref ) {
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+
+ if ( references ) {
+ ber_memvfree( (void **)references );
+ }
+
+ if ( attrs ) {
+ op->o_tmpfree( attrs, op->o_tmpmemctx );
+ }
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE &&
+ /* if we originally bound and wanted rebind-as-user, must drop
+ * the connection now because we just discarded the credentials.
+ * ITS#7464, #8142
+ */
+ LDAP_BACK_SAVECRED( li ) && SLAP_IS_AUTHZ_BACKEND( op ) )
+ rs->sr_err = SLAPD_DISCONNECT;
+ return rs->sr_err;
+}
+
+static int
+ldap_build_entry(
+ Operation *op,
+ LDAPMessage *e,
+ Entry *ent,
+ struct berval *bdn,
+ int remove_unknown_schema)
+{
+ struct berval a;
+ BerElement ber = *ldap_get_message_ber( e );
+ Attribute *attr, **attrp;
+ const char *text;
+ int last;
+ char *lastb;
+ ber_len_t len;
+
+ /* safe assumptions ... */
+ assert( ent != NULL );
+ BER_BVZERO( &ent->e_bv );
+
+ if ( ber_scanf( &ber, "{m", bdn ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ *
+ * FIXME: should we log anything, or delegate to dnNormalize?
+ */
+ /* Note: if the distinguished values or the naming attributes
+ * change, should we massage them as well?
+ */
+ if ( dnPrettyNormal( NULL, bdn, &ent->e_name, &ent->e_nname,
+ op->o_tmpmemctx ) != LDAP_SUCCESS )
+ {
+ return LDAP_INVALID_DN_SYNTAX;
+ }
+
+ ent->e_attrs = NULL;
+ if ( ber_first_element( &ber, &len, &lastb ) != LBER_SEQUENCE ) {
+ return LDAP_SUCCESS;
+ }
+
+ attrp = &ent->e_attrs;
+ while ( ber_next_element( &ber, &len, lastb ) == LBER_SEQUENCE &&
+ ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) {
+ int i;
+ slap_syntax_validate_func *validate;
+ slap_syntax_transform_func *pretty;
+
+ attr = attr_alloc( NULL );
+ if ( attr == NULL ) {
+ return LDAP_OTHER;
+ }
+ if ( slap_bv2ad( &a, &attr->a_desc, &text )
+ != LDAP_SUCCESS )
+ {
+ if ( slap_bv2undef_ad( &a, &attr->a_desc, &text,
+ (remove_unknown_schema ? SLAP_AD_NOINSERT : SLAP_AD_PROXIED )) != LDAP_SUCCESS )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "%s ldap_build_entry: "
+ "slap_bv2undef_ad(%s): %s\n",
+ op->o_log_prefix, a.bv_val, text );
+
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ attr_free( attr );
+ continue;
+ }
+ }
+
+ /* no subschemaSubentry */
+ if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry
+ || attr->a_desc == slap_schema.si_ad_entryDN )
+ {
+
+ /*
+ * We eat target's subschemaSubentry because
+ * a search for this value is likely not
+ * to resolve to the appropriate backend;
+ * later, the local subschemaSubentry is
+ * added.
+ *
+ * We also eat entryDN because the frontend
+ * will reattach it without checking if already
+ * present...
+ */
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ attr_free( attr );
+ continue;
+ }
+
+ if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR
+ || attr->a_vals == NULL )
+ {
+ /*
+ * Note: attr->a_vals can be null when using
+ * values result filter
+ */
+ attr->a_vals = (struct berval *)&slap_dummy_bv;
+ }
+
+ validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate;
+ pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( !validate && !pretty ) {
+ attr->a_nvals = NULL;
+ attr_free( attr );
+ goto next_attr;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &attr->a_vals[i] ); i++ ) ;
+ last = i;
+
+ /*
+ * check that each value is valid per syntax
+ * and pretty if appropriate
+ */
+ for ( i = 0; i<last; i++ ) {
+ struct berval pval;
+ int rc;
+
+ if ( pretty ) {
+ rc = ordered_value_pretty( attr->a_desc,
+ &attr->a_vals[i], &pval, NULL );
+
+ } else {
+ rc = ordered_value_validate( attr->a_desc,
+ &attr->a_vals[i], 0 );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ ObjectClass *oc;
+
+ /* check if, by chance, it's an undefined objectClass */
+ if ( attr->a_desc == slap_schema.si_ad_objectClass &&
+ ( oc = oc_bvfind_undef_ex( &attr->a_vals[i],
+ remove_unknown_schema ) ) != NULL )
+ {
+ ber_dupbv( &pval, &oc->soc_cname );
+ rc = LDAP_SUCCESS;
+
+ } else {
+ ber_memfree( attr->a_vals[i].bv_val );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[i] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ }
+ }
+
+ if ( rc == LDAP_SUCCESS && pretty ) {
+ ber_memfree( attr->a_vals[i].bv_val );
+ attr->a_vals[i] = pval;
+ }
+ }
+ attr->a_numvals = last = i;
+ if ( last == 0 && attr->a_vals != &slap_dummy_bv ) {
+ attr->a_nvals = NULL;
+ attr_free( attr );
+ goto next_attr;
+ }
+
+ if ( last && attr->a_desc->ad_type->sat_equality &&
+ attr->a_desc->ad_type->sat_equality->smr_normalize )
+ {
+ attr->a_nvals = ch_malloc( ( last + 1 )*sizeof( struct berval ) );
+ for ( i = 0; i < last; i++ ) {
+ int rc;
+
+ rc = ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ attr->a_desc,
+ attr->a_desc->ad_type->sat_equality,
+ &attr->a_vals[i], &attr->a_nvals[i],
+ NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ ber_memfree( attr->a_vals[i].bv_val );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[i] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ }
+ }
+ BER_BVZERO( &attr->a_nvals[i] );
+ if ( last == 0 ) {
+ attr_free( attr );
+ goto next_attr;
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ attr->a_numvals = last;
+
+ /* Handle sorted vals, strip dups but keep the attr */
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ while ( attr->a_numvals > 1 ) {
+ int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx );
+ if ( rc != LDAP_TYPE_OR_VALUE_EXISTS )
+ break;
+
+ /* Strip duplicate values */
+ if ( attr->a_nvals != attr->a_vals )
+ ber_memfree( attr->a_nvals[i].bv_val );
+ ber_memfree( attr->a_vals[i].bv_val );
+ attr->a_numvals--;
+
+ assert( i >= 0 );
+ if ( (unsigned)i < attr->a_numvals ) {
+ attr->a_vals[i] = attr->a_vals[attr->a_numvals];
+ if ( attr->a_nvals != attr->a_vals )
+ attr->a_nvals[i] = attr->a_nvals[attr->a_numvals];
+ }
+ BER_BVZERO(&attr->a_vals[attr->a_numvals]);
+ if ( attr->a_nvals != attr->a_vals )
+ BER_BVZERO(&attr->a_nvals[attr->a_numvals]);
+ }
+ attr->a_flags |= SLAP_ATTR_SORTED_VALS;
+ }
+
+ *attrp = attr;
+ attrp = &attr->a_next;
+
+next_attr:;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* return 0 IFF we can retrieve the entry with ndn
+ */
+int
+ldap_back_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ ldapinfo_t *li = (ldapinfo_t *) op->o_bd->be_private;
+
+ ldapconn_t *lc = NULL;
+ int rc;
+ struct berval bdn;
+ LDAPMessage *result = NULL,
+ *e = NULL;
+ char *attr[3], **attrp = NULL;
+ char *filter = NULL;
+ SlapReply rs;
+ int do_retry = 1;
+ LDAPControl **ctrls = NULL;
+ Operation op2 = *op;
+
+ int remove_unknown_schema =
+ LDAP_BACK_OMIT_UNKNOWN_SCHEMA (li);
+ *ent = NULL;
+
+ /* Tell getconn this is a privileged op */
+ op2.o_do_not_cache = 1;
+ /* use rootdn to be doubly explicit this is privileged */
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ /* ldap_back_entry_get() is an entry lookup, so it does not need
+ * to know what the entry is being looked up for */
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_ctrls = NULL;
+ rc = ldap_back_dobind( &lc, &op2, &rs, LDAP_BACK_DONTSEND );
+ if ( !rc ) {
+ return rs.sr_err;
+ }
+
+ if ( at ) {
+ attrp = attr;
+ if ( oc && at != slap_schema.si_ad_objectClass ) {
+ attr[0] = slap_schema.si_ad_objectClass->ad_cname.bv_val;
+ attr[1] = at->ad_cname.bv_val;
+ attr[2] = NULL;
+
+ } else {
+ attr[0] = at->ad_cname.bv_val;
+ attr[1] = NULL;
+ }
+ }
+
+ if ( oc ) {
+ char *ptr;
+
+ filter = op->o_tmpalloc( STRLENOF( "(objectClass=" ")" )
+ + oc->soc_cname.bv_len + 1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( filter, "(objectClass=" );
+ ptr = lutil_strcopy( ptr, oc->soc_cname.bv_val );
+ *ptr++ = ')';
+ *ptr++ = '\0';
+ }
+
+retry:
+ ctrls = NULL;
+ rc = ldap_back_controls_add( &op2, &rs, lc, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ /* TODO: timeout? */
+ rc = ldap_pvt_search_s( lc->lc_ld, ndn->bv_val, LDAP_SCOPE_BASE, filter,
+ attrp, LDAP_DEREF_NEVER, ctrls, NULL,
+ NULL, LDAP_NO_LIMIT, 0, &result );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( rc == LDAP_SERVER_DOWN && do_retry ) {
+ do_retry = 0;
+ if ( ldap_back_retry( &lc, &op2, &rs, LDAP_BACK_DONTSEND ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)ldap_back_controls_free( &op2, &rs, &ctrls );
+ goto retry;
+ }
+ }
+ goto cleanup;
+ }
+
+ e = ldap_first_entry( lc->lc_ld, result );
+ if ( e == NULL ) {
+ /* the entry exists, but it doesn't match the filter? */
+ rc = LDAP_NO_RESULTS_RETURNED;
+ goto cleanup;
+ }
+
+ *ent = entry_alloc();
+ if ( *ent == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto cleanup;
+ }
+
+ rc = ldap_build_entry( op, e, *ent, &bdn, remove_unknown_schema );
+
+ if ( rc != LDAP_SUCCESS ) {
+ entry_free( *ent );
+ *ent = NULL;
+ }
+
+cleanup:
+ (void)ldap_back_controls_free( &op2, &rs, &ctrls );
+
+ if ( result ) {
+ ldap_msgfree( result );
+ }
+
+ if ( filter ) {
+ op->o_tmpfree( filter, op->o_tmpmemctx );
+ }
+
+ if ( lc != NULL ) {
+ ldap_back_release_conn( li, lc );
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/back-ldap/unbind.c b/servers/slapd/back-ldap/unbind.c
new file mode 100644
index 0000000..071380e
--- /dev/null
+++ b/servers/slapd/back-ldap/unbind.c
@@ -0,0 +1,78 @@
+/* unbind.c - ldap backend unbind function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-ldap.h"
+
+int
+ldap_back_conn_destroy(
+ Backend *be,
+ Connection *conn
+)
+{
+ ldapinfo_t *li = (ldapinfo_t *) be->be_private;
+ ldapconn_t *lc = NULL, lc_curr;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=>ldap_back_conn_destroy: fetching conn %ld\n",
+ conn->c_connid );
+
+ lc_curr.lc_conn = conn;
+
+ ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, ">>> ldap_back_conn_destroy" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+ while ( ( lc = ldap_tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)&lc_curr, ldap_back_conn_cmp ) ) != NULL )
+ {
+ assert( !LDAP_BACK_PCONN_ISPRIV( lc ) );
+ Debug( LDAP_DEBUG_TRACE,
+ "=>ldap_back_conn_destroy: destroying conn %lu "
+ "refcnt=%d flags=0x%08x\n",
+ lc->lc_conn->c_connid, lc->lc_refcnt, lc->lc_lcflags );
+
+ if ( lc->lc_refcnt > 0 ) {
+ /* someone else might be accessing the connection;
+ * mark for deletion */
+ LDAP_BACK_CONN_CACHED_CLEAR( lc );
+ LDAP_BACK_CONN_TAINTED_SET( lc );
+
+ } else {
+ ldap_back_conn_free( lc );
+ }
+ }
+#if LDAP_BACK_PRINT_CONNTREE > 0
+ ldap_back_print_conntree( li, "<<< ldap_back_conn_destroy" );
+#endif /* LDAP_BACK_PRINT_CONNTREE */
+ ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+ return 0;
+}
diff --git a/servers/slapd/back-ldif/Makefile.in b/servers/slapd/back-ldif/Makefile.in
new file mode 100644
index 0000000..50f2553
--- /dev/null
+++ b/servers/slapd/back-ldif/Makefile.in
@@ -0,0 +1,41 @@
+# Makefile.in for back-ldif
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2005-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = ldif.c
+OBJS = ldif.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-ldif"
+BUILD_MOD = yes
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(yes_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_ldif
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-ldif/ldif.c b/servers/slapd/back-ldif/ldif.c
new file mode 100644
index 0000000..2de8b8a
--- /dev/null
+++ b/servers/slapd/back-ldif/ldif.c
@@ -0,0 +1,2159 @@
+/* ldif.c - the ldif backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Eric Stokes for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+#include <stdio.h>
+#include <ac/string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ac/dirent.h>
+#include <fcntl.h>
+#include <ac/errno.h>
+#include <ac/unistd.h>
+#include "slap.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+struct ldif_tool {
+ Entry **entries; /* collected by bi_tool_entry_first() */
+ ID elen; /* length of entries[] array */
+ ID ecount; /* number of entries */
+ ID ecurrent; /* bi_tool_entry_next() position */
+# define ENTRY_BUFF_INCREMENT 500 /* initial entries[] length */
+ struct berval *tl_base;
+ int tl_scope;
+ Filter *tl_filter;
+};
+
+/* Per-database data */
+struct ldif_info {
+ struct berval li_base_path; /* database directory */
+ struct ldif_tool li_tool; /* for slap tools */
+ /*
+ * Read-only LDAP requests readlock li_rdwr for filesystem input.
+ * Update requests first lock li_modop_mutex for filesystem I/O,
+ * and then writelock li_rdwr as well for filesystem output.
+ * This allows update requests to do callbacks that acquire
+ * read locks, e.g. access controls that inspect entries.
+ * (An alternative would be recursive read/write locks.)
+ */
+ ldap_pvt_thread_mutex_t li_modop_mutex; /* serialize update requests */
+ ldap_pvt_thread_rdwr_t li_rdwr; /* no other I/O when writing */
+};
+
+static int write_data( int fd, const char *spew, int len, int *save_errno );
+
+#ifdef _WIN32
+#define mkdir(a,b) mkdir(a)
+#define move_file(from, to) (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING))
+#else
+#define move_file(from, to) rename(from, to)
+#endif
+#define move_dir(from, to) rename(from, to)
+
+
+#define LDIF ".ldif"
+#define LDIF_FILETYPE_SEP '.' /* LDIF[0] */
+
+/*
+ * Unsafe/translated characters in the filesystem.
+ *
+ * LDIF_UNSAFE_CHAR(c) returns true if the character c is not to be used
+ * in relative filenames, except it should accept '\\', '{' and '}' even
+ * if unsafe. The value should be a constant expression.
+ *
+ * If '\\' is unsafe, #define LDIF_ESCAPE_CHAR as a safe character.
+ * If '{' and '}' are unsafe, #define IX_FSL/IX_FSR as safe characters.
+ * (Not digits, '-' or '+'. IX_FSL == IX_FSR is allowed.)
+ *
+ * Characters are escaped as LDIF_ESCAPE_CHAR followed by two hex digits,
+ * except '\\' is replaced with LDIF_ESCAPE_CHAR and {} with IX_FS[LR].
+ * Also some LDIF special chars are hex-escaped.
+ *
+ * Thus an LDIF filename is a valid normalized RDN (or suffix DN)
+ * followed by ".ldif", except with '\\' replaced with LDIF_ESCAPE_CHAR.
+ */
+
+#ifndef _WIN32
+
+/*
+ * Unix/MacOSX version. ':' vs '/' can cause confusion on MacOSX so we
+ * escape both. We escape them on Unix so both OS variants get the same
+ * filenames.
+ */
+#define LDIF_ESCAPE_CHAR '\\'
+#define LDIF_UNSAFE_CHAR(c) ((c) == '/' || (c) == ':')
+
+#else /* _WIN32 */
+
+/* Windows version - Microsoft's list of unsafe characters, except '\\' */
+#define LDIF_ESCAPE_CHAR '^' /* Not '\\' (unsafe on Windows) */
+#define LDIF_UNSAFE_CHAR(c) \
+ ((c) == '/' || (c) == ':' || \
+ (c) == '<' || (c) == '>' || (c) == '"' || \
+ (c) == '|' || (c) == '?' || (c) == '*')
+
+#endif /* !_WIN32 */
+
+/*
+ * Left and Right "{num}" prefix to ordered RDNs ("olcDatabase={1}mdb").
+ * IX_DN* are for LDAP RDNs, IX_FS* for their .ldif filenames.
+ */
+#define IX_DNL '{'
+#define IX_DNR '}'
+#ifndef IX_FSL
+#define IX_FSL IX_DNL
+#define IX_FSR IX_DNR
+#endif
+
+/*
+ * Test for unsafe chars, as well as chars handled specially by back-ldif:
+ * - If the escape char is not '\\', it must itself be escaped. Otherwise
+ * '\\' and the escape char would map to the same character.
+ * - Escape the '.' in ".ldif", so the directory for an RDN that actually
+ * ends with ".ldif" can not conflict with a file of the same name. And
+ * since some OSes/programs choke on multiple '.'s, escape all of them.
+ * - If '{' and '}' are translated to some other characters, those
+ * characters must in turn be escaped when they occur in an RDN.
+ */
+#ifndef LDIF_NEED_ESCAPE
+#define LDIF_NEED_ESCAPE(c) \
+ ((LDIF_UNSAFE_CHAR(c)) || \
+ LDIF_MAYBE_UNSAFE(c, LDIF_ESCAPE_CHAR) || \
+ LDIF_MAYBE_UNSAFE(c, LDIF_FILETYPE_SEP) || \
+ LDIF_MAYBE_UNSAFE(c, IX_FSL) || \
+ (IX_FSR != IX_FSL && LDIF_MAYBE_UNSAFE(c, IX_FSR)))
+#endif
+/*
+ * Helper macro for LDIF_NEED_ESCAPE(): Treat character x as unsafe if
+ * back-ldif does not already treat is specially.
+ */
+#define LDIF_MAYBE_UNSAFE(c, x) \
+ (!(LDIF_UNSAFE_CHAR(x) || (x) == '\\' || (x) == IX_DNL || (x) == IX_DNR) \
+ && (c) == (x))
+
+/* Collect other "safe char" tests here, until someone needs a fix. */
+enum {
+ eq_unsafe = LDIF_UNSAFE_CHAR('='),
+ safe_filenames = STRLENOF("" LDAP_DIRSEP "") == 1 && !(
+ LDIF_UNSAFE_CHAR('-') || /* for "{-1}frontend" in bconfig.c */
+ LDIF_UNSAFE_CHAR(LDIF_ESCAPE_CHAR) ||
+ LDIF_UNSAFE_CHAR(IX_FSL) || LDIF_UNSAFE_CHAR(IX_FSR))
+};
+/* Sanity check: Try to force a compilation error if !safe_filenames */
+typedef struct {
+ int assert_safe_filenames : safe_filenames ? 2 : -2;
+} assert_safe_filenames[safe_filenames ? 2 : -2];
+
+
+static ConfigTable ldifcfg[] = {
+ { "directory", "dir", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct ldif_info, li_base_path),
+ "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' "
+ "DESC 'Directory for database content' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs ldifocs[] = {
+ { "( OLcfgDbOc:2.1 "
+ "NAME 'olcLdifConfig' "
+ "DESC 'LDIF backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST ( olcDbDirectory ) )", Cft_Database, ldifcfg },
+ { NULL, 0, NULL }
+};
+
+
+/*
+ * Handle file/directory names.
+ */
+
+/* Set *res = LDIF filename path for the normalized DN */
+static int
+ndn2path( Operation *op, struct berval *dn, struct berval *res, int empty_ok )
+{
+ BackendDB *be = op->o_bd;
+ struct ldif_info *li = (struct ldif_info *) be->be_private;
+ struct berval *suffixdn = &be->be_nsuffix[0];
+ const char *start, *end, *next, *p;
+ char ch, *ptr;
+ ber_len_t len;
+ static const char hex[] = "0123456789ABCDEF";
+
+ assert( dn != NULL );
+ assert( !BER_BVISNULL( dn ) );
+ assert( suffixdn != NULL );
+ assert( !BER_BVISNULL( suffixdn ) );
+ assert( dnIsSuffix( dn, suffixdn ) );
+
+ if ( dn->bv_len == 0 && !empty_ok ) {
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ start = dn->bv_val;
+ end = start + dn->bv_len;
+
+ /* Room for dir, dirsep, dn, LDIF, "\hexpair"-escaping of unsafe chars */
+ len = li->li_base_path.bv_len + dn->bv_len + (1 + STRLENOF( LDIF ));
+ for ( p = start; p < end; ) {
+ ch = *p++;
+ if ( LDIF_NEED_ESCAPE( ch ) )
+ len += 2;
+ }
+ res->bv_val = ch_malloc( len + 1 );
+
+ ptr = lutil_strcopy( res->bv_val, li->li_base_path.bv_val );
+ for ( next = end - suffixdn->bv_len; end > start; end = next ) {
+ /* Set p = start of DN component, next = &',' or start of DN */
+ while ( (p = next) > start ) {
+ --next;
+ if ( DN_SEPARATOR( *next ) )
+ break;
+ }
+ /* Append <dirsep> <p..end-1: RDN or database-suffix> */
+ for ( *ptr++ = LDAP_DIRSEP[0]; p < end; *ptr++ = ch ) {
+ ch = *p++;
+ if ( LDIF_ESCAPE_CHAR != '\\' && ch == '\\' ) {
+ ch = LDIF_ESCAPE_CHAR;
+ } else if ( IX_FSL != IX_DNL && ch == IX_DNL ) {
+ ch = IX_FSL;
+ } else if ( IX_FSR != IX_DNR && ch == IX_DNR ) {
+ ch = IX_FSR;
+ } else if ( LDIF_NEED_ESCAPE( ch ) ) {
+ *ptr++ = LDIF_ESCAPE_CHAR;
+ *ptr++ = hex[(ch & 0xFFU) >> 4];
+ ch = hex[ch & 0x0FU];
+ }
+ }
+ }
+ ptr = lutil_strcopy( ptr, LDIF );
+ res->bv_len = ptr - res->bv_val;
+
+ assert( res->bv_len <= len );
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * *dest = dupbv(<dir + LDAP_DIRSEP>), plus room for <more>-sized filename.
+ * Return pointer past the dirname.
+ */
+static char *
+fullpath_alloc( struct berval *dest, const struct berval *dir, ber_len_t more )
+{
+ char *s = SLAP_MALLOC( dir->bv_len + more + 2 );
+
+ dest->bv_val = s;
+ if ( s == NULL ) {
+ dest->bv_len = 0;
+ Debug( LDAP_DEBUG_ANY, "back-ldif: out of memory\n" );
+ } else {
+ s = lutil_strcopy( dest->bv_val, dir->bv_val );
+ *s++ = LDAP_DIRSEP[0];
+ *s = '\0';
+ dest->bv_len = s - dest->bv_val;
+ }
+ return s;
+}
+
+/*
+ * Append filename to fullpath_alloc() dirname or replace previous filename.
+ * dir_end = fullpath_alloc() return value.
+ */
+#define FILL_PATH(fpath, dir_end, filename) \
+ ((fpath)->bv_len = lutil_strcopy(dir_end, filename) - (fpath)->bv_val)
+
+
+/* .ldif entry filename length <-> subtree dirname length. */
+#define ldif2dir_len(bv) ((bv).bv_len -= STRLENOF(LDIF))
+#define dir2ldif_len(bv) ((bv).bv_len += STRLENOF(LDIF))
+/* .ldif entry filename <-> subtree dirname, both with dirname length. */
+#define ldif2dir_name(bv) ((bv).bv_val[(bv).bv_len] = '\0')
+#define dir2ldif_name(bv) ((bv).bv_val[(bv).bv_len] = LDIF_FILETYPE_SEP)
+
+/* Get the parent directory path, plus the LDIF suffix overwritten by a \0. */
+static int
+get_parent_path( struct berval *dnpath, struct berval *res )
+{
+ ber_len_t i = dnpath->bv_len;
+
+ while ( i > 0 && dnpath->bv_val[ --i ] != LDAP_DIRSEP[0] ) ;
+ if ( res == NULL ) {
+ res = dnpath;
+ } else {
+ res->bv_val = SLAP_MALLOC( i + 1 + STRLENOF(LDIF) );
+ if ( res->bv_val == NULL )
+ return LDAP_OTHER;
+ AC_MEMCPY( res->bv_val, dnpath->bv_val, i );
+ }
+ res->bv_len = i;
+ strcpy( res->bv_val + i, LDIF );
+ res->bv_val[i] = '\0';
+ return LDAP_SUCCESS;
+}
+
+/* Make temporary filename pattern for mkstemp() based on dnpath. */
+static char *
+ldif_tempname( const struct berval *dnpath )
+{
+ static const char suffix[] = ".XXXXXX";
+ ber_len_t len = dnpath->bv_len - STRLENOF( LDIF );
+ char *name = SLAP_MALLOC( len + sizeof( suffix ) );
+
+ if ( name != NULL ) {
+ AC_MEMCPY( name, dnpath->bv_val, len );
+ strcpy( name + len, suffix );
+ }
+ return name;
+}
+
+/* CRC-32 table for the polynomial:
+ * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
+ *
+ * As used by zlib
+ */
+
+static const ber_uint_t crctab[256] = {
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+};
+
+#define CRC1 crc = crctab[(crc ^ *buf++) & 0xff] ^ (crc >> 8)
+#define CRC8 CRC1; CRC1; CRC1; CRC1; CRC1; CRC1; CRC1; CRC1
+unsigned int
+crc32(const void *vbuf, int len)
+{
+ const unsigned char *buf = vbuf;
+ ber_uint_t crc = 0xffffffff;
+
+ while (len > 7) {
+ CRC8;
+ len -= 8;
+ }
+ while (len) {
+ CRC1;
+ len--;
+ }
+
+ return crc ^ 0xffffffff;
+}
+
+/*
+ * Read a file, or stat() it if datap == NULL. Allocate and fill *datap.
+ * Return LDAP_SUCCESS, LDAP_NO_SUCH_OBJECT (no such file), or another error.
+ */
+static int
+ldif_read_file( const char *path, char **datap )
+{
+ int rc = LDAP_SUCCESS, fd, len;
+ int res = -1; /* 0:success, <0:error, >0:file too big/growing. */
+ struct stat st;
+ char *data = NULL, *ptr = NULL;
+ const char *msg;
+
+ if ( datap == NULL ) {
+ res = stat( path, &st );
+ goto done;
+ }
+ fd = open( path, O_RDONLY );
+ if ( fd >= 0 ) {
+ if ( fstat( fd, &st ) == 0 ) {
+ if ( st.st_size > INT_MAX - 2 ) {
+ res = 1;
+ } else {
+ len = st.st_size + 1; /* +1 detects file size > st.st_size */
+ *datap = data = ptr = SLAP_MALLOC( len + 1 );
+ if ( ptr != NULL ) {
+ while ( len && (res = read( fd, ptr, len )) ) {
+ if ( res > 0 ) {
+ len -= res;
+ ptr += res;
+ } else if ( errno != EINTR ) {
+ break;
+ }
+ }
+ *ptr = '\0';
+ }
+ }
+ }
+ if ( close( fd ) < 0 )
+ res = -1;
+ }
+
+ done:
+ if ( res == 0 ) {
+#ifdef LDAP_DEBUG
+ msg = "entry file exists";
+ if ( datap ) {
+ msg = "read entry file";
+ len = ptr - data;
+ ptr = strstr( data, "\n# CRC32" );
+ if (!ptr) {
+ msg = "read entry file without checksum";
+ } else {
+ unsigned int crc1 = 0, crc2 = 1;
+ if ( sscanf( ptr + 9, "%08x", &crc1) == 1) {
+ ptr = strchr(ptr+1, '\n');
+ if ( ptr ) {
+ ptr++;
+ len -= (ptr - data);
+ crc2 = crc32( ptr, len );
+ }
+ }
+ if ( crc1 != crc2 ) {
+ Debug( LDAP_DEBUG_ANY, "ldif_read_file: checksum error on \"%s\"\n",
+ path );
+ return rc;
+ }
+ }
+ }
+ Debug( LDAP_DEBUG_TRACE, "ldif_read_file: %s: \"%s\"\n", msg, path );
+#endif /* LDAP_DEBUG */
+ } else {
+ char ebuf[128];
+ if ( res < 0 && errno == ENOENT ) {
+ Debug( LDAP_DEBUG_TRACE, "ldif_read_file: "
+ "no entry file \"%s\"\n", path );
+ rc = LDAP_NO_SUCH_OBJECT;
+ } else {
+ msg = res < 0 ? AC_STRERROR_R( errno, ebuf, sizeof(ebuf) ) : "bad stat() size";
+ Debug( LDAP_DEBUG_ANY, "ldif_read_file: %s for \"%s\"\n",
+ msg, path );
+ rc = LDAP_OTHER;
+ }
+ if ( data != NULL )
+ SLAP_FREE( data );
+ }
+ return rc;
+}
+
+/*
+ * return nonnegative for success or -1 for error
+ * do not return numbers less than -1
+ */
+static int
+spew_file( int fd, const char *spew, int len, int *save_errno )
+{
+ int writeres;
+#define HEADER "# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.\n"
+ char header[sizeof(HEADER "# CRC32 12345678\n")];
+
+ sprintf(header, HEADER "# CRC32 %08x\n", crc32(spew, len));
+ writeres = write_data(fd, header, sizeof(header)-1, save_errno);
+ return writeres < 0 ? writeres : write_data(fd, spew, len, save_errno);
+}
+
+static int
+write_data( int fd, const char *spew, int len, int *save_errno )
+{
+ int writeres = 0;
+ while(len > 0) {
+ writeres = write(fd, spew, len);
+ if(writeres == -1) {
+ *save_errno = errno;
+ if (*save_errno != EINTR)
+ break;
+ }
+ else {
+ spew += writeres;
+ len -= writeres;
+ }
+ }
+ return writeres;
+}
+
+/* Write an entry LDIF file. Create parentdir first if non-NULL. */
+static int
+ldif_write_entry(
+ Operation *op,
+ Entry *e,
+ const struct berval *path,
+ const char *parentdir,
+ const char **text )
+{
+ int rc = LDAP_OTHER, res, save_errno = 0;
+ int fd, entry_length;
+ char *entry_as_string, *tmpfname;
+ char ebuf[128];
+
+ if ( op->o_abandon )
+ return SLAPD_ABANDON;
+
+ if ( parentdir != NULL && mkdir( parentdir, 0750 ) < 0 ) {
+ save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n",
+ "cannot create parent directory",
+ parentdir, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ *text = "internal error (cannot create parent directory)";
+ return rc;
+ }
+
+ tmpfname = ldif_tempname( path );
+ fd = tmpfname == NULL ? -1 : mkstemp( tmpfname );
+ if ( fd < 0 ) {
+ save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s for \"%s\": %s\n",
+ "cannot create file", e->e_dn, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ *text = "internal error (cannot create file)";
+
+ } else {
+ ber_len_t dn_len = e->e_name.bv_len;
+ struct berval rdn;
+
+ /* Only save the RDN onto disk */
+ dnRdn( &e->e_name, &rdn );
+ if ( rdn.bv_len != dn_len ) {
+ e->e_name.bv_val[rdn.bv_len] = '\0';
+ e->e_name.bv_len = rdn.bv_len;
+ }
+
+ res = -2;
+ ldap_pvt_thread_mutex_lock( &entry2str_mutex );
+ entry_as_string = entry2str( e, &entry_length );
+ if ( entry_as_string != NULL )
+ res = spew_file( fd, entry_as_string, entry_length, &save_errno );
+ ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
+
+ /* Restore full DN */
+ if ( rdn.bv_len != dn_len ) {
+ e->e_name.bv_val[rdn.bv_len] = ',';
+ e->e_name.bv_len = dn_len;
+ }
+
+ if ( close( fd ) < 0 && res >= 0 ) {
+ res = -1;
+ save_errno = errno;
+ }
+
+ if ( res >= 0 ) {
+ if ( move_file( tmpfname, path->bv_val ) == 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "ldif_write_entry: "
+ "wrote entry \"%s\"\n", e->e_name.bv_val );
+ rc = LDAP_SUCCESS;
+ } else {
+ save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "ldif_write_entry: "
+ "could not put entry file for \"%s\" in place: %s\n",
+ e->e_name.bv_val, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ *text = "internal error (could not put entry file in place)";
+ }
+ } else if ( res == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n",
+ "write error to", tmpfname, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ *text = "internal error (write error to entry file)";
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ unlink( tmpfname );
+ }
+ }
+
+ if ( tmpfname )
+ SLAP_FREE( tmpfname );
+ return rc;
+}
+
+/*
+ * Read the entry at path, or if entryp==NULL just see if it exists.
+ * pdn and pndn are the parent's DN and normalized DN, or both NULL.
+ * Return an LDAP result code.
+ */
+static int
+ldif_read_entry(
+ Operation *op,
+ const char *path,
+ struct berval *pdn,
+ struct berval *pndn,
+ Entry **entryp,
+ const char **text )
+{
+ int rc;
+ Entry *entry;
+ char *entry_as_string;
+ struct berval rdn;
+
+ /* TODO: Does slapd prevent Abandon of Bind as per rfc4511?
+ * If so we need not check for LDAP_REQ_BIND here.
+ */
+ if ( op->o_abandon && op->o_tag != LDAP_REQ_BIND )
+ return SLAPD_ABANDON;
+
+ rc = ldif_read_file( path, entryp ? &entry_as_string : NULL );
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ if ( entryp == NULL )
+ break;
+ *entryp = entry = str2entry( entry_as_string );
+ SLAP_FREE( entry_as_string );
+ if ( entry == NULL ) {
+ rc = LDAP_OTHER;
+ if ( text != NULL )
+ *text = "internal error (cannot parse some entry file)";
+ break;
+ }
+ if ( pdn == NULL || BER_BVISEMPTY( pdn ) )
+ break;
+ /* Append parent DN to DN from LDIF file */
+ rdn = entry->e_name;
+ build_new_dn( &entry->e_name, pdn, &rdn, NULL );
+ SLAP_FREE( rdn.bv_val );
+ rdn = entry->e_nname;
+ build_new_dn( &entry->e_nname, pndn, &rdn, NULL );
+ SLAP_FREE( rdn.bv_val );
+ break;
+
+ case LDAP_OTHER:
+ if ( text != NULL )
+ *text = entryp
+ ? "internal error (cannot read some entry file)"
+ : "internal error (cannot stat some entry file)";
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Read the operation's entry, or if entryp==NULL just see if it exists.
+ * Return an LDAP result code. May set *text to a message on failure.
+ * If pathp is non-NULL, set it to the entry filename on success.
+ */
+static int
+get_entry(
+ Operation *op,
+ Entry **entryp,
+ struct berval *pathp,
+ const char **text )
+{
+ int rc;
+ struct berval path, pdn, pndn;
+
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+ rc = ndn2path( op, &op->o_req_ndn, &path, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = ldif_read_entry( op, path.bv_val, &pdn, &pndn, entryp, text );
+
+ if ( rc == LDAP_SUCCESS && pathp != NULL ) {
+ *pathp = path;
+ } else {
+ SLAP_FREE( path.bv_val );
+ }
+ done:
+ return rc;
+}
+
+
+/*
+ * RDN-named directory entry, with special handling of "attr={num}val" RDNs.
+ * For sorting, filename "attr=val.ldif" is truncated to "attr="val\0ldif",
+ * and filename "attr={num}val.ldif" to "attr={\0um}val.ldif".
+ * Does not sort escaped chars correctly, would need to un-escape them.
+ */
+typedef struct bvlist {
+ struct bvlist *next;
+ char *trunc; /* filename was truncated here */
+ int inum; /* num from "attr={num}" in filename, or INT_MIN */
+ char savech; /* original char at *trunc */
+ /* BVL_NAME(&bvlist) is the filename, allocated after the struct: */
+# define BVL_NAME(bvl) ((char *) ((bvl) + 1))
+# define BVL_SIZE(namelen) (sizeof(bvlist) + (namelen) + 1)
+} bvlist;
+
+static int
+ldif_send_entry( Operation *op, SlapReply *rs, Entry *e, int scope )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( scope == LDAP_SCOPE_BASE || scope == LDAP_SCOPE_SUBTREE ) {
+ if ( rs == NULL ) {
+ /* Save the entry for tool mode */
+ struct ldif_tool *tl =
+ &((struct ldif_info *) op->o_bd->be_private)->li_tool;
+
+ if ( tl->ecount >= tl->elen ) {
+ /* Allocate/grow entries */
+ ID elen = tl->elen ? tl->elen * 2 : ENTRY_BUFF_INCREMENT;
+ Entry **entries = (Entry **) SLAP_REALLOC( tl->entries,
+ sizeof(Entry *) * elen );
+ if ( entries == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldif_send_entry: out of memory\n" );
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ tl->elen = elen;
+ tl->entries = entries;
+ }
+ tl->entries[tl->ecount++] = e;
+ e->e_id = tl->ecount;
+ return rc;
+ }
+
+ else if ( !get_manageDSAit( op ) && is_entry_referral( e ) ) {
+ /* Send a continuation reference.
+ * (ldif_back_referrals() handles baseobject referrals.)
+ * Don't check the filter since it's only a candidate.
+ */
+ BerVarray refs = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( refs, &e->e_name, NULL, scope );
+ rs->sr_entry = e;
+ rc = send_search_reference( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ ber_bvarray_free( refs );
+ rs->sr_ref = NULL;
+ rs->sr_entry = NULL;
+ }
+
+ else if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = e;
+ rs->sr_attrs = op->ors_attrs;
+ /* Could set REP_ENTRY_MUSTBEFREED too for efficiency,
+ * but refraining lets us test unFREEable MODIFIABLE
+ * entries. Like entries built on the stack.
+ */
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ rc = send_search_entry( op, rs );
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ }
+ }
+
+ done:
+ entry_free( e );
+ return rc;
+}
+
+/* Read LDIF directory <path> into <listp>. Set *fname_maxlenp. */
+static int
+ldif_readdir(
+ Operation *op,
+ SlapReply *rs,
+ const struct berval *path,
+ bvlist **listp,
+ ber_len_t *fname_maxlenp )
+{
+ int rc = LDAP_SUCCESS;
+ DIR *dir_of_path;
+ char ebuf[128];
+
+ *listp = NULL;
+ *fname_maxlenp = 0;
+
+ dir_of_path = opendir( path->bv_val );
+ if ( dir_of_path == NULL ) {
+ int save_errno = errno;
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ int is_rootDSE = (path->bv_len == li->li_base_path.bv_len);
+
+ /* Absent directory is OK (leaf entry), except the database dir */
+ if ( is_rootDSE || save_errno != ENOENT ) {
+ Debug( LDAP_DEBUG_ANY,
+ "=> ldif_search_entry: failed to opendir \"%s\": %s\n",
+ path->bv_val, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ rc = LDAP_OTHER;
+ if ( rs != NULL )
+ rs->sr_text =
+ save_errno != ENOENT ? "internal error (bad directory)"
+ : "internal error (database directory does not exist)";
+ }
+
+ } else {
+ bvlist *ptr;
+ struct dirent *dir;
+ int save_errno = 0;
+
+ while ( (dir = readdir( dir_of_path )) != NULL ) {
+ size_t fname_len;
+ bvlist *bvl, **prev;
+ char *trunc, *idxp, *endp, *endp2;
+
+ fname_len = strlen( dir->d_name );
+ if ( fname_len < STRLENOF( "x=" LDIF )) /* min filename size */
+ continue;
+ if ( strcmp( dir->d_name + fname_len - STRLENOF(LDIF), LDIF ))
+ continue;
+
+ if ( *fname_maxlenp < fname_len )
+ *fname_maxlenp = fname_len;
+
+ bvl = SLAP_MALLOC( BVL_SIZE( fname_len ) );
+ if ( bvl == NULL ) {
+ rc = LDAP_OTHER;
+ save_errno = errno;
+ break;
+ }
+ strcpy( BVL_NAME( bvl ), dir->d_name );
+
+ /* Make it sortable by ("attr=val" or <preceding {num}, num>) */
+ trunc = BVL_NAME( bvl ) + fname_len - STRLENOF( LDIF );
+ if ( (idxp = strchr( BVL_NAME( bvl ) + 2, IX_FSL )) != NULL &&
+ (endp = strchr( ++idxp, IX_FSR )) != NULL && endp > idxp &&
+ (eq_unsafe || idxp[-2] == '=' || endp + 1 == trunc) )
+ {
+ /* attr={n}val or bconfig.c's "pseudo-indexed" attr=val{n} */
+ bvl->inum = strtol( idxp, &endp2, 10 );
+ if ( endp2 == endp ) {
+ trunc = idxp;
+ goto truncate;
+ }
+ }
+ bvl->inum = INT_MIN;
+ truncate:
+ bvl->trunc = trunc;
+ bvl->savech = *trunc;
+ *trunc = '\0';
+
+ /* Insertion sort */
+ for ( prev = listp; (ptr = *prev) != NULL; prev = &ptr->next ) {
+ int cmp = strcmp( BVL_NAME( bvl ), BVL_NAME( ptr ));
+ if ( cmp < 0 || (cmp == 0 && bvl->inum < ptr->inum) )
+ break;
+ }
+ *prev = bvl;
+ bvl->next = ptr;
+ }
+
+ if ( closedir( dir_of_path ) < 0 ) {
+ save_errno = errno;
+ rc = LDAP_OTHER;
+ if ( rs != NULL )
+ rs->sr_text = "internal error (bad directory)";
+ }
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "ldif_search_entry: %s \"%s\": %s\n",
+ "error reading directory", path->bv_val,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Send an entry, recursively search its children, and free or save it.
+ * Return an LDAP result code. Parameters:
+ * op, rs operation and reply. rs == NULL for slap tools.
+ * e entry to search, or NULL for rootDSE.
+ * scope scope for the part of the search from this entry.
+ * path LDIF filename -- bv_len and non-directory part are overwritten.
+ */
+static int
+ldif_search_entry(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ int scope,
+ struct berval *path )
+{
+ int rc = LDAP_SUCCESS;
+ struct berval dn = BER_BVC( "" ), ndn = BER_BVC( "" );
+
+ if ( scope != LDAP_SCOPE_BASE && e != NULL ) {
+ /* Copy DN/NDN since we send the entry with REP_ENTRY_MODIFIABLE,
+ * which bconfig.c seems to need. (TODO: see config_rename_one.)
+ */
+ if ( ber_dupbv( &dn, &e->e_name ) == NULL ||
+ ber_dupbv( &ndn, &e->e_nname ) == NULL )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "ldif_search_entry: out of memory\n" );
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+ /* Send the entry if appropriate, and free or save it */
+ if ( e != NULL )
+ rc = ldif_send_entry( op, rs, e, scope );
+
+ /* Search the children */
+ if ( scope != LDAP_SCOPE_BASE && rc == LDAP_SUCCESS ) {
+ bvlist *list, *ptr;
+ struct berval fpath; /* becomes child pathname */
+ char *dir_end; /* will point past dirname in fpath */
+
+ ldif2dir_len( *path );
+ ldif2dir_name( *path );
+ rc = ldif_readdir( op, rs, path, &list, &fpath.bv_len );
+
+ if ( list != NULL ) {
+ const char **text = rs == NULL ? NULL : &rs->sr_text;
+
+ if ( scope == LDAP_SCOPE_ONELEVEL )
+ scope = LDAP_SCOPE_BASE;
+ else if ( scope == LDAP_SCOPE_SUBORDINATE )
+ scope = LDAP_SCOPE_SUBTREE;
+
+ /* Allocate fpath and fill in directory part */
+ dir_end = fullpath_alloc( &fpath, path, fpath.bv_len );
+ if ( dir_end == NULL )
+ rc = LDAP_OTHER;
+
+ do {
+ ptr = list;
+
+ if ( rc == LDAP_SUCCESS ) {
+ *ptr->trunc = ptr->savech;
+ FILL_PATH( &fpath, dir_end, BVL_NAME( ptr ));
+
+ rc = ldif_read_entry( op, fpath.bv_val, &dn, &ndn,
+ &e, text );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ rc = ldif_search_entry( op, rs, e, scope, &fpath );
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+ /* Only the search baseDN may produce noSuchObject. */
+ rc = LDAP_OTHER;
+ if ( rs != NULL )
+ rs->sr_text = "internal error "
+ "(did someone just remove an entry file?)";
+ Debug( LDAP_DEBUG_ANY, "ldif_search_entry: "
+ "file listed in parent directory does not exist: "
+ "\"%s\"\n", fpath.bv_val );
+ break;
+ }
+ }
+
+ list = ptr->next;
+ SLAP_FREE( ptr );
+ } while ( list != NULL );
+
+ if ( !BER_BVISNULL( &fpath ) )
+ SLAP_FREE( fpath.bv_val );
+ }
+ }
+
+ done:
+ if ( !BER_BVISEMPTY( &dn ) )
+ ber_memfree( dn.bv_val );
+ if ( !BER_BVISEMPTY( &ndn ) )
+ ber_memfree( ndn.bv_val );
+ return rc;
+}
+
+static int
+search_tree( Operation *op, SlapReply *rs )
+{
+ int rc = LDAP_SUCCESS;
+ Entry *e = NULL;
+ struct berval path;
+ struct berval pdn, pndn;
+
+ (void) ndn2path( op, &op->o_req_ndn, &path, 1 );
+ if ( !BER_BVISEMPTY( &op->o_req_ndn ) ) {
+ /* Read baseObject */
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+ rc = ldif_read_entry( op, path.bv_val, &pdn, &pndn, &e,
+ rs == NULL ? NULL : &rs->sr_text );
+ }
+ if ( rc == LDAP_SUCCESS )
+ rc = ldif_search_entry( op, rs, e, op->ors_scope, &path );
+
+ ch_free( path.bv_val );
+ return rc;
+}
+
+
+/*
+ * Prepare to create or rename an entry:
+ * Check that the entry does not already exist.
+ * Check that the parent entry exists and can have subordinates,
+ * unless need_dir is NULL or adding the suffix entry.
+ *
+ * Return an LDAP result code. May set *text to a message on failure.
+ * If success, set *dnpath to LDIF entry path and *need_dir to
+ * (directory must be created ? dirname : NULL).
+ */
+static int
+ldif_prepare_create(
+ Operation *op,
+ Entry *e,
+ struct berval *dnpath,
+ char **need_dir,
+ const char **text )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval *ndn = &e->e_nname;
+ struct berval ppath = BER_BVNULL;
+ struct stat st;
+ Entry *parent = NULL;
+ int rc;
+ char ebuf[128];
+
+ if ( op->o_abandon )
+ return SLAPD_ABANDON;
+
+ rc = ndn2path( op, ndn, dnpath, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( stat( dnpath->bv_val, &st ) == 0 ) { /* entry .ldif file */
+ rc = LDAP_ALREADY_EXISTS;
+
+ } else if ( errno != ENOENT ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ldif_prepare_create: cannot stat \"%s\": %s\n",
+ dnpath->bv_val, AC_STRERROR_R( errno, ebuf, sizeof(ebuf) ) );
+ rc = LDAP_OTHER;
+ *text = "internal error (cannot check entry file)";
+
+ } else if ( need_dir != NULL ) {
+ *need_dir = NULL;
+ rc = get_parent_path( dnpath, &ppath );
+ /* If parent dir exists, so does parent .ldif:
+ * The directory gets created after and removed before the .ldif.
+ * Except with the database directory, which has no matching entry.
+ */
+ if ( rc == LDAP_SUCCESS && stat( ppath.bv_val, &st ) < 0 ) {
+ rc = errno == ENOENT && ppath.bv_len > li->li_base_path.bv_len
+ ? LDAP_NO_SUCH_OBJECT : LDAP_OTHER;
+ }
+ switch ( rc ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* No parent dir, check parent .ldif */
+ dir2ldif_name( ppath );
+ rc = ldif_read_entry( op, ppath.bv_val, NULL, NULL,
+ (op->o_tag != LDAP_REQ_ADD || get_manageDSAit( op )
+ ? &parent : NULL),
+ text );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ /* Check that parent is not a referral, unless
+ * ldif_back_referrals() already checked.
+ */
+ if ( parent != NULL ) {
+ int is_ref = is_entry_referral( parent );
+ entry_free( parent );
+ if ( is_ref ) {
+ rc = LDAP_AFFECTS_MULTIPLE_DSAS;
+ *text = op->o_tag == LDAP_REQ_MODDN
+ ? "newSuperior is a referral object"
+ : "parent is a referral object";
+ break;
+ }
+ }
+ /* Must create parent directory. */
+ ldif2dir_name( ppath );
+ *need_dir = ppath.bv_val;
+ break;
+ case LDAP_NO_SUCH_OBJECT:
+ *text = op->o_tag == LDAP_REQ_MODDN
+ ? "newSuperior object does not exist"
+ : "parent does not exist";
+ break;
+ }
+ break;
+ case LDAP_OTHER:
+ Debug( LDAP_DEBUG_ANY,
+ "ldif_prepare_create: cannot stat \"%s\" parent dir: %s\n",
+ ndn->bv_val, AC_STRERROR_R( errno, ebuf, sizeof(ebuf) ) );
+ *text = "internal error (cannot stat parent dir)";
+ break;
+ }
+ if ( *need_dir == NULL && ppath.bv_val != NULL )
+ SLAP_FREE( ppath.bv_val );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ SLAP_FREE( dnpath->bv_val );
+ BER_BVZERO( dnpath );
+ }
+ return rc;
+}
+
+static int
+apply_modify_to_entry(
+ Entry *entry,
+ Modifications *modlist,
+ Operation *op,
+ SlapReply *rs,
+ char *textbuf )
+{
+ int rc = modlist ? LDAP_UNWILLING_TO_PERFORM : LDAP_SUCCESS;
+ int is_oc = 0;
+ Modification *mods;
+
+ if (!acl_check_modlist(op, entry, modlist)) {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ for (; modlist != NULL; modlist = modlist->sml_next) {
+ mods = &modlist->sml_mod;
+
+ if ( mods->sm_desc == slap_schema.si_ad_objectClass ) {
+ is_oc = 1;
+ }
+ switch (mods->sm_op) {
+ case LDAP_MOD_ADD:
+ rc = modify_add_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ break;
+
+ case LDAP_MOD_DELETE:
+ rc = modify_delete_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ rc = modify_replace_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ rc = modify_increment_values( entry,
+ mods, get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ mods->sm_op = LDAP_MOD_ADD;
+ rc = modify_add_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ mods->sm_op = SLAP_MOD_SOFTADD;
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ rc = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_SOFTDEL:
+ mods->sm_op = LDAP_MOD_DELETE;
+ rc = modify_delete_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ mods->sm_op = SLAP_MOD_SOFTDEL;
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
+ rc = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( attr_find( entry->e_attrs, mods->sm_desc ) ) {
+ rc = LDAP_SUCCESS;
+ break;
+ }
+ mods->sm_op = LDAP_MOD_ADD;
+ rc = modify_add_values(entry, mods,
+ get_permissiveModify(op),
+ &rs->sr_text, textbuf,
+ SLAP_TEXT_BUFLEN );
+ mods->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+ break;
+ }
+ if(rc != LDAP_SUCCESS) break;
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_text = NULL; /* Needed at least with SLAP_MOD_SOFTADD */
+ if ( is_oc ) {
+ entry->e_ocflags = 0;
+ }
+ /* check that the entry still obeys the schema */
+ rc = entry_schema_check( op, entry, NULL, 0, 0, NULL,
+ &rs->sr_text, textbuf, SLAP_TEXT_BUFLEN );
+ }
+
+ return rc;
+}
+
+
+static int
+ldif_back_referrals( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval path, dn = op->o_req_dn, ndn = op->o_req_ndn;
+ ber_len_t min_dnlen;
+ Entry *entry = NULL, **entryp;
+ BerVarray ref;
+ int rc;
+
+ min_dnlen = op->o_bd->be_nsuffix[0].bv_len;
+ if ( min_dnlen == 0 ) {
+ /* Catch root DSE (empty DN), it is not a referral */
+ min_dnlen = 1;
+ }
+ if ( ndn2path( op, &ndn, &path, 0 ) != LDAP_SUCCESS ) {
+ return LDAP_SUCCESS; /* Root DSE again */
+ }
+
+ entryp = get_manageDSAit( op ) ? NULL : &entry;
+ ldap_pvt_thread_rdwr_rlock( &li->li_rdwr );
+
+ for (;;) {
+ dnParent( &dn, &dn );
+ dnParent( &ndn, &ndn );
+ rc = ldif_read_entry( op, path.bv_val, &dn, &ndn,
+ entryp, &rs->sr_text );
+ if ( rc != LDAP_NO_SUCH_OBJECT )
+ break;
+
+ rc = LDAP_SUCCESS;
+ if ( ndn.bv_len < min_dnlen )
+ break;
+ (void) get_parent_path( &path, NULL );
+ dir2ldif_name( path );
+ entryp = &entry;
+ }
+
+ ldap_pvt_thread_rdwr_runlock( &li->li_rdwr );
+ SLAP_FREE( path.bv_val );
+
+ if ( entry != NULL ) {
+ if ( is_entry_referral( entry ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "ldif_back_referrals: tag=%lu target=\"%s\" matched=\"%s\"\n",
+ (unsigned long) op->o_tag, op->o_req_dn.bv_val, entry->e_dn );
+
+ ref = get_entry_referrals( op, entry );
+ rs->sr_ref = referral_rewrite( ref, &entry->e_name, &op->o_req_dn,
+ op->o_tag == LDAP_REQ_SEARCH ?
+ op->ors_scope : LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+
+ if ( rs->sr_ref != NULL ) {
+ /* send referral */
+ rc = rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = entry->e_dn;
+ send_ldap_result( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ } else {
+ rc = LDAP_OTHER;
+ rs->sr_text = "bad referral object";
+ }
+ rs->sr_matched = NULL;
+ }
+
+ entry_free( entry );
+ }
+
+ return rc;
+}
+
+
+/* LDAP operations */
+
+static int
+ldif_back_bind( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li;
+ Attribute *a;
+ AttributeDescription *password = slap_schema.si_ad_userPassword;
+ int return_val;
+ Entry *entry = NULL;
+
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* in case of success, front end will send result;
+ * otherwise, be_rootdn_bind() did */
+ return rs->sr_err;
+ }
+
+ li = (struct ldif_info *) op->o_bd->be_private;
+ ldap_pvt_thread_rdwr_rlock(&li->li_rdwr);
+ return_val = get_entry(op, &entry, NULL, NULL);
+
+ /* no object is found for them */
+ if(return_val != LDAP_SUCCESS) {
+ rs->sr_err = return_val = LDAP_INVALID_CREDENTIALS;
+ goto return_result;
+ }
+
+ /* they don't have userpassword */
+ if((a = attr_find(entry->e_attrs, password)) == NULL) {
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ return_val = 1;
+ goto return_result;
+ }
+
+ /* authentication actually failed */
+ if(slap_passwd_check(op, entry, a, &op->oq_bind.rb_cred,
+ &rs->sr_text) != 0) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ return_val = 1;
+ goto return_result;
+ }
+
+ /* let the front-end send success */
+ return_val = LDAP_SUCCESS;
+
+ return_result:
+ ldap_pvt_thread_rdwr_runlock(&li->li_rdwr);
+ if(return_val != LDAP_SUCCESS)
+ send_ldap_result( op, rs );
+ if(entry != NULL)
+ entry_free(entry);
+ return return_val;
+}
+
+static int
+ldif_back_search( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+
+ ldap_pvt_thread_rdwr_rlock(&li->li_rdwr);
+ rs->sr_err = search_tree( op, rs );
+ ldap_pvt_thread_rdwr_runlock(&li->li_rdwr);
+ rs->sr_ctrls = NULL;
+ send_ldap_result(op, rs);
+
+ return rs->sr_err;
+}
+
+static int
+ldif_back_add( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ Entry * e = op->ora_e;
+ struct berval path;
+ char *parentdir;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ int rc;
+
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "ldif_back_add: \"%s\"\n", e->e_dn );
+
+ rc = entry_schema_check( op, e, NULL, 0, 1, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rc != LDAP_SUCCESS )
+ goto send_res;
+
+ rc = slap_add_opattrs( op, &rs->sr_text, textbuf, sizeof( textbuf ), 1 );
+ if ( rc != LDAP_SUCCESS )
+ goto send_res;
+
+ ldap_pvt_thread_mutex_lock( &li->li_modop_mutex );
+
+ rc = ldif_prepare_create( op, e, &path, &parentdir, &rs->sr_text );
+
+ if ( rc == LDAP_SUCCESS && op->o_postread ) {
+ if ( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, e,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "ldif_back_add: "
+ "post-read failed \"%s\"\n",
+ e->e_name.bv_val );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ rc = rs->sr_err;
+ }
+ }
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ ldap_pvt_thread_rdwr_wlock( &li->li_rdwr );
+ rc = ldif_write_entry( op, e, &path, parentdir, &rs->sr_text );
+ ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr );
+
+ SLAP_FREE( path.bv_val );
+ if ( parentdir != NULL )
+ SLAP_FREE( parentdir );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex );
+
+ send_res:
+ rs->sr_err = rc;
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+ Debug( LDAP_DEBUG_TRACE, "ldif_back_add: err: %d text: %s\n",
+ rc, rs->sr_text ? rs->sr_text : "" );
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+ rs->sr_text = NULL; /* remove possible pointer to textbuf */
+ return rs->sr_err;
+}
+
+static int
+ldif_back_modify( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ Modifications * modlst = op->orm_modlist;
+ struct berval path;
+ Entry *entry;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ int rc;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+
+ ldap_pvt_thread_mutex_lock( &li->li_modop_mutex );
+
+ rc = get_entry( op, &entry, &path, &rs->sr_text );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( op->o_preread ) {
+ if ( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, entry,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "ldif_back_modify: "
+ "pre-read failed \"%s\"\n",
+ entry->e_name.bv_val );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ rc = rs->sr_err;
+ }
+ }
+ }
+
+ rc = apply_modify_to_entry( entry, modlst, op, rs, textbuf );
+
+ if ( rc == LDAP_SUCCESS && op->o_postread ) {
+ if ( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, entry,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "ldif_back_modify: "
+ "post-read failed \"%s\"\n",
+ entry->e_name.bv_val );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ rc = rs->sr_err;
+ }
+ }
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ ldap_pvt_thread_rdwr_wlock( &li->li_rdwr );
+ rc = ldif_write_entry( op, entry, &path, NULL, &rs->sr_text );
+ ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr );
+ }
+
+ entry_free( entry );
+ SLAP_FREE( path.bv_val );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex );
+
+ rs->sr_err = rc;
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+ rs->sr_text = NULL; /* remove possible pointer to textbuf */
+ return rs->sr_err;
+}
+
+static int
+ldif_back_delete( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval path;
+ int rc = LDAP_SUCCESS;
+ char ebuf[128];
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ if ( BER_BVISEMPTY( &op->o_csn )) {
+ struct berval csn;
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof( csnbuf );
+ slap_get_csn( op, &csn, 1 );
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_modop_mutex );
+ ldap_pvt_thread_rdwr_wlock( &li->li_rdwr );
+ if ( op->o_abandon ) {
+ rc = SLAPD_ABANDON;
+ goto done;
+ }
+
+ rc = ndn2path( op, &op->o_req_ndn, &path, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ ldif2dir_len( path );
+ ldif2dir_name( path );
+ if ( rmdir( path.bv_val ) < 0 ) {
+ switch ( errno ) {
+ case ENOTEMPTY:
+ rc = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ break;
+ case ENOENT:
+ /* is leaf, go on */
+ break;
+ default:
+ rc = LDAP_OTHER;
+ rs->sr_text = "internal error (cannot delete subtree directory)";
+ break;
+ }
+ }
+
+ /* pre-read */
+ if ( op->o_preread ) {
+ Entry *e = NULL;
+
+ if ( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ rc = get_entry( op, &e, &path, &rs->sr_text );
+ if ( rc == LDAP_SUCCESS && slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "ldif_back_delete: "
+ "pre-read failed \"%s\"\n",
+ e->e_name.bv_val );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ rc = rs->sr_err;
+ }
+ }
+ entry_free( e );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ dir2ldif_name( path );
+ if ( unlink( path.bv_val ) < 0 ) {
+ rc = LDAP_NO_SUCH_OBJECT;
+ if ( errno != ENOENT ) {
+ rc = LDAP_OTHER;
+ rs->sr_text = "internal error (cannot delete entry file)";
+ }
+ }
+ }
+
+ if ( rc == LDAP_OTHER ) {
+ Debug( LDAP_DEBUG_ANY, "ldif_back_delete: %s \"%s\": %s\n",
+ "cannot delete", path.bv_val, AC_STRERROR_R( errno, ebuf, sizeof(ebuf) ) );
+ }
+
+ SLAP_FREE( path.bv_val );
+ done:
+ ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr );
+ ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex );
+ rs->sr_err = rc;
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+ return rs->sr_err;
+}
+
+
+static int
+ldif_move_entry(
+ Operation *op,
+ Entry *entry,
+ int same_ndn,
+ struct berval *oldpath,
+ const char **text )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval newpath;
+ char *parentdir = NULL, *trash;
+ int rc, rename_res;
+ char ebuf[128];
+
+ if ( same_ndn ) {
+ rc = LDAP_SUCCESS;
+ newpath = *oldpath;
+ } else {
+ rc = ldif_prepare_create( op, entry, &newpath,
+ op->orr_newSup ? &parentdir : NULL, text );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ ldap_pvt_thread_rdwr_wlock( &li->li_rdwr );
+
+ rc = ldif_write_entry( op, entry, &newpath, parentdir, text );
+ if ( rc == LDAP_SUCCESS && !same_ndn ) {
+ trash = oldpath->bv_val; /* will be .ldif file to delete */
+ ldif2dir_len( newpath );
+ ldif2dir_len( *oldpath );
+ /* Move subdir before deleting old entry,
+ * so .ldif always exists if subdir does.
+ */
+ ldif2dir_name( newpath );
+ ldif2dir_name( *oldpath );
+ rename_res = move_dir( oldpath->bv_val, newpath.bv_val );
+ if ( rename_res != 0 && errno != ENOENT ) {
+ rc = LDAP_OTHER;
+ *text = "internal error (cannot move this subtree)";
+ trash = newpath.bv_val;
+ }
+
+ /* Delete old entry, or if error undo change */
+ for (;;) {
+ dir2ldif_name( newpath );
+ dir2ldif_name( *oldpath );
+ if ( unlink( trash ) == 0 )
+ break;
+ if ( rc == LDAP_SUCCESS ) {
+ /* Prepare to undo change and return failure */
+ rc = LDAP_OTHER;
+ *text = "internal error (cannot move this entry)";
+ trash = newpath.bv_val;
+ if ( rename_res != 0 )
+ continue;
+ /* First move subdirectory back */
+ ldif2dir_name( newpath );
+ ldif2dir_name( *oldpath );
+ if ( move_dir( newpath.bv_val, oldpath->bv_val ) == 0 )
+ continue;
+ }
+ *text = "added new but couldn't delete old entry!";
+ break;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY,
+ "ldif_move_entry: %s (%s): \"%s\" -> \"%s\"\n",
+ *text, AC_STRERROR_R(errno, ebuf, sizeof(ebuf)),
+ op->o_req_dn.bv_val, entry->e_dn );
+ }
+ }
+
+ ldap_pvt_thread_rdwr_wunlock( &li->li_rdwr );
+ if ( !same_ndn )
+ SLAP_FREE( newpath.bv_val );
+ if ( parentdir != NULL )
+ SLAP_FREE( parentdir );
+ }
+
+ return rc;
+}
+
+static int
+ldif_back_modrdn( Operation *op, SlapReply *rs )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval old_path;
+ Entry *entry;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ int rc, same_ndn;
+
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+ ldap_pvt_thread_mutex_lock( &li->li_modop_mutex );
+
+ rc = get_entry( op, &entry, &old_path, &rs->sr_text );
+ if ( rc == LDAP_SUCCESS ) {
+ same_ndn = !ber_bvcmp( &entry->e_nname, &op->orr_nnewDN );
+ ber_bvreplace( &entry->e_name, &op->orr_newDN );
+ ber_bvreplace( &entry->e_nname, &op->orr_nnewDN );
+
+ /* perform the modifications */
+ rc = apply_modify_to_entry( entry, op->orr_modlist, op, rs, textbuf );
+ if ( rc == LDAP_SUCCESS )
+ rc = ldif_move_entry( op, entry, same_ndn, &old_path,
+ &rs->sr_text );
+
+ entry_free( entry );
+ SLAP_FREE( old_path.bv_val );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &li->li_modop_mutex );
+ rs->sr_err = rc;
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+ rs->sr_text = NULL; /* remove possible pointer to textbuf */
+ return rs->sr_err;
+}
+
+
+/* Return LDAP_SUCCESS IFF we retrieve the specified entry. */
+static int
+ldif_back_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **e )
+{
+ struct ldif_info *li = (struct ldif_info *) op->o_bd->be_private;
+ struct berval op_dn = op->o_req_dn, op_ndn = op->o_req_ndn;
+ int rc;
+
+ assert( ndn != NULL );
+ assert( !BER_BVISNULL( ndn ) );
+
+ ldap_pvt_thread_rdwr_rlock( &li->li_rdwr );
+ op->o_req_dn = *ndn;
+ op->o_req_ndn = *ndn;
+ rc = get_entry( op, e, NULL, NULL );
+ op->o_req_dn = op_dn;
+ op->o_req_ndn = op_ndn;
+ ldap_pvt_thread_rdwr_runlock( &li->li_rdwr );
+
+ if ( rc == LDAP_SUCCESS && oc && !is_entry_objectclass_or_sub( *e, oc ) ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ entry_free( *e );
+ *e = NULL;
+ }
+
+ return rc;
+}
+
+static int
+ldif_back_entry_release_rw (
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ ID id = e->e_id;
+
+ /* only tool mode assigns valid IDs */
+ if ( id != 0 && id != NOID )
+ {
+ struct ldif_tool *tl = &((struct ldif_info *) op->o_bd->be_private)->li_tool;
+
+ id--;
+
+ assert( id < tl->ecount );
+ assert( e == tl->entries[id] );
+ tl->entries[id] = NULL;
+ }
+
+ entry_free( e );
+ return 0;
+}
+
+
+/* Slap tools */
+
+static int
+ldif_tool_entry_open( BackendDB *be, int mode )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ tl->ecurrent = 0;
+ return 0;
+}
+
+static int
+ldif_tool_entry_close( BackendDB *be )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+ Entry **entries = tl->entries;
+ ID i;
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ for ( i = tl->ecount; i--; )
+ if ( entries[i] )
+ entry_free( entries[i] );
+ SLAP_FREE( entries );
+ tl->entries = NULL;
+ tl->ecount = tl->elen = 0;
+ return 0;
+}
+
+static ID
+ldif_tool_entry_next( BackendDB *be )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+
+ do {
+ Entry *e = tl->entries[ tl->ecurrent ];
+
+ if ( tl->ecurrent >= tl->ecount ) {
+ return NOID;
+ }
+
+ ++tl->ecurrent;
+
+ if ( tl->tl_base && !dnIsSuffixScope( &e->e_nname, tl->tl_base, tl->tl_scope ) ) {
+ continue;
+ }
+
+ if ( tl->tl_filter && test_filter( NULL, e, tl->tl_filter ) != LDAP_COMPARE_TRUE ) {
+ continue;
+ }
+
+ break;
+ } while ( 1 );
+
+ return tl->ecurrent;
+}
+
+static ID
+ldif_tool_entry_first_x( BackendDB *be, struct berval *base, int scope, Filter *f )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+
+ tl->tl_base = base;
+ tl->tl_scope = scope;
+ tl->tl_filter = f;
+
+ if ( tl->entries == NULL ) {
+ Operation op = {0};
+
+ op.o_bd = be;
+ op.o_req_dn = *be->be_suffix;
+ op.o_req_ndn = *be->be_nsuffix;
+ op.ors_scope = LDAP_SCOPE_SUBTREE;
+ if ( search_tree( &op, NULL ) != LDAP_SUCCESS ) {
+ tl->ecurrent = tl->ecount; /* fail ldif_tool_entry_next() */
+ return NOID; /* fail ldif_tool_entry_get() */
+ }
+ }
+ return ldif_tool_entry_next( be );
+}
+
+static ID
+ldif_tool_dn2id_get( BackendDB *be, struct berval *dn )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+
+ Operation op = {0};
+
+ op.o_bd = be;
+ op.o_req_dn = *dn;
+ op.o_req_ndn = *dn;
+ op.ors_scope = LDAP_SCOPE_BASE;
+ if ( search_tree( &op, NULL ) != LDAP_SUCCESS ) {
+ return NOID;
+ }
+ return tl->ecount;
+}
+
+static Entry *
+ldif_tool_entry_get( BackendDB *be, ID id )
+{
+ struct ldif_tool *tl = &((struct ldif_info *) be->be_private)->li_tool;
+ Entry *e = NULL;
+
+ --id;
+ if ( id < tl->ecount ) {
+ e = tl->entries[id];
+ }
+ return e;
+}
+
+static ID
+ldif_tool_entry_put( BackendDB *be, Entry *e, struct berval *text )
+{
+ int rc;
+ const char *errmsg = NULL;
+ struct berval path;
+ char *parentdir;
+ Operation op = {0};
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ op.o_bd = be;
+ rc = ldif_prepare_create( &op, e, &path, &parentdir, &errmsg );
+ if ( rc == LDAP_SUCCESS ) {
+ rc = ldif_write_entry( &op, e, &path, parentdir, &errmsg );
+
+ SLAP_FREE( path.bv_val );
+ if ( parentdir != NULL )
+ SLAP_FREE( parentdir );
+ if ( rc == LDAP_SUCCESS )
+ return 1;
+ }
+
+ if ( errmsg == NULL && rc != LDAP_OTHER )
+ errmsg = ldap_err2string( rc );
+ if ( errmsg != NULL )
+ snprintf( text->bv_val, text->bv_len, "%s", errmsg );
+ return NOID;
+}
+
+static ID
+ldif_tool_entry_modify( BackendDB *be, Entry *e, struct berval *text )
+{
+ int rc;
+ const char *errmsg = NULL;
+ struct berval path;
+ Operation op = {0};
+
+ op.o_bd = be;
+ ndn2path( &op, &e->e_nname, &path, 0 );
+ rc = ldif_write_entry( &op, e, &path, NULL, &errmsg );
+ SLAP_FREE( path.bv_val );
+ if ( rc == LDAP_SUCCESS )
+ return 1;
+
+ if ( errmsg == NULL && rc != LDAP_OTHER )
+ errmsg = ldap_err2string( rc );
+ if ( errmsg != NULL )
+ snprintf( text->bv_val, text->bv_len, "%s", errmsg );
+ return NOID;
+}
+
+static int
+ldif_tool_entry_delete( BackendDB *be, struct berval *ndn, struct berval *text )
+{
+ int rc = LDAP_SUCCESS;
+ const char *errmsg = NULL;
+ struct berval path;
+ Operation op = {0};
+
+ op.o_bd = be;
+ ndn2path( &op, ndn, &path, 0 );
+
+ ldif2dir_len( path );
+ ldif2dir_name( path );
+ if ( rmdir( path.bv_val ) < 0 ) {
+ switch ( errno ) {
+ case ENOTEMPTY:
+ rc = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ break;
+ case ENOENT:
+ /* is leaf, go on */
+ break;
+ default:
+ rc = LDAP_OTHER;
+ errmsg = "internal error (cannot delete subtree directory)";
+ break;
+ }
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ dir2ldif_name( path );
+ if ( unlink( path.bv_val ) < 0 ) {
+ rc = LDAP_NO_SUCH_OBJECT;
+ if ( errno != ENOENT ) {
+ rc = LDAP_OTHER;
+ errmsg = "internal error (cannot delete entry file)";
+ }
+ }
+ }
+
+ SLAP_FREE( path.bv_val );
+
+ if ( errmsg == NULL && rc != LDAP_OTHER )
+ errmsg = ldap_err2string( rc );
+ if ( errmsg != NULL )
+ snprintf( text->bv_val, text->bv_len, "%s", errmsg );
+ return rc;
+}
+
+
+/* Setup */
+
+static int
+ldif_back_db_init( BackendDB *be, ConfigReply *cr )
+{
+ struct ldif_info *li;
+
+ li = ch_calloc( 1, sizeof(struct ldif_info) );
+ be->be_private = li;
+ be->be_cf_ocs = ldifocs;
+ ldap_pvt_thread_mutex_init( &li->li_modop_mutex );
+ ldap_pvt_thread_rdwr_init( &li->li_rdwr );
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX;
+ return 0;
+}
+
+static int
+ldif_back_db_destroy( Backend *be, ConfigReply *cr )
+{
+ struct ldif_info *li = be->be_private;
+
+ ch_free( li->li_base_path.bv_val );
+ ldap_pvt_thread_rdwr_destroy( &li->li_rdwr );
+ ldap_pvt_thread_mutex_destroy( &li->li_modop_mutex );
+ free( be->be_private );
+ return 0;
+}
+
+static int
+ldif_back_db_open( Backend *be, ConfigReply *cr )
+{
+ struct ldif_info *li = (struct ldif_info *) be->be_private;
+ if( BER_BVISEMPTY(&li->li_base_path)) {/* missing base path */
+ Debug( LDAP_DEBUG_ANY, "missing base path for back-ldif\n" );
+ return 1;
+ }
+ return 0;
+}
+
+int
+ldif_back_initialize( BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_PRE_READ,
+ LDAP_CONTROL_POST_READ,
+ NULL
+ };
+ int rc;
+
+ bi->bi_flags |=
+ SLAP_BFLAG_INCREMENT |
+ SLAP_BFLAG_REFERRALS;
+
+ bi->bi_controls = controls;
+
+ bi->bi_open = 0;
+ bi->bi_close = 0;
+ bi->bi_config = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = ldif_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = ldif_back_db_open;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = ldif_back_db_destroy;
+
+ bi->bi_op_bind = ldif_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = ldif_back_search;
+ bi->bi_op_compare = 0;
+ bi->bi_op_modify = ldif_back_modify;
+ bi->bi_op_modrdn = ldif_back_modrdn;
+ bi->bi_op_add = ldif_back_add;
+ bi->bi_op_delete = ldif_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = ldif_back_referrals;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ bi->bi_entry_get_rw = ldif_back_entry_get;
+ bi->bi_entry_release_rw = ldif_back_entry_release_rw;
+
+#if 0 /* NOTE: uncomment to completely disable access control */
+ bi->bi_access_allowed = slap_access_always_allowed;
+#endif
+
+ bi->bi_tool_entry_open = ldif_tool_entry_open;
+ bi->bi_tool_entry_close = ldif_tool_entry_close;
+ bi->bi_tool_entry_first = backend_tool_entry_first;
+ bi->bi_tool_entry_first_x = ldif_tool_entry_first_x;
+ bi->bi_tool_entry_next = ldif_tool_entry_next;
+ bi->bi_tool_dn2id_get = ldif_tool_dn2id_get;
+ bi->bi_tool_entry_get = ldif_tool_entry_get;
+ bi->bi_tool_entry_put = ldif_tool_entry_put;
+ bi->bi_tool_entry_modify = ldif_tool_entry_modify;
+ bi->bi_tool_entry_delete = ldif_tool_entry_delete;
+ bi->bi_tool_entry_reindex = 0;
+ bi->bi_tool_sync = 0;
+
+ bi->bi_cf_ocs = ldifocs;
+
+ rc = config_register_schema( ldifcfg, ldifocs );
+ if ( rc ) return rc;
+ return 0;
+}
diff --git a/servers/slapd/back-mdb/Makefile.in b/servers/slapd/back-mdb/Makefile.in
new file mode 100644
index 0000000..ad38048
--- /dev/null
+++ b/servers/slapd/back-mdb/Makefile.in
@@ -0,0 +1,62 @@
+# Makefile.in for back-mdb
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2011-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c tools.c config.c \
+ add.c bind.c compare.c delete.c modify.c modrdn.c search.c \
+ extended.c operational.c \
+ attr.c index.c key.c filterindex.c \
+ dn2entry.c dn2id.c id2entry.c idl.c \
+ nextid.c monitor.c
+
+OBJS = init.lo tools.lo config.lo \
+ add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \
+ extended.lo operational.lo \
+ attr.lo index.lo key.lo filterindex.lo \
+ dn2entry.lo dn2id.lo id2entry.lo idl.lo \
+ nextid.lo monitor.lo mdb.lo midl.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+MDB_SUBDIR = $(srcdir)/$(LDAP_LIBDIR)/liblmdb
+
+BUILD_OPT = "--enable-mdb"
+BUILD_MOD = @BUILD_MDB@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_MDB@_DEFS)
+MOD_LIBS = $(MDB_LIBS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_mdb
+
+XINCPATH = -I.. -I$(srcdir)/.. -I$(MDB_SUBDIR)
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
+mdb.lo: $(MDB_SUBDIR)/mdb.c
+ $(LTCOMPILE_MOD) $(MDB_SUBDIR)/mdb.c
+
+midl.lo: $(MDB_SUBDIR)/midl.c
+ $(LTCOMPILE_MOD) $(MDB_SUBDIR)/midl.c
+
+veryclean-local-lib: FORCE
+ $(RM) $(XXHEADERS) $(XXSRCS) .links
diff --git a/servers/slapd/back-mdb/add.c b/servers/slapd/back-mdb/add.c
new file mode 100644
index 0000000..f1632e2
--- /dev/null
+++ b/servers/slapd/back-mdb/add.c
@@ -0,0 +1,419 @@
+/* add.c - ldap mdb back-end add routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+int
+mdb_add(Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ struct berval pdn;
+ Entry *p = NULL, *oe = op->ora_e;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ MDB_txn *txn = NULL;
+ MDB_cursor *mc = NULL;
+ MDB_cursor *mcd;
+ ID eid, pid = 0;
+ mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
+ int subentry;
+ int numads = mdb->mi_numads;
+
+ int success;
+
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ Debug(LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_add) ": %s\n",
+ op->ora_e->e_name.bv_val );
+
+ ctrls[num_ctrls] = 0;
+
+ /* check entry's schema */
+ rs->sr_err = entry_schema_check( op, op->ora_e, NULL,
+ get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": entry failed schema check: "
+ "%s (%d)\n", rs->sr_text, rs->sr_err );
+ goto return_results;
+ }
+
+ /* begin transaction */
+ rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi );
+ rs->sr_text = NULL;
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": txn_begin failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ txn = moi->moi_txn;
+
+ /* add opattrs to shadow as well, only missing attrs will actually
+ * be added; helps compatibility with older OL versions */
+ rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": entry failed op attrs add: "
+ "%s (%d)\n", rs->sr_text, rs->sr_err );
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ subentry = is_entry_subentry( op->ora_e );
+
+ /*
+ * Get the parent dn and see if the corresponding entry exists.
+ */
+ if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) {
+ pdn = slap_empty_bv;
+ } else {
+ dnParent( &op->ora_e->e_nname, &pdn );
+ }
+
+ rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mcd );
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n",
+ rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* get entry or parent */
+ rs->sr_err = mdb_dn2entry( op, txn, mcd, &op->ora_e->e_nname, &p, NULL, 1 );
+ switch( rs->sr_err ) {
+ case 0:
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ mdb_entry_return( op, p );
+ p = NULL;
+ goto return_results;
+ case MDB_NOTFOUND:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( !p )
+ p = (Entry *)&slap_entry_root;
+
+ if ( !bvmatch( &pdn, &p->e_nname ) ) {
+ rs->sr_matched = ber_strdup_x( p->e_name.bv_val,
+ op->o_tmpmemctx );
+ if ( p != (Entry *)&slap_entry_root && is_entry_referral( p )) {
+ BerVarray ref = get_entry_referrals( op, p );
+ rs->sr_ref = referral_rewrite( ref, &p->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ if ( p != (Entry *)&slap_entry_root )
+ mdb_entry_return( op, p );
+ p = NULL;
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": parent "
+ "does not exist\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WADD, NULL );
+
+ if ( ! rs->sr_err ) {
+ if ( p != (Entry *)&slap_entry_root )
+ mdb_entry_return( op, p );
+ p = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": no write access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;;
+ }
+
+ if ( p != (Entry *)&slap_entry_root ) {
+ if ( is_entry_subentry( p ) ) {
+ mdb_entry_return( op, p );
+ p = NULL;
+ /* parent is a subentry, don't allow add */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": parent is subentry\n" );
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "parent is a subentry";
+ goto return_results;;
+ }
+
+ if ( is_entry_alias( p ) ) {
+ mdb_entry_return( op, p );
+ p = NULL;
+ /* parent is an alias, don't allow add */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": parent is alias\n" );
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ rs->sr_text = "parent is an alias";
+ goto return_results;;
+ }
+
+ if ( is_entry_referral( p ) ) {
+ BerVarray ref = get_entry_referrals( op, p );
+ /* parent is a referral, don't allow add */
+ rs->sr_matched = ber_strdup_x( p->e_name.bv_val,
+ op->o_tmpmemctx );
+ rs->sr_ref = referral_rewrite( ref, &p->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ mdb_entry_return( op, p );
+ p = NULL;
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": parent is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ }
+
+ if ( subentry ) {
+ /* FIXME: */
+ /* parent must be an administrative point of the required kind */
+ }
+
+ /* free parent */
+ if ( p != (Entry *)&slap_entry_root ) {
+ pid = p->e_id;
+ if ( p->e_nname.bv_len ) {
+ struct berval ppdn;
+
+ /* ITS#5326: use parent's DN if differs from provided one */
+ dnParent( &op->ora_e->e_name, &ppdn );
+ if ( !dn_match( &p->e_name, &ppdn ) ) {
+ struct berval rdn;
+ struct berval newdn;
+
+ dnRdn( &op->ora_e->e_name, &rdn );
+
+ build_new_dn( &newdn, &p->e_name, &rdn, NULL );
+ if ( op->ora_e->e_name.bv_val != op->o_req_dn.bv_val )
+ ber_memfree( op->ora_e->e_name.bv_val );
+ op->ora_e->e_name = newdn;
+
+ /* FIXME: should check whether
+ * dnNormalize(newdn) == e->e_nname ... */
+ }
+ }
+
+ mdb_entry_return( op, p );
+ }
+ p = NULL;
+
+ rs->sr_err = access_allowed( op, op->ora_e,
+ entry, NULL, ACL_WADD, NULL );
+
+ if ( ! rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": no write access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to entry";
+ goto return_results;;
+ }
+
+ /*
+ * Check ACL for attribute write access
+ */
+ if (!acl_check_modlist(op, oe, op->ora_modlist)) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": no write access to attribute\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to attribute";
+ goto return_results;;
+ }
+
+ rs->sr_err = mdb_cursor_open( txn, mdb->mi_id2entry, &mc );
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": mdb_cursor_open failed (%d)\n",
+ rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ rs->sr_err = mdb_next_id( op->o_bd, mc, &eid );
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": next_id failed (%d)\n",
+ rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ op->ora_e->e_id = eid;
+
+ /* dn2id index */
+ rs->sr_err = mdb_dn2id_add( op, mcd, mcd, pid, 1, 1, op->ora_e );
+ mdb_cursor_close( mcd );
+ if ( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": dn2id_add failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+
+ switch( rs->sr_err ) {
+ case MDB_KEYEXIST:
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ break;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ }
+ goto return_results;
+ }
+
+ /* attribute indexes */
+ rs->sr_err = mdb_index_entry_add( op, txn, op->ora_e );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": index_entry_add failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "index generation failed";
+ goto return_results;
+ }
+
+ /* id2entry index */
+ rs->sr_err = mdb_id2entry_add( op, txn, mc, op->ora_e );
+ if ( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": id2entry_add failed\n" );
+ if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) {
+ rs->sr_text = "entry is too big";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry store failed";
+ }
+ goto return_results;
+ }
+
+ /* post-read */
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, op->ora_e,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_add) ": post-read "
+ "failed!\n" );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ if ( moi == &opinfo ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ opinfo.moi_oe.oe_key = NULL;
+ if ( op->o_noop ) {
+ mdb->mi_numads = numads;
+ mdb_txn_abort( txn );
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ txn = NULL;
+ goto return_results;
+ }
+
+ rs->sr_err = mdb_txn_commit( txn );
+ txn = NULL;
+ if ( rs->sr_err != 0 ) {
+ mdb->mi_numads = numads;
+ rs->sr_text = "txn_commit failed";
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_add) ": %s : %s (%d)\n",
+ rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_add) ": added%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ op->ora_e->e_id, op->ora_e->e_dn );
+
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ success = rs->sr_err;
+ send_ldap_result( op, rs );
+
+ if( moi == &opinfo ) {
+ if( txn != NULL ) {
+ mdb->mi_numads = numads;
+ mdb_txn_abort( txn );
+ }
+ if ( opinfo.moi_oe.oe_key ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ }
+ } else {
+ moi->moi_ref--;
+ }
+
+ if( success == LDAP_SUCCESS ) {
+#if 0
+ if ( mdb->bi_txn_cp_kbyte ) {
+ TXN_CHECKPOINT( mdb->bi_dbenv,
+ mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 );
+ }
+#endif
+ }
+
+ slap_graduate_commit_csn( op );
+
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/attr.c b/servers/slapd/back-mdb/attr.c
new file mode 100644
index 0000000..7219705
--- /dev/null
+++ b/servers/slapd/back-mdb/attr.c
@@ -0,0 +1,828 @@
+/* attr.c - backend routines for dealing with attributes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-mdb.h"
+#include "slap-config.h"
+#include "lutil.h"
+
+/* Find the ad, return -1 if not found,
+ * set point for insertion if ins is non-NULL
+ */
+int
+mdb_attr_slot( struct mdb_info *mdb, AttributeDescription *ad, int *ins )
+{
+ unsigned base = 0, cursor = 0;
+ unsigned n = mdb->mi_nattrs;
+ int val = 0;
+
+ while ( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot;
+
+ val = SLAP_PTRCMP( ad, mdb->mi_attrs[cursor]->ai_desc );
+ if ( val < 0 ) {
+ n = pivot;
+ } else if ( val > 0 ) {
+ base = cursor + 1;
+ n -= pivot + 1;
+ } else {
+ return cursor;
+ }
+ }
+ if ( ins ) {
+ if ( val > 0 )
+ ++cursor;
+ *ins = cursor;
+ }
+ return -1;
+}
+
+static int
+ainfo_insert( struct mdb_info *mdb, AttrInfo *a )
+{
+ int x;
+ int i = mdb_attr_slot( mdb, a->ai_desc, &x );
+
+ /* Is it a dup? */
+ if ( i >= 0 )
+ return -1;
+
+ mdb->mi_attrs = ch_realloc( mdb->mi_attrs, ( mdb->mi_nattrs+1 ) *
+ sizeof( AttrInfo * ));
+ if ( x < mdb->mi_nattrs )
+ AC_MEMCPY( &mdb->mi_attrs[x+1], &mdb->mi_attrs[x],
+ ( mdb->mi_nattrs - x ) * sizeof( AttrInfo *));
+ mdb->mi_attrs[x] = a;
+ mdb->mi_nattrs++;
+ return 0;
+}
+
+AttrInfo *
+mdb_attr_mask(
+ struct mdb_info *mdb,
+ AttributeDescription *desc )
+{
+ int i = mdb_attr_slot( mdb, desc, NULL );
+ return i < 0 ? NULL : mdb->mi_attrs[i];
+}
+
+/* Open all un-opened index DB handles */
+int
+mdb_attr_dbs_open(
+ BackendDB *be, MDB_txn *tx0, ConfigReply *cr )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ MDB_txn *txn;
+ MDB_dbi *dbis = NULL;
+ int i, flags;
+ int rc;
+
+ txn = tx0;
+ if ( txn == NULL ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn );
+ if ( rc ) {
+ snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+ "txn_begin failed: %s (%d).",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_attr_dbs) ": %s\n",
+ cr->msg );
+ return rc;
+ }
+ dbis = ch_calloc( 1, mdb->mi_nattrs * sizeof(MDB_dbi) );
+ } else {
+ rc = 0;
+ }
+
+ flags = MDB_DUPSORT|MDB_DUPFIXED|MDB_INTEGERDUP;
+ if ( !(slapMode & SLAP_TOOL_READONLY) )
+ flags |= MDB_CREATE;
+
+ for ( i=0; i<mdb->mi_nattrs; i++ ) {
+ if ( mdb->mi_attrs[i]->ai_dbi ) /* already open */
+ continue;
+ if ( !( mdb->mi_attrs[i]->ai_indexmask || mdb->mi_attrs[i]->ai_newmask )) /* not an index record */
+ continue;
+ rc = mdb_dbi_open( txn, mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val,
+ flags, &mdb->mi_attrs[i]->ai_dbi );
+ if ( rc ) {
+ snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+ "mdb_dbi_open(%s) failed: %s (%d).",
+ be->be_suffix[0].bv_val,
+ mdb->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val,
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_attr_dbs) ": %s\n",
+ cr->msg );
+ break;
+ }
+ /* Remember newly opened DBI handles */
+ if ( dbis )
+ dbis[i] = mdb->mi_attrs[i]->ai_dbi;
+ }
+
+ /* Only commit if this is our txn */
+ if ( tx0 == NULL ) {
+ if ( !rc ) {
+ rc = mdb_txn_commit( txn );
+ if ( rc ) {
+ snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+ "txn_commit failed: %s (%d).",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_attr_dbs) ": %s\n",
+ cr->msg );
+ }
+ } else {
+ mdb_txn_abort( txn );
+ }
+ /* Something failed, forget anything we just opened */
+ if ( rc ) {
+ for ( i=0; i<mdb->mi_nattrs; i++ ) {
+ if ( dbis[i] ) {
+ mdb->mi_attrs[i]->ai_dbi = 0;
+ mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING;
+ }
+ }
+ mdb_attr_flush( mdb );
+ }
+ ch_free( dbis );
+ }
+
+ return rc;
+}
+
+void
+mdb_attr_dbs_close(
+ struct mdb_info *mdb
+)
+{
+ int i;
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ if ( mdb->mi_attrs[i]->ai_dbi ) {
+ mdb_dbi_close( mdb->mi_dbenv, mdb->mi_attrs[i]->ai_dbi );
+ mdb->mi_attrs[i]->ai_dbi = 0;
+ }
+}
+
+int
+mdb_attr_index_config(
+ struct mdb_info *mdb,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ struct config_reply_s *c_reply)
+{
+ int rc = 0;
+ int i;
+ slap_mask_t mask;
+ char **attrs;
+ char **indexes = NULL;
+
+ attrs = ldap_str2charray( argv[0], "," );
+
+ if( attrs == NULL ) {
+ fprintf( stderr, "%s: line %d: "
+ "no attributes specified: %s\n",
+ fname, lineno, argv[0] );
+ return LDAP_PARAM_ERROR;
+ }
+
+ if ( argc > 1 ) {
+ indexes = ldap_str2charray( argv[1], "," );
+
+ if( indexes == NULL ) {
+ fprintf( stderr, "%s: line %d: "
+ "no indexes specified: %s\n",
+ fname, lineno, argv[1] );
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+ }
+
+ if( indexes == NULL ) {
+ mask = mdb->mi_defaultmask;
+
+ } else {
+ mask = 0;
+
+ for ( i = 0; indexes[i] != NULL; i++ ) {
+ slap_mask_t index;
+ rc = slap_str2index( indexes[i], &index );
+
+ if( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index type \"%s\" undefined", indexes[i] );
+
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+
+ mask |= index;
+ }
+ }
+
+ if( !mask ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "no indexes selected" );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ AttrInfo *a;
+ AttributeDescription *ad;
+ const char *text;
+#ifdef LDAP_COMP_MATCH
+ ComponentReference* cr = NULL;
+ AttrInfo *a_cr = NULL;
+#endif
+
+ if( strcasecmp( attrs[i], "default" ) == 0 ) {
+ mdb->mi_defaultmask |= mask;
+ continue;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( is_component_reference( attrs[i] ) ) {
+ rc = extract_component_reference( attrs[i], &cr );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index component reference\"%s\" undefined",
+ attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ goto done;
+ }
+ cr->cr_indexmask = mask;
+ /*
+ * After extracting a component reference
+ * only the name of a attribute will be remaining
+ */
+ } else {
+ cr = NULL;
+ }
+#endif
+ ad = NULL;
+ rc = slap_str2ad( attrs[i], &ad, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index attribute \"%s\" undefined",
+ attrs[i] );
+
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+fail:
+#ifdef LDAP_COMP_MATCH
+ ch_free( cr );
+#endif
+ goto done;
+ }
+
+ if( ad == slap_schema.si_ad_entryDN || slap_ad_is_binary( ad ) ) {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) && !(
+ ad->ad_type->sat_approx
+ && ad->ad_type->sat_approx->smr_indexer
+ && ad->ad_type->sat_approx->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "approx index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) && !(
+ ad->ad_type->sat_equality
+ && ad->ad_type->sat_equality->smr_indexer
+ && ad->ad_type->sat_equality->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "equality index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) && !(
+ ad->ad_type->sat_substr
+ && ad->ad_type->sat_substr->smr_indexer
+ && ad->ad_type->sat_substr->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "substr index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n",
+ ad->ad_cname.bv_val, mask );
+
+ a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) );
+
+#ifdef LDAP_COMP_MATCH
+ a->ai_cr = NULL;
+#endif
+ a->ai_cursor = NULL;
+ a->ai_root = NULL;
+ a->ai_desc = ad;
+ a->ai_dbi = 0;
+ a->ai_multi_hi = UINT_MAX;
+ a->ai_multi_lo = UINT_MAX;
+
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ a->ai_indexmask = 0;
+ a->ai_newmask = mask;
+ } else {
+ a->ai_indexmask = mask;
+ a->ai_newmask = 0;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( cr ) {
+ a_cr = mdb_attr_mask( mdb, ad );
+ if ( a_cr ) {
+ /*
+ * AttrInfo is already in AVL
+ * just add the extracted component reference
+ * in the AttrInfo
+ */
+ ch_free( a );
+ rc = insert_component_reference( cr, &a_cr->ai_cr );
+ if ( rc != LDAP_SUCCESS) {
+ fprintf( stderr, " error during inserting component reference in %s ", attrs[i]);
+ rc = LDAP_PARAM_ERROR;
+ goto fail;
+ }
+ continue;
+ } else {
+ rc = insert_component_reference( cr, &a->ai_cr );
+ if ( rc != LDAP_SUCCESS) {
+ fprintf( stderr, " error during inserting component reference in %s ", attrs[i]);
+ rc = LDAP_PARAM_ERROR;
+ ch_free( a );
+ goto fail;
+ }
+ }
+ }
+#endif
+ rc = ainfo_insert( mdb, a );
+ if( rc ) {
+ AttrInfo *b = mdb_attr_mask( mdb, ad );
+ /* If this is just a multival record, reuse it for index info */
+ if ( !( b->ai_indexmask || b->ai_newmask ) && b->ai_multi_lo < UINT_MAX ) {
+ b->ai_indexmask = a->ai_indexmask;
+ b->ai_newmask = a->ai_newmask;
+ ch_free( a );
+ rc = 0;
+ continue;
+ }
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ /* If there is already an index defined for this attribute
+ * it must be replaced. Otherwise we end up with multiple
+ * olcIndex values for the same attribute */
+ if ( b->ai_indexmask & MDB_INDEX_DELETING ) {
+ /* If we were editing this attr, reset it */
+ b->ai_indexmask &= ~MDB_INDEX_DELETING;
+ /* If this is leftover from a previous add, commit it */
+ if ( b->ai_newmask )
+ b->ai_indexmask = b->ai_newmask;
+ /* If the mask changed, remember it */
+ if ( b->ai_indexmask != a->ai_newmask )
+ b->ai_newmask = a->ai_newmask;
+ else /* else ignore it */
+ b->ai_newmask = 0;
+ ch_free( a );
+ rc = 0;
+ continue;
+ }
+ }
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "duplicate index definition for attr \"%s\"",
+ attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+ }
+
+done:
+ ldap_charray_free( attrs );
+ if ( indexes != NULL ) ldap_charray_free( indexes );
+
+ return rc;
+}
+
+static int
+mdb_attr_index_unparser( void *v1, void *v2 )
+{
+ AttrInfo *ai = v1;
+ BerVarray *bva = v2;
+ struct berval bv;
+ char *ptr;
+
+ slap_index2bvlen( ai->ai_indexmask, &bv );
+ if ( bv.bv_len ) {
+ bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1;
+ ptr = ch_malloc( bv.bv_len+1 );
+ bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val );
+ *bv.bv_val++ = ' ';
+ slap_index2bv( ai->ai_indexmask, &bv );
+ bv.bv_val = ptr;
+ ber_bvarray_add( bva, &bv );
+ }
+ return 0;
+}
+
+static AttributeDescription addef = { NULL, NULL, BER_BVC("default") };
+static AttrInfo aidef = { &addef };
+
+void
+mdb_attr_index_unparse( struct mdb_info *mdb, BerVarray *bva )
+{
+ int i;
+
+ if ( mdb->mi_defaultmask ) {
+ aidef.ai_indexmask = mdb->mi_defaultmask;
+ mdb_attr_index_unparser( &aidef, bva );
+ }
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ if ( mdb->mi_attrs[i]->ai_indexmask )
+ mdb_attr_index_unparser( mdb->mi_attrs[i], bva );
+}
+
+int
+mdb_attr_multi_config(
+ struct mdb_info *mdb,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ struct config_reply_s *c_reply)
+{
+ int rc = 0;
+ int i;
+ unsigned hi,lo;
+ char **attrs, *next, *s;
+
+ attrs = ldap_str2charray( argv[0], "," );
+
+ if( attrs == NULL ) {
+ fprintf( stderr, "%s: line %d: "
+ "no attributes specified: %s\n",
+ fname, lineno, argv[0] );
+ return LDAP_PARAM_ERROR;
+ }
+
+ hi = strtoul( argv[1], &next, 10 );
+ if ( next == argv[1] || next[0] != ',' )
+ goto badval;
+ s = next+1;
+ lo = strtoul( s, &next, 10 );
+ if ( next == s || next[0] != '\0' )
+ goto badval;
+
+ if ( lo > hi ) {
+badval:
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "invalid hi/lo thresholds" );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ return LDAP_PARAM_ERROR;
+ }
+
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ AttrInfo *a;
+ AttributeDescription *ad;
+ const char *text;
+
+ if( strcasecmp( attrs[i], "default" ) == 0 ) {
+ mdb->mi_multi_hi = hi;
+ mdb->mi_multi_lo = lo;
+ continue;
+ }
+
+ ad = NULL;
+ rc = slap_str2ad( attrs[i], &ad, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "multival attribute \"%s\" undefined",
+ attrs[i] );
+
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+fail:
+ goto done;
+ }
+
+ a = (AttrInfo *) ch_calloc( 1, sizeof(AttrInfo) );
+
+ a->ai_desc = ad;
+ a->ai_multi_hi = hi;
+ a->ai_multi_lo = lo;
+
+ rc = ainfo_insert( mdb, a );
+ if( rc ) {
+ AttrInfo *b = mdb_attr_mask( mdb, ad );
+ /* If this is just an index record, reuse it for multival info */
+ if ( b->ai_multi_lo == UINT_MAX ) {
+ b->ai_multi_hi = a->ai_multi_hi;
+ b->ai_multi_lo = a->ai_multi_lo;
+ ch_free( a );
+ rc = 0;
+ continue;
+ }
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "duplicate multival definition for attr \"%s\"",
+ attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+ }
+
+done:
+ ldap_charray_free( attrs );
+
+ return rc;
+}
+
+static int
+mdb_attr_multi_unparser( void *v1, void *v2 )
+{
+ AttrInfo *ai = v1;
+ BerVarray *bva = v2;
+ struct berval bv;
+ char digbuf[sizeof("4294967296,4294967296")];
+ char *ptr;
+
+ bv.bv_len = snprintf( digbuf, sizeof(digbuf), "%u,%u",
+ ai->ai_multi_hi, ai->ai_multi_lo );
+ if ( bv.bv_len ) {
+ bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1;
+ ptr = ch_malloc( bv.bv_len+1 );
+ bv.bv_val = lutil_strcopy( ptr, ai->ai_desc->ad_cname.bv_val );
+ *bv.bv_val++ = ' ';
+ strcpy(bv.bv_val, digbuf);
+ bv.bv_val = ptr;
+ ber_bvarray_add( bva, &bv );
+ }
+ return 0;
+}
+
+void
+mdb_attr_multi_unparse( struct mdb_info *mdb, BerVarray *bva )
+{
+ int i;
+
+ if ( mdb->mi_multi_hi < UINT_MAX ) {
+ aidef.ai_multi_hi = mdb->mi_multi_hi;
+ aidef.ai_multi_lo = mdb->mi_multi_lo;
+ mdb_attr_multi_unparser( &aidef, bva );
+ }
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ if ( mdb->mi_attrs[i]->ai_multi_hi < UINT_MAX )
+ mdb_attr_multi_unparser( mdb->mi_attrs[i], bva );
+}
+
+void
+mdb_attr_multi_thresh( struct mdb_info *mdb, AttributeDescription *ad, unsigned *hi, unsigned *lo )
+{
+ AttrInfo *ai = mdb_attr_mask( mdb, ad );
+ if ( ai && ai->ai_multi_hi < UINT_MAX )
+ {
+ if ( hi )
+ *hi = ai->ai_multi_hi;
+ if ( lo )
+ *lo = ai->ai_multi_lo;
+ } else
+ {
+ if ( hi )
+ *hi = mdb->mi_multi_hi;
+ if ( lo )
+ *lo = mdb->mi_multi_lo;
+ }
+}
+
+void
+mdb_attr_info_free( AttrInfo *ai )
+{
+#ifdef LDAP_COMP_MATCH
+ free( ai->ai_cr );
+#endif
+ free( ai );
+}
+
+void
+mdb_attr_index_destroy( struct mdb_info *mdb )
+{
+ int i;
+
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ mdb_attr_info_free( mdb->mi_attrs[i] );
+
+ free( mdb->mi_attrs );
+}
+
+void mdb_attr_index_free( struct mdb_info *mdb, AttributeDescription *ad )
+{
+ int i;
+
+ i = mdb_attr_slot( mdb, ad, NULL );
+ if ( i >= 0 ) {
+ mdb_attr_info_free( mdb->mi_attrs[i] );
+ mdb->mi_nattrs--;
+ for (; i<mdb->mi_nattrs; i++)
+ mdb->mi_attrs[i] = mdb->mi_attrs[i+1];
+ }
+}
+
+void mdb_attr_flush( struct mdb_info *mdb )
+{
+ int i;
+
+ for ( i=0; i<mdb->mi_nattrs; i++ ) {
+ if ( mdb->mi_attrs[i]->ai_indexmask & MDB_INDEX_DELETING ) {
+ /* if this is also a multival rec, just clear index */
+ if ( mdb->mi_attrs[i]->ai_multi_lo < UINT_MAX ) {
+ mdb->mi_attrs[i]->ai_indexmask = 0;
+ mdb->mi_attrs[i]->ai_newmask = 0;
+ } else {
+ int j;
+ mdb_attr_info_free( mdb->mi_attrs[i] );
+ mdb->mi_nattrs--;
+ for (j=i; j<mdb->mi_nattrs; j++)
+ mdb->mi_attrs[j] = mdb->mi_attrs[j+1];
+ i--;
+ }
+ }
+ }
+}
+
+int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn )
+{
+ int i, rc;
+ MDB_cursor *mc;
+ MDB_val key, data;
+ struct berval bdata;
+ const char *text;
+ AttributeDescription *ad;
+
+ rc = mdb_cursor_open( txn, mdb->mi_ad2id, &mc );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_ad_read: cursor_open failed %s(%d)\n",
+ mdb_strerror(rc), rc );
+ return rc;
+ }
+
+ /* our array is 1-based, an index of 0 means no data */
+ i = mdb->mi_numads+1;
+ key.mv_size = sizeof(int);
+ key.mv_data = &i;
+
+ rc = mdb_cursor_get( mc, &key, &data, MDB_SET );
+
+ while ( rc == MDB_SUCCESS ) {
+ bdata.bv_len = data.mv_size;
+ bdata.bv_val = data.mv_data;
+ ad = NULL;
+ rc = slap_bv2ad( &bdata, &ad, &text );
+ if ( rc ) {
+ rc = slap_bv2undef_ad( &bdata, &mdb->mi_ads[i], &text, 0 );
+ } else {
+ if ( ad->ad_index >= MDB_MAXADS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_adb_read: too many AttributeDescriptions in use\n" );
+ return LDAP_OTHER;
+ }
+ mdb->mi_adxs[ad->ad_index] = i;
+ mdb->mi_ads[i] = ad;
+ }
+ i++;
+ rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT );
+ }
+ mdb->mi_numads = i-1;
+
+done:
+ if ( rc == MDB_NOTFOUND )
+ rc = 0;
+
+ mdb_cursor_close( mc );
+
+ return rc;
+}
+
+int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad )
+{
+ int i, rc;
+ MDB_val key, val;
+
+ rc = mdb_ad_read( mdb, txn );
+ if (rc)
+ return rc;
+
+ if ( mdb->mi_adxs[ad->ad_index] )
+ return 0;
+
+ i = mdb->mi_numads+1;
+ key.mv_size = sizeof(int);
+ key.mv_data = &i;
+ val.mv_size = ad->ad_cname.bv_len;
+ val.mv_data = ad->ad_cname.bv_val;
+
+ rc = mdb_put( txn, mdb->mi_ad2id, &key, &val, 0 );
+ if ( rc == MDB_SUCCESS ) {
+ mdb->mi_adxs[ad->ad_index] = i;
+ mdb->mi_ads[i] = ad;
+ mdb->mi_numads = i;
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_ad_get: mdb_put failed %s(%d)\n",
+ mdb_strerror(rc), rc );
+ }
+
+ return rc;
+}
+
+void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads )
+{
+ int i;
+
+ for (i=mdb->mi_numads; i>prev_ads; i--) {
+ mdb->mi_adxs[mdb->mi_ads[i]->ad_index] = 0;
+ mdb->mi_ads[i] = NULL;
+ }
+ mdb->mi_numads = i;
+}
diff --git a/servers/slapd/back-mdb/back-mdb.h b/servers/slapd/back-mdb/back-mdb.h
new file mode 100644
index 0000000..4dba1a0
--- /dev/null
+++ b/servers/slapd/back-mdb/back-mdb.h
@@ -0,0 +1,209 @@
+/* back-mdb.h - mdb back-end header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _BACK_MDB_H_
+#define _BACK_MDB_H_
+
+#include <portable.h>
+#include "slap.h"
+#include "lmdb.h"
+
+LDAP_BEGIN_DECL
+
+#undef MDB_TOOL_IDL_CACHING /* currently no perf gain */
+
+#define DN_BASE_PREFIX SLAP_INDEX_EQUALITY_PREFIX
+#define DN_ONE_PREFIX '%'
+#define DN_SUBTREE_PREFIX '@'
+
+#define MDB_AD2ID 0
+#define MDB_DN2ID 1
+#define MDB_ID2ENTRY 2
+#define MDB_ID2VAL 3
+#define MDB_IDXCKP 4
+#define MDB_NDB 5
+
+/* The default search IDL stack cache depth */
+#define DEFAULT_SEARCH_STACK_DEPTH 16
+
+/* The minimum we can function with */
+#define MINIMUM_SEARCH_STACK_DEPTH 8
+
+#define MDB_INDICES 256
+
+#define MDB_MAXADS 65536
+
+/* Default to 10MB max */
+#define DEFAULT_MAPSIZE (10*1048576)
+
+/* Most users will never see this */
+#define DEFAULT_RTXN_SIZE 10000
+
+#ifdef LDAP_DEVEL
+#define MDB_MONITOR_IDX
+#endif
+
+typedef struct mdb_monitor_t {
+ void *mdm_cb;
+ struct berval mdm_ndn;
+} mdb_monitor_t;
+
+/* From ldap_rq.h */
+struct re_s;
+
+struct mdb_info {
+ MDB_env *mi_dbenv;
+
+ /* DB_ENV parameters */
+ char *mi_dbenv_home;
+ unsigned mi_dbenv_flags;
+ int mi_dbenv_mode;
+
+ size_t mi_mapsize;
+ ID mi_nextid;
+ size_t mi_maxentrysize;
+
+ slap_mask_t mi_defaultmask;
+ int mi_nattrs;
+ struct mdb_attrinfo **mi_attrs;
+ void *mi_search_stack;
+ int mi_search_stack_depth;
+ int mi_readers;
+
+ unsigned mi_rtxn_size;
+ int mi_txn_cp;
+ unsigned mi_txn_cp_min;
+ unsigned mi_txn_cp_kbyte;
+
+ struct re_s *mi_txn_cp_task;
+ struct re_s *mi_index_task;
+
+ mdb_monitor_t mi_monitor;
+
+#ifdef MDB_MONITOR_IDX
+ ldap_pvt_thread_mutex_t mi_idx_mutex;
+ Avlnode *mi_idx;
+#endif /* MDB_MONITOR_IDX */
+
+ int mi_flags;
+#define MDB_IS_OPEN 0x01
+#define MDB_OPEN_INDEX 0x02
+#define MDB_DEL_INDEX 0x08
+#define MDB_RE_OPEN 0x10
+#define MDB_NEED_UPGRADE 0x20
+
+ int mi_numads;
+
+ unsigned mi_multi_hi;
+ /* more than this many values in an attr goes
+ * into a separate DB */
+ unsigned mi_multi_lo;
+ /* less than this many values in an attr goes
+ * back into main blob */
+
+ MDB_dbi mi_dbis[MDB_NDB];
+ AttributeDescription *mi_ads[MDB_MAXADS];
+ int mi_adxs[MDB_MAXADS];
+};
+
+#define mi_id2entry mi_dbis[MDB_ID2ENTRY]
+#define mi_dn2id mi_dbis[MDB_DN2ID]
+#define mi_ad2id mi_dbis[MDB_AD2ID]
+#define mi_id2val mi_dbis[MDB_ID2VAL]
+#define mi_idxckp mi_dbis[MDB_IDXCKP]
+
+typedef struct mdb_op_info {
+ OpExtra moi_oe;
+ MDB_txn* moi_txn;
+ int moi_ref;
+ char moi_flag;
+} mdb_op_info;
+#define MOI_READER 0x01
+#define MOI_FREEIT 0x02
+#define MOI_KEEPER 0x04
+
+LDAP_END_DECL
+
+/* for the cache of attribute information (which are indexed, etc.) */
+typedef struct mdb_attrinfo {
+ AttributeDescription *ai_desc; /* attribute description cn;lang-en */
+ slap_mask_t ai_indexmask; /* how the attr is indexed */
+ slap_mask_t ai_newmask; /* new settings to replace old mask */
+#ifdef LDAP_COMP_MATCH
+ ComponentReference* ai_cr; /*component indexing*/
+#endif
+ TAvlnode *ai_root; /* for tools */
+ MDB_cursor *ai_cursor; /* for tools */
+ int ai_idx; /* position in AI array */
+ MDB_dbi ai_dbi;
+ unsigned ai_multi_hi;
+ unsigned ai_multi_lo;
+} AttrInfo;
+
+/* tool threaded indexer state */
+typedef struct mdb_attrixinfo {
+ OpExtra ai_oe;
+ void *ai_flist;
+ void *ai_clist;
+ AttrInfo *ai_ai;
+} AttrIxInfo;
+
+/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */
+#define MDB_INDEX_DELETING 0x8000U /* index is being modified */
+#define MDB_INDEX_UPDATE_OP 0x03 /* performing an index update */
+
+/* For slapindex to record which attrs in an entry belong to which
+ * index database
+ */
+typedef struct AttrList {
+ struct AttrList *next;
+ Attribute *attr;
+} AttrList;
+
+#ifndef CACHELINE
+#define CACHELINE 64
+#endif
+
+#if defined(__i386) || defined(__x86_64)
+#define MISALIGNED_OK 1
+#else
+#define ALIGNER (sizeof(size_t)-1)
+#endif
+
+typedef struct IndexRbody {
+ AttrInfo *ai;
+ AttrList *attrs;
+ void *tptr;
+ int i;
+} IndexRbody;
+
+typedef struct IndexRec {
+ union {
+ IndexRbody irb;
+#define ir_ai iru.irb.ai
+#define ir_attrs iru.irb.attrs
+#define ir_tptr iru.irb.tptr
+#define ir_i iru.irb.i
+ /* cache line alignment */
+ char pad[(sizeof(IndexRbody)+CACHELINE-1) & (!CACHELINE-1)];
+ } iru;
+} IndexRec;
+
+#define MAXRDNS SLAP_LDAPDN_MAXLEN/4
+
+#include "proto-mdb.h"
+
+#endif /* _BACK_MDB_H_ */
diff --git a/servers/slapd/back-mdb/bind.c b/servers/slapd/back-mdb/bind.c
new file mode 100644
index 0000000..6df3f2d
--- /dev/null
+++ b/servers/slapd/back-mdb/bind.c
@@ -0,0 +1,156 @@
+/* bind.c - mdb backend bind routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "back-mdb.h"
+
+int
+mdb_bind( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ Entry *e;
+ Attribute *a;
+
+ AttributeDescription *password = slap_schema.si_ad_userPassword;
+
+ MDB_txn *rtxn;
+ mdb_op_info opinfo = {{{0}}}, *moi = &opinfo;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "==> " LDAP_XSTRING(mdb_bind) ": dn: %s\n",
+ op->o_req_dn.bv_val );
+
+ /* allow noauth binds */
+ switch ( be_rootdn_bind( op, NULL ) ) {
+ case LDAP_SUCCESS:
+ /* frontend will send result */
+ return rs->sr_err = LDAP_SUCCESS;
+
+ default:
+ /* give the database a chance */
+ /* NOTE: this behavior departs from that of other backends,
+ * since the others, in case of password checking failure
+ * do not give the database a chance. If an entry with
+ * rootdn's name does not exist in the database the result
+ * will be the same. See ITS#4962 for discussion. */
+ break;
+ }
+
+ rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi);
+ switch(rs->sr_err) {
+ case 0:
+ break;
+ default:
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ rtxn = moi->moi_txn;
+
+ /* get entry with reader lock */
+ rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 0 );
+
+ switch(rs->sr_err) {
+ case MDB_NOTFOUND:
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap_server_busy";
+ goto done;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto done;
+ }
+
+ ber_dupbv( &op->oq_bind.rb_edn, &e->e_name );
+
+ /* check for deleted */
+ if ( is_entry_subentry( e ) ) {
+ /* entry is an subentry, don't allow bind */
+ Debug( LDAP_DEBUG_TRACE, "entry is subentry\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( is_entry_alias( e ) ) {
+ /* entry is an alias, don't allow bind */
+ Debug( LDAP_DEBUG_TRACE, "entry is alias\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( is_entry_referral( e ) ) {
+ Debug( LDAP_DEBUG_TRACE, "entry is referral\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ switch ( op->oq_bind.rb_method ) {
+ case LDAP_AUTH_SIMPLE:
+ a = attr_find( e->e_attrs, password );
+ if ( a == NULL ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( slap_passwd_check( op, e, a, &op->oq_bind.rb_cred,
+ &rs->sr_text ) != 0 )
+ {
+ /* failure; stop front end from sending result */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ rs->sr_err = 0;
+ break;
+
+ default:
+ assert( 0 ); /* should not be reachable */
+ rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED;
+ rs->sr_text = "authentication method not supported";
+ }
+
+done:
+ if ( moi == &opinfo ) {
+ mdb_txn_reset( moi->moi_txn );
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ } else {
+ moi->moi_ref--;
+ }
+ /* free entry and reader lock */
+ if( e != NULL ) {
+ mdb_entry_return( op, e );
+ }
+
+ if ( rs->sr_err ) {
+ send_ldap_result( op, rs );
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ /* front end will send result on success (rs->sr_err==0) */
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/compare.c b/servers/slapd/back-mdb/compare.c
new file mode 100644
index 0000000..10a6ccd
--- /dev/null
+++ b/servers/slapd/back-mdb/compare.c
@@ -0,0 +1,142 @@
+/* compare.c - mdb backend compare routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+int
+mdb_compare( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ Entry *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+
+ MDB_txn *rtxn;
+ mdb_op_info opinfo = {{{0}}}, *moi = &opinfo;
+
+ rs->sr_err = mdb_opinfo_get(op, mdb, 1, &moi);
+ switch(rs->sr_err) {
+ case 0:
+ break;
+ default:
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return rs->sr_err;
+ }
+
+ rtxn = moi->moi_txn;
+
+ /* get entry */
+ rs->sr_err = mdb_dn2entry( op, rtxn, NULL, &op->o_req_ndn, &e, NULL, 1 );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( rs->sr_err == MDB_NOTFOUND ) {
+ if ( e != NULL ) {
+ /* return referral only if "disclose" is granted on the object */
+ if ( ! access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ rs->sr_err = LDAP_REFERRAL;
+ }
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ rs->sr_err = rs->sr_ref ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT;
+ }
+
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if (!manageDSAit && is_entry_referral( e ) ) {
+ /* return referral only if "disclose" is granted on the object */
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ /* entry is a referral, don't allow compare */
+ rs->sr_ref = get_entry_referrals( op, e );
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = e->e_name.bv_val;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "entry is referral\n" );
+
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ rs->sr_err = slap_compare_entry( op, e, op->orc_ava );
+
+return_results:
+ send_ldap_result( op, rs );
+
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_FALSE:
+ case LDAP_COMPARE_TRUE:
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+
+done:
+ if ( moi == &opinfo ) {
+ mdb_txn_reset( moi->moi_txn );
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ } else {
+ moi->moi_ref--;
+ }
+ /* free entry */
+ if ( e != NULL ) {
+ mdb_entry_return( op, e );
+ }
+
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/config.c b/servers/slapd/back-mdb/config.c
new file mode 100644
index 0000000..8676ac3
--- /dev/null
+++ b/servers/slapd/back-mdb/config.c
@@ -0,0 +1,1004 @@
+/* config.c - mdb backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/errno.h>
+
+#include "back-mdb.h"
+#include "idl.h"
+
+#include "slap-config.h"
+
+#include "lutil.h"
+#include "ldap_rq.h"
+
+
+static ConfigDriver mdb_cf_gen;
+static ConfigDriver mdb_bk_cfg;
+
+enum {
+ MDB_CHKPT = 1,
+ MDB_DIRECTORY,
+ MDB_DBNOSYNC,
+ MDB_ENVFLAGS,
+ MDB_INDEX,
+ MDB_MAXREADERS,
+ MDB_MAXSIZE,
+ MDB_MODE,
+ MDB_SSTACK,
+ MDB_MULTIVAL,
+ MDB_IDLEXP,
+};
+
+static ConfigTable mdbcfg[] = {
+ { "idlexp", "log", 2, 2, 0, ARG_UINT|ARG_MAGIC|MDB_IDLEXP,
+ mdb_bk_cfg, "( OLcfgBkAt:12.1 NAME 'olcBkMdbIdlExp' "
+ "DESC 'Power of 2 used to set IDL size' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|MDB_DIRECTORY,
+ mdb_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' "
+ "DESC 'Directory for database content' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "checkpoint", "kbyte> <min", 3, 3, 0, ARG_MAGIC|MDB_CHKPT,
+ mdb_cf_gen, "( OLcfgDbAt:1.2 NAME 'olcDbCheckpoint' "
+ "DESC 'Database checkpoint interval in kbytes and minutes' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",NULL, NULL },
+ { "dbnosync", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|MDB_DBNOSYNC,
+ mdb_cf_gen, "( OLcfgDbAt:1.4 NAME 'olcDbNoSync' "
+ "DESC 'Disable synchronous database writes' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "envflags", "flags", 2, 0, 0, ARG_MAGIC|MDB_ENVFLAGS,
+ mdb_cf_gen, "( OLcfgDbAt:12.3 NAME 'olcDbEnvFlags' "
+ "DESC 'Database environment flags' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|MDB_INDEX,
+ mdb_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' "
+ "DESC 'Attribute index parameters' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "maxentrysize", "size", 2, 2, 0, ARG_ULONG|ARG_OFFSET,
+ (void *)offsetof(struct mdb_info, mi_maxentrysize),
+ "( OLcfgDbAt:12.4 NAME 'olcDbMaxEntrySize' "
+ "DESC 'Maximum size of an entry in bytes' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "maxreaders", "num", 2, 2, 0, ARG_UINT|ARG_MAGIC|MDB_MAXREADERS,
+ mdb_cf_gen, "( OLcfgDbAt:12.1 NAME 'olcDbMaxReaders' "
+ "DESC 'Maximum number of threads that may access the DB concurrently' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "maxsize", "size", 2, 2, 0, ARG_ULONG|ARG_MAGIC|MDB_MAXSIZE,
+ mdb_cf_gen, "( OLcfgDbAt:12.2 NAME 'olcDbMaxSize' "
+ "DESC 'Maximum size of DB in bytes' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "mode", "mode", 2, 2, 0, ARG_MAGIC|MDB_MODE,
+ mdb_cf_gen, "( OLcfgDbAt:0.3 NAME 'olcDbMode' "
+ "DESC 'Unix permissions of database files' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "multival", "attr> <hi,lo", 3, 3, 0, ARG_MAGIC|MDB_MULTIVAL,
+ mdb_cf_gen,
+ "( OLcfgDbAt:12.6 NAME 'olcDbMultival' "
+ "DESC 'Hi/Lo thresholds for splitting multivalued attr out of main blob' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "rtxnsize", "entries", 2, 2, 0, ARG_UINT|ARG_OFFSET,
+ (void *)offsetof(struct mdb_info, mi_rtxn_size),
+ "( OLcfgDbAt:12.5 NAME 'olcDbRtxnSize' "
+ "DESC 'Number of entries to process in one read transaction' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = DEFAULT_RTXN_SIZE } },
+ { "searchstack", "depth", 2, 2, 0, ARG_INT|ARG_MAGIC|MDB_SSTACK,
+ mdb_cf_gen, "( OLcfgDbAt:1.9 NAME 'olcDbSearchStack' "
+ "DESC 'Depth of search stack in IDLs' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs mdbocs[] = {
+ {
+ "( OLcfgBkOc:12.1 "
+ "NAME 'olcMdbBkConfig' "
+ "DESC 'MDB backend configuration' "
+ "SUP olcBackendConfig "
+ "MAY olcBkMdbIdlExp )",
+ Cft_Backend, mdbcfg },
+ {
+ "( OLcfgDbOc:12.1 "
+ "NAME 'olcMdbConfig' "
+ "DESC 'MDB database configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST olcDbDirectory "
+ "MAY ( olcDbCheckpoint $ olcDbEnvFlags $ "
+ "olcDbNoSync $ olcDbIndex $ olcDbMaxReaders $ olcDbMaxSize $ "
+ "olcDbMode $ olcDbSearchStack $ olcDbMaxEntrySize $ olcDbRtxnSize $ "
+ "olcDbMultival ) )",
+ Cft_Database, mdbcfg+1 },
+ { NULL, 0, NULL }
+};
+
+static slap_verbmasks mdb_envflags[] = {
+ { BER_BVC("nosync"), MDB_NOSYNC },
+ { BER_BVC("nometasync"), MDB_NOMETASYNC },
+ { BER_BVC("writemap"), MDB_WRITEMAP },
+ { BER_BVC("mapasync"), MDB_MAPASYNC },
+ { BER_BVC("nordahead"), MDB_NORDAHEAD },
+ { BER_BVNULL, 0 }
+};
+
+static int
+mdb_bk_cfg( ConfigArgs *c )
+{
+ int rc = 0;
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( MDB_idl_logn != MDB_IDL_LOGN )
+ c->value_int = MDB_idl_logn;
+ else
+ rc = 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* We expect to immediately be followed by an Add, but */
+ MDB_idl_logn = MDB_IDL_LOGN; /* return to default for safety */
+ mdb_idl_reset();
+ c->bi->bi_private = 0;
+ } else {
+ /* with 32 bit ints, db_size max is 2^30 and um_size max is 2^31 */
+ if ( c->value_int >= MDB_IDL_LOGN && ( c->value_int < sizeof(int) * CHAR_BIT - 1 )) {
+ MDB_idl_logn = c->value_int;
+ mdb_idl_reset();
+ c->bi->bi_private = (void *)8; /* non-NULL to show we're using it */
+ } else {
+ rc = 1;
+ }
+ }
+ return rc;
+}
+
+/* perform periodic syncs */
+static void *
+mdb_checkpoint( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ struct mdb_info *mdb = rtask->arg;
+
+ mdb_env_sync( mdb->mi_dbenv, 1 );
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
+/* reindex entries on the fly */
+static void *
+mdb_online_index( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ BackendDB *be = rtask->arg;
+ struct mdb_info *mdb = be->be_private;
+
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+
+ MDB_cursor *curs;
+ MDB_val key, data;
+ MDB_txn *txn;
+ ID id;
+ Entry *e;
+ int rc, getnext = 1;
+ int i, first = 1;
+ int intr = 0;
+
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_online_index) ": database %s: "
+ "starting\n", be->be_suffix[0].bv_val );
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ op->o_bd = be;
+
+ key.mv_size = sizeof(ID);
+
+ while ( 1 ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn );
+ if ( rc )
+ break;
+
+ /* pick up where we left off */
+ if ( first ) {
+ MDB_val k0;
+ unsigned short s = 0;
+
+ first = 0;
+ k0.mv_size = sizeof(s);
+ k0.mv_data = &s;
+ rc = mdb_get( txn, mdb->mi_idxckp, &k0, &data );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ break;
+ }
+ memcpy( &id, data.mv_data, sizeof( id ));
+ }
+
+ /* Save our stopping point */
+ if ( slapd_shutdown || ldap_pvt_thread_pool_pausequery( &connection_pool )) {
+ MDB_val k0;
+ unsigned short s = 0;
+
+ k0.mv_size = sizeof(s);
+ k0.mv_data = &s;
+ data.mv_data = &id;
+ data.mv_size = sizeof( id );
+ mdb_put( txn, mdb->mi_idxckp, &k0, &data, 0 );
+ mdb_txn_commit( txn );
+ intr = 1;
+ break;
+ }
+
+ rc = mdb_cursor_open( txn, mdb->mi_id2entry, &curs );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ break;
+ }
+ if ( getnext ) {
+ getnext = 0;
+ key.mv_data = &id;
+ rc = mdb_cursor_get( curs, &key, &data, MDB_SET_RANGE );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ if ( rc == MDB_NOTFOUND )
+ rc = 0;
+ break;
+ }
+ memcpy( &id, key.mv_data, sizeof( id ));
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_online_index) ": database %s: "
+ "indexing %lx\n", be->be_suffix[0].bv_val, (long)id );
+
+ rc = mdb_id2entry( op, curs, id, &e );
+ mdb_cursor_close( curs );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ if ( rc == MDB_NOTFOUND ) {
+ id++;
+ getnext = 1;
+ continue;
+ }
+ break;
+ }
+ rc = mdb_index_entry( op, txn, MDB_INDEX_UPDATE_OP, e );
+ mdb_entry_return( op, e );
+ if ( rc == 0 ) {
+ rc = mdb_txn_commit( txn );
+ txn = NULL;
+ } else {
+ mdb_txn_abort( txn );
+ txn = NULL;
+ }
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_online_index) ": database %s: "
+ "txn_commit failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ break;
+ }
+ id++;
+ getnext = 1;
+ }
+
+ /* all done */
+ if ( !intr ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_online_index) ": database %s: "
+ "final txn_begin failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ intr = 1; /* maybe it will succeed on a future retry */
+ } else {
+ for ( i = 0; i < mdb->mi_nattrs; i++ ) {
+ if ( mdb->mi_attrs[ i ]->ai_indexmask & MDB_INDEX_DELETING
+ || mdb->mi_attrs[ i ]->ai_newmask == 0 )
+ {
+ continue;
+ }
+ mdb->mi_attrs[ i ]->ai_indexmask = mdb->mi_attrs[ i ]->ai_newmask;
+ mdb->mi_attrs[ i ]->ai_newmask = 0;
+ }
+
+ /* zero out checkpoint DB */
+ mdb_drop( txn, mdb->mi_idxckp, 0 );
+ mdb_txn_commit( txn );
+ }
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_online_index) ": database %s: "
+ "stopping, %s done\n", be->be_suffix[0].bv_val, intr ? "not" : "all" );
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask ))
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ if ( intr && !slapd_shutdown ) {
+ /* on pause, resched to run again immediately */
+ time_t t = rtask->interval.tv_sec;
+ rtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ rtask->interval.tv_sec = t;
+ } else if ( mdb->mi_index_task ) {
+ mdb->mi_index_task = NULL;
+ ldap_pvt_runqueue_remove( &slapd_rq, rtask );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+static int
+mdb_setup_indexer( struct mdb_info *mdb )
+{
+ MDB_txn *txn;
+ MDB_cursor *curs;
+ MDB_val key, data;
+ int i, rc, changed = 0;
+ unsigned short s;
+
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txn );
+ if ( rc )
+ return rc;
+ rc = mdb_cursor_open( txn, mdb->mi_idxckp, &curs );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_setup_indexer) ": path %s: "
+ "starting\n", mdb->mi_dbenv_home );
+
+ key.mv_size = sizeof( s );
+ key.mv_data = &s;
+
+ /* record current and new index masks for all new index definitions */
+ {
+ slap_mask_t mask[2];
+ data.mv_size = sizeof(mask);
+ data.mv_data = mask;
+
+ for ( i = 0; i < mdb->mi_nattrs; i++ ) {
+ if ( !mdb->mi_attrs[i]->ai_newmask ) continue;
+ s = mdb->mi_adxs[ mdb->mi_attrs[i]->ai_desc->ad_index ];
+ mask[0] = mdb->mi_attrs[i]->ai_indexmask;
+ mask[1] = mdb->mi_attrs[i]->ai_newmask;
+ rc = mdb_cursor_put( curs, &key, &data, 0 );
+ if ( rc )
+ goto done;
+ changed = 1;
+ }
+ }
+
+ /* set indexer task to start at first entry */
+ if ( changed ) {
+ ID id = 0;
+ s = 0; /* key 0 records next entryID to index */
+ data.mv_size = sizeof( ID );
+ data.mv_data = &id;
+ rc = mdb_cursor_put( curs, &key, &data, 0 );
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_setup_indexer) ": path %s: "
+ "resetting to 0\n", mdb->mi_dbenv_home );
+ }
+
+done:
+ mdb_cursor_close( curs );
+ if ( !rc )
+ mdb_txn_commit( txn );
+ else
+ mdb_txn_abort( txn );
+ return rc;
+}
+
+int
+mdb_resume_index( BackendDB *be, MDB_txn *txn )
+{
+ struct mdb_info *mdb = be->be_private;
+ MDB_cursor *curs;
+ MDB_val key, data;
+ int i, rc, do_task = 0;
+ unsigned short *s;
+ slap_mask_t *mask;
+ AttributeDescription *ad;
+
+ rc = mdb_cursor_open( txn, mdb->mi_idxckp, &curs );
+ if ( rc )
+ return 0;
+
+ while(( rc = mdb_cursor_get( curs, &key, &data, MDB_NEXT )) == 0) {
+ s = key.mv_data;
+ if ( !*s )
+ continue;
+ ad = mdb->mi_ads[*s];
+ for ( i=0; i<mdb->mi_nattrs; i++) {
+ if (mdb->mi_attrs[i]->ai_desc == ad ) {
+ mask = data.mv_data;
+ mdb->mi_attrs[i]->ai_indexmask = mask[0];
+ mdb->mi_attrs[i]->ai_newmask = mask[1];
+ do_task = 1;
+ break;
+ }
+ }
+ }
+ mdb_cursor_close( curs );
+ return do_task;
+}
+
+void
+mdb_start_index_task( BackendDB *be )
+{
+ struct mdb_info *mdb = be->be_private;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mdb->mi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000,
+ mdb_online_index, be,
+ LDAP_XSTRING(mdb_online_index), be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+}
+
+/* Cleanup loose ends after Modify completes */
+static int
+mdb_cf_cleanup( ConfigArgs *c )
+{
+ struct mdb_info *mdb = c->be->be_private;
+ int rc = 0;
+
+ if ( mdb->mi_flags & MDB_DEL_INDEX ) {
+ mdb_attr_flush( mdb );
+ mdb->mi_flags ^= MDB_DEL_INDEX;
+ }
+
+ if ( mdb->mi_flags & MDB_RE_OPEN ) {
+ mdb->mi_flags ^= MDB_RE_OPEN;
+ rc = c->be->bd_info->bi_db_close( c->be, &c->reply );
+ if ( rc == 0 )
+ rc = c->be->bd_info->bi_db_open( c->be, &c->reply );
+ /* If this fails, we need to restart */
+ if ( rc ) {
+ slapd_shutdown = 2;
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "failed to reopen database, rc=%d", rc );
+ Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_cf_cleanup)
+ ": %s\n", c->cr_msg );
+ rc = LDAP_OTHER;
+ }
+ }
+
+ if ( mdb->mi_flags & MDB_OPEN_INDEX ) {
+ mdb->mi_flags ^= MDB_OPEN_INDEX;
+ rc = mdb_attr_dbs_open( c->be, NULL, &c->reply );
+ if ( rc )
+ rc = LDAP_OTHER;
+ mdb_setup_indexer( mdb );
+ }
+ return rc;
+}
+
+static int
+mdb_cf_gen( ConfigArgs *c )
+{
+ struct mdb_info *mdb = c->be->be_private;
+ int rc;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ rc = 0;
+ switch( c->type ) {
+ case MDB_MODE: {
+ char buf[64];
+ struct berval bv;
+ bv.bv_len = snprintf( buf, sizeof(buf), "0%o", mdb->mi_dbenv_mode );
+ if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) {
+ bv.bv_val = buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ } break;
+
+ case MDB_CHKPT:
+ if ( mdb->mi_txn_cp ) {
+ char buf[64];
+ struct berval bv;
+ bv.bv_len = snprintf( buf, sizeof(buf), "%ld %ld",
+ (long) mdb->mi_txn_cp_kbyte, (long) mdb->mi_txn_cp_min );
+ if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) {
+ bv.bv_val = buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case MDB_DIRECTORY:
+ if ( mdb->mi_dbenv_home ) {
+ c->value_string = ch_strdup( mdb->mi_dbenv_home );
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case MDB_DBNOSYNC:
+ if ( mdb->mi_dbenv_flags & MDB_NOSYNC )
+ c->value_int = 1;
+ break;
+
+ case MDB_ENVFLAGS:
+ if ( mdb->mi_dbenv_flags ) {
+ mask_to_verbs( mdb_envflags, mdb->mi_dbenv_flags, &c->rvalue_vals );
+ }
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+
+ case MDB_INDEX:
+ mdb_attr_index_unparse( mdb, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+
+ case MDB_SSTACK:
+ c->value_int = mdb->mi_search_stack_depth;
+ break;
+
+ case MDB_MAXREADERS:
+ c->value_int = mdb->mi_readers;
+ break;
+
+ case MDB_MAXSIZE:
+ c->value_ulong = mdb->mi_mapsize;
+ break;
+
+ case MDB_MULTIVAL:
+ mdb_attr_multi_unparse( mdb, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ rc = 0;
+ switch( c->type ) {
+ case MDB_MODE:
+#if 0
+ /* FIXME: does it make any sense to change the mode,
+ * if we don't exec a chmod()? */
+ mdb->bi_dbenv_mode = SLAPD_DEFAULT_DB_MODE;
+ break;
+#endif
+
+ /* single-valued no-ops */
+ case MDB_SSTACK:
+ case MDB_MAXREADERS:
+ case MDB_MAXSIZE:
+ break;
+
+ case MDB_CHKPT:
+ if ( mdb->mi_txn_cp_task ) {
+ struct re_s *re = mdb->mi_txn_cp_task;
+ mdb->mi_txn_cp_task = NULL;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) )
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ mdb->mi_txn_cp = 0;
+ break;
+ case MDB_DIRECTORY:
+ mdb->mi_flags |= MDB_RE_OPEN;
+ ch_free( mdb->mi_dbenv_home );
+ mdb->mi_dbenv_home = NULL;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ ldap_pvt_thread_pool_purgekey( mdb->mi_dbenv );
+ break;
+ case MDB_DBNOSYNC:
+ mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC, 0 );
+ mdb->mi_dbenv_flags &= ~MDB_NOSYNC;
+ break;
+
+ case MDB_ENVFLAGS:
+ if ( c->valx == -1 ) {
+ int i;
+ for ( i=0; mdb_envflags[i].mask; i++) {
+ if ( mdb->mi_dbenv_flags & mdb_envflags[i].mask ) {
+ /* not all flags are runtime resettable */
+ rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 );
+ if ( rc ) {
+ mdb->mi_flags |= MDB_RE_OPEN;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ rc = 0;
+ }
+ mdb->mi_dbenv_flags ^= mdb_envflags[i].mask;
+ }
+ }
+ } else {
+ int i = verb_to_mask( c->line, mdb_envflags );
+ if ( mdb_envflags[i].mask & mdb->mi_dbenv_flags ) {
+ rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[i].mask, 0 );
+ if ( rc ) {
+ mdb->mi_flags |= MDB_RE_OPEN;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ rc = 0;
+ }
+ mdb->mi_dbenv_flags ^= mdb_envflags[i].mask;
+ } else {
+ /* unknown keyword */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"",
+ c->argv[0], c->argv[i] );
+ Debug( LDAP_DEBUG_CONFIG, "%s %s\n", c->log, c->cr_msg );
+ rc = 1;
+ }
+ }
+ break;
+
+ case MDB_INDEX:
+ if ( c->valx == -1 ) {
+ int i;
+
+ /* delete all */
+ for ( i = 0; i < mdb->mi_nattrs; i++ ) {
+ mdb->mi_attrs[i]->ai_indexmask |= MDB_INDEX_DELETING;
+ }
+ mdb->mi_defaultmask = 0;
+ mdb->mi_flags |= MDB_DEL_INDEX;
+ config_push_cleanup( c, mdb_cf_cleanup );
+
+ } else {
+ struct berval bv, def = BER_BVC("default");
+ char *ptr;
+
+ for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++);
+
+ bv.bv_val = c->line;
+ bv.bv_len = ptr - bv.bv_val;
+ if ( bvmatch( &bv, &def )) {
+ mdb->mi_defaultmask = 0;
+
+ } else {
+ int i;
+ char **attrs;
+ char sep;
+
+ sep = bv.bv_val[ bv.bv_len ];
+ bv.bv_val[ bv.bv_len ] = '\0';
+ attrs = ldap_str2charray( bv.bv_val, "," );
+
+ for ( i = 0; attrs[ i ]; i++ ) {
+ AttributeDescription *ad = NULL;
+ const char *text;
+ AttrInfo *ai;
+
+ slap_str2ad( attrs[ i ], &ad, &text );
+ /* if we got here... */
+ assert( ad != NULL );
+
+ ai = mdb_attr_mask( mdb, ad );
+ /* if we got here... */
+ assert( ai != NULL );
+
+ ai->ai_indexmask |= MDB_INDEX_DELETING;
+ mdb->mi_flags |= MDB_DEL_INDEX;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ }
+
+ bv.bv_val[ bv.bv_len ] = sep;
+ ldap_charray_free( attrs );
+ }
+ }
+ break;
+ case MDB_MULTIVAL:
+ if ( c->valx == -1 ) {
+ int i;
+
+ /* delete all */
+ for ( i = 0; i < mdb->mi_nattrs; i++ ) {
+ mdb->mi_attrs[i]->ai_multi_hi = UINT_MAX;
+ mdb->mi_attrs[i]->ai_multi_lo = UINT_MAX;
+ }
+ mdb->mi_multi_hi = UINT_MAX;
+ mdb->mi_multi_lo = UINT_MAX;
+
+ } else {
+ struct berval bv, def = BER_BVC("default");
+ char *ptr;
+
+ for (ptr = c->line; !isspace( (unsigned char) *ptr ); ptr++);
+
+ bv.bv_val = c->line;
+ bv.bv_len = ptr - bv.bv_val;
+ if ( bvmatch( &bv, &def )) {
+ mdb->mi_multi_hi = UINT_MAX;
+ mdb->mi_multi_lo = UINT_MAX;
+
+ } else {
+ int i;
+ char **attrs;
+ char sep;
+
+ sep = bv.bv_val[ bv.bv_len ];
+ bv.bv_val[ bv.bv_len ] = '\0';
+ attrs = ldap_str2charray( bv.bv_val, "," );
+
+ for ( i = 0; attrs[ i ]; i++ ) {
+ AttributeDescription *ad = NULL;
+ const char *text;
+ AttrInfo *ai;
+
+ slap_str2ad( attrs[ i ], &ad, &text );
+ /* if we got here... */
+ assert( ad != NULL );
+
+ ai = mdb_attr_mask( mdb, ad );
+ /* if we got here... */
+ assert( ai != NULL );
+
+ ai->ai_multi_hi = UINT_MAX;
+ ai->ai_multi_lo = UINT_MAX;
+ }
+
+ bv.bv_val[ bv.bv_len ] = sep;
+ ldap_charray_free( attrs );
+ }
+ }
+ break;
+ }
+ return rc;
+ }
+
+ switch( c->type ) {
+ case MDB_MODE:
+ if ( ASCII_DIGIT( c->argv[1][0] ) ) {
+ long mode;
+ char *next;
+ errno = 0;
+ mode = strtol( c->argv[1], &next, 0 );
+ if ( errno != 0 || next == c->argv[1] || next[0] != '\0' ) {
+ fprintf( stderr, "%s: "
+ "unable to parse mode=\"%s\".\n",
+ c->log, c->argv[1] );
+ return 1;
+ }
+ mdb->mi_dbenv_mode = mode;
+
+ } else {
+ char *m = c->argv[1];
+ int who, what, mode = 0;
+
+ if ( strlen( m ) != STRLENOF("-rwxrwxrwx") ) {
+ return 1;
+ }
+
+ if ( m[0] != '-' ) {
+ return 1;
+ }
+
+ m++;
+ for ( who = 0; who < 3; who++ ) {
+ for ( what = 0; what < 3; what++, m++ ) {
+ if ( m[0] == '-' ) {
+ continue;
+ } else if ( m[0] != "rwx"[what] ) {
+ return 1;
+ }
+ mode += ((1 << (2 - what)) << 3*(2 - who));
+ }
+ }
+ mdb->mi_dbenv_mode = mode;
+ }
+ break;
+ case MDB_CHKPT: {
+ unsigned cp_kbyte, cp_min;
+ if ( lutil_atoux( &cp_kbyte, c->argv[1], 0 ) != 0 ) {
+ fprintf( stderr, "%s: "
+ "invalid kbyte \"%s\" in \"checkpoint\".\n",
+ c->log, c->argv[1] );
+ return 1;
+ }
+ if ( lutil_atoux( &cp_min, c->argv[2], 0 ) != 0 ) {
+ fprintf( stderr, "%s: "
+ "invalid minutes \"%s\" in \"checkpoint\".\n",
+ c->log, c->argv[2] );
+ return 1;
+ }
+ mdb->mi_txn_cp = 1;
+ mdb->mi_txn_cp_kbyte = cp_kbyte;
+ mdb->mi_txn_cp_min = cp_min;
+ /* If we're in server mode and time-based checkpointing is enabled,
+ * submit a task to perform periodic checkpoints.
+ */
+ if ((slapMode & SLAP_SERVER_MODE) && mdb->mi_txn_cp_min ) {
+ struct re_s *re = mdb->mi_txn_cp_task;
+ if ( re ) {
+ re->interval.tv_sec = mdb->mi_txn_cp_min * 60;
+ } else {
+ if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) {
+ fprintf( stderr, "%s: "
+ "\"checkpoint\" must occur after \"suffix\".\n",
+ c->log );
+ return 1;
+ }
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mdb->mi_txn_cp_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ mdb->mi_txn_cp_min * 60, mdb_checkpoint, mdb,
+ LDAP_XSTRING(mdb_checkpoint), c->be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ }
+ } break;
+
+ case MDB_DIRECTORY: {
+ FILE *f;
+ char *ptr, *testpath;
+ int len;
+
+ len = strlen( c->value_string );
+ testpath = ch_malloc( len + STRLENOF(LDAP_DIRSEP) + STRLENOF("DUMMY") + 1 );
+ ptr = lutil_strcopy( testpath, c->value_string );
+ *ptr++ = LDAP_DIRSEP[0];
+ strcpy( ptr, "DUMMY" );
+ f = fopen( testpath, "w" );
+ if ( f ) {
+ fclose( f );
+ unlink( testpath );
+ }
+ ch_free( testpath );
+ if ( !f ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: invalid path: %s",
+ c->log, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ) );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ return -1;
+ }
+
+ if ( mdb->mi_dbenv_home )
+ ch_free( mdb->mi_dbenv_home );
+ mdb->mi_dbenv_home = c->value_string;
+
+ }
+ break;
+
+ case MDB_DBNOSYNC:
+ if ( c->value_int )
+ mdb->mi_dbenv_flags |= MDB_NOSYNC;
+ else
+ mdb->mi_dbenv_flags &= ~MDB_NOSYNC;
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ mdb_env_set_flags( mdb->mi_dbenv, MDB_NOSYNC,
+ c->value_int );
+ }
+ break;
+
+ case MDB_ENVFLAGS: {
+ int i, j;
+ for ( i=1; i<c->argc; i++ ) {
+ j = verb_to_mask( c->argv[i], mdb_envflags );
+ if ( mdb_envflags[j].mask ) {
+ if ( mdb->mi_flags & MDB_IS_OPEN )
+ rc = mdb_env_set_flags( mdb->mi_dbenv, mdb_envflags[j].mask, 1 );
+ else
+ rc = 0;
+ if ( rc ) {
+ mdb->mi_flags |= MDB_RE_OPEN;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ rc = 0;
+ }
+ mdb->mi_dbenv_flags |= mdb_envflags[j].mask;
+ } else {
+ /* unknown keyword */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: unknown keyword \"%s\"",
+ c->argv[0], c->argv[i] );
+ Debug( LDAP_DEBUG_ANY, "%s %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+ break;
+
+ case MDB_INDEX:
+ rc = mdb_attr_index_config( mdb, c->fname, c->lineno,
+ c->argc - 1, &c->argv[1], &c->reply);
+
+ if( rc != LDAP_SUCCESS ) return 1;
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ mdb->mi_flags |= MDB_OPEN_INDEX;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ if ( !mdb->mi_index_task ) {
+ /* Start the task as soon as we finish here. Set a long
+ * interval (10 hours) so that it only gets scheduled once.
+ */
+ if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) {
+ fprintf( stderr, "%s: "
+ "\"index\" must occur after \"suffix\".\n",
+ c->log );
+ return 1;
+ }
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mdb->mi_index_task = ldap_pvt_runqueue_insert( &slapd_rq, 36000,
+ mdb_online_index, c->be,
+ LDAP_XSTRING(mdb_online_index), c->be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ }
+ break;
+
+ case MDB_SSTACK:
+ if ( c->value_int < MINIMUM_SEARCH_STACK_DEPTH ) {
+ fprintf( stderr,
+ "%s: depth %d too small, using %d\n",
+ c->log, c->value_int, MINIMUM_SEARCH_STACK_DEPTH );
+ c->value_int = MINIMUM_SEARCH_STACK_DEPTH;
+ }
+ mdb->mi_search_stack_depth = c->value_int;
+ break;
+
+ case MDB_MAXREADERS:
+ mdb->mi_readers = c->value_int;
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ mdb->mi_flags |= MDB_RE_OPEN;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ }
+ break;
+
+ case MDB_MAXSIZE:
+ mdb->mi_mapsize = c->value_ulong;
+ if ( mdb->mi_flags & MDB_IS_OPEN ) {
+ mdb->mi_flags |= MDB_RE_OPEN;
+ config_push_cleanup( c, mdb_cf_cleanup );
+ }
+ break;
+
+ case MDB_MULTIVAL:
+ rc = mdb_attr_multi_config( mdb, c->fname, c->lineno,
+ c->argc - 1, &c->argv[1], &c->reply);
+
+ if( rc != LDAP_SUCCESS ) return 1;
+ break;
+ }
+ return 0;
+}
+
+int mdb_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+ bi->bi_cf_ocs = mdbocs;
+
+ rc = config_register_schema( mdbcfg, mdbocs );
+ if ( rc ) return rc;
+ return 0;
+}
diff --git a/servers/slapd/back-mdb/delete.c b/servers/slapd/back-mdb/delete.c
new file mode 100644
index 0000000..d4e6b8d
--- /dev/null
+++ b/servers/slapd/back-mdb/delete.c
@@ -0,0 +1,436 @@
+/* delete.c - mdb backend delete routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "lutil.h"
+#include "back-mdb.h"
+
+int
+mdb_delete( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ struct berval pdn = {0, NULL};
+ Entry *e = NULL;
+ Entry *p = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ MDB_txn *txn = NULL;
+ MDB_cursor *mc;
+ mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ int parent_is_glue = 0;
+ int parent_is_leaf = 0;
+
+ Debug( LDAP_DEBUG_ARGS, "==> " LDAP_XSTRING(mdb_delete) ": %s\n",
+ op->o_req_dn.bv_val );
+
+ ctrls[num_ctrls] = 0;
+
+ /* begin transaction */
+ rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi );
+ rs->sr_text = NULL;
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_delete) ": txn_begin failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ txn = moi->moi_txn;
+
+ /* allocate CSN */
+ if ( BER_BVISNULL( &op->o_csn ) ) {
+ struct berval csn;
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof(csnbuf);
+ slap_get_csn( op, &csn, 1 );
+ }
+
+ rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc );
+ if ( rs->sr_err ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ dnParent( &op->o_req_ndn, &pdn );
+
+ /* get parent */
+ rs->sr_err = mdb_dn2entry( op, txn, mc, &pdn, &p, NULL, 1 );
+ switch( rs->sr_err ) {
+ case 0:
+ case MDB_NOTFOUND:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ if ( rs->sr_err == MDB_NOTFOUND ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n",
+ op->o_req_dn.bv_val );
+
+ if ( p && !BER_BVISEMPTY( &p->e_name )) {
+ rs->sr_matched = ch_strdup( p->e_name.bv_val );
+ if ( is_entry_referral( p )) {
+ BerVarray ref = get_entry_referrals( op, p );
+ rs->sr_ref = referral_rewrite( ref, &p->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral, NULL,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ }
+ if ( p ) {
+ mdb_entry_return( op, p );
+ p = NULL;
+ }
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+ }
+
+ /* get entry */
+ rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, NULL, 0 );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ e = p;
+ p = NULL;
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* FIXME : dn2entry() should return non-glue entry */
+ if ( rs->sr_err == MDB_NOTFOUND || ( !manageDSAit && is_entry_glue( e ))) {
+ Debug( LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_delete) ": no such object %s\n",
+ op->o_req_dn.bv_val );
+
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ if ( pdn.bv_len != 0 ) {
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WDEL, NULL );
+
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": no write "
+ "access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;
+ }
+
+ } else {
+ /* no parent, must be root to delete */
+ if( ! be_isroot( op ) ) {
+ if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv )
+ || be_shadow_update( op ) ) {
+ p = (Entry *)&slap_entry_root;
+
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WDEL, NULL );
+
+ p = NULL;
+
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete)
+ ": no access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete)
+ ": no parent and not root\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+ }
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ rs->sr_err = access_allowed( op, e,
+ entry, NULL, ACL_WDEL, NULL );
+
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": no write access "
+ "to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to entry";
+ goto return_results;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral, don't allow delete */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_delete) ": entry is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = ch_strdup( e->e_name.bv_val );
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ /* pre-read */
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": pre-read "
+ "failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ rs->sr_text = NULL;
+
+ /* Can't do it if we have kids */
+ rs->sr_err = mdb_dn2id_children( op, txn, e );
+ if( rs->sr_err != MDB_NOTFOUND ) {
+ switch( rs->sr_err ) {
+ case 0:
+ Debug(LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_delete)
+ ": non-leaf %s\n",
+ op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subordinate objects must be deleted first";
+ break;
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_delete)
+ ": has_children failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ }
+ goto return_results;
+ }
+
+ /* delete from dn2id */
+ rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, 1 );
+ mdb_cursor_close( mc );
+ if ( rs->sr_err != 0 ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": dn2id failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_text = "DN index delete failed";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+
+ /* delete indices for old attributes */
+ rs->sr_err = mdb_index_entry_del( op, txn, e );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": index failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_text = "entry index delete failed";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+
+ /* fixup delete CSN */
+ if ( !SLAP_SHADOW( op->o_bd )) {
+ struct berval vals[2];
+
+ assert( !BER_BVISNULL( &op->o_csn ) );
+ vals[0] = op->o_csn;
+ BER_BVZERO( &vals[1] );
+ rs->sr_err = mdb_index_values( op, txn, slap_schema.si_ad_entryCSN,
+ vals, 0, SLAP_INDEX_ADD_OP );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = "entryCSN index update failed";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+ }
+
+ /* delete from id2entry */
+ rs->sr_err = mdb_id2entry_delete( op->o_bd, txn, e );
+ if ( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_delete) ": id2entry failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_text = "entry delete failed";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+
+ if ( pdn.bv_len != 0 ) {
+ parent_is_glue = is_entry_glue(p);
+ rs->sr_err = mdb_dn2id_children( op, txn, p );
+ if ( rs->sr_err != MDB_NOTFOUND ) {
+ switch( rs->sr_err ) {
+ case 0:
+ break;
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_delete)
+ ": has_children failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ parent_is_leaf = 1;
+ }
+ mdb_entry_return( op, p );
+ p = NULL;
+ }
+
+ if( moi == &opinfo ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ opinfo.moi_oe.oe_key = NULL;
+ if( op->o_noop ) {
+ mdb_txn_abort( txn );
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ txn = NULL;
+ goto return_results;
+ } else {
+ rs->sr_err = mdb_txn_commit( txn );
+ }
+ txn = NULL;
+ }
+
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_delete) ": txn_%s failed: %s (%d)\n",
+ op->o_noop ? "abort (no-op)" : "commit",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit failed";
+
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_delete) ": deleted%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ e->e_id, op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) {
+ op->o_delete_glue_parent = 1;
+ }
+
+ if ( p != NULL ) {
+ mdb_entry_return( op, p );
+ }
+
+ /* free entry */
+ if( e != NULL ) {
+ mdb_entry_return( op, e );
+ }
+
+ if( moi == &opinfo ) {
+ if( txn != NULL ) {
+ mdb_txn_abort( txn );
+ }
+ if ( opinfo.moi_oe.oe_key ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ }
+ } else {
+ moi->moi_ref--;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+
+#if 0
+ if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) {
+ TXN_CHECKPOINT( mdb->bi_dbenv,
+ mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 );
+ }
+#endif
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/dn2entry.c b/servers/slapd/back-mdb/dn2entry.c
new file mode 100644
index 0000000..2262147
--- /dev/null
+++ b/servers/slapd/back-mdb/dn2entry.c
@@ -0,0 +1,79 @@
+/* dn2entry.c - routines to deal with the dn2id / id2entry glue */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+/*
+ * dn2entry - look up dn in the cache/indexes and return the corresponding
+ * entry. If the requested DN is not found and matched is TRUE, return info
+ * for the closest ancestor of the DN. Otherwise e is NULL.
+ */
+
+int
+mdb_dn2entry(
+ Operation *op,
+ MDB_txn *tid,
+ MDB_cursor *m2,
+ struct berval *dn,
+ Entry **e,
+ ID *nsubs,
+ int matched )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ int rc, rc2;
+ ID id = NOID;
+ struct berval mbv, nmbv;
+ MDB_cursor *mc;
+
+ Debug(LDAP_DEBUG_TRACE, "mdb_dn2entry(\"%s\")\n",
+ dn->bv_val ? dn->bv_val : "" );
+
+ *e = NULL;
+
+ rc = mdb_dn2id( op, tid, m2, dn, &id, nsubs, &mbv, &nmbv );
+ if ( rc ) {
+ if ( matched ) {
+ rc2 = mdb_cursor_open( tid, mdb->mi_id2entry, &mc );
+ if ( rc2 == MDB_SUCCESS ) {
+ rc2 = mdb_id2entry( op, mc, id, e );
+ mdb_cursor_close( mc );
+ }
+ }
+
+ } else {
+ rc = mdb_cursor_open( tid, mdb->mi_id2entry, &mc );
+ if ( rc == MDB_SUCCESS ) {
+ rc = mdb_id2entry( op, mc, id, e );
+ mdb_cursor_close(mc);
+ }
+ }
+ if ( *e ) {
+ (*e)->e_name = mbv;
+ if ( rc == MDB_SUCCESS )
+ ber_dupbv_x( &(*e)->e_nname, dn, op->o_tmpmemctx );
+ else
+ ber_dupbv_x( &(*e)->e_nname, &nmbv, op->o_tmpmemctx );
+ } else {
+ op->o_tmpfree( mbv.bv_val, op->o_tmpmemctx );
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/back-mdb/dn2id.c b/servers/slapd/back-mdb/dn2id.c
new file mode 100644
index 0000000..68000ca
--- /dev/null
+++ b/servers/slapd/back-mdb/dn2id.c
@@ -0,0 +1,981 @@
+/* dn2id.c - routines to deal with the dn2id index */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+#include "idl.h"
+#include "lutil.h"
+
+/* Management routines for a hierarchically structured database.
+ *
+ * Instead of a ldbm-style dn2id database, we use a hierarchical one. Each
+ * entry in this database is a struct diskNode, keyed by entryID and with
+ * the data containing the RDN and entryID of the node's children. We use
+ * a B-Tree with sorted duplicates to store all the children of a node under
+ * the same key. Also, the first item under the key contains the entry's own
+ * rdn and the ID of the node's parent, to allow bottom-up tree traversal as
+ * well as top-down. To keep this info first in the list, the high bit of all
+ * subsequent nrdnlen's is always set. This means we can only accommodate
+ * RDNs up to length 32767, but that's fine since full DNs are already
+ * restricted to 8192.
+ *
+ * Also each child node contains a count of the number of entries in
+ * its subtree, appended after its entryID.
+ *
+ * The diskNode is a variable length structure. This definition is not
+ * directly usable for in-memory manipulation.
+ */
+typedef struct diskNode {
+ unsigned char nrdnlen[2];
+ char nrdn[1];
+ char rdn[1]; /* variable placement */
+ unsigned char entryID[sizeof(ID)]; /* variable placement */
+ /* unsigned char nsubs[sizeof(ID)]; in child nodes only */
+} diskNode;
+
+/* Sort function for the sorted duplicate data items of a dn2id key.
+ * Sorts based on normalized RDN, in length order.
+ */
+int
+mdb_dup_compare(
+ const MDB_val *usrkey,
+ const MDB_val *curkey
+)
+{
+ diskNode *un, *cn;
+ int rc, nrlen;
+
+ un = (diskNode *)usrkey->mv_data;
+ cn = (diskNode *)curkey->mv_data;
+
+ /* data is not aligned, cannot compare directly */
+ rc = un->nrdnlen[0] - cn->nrdnlen[0];
+ if ( rc ) return rc;
+ rc = un->nrdnlen[1] - cn->nrdnlen[1];
+ if ( rc ) return rc;
+
+ nrlen = ((un->nrdnlen[0] & 0x7f) << 8) | un->nrdnlen[1];
+ return strncmp( un->nrdn, cn->nrdn, nrlen );
+}
+
+/* We add two elements to the DN2ID database - a data item under the parent's
+ * entryID containing the child's RDN and entryID, and an item under the
+ * child's entryID containing the parent's entryID.
+ */
+int
+mdb_dn2id_add(
+ Operation *op,
+ MDB_cursor *mcp,
+ MDB_cursor *mcd,
+ ID pid,
+ ID nsubs,
+ int upsub,
+ Entry *e )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_val key, data;
+ ID nid;
+ int rc, rlen, nrlen;
+ diskNode *d;
+ char *ptr;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_add 0x%lx: \"%s\"\n",
+ e->e_id, e->e_ndn ? e->e_ndn : "" );
+
+ nrlen = dn_rdnlen( op->o_bd, &e->e_nname );
+ if (nrlen) {
+ rlen = dn_rdnlen( op->o_bd, &e->e_name );
+ } else {
+ nrlen = e->e_nname.bv_len;
+ rlen = e->e_name.bv_len;
+ }
+
+ d = op->o_tmpalloc(sizeof(diskNode) + rlen + nrlen + sizeof(ID), op->o_tmpmemctx);
+ d->nrdnlen[1] = nrlen & 0xff;
+ d->nrdnlen[0] = (nrlen >> 8) | 0x80;
+ ptr = lutil_strncopy( d->nrdn, e->e_nname.bv_val, nrlen );
+ *ptr++ = '\0';
+ ptr = lutil_strncopy( ptr, e->e_name.bv_val, rlen );
+ *ptr++ = '\0';
+ memcpy( ptr, &e->e_id, sizeof( ID ));
+ ptr += sizeof( ID );
+ memcpy( ptr, &nsubs, sizeof( ID ));
+
+ key.mv_size = sizeof(ID);
+ key.mv_data = &nid;
+
+ nid = pid;
+
+ /* Need to make dummy root node once. Subsequent attempts
+ * will fail harmlessly.
+ */
+ if ( pid == 0 ) {
+ diskNode dummy = {{0, 0}, "", "", ""};
+ data.mv_data = &dummy;
+ data.mv_size = sizeof(diskNode);
+
+ mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA );
+ }
+
+ data.mv_data = d;
+ data.mv_size = sizeof(diskNode) + rlen + nrlen + sizeof( ID );
+
+ /* Add our child node under parent's key */
+ rc = mdb_cursor_put( mcp, &key, &data, MDB_NODUPDATA );
+
+ /* Add our own node */
+ if (rc == 0) {
+ int flag = MDB_NODUPDATA;
+ nid = e->e_id;
+ /* drop subtree count */
+ data.mv_size -= sizeof( ID );
+ ptr -= sizeof( ID );
+ memcpy( ptr, &pid, sizeof( ID ));
+ d->nrdnlen[0] ^= 0x80;
+
+ if ((slapMode & SLAP_TOOL_MODE) || (e->e_id == mdb->mi_nextid))
+ flag |= MDB_APPEND;
+ rc = mdb_cursor_put( mcd, &key, &data, flag );
+ }
+ op->o_tmpfree( d, op->o_tmpmemctx );
+
+ /* Add our subtree count to all superiors */
+ if ( rc == 0 && upsub && pid ) {
+ ID subs;
+ nid = pid;
+ do {
+ /* Get parent's RDN */
+ rc = mdb_cursor_get( mcp, &key, &data, MDB_SET );
+ if ( !rc ) {
+ char *p2;
+ ptr = (char *)data.mv_data + data.mv_size - sizeof( ID );
+ memcpy( &nid, ptr, sizeof( ID ));
+ /* Get parent's node under grandparent */
+ d = data.mv_data;
+ rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1];
+ p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx );
+ memcpy( p2, data.mv_data, rlen+2 );
+ *p2 ^= 0x80;
+ data.mv_data = p2;
+ rc = mdb_cursor_get( mcp, &key, &data, MDB_GET_BOTH );
+ op->o_tmpfree( p2, op->o_tmpmemctx );
+ if ( !rc ) {
+ /* Get parent's subtree count */
+ ptr = (char *)data.mv_data + data.mv_size - sizeof( ID );
+ memcpy( &subs, ptr, sizeof( ID ));
+ subs += nsubs;
+ p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+ memcpy( p2, data.mv_data, data.mv_size - sizeof( ID ));
+ memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID ));
+ data.mv_data = p2;
+ rc = mdb_cursor_put( mcp, &key, &data, MDB_CURRENT );
+ op->o_tmpfree( p2, op->o_tmpmemctx );
+ }
+ }
+ if ( rc )
+ break;
+ } while ( nid );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_add 0x%lx: %d\n", e->e_id, rc );
+
+ return rc;
+}
+
+/* mc must have been set by mdb_dn2id */
+int
+mdb_dn2id_delete(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ ID nsubs )
+{
+ ID nid;
+ char *ptr;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id_delete 0x%lx\n",
+ id );
+
+ /* Delete our ID from the parent's list */
+ rc = mdb_cursor_del( mc, 0 );
+
+ /* Delete our ID from the tree. With sorted duplicates, this
+ * will leave any child nodes still hanging around. This is OK
+ * for modrdn, which will add our info back in later.
+ */
+ if ( rc == 0 ) {
+ MDB_val key, data;
+ if ( nsubs ) {
+ mdb_cursor_get( mc, &key, NULL, MDB_GET_CURRENT );
+ memcpy( &nid, key.mv_data, sizeof( ID ));
+ }
+ key.mv_size = sizeof(ID);
+ key.mv_data = &id;
+ rc = mdb_cursor_get( mc, &key, &data, MDB_SET );
+ if ( rc == 0 )
+ rc = mdb_cursor_del( mc, 0 );
+ }
+
+ /* Delete our subtree count from all superiors */
+ if ( rc == 0 && nsubs && nid ) {
+ MDB_val key, data;
+ ID subs;
+ key.mv_data = &nid;
+ key.mv_size = sizeof( ID );
+ do {
+ rc = mdb_cursor_get( mc, &key, &data, MDB_SET );
+ if ( !rc ) {
+ char *p2;
+ diskNode *d;
+ int rlen;
+ ptr = (char *)data.mv_data + data.mv_size - sizeof( ID );
+ memcpy( &nid, ptr, sizeof( ID ));
+ /* Get parent's node under grandparent */
+ d = data.mv_data;
+ rlen = ( d->nrdnlen[0] << 8 ) | d->nrdnlen[1];
+ p2 = op->o_tmpalloc( rlen + 2, op->o_tmpmemctx );
+ memcpy( p2, data.mv_data, rlen+2 );
+ *p2 ^= 0x80;
+ data.mv_data = p2;
+ rc = mdb_cursor_get( mc, &key, &data, MDB_GET_BOTH );
+ op->o_tmpfree( p2, op->o_tmpmemctx );
+ if ( !rc ) {
+ /* Get parent's subtree count */
+ ptr = (char *)data.mv_data + data.mv_size - sizeof( ID );
+ memcpy( &subs, ptr, sizeof( ID ));
+ subs -= nsubs;
+ p2 = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+ memcpy( p2, data.mv_data, data.mv_size - sizeof( ID ));
+ memcpy( p2+data.mv_size - sizeof( ID ), &subs, sizeof( ID ));
+ data.mv_data = p2;
+ rc = mdb_cursor_put( mc, &key, &data, MDB_CURRENT );
+ op->o_tmpfree( p2, op->o_tmpmemctx );
+ }
+
+ }
+ if ( rc )
+ break;
+ } while ( nid );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id_delete 0x%lx: %d\n", id, rc );
+ return rc;
+}
+
+/* return last found ID in *id if no match
+ * If mc is provided, it will be left pointing to the RDN's
+ * record under the parent's ID. If nsubs is provided, return
+ * the number of entries in this entry's subtree.
+ */
+int
+mdb_dn2id(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor *mc,
+ struct berval *in,
+ ID *id,
+ ID *nsubs,
+ struct berval *matched,
+ struct berval *nmatched )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_cursor *cursor;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ int rc = 0, nrlen;
+ diskNode *d;
+ char *ptr;
+ char dn[SLAP_LDAPDN_MAXLEN];
+ ID pid, nid;
+ struct berval tmp;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2id(\"%s\")\n", in->bv_val ? in->bv_val : "" );
+
+ if ( matched ) {
+ matched->bv_val = dn + sizeof(dn) - 1;
+ matched->bv_len = 0;
+ *matched->bv_val-- = '\0';
+ }
+ if ( nmatched ) {
+ nmatched->bv_len = 0;
+ nmatched->bv_val = 0;
+ }
+
+ if ( !in->bv_len ) {
+ *id = 0;
+ nid = 0;
+ goto done;
+ }
+
+ tmp = *in;
+
+ if ( op->o_bd->be_nsuffix[0].bv_len ) {
+ nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len;
+ tmp.bv_val += nrlen;
+ tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len;
+ } else {
+ for ( ptr = tmp.bv_val + tmp.bv_len - 1; ptr >= tmp.bv_val; ptr-- )
+ if (DN_SEPARATOR(*ptr))
+ break;
+ ptr++;
+ tmp.bv_len -= ptr - tmp.bv_val;
+ tmp.bv_val = ptr;
+ }
+ nid = 0;
+ key.mv_size = sizeof(ID);
+
+ if ( mc ) {
+ cursor = mc;
+ } else {
+ rc = mdb_cursor_open( txn, dbi, &cursor );
+ if ( rc ) goto done;
+ }
+
+ for (;;) {
+ key.mv_data = &pid;
+ pid = nid;
+
+ data.mv_size = sizeof(diskNode) + tmp.bv_len;
+ d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+ d->nrdnlen[1] = tmp.bv_len & 0xff;
+ d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80;
+ ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len );
+ *ptr = '\0';
+ data.mv_data = d;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH );
+ op->o_tmpfree( d, op->o_tmpmemctx );
+ if ( rc )
+ break;
+ ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID);
+ memcpy( &nid, ptr, sizeof(ID));
+
+ /* grab the non-normalized RDN */
+ if ( matched ) {
+ int rlen;
+ d = data.mv_data;
+ rlen = data.mv_size - sizeof(diskNode) - tmp.bv_len - sizeof(ID);
+ matched->bv_len += rlen;
+ matched->bv_val -= rlen + 1;
+ ptr = lutil_strcopy( matched->bv_val, d->rdn + tmp.bv_len );
+ if ( pid ) {
+ *ptr = ',';
+ matched->bv_len++;
+ }
+ }
+ if ( nmatched ) {
+ nmatched->bv_val = tmp.bv_val;
+ }
+
+ if ( tmp.bv_val > in->bv_val ) {
+ for (ptr = tmp.bv_val - 2; ptr > in->bv_val &&
+ !DN_SEPARATOR(*ptr); ptr--) /* empty */;
+ if ( ptr >= in->bv_val ) {
+ if (DN_SEPARATOR(*ptr)) ptr++;
+ tmp.bv_len = tmp.bv_val - ptr - 1;
+ tmp.bv_val = ptr;
+ }
+ } else {
+ break;
+ }
+ }
+ *id = nid;
+ /* return subtree count if requested */
+ if ( !rc && nsubs ) {
+ ptr = (char *)data.mv_data + data.mv_size - sizeof(ID);
+ memcpy( nsubs, ptr, sizeof( ID ));
+ }
+ if ( !mc )
+ mdb_cursor_close( cursor );
+done:
+ if ( matched ) {
+ if ( matched->bv_len ) {
+ ptr = op->o_tmpalloc( matched->bv_len+1, op->o_tmpmemctx );
+ strcpy( ptr, matched->bv_val );
+ matched->bv_val = ptr;
+ } else {
+ if ( BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) && !nid ) {
+ ber_dupbv( matched, (struct berval *)&slap_empty_bv );
+ } else {
+ matched->bv_val = NULL;
+ }
+ }
+ }
+ if ( nmatched ) {
+ if ( nmatched->bv_val ) {
+ nmatched->bv_len = in->bv_len - (nmatched->bv_val - in->bv_val);
+ } else {
+ *nmatched = slap_empty_bv;
+ }
+ }
+
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: get failed: %s (%d)\n",
+ mdb_strerror( rc ), rc );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2id: got id=0x%lx\n",
+ nid );
+ }
+
+ return rc;
+}
+
+/* return IDs from root to parent of DN */
+int
+mdb_dn2sups(
+ Operation *op,
+ MDB_txn *txn,
+ struct berval *in,
+ ID *ids )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_cursor *cursor;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ int rc = 0, nrlen;
+ diskNode *d;
+ char *ptr;
+ ID pid, nid;
+ struct berval tmp;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_dn2sups(\"%s\")\n", in->bv_val );
+
+ if ( !in->bv_len ) {
+ goto done;
+ }
+
+ tmp = *in;
+
+ nrlen = tmp.bv_len - op->o_bd->be_nsuffix[0].bv_len;
+ tmp.bv_val += nrlen;
+ tmp.bv_len = op->o_bd->be_nsuffix[0].bv_len;
+ nid = 0;
+ key.mv_size = sizeof(ID);
+
+ rc = mdb_cursor_open( txn, dbi, &cursor );
+ if ( rc ) goto done;
+
+ for (;;) {
+ key.mv_data = &pid;
+ pid = nid;
+
+ data.mv_size = sizeof(diskNode) + tmp.bv_len;
+ d = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+ d->nrdnlen[1] = tmp.bv_len & 0xff;
+ d->nrdnlen[0] = (tmp.bv_len >> 8) | 0x80;
+ ptr = lutil_strncopy( d->nrdn, tmp.bv_val, tmp.bv_len );
+ *ptr = '\0';
+ data.mv_data = d;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH );
+ op->o_tmpfree( d, op->o_tmpmemctx );
+ if ( rc )
+ break;
+ ptr = (char *) data.mv_data + data.mv_size - 2*sizeof(ID);
+ memcpy( &nid, ptr, sizeof(ID));
+
+ if ( pid )
+ mdb_idl_insert( ids, pid );
+
+ if ( tmp.bv_val > in->bv_val ) {
+ for (ptr = tmp.bv_val - 2; ptr > in->bv_val &&
+ !DN_SEPARATOR(*ptr); ptr--) /* empty */;
+ if ( ptr >= in->bv_val ) {
+ if (DN_SEPARATOR(*ptr)) ptr++;
+ tmp.bv_len = tmp.bv_val - ptr - 1;
+ tmp.bv_val = ptr;
+ }
+ } else {
+ break;
+ }
+ }
+ mdb_cursor_close( cursor );
+done:
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_dn2sups: get failed: %s (%d)\n",
+ mdb_strerror( rc ), rc );
+ }
+
+ return rc;
+}
+
+int
+mdb_dn2id_children(
+ Operation *op,
+ MDB_txn *txn,
+ Entry *e )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ MDB_cursor *cursor;
+ int rc;
+ ID id;
+
+ key.mv_size = sizeof(ID);
+ key.mv_data = &id;
+ id = e->e_id;
+
+ rc = mdb_cursor_open( txn, dbi, &cursor );
+ if ( rc ) return rc;
+
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ if ( rc == 0 ) {
+ size_t dkids;
+ rc = mdb_cursor_count( cursor, &dkids );
+ if ( rc == 0 ) {
+ if ( dkids < 2 ) rc = MDB_NOTFOUND;
+ }
+ }
+ mdb_cursor_close( cursor );
+ return rc;
+}
+
+int
+mdb_id2name(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor **cursp,
+ ID id,
+ struct berval *name,
+ struct berval *nname )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ MDB_cursor *cursor;
+ int rc, len, nlen;
+ char dn[SLAP_LDAPDN_MAXLEN], ndn[SLAP_LDAPDN_MAXLEN], *ptr;
+ char *dptr, *nptr;
+ diskNode *d;
+
+ key.mv_size = sizeof(ID);
+
+ if ( !*cursp ) {
+ rc = mdb_cursor_open( txn, dbi, cursp );
+ if ( rc ) return rc;
+ }
+ cursor = *cursp;
+
+ len = 0;
+ nlen = 0;
+ dptr = dn;
+ nptr = ndn;
+ while (id) {
+ unsigned int nrlen, rlen;
+ key.mv_data = &id;
+ data.mv_size = 0;
+ data.mv_data = "";
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ if ( rc ) break;
+ ptr = data.mv_data;
+ ptr += data.mv_size - sizeof(ID);
+ memcpy( &id, ptr, sizeof(ID) );
+ d = data.mv_data;
+ nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1];
+ rlen = data.mv_size - sizeof(diskNode) - nrlen;
+ assert( nrlen < 1024 && rlen < 1024 ); /* FIXME: Sanity check */
+ if (nptr > ndn) {
+ *nptr++ = ',';
+ *dptr++ = ',';
+ }
+ /* copy name and trailing NUL */
+ memcpy( nptr, d->nrdn, nrlen+1 );
+ memcpy( dptr, d->nrdn+nrlen+1, rlen+1 );
+ nptr += nrlen;
+ dptr += rlen;
+ }
+ if ( rc == 0 ) {
+ name->bv_len = dptr - dn;
+ nname->bv_len = nptr - ndn;
+ name->bv_val = op->o_tmpalloc( name->bv_len + 1, op->o_tmpmemctx );
+ nname->bv_val = op->o_tmpalloc( nname->bv_len + 1, op->o_tmpmemctx );
+ memcpy( name->bv_val, dn, name->bv_len );
+ name->bv_val[name->bv_len] = '\0';
+ memcpy( nname->bv_val, ndn, nname->bv_len );
+ nname->bv_val[nname->bv_len] = '\0';
+ }
+ return rc;
+}
+
+/* Find each id in ids that is a child of base and move it to res.
+ */
+int
+mdb_idscope(
+ Operation *op,
+ MDB_txn *txn,
+ ID base,
+ ID *ids,
+ ID *res )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ MDB_cursor *cursor;
+ ID ida, id, cid = 0, ci0 = 0, idc = 0;
+ char *ptr;
+ int rc, copy;
+
+ key.mv_size = sizeof(ID);
+
+ MDB_IDL_ZERO( res );
+
+ rc = mdb_cursor_open( txn, dbi, &cursor );
+ if ( rc ) return rc;
+
+ /* first see if base has any children at all */
+ key.mv_data = &base;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ if ( rc ) {
+ goto leave;
+ }
+ {
+ size_t dkids;
+ rc = mdb_cursor_count( cursor, &dkids );
+ if ( rc == 0 ) {
+ if ( dkids < 2 ) {
+ goto leave;
+ }
+ }
+ }
+
+ ida = mdb_idl_first( ids, &cid );
+
+ /* Don't bother moving out of ids if it's a range */
+ if (!MDB_IDL_IS_RANGE(ids)) {
+ idc = ids[0];
+ ci0 = cid;
+ }
+
+ while (ida != NOID) {
+ copy = 1;
+ id = ida;
+ while (id) {
+ key.mv_data = &id;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ if ( rc ) {
+ /* not found, drop this from ids */
+ copy = 0;
+ break;
+ }
+ ptr = data.mv_data;
+ ptr += data.mv_size - sizeof(ID);
+ memcpy( &id, ptr, sizeof(ID) );
+ if ( id == base ) {
+ if ( res[0] >= MDB_idl_db_max ) {
+ /* too many aliases in scope. Fallback to range */
+ MDB_IDL_RANGE( res, MDB_IDL_FIRST( ids ), MDB_IDL_LAST( ids ));
+ goto leave;
+ }
+ res[0]++;
+ res[res[0]] = ida;
+ copy = 0;
+ break;
+ }
+ if ( op->ors_scope == LDAP_SCOPE_ONELEVEL )
+ break;
+ }
+ if (idc) {
+ if (copy) {
+ if (ci0 != cid)
+ ids[ci0] = ids[cid];
+ ci0++;
+ } else
+ idc--;
+ }
+ ida = mdb_idl_next( ids, &cid );
+ }
+ if (!MDB_IDL_IS_RANGE( ids ))
+ ids[0] = idc;
+
+leave:
+ mdb_cursor_close( cursor );
+ return rc;
+}
+
+/* See if base is a child of any of the scopes
+ */
+int
+mdb_idscopes(
+ Operation *op,
+ IdScopes *isc )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_dbi dbi = mdb->mi_dn2id;
+ MDB_val key, data;
+ ID id, prev;
+ ID2 id2;
+ char *ptr;
+ int rc = 0;
+ unsigned int x;
+ unsigned int nrlen, rlen;
+ diskNode *d;
+
+ key.mv_size = sizeof(ID);
+
+ if ( !isc->mc ) {
+ rc = mdb_cursor_open( isc->mt, dbi, &isc->mc );
+ if ( rc ) return rc;
+ }
+
+ id = isc->id;
+
+ /* Catch entries from deref'd aliases */
+ x = mdb_id2l_search( isc->scopes, id );
+ if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) {
+ isc->nscope = x;
+ return MDB_SUCCESS;
+ }
+
+ isc->sctmp[0].mid = 0;
+ while (id) {
+ if ( !rc ) {
+ key.mv_data = &id;
+ rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ if ( rc )
+ return rc;
+
+ /* save RDN info */
+ }
+ d = data.mv_data;
+ nrlen = (d->nrdnlen[0] << 8) | d->nrdnlen[1];
+ rlen = data.mv_size - sizeof(diskNode) - nrlen;
+ isc->nrdns[isc->numrdns].bv_len = nrlen;
+ isc->nrdns[isc->numrdns].bv_val = d->nrdn;
+ isc->rdns[isc->numrdns].bv_len = rlen;
+ isc->rdns[isc->numrdns].bv_val = d->nrdn+nrlen+1;
+ isc->numrdns++;
+
+ if (!rc && id != isc->id) {
+ /* remember our chain of parents */
+ id2.mid = id;
+ id2.mval = data;
+ mdb_id2l_insert( isc->sctmp, &id2 );
+ }
+ ptr = data.mv_data;
+ ptr += data.mv_size - sizeof(ID);
+ prev = id;
+ memcpy( &id, ptr, sizeof(ID) );
+ /* If we didn't advance, some parent is missing */
+ if ( id == prev )
+ return MDB_NOTFOUND;
+
+ x = mdb_id2l_search( isc->scopes, id );
+ if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id ) {
+ if ( !isc->scopes[x].mval.mv_data ) {
+ /* This node is in scope, add parent chain to scope */
+ int i;
+ for ( i = 1; i <= isc->sctmp[0].mid; i++ ) {
+ rc = mdb_id2l_insert( isc->scopes, &isc->sctmp[i] );
+ if ( rc )
+ break;
+ }
+ /* check id again since inserts may have changed its position */
+ if ( isc->scopes[x].mid != id )
+ x = mdb_id2l_search( isc->scopes, id );
+ isc->nscope = x;
+ return MDB_SUCCESS;
+ }
+ data = isc->scopes[x].mval;
+ rc = 1;
+ }
+ if ( op->ors_scope == LDAP_SCOPE_ONELEVEL )
+ break;
+ }
+ return MDB_SUCCESS;
+}
+
+/* See if ID is a child of any of the scopes,
+ * return MDB_KEYEXIST if so.
+ */
+int
+mdb_idscopechk(
+ Operation *op,
+ IdScopes *isc )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_val key, data;
+ ID id, prev;
+ char *ptr;
+ int rc = 0;
+ unsigned int x;
+
+ key.mv_size = sizeof(ID);
+
+ if ( !isc->mc ) {
+ rc = mdb_cursor_open( isc->mt, mdb->mi_dn2id, &isc->mc );
+ if ( rc ) return rc;
+ }
+
+ id = isc->id;
+
+ while (id) {
+ if ( !rc ) {
+ key.mv_data = &id;
+ rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ if ( rc )
+ return rc;
+ }
+
+ ptr = data.mv_data;
+ ptr += data.mv_size - sizeof(ID);
+ prev = id;
+ memcpy( &id, ptr, sizeof(ID) );
+ /* If we didn't advance, some parent is missing */
+ if ( id == prev )
+ return MDB_NOTFOUND;
+
+ x = mdb_id2l_search( isc->scopes, id );
+ if ( x <= isc->scopes[0].mid && isc->scopes[x].mid == id )
+ return MDB_KEYEXIST;
+ }
+ return MDB_SUCCESS;
+}
+
+int
+mdb_dn2id_walk(
+ Operation *op,
+ IdScopes *isc
+)
+{
+ MDB_val key, data;
+ diskNode *d;
+ char *ptr;
+ int rc, n;
+ ID nsubs;
+
+ if ( !isc->numrdns ) {
+ key.mv_data = &isc->id;
+ key.mv_size = sizeof(ID);
+ rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ isc->scopes[0].mid = isc->id;
+ isc->numrdns++;
+ isc->nscope = 0;
+ /* skip base if not a subtree walk */
+ if ( isc->oscope == LDAP_SCOPE_SUBTREE ||
+ isc->oscope == LDAP_SCOPE_BASE )
+ return rc;
+ }
+ if ( isc->oscope == LDAP_SCOPE_BASE )
+ return MDB_NOTFOUND;
+
+ for (;;) {
+ /* Get next sibling */
+ rc = mdb_cursor_get( isc->mc, &key, &data, MDB_NEXT_DUP );
+ if ( !rc ) {
+ ptr = (char *)data.mv_data + data.mv_size - 2*sizeof(ID);
+ d = data.mv_data;
+ memcpy( &isc->id, ptr, sizeof(ID));
+
+ /* If we're pushing down, see if there's any children to find */
+ if ( isc->nscope ) {
+ ptr += sizeof(ID);
+ memcpy( &nsubs, ptr, sizeof(ID));
+ /* No children, go to next sibling */
+ if ( nsubs < 2 )
+ continue;
+ }
+ n = isc->numrdns;
+ isc->scopes[n].mid = isc->id;
+ n--;
+ isc->nrdns[n].bv_len = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1];
+ isc->nrdns[n].bv_val = d->nrdn;
+ isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1;
+ isc->rdns[n].bv_len = data.mv_size - sizeof(diskNode) - isc->nrdns[n].bv_len - sizeof(ID);
+ /* return this ID to caller */
+ if ( !isc->nscope )
+ break;
+
+ /* push down to child */
+ key.mv_data = &isc->id;
+ mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ isc->nscope = 0;
+ isc->numrdns++;
+ continue;
+
+ } else if ( rc == MDB_NOTFOUND ) {
+ if ( !isc->nscope && isc->oscope != LDAP_SCOPE_ONELEVEL ) {
+ /* reset to first dup */
+ mdb_cursor_get( isc->mc, &key, NULL, MDB_GET_CURRENT );
+ mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ isc->nscope = 1;
+ continue;
+ } else {
+ isc->numrdns--;
+ /* stack is empty? */
+ if ( !isc->numrdns )
+ break;
+ /* pop up to prev node */
+ n = isc->numrdns - 1;
+ key.mv_data = &isc->scopes[n].mid;
+ key.mv_size = sizeof(ID);
+ data.mv_data = isc->nrdns[n].bv_val - 2;
+ data.mv_size = 1; /* just needs to be non-zero, mdb_dup_compare doesn't care */
+ mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH );
+ continue;
+ }
+ } else {
+ break;
+ }
+ }
+ return rc;
+}
+
+/* restore the nrdn/rdn pointers after a txn reset */
+void mdb_dn2id_wrestore (
+ Operation *op,
+ IdScopes *isc
+)
+{
+ MDB_val key, data;
+ diskNode *d;
+ int rc, n, nrlen;
+ char *ptr;
+
+ /* We only need to restore up to the n-1th element,
+ * the nth element will be replaced anyway
+ */
+ key.mv_size = sizeof(ID);
+ for ( n=0; n<isc->numrdns-1; n++ ) {
+ key.mv_data = &isc->scopes[n+1].mid;
+ rc = mdb_cursor_get( isc->mc, &key, &data, MDB_SET );
+ if ( rc )
+ continue;
+ /* we can't use this data directly since its nrlen
+ * is missing the high bit setting, so copy it and
+ * set it properly. we just copy enough to satisfy
+ * mdb_dup_compare.
+ */
+ d = data.mv_data;
+ nrlen = ((d->nrdnlen[0] & 0x7f) << 8) | d->nrdnlen[1];
+ ptr = op->o_tmpalloc( nrlen+2, op->o_tmpmemctx );
+ memcpy( ptr, data.mv_data, nrlen+2 );
+ key.mv_data = &isc->scopes[n].mid;
+ data.mv_data = ptr;
+ data.mv_size = 1;
+ *ptr |= 0x80;
+ mdb_cursor_get( isc->mc, &key, &data, MDB_GET_BOTH );
+ op->o_tmpfree( ptr, op->o_tmpmemctx );
+
+ /* now we're back to where we wanted to be */
+ d = data.mv_data;
+ isc->nrdns[n].bv_val = d->nrdn;
+ isc->rdns[n].bv_val = d->nrdn+isc->nrdns[n].bv_len+1;
+ }
+}
diff --git a/servers/slapd/back-mdb/extended.c b/servers/slapd/back-mdb/extended.c
new file mode 100644
index 0000000..9ede8b7
--- /dev/null
+++ b/servers/slapd/back-mdb/extended.c
@@ -0,0 +1,54 @@
+/* extended.c - mdb backend extended routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+#include "lber_pvt.h"
+
+static struct exop {
+ struct berval *oid;
+ BI_op_extended *extended;
+} exop_table[] = {
+ { NULL, NULL }
+};
+
+int
+mdb_extended( Operation *op, SlapReply *rs )
+/* struct berval *reqoid,
+ struct berval *reqdata,
+ char **rspoid,
+ struct berval **rspdata,
+ LDAPControl *** rspctrls,
+ const char** text,
+ BerVarray *refs
+) */
+{
+ int i;
+
+ for( i=0; exop_table[i].extended != NULL; i++ ) {
+ if( ber_bvcmp( exop_table[i].oid, &op->oq_extended.rs_reqoid ) == 0 ) {
+ return (exop_table[i].extended)( op, rs );
+ }
+ }
+
+ rs->sr_text = "not supported within naming context";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+}
+
diff --git a/servers/slapd/back-mdb/filterindex.c b/servers/slapd/back-mdb/filterindex.c
new file mode 100644
index 0000000..70cb37b
--- /dev/null
+++ b/servers/slapd/back-mdb/filterindex.c
@@ -0,0 +1,1173 @@
+/* filterindex.c - generate the list of candidate entries from a filter */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+#include "idl.h"
+#ifdef LDAP_COMP_MATCH
+#include <component.h>
+#endif
+
+static int presence_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeDescription *desc,
+ ID *ids );
+
+static int equality_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp );
+static int inequality_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp,
+ int gtorlt );
+static int approx_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp );
+static int substring_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ SubstringsAssertion *sub,
+ ID *ids,
+ ID *tmp );
+
+static int list_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ Filter *flist,
+ int ftype,
+ ID *ids,
+ ID *tmp,
+ ID *stack );
+
+static int
+ext_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion *mra,
+ ID *ids,
+ ID *tmp,
+ ID *stack);
+
+#ifdef LDAP_COMP_MATCH
+static int
+comp_candidates (
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion *mra,
+ ComponentFilter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack);
+
+static int
+ava_comp_candidates (
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ AttributeAliasing *aa,
+ ID *ids,
+ ID *tmp,
+ ID *stack);
+#endif
+
+int
+mdb_filter_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ Filter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack )
+{
+ int rc = 0;
+#ifdef LDAP_COMP_MATCH
+ AttributeAliasing *aa;
+#endif
+ Debug( LDAP_DEBUG_FILTER, "=> mdb_filter_candidates\n" );
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ MDB_IDL_ZERO( ids );
+ goto out;
+ }
+
+ switch ( f->f_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ switch( f->f_result ) {
+ case SLAPD_COMPARE_UNDEFINED:
+ /* This technically is not the same as FALSE, but it
+ * certainly will produce no matches.
+ */
+ /* FALL THRU */
+ case LDAP_COMPARE_FALSE:
+ MDB_IDL_ZERO( ids );
+ break;
+ case LDAP_COMPARE_TRUE:
+ MDB_IDL_ALL( ids );
+ break;
+ case LDAP_SUCCESS:
+ /* this is a pre-computed scope, leave it alone */
+ break;
+ }
+ break;
+ case LDAP_FILTER_PRESENT:
+ Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n" );
+ rc = presence_candidates( op, rtxn, f->f_desc, ids );
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n" );
+#ifdef LDAP_COMP_MATCH
+ if ( is_aliased_attribute && ( aa = is_aliased_attribute ( f->f_ava->aa_desc ) ) ) {
+ rc = ava_comp_candidates ( op, rtxn, f->f_ava, aa, ids, tmp, stack );
+ }
+ else
+#endif
+ {
+ rc = equality_candidates( op, rtxn, f->f_ava, ids, tmp );
+ }
+ break;
+
+ case LDAP_FILTER_APPROX:
+ Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n" );
+ rc = approx_candidates( op, rtxn, f->f_ava, ids, tmp );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n" );
+ rc = substring_candidates( op, rtxn, f->f_sub, ids, tmp );
+ break;
+
+ case LDAP_FILTER_GE:
+ /* if no GE index, use pres */
+ Debug( LDAP_DEBUG_FILTER, "\tGE\n" );
+ if( f->f_ava->aa_desc->ad_type->sat_ordering &&
+ ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) )
+ rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_GE );
+ else
+ rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids );
+ break;
+
+ case LDAP_FILTER_LE:
+ /* if no LE index, use pres */
+ Debug( LDAP_DEBUG_FILTER, "\tLE\n" );
+ if( f->f_ava->aa_desc->ad_type->sat_ordering &&
+ ( f->f_ava->aa_desc->ad_type->sat_ordering->smr_usage & SLAP_MR_ORDERED_INDEX ) )
+ rc = inequality_candidates( op, rtxn, f->f_ava, ids, tmp, LDAP_FILTER_LE );
+ else
+ rc = presence_candidates( op, rtxn, f->f_ava->aa_desc, ids );
+ break;
+
+ case LDAP_FILTER_NOT:
+ /* no indexing to support NOT filters */
+ Debug( LDAP_DEBUG_FILTER, "\tNOT\n" );
+ MDB_IDL_ALL( ids );
+ break;
+
+ case LDAP_FILTER_AND:
+ Debug( LDAP_DEBUG_FILTER, "\tAND\n" );
+ rc = list_candidates( op, rtxn,
+ f->f_and, LDAP_FILTER_AND, ids, tmp, stack );
+ break;
+
+ case LDAP_FILTER_OR:
+ Debug( LDAP_DEBUG_FILTER, "\tOR\n" );
+ rc = list_candidates( op, rtxn,
+ f->f_or, LDAP_FILTER_OR, ids, tmp, stack );
+ break;
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, "\tEXT\n" );
+ rc = ext_candidates( op, rtxn, f->f_mra, ids, tmp, stack );
+ break;
+ default:
+ Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n",
+ (unsigned long) f->f_choice );
+ /* Must not return NULL, otherwise extended filters break */
+ MDB_IDL_ALL( ids );
+ }
+ if ( ids[2] == NOID && MDB_IDL_IS_RANGE( ids )) {
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ ID last;
+
+ if ( mdb->mi_nextid ) {
+ last = mdb->mi_nextid;
+ } else {
+ MDB_cursor *mc;
+ MDB_val key;
+
+ last = 0;
+ rc = mdb_cursor_open( rtxn, mdb->mi_id2entry, &mc );
+ if ( !rc ) {
+ rc = mdb_cursor_get( mc, &key, NULL, MDB_LAST );
+ if ( !rc )
+ memcpy( &last, key.mv_data, sizeof( last ));
+ mdb_cursor_close( mc );
+ }
+ }
+ if ( last ) {
+ ids[2] = last;
+ } else {
+ MDB_IDL_ZERO( ids );
+ }
+ }
+
+out:
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_filter_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST( ids ),
+ (long) MDB_IDL_LAST( ids ) );
+
+ return rc;
+}
+
+#ifdef LDAP_COMP_MATCH
+static int
+comp_list_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion* mra,
+ ComponentFilter *flist,
+ int ftype,
+ ID *ids,
+ ID *tmp,
+ ID *save )
+{
+ int rc = 0;
+ ComponentFilter *f;
+
+ Debug( LDAP_DEBUG_FILTER, "=> comp_list_candidates 0x%x\n", ftype );
+ for ( f = flist; f != NULL; f = f->cf_next ) {
+ /* ignore precomputed scopes */
+ if ( f->cf_choice == SLAPD_FILTER_COMPUTED &&
+ f->cf_result == LDAP_SUCCESS ) {
+ continue;
+ }
+ MDB_IDL_ZERO( save );
+ rc = comp_candidates( op, rtxn, mra, f, save, tmp, save+MDB_idl_um_size );
+
+ if ( rc != 0 ) {
+ if ( ftype == LDAP_COMP_FILTER_AND ) {
+ rc = 0;
+ continue;
+ }
+ break;
+ }
+
+ if ( ftype == LDAP_COMP_FILTER_AND ) {
+ if ( f == flist ) {
+ MDB_IDL_CPY( ids, save );
+ } else {
+ mdb_idl_intersection( ids, save );
+ }
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ } else {
+ if ( f == flist ) {
+ MDB_IDL_CPY( ids, save );
+ } else {
+ mdb_idl_union( ids, save );
+ }
+ }
+ }
+
+ if( rc == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= comp_list_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+
+ } else {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= comp_list_candidates: undefined rc=%d\n",
+ rc );
+ }
+
+ return rc;
+}
+
+static int
+comp_equality_candidates (
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion *mra,
+ ComponentAssertion *ca,
+ ID *ids,
+ ID *tmp,
+ ID *stack)
+{
+ MDB_dbi dbi;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr = mra->ma_rule;
+ Syntax *sat_syntax;
+ ComponentReference* cr_list, *cr;
+ AttrInfo *ai;
+
+ MDB_IDL_ALL( ids );
+
+ if ( !ca->ca_comp_ref )
+ return 0;
+
+ ai = mdb_attr_mask( op->o_bd->be_private, mra->ma_desc );
+ if( ai ) {
+ cr_list = ai->ai_cr;
+ }
+ else {
+ return 0;
+ }
+ /* find a component reference to be indexed */
+ sat_syntax = ca->ca_ma_rule->smr_syntax;
+ for ( cr = cr_list ; cr ; cr = cr->cr_next ) {
+ if ( cr->cr_string.bv_len == ca->ca_comp_ref->cr_string.bv_len &&
+ strncmp( cr->cr_string.bv_val, ca->ca_comp_ref->cr_string.bv_val,cr->cr_string.bv_len ) == 0 )
+ break;
+ }
+
+ if ( !cr )
+ return 0;
+
+ rc = mdb_index_param( op->o_bd, mra->ma_desc, LDAP_FILTER_EQUALITY,
+ &dbi, &mask, &prefix );
+
+ if( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (ca->ca_ma_rule->smr_filter)(
+ LDAP_FILTER_EQUALITY,
+ cr->cr_indexmask,
+ sat_syntax,
+ ca->ca_ma_rule,
+ &prefix,
+ &ca->ca_ma_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ return 0;
+ }
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 );
+
+ if( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ break;
+ }
+
+ if( MDB_IDL_IS_ZERO( tmp ) ) {
+ MDB_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ MDB_IDL_CPY( ids, tmp );
+ } else {
+ mdb_idl_intersection( ids, tmp );
+ }
+
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ }
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= comp_equality_candidates: id=%ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ return( rc );
+}
+
+static int
+ava_comp_candidates (
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ AttributeAliasing *aa,
+ ID *ids,
+ ID *tmp,
+ ID *stack )
+{
+ MatchingRuleAssertion mra;
+
+ mra.ma_rule = ava->aa_desc->ad_type->sat_equality;
+ if ( !mra.ma_rule ) {
+ MDB_IDL_ALL( ids );
+ return 0;
+ }
+ mra.ma_desc = aa->aa_aliased_ad;
+ mra.ma_rule = ava->aa_desc->ad_type->sat_equality;
+
+ return comp_candidates ( op, rtxn, &mra, ava->aa_cf, ids, tmp, stack );
+}
+
+static int
+comp_candidates (
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion *mra,
+ ComponentFilter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack)
+{
+ int rc;
+
+ if ( !f ) return LDAP_PROTOCOL_ERROR;
+
+ Debug( LDAP_DEBUG_FILTER, "comp_candidates\n" );
+ switch ( f->cf_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ rc = f->cf_result;
+ break;
+ case LDAP_COMP_FILTER_AND:
+ rc = comp_list_candidates( op, rtxn, mra, f->cf_and, LDAP_COMP_FILTER_AND, ids, tmp, stack );
+ break;
+ case LDAP_COMP_FILTER_OR:
+ rc = comp_list_candidates( op, rtxn, mra, f->cf_or, LDAP_COMP_FILTER_OR, ids, tmp, stack );
+ break;
+ case LDAP_COMP_FILTER_NOT:
+ /* No component indexing supported for NOT filter */
+ Debug( LDAP_DEBUG_FILTER, "\tComponent NOT\n" );
+ MDB_IDL_ALL( ids );
+ rc = LDAP_PROTOCOL_ERROR;
+ break;
+ case LDAP_COMP_FILTER_ITEM:
+ rc = comp_equality_candidates( op, rtxn, mra, f->cf_ca, ids, tmp, stack );
+ break;
+ default:
+ MDB_IDL_ALL( ids );
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+
+ return( rc );
+}
+#endif
+
+static int
+ext_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ MatchingRuleAssertion *mra,
+ ID *ids,
+ ID *tmp,
+ ID *stack)
+{
+#ifdef LDAP_COMP_MATCH
+ /*
+ * Currently Only Component Indexing for componentFilterMatch is supported
+ * Indexing for an extensible filter is not supported yet
+ */
+ if ( mra->ma_cf ) {
+ return comp_candidates ( op, rtxn, mra, mra->ma_cf, ids, tmp, stack);
+ }
+#endif
+ if ( mra->ma_desc == slap_schema.si_ad_entryDN ) {
+ int rc;
+ ID id;
+
+ MDB_IDL_ZERO( ids );
+ if ( mra->ma_rule == slap_schema.si_mr_distinguishedNameMatch ) {
+base:
+ rc = mdb_dn2id( op, rtxn, NULL, &mra->ma_value, &id, NULL, NULL, NULL );
+ if ( rc == MDB_SUCCESS ) {
+ mdb_idl_insert( ids, id );
+ }
+ return 0;
+ } else if ( mra->ma_rule && mra->ma_rule->smr_match ==
+ dnRelativeMatch && dnIsSuffix( &mra->ma_value,
+ op->o_bd->be_nsuffix )) {
+ int scope;
+ if ( mra->ma_rule == slap_schema.si_mr_dnSuperiorMatch ) {
+ mdb_dn2sups( op, rtxn, &mra->ma_value, ids );
+ return 0;
+ }
+ if ( mra->ma_rule == slap_schema.si_mr_dnSubtreeMatch )
+ scope = LDAP_SCOPE_SUBTREE;
+ else if ( mra->ma_rule == slap_schema.si_mr_dnOneLevelMatch )
+ scope = LDAP_SCOPE_ONELEVEL;
+ else if ( mra->ma_rule == slap_schema.si_mr_dnSubordinateMatch )
+ scope = LDAP_SCOPE_SUBORDINATE;
+ else
+ goto base; /* scope = LDAP_SCOPE_BASE; */
+#if 0
+ if ( scope > LDAP_SCOPE_BASE ) {
+ ei = NULL;
+ rc = mdb_cache_find_ndn( op, rtxn, &mra->ma_value, &ei );
+ if ( ei )
+ mdb_cache_entryinfo_unlock( ei );
+ if ( rc == LDAP_SUCCESS ) {
+ int sc = op->ors_scope;
+ op->ors_scope = scope;
+ rc = mdb_dn2idl( op, rtxn, &mra->ma_value, ei, ids,
+ stack );
+ op->ors_scope = sc;
+ }
+ return 0;
+ }
+#endif
+ }
+ }
+
+ MDB_IDL_ALL( ids );
+ return 0;
+}
+
+static int
+list_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ Filter *flist,
+ int ftype,
+ ID *ids,
+ ID *tmp,
+ ID *save )
+{
+ int rc = 0;
+ Filter *f;
+
+ Debug( LDAP_DEBUG_FILTER, "=> mdb_list_candidates 0x%x\n", ftype );
+ for ( f = flist; f != NULL; f = f->f_next ) {
+ /* ignore precomputed scopes */
+ if ( f->f_choice == SLAPD_FILTER_COMPUTED &&
+ f->f_result == LDAP_SUCCESS ) {
+ continue;
+ }
+ MDB_IDL_ZERO( save );
+ rc = mdb_filter_candidates( op, rtxn, f, save, tmp,
+ save+MDB_idl_um_size );
+
+ if ( rc != 0 ) {
+ if ( ftype == LDAP_FILTER_AND ) {
+ rc = 0;
+ continue;
+ }
+ break;
+ }
+
+
+ if ( ftype == LDAP_FILTER_AND ) {
+ if ( f == flist ) {
+ MDB_IDL_CPY( ids, save );
+ } else {
+ mdb_idl_intersection( ids, save );
+ }
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ } else {
+ if ( f == flist ) {
+ MDB_IDL_CPY( ids, save );
+ } else {
+ mdb_idl_union( ids, save );
+ }
+ }
+ }
+
+ if( rc == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_list_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+
+ } else {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_list_candidates: undefined rc=%d\n",
+ rc );
+ }
+
+ return rc;
+}
+
+static int
+presence_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeDescription *desc,
+ ID *ids )
+{
+ MDB_dbi dbi;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_presence_candidates (%s)\n",
+ desc->ad_cname.bv_val );
+
+ MDB_IDL_ALL( ids );
+
+ if( desc == slap_schema.si_ad_objectClass ) {
+ return 0;
+ }
+
+ rc = mdb_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT,
+ &dbi, &mask, &prefix );
+
+ if( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ /* not indexed */
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_presence_candidates: (%s) not indexed\n",
+ desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_presence_candidates: (%s) index_param "
+ "returned=%d\n",
+ desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( prefix.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_presence_candidates: (%s) no prefix\n",
+ desc->ad_cname.bv_val );
+ return -1;
+ }
+
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &prefix, ids, NULL, 0 );
+
+ if( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_presence_candidates: (%s) "
+ "key read failed (%d)\n",
+ desc->ad_cname.bv_val, rc );
+ goto done;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "<= mdb_presence_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+
+done:
+ return rc;
+}
+
+static int
+equality_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp )
+{
+ MDB_dbi dbi;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_equality_candidates (%s)\n",
+ ava->aa_desc->ad_cname.bv_val );
+
+ if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
+ ID id;
+ rc = mdb_dn2id( op, rtxn, NULL, &ava->aa_value, &id, NULL, NULL, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ /* exactly one ID can match */
+ ids[0] = 1;
+ ids[1] = id;
+ }
+ if ( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ }
+ return rc;
+ }
+
+ MDB_IDL_ALL( ids );
+
+ rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY,
+ &dbi, &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_equality_candidates: (%s) not indexed\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= mdb_equality_candidates: (%s) "
+ "index_param failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = ava->aa_desc->ad_type->sat_equality;
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_EQUALITY,
+ mask,
+ ava->aa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ &ava->aa_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_equality_candidates: (%s, %s) "
+ "MR filter failed (%d)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_equality_candidates: (%s) no keys\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 );
+
+ if( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_equality_candidates: (%s) "
+ "key read failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( MDB_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_equality_candidates: (%s) NULL\n",
+ ava->aa_desc->ad_cname.bv_val );
+ MDB_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ MDB_IDL_CPY( ids, tmp );
+ } else {
+ mdb_idl_intersection( ids, tmp );
+ }
+
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_equality_candidates: id=%ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ return( rc );
+}
+
+
+static int
+approx_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp )
+{
+ MDB_dbi dbi;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_approx_candidates (%s)\n",
+ ava->aa_desc->ad_cname.bv_val );
+
+ MDB_IDL_ALL( ids );
+
+ rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX,
+ &dbi, &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_approx_candidates: (%s) not indexed\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= mdb_approx_candidates: (%s) "
+ "index_param failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = ava->aa_desc->ad_type->sat_approx;
+ if( !mr ) {
+ /* no approx matching rule, try equality matching rule */
+ mr = ava->aa_desc->ad_type->sat_equality;
+ }
+
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_APPROX,
+ mask,
+ ava->aa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ &ava->aa_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_approx_candidates: (%s, %s) "
+ "MR filter failed (%d)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_approx_candidates: (%s) no keys (%s)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 );
+
+ if( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_approx_candidates: (%s) "
+ "key read failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( MDB_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_approx_candidates: (%s) NULL\n",
+ ava->aa_desc->ad_cname.bv_val );
+ MDB_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ MDB_IDL_CPY( ids, tmp );
+ } else {
+ mdb_idl_intersection( ids, tmp );
+ }
+
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_approx_candidates %ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ return( rc );
+}
+
+static int
+substring_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ SubstringsAssertion *sub,
+ ID *ids,
+ ID *tmp )
+{
+ MDB_dbi dbi;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_substring_candidates (%s)\n",
+ sub->sa_desc->ad_cname.bv_val );
+
+ MDB_IDL_ALL( ids );
+
+ rc = mdb_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS,
+ &dbi, &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_substring_candidates: (%s) not indexed\n",
+ sub->sa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= mdb_substring_candidates: (%s) "
+ "index_param failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = sub->sa_desc->ad_type->sat_substr;
+
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_SUBSTRINGS,
+ mask,
+ sub->sa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ sub,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_substring_candidates: (%s) "
+ "MR filter failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_substring_candidates: (0x%04lx) no keys (%s)\n",
+ mask, sub->sa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[i], tmp, NULL, 0 );
+
+ if( rc == MDB_NOTFOUND ) {
+ MDB_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_substring_candidates: (%s) "
+ "key read failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( MDB_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_substring_candidates: (%s) NULL\n",
+ sub->sa_desc->ad_cname.bv_val );
+ MDB_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ MDB_IDL_CPY( ids, tmp );
+ } else {
+ mdb_idl_intersection( ids, tmp );
+ }
+
+ if( MDB_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_substring_candidates: %ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ return( rc );
+}
+
+static int
+inequality_candidates(
+ Operation *op,
+ MDB_txn *rtxn,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp,
+ int gtorlt )
+{
+ MDB_dbi dbi;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+ MDB_cursor *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_inequality_candidates (%s)\n",
+ ava->aa_desc->ad_cname.bv_val );
+
+ MDB_IDL_ALL( ids );
+
+ rc = mdb_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY,
+ &dbi, &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= mdb_inequality_candidates: (%s) not indexed\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= mdb_inequality_candidates: (%s) "
+ "index_param failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = ava->aa_desc->ad_type->sat_equality;
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_EQUALITY,
+ mask,
+ ava->aa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ &ava->aa_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_inequality_candidates: (%s, %s) "
+ "MR filter failed (%d)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_inequality_candidates: (%s) no keys\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ MDB_IDL_ZERO( ids );
+ while(1) {
+ rc = mdb_key_read( op->o_bd, rtxn, dbi, &keys[0], tmp, &cursor, gtorlt );
+
+ if( rc == MDB_NOTFOUND ) {
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_inequality_candidates: (%s) "
+ "key read failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( MDB_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_inequality_candidates: (%s) NULL\n",
+ ava->aa_desc->ad_cname.bv_val );
+ break;
+ }
+
+ mdb_idl_union( ids, tmp );
+
+ if( op->ors_limit && op->ors_limit->lms_s_unchecked != -1 &&
+ MDB_IDL_N( ids ) >= (unsigned) op->ors_limit->lms_s_unchecked ) {
+ mdb_cursor_close( cursor );
+ break;
+ }
+ }
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= mdb_inequality_candidates: id=%ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ return( rc );
+}
diff --git a/servers/slapd/back-mdb/id2entry.c b/servers/slapd/back-mdb/id2entry.c
new file mode 100644
index 0000000..cfe02f0
--- /dev/null
+++ b/servers/slapd/back-mdb/id2entry.c
@@ -0,0 +1,1160 @@
+/* id2entry.c - routines to deal with the id2entry database */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/errno.h>
+
+#include "back-mdb.h"
+
+typedef struct Ecount {
+ ber_len_t len; /* total entry size */
+ ber_len_t dlen; /* contiguous data size */
+ int nattrs;
+ int nvals;
+ int offset;
+ Attribute *multi;
+} Ecount;
+
+static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e,
+ Ecount *eh);
+static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data,
+ Ecount *ec);
+static Entry *mdb_entry_alloc( Operation *op, int nattrs, int nvals );
+
+#define ID2VKSZ (sizeof(ID)+2)
+
+int
+mdb_id2v_compare(
+ const MDB_val *usrkey,
+ const MDB_val *curkey
+)
+{
+ unsigned short *uv, *cv;
+ ID ui, ci;
+ int rc;
+
+ memcpy(&ui, usrkey->mv_data, sizeof(ID));
+ memcpy(&ci, curkey->mv_data, sizeof(ID));
+ if (ui < ci)
+ return -1;
+ if (ui > ci)
+ return 1;
+ uv = usrkey->mv_data;
+ cv = curkey->mv_data;
+ return uv[sizeof(ID)/2] - cv[sizeof(ID)/2];
+}
+
+/* usrkey[0] is the key in DB format, as described at mdb_mval_put.
+ * usrkey[1] is the value we'll actually match against.
+ * usrkey[2] is the attributeDescription for this value.
+ */
+int
+mdb_id2v_dupsort(
+ const MDB_val *usrkey,
+ const MDB_val *curkey
+)
+{
+ AttributeDescription *ad = usrkey[2].mv_data;
+ struct berval bv1, bv2;
+ int rc, match, olen;
+ unsigned short s;
+ char *ptr;
+
+ ptr = curkey->mv_data + curkey->mv_size - 2;
+ memcpy(&s, ptr, 2);
+ bv2.bv_val = curkey->mv_data;
+ bv2.bv_len = curkey->mv_size - 3;
+ if (s)
+ bv2.bv_len -= (s+1);
+
+ bv1.bv_val = usrkey[1].mv_data;
+ bv1.bv_len = usrkey[1].mv_size;
+
+ if (ad && ad->ad_type->sat_equality) {
+ MatchingRule *mr = ad->ad_type->sat_equality;
+ rc = mr->smr_match(&match, SLAP_MR_EQUALITY
+ | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX
+ | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
+ | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ ad->ad_type->sat_syntax, mr, &bv1, &bv2);
+ } else {
+ match = ber_bvcmp(&bv1, &bv2);
+ }
+
+ return match;
+}
+
+/* Values are stored as
+ * [normalized-value NUL ] original-value NUL 2-byte-len
+ * The trailing 2-byte-len is zero if there is no normalized value.
+ * Otherwise, it is the length of the original-value.
+ */
+int mdb_mval_put(Operation *op, MDB_cursor *mc, ID id, Attribute *a)
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_val key, data[3];
+ char *buf;
+ char ivk[ID2VKSZ];
+ unsigned i;
+ unsigned short s;
+ int rc, len;
+
+ memcpy(ivk, &id, sizeof(id));
+ s = mdb->mi_adxs[a->a_desc->ad_index];
+ memcpy(ivk+sizeof(ID), &s, 2);
+ key.mv_data = &ivk;
+ key.mv_size = sizeof(ivk);
+ if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass)
+ data[2].mv_data = NULL;
+ else
+ data[2].mv_data = a->a_desc;
+
+ for (i=0; i<a->a_numvals; i++) {
+ len = a->a_nvals[i].bv_len + 1 + 2;
+ if (a->a_nvals != a->a_vals) {
+ len += a->a_vals[i].bv_len + 1;
+ data[1].mv_data = a->a_nvals[i].bv_val;
+ data[1].mv_size = a->a_nvals[i].bv_len;
+ } else {
+ data[1].mv_data = a->a_vals[i].bv_val;
+ data[1].mv_size = a->a_vals[i].bv_len;
+ }
+ data[0].mv_size = len;
+ buf = op->o_tmpalloc( len, op->o_tmpmemctx );
+ data[0].mv_data = buf;
+ memcpy(buf, a->a_nvals[i].bv_val, a->a_nvals[i].bv_len);
+ buf += a->a_nvals[i].bv_len;
+ *buf++ = 0;
+ if (a->a_nvals != a->a_vals) {
+ s = a->a_vals[i].bv_len;
+ memcpy(buf, a->a_vals[i].bv_val, a->a_vals[i].bv_len);
+ buf += a->a_vals[i].bv_len;
+ *buf++ = 0;
+ memcpy(buf, &s, 2);
+ } else {
+ *buf++ = 0;
+ *buf++ = 0;
+ }
+ rc = mdb_cursor_put(mc, &key, data, 0);
+ op->o_tmpfree( data[0].mv_data, op->o_tmpmemctx );
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+int mdb_mval_del(Operation *op, MDB_cursor *mc, ID id, Attribute *a)
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_val key, data[3];
+ char *ptr;
+ char ivk[ID2VKSZ];
+ unsigned i;
+ int rc;
+ unsigned short s;
+
+ memcpy(ivk, &id, sizeof(id));
+ s = mdb->mi_adxs[a->a_desc->ad_index];
+ memcpy(ivk+sizeof(ID), &s, 2);
+ key.mv_data = &ivk;
+ key.mv_size = sizeof(ivk);
+ if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass)
+ data[2].mv_data = NULL;
+ else
+ data[2].mv_data = a->a_desc;
+
+ if (a->a_numvals) {
+ for (i=0; i<a->a_numvals; i++) {
+ data[0].mv_data = a->a_nvals[i].bv_val;
+ data[0].mv_size = a->a_nvals[i].bv_len+1;
+ if (a->a_nvals != a->a_vals) {
+ data[1].mv_data = a->a_nvals[i].bv_val;
+ data[1].mv_size = a->a_nvals[i].bv_len;
+ } else {
+ data[1].mv_data = a->a_vals[i].bv_val;
+ data[1].mv_size = a->a_vals[i].bv_len;
+ }
+ rc = mdb_cursor_get(mc, &key, data, MDB_GET_BOTH_RANGE);
+ if (rc)
+ return rc;
+ rc = mdb_cursor_del(mc, 0);
+ if (rc)
+ return rc;
+ }
+ } else {
+ rc = mdb_cursor_get(mc, &key, data, MDB_SET);
+ if (rc)
+ return rc;
+ rc = mdb_cursor_del(mc, MDB_NODUPDATA);
+ }
+ return rc;
+}
+
+static int mdb_mval_get(Operation *op, MDB_cursor *mc, ID id, Attribute *a, int have_nvals)
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_val key, data[3];
+ char *ptr;
+ char ivk[ID2VKSZ];
+ unsigned i;
+ int rc = 0;
+ unsigned short s;
+
+ memcpy(ivk, &id, sizeof(id));
+ s = mdb->mi_adxs[a->a_desc->ad_index];
+ memcpy(ivk+sizeof(ID), &s, 2);
+ key.mv_data = &ivk;
+ key.mv_size = sizeof(ivk);
+
+ /* not needed */
+ if ((a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED) || a->a_desc == slap_schema.si_ad_objectClass)
+ data[2].mv_data = NULL;
+ else
+ data[2].mv_data = a->a_desc;
+
+ if (have_nvals)
+ a->a_nvals = a->a_vals + a->a_numvals + 1;
+ else
+ a->a_nvals = a->a_vals;
+ for (i=0; i<a->a_numvals; i++) {
+ if (!i)
+ rc = mdb_cursor_get(mc, &key, data, MDB_SET);
+ else
+ rc = mdb_cursor_get(mc, &key, data, MDB_NEXT_DUP);
+ if (rc)
+ break;
+ ptr = (char*)data[0].mv_data + data[0].mv_size - 2;
+ memcpy(&s, ptr, 2);
+ if (have_nvals) {
+ a->a_nvals[i].bv_val = data[0].mv_data;
+ a->a_vals[i].bv_len = s;
+ a->a_vals[i].bv_val = ptr - a->a_vals[i].bv_len - 1;
+ a->a_nvals[i].bv_len = a->a_vals[i].bv_val - a->a_nvals[i].bv_val - 1;
+ } else {
+ assert(!s);
+ a->a_vals[i].bv_val = data[0].mv_data;
+ a->a_vals[i].bv_len = data[0].mv_size - 3;
+ }
+ }
+ a->a_numvals = i;
+ BER_BVZERO(&a->a_vals[i]);
+ if (have_nvals) {
+ BER_BVZERO(&a->a_nvals[i]);
+ }
+ return rc;
+}
+
+#define ADD_FLAGS (MDB_NOOVERWRITE|MDB_APPEND)
+
+static int mdb_id2entry_put(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor *mc,
+ Entry *e,
+ int flag )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ Ecount ec;
+ MDB_val key, data;
+ int rc, adding = flag, prev_ads = mdb->mi_numads;
+
+ /* We only store rdns, and they go in the dn2id database. */
+
+ key.mv_data = &e->e_id;
+ key.mv_size = sizeof(ID);
+
+ rc = mdb_entry_partsize( mdb, txn, e, &ec );
+ if (rc) {
+ rc = LDAP_OTHER;
+ goto fail;
+ }
+
+ flag |= MDB_RESERVE;
+
+ if (e->e_id < mdb->mi_nextid)
+ flag &= ~MDB_APPEND;
+
+ if (mdb->mi_maxentrysize && ec.len > mdb->mi_maxentrysize) {
+ rc = LDAP_ADMINLIMIT_EXCEEDED;
+ goto fail;
+ }
+
+again:
+ data.mv_size = ec.dlen;
+ if ( mc )
+ rc = mdb_cursor_put( mc, &key, &data, flag );
+ else
+ rc = mdb_put( txn, mdb->mi_id2entry, &key, &data, flag );
+ if (rc == MDB_SUCCESS) {
+ rc = mdb_entry_encode( op, e, &data, &ec );
+ if( rc != LDAP_SUCCESS )
+ goto fail;
+ /* Handle adds of large multi-valued attrs here.
+ * Modifies handle them directly.
+ */
+ if (adding && ec.multi) {
+ MDB_cursor *mvc;
+ Attribute *a;
+ rc = mdb_cursor_open( txn, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if( !rc ) {
+ for ( a = ec.multi; a; a=a->a_next ) {
+ if (!(a->a_flags & SLAP_ATTR_BIG_MULTI))
+ continue;
+ rc = mdb_mval_put( op, mvc, e->e_id, a );
+ if( rc )
+ break;
+ }
+ mdb_cursor_close( mvc );
+ }
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_id2entry_put: mdb_mval_put failed: %s(%d) \"%s\"\n",
+ mdb_strerror(rc), rc,
+ e->e_nname.bv_val );
+ rc = LDAP_OTHER;
+ goto fail;
+ }
+ }
+ }
+ if (rc) {
+ /* Was there a hole from slapadd? */
+ if ( (flag & MDB_NOOVERWRITE) && data.mv_size == 0 ) {
+ flag &= ~ADD_FLAGS;
+ goto again;
+ }
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_id2entry_put: mdb_put failed: %s(%d) \"%s\"\n",
+ mdb_strerror(rc), rc,
+ e->e_nname.bv_val );
+ if ( rc != MDB_KEYEXIST )
+ rc = LDAP_OTHER;
+ }
+fail:
+ if (rc) {
+ mdb_ad_unwind( mdb, prev_ads );
+ }
+ return rc;
+}
+
+/*
+ * This routine adds (or updates) an entry on disk.
+ */
+int mdb_id2entry_add(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor *mc,
+ Entry *e )
+{
+ return mdb_id2entry_put(op, txn, mc, e, ADD_FLAGS);
+}
+
+int mdb_id2entry_update(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor *mc,
+ Entry *e )
+{
+ return mdb_id2entry_put(op, txn, mc, e, 0);
+}
+
+int mdb_id2edata(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ MDB_val *data )
+{
+ MDB_val key;
+ int rc;
+
+ key.mv_data = &id;
+ key.mv_size = sizeof(ID);
+
+ /* fetch it */
+ rc = mdb_cursor_get( mc, &key, data, MDB_SET );
+ /* stubs from missing parents - DB is actually invalid */
+ if ( rc == MDB_SUCCESS && !data->mv_size )
+ rc = MDB_NOTFOUND;
+ return rc;
+}
+
+int mdb_id2entry(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ Entry **e )
+{
+ MDB_val key, data;
+ int rc = 0;
+
+ *e = NULL;
+
+ key.mv_data = &id;
+ key.mv_size = sizeof(ID);
+
+ /* fetch it */
+ rc = mdb_cursor_get( mc, &key, &data, MDB_SET );
+ if ( rc == MDB_NOTFOUND ) {
+ /* Looking for root entry on an empty-dn suffix? */
+ if ( !id && BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] )) {
+ struct berval gluebv = BER_BVC("glue");
+ Entry *r = mdb_entry_alloc(op, 2, 4);
+ Attribute *a = r->e_attrs;
+ struct berval *bptr;
+
+ r->e_id = 0;
+ r->e_ocflags = SLAP_OC_GLUE|SLAP_OC__END;
+ bptr = a->a_vals;
+ a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS;
+ a->a_desc = slap_schema.si_ad_objectClass;
+ a->a_nvals = a->a_vals;
+ a->a_numvals = 1;
+ *bptr++ = gluebv;
+ BER_BVZERO(bptr);
+ bptr++;
+ a->a_next = a+1;
+ a = a->a_next;
+ a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS;
+ a->a_desc = slap_schema.si_ad_structuralObjectClass;
+ a->a_vals = bptr;
+ a->a_nvals = a->a_vals;
+ a->a_numvals = 1;
+ *bptr++ = gluebv;
+ BER_BVZERO(bptr);
+ a->a_next = NULL;
+ *e = r;
+ return MDB_SUCCESS;
+ }
+ }
+ /* stubs from missing parents - DB is actually invalid */
+ if ( rc == MDB_SUCCESS && !data.mv_size )
+ rc = MDB_NOTFOUND;
+ if ( rc ) return rc;
+
+ rc = mdb_entry_decode( op, mdb_cursor_txn( mc ), &data, id, e );
+ if ( rc ) return rc;
+
+ (*e)->e_id = id;
+ (*e)->e_name.bv_val = NULL;
+ (*e)->e_nname.bv_val = NULL;
+
+ return rc;
+}
+
+int mdb_id2entry_delete(
+ BackendDB *be,
+ MDB_txn *tid,
+ Entry *e )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ MDB_dbi dbi = mdb->mi_id2entry;
+ MDB_val key;
+ MDB_cursor *mvc;
+ char kbuf[sizeof(ID) + sizeof(unsigned short)];
+ int rc;
+
+ memcpy( kbuf, &e->e_id, sizeof(ID) );
+ memset( kbuf+sizeof(ID), 0, sizeof(unsigned short) );
+ key.mv_data = kbuf;
+ key.mv_size = sizeof(kbuf);
+
+ /* delete from database */
+ rc = mdb_del( tid, dbi, &key, NULL );
+ if (rc)
+ return rc;
+ rc = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if (rc)
+ return rc;
+
+ rc = mdb_cursor_get( mvc, &key, NULL, MDB_SET_RANGE );
+ if (rc) {
+ if (rc == MDB_NOTFOUND)
+ rc = MDB_SUCCESS;
+ return rc;
+ }
+ while (*(ID *)key.mv_data == e->e_id ) {
+ rc = mdb_cursor_del( mvc, MDB_NODUPDATA );
+ if (rc)
+ return rc;
+ rc = mdb_cursor_get( mvc, &key, NULL, MDB_GET_CURRENT );
+ if (rc) {
+ /* no record or DB is empty */
+ if (rc == MDB_NOTFOUND || rc == EINVAL)
+ rc = MDB_SUCCESS;
+ break;
+ }
+ }
+ return rc;
+}
+
+static Entry * mdb_entry_alloc(
+ Operation *op,
+ int nattrs,
+ int nvals )
+{
+ Entry *e = op->o_tmpalloc( sizeof(Entry) +
+ nattrs * sizeof(Attribute) +
+ nvals * sizeof(struct berval), op->o_tmpmemctx );
+ BER_BVZERO(&e->e_bv);
+ e->e_private = e;
+ if (nattrs) {
+ e->e_attrs = (Attribute *)(e+1);
+ e->e_attrs->a_vals = (struct berval *)(e->e_attrs+nattrs);
+ } else {
+ e->e_attrs = NULL;
+ }
+
+ return e;
+}
+
+int mdb_entry_return(
+ Operation *op,
+ Entry *e
+)
+{
+ if ( !e )
+ return 0;
+ if ( e->e_private ) {
+ if ( op->o_hdr && op->o_tmpmfuncs ) {
+ op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( e, op->o_tmpmemctx );
+ } else {
+ ch_free( e->e_nname.bv_val );
+ ch_free( e->e_name.bv_val );
+ ch_free( e );
+ }
+ } else {
+ entry_free( e );
+ }
+ return 0;
+}
+
+int mdb_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ struct mdb_op_info *moi = NULL;
+ int release = 1;
+
+ /* slapMode : SLAP_SERVER_MODE, SLAP_TOOL_MODE,
+ SLAP_TRUNCATE_MODE, SLAP_UNDEFINED_MODE */
+
+ if ( slapMode & SLAP_SERVER_MODE ) {
+ OpExtra *oex;
+
+ /* Only Add ops call with rw set, and in that case the entry
+ * was not created by the backend. So always just release it.
+ *
+ * Otherwise, the entry was read from a backend, and we need
+ * to be sure it was read from this backend, otherwise leave
+ * it alone for someone else to release.
+ */
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == mdb ) {
+ moi = (mdb_op_info *)oex;
+ /* If it was setup by entry_get we should probably free it */
+ if (( moi->moi_flag & (MOI_FREEIT|MOI_KEEPER)) == MOI_FREEIT ) {
+ moi->moi_ref--;
+ if ( moi->moi_ref < 1 ) {
+ mdb_txn_reset( moi->moi_txn );
+ moi->moi_ref = 0;
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ op->o_tmpfree( moi, op->o_tmpmemctx );
+ }
+ }
+ break;
+ }
+ }
+ /* If read, other backends were in use, and not ours, don't release */
+ if ( !rw && ( LDAP_SLIST_FIRST( &op->o_extra ) && !oex ))
+ release = 0;
+ }
+
+ if (release)
+ mdb_entry_return( op, e );
+
+ return release ? 0 : SLAP_CB_CONTINUE;
+}
+
+/* return LDAP_SUCCESS IFF we can retrieve the specified entry.
+ */
+int mdb_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ struct mdb_op_info *moi = NULL;
+ MDB_txn *txn = NULL;
+ Entry *e = NULL;
+ int rc;
+ const char *at_name = at ? at->ad_cname.bv_val : "(null)";
+
+ Debug( LDAP_DEBUG_ARGS,
+ "=> mdb_entry_get: ndn: \"%s\"\n", ndn->bv_val );
+ Debug( LDAP_DEBUG_ARGS,
+ "=> mdb_entry_get: oc: \"%s\", at: \"%s\"\n",
+ oc ? oc->soc_cname.bv_val : "(null)", at_name );
+
+ rc = mdb_opinfo_get( op, mdb, rw == 0, &moi );
+ if ( rc )
+ return LDAP_OTHER;
+ txn = moi->moi_txn;
+
+ /* can we find entry */
+ rc = mdb_dn2entry( op, txn, NULL, ndn, &e, NULL, 0 );
+ switch( rc ) {
+ case MDB_NOTFOUND:
+ case 0:
+ break;
+ default:
+ return (rc != LDAP_BUSY) ? LDAP_OTHER : LDAP_BUSY;
+ }
+ if (e == NULL) {
+ Debug( LDAP_DEBUG_ACL,
+ "=> mdb_entry_get: cannot find entry: \"%s\"\n",
+ ndn->bv_val );
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "=> mdb_entry_get: found entry: \"%s\"\n",
+ ndn->bv_val );
+
+ if ( oc && !is_entry_objectclass( e, oc, 0 )) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= mdb_entry_get: failed to find objectClass %s\n",
+ oc->soc_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+ /* NOTE: attr_find() or attrs_find()? */
+ if ( at && attr_find( e->e_attrs, at ) == NULL ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= mdb_entry_get: failed to find attribute %s\n",
+ at->ad_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+return_results:
+ if( rc != LDAP_SUCCESS ) {
+ /* free entry */
+ mdb_entry_release( op, e, rw );
+ } else {
+ *ent = e;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "mdb_entry_get: rc=%d\n",
+ rc );
+ return(rc);
+}
+
+static void
+mdb_reader_free( void *key, void *data )
+{
+ MDB_txn *txn = data;
+
+ if ( txn ) mdb_txn_abort( txn );
+}
+
+/* free up any keys used by the main thread */
+void
+mdb_reader_flush( MDB_env *env )
+{
+ void *data;
+ void *ctx = ldap_pvt_thread_pool_context();
+
+ if ( !ldap_pvt_thread_pool_getkey( ctx, env, &data, NULL ) ) {
+ ldap_pvt_thread_pool_setkey( ctx, env, NULL, 0, NULL, NULL );
+ mdb_reader_free( env, data );
+ }
+}
+
+extern MDB_txn *mdb_tool_txn;
+
+int
+mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moip )
+{
+ int rc, renew = 0;
+ void *data;
+ void *ctx;
+ mdb_op_info *moi = NULL;
+ OpExtra *oex;
+
+ assert( op != NULL );
+
+ if ( !mdb || !moip ) return -1;
+
+ /* If no op was provided, try to find the ctx anyway... */
+ if ( op ) {
+ ctx = op->o_threadctx;
+ } else {
+ ctx = ldap_pvt_thread_pool_context();
+ }
+
+ if ( op ) {
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == mdb ) break;
+ }
+ moi = (mdb_op_info *)oex;
+ }
+
+ if ( !moi ) {
+ moi = *moip;
+
+ if ( !moi ) {
+ if ( op ) {
+ moi = op->o_tmpalloc(sizeof(struct mdb_op_info),op->o_tmpmemctx);
+ } else {
+ moi = ch_malloc(sizeof(mdb_op_info));
+ }
+ moi->moi_flag = MOI_FREEIT;
+ *moip = moi;
+ }
+ LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next );
+ moi->moi_oe.oe_key = mdb;
+ moi->moi_ref = 0;
+ moi->moi_txn = NULL;
+ }
+
+ if ( !rdonly ) {
+ /* This op started as a reader, but now wants to write. */
+ if ( moi->moi_flag & MOI_READER ) {
+ moi = *moip;
+ LDAP_SLIST_INSERT_HEAD( &op->o_extra, &moi->moi_oe, oe_next );
+ } else {
+ /* This op is continuing an existing write txn */
+ *moip = moi;
+ }
+ moi->moi_ref++;
+ if ( !moi->moi_txn ) {
+ if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) {
+ moi->moi_txn = mdb_tool_txn;
+ } else {
+ int flag = 0;
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+ if ( get_lazyCommit( op ))
+ flag |= MDB_NOMETASYNC;
+#endif
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, flag, &moi->moi_txn );
+ if (rc) {
+ Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+ mdb_strerror(rc), rc );
+ }
+ return rc;
+ }
+ }
+ return 0;
+ }
+
+ /* OK, this is a reader */
+ if ( !moi->moi_txn ) {
+ if (( slapMode & SLAP_TOOL_MODE ) && mdb_tool_txn ) {
+ moi->moi_txn = mdb_tool_txn;
+ goto ok;
+ }
+ if ( !ctx ) {
+ /* Shouldn't happen unless we're single-threaded */
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn );
+ if (rc) {
+ Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+ mdb_strerror(rc), rc );
+ }
+ return rc;
+ }
+ if ( ldap_pvt_thread_pool_getkey( ctx, mdb->mi_dbenv, &data, NULL ) ) {
+ int retried = 0;
+retry:
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &moi->moi_txn );
+ if (rc == MDB_READERS_FULL && !retried) {
+ int dead;
+ /* if any stale readers were cleared, a slot should be available */
+ if (!mdb_reader_check( mdb->mi_dbenv, &dead ) && dead) {
+ retried = 1;
+ goto retry;
+ }
+ }
+ if (rc) {
+ Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: err %s(%d)\n",
+ mdb_strerror(rc), rc );
+ return rc;
+ }
+ data = moi->moi_txn;
+ if ( ( rc = ldap_pvt_thread_pool_setkey( ctx, mdb->mi_dbenv,
+ data, mdb_reader_free, NULL, NULL ) ) ) {
+ mdb_txn_abort( moi->moi_txn );
+ moi->moi_txn = NULL;
+ Debug( LDAP_DEBUG_ANY, "mdb_opinfo_get: thread_pool_setkey failed err (%d)\n",
+ rc );
+ return rc;
+ }
+ } else {
+ moi->moi_txn = data;
+ renew = 1;
+ }
+ moi->moi_flag |= MOI_READER;
+ }
+ok:
+ if ( moi->moi_ref < 1 ) {
+ moi->moi_ref = 0;
+ }
+ if ( renew ) {
+ rc = mdb_txn_renew( moi->moi_txn );
+ assert(!rc);
+ }
+ moi->moi_ref++;
+ if ( *moip != moi )
+ *moip = moi;
+
+ return 0;
+}
+
+int mdb_txn( Operation *op, int txnop, OpExtra **ptr )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ mdb_op_info **moip = (mdb_op_info **)ptr, *moi = *moip;
+ int rc;
+
+ switch( txnop ) {
+ case SLAP_TXN_BEGIN:
+ rc = mdb_opinfo_get( op, mdb, 0, moip );
+ if ( !rc ) {
+ moi = *moip;
+ moi->moi_flag |= MOI_KEEPER;
+ }
+ return rc;
+ case SLAP_TXN_COMMIT:
+ rc = mdb_txn_commit( moi->moi_txn );
+ if ( rc )
+ mdb->mi_numads = 0;
+ op->o_tmpfree( moi, op->o_tmpmemctx );
+ return rc;
+ case SLAP_TXN_ABORT:
+ mdb->mi_numads = 0;
+ mdb_txn_abort( moi->moi_txn );
+ op->o_tmpfree( moi, op->o_tmpmemctx );
+ return 0;
+ }
+ return LDAP_OTHER;
+}
+
+/* Count up the sizes of the components of an entry */
+static int mdb_entry_partsize(struct mdb_info *mdb, MDB_txn *txn, Entry *e,
+ Ecount *eh)
+{
+ ber_len_t len, dlen;
+ int i, nat = 0, nval = 0, nnval = 0, doff = 0;
+ Attribute *a;
+ unsigned hi;
+
+ eh->multi = NULL;
+ len = 4*sizeof(int); /* nattrs, nvals, ocflags, offset */
+ dlen = len;
+ for (a=e->e_attrs; a; a=a->a_next) {
+ /* For AttributeDesc, we only store the attr index */
+ nat++;
+ if (a->a_desc->ad_index >= MDB_MAXADS) {
+ Debug( LDAP_DEBUG_ANY, "mdb_entry_partsize: too many AttributeDescriptions used\n" );
+ return LDAP_OTHER;
+ }
+ if (!mdb->mi_adxs[a->a_desc->ad_index]) {
+ int rc = mdb_ad_get(mdb, txn, a->a_desc);
+ if (rc)
+ return rc;
+ }
+ len += 2*sizeof(int); /* AD index, numvals */
+ dlen += 2*sizeof(int);
+ nval += a->a_numvals + 1; /* empty berval at end */
+ mdb_attr_multi_thresh( mdb, a->a_desc, &hi, NULL );
+ if (a->a_numvals > hi)
+ a->a_flags |= SLAP_ATTR_BIG_MULTI;
+ if (a->a_flags & SLAP_ATTR_BIG_MULTI)
+ doff += a->a_numvals;
+ for (i=0; i<a->a_numvals; i++) {
+ int alen = a->a_vals[i].bv_len + 1 + sizeof(int); /* len */
+ len += alen;
+ if (a->a_flags & SLAP_ATTR_BIG_MULTI) {
+ if (!eh->multi)
+ eh->multi = a;
+ } else {
+ dlen += alen;
+ }
+ }
+ if (a->a_nvals != a->a_vals) {
+ nval += a->a_numvals + 1;
+ nnval++;
+ if (a->a_flags & SLAP_ATTR_BIG_MULTI)
+ doff += a->a_numvals;
+ for (i=0; i<a->a_numvals; i++) {
+ int alen = a->a_nvals[i].bv_len + 1 + sizeof(int);
+ len += alen;
+ if (!(a->a_flags & SLAP_ATTR_BIG_MULTI))
+ dlen += alen;
+ }
+ }
+ }
+ /* padding */
+ dlen = (dlen + sizeof(ID)-1) & ~(sizeof(ID)-1);
+ eh->len = len;
+ eh->dlen = dlen;
+ eh->nattrs = nat;
+ eh->nvals = nval;
+ eh->offset = nat + nval - nnval - doff;
+ return 0;
+}
+
+/* Flag bits for an encoded attribute */
+#define MDB_AT_SORTED (1U<<(sizeof(unsigned int)*CHAR_BIT-1))
+ /* the values are in sorted order */
+#define MDB_AT_MULTI (1<<(sizeof(unsigned int)*CHAR_BIT-2))
+ /* the values of this multi-valued attr are stored separately */
+
+#define MDB_AT_NVALS (1U<<(sizeof(unsigned int)*CHAR_BIT-1))
+ /* this attribute has normalized values */
+
+/* Flatten an Entry into a buffer. The buffer starts with the count of the
+ * number of attributes in the entry, the total number of values in the
+ * entry, and the e_ocflags. It then contains a list of integers for each
+ * attribute. For each attribute the first integer gives the index of the
+ * matching AttributeDescription, followed by the number of values in the
+ * attribute. If the MDB_AT_SORTED bit of the attr index is set, the
+ * attribute's values are already sorted. If the MDB_AT_MULTI bit of the
+ * attr index is set, the values are stored separately.
+ *
+ * If the MDB_AT_NVALS bit of numvals is set, the attribute also has
+ * normalized values present. (Note - a_numvals is an unsigned int, so this
+ * means it's possible to receive an attribute that we can't encode due
+ * to size overflow. In practice, this should not be an issue.)
+ *
+ * Then the length of each value is listed. If there are normalized values,
+ * their lengths come next. This continues for each attribute. After all
+ * of the lengths for the last attribute, the actual values are copied,
+ * with a NUL terminator after each value.
+ * The buffer is padded to the sizeof(ID). The entire buffer size is
+ * precomputed so that a single malloc can be performed.
+ */
+static int mdb_entry_encode(Operation *op, Entry *e, MDB_val *data, Ecount *eh)
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ ber_len_t i;
+ Attribute *a;
+ unsigned char *ptr;
+ unsigned int *lp, l;
+
+ Debug( LDAP_DEBUG_TRACE, "=> mdb_entry_encode(0x%08lx): %s\n",
+ (long) e->e_id, e->e_dn );
+
+ /* make sure e->e_ocflags is set */
+ if (is_entry_referral(e))
+ ; /* empty */
+
+ lp = (unsigned int *)data->mv_data;
+ *lp++ = eh->nattrs;
+ *lp++ = eh->nvals;
+ *lp++ = (unsigned int)e->e_ocflags;
+ *lp++ = eh->offset;
+ ptr = (unsigned char *)(lp + eh->offset);
+
+ for (a=e->e_attrs; a; a=a->a_next) {
+ if (!a->a_desc->ad_index)
+ return LDAP_UNDEFINED_TYPE;
+ l = mdb->mi_adxs[a->a_desc->ad_index];
+ if (a->a_flags & SLAP_ATTR_BIG_MULTI)
+ l |= MDB_AT_MULTI;
+ if (a->a_flags & SLAP_ATTR_SORTED_VALS)
+ l |= MDB_AT_SORTED;
+ *lp++ = l;
+ l = a->a_numvals;
+ if (a->a_nvals != a->a_vals)
+ l |= MDB_AT_NVALS;
+ *lp++ = l;
+ if (a->a_flags & SLAP_ATTR_BIG_MULTI) {
+ continue;
+ } else {
+ if (a->a_vals) {
+ for (i=0; a->a_vals[i].bv_val; i++);
+ assert( i == a->a_numvals );
+ for (i=0; i<a->a_numvals; i++) {
+ *lp++ = a->a_vals[i].bv_len;
+ memcpy(ptr, a->a_vals[i].bv_val,
+ a->a_vals[i].bv_len);
+ ptr += a->a_vals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ if (a->a_nvals != a->a_vals) {
+ for (i=0; i<a->a_numvals; i++) {
+ *lp++ = a->a_nvals[i].bv_len;
+ memcpy(ptr, a->a_nvals[i].bv_val,
+ a->a_nvals[i].bv_len);
+ ptr += a->a_nvals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ }
+ }
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_entry_encode(0x%08lx): %s\n",
+ (long) e->e_id, e->e_dn );
+
+ return 0;
+}
+
+/* Retrieve an Entry that was stored using entry_encode above.
+ *
+ * Note: everything is stored in a single contiguous block, so
+ * you can not free individual attributes or names from this
+ * structure. Attempting to do so will likely corrupt memory.
+ */
+
+int mdb_entry_decode(Operation *op, MDB_txn *txn, MDB_val *data, ID id, Entry **e)
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ int i, j, nattrs, nvals;
+ int rc;
+ Attribute *a;
+ Entry *x;
+ const char *text;
+ unsigned int *lp = (unsigned int *)data->mv_data;
+ unsigned char *ptr;
+ BerVarray bptr;
+ MDB_cursor *mvc = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> mdb_entry_decode:\n" );
+
+ nattrs = *lp++;
+ nvals = *lp++;
+ x = mdb_entry_alloc(op, nattrs, nvals);
+ x->e_ocflags = *lp++;
+ if (!nvals) {
+ goto done;
+ }
+ a = x->e_attrs;
+ bptr = a->a_vals;
+ i = *lp++;
+ ptr = (unsigned char *)(lp + i);
+
+ for (;nattrs>0; nattrs--) {
+ int have_nval = 0, multi = 0;
+ a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS;
+ i = *lp++;
+ if (i & MDB_AT_SORTED) {
+ i ^= MDB_AT_SORTED;
+ a->a_flags |= SLAP_ATTR_SORTED_VALS;
+ }
+ if (i & MDB_AT_MULTI) {
+ i ^= MDB_AT_MULTI;
+ a->a_flags |= SLAP_ATTR_BIG_MULTI;
+ multi = 1;
+ }
+ if (i > mdb->mi_numads) {
+ rc = mdb_ad_read(mdb, txn);
+ if (rc)
+ goto leave;
+ if (i > mdb->mi_numads) {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_entry_decode: attribute index %d not recognized\n",
+ i );
+ rc = LDAP_OTHER;
+ goto leave;
+ }
+ }
+ a->a_desc = mdb->mi_ads[i];
+ a->a_numvals = *lp++;
+ if (a->a_numvals & MDB_AT_NVALS) {
+ a->a_numvals ^= MDB_AT_NVALS;
+ have_nval = 1;
+ }
+ a->a_vals = bptr;
+ if (multi) {
+ if (!mvc) {
+ rc = mdb_cursor_open(txn, mdb->mi_dbis[MDB_ID2VAL], &mvc);
+ if (rc)
+ goto leave;
+ }
+ i = a->a_numvals;
+ mdb_mval_get(op, mvc, id, a, have_nval);
+ bptr += i + 1;
+ if (have_nval)
+ bptr += i + 1;
+ } else {
+ for (i=0; i<a->a_numvals; i++) {
+ bptr->bv_len = *lp++;
+ bptr->bv_val = (char *)ptr;
+ ptr += bptr->bv_len+1;
+ bptr++;
+ }
+ bptr->bv_val = NULL;
+ bptr->bv_len = 0;
+ bptr++;
+
+ if (have_nval) {
+ a->a_nvals = bptr;
+ for (i=0; i<a->a_numvals; i++) {
+ bptr->bv_len = *lp++;
+ bptr->bv_val = (char *)ptr;
+ ptr += bptr->bv_len+1;
+ bptr++;
+ }
+ bptr->bv_val = NULL;
+ bptr->bv_len = 0;
+ bptr++;
+ } else {
+ a->a_nvals = a->a_vals;
+ }
+ }
+
+ /* FIXME: This is redundant once a sorted entry is saved into the DB */
+ if (( a->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
+ && !(a->a_flags & SLAP_ATTR_SORTED_VALS)) {
+ rc = slap_sort_vals( (Modifications *)a, &text, &j, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ a->a_flags |= SLAP_ATTR_SORTED_VALS;
+ } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ /* should never happen */
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_entry_decode: attributeType %s value #%d provided more than once\n",
+ a->a_desc->ad_cname.bv_val, j );
+ goto leave;
+ }
+ }
+ a->a_next = a+1;
+ a = a->a_next;
+ }
+ a[-1].a_next = NULL;
+done:
+ Debug(LDAP_DEBUG_TRACE, "<= mdb_entry_decode\n" );
+ *e = x;
+ rc = 0;
+
+leave:
+ if (mvc)
+ mdb_cursor_close(mvc);
+ return rc;
+}
diff --git a/servers/slapd/back-mdb/idl.c b/servers/slapd/back-mdb/idl.c
new file mode 100644
index 0000000..c7ffbfa
--- /dev/null
+++ b/servers/slapd/back-mdb/idl.c
@@ -0,0 +1,1293 @@
+/* idl.c - ldap id list handling routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+#include "idl.h"
+
+unsigned int MDB_idl_logn = MDB_IDL_LOGN;
+unsigned int MDB_idl_db_size = 1 << MDB_IDL_LOGN;
+unsigned int MDB_idl_um_size = 1 << (MDB_IDL_LOGN+1);
+unsigned int MDB_idl_db_max = (1 << MDB_IDL_LOGN) - 1;
+unsigned int MDB_idl_um_max = (1 << (MDB_IDL_LOGN+1)) - 1;
+
+#define IDL_MAX(x,y) ( (x) > (y) ? (x) : (y) )
+#define IDL_MIN(x,y) ( (x) < (y) ? (x) : (y) )
+#define IDL_CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
+
+#if IDL_DEBUG > 0
+static void idl_check( ID *ids )
+{
+ if( MDB_IDL_IS_RANGE( ids ) ) {
+ assert( MDB_IDL_RANGE_FIRST(ids) <= MDB_IDL_RANGE_LAST(ids) );
+ } else {
+ ID i;
+ for( i=1; i < ids[0]; i++ ) {
+ assert( ids[i+1] > ids[i] );
+ }
+ }
+}
+
+#if IDL_DEBUG > 1
+static void idl_dump( ID *ids )
+{
+ if( MDB_IDL_IS_RANGE( ids ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "IDL: range ( %ld - %ld )\n",
+ (long) MDB_IDL_RANGE_FIRST( ids ),
+ (long) MDB_IDL_RANGE_LAST( ids ) );
+
+ } else {
+ ID i;
+ Debug( LDAP_DEBUG_ANY, "IDL: size %ld", (long) ids[0] );
+
+ for( i=1; i<=ids[0]; i++ ) {
+ if( i % 16 == 1 ) {
+ Debug( LDAP_DEBUG_ANY, "\n" );
+ }
+ Debug( LDAP_DEBUG_ANY, " %02lx", (long) ids[i] );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "\n" );
+ }
+
+ idl_check( ids );
+}
+#endif /* IDL_DEBUG > 1 */
+#endif /* IDL_DEBUG > 0 */
+
+void mdb_idl_reset()
+{
+ if ( !MDB_idl_logn )
+ MDB_idl_logn = MDB_IDL_LOGN;
+
+ MDB_idl_db_size = 1 << MDB_idl_logn;
+ MDB_idl_um_size = 1 << (MDB_idl_logn+1);
+ MDB_idl_db_max = MDB_idl_db_size - 1;
+ MDB_idl_um_max = MDB_idl_um_size - 1;
+}
+
+unsigned mdb_idl_search( ID *ids, ID id )
+{
+#define IDL_BINARY_SEARCH 1
+#ifdef IDL_BINARY_SEARCH
+ /*
+ * binary search of id in ids
+ * if found, returns position of id
+ * if not found, returns first position greater than id
+ */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = ids[0];
+
+#if IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = IDL_CMP( id, ids[cursor] );
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ return cursor;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ return cursor;
+
+#else
+ /* (reverse) linear search */
+ int i;
+
+#if IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ for( i=ids[0]; i; i-- ) {
+ if( id > ids[i] ) {
+ break;
+ }
+ }
+
+ return i+1;
+#endif
+}
+
+int mdb_idl_insert( ID *ids, ID id )
+{
+ unsigned x;
+
+#if IDL_DEBUG > 1
+ Debug( LDAP_DEBUG_ANY, "insert: %04lx at %d\n", (long) id, x );
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ if (MDB_IDL_IS_RANGE( ids )) {
+ /* if already in range, treat as a dup */
+ if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids))
+ return -1;
+ if (id < MDB_IDL_RANGE_FIRST(ids))
+ ids[1] = id;
+ else if (id > MDB_IDL_RANGE_LAST(ids))
+ ids[2] = id;
+ return 0;
+ }
+
+ x = mdb_idl_search( ids, id );
+ assert( x > 0 );
+
+ if( x < 1 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if ( x <= ids[0] && ids[x] == id ) {
+ /* duplicate */
+ return -1;
+ }
+
+ if ( ++ids[0] >= MDB_idl_db_max ) {
+ if( id < ids[1] ) {
+ ids[1] = id;
+ ids[2] = ids[ids[0]-1];
+ } else if ( ids[ids[0]-1] < id ) {
+ ids[2] = id;
+ } else {
+ ids[2] = ids[ids[0]-1];
+ }
+ ids[0] = NOID;
+
+ } else {
+ /* insert id */
+ AC_MEMCPY( &ids[x+1], &ids[x], (ids[0]-x) * sizeof(ID) );
+ ids[x] = id;
+ }
+
+#if IDL_DEBUG > 1
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ return 0;
+}
+
+static int mdb_idl_delete( ID *ids, ID id )
+{
+ unsigned x;
+
+#if IDL_DEBUG > 1
+ Debug( LDAP_DEBUG_ANY, "delete: %04lx at %d\n", (long) id, x );
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ if (MDB_IDL_IS_RANGE( ids )) {
+ /* If deleting a range boundary, adjust */
+ if ( ids[1] == id )
+ ids[1]++;
+ else if ( ids[2] == id )
+ ids[2]--;
+ /* deleting from inside a range is a no-op */
+
+ /* If the range has collapsed, re-adjust */
+ if ( ids[1] > ids[2] )
+ ids[0] = 0;
+ else if ( ids[1] == ids[2] )
+ ids[1] = 1;
+ return 0;
+ }
+
+ x = mdb_idl_search( ids, id );
+ assert( x > 0 );
+
+ if( x <= 0 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if( x > ids[0] || ids[x] != id ) {
+ /* not found */
+ return -1;
+
+ } else if ( --ids[0] == 0 ) {
+ if( x != 1 ) {
+ return -3;
+ }
+
+ } else {
+ AC_MEMCPY( &ids[x], &ids[x+1], (1+ids[0]-x) * sizeof(ID) );
+ }
+
+#if IDL_DEBUG > 1
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ return 0;
+}
+
+static char *
+mdb_show_key(
+ char *buf,
+ void *val,
+ size_t len )
+{
+ if ( len == 4 /* LUTIL_HASH_BYTES */ ) {
+ unsigned char *c = val;
+ sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] );
+ return buf;
+ } else {
+ return val;
+ }
+}
+
+int
+mdb_idl_fetch_key(
+ BackendDB *be,
+ MDB_txn *txn,
+ MDB_dbi dbi,
+ MDB_val *key,
+ ID *ids,
+ MDB_cursor **saved_cursor,
+ int get_flag )
+{
+ MDB_val data, key2, *kptr;
+ MDB_cursor *cursor;
+ ID *i;
+ size_t len;
+ int rc;
+ MDB_cursor_op opflag;
+
+ char keybuf[16];
+
+ Debug( LDAP_DEBUG_ARGS,
+ "mdb_idl_fetch_key: %s\n",
+ mdb_show_key( keybuf, key->mv_data, key->mv_size ) );
+
+ assert( ids != NULL );
+
+ if ( saved_cursor && *saved_cursor ) {
+ opflag = MDB_NEXT;
+ } else if ( get_flag == LDAP_FILTER_GE ) {
+ opflag = MDB_SET_RANGE;
+ } else if ( get_flag == LDAP_FILTER_LE ) {
+ opflag = MDB_FIRST;
+ } else {
+ opflag = MDB_SET;
+ }
+
+ /* If we're not reusing an existing cursor, get a new one */
+ if( opflag != MDB_NEXT ) {
+ rc = mdb_cursor_open( txn, dbi, &cursor );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: "
+ "cursor failed: %s (%d)\n", mdb_strerror(rc), rc );
+ return rc;
+ }
+ } else {
+ cursor = *saved_cursor;
+ }
+
+ /* If this is a LE lookup, save original key so we can determine
+ * when to stop. If this is a GE lookup, save the key since it
+ * will be overwritten.
+ */
+ if ( get_flag == LDAP_FILTER_LE || get_flag == LDAP_FILTER_GE ) {
+ key2.mv_data = keybuf;
+ key2.mv_size = key->mv_size;
+ AC_MEMCPY( keybuf, key->mv_data, key->mv_size );
+ kptr = &key2;
+ } else {
+ kptr = key;
+ }
+ len = key->mv_size;
+ rc = mdb_cursor_get( cursor, kptr, &data, opflag );
+
+ /* skip presence key on range inequality lookups */
+ while (rc == 0 && kptr->mv_size != len) {
+ rc = mdb_cursor_get( cursor, kptr, &data, MDB_NEXT_NODUP );
+ }
+ /* If we're doing a LE compare and the new key is greater than
+ * our search key, we're done
+ */
+ if (rc == 0 && get_flag == LDAP_FILTER_LE && memcmp( kptr->mv_data,
+ key->mv_data, key->mv_size ) > 0 ) {
+ rc = MDB_NOTFOUND;
+ }
+ if (rc == 0) {
+ i = ids+1;
+ rc = mdb_cursor_get( cursor, key, &data, MDB_GET_MULTIPLE );
+ while (rc == 0) {
+ memcpy( i, data.mv_data, data.mv_size );
+ i += data.mv_size / sizeof(ID);
+ rc = mdb_cursor_get( cursor, key, &data, MDB_NEXT_MULTIPLE );
+ }
+ if ( rc == MDB_NOTFOUND ) rc = 0;
+ ids[0] = i - &ids[1];
+ /* On disk, a range is denoted by 0 in the first element */
+ if (ids[1] == 0) {
+ if (ids[0] != MDB_IDL_RANGE_SIZE) {
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: "
+ "range size mismatch: expected %d, got %ld\n",
+ MDB_IDL_RANGE_SIZE, ids[0] );
+ mdb_cursor_close( cursor );
+ return -1;
+ }
+ MDB_IDL_RANGE( ids, ids[2], ids[3] );
+ }
+ data.mv_size = MDB_IDL_SIZEOF(ids);
+ }
+
+ if ( saved_cursor && rc == 0 ) {
+ if ( !*saved_cursor )
+ *saved_cursor = cursor;
+ }
+ else
+ mdb_cursor_close( cursor );
+
+ if( rc == MDB_NOTFOUND ) {
+ return rc;
+
+ } else if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: "
+ "get failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ return rc;
+
+ } else if ( data.mv_size == 0 || data.mv_size % sizeof( ID ) ) {
+ /* size not multiple of ID size */
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: "
+ "odd size: expected %ld multiple, got %ld\n",
+ (long) sizeof( ID ), (long) data.mv_size );
+ return -1;
+
+ } else if ( data.mv_size != MDB_IDL_SIZEOF(ids) ) {
+ /* size mismatch */
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_fetch_key: "
+ "get size mismatch: expected %ld, got %ld\n",
+ (long) ((1 + ids[0]) * sizeof( ID )), (long) data.mv_size );
+ return -1;
+ }
+
+ return rc;
+}
+
+int
+mdb_idl_insert_keys(
+ BackendDB *be,
+ MDB_cursor *cursor,
+ struct berval *keys,
+ ID id )
+{
+ struct mdb_info *mdb = be->be_private;
+ MDB_val key, data;
+ ID lo, hi, *i;
+ char *err;
+ int rc = 0, k;
+ unsigned int flag = MDB_NODUPDATA;
+#ifndef MISALIGNED_OK
+ int kbuf[2];
+#endif
+
+ {
+ char buf[16];
+ Debug( LDAP_DEBUG_ARGS,
+ "mdb_idl_insert_keys: %lx %s\n",
+ (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ) );
+ }
+
+ assert( id != NOID );
+
+#ifndef MISALIGNED_OK
+ if (keys[0].bv_len & ALIGNER)
+ kbuf[1] = 0;
+#endif
+ for ( k=0; keys[k].bv_val; k++ ) {
+ /* Fetch the first data item for this key, to see if it
+ * exists and if it's a range.
+ */
+#ifndef MISALIGNED_OK
+ if (keys[k].bv_len & ALIGNER) {
+ key.mv_size = sizeof(kbuf);
+ key.mv_data = kbuf;
+ memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len);
+ } else
+#endif
+ {
+ key.mv_size = keys[k].bv_len;
+ key.mv_data = keys[k].bv_val;
+ }
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ err = "c_get";
+ if ( rc == 0 ) {
+ i = data.mv_data;
+ memcpy(&lo, data.mv_data, sizeof(ID));
+ if ( lo != 0 ) {
+ /* not a range, count the number of items */
+ size_t count;
+ rc = mdb_cursor_count( cursor, &count );
+ if ( rc != 0 ) {
+ err = "c_count";
+ goto fail;
+ }
+ if ( count >= MDB_idl_db_max ) {
+ /* No room, convert to a range */
+ lo = *i;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_LAST_DUP );
+ if ( rc != 0 && rc != MDB_NOTFOUND ) {
+ err = "c_get last_dup";
+ goto fail;
+ }
+ i = data.mv_data;
+ hi = *i;
+ /* Update hi/lo if needed */
+ if ( id < lo ) {
+ lo = id;
+ } else if ( id > hi ) {
+ hi = id;
+ }
+ /* delete the old key */
+ rc = mdb_cursor_del( cursor, MDB_NODUPDATA );
+ if ( rc != 0 ) {
+ err = "c_del dups";
+ goto fail;
+ }
+ /* Store the range */
+ data.mv_size = sizeof(ID);
+ data.mv_data = &id;
+ id = 0;
+ rc = mdb_cursor_put( cursor, &key, &data, 0 );
+ if ( rc != 0 ) {
+ err = "c_put range";
+ goto fail;
+ }
+ id = lo;
+ rc = mdb_cursor_put( cursor, &key, &data, 0 );
+ if ( rc != 0 ) {
+ err = "c_put lo";
+ goto fail;
+ }
+ id = hi;
+ rc = mdb_cursor_put( cursor, &key, &data, 0 );
+ if ( rc != 0 ) {
+ err = "c_put hi";
+ goto fail;
+ }
+ } else {
+ /* There's room, just store it */
+ if (id == mdb->mi_nextid)
+ flag |= MDB_APPENDDUP;
+ goto put1;
+ }
+ } else {
+ /* It's a range, see if we need to rewrite
+ * the boundaries
+ */
+ lo = i[1];
+ hi = i[2];
+ if ( id < lo || id > hi ) {
+ /* position on lo */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ if ( rc != 0 ) {
+ err = "c_get lo";
+ goto fail;
+ }
+ if ( id > hi ) {
+ /* position on hi */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ if ( rc != 0 ) {
+ err = "c_get hi";
+ goto fail;
+ }
+ }
+ data.mv_size = sizeof(ID);
+ data.mv_data = &id;
+ /* Replace the current lo/hi */
+ rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT );
+ if ( rc != 0 ) {
+ err = "c_put lo/hi";
+ goto fail;
+ }
+ }
+ }
+ } else if ( rc == MDB_NOTFOUND ) {
+ flag &= ~MDB_APPENDDUP;
+put1: data.mv_data = &id;
+ data.mv_size = sizeof(ID);
+ rc = mdb_cursor_put( cursor, &key, &data, flag );
+ /* Don't worry if it's already there */
+ if ( rc == MDB_KEYEXIST )
+ rc = 0;
+ if ( rc ) {
+ err = "c_put id";
+ goto fail;
+ }
+ } else {
+ /* initial c_get failed, nothing was done */
+fail:
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_insert_keys: "
+ "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc );
+ break;
+ }
+ }
+ return rc;
+}
+
+int
+mdb_idl_delete_keys(
+ BackendDB *be,
+ MDB_cursor *cursor,
+ struct berval *keys,
+ ID id )
+{
+ int rc = 0, k;
+ MDB_val key, data;
+ ID lo, hi, tmp, *i;
+ char *err;
+#ifndef MISALIGNED_OK
+ int kbuf[2];
+#endif
+
+ {
+ char buf[16];
+ Debug( LDAP_DEBUG_ARGS,
+ "mdb_idl_delete_keys: %lx %s\n",
+ (long) id, mdb_show_key( buf, keys->bv_val, keys->bv_len ) );
+ }
+ assert( id != NOID );
+
+#ifndef MISALIGNED_OK
+ if (keys[0].bv_len & ALIGNER)
+ kbuf[1] = 0;
+#endif
+ for ( k=0; keys[k].bv_val; k++) {
+ /* Fetch the first data item for this key, to see if it
+ * exists and if it's a range.
+ */
+#ifndef MISALIGNED_OK
+ if (keys[k].bv_len & ALIGNER) {
+ key.mv_size = sizeof(kbuf);
+ key.mv_data = kbuf;
+ memcpy(key.mv_data, keys[k].bv_val, keys[k].bv_len);
+ } else
+#endif
+ {
+ key.mv_size = keys[k].bv_len;
+ key.mv_data = keys[k].bv_val;
+ }
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ err = "c_get";
+ if ( rc == 0 ) {
+ memcpy( &tmp, data.mv_data, sizeof(ID) );
+ i = data.mv_data;
+ if ( tmp != 0 ) {
+ /* Not a range, just delete it */
+ data.mv_data = &id;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_GET_BOTH );
+ if ( rc != 0 ) {
+ err = "c_get id";
+ goto fail;
+ }
+ rc = mdb_cursor_del( cursor, 0 );
+ if ( rc != 0 ) {
+ err = "c_del id";
+ goto fail;
+ }
+ } else {
+ /* It's a range, see if we need to rewrite
+ * the boundaries
+ */
+ lo = i[1];
+ hi = i[2];
+ if ( id == lo || id == hi ) {
+ ID lo2 = lo, hi2 = hi;
+ if ( id == lo ) {
+ lo2++;
+ } else if ( id == hi ) {
+ hi2--;
+ }
+ if ( lo2 >= hi2 ) {
+ /* The range has collapsed... */
+ /* delete the range marker */
+ rc = mdb_cursor_del( cursor, 0 );
+ if ( rc != 0 ) {
+ err = "c_del dup1";
+ goto fail;
+ }
+ /* skip past deleted marker */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ if ( rc != 0 ) {
+ err = "c_get dup1";
+ goto fail;
+ }
+ /* delete the requested id */
+ if ( id == hi ) {
+ /* skip lo */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ if ( rc != 0 ) {
+ err = "c_get dup2";
+ goto fail;
+ }
+ }
+ rc = mdb_cursor_del( cursor, 0 );
+ if ( rc != 0 ) {
+ err = "c_del dup2";
+ goto fail;
+ }
+ } else {
+ /* position on lo */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ if ( id == lo )
+ data.mv_data = &lo2;
+ else {
+ /* position on hi */
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT_DUP );
+ data.mv_data = &hi2;
+ }
+ /* Replace the current lo/hi */
+ data.mv_size = sizeof(ID);
+ rc = mdb_cursor_put( cursor, &key, &data, MDB_CURRENT );
+ if ( rc != 0 ) {
+ err = "c_put lo/hi";
+ goto fail;
+ }
+ }
+ }
+ }
+ } else {
+ /* initial c_get failed, nothing was done */
+fail:
+ if ( rc == MDB_NOTFOUND )
+ rc = 0;
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "=> mdb_idl_delete_key: "
+ "%s failed: %s (%d)\n", err, mdb_strerror(rc), rc );
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/*
+ * idl_intersection - return a = a intersection b
+ */
+int
+mdb_idl_intersection(
+ ID *a,
+ ID *b )
+{
+ ID ida, idb;
+ ID idmax, idmin;
+ ID cursora = 0, cursorb = 0, cursorc;
+ int swap = 0;
+
+ if ( MDB_IDL_IS_ZERO( a ) || MDB_IDL_IS_ZERO( b ) ) {
+ a[0] = 0;
+ return 0;
+ }
+
+ idmin = IDL_MAX( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) );
+ idmax = IDL_MIN( MDB_IDL_LAST(a), MDB_IDL_LAST(b) );
+ if ( idmin > idmax ) {
+ a[0] = 0;
+ return 0;
+ } else if ( idmin == idmax ) {
+ a[0] = 1;
+ a[1] = idmin;
+ return 0;
+ }
+
+ if ( MDB_IDL_IS_RANGE( a ) ) {
+ if ( MDB_IDL_IS_RANGE(b) ) {
+ /* If both are ranges, just shrink the boundaries */
+ a[1] = idmin;
+ a[2] = idmax;
+ return 0;
+ } else {
+ /* Else swap so that b is the range, a is a list */
+ ID *tmp = a;
+ a = b;
+ b = tmp;
+ swap = 1;
+ }
+ }
+
+ /* If a range completely covers the list, the result is
+ * just the list.
+ */
+ if ( MDB_IDL_IS_RANGE( b )
+ && MDB_IDL_RANGE_FIRST( b ) <= MDB_IDL_FIRST( a )
+ && MDB_IDL_RANGE_LAST( b ) >= MDB_IDL_LLAST( a ) ) {
+ goto done;
+ }
+
+ /* Fine, do the intersection one element at a time.
+ * First advance to idmin in both IDLs.
+ */
+ cursora = cursorb = idmin;
+ ida = mdb_idl_first( a, &cursora );
+ idb = mdb_idl_first( b, &cursorb );
+ cursorc = 0;
+
+ while( ida <= idmax || idb <= idmax ) {
+ if( ida == idb ) {
+ a[++cursorc] = ida;
+ ida = mdb_idl_next( a, &cursora );
+ idb = mdb_idl_next( b, &cursorb );
+ } else if ( ida < idb ) {
+ ida = mdb_idl_next( a, &cursora );
+ } else {
+ idb = mdb_idl_next( b, &cursorb );
+ }
+ }
+ a[0] = cursorc;
+done:
+ if (swap)
+ MDB_IDL_CPY( b, a );
+
+ return 0;
+}
+
+
+/*
+ * idl_union - return a = a union b
+ */
+int
+mdb_idl_union(
+ ID *a,
+ ID *b )
+{
+ ID ida, idb;
+ ID cursora = 0, cursorb = 0, cursorc;
+
+ if ( MDB_IDL_IS_ZERO( b ) ) {
+ return 0;
+ }
+
+ if ( MDB_IDL_IS_ZERO( a ) ) {
+ MDB_IDL_CPY( a, b );
+ return 0;
+ }
+
+ if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) ) {
+over: ida = IDL_MIN( MDB_IDL_FIRST(a), MDB_IDL_FIRST(b) );
+ idb = IDL_MAX( MDB_IDL_LAST(a), MDB_IDL_LAST(b) );
+ a[0] = NOID;
+ a[1] = ida;
+ a[2] = idb;
+ return 0;
+ }
+
+ ida = mdb_idl_first( a, &cursora );
+ idb = mdb_idl_first( b, &cursorb );
+
+ cursorc = b[0];
+
+ /* The distinct elements of a are cat'd to b */
+ while( ida != NOID || idb != NOID ) {
+ if ( ida < idb ) {
+ if( ++cursorc > MDB_idl_um_max ) {
+ goto over;
+ }
+ b[cursorc] = ida;
+ ida = mdb_idl_next( a, &cursora );
+
+ } else {
+ if ( ida == idb )
+ ida = mdb_idl_next( a, &cursora );
+ idb = mdb_idl_next( b, &cursorb );
+ }
+ }
+
+ /* b is copied back to a in sorted order */
+ a[0] = cursorc;
+ cursora = 1;
+ cursorb = 1;
+ cursorc = b[0]+1;
+ while (cursorb <= b[0] || cursorc <= a[0]) {
+ if (cursorc > a[0])
+ idb = NOID;
+ else
+ idb = b[cursorc];
+ if (cursorb <= b[0] && b[cursorb] < idb)
+ a[cursora++] = b[cursorb++];
+ else {
+ a[cursora++] = idb;
+ cursorc++;
+ }
+ }
+
+ return 0;
+}
+
+
+#if 0
+/*
+ * mdb_idl_notin - return a intersection ~b (or a minus b)
+ */
+int
+mdb_idl_notin(
+ ID *a,
+ ID *b,
+ ID *ids )
+{
+ ID ida, idb;
+ ID cursora = 0, cursorb = 0;
+
+ if( MDB_IDL_IS_ZERO( a ) ||
+ MDB_IDL_IS_ZERO( b ) ||
+ MDB_IDL_IS_RANGE( b ) )
+ {
+ MDB_IDL_CPY( ids, a );
+ return 0;
+ }
+
+ if( MDB_IDL_IS_RANGE( a ) ) {
+ MDB_IDL_CPY( ids, a );
+ return 0;
+ }
+
+ ida = mdb_idl_first( a, &cursora ),
+ idb = mdb_idl_first( b, &cursorb );
+
+ ids[0] = 0;
+
+ while( ida != NOID ) {
+ if ( idb == NOID ) {
+ /* we could shortcut this */
+ ids[++ids[0]] = ida;
+ ida = mdb_idl_next( a, &cursora );
+
+ } else if ( ida < idb ) {
+ ids[++ids[0]] = ida;
+ ida = mdb_idl_next( a, &cursora );
+
+ } else if ( ida > idb ) {
+ idb = mdb_idl_next( b, &cursorb );
+
+ } else {
+ ida = mdb_idl_next( a, &cursora );
+ idb = mdb_idl_next( b, &cursorb );
+ }
+ }
+
+ return 0;
+}
+#endif
+
+ID mdb_idl_first( ID *ids, ID *cursor )
+{
+ ID pos;
+
+ if ( ids[0] == 0 ) {
+ *cursor = NOID;
+ return NOID;
+ }
+
+ if ( MDB_IDL_IS_RANGE( ids ) ) {
+ if( *cursor < ids[1] ) {
+ *cursor = ids[1];
+ }
+ return *cursor;
+ }
+
+ if ( *cursor == 0 )
+ pos = 1;
+ else
+ pos = mdb_idl_search( ids, *cursor );
+
+ if( pos > ids[0] ) {
+ return NOID;
+ }
+
+ *cursor = pos;
+ return ids[pos];
+}
+
+ID mdb_idl_next( ID *ids, ID *cursor )
+{
+ if ( MDB_IDL_IS_RANGE( ids ) ) {
+ if( ids[2] < ++(*cursor) ) {
+ return NOID;
+ }
+ return *cursor;
+ }
+
+ if ( ++(*cursor) <= ids[0] ) {
+ return ids[*cursor];
+ }
+
+ return NOID;
+}
+
+/* Add one ID to an unsorted list. We ensure that the first element is the
+ * minimum and the last element is the maximum, for fast range compaction.
+ * this means IDLs up to length 3 are always sorted...
+ */
+int mdb_idl_append_one( ID *ids, ID id )
+{
+ if (MDB_IDL_IS_RANGE( ids )) {
+ /* if already in range, treat as a dup */
+ if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids))
+ return -1;
+ if (id < MDB_IDL_RANGE_FIRST(ids))
+ ids[1] = id;
+ else if (id > MDB_IDL_RANGE_LAST(ids))
+ ids[2] = id;
+ return 0;
+ }
+ if ( ids[0] ) {
+ ID tmp;
+
+ if (id < ids[1]) {
+ tmp = ids[1];
+ ids[1] = id;
+ id = tmp;
+ }
+ if ( ids[0] > 1 && id < ids[ids[0]] ) {
+ tmp = ids[ids[0]];
+ ids[ids[0]] = id;
+ id = tmp;
+ }
+ }
+ ids[0]++;
+ if ( ids[0] >= MDB_idl_um_max ) {
+ ids[0] = NOID;
+ ids[2] = id;
+ } else {
+ ids[ids[0]] = id;
+ }
+ return 0;
+}
+
+/* Append sorted list b to sorted list a. The result is unsorted but
+ * a[1] is the min of the result and a[a[0]] is the max.
+ */
+int mdb_idl_append( ID *a, ID *b )
+{
+ ID ida, idb, tmp, swap = 0;
+
+ if ( MDB_IDL_IS_ZERO( b ) ) {
+ return 0;
+ }
+
+ if ( MDB_IDL_IS_ZERO( a ) ) {
+ MDB_IDL_CPY( a, b );
+ return 0;
+ }
+
+ ida = MDB_IDL_LAST( a );
+ idb = MDB_IDL_LAST( b );
+ if ( MDB_IDL_IS_RANGE( a ) || MDB_IDL_IS_RANGE(b) ||
+ a[0] + b[0] >= MDB_idl_um_max ) {
+ a[2] = IDL_MAX( ida, idb );
+ a[1] = IDL_MIN( a[1], b[1] );
+ a[0] = NOID;
+ return 0;
+ }
+
+ if ( b[0] > 1 && ida > idb ) {
+ swap = idb;
+ a[a[0]] = idb;
+ b[b[0]] = ida;
+ }
+
+ if ( b[1] < a[1] ) {
+ tmp = a[1];
+ a[1] = b[1];
+ } else {
+ tmp = b[1];
+ }
+ a[0]++;
+ a[a[0]] = tmp;
+
+ if ( b[0] > 1 ) {
+ int i = b[0] - 1;
+ AC_MEMCPY(a+a[0]+1, b+2, i * sizeof(ID));
+ a[0] += i;
+ }
+ if ( swap ) {
+ b[b[0]] = swap;
+ }
+ return 0;
+}
+
+#if 1
+
+/* Quicksort + Insertion sort for small arrays */
+
+#define SMALL 8
+#define SWAP(a,b) itmp=(a);(a)=(b);(b)=itmp
+
+void
+mdb_idl_sort( ID *ids, ID *tmp )
+{
+ int *istack = (int *)tmp; /* Private stack, not used by caller */
+ int i,j,k,l,ir,jstack;
+ ID a, itmp;
+
+ if ( MDB_IDL_IS_RANGE( ids ))
+ return;
+
+ ir = ids[0];
+ l = 1;
+ jstack = 0;
+ for(;;) {
+ if (ir - l < SMALL) { /* Insertion sort */
+ for (j=l+1;j<=ir;j++) {
+ a = ids[j];
+ for (i=j-1;i>=1;i--) {
+ if (ids[i] <= a) break;
+ ids[i+1] = ids[i];
+ }
+ ids[i+1] = a;
+ }
+ if (jstack == 0) break;
+ ir = istack[jstack--];
+ l = istack[jstack--];
+ } else {
+ k = (l + ir) >> 1; /* Choose median of left, center, right */
+ SWAP(ids[k], ids[l+1]);
+ if (ids[l] > ids[ir]) {
+ SWAP(ids[l], ids[ir]);
+ }
+ if (ids[l+1] > ids[ir]) {
+ SWAP(ids[l+1], ids[ir]);
+ }
+ if (ids[l] > ids[l+1]) {
+ SWAP(ids[l], ids[l+1]);
+ }
+ i = l+1;
+ j = ir;
+ a = ids[l+1];
+ for(;;) {
+ do i++; while(ids[i] < a);
+ do j--; while(ids[j] > a);
+ if (j < i) break;
+ SWAP(ids[i],ids[j]);
+ }
+ ids[l+1] = ids[j];
+ ids[j] = a;
+ jstack += 2;
+ if (ir-i+1 >= j-l) {
+ istack[jstack] = ir;
+ istack[jstack-1] = i;
+ ir = j-1;
+ } else {
+ istack[jstack] = j-1;
+ istack[jstack-1] = l;
+ l = i;
+ }
+ }
+ }
+}
+
+#else
+
+/* 8 bit Radix sort + insertion sort
+ *
+ * based on code from http://www.cubic.org/docs/radix.htm
+ * with improvements by ebackes@symas.com and hyc@symas.com
+ *
+ * This code is O(n) but has a relatively high constant factor. For lists
+ * up to ~50 Quicksort is slightly faster; up to ~100 they are even.
+ * Much faster than quicksort for lists longer than ~100. Insertion
+ * sort is actually superior for lists <50.
+ */
+
+#define BUCKETS (1<<8)
+#define SMALL 50
+
+void
+mdb_idl_sort( ID *ids, ID *tmp )
+{
+ int count, soft_limit, phase = 0, size = ids[0];
+ ID *idls[2];
+ unsigned char *maxv = (unsigned char *)&ids[size];
+
+ if ( MDB_IDL_IS_RANGE( ids ))
+ return;
+
+ /* Use insertion sort for small lists */
+ if ( size <= SMALL ) {
+ int i,j;
+ ID a;
+
+ for (j=1;j<=size;j++) {
+ a = ids[j];
+ for (i=j-1;i>=1;i--) {
+ if (ids[i] <= a) break;
+ ids[i+1] = ids[i];
+ }
+ ids[i+1] = a;
+ }
+ return;
+ }
+
+ tmp[0] = size;
+ idls[0] = ids;
+ idls[1] = tmp;
+
+#if BYTE_ORDER == BIG_ENDIAN
+ for (soft_limit = 0; !maxv[soft_limit]; soft_limit++);
+#else
+ for (soft_limit = sizeof(ID)-1; !maxv[soft_limit]; soft_limit--);
+#endif
+
+ for (
+#if BYTE_ORDER == BIG_ENDIAN
+ count = sizeof(ID)-1; count >= soft_limit; --count
+#else
+ count = 0; count <= soft_limit; ++count
+#endif
+ ) {
+ unsigned int num[BUCKETS], * np, n, sum;
+ int i;
+ ID *sp, *source, *dest;
+ unsigned char *bp, *source_start;
+
+ source = idls[phase]+1;
+ dest = idls[phase^1]+1;
+ source_start = ((unsigned char *) source) + count;
+
+ np = num;
+ for ( i = BUCKETS; i > 0; --i ) *np++ = 0;
+
+ /* count occurrences of every byte value */
+ bp = source_start;
+ for ( i = size; i > 0; --i, bp += sizeof(ID) )
+ num[*bp]++;
+
+ /* transform count into index by summing elements and storing
+ * into same array
+ */
+ sum = 0;
+ np = num;
+ for ( i = BUCKETS; i > 0; --i ) {
+ n = *np;
+ *np++ = sum;
+ sum += n;
+ }
+
+ /* fill dest with the right values in the right place */
+ bp = source_start;
+ sp = source;
+ for ( i = size; i > 0; --i, bp += sizeof(ID) ) {
+ np = num + *bp;
+ dest[*np] = *sp++;
+ ++(*np);
+ }
+ phase ^= 1;
+ }
+
+ /* copy back from temp if needed */
+ if ( phase ) {
+ ids++; tmp++;
+ for ( count = 0; count < size; ++count )
+ *ids++ = *tmp++;
+ }
+}
+#endif /* Quick vs Radix */
+
+unsigned mdb_id2l_search( ID2L ids, ID id )
+{
+ /*
+ * binary search of id in ids
+ * if found, returns position of id
+ * if not found, returns first position greater than id
+ */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = ids[0].mid;
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = IDL_CMP( id, ids[cursor].mid );
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ return cursor;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ return cursor;
+}
+
+int mdb_id2l_insert( ID2L ids, ID2 *id )
+{
+ unsigned x, i;
+
+ x = mdb_id2l_search( ids, id->mid );
+ assert( x > 0 );
+
+ if( x < 1 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
+ /* duplicate */
+ return -1;
+ }
+
+ if ( ids[0].mid >= MDB_idl_um_max ) {
+ /* too big */
+ return -2;
+
+ } else {
+ /* insert id */
+ ids[0].mid++;
+ for (i=ids[0].mid; i>x; i--)
+ ids[i] = ids[i-1];
+ ids[x] = *id;
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-mdb/idl.h b/servers/slapd/back-mdb/idl.h
new file mode 100644
index 0000000..1405571
--- /dev/null
+++ b/servers/slapd/back-mdb/idl.h
@@ -0,0 +1,118 @@
+/* idl.h - ldap mdb back-end ID list header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _MDB_IDL_H_
+#define _MDB_IDL_H_
+
+/* IDL sizes - likely should be even bigger
+ * limiting factors: sizeof(ID), thread stack size
+ */
+#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
+extern unsigned int MDB_idl_logn;
+extern unsigned int MDB_idl_db_size;
+extern unsigned int MDB_idl_um_size;
+extern unsigned int MDB_idl_db_max;
+extern unsigned int MDB_idl_um_max;
+
+#define MDB_IDL_IS_RANGE(ids) ((ids)[0] == NOID)
+#define MDB_IDL_RANGE_SIZE (3)
+#define MDB_IDL_RANGE_SIZEOF (MDB_IDL_RANGE_SIZE * sizeof(ID))
+#define MDB_IDL_SIZEOF(ids) ((MDB_IDL_IS_RANGE(ids) \
+ ? MDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID))
+
+#define MDB_IDL_RANGE_FIRST(ids) ((ids)[1])
+#define MDB_IDL_RANGE_LAST(ids) ((ids)[2])
+
+#define MDB_IDL_RANGE( ids, f, l ) \
+ do { \
+ (ids)[0] = NOID; \
+ (ids)[1] = (f); \
+ (ids)[2] = (l); \
+ } while(0)
+
+#define MDB_IDL_ZERO(ids) \
+ do { \
+ (ids)[0] = 0; \
+ (ids)[1] = 0; \
+ (ids)[2] = 0; \
+ } while(0)
+
+#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
+#define MDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \
+ && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] )
+
+#define MDB_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, MDB_IDL_SIZEOF( src ) ))
+
+#define MDB_IDL_ID( mdb, ids, id ) MDB_IDL_RANGE( ids, id, NOID )
+#define MDB_IDL_ALL( ids ) MDB_IDL_RANGE( ids, 1, NOID )
+
+#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
+#define MDB_IDL_LLAST( ids ) ( (ids)[(ids)[0]] )
+#define MDB_IDL_LAST( ids ) ( MDB_IDL_IS_RANGE(ids) \
+ ? (ids)[2] : (ids)[(ids)[0]] )
+
+#define MDB_IDL_N( ids ) ( MDB_IDL_IS_RANGE(ids) \
+ ? ((ids)[2]-(ids)[1])+1 : (ids)[0] )
+
+ /** An ID2 is an ID/value pair.
+ */
+typedef struct ID2 {
+ ID mid; /**< The ID */
+ MDB_val mval; /**< The value */
+} ID2;
+
+ /** An ID2L is an ID2 List, a sorted array of ID2s.
+ * The first element's \b mid member is a count of how many actual
+ * elements are in the array. The \b mptr member of the first element is unused.
+ * The array is sorted in ascending order by \b mid.
+ */
+typedef ID2 *ID2L;
+
+typedef struct IdScopes {
+ MDB_txn *mt;
+ MDB_cursor *mc;
+ ID id;
+ ID2L scopes;
+ ID2L sctmp;
+ int numrdns;
+ int nscope;
+ int oscope;
+ struct berval rdns[MAXRDNS];
+ struct berval nrdns[MAXRDNS];
+} IdScopes;
+
+LDAP_BEGIN_DECL
+ /** Reset IDL params after changing logn */
+void mdb_idl_reset();
+
+
+ /** Search for an ID in an ID2L.
+ * @param[in] ids The ID2L to search.
+ * @param[in] id The ID to search for.
+ * @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
+ */
+unsigned mdb_id2l_search( ID2L ids, ID id );
+
+
+ /** Insert an ID2 into a ID2L.
+ * @param[in,out] ids The ID2L to insert into.
+ * @param[in] id The ID2 to insert.
+ * @return 0 on success, -1 if the ID was already present in the MIDL2.
+ */
+int mdb_id2l_insert( ID2L ids, ID2 *id );
+LDAP_END_DECL
+
+#endif
diff --git a/servers/slapd/back-mdb/index.c b/servers/slapd/back-mdb/index.c
new file mode 100644
index 0000000..fa9006c
--- /dev/null
+++ b/servers/slapd/back-mdb/index.c
@@ -0,0 +1,577 @@
+/* index.c - routines for dealing with attribute indexes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-mdb.h"
+#include "lutil_hash.h"
+
+static char presence_keyval[] = {0,0,0,0,0};
+static struct berval presence_key[2] = {BER_BVC(presence_keyval), BER_BVNULL};
+
+AttrInfo *mdb_index_mask(
+ Backend *be,
+ AttributeDescription *desc,
+ struct berval *atname )
+{
+ AttributeType *at;
+ AttrInfo *ai = mdb_attr_mask( be->be_private, desc );
+
+ if( ai ) {
+ *atname = desc->ad_cname;
+ return ai;
+ }
+
+ /* If there is a tagging option, did we ever index the base
+ * type? If so, check for mask, otherwise it's not there.
+ */
+ if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) {
+ /* has tagging option */
+ ai = mdb_attr_mask( be->be_private, desc->ad_type->sat_ad );
+
+ if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOTAGS ) ) {
+ *atname = desc->ad_type->sat_cname;
+ return ai;
+ }
+ }
+
+ /* see if supertype defined mask for its subtypes */
+ for( at = desc->ad_type; at != NULL ; at = at->sat_sup ) {
+ /* If no AD, we've never indexed this type */
+ if ( !at->sat_ad ) continue;
+
+ ai = mdb_attr_mask( be->be_private, at->sat_ad );
+
+ if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOSUBTYPES ) ) {
+ *atname = at->sat_cname;
+ return ai;
+ }
+ }
+
+ return 0;
+}
+
+/* This function is only called when evaluating search filters.
+ */
+int mdb_index_param(
+ Backend *be,
+ AttributeDescription *desc,
+ int ftype,
+ MDB_dbi *dbip,
+ slap_mask_t *maskp,
+ struct berval *prefixp )
+{
+ AttrInfo *ai;
+ slap_mask_t mask, type = 0;
+
+ ai = mdb_index_mask( be, desc, prefixp );
+
+ if ( !ai ) {
+#ifdef MDB_MONITOR_IDX
+ switch ( ftype ) {
+ case LDAP_FILTER_PRESENT:
+ type = SLAP_INDEX_PRESENT;
+ break;
+ case LDAP_FILTER_APPROX:
+ type = SLAP_INDEX_APPROX;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ type = SLAP_INDEX_EQUALITY;
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ type = SLAP_INDEX_SUBSTR;
+ break;
+ default:
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ mdb_monitor_idx_add( be->be_private, desc, type );
+#endif /* MDB_MONITOR_IDX */
+
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ mask = ai->ai_indexmask;
+
+ switch( ftype ) {
+ case LDAP_FILTER_PRESENT:
+ type = SLAP_INDEX_PRESENT;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) {
+ *prefixp = presence_key[0];
+ goto done;
+ }
+ break;
+
+ case LDAP_FILTER_APPROX:
+ type = SLAP_INDEX_APPROX;
+ if ( desc->ad_type->sat_approx ) {
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) {
+ goto done;
+ }
+ break;
+ }
+
+ /* Use EQUALITY rule and index for approximate match */
+ /* fall thru */
+
+ case LDAP_FILTER_EQUALITY:
+ type = SLAP_INDEX_EQUALITY;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ type = SLAP_INDEX_SUBSTR;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) {
+ goto done;
+ }
+ break;
+
+ default:
+ return LDAP_OTHER;
+ }
+
+#ifdef MDB_MONITOR_IDX
+ mdb_monitor_idx_add( be->be_private, desc, type );
+#endif /* MDB_MONITOR_IDX */
+
+ return LDAP_INAPPROPRIATE_MATCHING;
+
+done:
+ *dbip = ai->ai_dbi;
+ *maskp = mask;
+ return LDAP_SUCCESS;
+}
+
+static int indexer(
+ Operation *op,
+ MDB_txn *txn,
+ struct mdb_attrinfo *ai,
+ AttributeDescription *ad,
+ struct berval *atname,
+ BerVarray vals,
+ ID id,
+ int opid,
+ slap_mask_t mask )
+{
+ int rc;
+ struct berval *keys;
+ MDB_cursor *mc = ai->ai_cursor;
+ mdb_idl_keyfunc *keyfunc;
+ char *err;
+
+ assert( mask != 0 );
+
+ if ( !mc ) {
+ err = "c_open";
+ rc = mdb_cursor_open( txn, ai->ai_dbi, &mc );
+ if ( rc ) goto done;
+ if ( slapMode & SLAP_TOOL_QUICK )
+ ai->ai_cursor = mc;
+ }
+
+ if ( opid == SLAP_INDEX_ADD_OP ) {
+#ifdef MDB_TOOL_IDL_CACHING
+ if (( slapMode & SLAP_TOOL_QUICK ) && slap_tool_thread_max > 2 ) {
+ AttrIxInfo *ax = (AttrIxInfo *)LDAP_SLIST_FIRST(&op->o_extra);
+ ax->ai_ai = ai;
+ keyfunc = mdb_tool_idl_add;
+ mc = (MDB_cursor *)ax;
+ } else
+#endif
+ keyfunc = mdb_idl_insert_keys;
+ } else
+ keyfunc = mdb_idl_delete_keys;
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) {
+ rc = keyfunc( op->o_bd, mc, presence_key, id );
+ if( rc ) {
+ err = "presence";
+ goto done;
+ }
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) {
+ rc = ad->ad_type->sat_equality->smr_indexer(
+ LDAP_FILTER_EQUALITY,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_equality,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ rc = keyfunc( op->o_bd, mc, keys, id );
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ if ( rc ) {
+ err = "equality";
+ goto done;
+ }
+ }
+ rc = LDAP_SUCCESS;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) {
+ rc = ad->ad_type->sat_approx->smr_indexer(
+ LDAP_FILTER_APPROX,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_approx,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ rc = keyfunc( op->o_bd, mc, keys, id );
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ if ( rc ) {
+ err = "approx";
+ goto done;
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) {
+ rc = ad->ad_type->sat_substr->smr_indexer(
+ LDAP_FILTER_SUBSTRINGS,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_substr,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ rc = keyfunc( op->o_bd, mc, keys, id );
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ if( rc ) {
+ err = "substr";
+ goto done;
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+ }
+
+done:
+ if ( !(slapMode & SLAP_TOOL_QUICK))
+ mdb_cursor_close( mc );
+ switch( rc ) {
+ /* The callers all know how to deal with these results */
+ case 0:
+ break;
+ /* Anything else is bad news */
+ default:
+ rc = LDAP_OTHER;
+ }
+ return rc;
+}
+
+static int index_at_values(
+ Operation *op,
+ MDB_txn *txn,
+ AttributeDescription *ad,
+ AttributeType *type,
+ struct berval *tags,
+ BerVarray vals,
+ ID id,
+ int opid )
+{
+ int rc;
+ slap_mask_t mask = 0;
+ int ixop = opid;
+ AttrInfo *ai = NULL;
+
+ if ( opid == MDB_INDEX_UPDATE_OP )
+ ixop = SLAP_INDEX_ADD_OP;
+
+ if( type->sat_sup ) {
+ /* recurse */
+ rc = index_at_values( op, txn, NULL,
+ type->sat_sup, tags,
+ vals, id, opid );
+
+ if( rc ) return rc;
+ }
+
+ /* If this type has no AD, we've never used it before */
+ if( type->sat_ad ) {
+ ai = mdb_attr_mask( op->o_bd->be_private, type->sat_ad );
+ if ( ai && ( ai->ai_indexmask || ai->ai_newmask )) {
+#ifdef LDAP_COMP_MATCH
+ /* component indexing */
+ if ( ai->ai_cr ) {
+ ComponentReference *cr;
+ for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) {
+ rc = indexer( op, txn, ai, cr->cr_ad, &type->sat_cname,
+ cr->cr_nvals, id, ixop,
+ cr->cr_indexmask );
+ }
+ }
+#endif
+ ad = type->sat_ad;
+ /* If we're updating the index, just set the new bits that aren't
+ * already in the old mask.
+ */
+ if ( opid == MDB_INDEX_UPDATE_OP )
+ mask = ai->ai_newmask & ~ai->ai_indexmask;
+ else
+ /* For regular updates, if there is a newmask use it. Otherwise
+ * just use the old mask.
+ */
+ mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
+ if( mask ) {
+ rc = indexer( op, txn, ai, ad, &type->sat_cname,
+ vals, id, ixop, mask );
+
+ if( rc ) return rc;
+ }
+ }
+ }
+
+ if( tags->bv_len ) {
+ AttributeDescription *desc;
+
+ desc = ad_find_tags( type, tags );
+ if( desc ) {
+ ai = mdb_attr_mask( op->o_bd->be_private, desc );
+
+ if( ai && ( ai->ai_indexmask || ai->ai_newmask )) {
+ if ( opid == MDB_INDEX_UPDATE_OP )
+ mask = ai->ai_newmask & ~ai->ai_indexmask;
+ else
+ mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
+ if ( mask ) {
+ rc = indexer( op, txn, ai, desc, &desc->ad_cname,
+ vals, id, ixop, mask );
+
+ if( rc ) {
+ return rc;
+ }
+ }
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int mdb_index_values(
+ Operation *op,
+ MDB_txn *txn,
+ AttributeDescription *desc,
+ BerVarray vals,
+ ID id,
+ int opid )
+{
+ int rc;
+
+ /* Never index ID 0 */
+ if ( id == 0 )
+ return 0;
+
+ rc = index_at_values( op, txn, desc,
+ desc->ad_type, &desc->ad_tags,
+ vals, id, opid );
+
+ return rc;
+}
+
+/* Get the list of which indices apply to this attr */
+int
+mdb_index_recset(
+ struct mdb_info *mdb,
+ Attribute *a,
+ AttributeType *type,
+ struct berval *tags,
+ IndexRec *ir )
+{
+ int rc, slot;
+ AttrList *al;
+
+ if( type->sat_sup ) {
+ /* recurse */
+ rc = mdb_index_recset( mdb, a, type->sat_sup, tags, ir );
+ if( rc ) return rc;
+ }
+ /* If this type has no AD, we've never used it before */
+ if( type->sat_ad ) {
+ slot = mdb_attr_slot( mdb, type->sat_ad, NULL );
+ if ( slot >= 0 ) {
+ ir[slot].ir_ai = mdb->mi_attrs[slot];
+ al = ch_malloc( sizeof( AttrList ));
+ al->attr = a;
+ al->next = ir[slot].ir_attrs;
+ ir[slot].ir_attrs = al;
+ }
+ }
+ if( tags->bv_len ) {
+ AttributeDescription *desc;
+
+ desc = ad_find_tags( type, tags );
+ if( desc ) {
+ slot = mdb_attr_slot( mdb, desc, NULL );
+ if ( slot >= 0 ) {
+ ir[slot].ir_ai = mdb->mi_attrs[slot];
+ al = ch_malloc( sizeof( AttrList ));
+ al->attr = a;
+ al->next = ir[slot].ir_attrs;
+ ir[slot].ir_attrs = al;
+ }
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+/* Apply the indices for the recset */
+int mdb_index_recrun(
+ Operation *op,
+ MDB_txn *txn,
+ struct mdb_info *mdb,
+ IndexRec *ir0,
+ ID id,
+ int base )
+{
+ IndexRec *ir;
+ AttrList *al;
+ int i, rc = 0;
+
+ /* Never index ID 0 */
+ if ( id == 0 )
+ return 0;
+
+ for (i=base; i<mdb->mi_nattrs; i+=slap_tool_thread_max-1) {
+ ir = ir0 + i;
+ if ( !ir->ir_ai ) continue;
+ while (( al = ir->ir_attrs )) {
+ ir->ir_attrs = al->next;
+ rc = indexer( op, txn, ir->ir_ai, ir->ir_ai->ai_desc,
+ &ir->ir_ai->ai_desc->ad_type->sat_cname,
+ al->attr->a_nvals, id, SLAP_INDEX_ADD_OP,
+ ir->ir_ai->ai_indexmask );
+ free( al );
+ if ( rc ) break;
+ }
+ }
+ return rc;
+}
+
+int
+mdb_index_entry(
+ Operation *op,
+ MDB_txn *txn,
+ int opid,
+ Entry *e )
+{
+ int rc;
+ Attribute *ap = e->e_attrs;
+#if 0 /* ifdef LDAP_COMP_MATCH */
+ ComponentReference *cr_list = NULL;
+ ComponentReference *cr = NULL, *dupped_cr = NULL;
+ void* decoded_comp;
+ ComponentSyntaxInfo* csi_attr;
+ Syntax* syn;
+ AttributeType* at;
+ int i, num_attr;
+ void* mem_op;
+ struct berval value = {0};
+#endif
+
+ /* Never index ID 0 */
+ if ( e->e_id == 0 )
+ return 0;
+
+ Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n",
+ opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
+ (long) e->e_id, e->e_dn ? e->e_dn : "" );
+
+ /* add each attribute to the indexes */
+ for ( ; ap != NULL; ap = ap->a_next ) {
+#if 0 /* ifdef LDAP_COMP_MATCH */
+ AttrInfo *ai;
+ /* see if attribute has components to be indexed */
+ ai = mdb_attr_mask( op->o_bd->be_private, ap->a_desc->ad_type->sat_ad );
+ if ( !ai ) continue;
+ cr_list = ai->ai_cr;
+ if ( attr_converter && cr_list ) {
+ syn = ap->a_desc->ad_type->sat_syntax;
+ ap->a_comp_data = op->o_tmpalloc( sizeof( ComponentData ), op->o_tmpmemctx );
+ /* Memory chunk(nibble) pre-allocation for decoders */
+ mem_op = nibble_mem_allocator ( 1024*16, 1024*4 );
+ ap->a_comp_data->cd_mem_op = mem_op;
+ for( cr = cr_list ; cr ; cr = cr->cr_next ) {
+ /* count how many values in an attribute */
+ for( num_attr=0; ap->a_vals[num_attr].bv_val != NULL; num_attr++ );
+ num_attr++;
+ cr->cr_nvals = (BerVarray)op->o_tmpalloc( sizeof( struct berval )*num_attr, op->o_tmpmemctx );
+ for( i=0; ap->a_vals[i].bv_val != NULL; i++ ) {
+ /* decoding attribute value */
+ decoded_comp = attr_converter ( ap, syn, &ap->a_vals[i] );
+ if ( !decoded_comp )
+ return LDAP_DECODING_ERROR;
+ /* extracting the referenced component */
+ dupped_cr = dup_comp_ref( op, cr );
+ csi_attr = ((ComponentSyntaxInfo*)decoded_comp)->csi_comp_desc->cd_extract_i( mem_op, dupped_cr, decoded_comp );
+ if ( !csi_attr )
+ return LDAP_DECODING_ERROR;
+ cr->cr_asn_type_id = csi_attr->csi_comp_desc->cd_type_id;
+ cr->cr_ad = (AttributeDescription*)get_component_description ( cr->cr_asn_type_id );
+ if ( !cr->cr_ad )
+ return LDAP_INVALID_SYNTAX;
+ at = cr->cr_ad->ad_type;
+ /* encoding the value of component in GSER */
+ rc = component_encoder( mem_op, csi_attr, &value );
+ if ( rc != LDAP_SUCCESS )
+ return LDAP_ENCODING_ERROR;
+ /* Normalize the encoded component values */
+ if ( at->sat_equality && at->sat_equality->smr_normalize ) {
+ rc = at->sat_equality->smr_normalize (
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ at->sat_syntax, at->sat_equality,
+ &value, &cr->cr_nvals[i], op->o_tmpmemctx );
+ } else {
+ cr->cr_nvals[i] = value;
+ }
+ }
+ /* The end of BerVarray */
+ cr->cr_nvals[num_attr-1].bv_val = NULL;
+ cr->cr_nvals[num_attr-1].bv_len = 0;
+ }
+ op->o_tmpfree( ap->a_comp_data, op->o_tmpmemctx );
+ nibble_mem_free ( mem_op );
+ ap->a_comp_data = NULL;
+ }
+#endif
+ rc = mdb_index_values( op, txn, ap->a_desc,
+ ap->a_nvals, e->e_id, opid );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= index_entry_%s( %ld, \"%s\" ) failure\n",
+ opid == SLAP_INDEX_ADD_OP ? "add" : "del",
+ (long) e->e_id, e->e_dn );
+ return rc;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n",
+ opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
+ (long) e->e_id, e->e_dn ? e->e_dn : "" );
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/back-mdb/init.c b/servers/slapd/back-mdb/init.c
new file mode 100644
index 0000000..606edbc
--- /dev/null
+++ b/servers/slapd/back-mdb/init.c
@@ -0,0 +1,530 @@
+/* init.c - initialize mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+#include <ac/stdlib.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include "back-mdb.h"
+#include <lutil.h>
+#include <ldap_rq.h>
+#include "slap-config.h"
+
+static const struct berval mdmi_databases[] = {
+ BER_BVC("ad2i"),
+ BER_BVC("dn2i"),
+ BER_BVC("id2e"),
+ BER_BVC("id2v"),
+ BER_BVC("ixck"),
+ BER_BVNULL
+};
+
+static int
+mdb_id_compare( const MDB_val *a, const MDB_val *b )
+{
+ return *(ID *)a->mv_data < *(ID *)b->mv_data ? -1 : *(ID *)a->mv_data > *(ID *)b->mv_data;
+}
+
+static int
+mdb_db_init( BackendDB *be, ConfigReply *cr )
+{
+ struct mdb_info *mdb;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_db_init) ": Initializing mdb database\n" );
+
+ /* allocate backend-database-specific stuff */
+ mdb = (struct mdb_info *) ch_calloc( 1, sizeof(struct mdb_info) );
+
+ /* DBEnv parameters */
+ mdb->mi_dbenv_home = ch_strdup( SLAPD_DEFAULT_DB_DIR );
+ mdb->mi_dbenv_flags = 0;
+ mdb->mi_dbenv_mode = SLAPD_DEFAULT_DB_MODE;
+
+ mdb->mi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH;
+ mdb->mi_search_stack = NULL;
+
+ mdb->mi_mapsize = DEFAULT_MAPSIZE;
+ mdb->mi_rtxn_size = DEFAULT_RTXN_SIZE;
+ mdb->mi_multi_hi = UINT_MAX;
+ mdb->mi_multi_lo = UINT_MAX;
+
+ be->be_private = mdb;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs+1;
+
+#ifndef MDB_MULTIPLE_SUFFIXES
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_ONE_SUFFIX;
+#endif
+
+ rc = mdb_monitor_db_init( be );
+
+ return rc;
+}
+
+static int
+mdb_db_close( BackendDB *be, ConfigReply *cr );
+
+static int
+mdb_db_open( BackendDB *be, ConfigReply *cr )
+{
+ int rc, i;
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ struct stat stat1;
+ unsigned flags;
+ char *dbhome;
+ MDB_txn *txn;
+ int do_index = 0;
+
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": need suffix.\n" );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ LDAP_XSTRING(mdb_db_open) ": \"%s\"\n",
+ be->be_suffix[0].bv_val );
+
+ /* Check existence of dbenv_home. Any error means trouble */
+ rc = stat( mdb->mi_dbenv_home, &stat1 );
+ if( rc != 0 ) {
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "cannot access database directory \"%s\" (%d).\n",
+ be->be_suffix[0].bv_val, mdb->mi_dbenv_home, saved_errno );
+ return -1;
+ }
+
+ /* mdb is always clean */
+ be->be_flags |= SLAP_DBFLAG_CLEAN;
+
+ rc = mdb_env_create( &mdb->mi_dbenv );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "mdb_env_create failed: %s (%d).\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+ if ( mdb->mi_readers ) {
+ rc = mdb_env_set_maxreaders( mdb->mi_dbenv, mdb->mi_readers );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "mdb_env_set_maxreaders failed: %s (%d).\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+ }
+
+ rc = mdb_env_set_mapsize( mdb->mi_dbenv, mdb->mi_mapsize );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "mdb_env_set_mapsize failed: %s (%d).\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+ rc = mdb_env_set_maxdbs( mdb->mi_dbenv, MDB_INDICES );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "mdb_env_set_maxdbs failed: %s (%d).\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+#ifdef HAVE_EBCDIC
+ strcpy( path, mdb->mi_dbenv_home );
+ __atoe( path );
+ dbhome = path;
+#else
+ dbhome = mdb->mi_dbenv_home;
+#endif
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\": "
+ "dbenv_open(%s).\n",
+ be->be_suffix[0].bv_val, mdb->mi_dbenv_home );
+
+ flags = mdb->mi_dbenv_flags;
+
+ if ( slapMode & SLAP_TOOL_QUICK )
+ flags |= MDB_NOSYNC|MDB_WRITEMAP;
+
+ if ( slapMode & SLAP_TOOL_READONLY)
+ flags |= MDB_RDONLY;
+
+ rc = mdb_env_open( mdb->mi_dbenv, dbhome,
+ flags, mdb->mi_dbenv_mode );
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). "
+ "Restore from backup!\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, flags & MDB_RDONLY, &txn );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database \"%s\" cannot be opened: %s (%d). "
+ "Restore from backup!\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+ /* open (and create) main databases */
+ for( i = 0; mdmi_databases[i].bv_val; i++ ) {
+ flags = MDB_INTEGERKEY;
+ if( i == MDB_ID2ENTRY ) {
+ if ( !(slapMode & (SLAP_TOOL_READMAIN|SLAP_TOOL_READONLY) ))
+ flags |= MDB_CREATE;
+ } else {
+ if ( i == MDB_DN2ID )
+ flags |= MDB_DUPSORT;
+ if ( i == MDB_ID2VAL )
+ flags ^= MDB_INTEGERKEY|MDB_DUPSORT;
+ if ( !(slapMode & SLAP_TOOL_READONLY) )
+ flags |= MDB_CREATE;
+ }
+
+ rc = mdb_dbi_open( txn,
+ mdmi_databases[i].bv_val,
+ flags,
+ &mdb->mi_dbis[i] );
+
+ if ( rc != 0 ) {
+ /* when read-only, it's ok for ID2VAL or IDXCKP to not exist */
+ if (( flags & MDB_CREATE ) || ( i < MDB_ID2VAL )) {
+ snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+ "mdb_dbi_open(%s/%s) failed: %s (%d).",
+ be->be_suffix[0].bv_val,
+ mdb->mi_dbenv_home, mdmi_databases[i].bv_val,
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": %s\n",
+ cr->msg );
+ goto fail;
+ }
+ }
+
+ if ( i == MDB_ID2ENTRY )
+ mdb_set_compare( txn, mdb->mi_dbis[i], mdb_id_compare );
+ else if ( i == MDB_ID2VAL ) {
+ mdb_set_compare( txn, mdb->mi_dbis[i], mdb_id2v_compare );
+ mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_id2v_dupsort );
+ } else if ( i == MDB_DN2ID ) {
+ MDB_cursor *mc;
+ MDB_val key, data;
+ mdb_set_dupsort( txn, mdb->mi_dbis[i], mdb_dup_compare );
+ /* check for old dn2id format */
+ rc = mdb_cursor_open( txn, mdb->mi_dbis[i], &mc );
+ /* first record is always ID 0 */
+ rc = mdb_cursor_get( mc, &key, &data, MDB_FIRST );
+ if ( rc == 0 ) {
+ rc = mdb_cursor_get( mc, &key, &data, MDB_NEXT );
+ if ( rc == 0 ) {
+ int len;
+ unsigned char *ptr;
+ ptr = data.mv_data;
+ len = (ptr[0] & 0x7f) << 8 | ptr[1];
+ if (data.mv_size < 2*len + 4 + 2*sizeof(ID)) {
+ snprintf( cr->msg, sizeof(cr->msg),
+ "database \"%s\": DN index needs upgrade, "
+ "run \"slapindex entryDN\".",
+ be->be_suffix[0].bv_val );
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": %s\n",
+ cr->msg );
+ if ( !(slapMode & SLAP_TOOL_READMAIN ))
+ rc = LDAP_OTHER;
+ mdb->mi_flags |= MDB_NEED_UPGRADE;
+ }
+ }
+ }
+ mdb_cursor_close( mc );
+ if ( rc == LDAP_OTHER )
+ goto fail;
+ }
+ }
+
+ rc = mdb_ad_read( mdb, txn );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ goto fail;
+ }
+
+ /* slapcat doesn't need indexes. avoid a failure if
+ * a configured index wasn't created yet.
+ */
+ if ( !(slapMode & SLAP_TOOL_READONLY) ) {
+ rc = mdb_attr_dbs_open( be, txn, cr );
+ if ( rc ) {
+ mdb_txn_abort( txn );
+ goto fail;
+ }
+ }
+
+ if ( slapMode & SLAP_SERVER_MODE ) {
+ MDB_stat st;
+ rc = mdb_stat( txn, mdb->mi_idxckp, &st );
+ if ( st.ms_entries )
+ do_index = mdb_resume_index( be, txn );
+ }
+
+ rc = mdb_txn_commit(txn);
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_db_open) ": database %s: "
+ "txn_commit failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ goto fail;
+ }
+
+ /* monitor setup */
+ rc = mdb_monitor_db_open( be );
+ if ( rc != 0 ) {
+ goto fail;
+ }
+
+ mdb->mi_flags |= MDB_IS_OPEN;
+
+ if ( do_index )
+ mdb_start_index_task( be->bd_self );
+
+ return 0;
+
+fail:
+ mdb_db_close( be, NULL );
+ return rc;
+}
+
+static int
+mdb_db_close( BackendDB *be, ConfigReply *cr )
+{
+ int rc;
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+
+ /* monitor handling */
+ (void)mdb_monitor_db_close( be );
+
+ mdb->mi_flags &= ~MDB_IS_OPEN;
+
+ /* remove indexer task */
+ if ( mdb->mi_index_task ) {
+ struct re_s *re = mdb->mi_index_task;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mdb->mi_index_task = NULL;
+ /* can never actually be running at this point, but paranoia */
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) )
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ if ( mdb->mi_dbenv ) {
+ mdb_reader_flush( mdb->mi_dbenv );
+
+ if ( mdb->mi_dbis[0] ) {
+ int i;
+
+ mdb_attr_dbs_close( mdb );
+ for ( i=0; i<MDB_NDB; i++ )
+ mdb_dbi_close( mdb->mi_dbenv, mdb->mi_dbis[i] );
+
+ /* force a sync, but not if we were ReadOnly,
+ * and not in Quick mode.
+ */
+ if (!(slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY))) {
+ rc = mdb_env_sync( mdb->mi_dbenv, 1 );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "mdb_db_close: database \"%s\": "
+ "mdb_env_sync failed: %s (%d).\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ }
+ }
+ }
+
+ mdb_env_close( mdb->mi_dbenv );
+ mdb->mi_dbenv = NULL;
+ }
+
+ return 0;
+}
+
+static int
+mdb_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+
+ /* stop and remove checkpoint task */
+ if ( mdb->mi_txn_cp_task ) {
+ struct re_s *re = mdb->mi_txn_cp_task;
+ mdb->mi_txn_cp_task = NULL;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) )
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ /* monitor handling */
+ (void)mdb_monitor_db_destroy( be );
+
+ if( mdb->mi_dbenv_home ) ch_free( mdb->mi_dbenv_home );
+
+ mdb_attr_index_destroy( mdb );
+
+ ch_free( mdb );
+ be->be_private = NULL;
+
+ return 0;
+}
+
+int
+mdb_back_initialize(
+ BackendInfo *bi )
+{
+ int rc;
+
+ static char *controls[] = {
+ LDAP_CONTROL_ASSERT,
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_NOOP,
+ LDAP_CONTROL_PAGEDRESULTS,
+ LDAP_CONTROL_PRE_READ,
+ LDAP_CONTROL_POST_READ,
+ LDAP_CONTROL_SUBENTRIES,
+ LDAP_CONTROL_X_PERMISSIVE_MODIFY,
+ LDAP_CONTROL_TXN_SPEC,
+ NULL
+ };
+
+ /* initialize the underlying database system */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_back_initialize) ": initialize "
+ MDB_UCTYPE " backend\n" );
+
+ bi->bi_flags |=
+ SLAP_BFLAG_INCREMENT |
+ SLAP_BFLAG_SUBENTRIES |
+ SLAP_BFLAG_ALIASES |
+ SLAP_BFLAG_REFERRALS |
+ SLAP_BFLAG_TXNS;
+
+ bi->bi_controls = controls;
+
+ { /* version check */
+ int major, minor, patch, ver;
+ char *version = mdb_version( &major, &minor, &patch );
+#ifdef HAVE_EBCDIC
+ char v2[1024];
+
+ /* All our stdio does an ASCII to EBCDIC conversion on
+ * the output. Strings from the MDB library are already
+ * in EBCDIC; we have to go back and forth...
+ */
+ strcpy( v2, version );
+ __etoa( v2 );
+ version = v2;
+#endif
+ ver = (major << 24) | (minor << 16) | patch;
+ if( ver != MDB_VERSION_FULL ) {
+ /* fail if a versions don't match */
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_back_initialize) ": "
+ "MDB library version mismatch:"
+ " expected " MDB_VERSION_STRING ","
+ " got %s\n", version );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_back_initialize)
+ ": %s\n", version );
+ }
+
+ bi->bi_open = 0;
+ bi->bi_close = 0;
+ bi->bi_config = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = mdb_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = mdb_db_open;
+ bi->bi_db_close = mdb_db_close;
+ bi->bi_db_destroy = mdb_db_destroy;
+
+ bi->bi_op_add = mdb_add;
+ bi->bi_op_bind = mdb_bind;
+ bi->bi_op_compare = mdb_compare;
+ bi->bi_op_delete = mdb_delete;
+ bi->bi_op_modify = mdb_modify;
+ bi->bi_op_modrdn = mdb_modrdn;
+ bi->bi_op_search = mdb_search;
+
+ bi->bi_op_unbind = 0;
+ bi->bi_op_txn = mdb_txn;
+
+ bi->bi_extended = mdb_extended;
+
+ bi->bi_chk_referrals = 0;
+ bi->bi_operational = mdb_operational;
+
+ bi->bi_has_subordinates = mdb_hasSubordinates;
+ bi->bi_entry_release_rw = mdb_entry_release;
+ bi->bi_entry_get_rw = mdb_entry_get;
+
+ /*
+ * hooks for slap tools
+ */
+ bi->bi_tool_entry_open = mdb_tool_entry_open;
+ bi->bi_tool_entry_close = mdb_tool_entry_close;
+ bi->bi_tool_entry_first = backend_tool_entry_first;
+ bi->bi_tool_entry_first_x = mdb_tool_entry_first_x;
+ bi->bi_tool_entry_next = mdb_tool_entry_next;
+ bi->bi_tool_entry_get = mdb_tool_entry_get;
+ bi->bi_tool_entry_put = mdb_tool_entry_put;
+ bi->bi_tool_entry_reindex = mdb_tool_entry_reindex;
+ bi->bi_tool_sync = 0;
+ bi->bi_tool_dn2id_get = mdb_tool_dn2id_get;
+ bi->bi_tool_entry_modify = mdb_tool_entry_modify;
+ bi->bi_tool_entry_delete = mdb_tool_entry_delete;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ rc = mdb_back_init_cf( bi );
+
+ return rc;
+}
+
+#if (SLAPD_MDB == SLAPD_MOD_DYNAMIC)
+
+SLAP_BACKEND_INIT_MODULE( mdb )
+
+#endif /* SLAPD_MDB == SLAPD_MOD_DYNAMIC */
+
diff --git a/servers/slapd/back-mdb/key.c b/servers/slapd/back-mdb/key.c
new file mode 100644
index 0000000..b0b453b
--- /dev/null
+++ b/servers/slapd/back-mdb/key.c
@@ -0,0 +1,72 @@
+/* index.c - routines for dealing with attribute indexes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-mdb.h"
+#include "idl.h"
+
+/* read a key */
+int
+mdb_key_read(
+ Backend *be,
+ MDB_txn *txn,
+ MDB_dbi dbi,
+ struct berval *k,
+ ID *ids,
+ MDB_cursor **saved_cursor,
+ int get_flag
+)
+{
+ int rc;
+ MDB_val key;
+#ifndef MISALIGNED_OK
+ int kbuf[2];
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, "=> key_read\n" );
+
+#ifndef MISALIGNED_OK
+ if (k->bv_len & ALIGNER) {
+ key.mv_size = sizeof(kbuf);
+ key.mv_data = kbuf;
+ kbuf[1] = 0;
+ memcpy(kbuf, k->bv_val, k->bv_len);
+ } else
+#endif
+ {
+ key.mv_size = k->bv_len;
+ key.mv_data = k->bv_val;
+ }
+
+ rc = mdb_idl_fetch_key( be, txn, dbi, &key, ids, saved_cursor, get_flag );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read: failed (%d)\n",
+ rc );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<= mdb_index_read %ld candidates\n",
+ (long) MDB_IDL_N(ids) );
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/back-mdb/modify.c b/servers/slapd/back-mdb/modify.c
new file mode 100644
index 0000000..f2233e8
--- /dev/null
+++ b/servers/slapd/back-mdb/modify.c
@@ -0,0 +1,843 @@
+/* modify.c - mdb backend modify routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "back-mdb.h"
+
+static struct berval scbva[] = {
+ BER_BVC("glue"),
+ BER_BVNULL
+};
+
+#define CHECK_ADD 1
+#define CHECK_DEL 2
+
+static void
+mdb_modify_idxflags(
+ Operation *op,
+ AttributeDescription *desc,
+ int ixcheck,
+ Attribute *newattrs,
+ Attribute *oldattrs )
+{
+ struct berval ix_at;
+ AttrInfo *ai;
+
+ /* check if modified attribute was indexed
+ * but not in case of NOOP... */
+ ai = mdb_index_mask( op->o_bd, desc, &ix_at );
+ if ( ai ) {
+ if ( ixcheck & CHECK_DEL ) {
+ Attribute *ap;
+ struct berval ix2;
+
+ ap = attr_find( oldattrs, desc );
+ if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL;
+
+ /* ITS#8678 FIXME
+ * If using 32bit hashes, or substring index, must account for
+ * possible index collisions. If no substring index, and using
+ * 64bit hashes, assume we don't need to check for collisions.
+ *
+ * In 2.5 use refcounts and avoid all of this mess.
+ */
+ if (!slap_hash64(-1) || (ai->ai_indexmask & SLAP_INDEX_SUBSTR)) {
+ /* Find all other attrs that index to same slot */
+ for ( ap = newattrs; ap; ap = ap->a_next ) {
+ ai = mdb_index_mask( op->o_bd, ap->a_desc, &ix2 );
+ if ( ai && ix2.bv_val == ix_at.bv_val )
+ ap->a_flags |= SLAP_ATTR_IXADD;
+ }
+ }
+
+ }
+ if ( ixcheck & CHECK_ADD ) {
+ Attribute *ap;
+
+ ap = attr_find( newattrs, desc );
+ if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD;
+ }
+ }
+}
+
+int mdb_modify_internal(
+ Operation *op,
+ MDB_txn *tid,
+ Modifications *modlist,
+ Entry *e,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ int rc, err;
+ Modification *mod;
+ Modifications *ml;
+ Attribute *save_attrs;
+ Attribute *ap, *aold, *anew;
+ int glue_attr_delete = 0;
+ int softop, chkpresent;
+ int ixcheck;
+ int a_flags;
+ MDB_cursor *mvc = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "mdb_modify_internal: 0x%08lx: %s\n",
+ e->e_id, e->e_dn );
+
+ if ( !acl_check_modlist( op, e, modlist )) {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ /* save_attrs will be disposed of by caller */
+ save_attrs = e->e_attrs;
+ e->e_attrs = attrs_dup( e->e_attrs );
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ int match;
+ mod = &ml->sml_mod;
+ switch( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) {
+ value_match( &match, slap_schema.si_ad_structuralObjectClass,
+ slap_schema.si_ad_structuralObjectClass->
+ ad_type->sat_equality,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &mod->sm_values[0], &scbva[0], text );
+ if ( !match ) glue_attr_delete = 1;
+ }
+ }
+ if ( glue_attr_delete )
+ break;
+ }
+
+ if ( glue_attr_delete ) {
+ Attribute **app = &e->e_attrs;
+ while ( *app != NULL ) {
+ if ( !is_at_operational( (*app)->a_desc->ad_type )) {
+ Attribute *save = *app;
+ *app = (*app)->a_next;
+ attr_free( save );
+ continue;
+ }
+ app = &(*app)->a_next;
+ }
+ }
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ mod = &ml->sml_mod;
+ ixcheck = 0;
+
+ aold = attr_find( e->e_attrs, mod->sm_desc );
+ if (aold)
+ a_flags = aold->a_flags;
+ else
+ a_flags = 0;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ softop = 0;
+ chkpresent = 0;
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: add %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+
+do_add:
+ err = modify_add_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+
+ if( softop ) {
+ mod->sm_op = SLAP_MOD_SOFTADD;
+ if ( err == LDAP_TYPE_OR_VALUE_EXISTS )
+ err = LDAP_SUCCESS;
+ }
+ if( chkpresent ) {
+ mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+ }
+
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+ err, *text );
+ } else {
+ unsigned hi;
+ if (!aold)
+ anew = attr_find( e->e_attrs, mod->sm_desc );
+ else
+ anew = aold;
+ mdb_attr_multi_thresh( mdb, mod->sm_desc, &hi, NULL );
+ /* check for big multivalued attrs */
+ if ( anew->a_numvals > hi )
+ anew->a_flags |= SLAP_ATTR_BIG_MULTI;
+ if ( anew->a_flags & SLAP_ATTR_BIG_MULTI ) {
+ if (!mvc) {
+ err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if (err) {
+mval_fail: strncpy( textbuf, mdb_strerror( err ), textlen );
+ err = LDAP_OTHER;
+ break;
+ }
+ }
+ /* if prev was set, just add new values */
+ if (a_flags & SLAP_ATTR_BIG_MULTI ) {
+ anew = (Attribute *)mod;
+ /* Tweak nvals */
+ if (!anew->a_nvals)
+ anew->a_nvals = anew->a_vals;
+ }
+ err = mdb_mval_put(op, mvc, e->e_id, anew);
+ if (a_flags & SLAP_ATTR_BIG_MULTI ) {
+ /* Undo nvals tweak */
+ if (anew->a_nvals == anew->a_vals)
+ anew->a_nvals = NULL;
+ }
+ if ( err )
+ goto mval_fail;
+ }
+ ixcheck |= CHECK_ADD;
+ }
+ break;
+
+ case LDAP_MOD_DELETE:
+ if ( glue_attr_delete ) {
+ err = LDAP_SUCCESS;
+ break;
+ }
+
+ softop = 0;
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: delete %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+do_del:
+ err = modify_delete_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+
+ if (softop) {
+ mod->sm_op = SLAP_MOD_SOFTDEL;
+ if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+ err = LDAP_SUCCESS;
+ softop = 2;
+ }
+ }
+
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+ err, *text );
+ } else {
+ if (softop != 2)
+ ixcheck |= CHECK_DEL;
+ /* check for big multivalued attrs */
+ if (a_flags & SLAP_ATTR_BIG_MULTI) {
+ Attribute a_dummy;
+ if (!mvc) {
+ err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if (err)
+ goto mval_fail;
+ }
+ if ( mod->sm_numvals ) {
+ anew = attr_find( e->e_attrs, mod->sm_desc );
+ if ( anew ) {
+ unsigned lo;
+ mdb_attr_multi_thresh( mdb, mod->sm_desc, NULL, &lo );
+ if ( anew->a_numvals < lo ) {
+ anew->a_flags ^= SLAP_ATTR_BIG_MULTI;
+ anew = NULL;
+ } else {
+ anew = (Attribute *)mod;
+ }
+ }
+ } else {
+ anew = NULL;
+ }
+ if (!anew) {
+ /* delete all values */
+ anew = &a_dummy;
+ anew->a_desc = mod->sm_desc;
+ anew->a_numvals = 0;
+ }
+ err = mdb_mval_del( op, mvc, e->e_id, anew );
+ if ( err )
+ goto mval_fail;
+ }
+ }
+ break;
+
+ case LDAP_MOD_REPLACE:
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: replace %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_replace_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+ err, *text );
+ } else {
+ unsigned hi;
+ ixcheck = CHECK_DEL;
+ if ( mod->sm_numvals )
+ ixcheck |= CHECK_ADD;
+ if (a_flags & SLAP_ATTR_BIG_MULTI) {
+ Attribute a_dummy;
+ if (!mvc) {
+ err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if (err)
+ goto mval_fail;
+ }
+ /* delete all values */
+ anew = &a_dummy;
+ anew->a_desc = mod->sm_desc;
+ anew->a_numvals = 0;
+ err = mdb_mval_del( op, mvc, e->e_id, anew );
+ if (err)
+ goto mval_fail;
+ }
+ anew = attr_find( e->e_attrs, mod->sm_desc );
+ mdb_attr_multi_thresh( mdb, mod->sm_desc, &hi, NULL );
+ if (mod->sm_numvals > hi) {
+ anew->a_flags |= SLAP_ATTR_BIG_MULTI;
+ if (!mvc) {
+ err = mdb_cursor_open( tid, mdb->mi_dbis[MDB_ID2VAL], &mvc );
+ if (err)
+ goto mval_fail;
+ }
+ err = mdb_mval_put(op, mvc, e->e_id, anew);
+ if (err)
+ goto mval_fail;
+ } else if (anew) {
+ /* revert back to normal attr */
+ anew->a_flags &= ~SLAP_ATTR_BIG_MULTI;
+ }
+ }
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: increment %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_increment_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: %d %s\n",
+ err, *text );
+ } else {
+ ixcheck = CHECK_ADD|CHECK_DEL;
+ }
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: softadd %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_add_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_ADD;
+ softop = 1;
+ chkpresent = 0;
+ goto do_add;
+
+ case SLAP_MOD_SOFTDEL:
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: softdel %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_delete_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_DELETE;
+ softop = 1;
+ goto do_del;
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) {
+ /* skip */
+ err = LDAP_SUCCESS;
+ break;
+ }
+
+ Debug(LDAP_DEBUG_ARGS,
+ "mdb_modify_internal: add_if_not_present %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_add_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_ADD;
+ softop = 0;
+ chkpresent = 1;
+ goto do_add;
+
+ default:
+ Debug(LDAP_DEBUG_ANY, "mdb_modify_internal: invalid op %d\n",
+ mod->sm_op );
+ *text = "Invalid modify operation";
+ err = LDAP_OTHER;
+ Debug(LDAP_DEBUG_ARGS, "mdb_modify_internal: %d %s\n",
+ err, *text );
+ }
+
+ if ( err != LDAP_SUCCESS ) {
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ /* unlock entry, delete from cache */
+ return err;
+ }
+
+ /* If objectClass was modified, reset the flags */
+ if ( mod->sm_desc == slap_schema.si_ad_objectClass ) {
+ e->e_ocflags = 0;
+ }
+
+ if ( glue_attr_delete ) e->e_ocflags = 0;
+
+
+ /* check if modified attribute was indexed
+ * but not in case of NOOP... */
+ if ( !op->o_noop ) {
+ mdb_modify_idxflags( op, mod->sm_desc, ixcheck, e->e_attrs, save_attrs );
+ }
+ }
+
+ /* check that the entry still obeys the schema */
+ ap = NULL;
+ rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap,
+ text, textbuf, textlen );
+ if ( rc != LDAP_SUCCESS || op->o_noop ) {
+ attrs_free( e->e_attrs );
+ /* clear the indexing flags */
+ for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
+ ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL);
+ }
+ e->e_attrs = save_attrs;
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "entry failed schema check: %s\n",
+ *text );
+ }
+
+ /* if NOOP then silently revert to saved attrs */
+ return rc;
+ }
+
+ /* structuralObjectClass modified! */
+ if ( ap ) {
+ assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass );
+ if ( !op->o_noop ) {
+ mdb_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass,
+ CHECK_ADD|CHECK_DEL, e->e_attrs, save_attrs );
+ }
+ }
+
+ /* update the indices of the modified attributes */
+
+ /* start with deleting the old index entries */
+ for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
+ if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
+ struct berval *vals;
+ Attribute *a2;
+ ap->a_flags &= ~SLAP_ATTR_IXDEL;
+ a2 = attr_find( e->e_attrs, ap->a_desc );
+ if ( a2 ) {
+ /* need to detect which values were deleted */
+ int i, j, k;
+ /* let add know there were deletes */
+ if ( a2->a_flags & SLAP_ATTR_IXADD )
+ a2->a_flags |= SLAP_ATTR_IXDEL;
+ vals = op->o_tmpalloc( (ap->a_numvals + 1) *
+ sizeof(struct berval), op->o_tmpmemctx );
+ j = 0;
+ for ( i=k=0; i < ap->a_numvals; i++ ) {
+ char found = 0;
+ BerValue* current = &ap->a_nvals[i];
+ int k2 = k;
+ for (k2 = k ; k2 < a2->a_numvals; k2 ++) {
+ int match = -1, rc;
+ const char *text;
+
+ rc = ordered_value_match( &match, a2->a_desc,
+ ap->a_desc->ad_type->sat_equality, 0,
+ &a2->a_nvals[k2], current, &text );
+ if ( rc == LDAP_SUCCESS && match == 0 ) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ vals[j++] = *current;
+ } else {
+ k = k2 + 1;
+ }
+ }
+ BER_BVZERO(vals+j);
+ } else {
+ /* attribute was completely deleted */
+ vals = ap->a_nvals;
+ }
+ rc = 0;
+ if ( !BER_BVISNULL( vals )) {
+ rc = mdb_index_values( op, tid, ap->a_desc,
+ vals, e->e_id, SLAP_INDEX_DELETE_OP );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: attribute \"%s\" index delete failure\n",
+ op->o_log_prefix, ap->a_desc->ad_cname.bv_val );
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ }
+ }
+ if ( vals != ap->a_nvals )
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ if ( rc ) return rc;
+ }
+ }
+
+ /* add the new index entries */
+ for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) {
+ if (ap->a_flags & SLAP_ATTR_IXADD) {
+ ap->a_flags &= ~SLAP_ATTR_IXADD;
+ if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
+ /* if any values were deleted, we must readd index
+ * for all remaining values.
+ */
+ ap->a_flags &= ~SLAP_ATTR_IXDEL;
+ rc = mdb_index_values( op, tid, ap->a_desc,
+ ap->a_nvals,
+ e->e_id, SLAP_INDEX_ADD_OP );
+ } else {
+ int found = 0;
+ /* if this was only an add, we only need to index
+ * the added values.
+ */
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ struct berval *vals;
+ if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals )
+ continue;
+ found = 1;
+ switch( ml->sml_op ) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ case LDAP_MOD_INCREMENT:
+ case SLAP_MOD_SOFTADD:
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( ml->sml_op == LDAP_MOD_INCREMENT )
+ vals = ap->a_nvals;
+ else if ( ml->sml_nvalues )
+ vals = ml->sml_nvalues;
+ else
+ vals = ml->sml_values;
+ rc = mdb_index_values( op, tid, ap->a_desc,
+ vals, e->e_id, SLAP_INDEX_ADD_OP );
+ break;
+ }
+ if ( rc )
+ break;
+ }
+ /* This attr was affected by a modify of a subtype, so
+ * there was no direct match in the modlist. Just readd
+ * all of its values.
+ */
+ if ( !found ) {
+ rc = mdb_index_values( op, tid, ap->a_desc,
+ ap->a_nvals,
+ e->e_id, SLAP_INDEX_ADD_OP );
+ }
+ }
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: attribute \"%s\" index add failure\n",
+ op->o_log_prefix, ap->a_desc->ad_cname.bv_val );
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+int
+mdb_modify( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ Entry *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ MDB_txn *txn = NULL;
+ mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
+ Entry dummy = {0};
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+ int numads = mdb->mi_numads;
+
+ Debug( LDAP_DEBUG_ARGS, LDAP_XSTRING(mdb_modify) ": %s\n",
+ op->o_req_dn.bv_val );
+
+ ctrls[num_ctrls] = NULL;
+
+ /* begin transaction */
+ rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi );
+ rs->sr_text = NULL;
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": txn_begin failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ txn = moi->moi_txn;
+
+ /* Don't touch the opattrs, if this is a contextCSN update
+ * initiated from updatedn */
+ if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next ||
+ op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) {
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+ }
+
+ /* get entry or ancestor */
+ rs->sr_err = mdb_dn2entry( op, txn, NULL, &op->o_req_ndn, &e, NULL, 1 );
+
+ if ( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": dn2entry failed (%d)\n",
+ rs->sr_err );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ }
+
+ /* acquire and lock entry */
+ /* FIXME: dn2entry() should return non-glue entry */
+ if (( rs->sr_err == MDB_NOTFOUND ) ||
+ ( !manageDSAit && e && is_entry_glue( e )))
+ {
+ if ( e != NULL ) {
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral, NULL,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ }
+
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral, don't allow modify */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": entry is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = e->e_name.bv_val;
+ rs->sr_flags = REP_REF_MUSTBEFREED;
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modify) ": pre-read "
+ "failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* Modify the entry */
+ dummy = *e;
+ rs->sr_err = mdb_modify_internal( op, txn, op->orm_modlist,
+ &dummy, &rs->sr_text, textbuf, textlen );
+
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": modify failed (%d)\n",
+ rs->sr_err );
+ /* Only free attrs if they were dup'd. */
+ if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+ goto return_results;
+ }
+
+ /* change the entry itself */
+ rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy );
+ if ( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": id2entry update failed " "(%d)\n",
+ rs->sr_err );
+ if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) {
+ rs->sr_text = "entry too big";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry update failed";
+ }
+ goto return_results;
+ }
+
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, &dummy,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modify)
+ ": post-read failed!\n" );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* Only free attrs if they were dup'd. */
+ if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+ if( moi == &opinfo ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ opinfo.moi_oe.oe_key = NULL;
+ if( op->o_noop ) {
+ mdb->mi_numads = numads;
+ mdb_txn_abort( txn );
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ txn = NULL;
+ goto return_results;
+ } else {
+ rs->sr_err = mdb_txn_commit( txn );
+ if ( rs->sr_err )
+ mdb->mi_numads = numads;
+ txn = NULL;
+ }
+ }
+
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_modify) ": txn_%s failed: %s (%d)\n",
+ op->o_noop ? "abort (no-op)" : "commit",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit failed";
+
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modify) ": updated%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ dummy.e_id, op->o_req_dn.bv_val );
+
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ if( dummy.e_attrs ) {
+ attrs_free( dummy.e_attrs );
+ }
+ send_ldap_result( op, rs );
+
+#if 0
+ if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) {
+ TXN_CHECKPOINT( mdb->bi_dbenv,
+ mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 );
+ }
+#endif
+
+done:
+ slap_graduate_commit_csn( op );
+
+ if( moi == &opinfo ) {
+ if( txn != NULL ) {
+ mdb->mi_numads = numads;
+ mdb_txn_abort( txn );
+ }
+ if ( opinfo.moi_oe.oe_key ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ }
+ } else {
+ moi->moi_ref--;
+ }
+
+ if( e != NULL ) {
+ mdb_entry_return( op, e );
+ }
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+
+ rs->sr_text = NULL;
+
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/modrdn.c b/servers/slapd/back-mdb/modrdn.c
new file mode 100644
index 0000000..840812c
--- /dev/null
+++ b/servers/slapd/back-mdb/modrdn.c
@@ -0,0 +1,612 @@
+/* modrdn.c - mdb backend modrdn routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+int
+mdb_modrdn( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ struct berval p_dn, p_ndn;
+ Entry *e = NULL;
+ Entry *p = NULL;
+ /* LDAP v2 supporting correct attribute handling. */
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ MDB_txn *txn = NULL;
+ MDB_cursor *mc;
+ struct mdb_op_info opinfo = {{{ 0 }}}, *moi = &opinfo;
+ Entry dummy = {0};
+
+ Entry *np = NULL; /* newSuperior Entry */
+ struct berval *np_dn = NULL; /* newSuperior dn */
+ struct berval *np_ndn = NULL; /* newSuperior ndn */
+ struct berval *new_parent_dn = NULL; /* np_dn, p_dn, or NULL */
+
+ int manageDSAit = get_manageDSAit( op );
+
+ ID nid, nsubs;
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ int parent_is_glue = 0;
+ int parent_is_leaf = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>" LDAP_XSTRING(mdb_modrdn) "(%s,%s,%s)\n",
+ op->o_req_dn.bv_val,op->oq_modrdn.rs_newrdn.bv_val,
+ op->oq_modrdn.rs_newSup ? op->oq_modrdn.rs_newSup->bv_val : "NULL" );
+
+ ctrls[num_ctrls] = NULL;
+
+ /* begin transaction */
+ rs->sr_err = mdb_opinfo_get( op, mdb, 0, &moi );
+ rs->sr_text = NULL;
+ if( rs->sr_err != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn) ": txn_begin failed: "
+ "%s (%d)\n", mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ txn = moi->moi_txn;
+
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+ if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+#ifdef MDB_MULTIPLE_SUFFIXES
+ /* Allow renaming one suffix entry to another */
+ p_ndn = slap_empty_bv;
+#else
+ /* There can only be one suffix entry */
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ rs->sr_text = "cannot rename suffix entry";
+ goto return_results;
+#endif
+ } else {
+ dnParent( &op->o_req_ndn, &p_ndn );
+ }
+ np_ndn = &p_ndn;
+ /* Make sure parent entry exist and we can write its
+ * children.
+ */
+ rs->sr_err = mdb_cursor_open( txn, mdb->mi_dn2id, &mc );
+ if ( rs->sr_err != 0 ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": cursor_open failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "DN cursor_open failed";
+ goto return_results;
+ }
+ rs->sr_err = mdb_dn2entry( op, txn, mc, &p_ndn, &p, NULL, 0 );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn)
+ ": parent does not exist\n" );
+ rs->sr_ref = referral_rewrite( default_referral, NULL,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ rs->sr_err = LDAP_REFERRAL;
+
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ goto done;
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, p,
+ children, NULL,
+ op->oq_modrdn.rs_newSup == NULL ?
+ ACL_WRITE : ACL_WDEL,
+ NULL );
+
+ if ( ! rs->sr_err ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ Debug( LDAP_DEBUG_TRACE, "no access to parent\n" );
+ rs->sr_text = "no write access to parent's children";
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn) ": wr to children "
+ "of entry %s OK\n", p_ndn.bv_val );
+
+ if ( p_ndn.bv_val == slap_empty_bv.bv_val ) {
+ p_dn = slap_empty_bv;
+ } else {
+ dnParent( &op->o_req_dn, &p_dn );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn) ": parent dn=%s\n",
+ p_dn.bv_val );
+
+ /* get entry */
+ rs->sr_err = mdb_dn2entry( op, txn, mc, &op->o_req_ndn, &e, &nsubs, 0 );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ e = p;
+ p = NULL;
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* FIXME: dn2entry() should return non-glue entry */
+ if (( rs->sr_err == MDB_NOTFOUND ) ||
+ ( !manageDSAit && e && is_entry_glue( e )))
+ {
+ if( e != NULL ) {
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral, NULL,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ }
+
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ free( (char *)rs->sr_matched );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ /* check write on old entry */
+ rs->sr_err = access_allowed( op, e, entry, NULL, ACL_WRITE, NULL );
+ if ( ! rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE, "no access to entry\n" );
+ rs->sr_text = "no write access to old entry";
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ if (!manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral, don't allow rename */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn)
+ ": entry %s is referral\n", e->e_dn );
+
+ rs->sr_err = LDAP_REFERRAL,
+ rs->sr_matched = e->e_name.bv_val;
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ new_parent_dn = &p_dn; /* New Parent unless newSuperior given */
+
+ if ( op->oq_modrdn.rs_newSup != NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": new parent \"%s\" requested...\n",
+ op->oq_modrdn.rs_newSup->bv_val );
+
+ /* newSuperior == oldParent? */
+ if( dn_match( &p_ndn, op->oq_modrdn.rs_nnewSup ) ) {
+ Debug( LDAP_DEBUG_TRACE, "mdb_back_modrdn: "
+ "new parent \"%s\" same as the old parent \"%s\"\n",
+ op->oq_modrdn.rs_newSup->bv_val, p_dn.bv_val );
+ op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */
+ }
+ }
+
+ /* There's a MDB_MULTIPLE_SUFFIXES case here that this code doesn't
+ * support. E.g., two suffixes dc=foo,dc=com and dc=bar,dc=net.
+ * We do not allow modDN
+ * dc=foo,dc=com
+ * newrdn dc=bar
+ * newsup dc=net
+ * and we probably should. But since MULTIPLE_SUFFIXES is deprecated
+ * I'm ignoring this problem for now.
+ */
+ if ( op->oq_modrdn.rs_newSup != NULL ) {
+ if ( op->oq_modrdn.rs_newSup->bv_len ) {
+ np_dn = op->oq_modrdn.rs_newSup;
+ np_ndn = op->oq_modrdn.rs_nnewSup;
+
+ /* newSuperior == oldParent? - checked above */
+ /* newSuperior == entry being moved?, if so ==> ERROR */
+ if ( dnIsSuffix( np_ndn, &e->e_nname )) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "new superior not found";
+ goto return_results;
+ }
+ /* Get Entry with dn=newSuperior. Does newSuperior exist? */
+ rs->sr_err = mdb_dn2entry( op, txn, NULL, np_ndn, &np, NULL, 0 );
+
+ switch( rs->sr_err ) {
+ case 0:
+ break;
+ case MDB_NOTFOUND:
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": newSup(ndn=%s) not here!\n",
+ np_ndn->bv_val );
+ rs->sr_text = "new superior not found";
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* check newSuperior for "children" acl */
+ rs->sr_err = access_allowed( op, np, children,
+ NULL, ACL_WADD, NULL );
+
+ if( ! rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": no wr to newSup children\n" );
+ rs->sr_text = "no write access to new superior's children";
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": wr to new parent OK np=%p, id=%ld\n",
+ (void *) np, (long) np->e_id );
+
+ if ( is_entry_alias( np ) ) {
+ /* parent is an alias, don't allow add */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": entry is alias\n" );
+ rs->sr_text = "new superior is an alias";
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ goto return_results;
+ }
+
+ if ( is_entry_referral( np ) ) {
+ /* parent is a referral, don't allow add */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": entry is referral\n" );
+ rs->sr_text = "new superior is a referral";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+ np_dn = &np->e_name;
+
+ } else {
+ np_dn = NULL;
+
+ /* no parent, modrdn entry directly under root */
+ if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv )
+ || be_isupdate( op ) ) {
+ np = (Entry *)&slap_entry_root;
+
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, np,
+ children, NULL, ACL_WADD, NULL );
+
+ np = NULL;
+
+ if ( ! rs->sr_err ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ Debug( LDAP_DEBUG_TRACE,
+ "no access to new superior\n" );
+ rs->sr_text =
+ "no write access to new superior's children";
+ goto return_results;
+ }
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": wr to new parent's children OK\n" );
+
+ new_parent_dn = np_dn;
+ }
+
+ /* Make sure target entry doesn't exist already. */
+ Debug( LDAP_DEBUG_TRACE, LDAP_XSTRING(mdb_modrdn) ": new ndn=%s\n",
+ op->orr_nnewDN.bv_val );
+
+ /* Shortcut the search */
+ rs->sr_err = mdb_dn2id ( op, txn, NULL, &op->orr_nnewDN, &nid, NULL, NULL, NULL );
+ switch( rs->sr_err ) {
+ case MDB_NOTFOUND:
+ break;
+ case 0:
+ /* Allow rename to same DN */
+ if ( nid == e->e_id )
+ break;
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ goto return_results;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": pre-read failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* delete old DN
+ * If moving to a new parent, must delete current subtree count,
+ * otherwise leave it unchanged since we'll be adding it right back.
+ */
+ rs->sr_err = mdb_dn2id_delete( op, mc, e->e_id, np ? nsubs : 0 );
+ if ( rs->sr_err != 0 ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": dn2id del failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "DN index delete fail";
+ goto return_results;
+ }
+
+ /* copy the entry, then override some fields */
+ dummy = *e;
+ dummy.e_name = op->orr_newDN;
+ dummy.e_nname = op->orr_nnewDN;
+ dummy.e_attrs = NULL;
+
+ /* add new DN */
+ rs->sr_err = mdb_dn2id_add( op, mc, mc, np ? np->e_id : p->e_id,
+ nsubs, np != NULL, &dummy );
+ if ( rs->sr_err != 0 ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": dn2id add failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "DN index add failed";
+ goto return_results;
+ }
+
+ dummy.e_attrs = e->e_attrs;
+
+ if ( op->orr_modlist != NULL ) {
+ /* modify entry */
+ rs->sr_err = mdb_modify_internal( op, txn, op->orr_modlist, &dummy,
+ &rs->sr_text, textbuf, textlen );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": modify failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ goto return_results;
+ }
+ }
+
+ /* id2entry index */
+ rs->sr_err = mdb_id2entry_update( op, txn, NULL, &dummy );
+ if ( rs->sr_err != 0 ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": id2entry failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) {
+ rs->sr_text = "entry too big";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry update failed";
+ }
+ goto return_results;
+ }
+
+ if ( p_ndn.bv_len != 0 ) {
+ if ((parent_is_glue = is_entry_glue(p))) {
+ rs->sr_err = mdb_dn2id_children( op, txn, p );
+ if ( rs->sr_err != MDB_NOTFOUND ) {
+ switch( rs->sr_err ) {
+ case 0:
+ break;
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": has_children failed: %s (%d)\n",
+ mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ } else {
+ parent_is_leaf = 1;
+ }
+ }
+ mdb_entry_return( op, p );
+ p = NULL;
+ }
+
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, &dummy,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- " LDAP_XSTRING(mdb_modrdn)
+ ": post-read failed!\n" );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ if( moi == &opinfo ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ opinfo.moi_oe.oe_key = NULL;
+ if( op->o_noop ) {
+ mdb_txn_abort( txn );
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ txn = NULL;
+ goto return_results;
+
+ } else {
+ if(( rs->sr_err=mdb_txn_commit( txn )) != 0 ) {
+ rs->sr_text = "txn_commit failed";
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ txn = NULL;
+ }
+ }
+
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_modrdn) ": %s : %s (%d)\n",
+ rs->sr_text, mdb_strerror(rs->sr_err), rs->sr_err );
+ rs->sr_err = LDAP_OTHER;
+
+ goto return_results;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_modrdn)
+ ": rdn modified%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ dummy.e_id, op->o_req_dn.bv_val );
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ if ( e != NULL && dummy.e_attrs != e->e_attrs ) {
+ attrs_free( dummy.e_attrs );
+ }
+ send_ldap_result( op, rs );
+
+#if 0
+ if( rs->sr_err == LDAP_SUCCESS && mdb->bi_txn_cp_kbyte ) {
+ TXN_CHECKPOINT( mdb->bi_dbenv,
+ mdb->bi_txn_cp_kbyte, mdb->bi_txn_cp_min, 0 );
+ }
+#endif
+
+ if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) {
+ op->o_delete_glue_parent = 1;
+ }
+
+done:
+ slap_graduate_commit_csn( op );
+
+ /* LDAP v3 Support */
+ if( np != NULL ) {
+ /* free new parent */
+ mdb_entry_return( op, np );
+ }
+
+ if( p != NULL ) {
+ /* free parent */
+ mdb_entry_return( op, p );
+ }
+
+ /* free entry */
+ if( e != NULL ) {
+ mdb_entry_return( op, e );
+ }
+
+ if( moi == &opinfo ) {
+ if( txn != NULL ) {
+ mdb_txn_abort( txn );
+ }
+ if ( opinfo.moi_oe.oe_key ) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &opinfo.moi_oe, OpExtra, oe_next );
+ }
+ } else {
+ moi->moi_ref--;
+ }
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-mdb/monitor.c b/servers/slapd/back-mdb/monitor.c
new file mode 100644
index 0000000..fc77bc6
--- /dev/null
+++ b/servers/slapd/back-mdb/monitor.c
@@ -0,0 +1,807 @@
+/* monitor.c - monitor mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+#include <ac/stdlib.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include "lutil.h"
+#include "back-mdb.h"
+
+#include "../back-monitor/back-monitor.h"
+
+#include "slap-config.h"
+
+static ObjectClass *oc_olmMDBDatabase;
+
+static AttributeDescription *ad_olmDbDirectory;
+
+#ifdef MDB_MONITOR_IDX
+static int
+mdb_monitor_idx_entry_add(
+ struct mdb_info *mdb,
+ Entry *e );
+
+static AttributeDescription *ad_olmDbNotIndexed;
+#endif /* MDB_MONITOR_IDX */
+
+static AttributeDescription *ad_olmMDBPagesMax,
+ *ad_olmMDBPagesUsed, *ad_olmMDBPagesFree;
+
+static AttributeDescription *ad_olmMDBReadersMax,
+ *ad_olmMDBReadersUsed;
+
+static AttributeDescription *ad_olmMDBEntries;
+
+/*
+ * NOTE: there's some confusion in monitor OID arc;
+ * by now, let's consider:
+ *
+ * Subsystems monitor attributes 1.3.6.1.4.1.4203.666.1.55.0
+ * Databases monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1
+ * MDB database monitor attributes 1.3.6.1.4.1.4203.666.1.55.0.1.3
+ *
+ * Subsystems monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0
+ * Databases monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1
+ * MDB database monitor objectclasses 1.3.6.1.4.1.4203.666.3.16.0.1.3
+ */
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "olmMDBAttributes", "olmDatabaseAttributes:1" },
+ { "olmMDBObjectClasses", "olmDatabaseObjectClasses:1" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **ad;
+} s_at[] = {
+ { "( olmDatabaseAttributes:1 "
+ "NAME ( 'olmDbDirectory' ) "
+ "DESC 'Path name of the directory "
+ "where the database environment resides' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbDirectory },
+
+#ifdef MDB_MONITOR_IDX
+ { "( olmDatabaseAttributes:2 "
+ "NAME ( 'olmDbNotIndexed' ) "
+ "DESC 'Missing indexes resulting from candidate selection' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmDbNotIndexed },
+#endif /* MDB_MONITOR_IDX */
+
+ { "( olmMDBAttributes:1 "
+ "NAME ( 'olmMDBPagesMax' ) "
+ "DESC 'Maximum number of pages' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBPagesMax },
+
+ { "( olmMDBAttributes:2 "
+ "NAME ( 'olmMDBPagesUsed' ) "
+ "DESC 'Number of pages in use' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBPagesUsed },
+
+ { "( olmMDBAttributes:3 "
+ "NAME ( 'olmMDBPagesFree' ) "
+ "DESC 'Number of free pages' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBPagesFree },
+
+ { "( olmMDBAttributes:4 "
+ "NAME ( 'olmMDBReadersMax' ) "
+ "DESC 'Maximum number of readers' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBReadersMax },
+
+ { "( olmMDBAttributes:5 "
+ "NAME ( 'olmMDBReadersUsed' ) "
+ "DESC 'Number of readers in use' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBReadersUsed },
+
+ { "( olmMDBAttributes:6 "
+ "NAME ( 'olmMDBEntries' ) "
+ "DESC 'Number of entries in DB' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmMDBEntries },
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **oc;
+} s_oc[] = {
+ /* augments an existing object, so it must be AUXILIARY
+ * FIXME: derive from some ABSTRACT "monitoredEntity"? */
+ { "( olmMDBObjectClasses:2 "
+ "NAME ( 'olmMDBDatabase' ) "
+ "SUP top AUXILIARY "
+ "MAY ( "
+ "olmDbDirectory "
+#ifdef MDB_MONITOR_IDX
+ "$ olmDbNotIndexed "
+#endif /* MDB_MONITOR_IDX */
+ "$ olmMDBPagesMax $ olmMDBPagesUsed $ olmMDBPagesFree "
+ "$ olmMDBReadersMax $ olmMDBReadersUsed $ olmMDBEntries "
+ ") )",
+ &oc_olmMDBDatabase },
+
+ { NULL }
+};
+
+static int
+mdb_monitor_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ struct mdb_info *mdb = (struct mdb_info *) priv;
+ Attribute *a;
+ char buf[ BUFSIZ ];
+ struct berval bv;
+ MDB_stat mst;
+ MDB_envinfo mei;
+ MDB_txn *txn;
+ int rc;
+
+#ifdef MDB_MONITOR_IDX
+
+ mdb_monitor_idx_entry_add( mdb, e );
+#endif /* MDB_MONITOR_IDX */
+
+ mdb_env_stat( mdb->mi_dbenv, &mst );
+ mdb_env_info( mdb->mi_dbenv, &mei );
+
+ a = attr_find( e->e_attrs, ad_olmMDBPagesMax );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_mapsize / mst.ms_psize );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ a = attr_find( e->e_attrs, ad_olmMDBPagesUsed );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mei.me_last_pgno+1 );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ a = attr_find( e->e_attrs, ad_olmMDBReadersMax );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%u", mei.me_maxreaders );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ a = attr_find( e->e_attrs, ad_olmMDBReadersUsed );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%u", mei.me_numreaders );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &txn );
+ if ( !rc ) {
+ MDB_cursor *cursor;
+ MDB_val key, data;
+ size_t pages = 0, *iptr;
+
+ rc = mdb_cursor_open( txn, 0, &cursor );
+ if ( !rc ) {
+ while (( rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT )) == 0 ) {
+ iptr = data.mv_data;
+ pages += *iptr;
+ }
+ mdb_cursor_close( cursor );
+ }
+
+ mdb_stat( txn, mdb->mi_id2entry, &mst );
+ a = attr_find( e->e_attrs, ad_olmMDBEntries );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", mst.ms_entries );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ mdb_txn_abort( txn );
+
+ a = attr_find( e->e_attrs, ad_olmMDBPagesFree );
+ assert( a != NULL );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", pages );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+#if 0 /* uncomment if required */
+static int
+mdb_monitor_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ return SLAP_CB_CONTINUE;
+}
+#endif
+
+static int
+mdb_monitor_free(
+ Entry *e,
+ void **priv )
+{
+ struct berval values[ 2 ];
+ Modification mod = { 0 };
+
+ const char *text;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ int i, rc;
+
+ /* NOTE: if slap_shutdown != 0, priv might have already been freed */
+ *priv = NULL;
+
+ /* Remove objectClass */
+ mod.sm_op = LDAP_MOD_DELETE;
+ mod.sm_desc = slap_schema.si_ad_objectClass;
+ mod.sm_values = values;
+ mod.sm_numvals = 1;
+ values[ 0 ] = oc_olmMDBDatabase->soc_cname;
+ BER_BVZERO( &values[ 1 ] );
+
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_numvals = 0;
+ for ( i = 0; s_at[ i ].desc != NULL; i++ ) {
+ mod.sm_desc = *s_at[ i ].ad;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * call from within mdb_initialize()
+ */
+static int
+mdb_monitor_initialize( void )
+{
+ int i, code;
+ ConfigArgs c;
+ char *argv[ 3 ];
+
+ static int mdb_monitor_initialized = 0;
+
+ /* set to 0 when successfully initialized; otherwise, remember failure */
+ static int mdb_monitor_initialized_failure = 1;
+
+ if ( mdb_monitor_initialized++ ) {
+ return mdb_monitor_initialized_failure;
+ }
+
+ if ( backend_info( "monitor" ) == NULL ) {
+ return -1;
+ }
+
+ /* register schema here */
+
+ argv[ 0 ] = "back-mdb monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+
+ for ( i = 0; s_oid[ i ].name; i++ ) {
+ c.lineno = i;
+ argv[ 1 ] = s_oid[ i ].name;
+ argv[ 2 ] = s_oid[ i ].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize)
+ ": unable to add "
+ "objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid );
+ return 2;
+ }
+ }
+
+ for ( i = 0; s_at[ i ].desc != NULL; i++ ) {
+ code = register_at( s_at[ i ].desc, s_at[ i ].ad, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize)
+ ": register_at failed for attributeType (%s)\n",
+ s_at[ i ].desc );
+ return 3;
+
+ } else {
+ (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ for ( i = 0; s_oc[ i ].desc != NULL; i++ ) {
+ code = register_oc( s_oc[ i ].desc, s_oc[ i ].oc, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, LDAP_XSTRING(mdb_monitor_initialize)
+ ": register_oc failed for objectClass (%s)\n",
+ s_oc[ i ].desc );
+ return 4;
+
+ } else {
+ (*s_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+ }
+
+ return ( mdb_monitor_initialized_failure = LDAP_SUCCESS );
+}
+
+/*
+ * call from within mdb_db_init()
+ */
+int
+mdb_monitor_db_init( BackendDB *be )
+{
+#ifdef MDB_MONITOR_IDX
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+#endif /* MDB_MONITOR_IDX */
+
+ if ( mdb_monitor_initialize() == LDAP_SUCCESS ) {
+ /* monitoring in back-mdb is on by default */
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING;
+ }
+
+#ifdef MDB_MONITOR_IDX
+ mdb->mi_idx = NULL;
+ ldap_pvt_thread_mutex_init( &mdb->mi_idx_mutex );
+#endif /* MDB_MONITOR_IDX */
+
+ return 0;
+}
+
+/*
+ * call from within mdb_db_open()
+ */
+int
+mdb_monitor_db_open( BackendDB *be )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ Attribute *a, *next;
+ monitor_callback_t *cb = NULL;
+ int rc = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+
+ if ( !SLAP_DBMONITORING( be ) ) {
+ return 0;
+ }
+
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING;
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG, LDAP_XSTRING(mdb_monitor_db_open)
+ ": monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ return 0;
+ }
+
+ /* alloc as many as required (plus 1 for objectClass) */
+ a = attrs_alloc( 1 + 7 );
+ if ( a == NULL ) {
+ rc = 1;
+ goto cleanup;
+ }
+
+ a->a_desc = slap_schema.si_ad_objectClass;
+ attr_valadd( a, &oc_olmMDBDatabase->soc_cname, NULL, 1 );
+ next = a->a_next;
+
+ {
+ struct berval bv = BER_BVC( "0" );
+
+ next->a_desc = ad_olmMDBPagesMax;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_olmMDBPagesUsed;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_olmMDBPagesFree;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_olmMDBReadersMax;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_olmMDBReadersUsed;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_olmMDBEntries;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+ }
+
+ {
+ struct berval bv, nbv;
+ ber_len_t pathlen = 0, len = 0;
+ char path[ MAXPATHLEN ] = { '\0' };
+ char *fname = mdb->mi_dbenv_home,
+ *ptr;
+
+ len = strlen( fname );
+ if ( fname[ 0 ] != '/' ) {
+ /* get full path name */
+ getcwd( path, sizeof( path ) );
+ pathlen = strlen( path );
+
+ if ( fname[ 0 ] == '.' && fname[ 1 ] == '/' ) {
+ fname += 2;
+ len -= 2;
+ }
+ }
+
+ bv.bv_len = pathlen + STRLENOF( "/" ) + len;
+ ptr = bv.bv_val = ch_malloc( bv.bv_len + STRLENOF( "/" ) + 1 );
+ if ( pathlen ) {
+ ptr = lutil_strncopy( ptr, path, pathlen );
+ ptr[ 0 ] = '/';
+ ptr++;
+ }
+ ptr = lutil_strncopy( ptr, fname, len );
+ if ( ptr[ -1 ] != '/' ) {
+ ptr[ 0 ] = '/';
+ ptr++;
+ }
+ ptr[ 0 ] = '\0';
+
+ attr_normalize_one( ad_olmDbDirectory, &bv, &nbv, NULL );
+
+ next->a_desc = ad_olmDbDirectory;
+ next->a_vals = ch_calloc( sizeof( struct berval ), 2 );
+ next->a_vals[ 0 ] = bv;
+ next->a_numvals = 1;
+
+ if ( BER_BVISNULL( &nbv ) ) {
+ next->a_nvals = next->a_vals;
+
+ } else {
+ next->a_nvals = ch_calloc( sizeof( struct berval ), 2 );
+ next->a_nvals[ 0 ] = nbv;
+ }
+
+ next = next->a_next;
+ }
+
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = mdb_monitor_update;
+#if 0 /* uncomment if required */
+ cb->mc_modify = mdb_monitor_modify;
+#endif
+ cb->mc_free = mdb_monitor_free;
+ cb->mc_private = (void *)mdb;
+
+ /* make sure the database is registered; then add monitor attributes */
+ rc = mbe->register_database( be, &mdb->mi_monitor.mdm_ndn );
+ if ( rc == 0 ) {
+ rc = mbe->register_entry_attrs( &mdb->mi_monitor.mdm_ndn, a, cb,
+ NULL, -1, NULL );
+ }
+
+cleanup:;
+ if ( rc != 0 ) {
+ if ( cb != NULL ) {
+ ch_free( cb );
+ cb = NULL;
+ }
+
+ if ( a != NULL ) {
+ attrs_free( a );
+ a = NULL;
+ }
+ }
+
+ /* store for cleanup */
+ mdb->mi_monitor.mdm_cb = (void *)cb;
+
+ /* we don't need to keep track of the attributes, because
+ * mdb_monitor_free() takes care of everything */
+ if ( a != NULL ) {
+ attrs_free( a );
+ }
+
+ return rc;
+}
+
+/*
+ * call from within mdb_db_close()
+ */
+int
+mdb_monitor_db_close( BackendDB *be )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+
+ if ( !BER_BVISNULL( &mdb->mi_monitor.mdm_ndn ) ) {
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe;
+
+ if ( mi && mi->bi_extra ) {
+ struct berval dummy = BER_BVNULL;
+ mbe = mi->bi_extra;
+ mbe->unregister_entry_callback( &mdb->mi_monitor.mdm_ndn,
+ (monitor_callback_t *)mdb->mi_monitor.mdm_cb,
+ &dummy, 0, &dummy );
+ }
+
+ memset( &mdb->mi_monitor, 0, sizeof( mdb->mi_monitor ) );
+ }
+
+ return 0;
+}
+
+/*
+ * call from within mdb_db_destroy()
+ */
+int
+mdb_monitor_db_destroy( BackendDB *be )
+{
+#ifdef MDB_MONITOR_IDX
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+
+ /* TODO: free tree */
+ ldap_pvt_thread_mutex_destroy( &mdb->mi_idx_mutex );
+ ldap_avl_free( mdb->mi_idx, ch_free );
+#endif /* MDB_MONITOR_IDX */
+
+ return 0;
+}
+
+#ifdef MDB_MONITOR_IDX
+
+#define MDB_MONITOR_IDX_TYPES (4)
+
+typedef struct monitor_idx_t monitor_idx_t;
+
+struct monitor_idx_t {
+ AttributeDescription *idx_ad;
+ unsigned long idx_count[MDB_MONITOR_IDX_TYPES];
+};
+
+static int
+mdb_monitor_bitmask2key( slap_mask_t bitmask )
+{
+ int key;
+
+ for ( key = 0; key < 8 * (int)sizeof(slap_mask_t) && !( bitmask & 0x1U );
+ key++ )
+ bitmask >>= 1;
+
+ return key;
+}
+
+static struct berval idxbv[] = {
+ BER_BVC( "present=" ),
+ BER_BVC( "equality=" ),
+ BER_BVC( "approx=" ),
+ BER_BVC( "substr=" ),
+ BER_BVNULL
+};
+
+static ber_len_t
+mdb_monitor_idx2len( monitor_idx_t *idx )
+{
+ int i;
+ ber_len_t len = 0;
+
+ for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) {
+ if ( idx->idx_count[ i ] != 0 ) {
+ len += idxbv[i].bv_len;
+ }
+ }
+
+ return len;
+}
+
+static int
+monitor_idx_cmp( const void *p1, const void *p2 )
+{
+ const monitor_idx_t *idx1 = (const monitor_idx_t *)p1;
+ const monitor_idx_t *idx2 = (const monitor_idx_t *)p2;
+
+ return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad );
+}
+
+static int
+monitor_idx_dup( void *p1, void *p2 )
+{
+ monitor_idx_t *idx1 = (monitor_idx_t *)p1;
+ monitor_idx_t *idx2 = (monitor_idx_t *)p2;
+
+ return SLAP_PTRCMP( idx1->idx_ad, idx2->idx_ad ) == 0 ? -1 : 0;
+}
+
+int
+mdb_monitor_idx_add(
+ struct mdb_info *mdb,
+ AttributeDescription *desc,
+ slap_mask_t type )
+{
+ monitor_idx_t idx_dummy = { 0 },
+ *idx;
+ int rc = 0, key;
+
+ idx_dummy.idx_ad = desc;
+ key = mdb_monitor_bitmask2key( type ) - 1;
+ if ( key >= MDB_MONITOR_IDX_TYPES ) {
+ /* invalid index type */
+ return -1;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex );
+
+ idx = (monitor_idx_t *)ldap_avl_find( mdb->mi_idx,
+ (caddr_t)&idx_dummy, monitor_idx_cmp );
+ if ( idx == NULL ) {
+ idx = (monitor_idx_t *)ch_calloc( sizeof( monitor_idx_t ), 1 );
+ idx->idx_ad = desc;
+ idx->idx_count[ key ] = 1;
+
+ switch ( ldap_avl_insert( &mdb->mi_idx, (caddr_t)idx,
+ monitor_idx_cmp, monitor_idx_dup ) )
+ {
+ case 0:
+ break;
+
+ default:
+ ch_free( idx );
+ rc = -1;
+ }
+
+ } else {
+ idx->idx_count[ key ]++;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex );
+
+ return rc;
+}
+
+static int
+mdb_monitor_idx_apply( void *v_idx, void *v_valp )
+{
+ monitor_idx_t *idx = (monitor_idx_t *)v_idx;
+ BerVarray *valp = (BerVarray *)v_valp;
+
+ struct berval bv;
+ char *ptr;
+ char count_buf[ MDB_MONITOR_IDX_TYPES ][ SLAP_TEXT_BUFLEN ];
+ ber_len_t count_len[ MDB_MONITOR_IDX_TYPES ],
+ idx_len;
+ int i, num = 0;
+
+ idx_len = mdb_monitor_idx2len( idx );
+
+ bv.bv_len = 0;
+ for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) {
+ if ( idx->idx_count[ i ] == 0 ) {
+ continue;
+ }
+
+ count_len[ i ] = snprintf( count_buf[ i ],
+ sizeof( count_buf[ i ] ), "%lu", idx->idx_count[ i ] );
+ bv.bv_len += count_len[ i ];
+ num++;
+ }
+
+ bv.bv_len += idx->idx_ad->ad_cname.bv_len
+ + num
+ + idx_len;
+ ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( ptr, idx->idx_ad->ad_cname.bv_val );
+ for ( i = 0; i < MDB_MONITOR_IDX_TYPES; i++ ) {
+ if ( idx->idx_count[ i ] == 0 ) {
+ continue;
+ }
+
+ ptr[ 0 ] = '#';
+ ++ptr;
+ ptr = lutil_strcopy( ptr, idxbv[ i ].bv_val );
+ ptr = lutil_strcopy( ptr, count_buf[ i ] );
+ }
+
+ ber_bvarray_add( valp, &bv );
+
+ return 0;
+}
+
+static int
+mdb_monitor_idx_entry_add(
+ struct mdb_info *mdb,
+ Entry *e )
+{
+ BerVarray vals = NULL;
+ Attribute *a;
+
+ a = attr_find( e->e_attrs, ad_olmDbNotIndexed );
+
+ ldap_pvt_thread_mutex_lock( &mdb->mi_idx_mutex );
+
+ ldap_avl_apply( mdb->mi_idx, mdb_monitor_idx_apply,
+ &vals, -1, AVL_INORDER );
+
+ ldap_pvt_thread_mutex_unlock( &mdb->mi_idx_mutex );
+
+ if ( vals != NULL ) {
+ if ( a != NULL ) {
+ assert( a->a_nvals == a->a_vals );
+
+ ber_bvarray_free( a->a_vals );
+
+ } else {
+ Attribute **ap;
+
+ for ( ap = &e->e_attrs; *ap != NULL; ap = &(*ap)->a_next )
+ ;
+ *ap = attr_alloc( ad_olmDbNotIndexed );
+ a = *ap;
+ }
+ a->a_vals = vals;
+ a->a_nvals = a->a_vals;
+ }
+
+ return 0;
+}
+
+#endif /* MDB_MONITOR_IDX */
diff --git a/servers/slapd/back-mdb/nextid.c b/servers/slapd/back-mdb/nextid.c
new file mode 100644
index 0000000..b342b48
--- /dev/null
+++ b/servers/slapd/back-mdb/nextid.c
@@ -0,0 +1,53 @@
+/* init.c - initialize mdb backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *out )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ int rc;
+ ID id = 0;
+ MDB_val key;
+
+ rc = mdb_cursor_get(mc, &key, NULL, MDB_LAST);
+
+ switch(rc) {
+ case MDB_NOTFOUND:
+ rc = 0;
+ *out = 1;
+ break;
+ case 0:
+ memcpy( &id, key.mv_data, sizeof( id ));
+ *out = ++id;
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "=> mdb_next_id: get failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto done;
+ }
+ mdb->mi_nextid = *out;
+
+done:
+ return rc;
+}
diff --git a/servers/slapd/back-mdb/operational.c b/servers/slapd/back-mdb/operational.c
new file mode 100644
index 0000000..e2d4495
--- /dev/null
+++ b/servers/slapd/back-mdb/operational.c
@@ -0,0 +1,121 @@
+/* operational.c - mdb backend operational attributes function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-mdb.h"
+
+/*
+ * sets *hasSubordinates to LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE
+ * if the entry has children or not.
+ */
+int
+mdb_hasSubordinates(
+ Operation *op,
+ Entry *e,
+ int *hasSubordinates )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ MDB_txn *rtxn;
+ mdb_op_info opinfo = {{{0}}}, *moi = &opinfo;
+ int rc;
+
+ assert( e != NULL );
+
+ rc = mdb_opinfo_get(op, mdb, 1, &moi);
+ switch(rc) {
+ case 0:
+ break;
+ default:
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ rtxn = moi->moi_txn;
+
+ rc = mdb_dn2id_children( op, rtxn, e );
+
+ switch( rc ) {
+ case 0:
+ *hasSubordinates = LDAP_COMPARE_TRUE;
+ break;
+
+ case MDB_NOTFOUND:
+ *hasSubordinates = LDAP_COMPARE_FALSE;
+ rc = LDAP_SUCCESS;
+ break;
+
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<=- " LDAP_XSTRING(mdb_hasSubordinates)
+ ": has_children failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ rc = LDAP_OTHER;
+ }
+
+done:;
+ if ( moi == &opinfo ) {
+ mdb_txn_reset( moi->moi_txn );
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ } else {
+ moi->moi_ref--;
+ }
+ return rc;
+}
+
+/*
+ * sets the supported operational attributes (if required)
+ */
+int
+mdb_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ Attribute **ap;
+
+ assert( rs->sr_entry != NULL );
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) {
+ break;
+ }
+ }
+
+ if ( *ap == NULL &&
+ attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL &&
+ ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) )
+ {
+ int hasSubordinates, rc;
+
+ rc = mdb_hasSubordinates( op, rs->sr_entry, &hasSubordinates );
+ if ( rc == LDAP_SUCCESS ) {
+ *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE );
+ assert( *ap != NULL );
+
+ ap = &(*ap)->a_next;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-mdb/proto-mdb.h b/servers/slapd/back-mdb/proto-mdb.h
new file mode 100644
index 0000000..58191e1
--- /dev/null
+++ b/servers/slapd/back-mdb/proto-mdb.h
@@ -0,0 +1,413 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _PROTO_MDB_H
+#define _PROTO_MDB_H
+
+LDAP_BEGIN_DECL
+
+#define MDB_UCTYPE "MDB"
+
+/*
+ * attr.c
+ */
+
+AttrInfo *mdb_attr_mask( struct mdb_info *mdb,
+ AttributeDescription *desc );
+
+void mdb_attr_flush( struct mdb_info *mdb );
+
+int mdb_attr_slot( struct mdb_info *mdb,
+ AttributeDescription *desc, int *insert );
+
+int mdb_attr_dbs_open( BackendDB *be, MDB_txn *txn, struct config_reply_s *cr );
+void mdb_attr_dbs_close( struct mdb_info *mdb );
+
+int mdb_attr_index_config LDAP_P(( struct mdb_info *mdb,
+ const char *fname, int lineno,
+ int argc, char **argv, struct config_reply_s *cr ));
+
+void mdb_attr_index_unparse LDAP_P(( struct mdb_info *mdb, BerVarray *bva ));
+void mdb_attr_index_destroy LDAP_P(( struct mdb_info *mdb ));
+void mdb_attr_index_free LDAP_P(( struct mdb_info *mdb,
+ AttributeDescription *ad ));
+
+int mdb_attr_multi_config LDAP_P(( struct mdb_info *mdb,
+ const char *fname, int lineno,
+ int argc, char **argv, struct config_reply_s *cr ));
+
+void mdb_attr_multi_unparse LDAP_P(( struct mdb_info *mdb, BerVarray *bva ));
+
+void mdb_attr_multi_thresh LDAP_P(( struct mdb_info *mdb, AttributeDescription *ad,
+ unsigned *hi, unsigned *lo ));
+
+void mdb_attr_info_free( AttrInfo *ai );
+
+int mdb_ad_read( struct mdb_info *mdb, MDB_txn *txn );
+int mdb_ad_get( struct mdb_info *mdb, MDB_txn *txn, AttributeDescription *ad );
+void mdb_ad_unwind( struct mdb_info *mdb, int prev_ads );
+
+/*
+ * config.c
+ */
+
+int mdb_back_init_cf( BackendInfo *bi );
+int mdb_resume_index( BackendDB *be, MDB_txn *txn );
+void mdb_start_index_task( BackendDB *be );
+
+/*
+ * dn2entry.c
+ */
+
+int mdb_dn2entry LDAP_P(( Operation *op, MDB_txn *tid, MDB_cursor *mc,
+ struct berval *dn, Entry **e, ID *nsubs, int matched ));
+
+/*
+ * dn2id.c
+ */
+
+int mdb_dn2id(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor *mc,
+ struct berval *ndn,
+ ID *id,
+ ID *nsubs,
+ struct berval *matched,
+ struct berval *nmatched );
+
+int mdb_dn2id_add(
+ Operation *op,
+ MDB_cursor *mcp,
+ MDB_cursor *mcd,
+ ID pid,
+ ID nsubs,
+ int upsub,
+ Entry *e );
+
+int mdb_dn2id_delete(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ ID nsubs );
+
+int mdb_dn2id_children(
+ Operation *op,
+ MDB_txn *tid,
+ Entry *e );
+
+int mdb_dn2sups (
+ Operation *op,
+ MDB_txn *tid,
+ struct berval *dn,
+ ID *sups
+ );
+
+int mdb_dn2idl(
+ Operation *op,
+ MDB_txn *txn,
+ struct berval *ndn,
+ ID eid,
+ ID *ids,
+ ID *stack );
+
+int mdb_dn2id_parent(
+ Operation *op,
+ MDB_txn *txn,
+ ID eid,
+ ID *idp );
+
+int mdb_id2name(
+ Operation *op,
+ MDB_txn *txn,
+ MDB_cursor **cursp,
+ ID eid,
+ struct berval *name,
+ struct berval *nname);
+
+int mdb_idscope(
+ Operation *op,
+ MDB_txn *txn,
+ ID base,
+ ID *ids,
+ ID *res );
+
+struct IdScopes;
+
+int mdb_idscopes(
+ Operation *op,
+ struct IdScopes *isc );
+
+int mdb_idscopechk(
+ Operation *op,
+ struct IdScopes *isc );
+
+int mdb_dn2id_walk(
+ Operation *op,
+ struct IdScopes *isc );
+
+void mdb_dn2id_wrestore(
+ Operation *op,
+ struct IdScopes *isc );
+
+MDB_cmp_func mdb_dup_compare;
+
+/*
+ * filterentry.c
+ */
+
+int mdb_filter_candidates(
+ Operation *op,
+ MDB_txn *txn,
+ Filter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack );
+
+/*
+ * id2entry.c
+ */
+
+MDB_cmp_func mdb_id2v_compare;
+MDB_cmp_func mdb_id2v_dupsort;
+
+int mdb_id2entry_add(
+ Operation *op,
+ MDB_txn *tid,
+ MDB_cursor *mc,
+ Entry *e );
+
+int mdb_id2entry_update(
+ Operation *op,
+ MDB_txn *tid,
+ MDB_cursor *mc,
+ Entry *e );
+
+int mdb_id2entry_delete(
+ BackendDB *be,
+ MDB_txn *tid,
+ Entry *e);
+
+int mdb_id2entry(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ Entry **e);
+
+int mdb_id2edata(
+ Operation *op,
+ MDB_cursor *mc,
+ ID id,
+ MDB_val *data);
+
+int mdb_entry_return( Operation *op, Entry *e );
+BI_entry_release_rw mdb_entry_release;
+BI_entry_get_rw mdb_entry_get;
+BI_op_txn mdb_txn;
+
+int mdb_entry_decode( Operation *op, MDB_txn *txn, MDB_val *data, ID id, Entry **e );
+
+void mdb_reader_flush( MDB_env *env );
+int mdb_opinfo_get( Operation *op, struct mdb_info *mdb, int rdonly, mdb_op_info **moi );
+
+int mdb_mval_put(Operation *op, MDB_cursor *mc, ID id, Attribute *a);
+int mdb_mval_del(Operation *op, MDB_cursor *mc, ID id, Attribute *a);
+
+/*
+ * idl.c
+ */
+
+unsigned mdb_idl_search( ID *ids, ID id );
+
+int mdb_idl_fetch_key(
+ BackendDB *be,
+ MDB_txn *txn,
+ MDB_dbi dbi,
+ MDB_val *key,
+ ID *ids,
+ MDB_cursor **saved_cursor,
+ int get_flag );
+
+int mdb_idl_insert( ID *ids, ID id );
+
+typedef int (mdb_idl_keyfunc)(
+ BackendDB *be,
+ MDB_cursor *mc,
+ struct berval *key,
+ ID id );
+
+mdb_idl_keyfunc mdb_idl_insert_keys;
+mdb_idl_keyfunc mdb_idl_delete_keys;
+
+int
+mdb_idl_intersection(
+ ID *a,
+ ID *b );
+
+int
+mdb_idl_union(
+ ID *a,
+ ID *b );
+
+ID mdb_idl_first( ID *ids, ID *cursor );
+ID mdb_idl_next( ID *ids, ID *cursor );
+
+void mdb_idl_sort( ID *ids, ID *tmp );
+int mdb_idl_append( ID *a, ID *b );
+int mdb_idl_append_one( ID *ids, ID id );
+
+
+/*
+ * index.c
+ */
+
+extern AttrInfo *
+mdb_index_mask LDAP_P((
+ Backend *be,
+ AttributeDescription *desc,
+ struct berval *name ));
+
+extern int
+mdb_index_param LDAP_P((
+ Backend *be,
+ AttributeDescription *desc,
+ int ftype,
+ MDB_dbi *dbi,
+ slap_mask_t *mask,
+ struct berval *prefix ));
+
+extern int
+mdb_index_values LDAP_P((
+ Operation *op,
+ MDB_txn *txn,
+ AttributeDescription *desc,
+ BerVarray vals,
+ ID id,
+ int opid ));
+
+extern int
+mdb_index_recset LDAP_P((
+ struct mdb_info *mdb,
+ Attribute *a,
+ AttributeType *type,
+ struct berval *tags,
+ IndexRec *ir ));
+
+extern int
+mdb_index_recrun LDAP_P((
+ Operation *op,
+ MDB_txn *txn,
+ struct mdb_info *mdb,
+ IndexRec *ir,
+ ID id,
+ int base ));
+
+int mdb_index_entry LDAP_P(( Operation *op, MDB_txn *t, int r, Entry *e ));
+
+#define mdb_index_entry_add(op,t,e) \
+ mdb_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e))
+#define mdb_index_entry_del(op,t,e) \
+ mdb_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e))
+
+/*
+ * key.c
+ */
+
+extern int
+mdb_key_read(
+ Backend *be,
+ MDB_txn *txn,
+ MDB_dbi dbi,
+ struct berval *k,
+ ID *ids,
+ MDB_cursor **saved_cursor,
+ int get_flags );
+
+/*
+ * nextid.c
+ */
+
+int mdb_next_id( BackendDB *be, MDB_cursor *mc, ID *id );
+
+/*
+ * modify.c
+ */
+
+int mdb_modify_internal(
+ Operation *op,
+ MDB_txn *tid,
+ Modifications *modlist,
+ Entry *e,
+ const char **text,
+ char *textbuf,
+ size_t textlen );
+
+/*
+ * monitor.c
+ */
+
+int mdb_monitor_db_init( BackendDB *be );
+int mdb_monitor_db_open( BackendDB *be );
+int mdb_monitor_db_close( BackendDB *be );
+int mdb_monitor_db_destroy( BackendDB *be );
+
+#ifdef MDB_MONITOR_IDX
+int
+mdb_monitor_idx_add(
+ struct mdb_info *mdb,
+ AttributeDescription *desc,
+ slap_mask_t type );
+#endif /* MDB_MONITOR_IDX */
+
+/*
+ * former external.h
+ */
+
+extern BI_init mdb_back_initialize;
+
+extern BI_db_config mdb_db_config;
+
+extern BI_op_add mdb_add;
+extern BI_op_bind mdb_bind;
+extern BI_op_compare mdb_compare;
+extern BI_op_delete mdb_delete;
+extern BI_op_modify mdb_modify;
+extern BI_op_modrdn mdb_modrdn;
+extern BI_op_search mdb_search;
+extern BI_op_extended mdb_extended;
+
+extern BI_chk_referrals mdb_referrals;
+
+extern BI_operational mdb_operational;
+
+extern BI_has_subordinates mdb_hasSubordinates;
+
+/* tools.c */
+extern BI_tool_entry_open mdb_tool_entry_open;
+extern BI_tool_entry_close mdb_tool_entry_close;
+extern BI_tool_entry_first_x mdb_tool_entry_first_x;
+extern BI_tool_entry_next mdb_tool_entry_next;
+extern BI_tool_entry_get mdb_tool_entry_get;
+extern BI_tool_entry_put mdb_tool_entry_put;
+extern BI_tool_entry_reindex mdb_tool_entry_reindex;
+extern BI_tool_dn2id_get mdb_tool_dn2id_get;
+extern BI_tool_entry_modify mdb_tool_entry_modify;
+extern BI_tool_entry_delete mdb_tool_entry_delete;
+
+extern mdb_idl_keyfunc mdb_tool_idl_add;
+
+LDAP_END_DECL
+
+#endif /* _PROTO_MDB_H */
diff --git a/servers/slapd/back-mdb/referral.c b/servers/slapd/back-mdb/referral.c
new file mode 100644
index 0000000..3164890
--- /dev/null
+++ b/servers/slapd/back-mdb/referral.c
@@ -0,0 +1,151 @@
+/* referral.c - MDB backend referral handler */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+
+int
+mdb_referrals( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ Entry *e = NULL;
+ int rc = LDAP_SUCCESS;
+
+ MDB_txn *rtxn;
+ mdb_op_info opinfo = {0}, *moi = &opinfo;
+
+ if( op->o_tag == LDAP_REQ_SEARCH ) {
+ /* let search take care of itself */
+ return rc;
+ }
+
+ if( get_manageDSAit( op ) ) {
+ /* let op take care of DSA management */
+ return rc;
+ }
+
+ rc = mdb_opinfo_get(op, mdb, 1, &moi);
+ switch(rc) {
+ case 0:
+ break;
+ default:
+ return LDAP_OTHER;
+ }
+
+ rtxn = moi->moi_txn;
+
+ /* get entry */
+ rc = mdb_dn2entry( op, rtxn, &op->o_req_ndn, &e, 1 );
+
+ switch(rc) {
+ case MDB_NOTFOUND:
+ case 0:
+ break;
+ case LDAP_BUSY:
+ rs->sr_text = "ldap server busy";
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_referrals)
+ ": dn2entry failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ rs->sr_text = "internal error";
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( rc == MDB_NOTFOUND ) {
+ rc = LDAP_SUCCESS;
+ rs->sr_matched = NULL;
+ if ( e != NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_referrals)
+ ": tag=%lu target=\"%s\" matched=\"%s\"\n",
+ (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val );
+
+ if( is_entry_referral( e ) ) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rc = LDAP_OTHER;
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ if ( rs->sr_ref ) {
+ rs->sr_matched = ber_strdup_x(
+ e->e_name.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ mdb_entry_return( op, e );
+ e = NULL;
+ }
+
+ if( rs->sr_ref != NULL ) {
+ /* send referrals */
+ rc = rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ } else if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = rs->sr_matched ? "bad referral object" : NULL;
+ }
+
+ if (rs->sr_matched) {
+ op->o_tmpfree( (char *)rs->sr_matched, op->o_tmpmemctx );
+ rs->sr_matched = NULL;
+ }
+ goto done;
+ }
+
+ if ( is_entry_referral( e ) ) {
+ /* entry is a referral */
+ BerVarray refs = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite(
+ refs, &e->e_name, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_referrals)
+ ": tag=%lu target=\"%s\" matched=\"%s\"\n",
+ (unsigned long)op->o_tag, op->o_req_dn.bv_val, e->e_name.bv_val );
+
+ rs->sr_matched = e->e_name.bv_val;
+ if( rs->sr_ref != NULL ) {
+ rc = rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ } else {
+ rc = LDAP_OTHER;
+ rs->sr_text = "bad referral object";
+ }
+
+ rs->sr_matched = NULL;
+ ber_bvarray_free( refs );
+ }
+
+done:
+ if ( moi == &opinfo ) {
+ mdb_txn_reset( moi->moi_txn );
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ } else {
+ moi->moi_ref--;
+ }
+ if ( e )
+ mdb_entry_return( op, e );
+ return rc;
+}
diff --git a/servers/slapd/back-mdb/search.c b/servers/slapd/back-mdb/search.c
new file mode 100644
index 0000000..a3d629c
--- /dev/null
+++ b/servers/slapd/back-mdb/search.c
@@ -0,0 +1,1541 @@
+/* search.c - search operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-mdb.h"
+#include "idl.h"
+
+static int base_candidate(
+ BackendDB *be,
+ Entry *e,
+ ID *ids );
+
+static int search_candidates(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ IdScopes *isc,
+ MDB_cursor *mci,
+ ID *ids,
+ ID *stack );
+
+static int parse_paged_cookie( Operation *op, SlapReply *rs );
+
+static void send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid,
+ int tentries );
+
+/* Dereference aliases for a single alias entry. Return the final
+ * dereferenced entry on success, NULL on any failure.
+ */
+static Entry * deref_base (
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ Entry **matched,
+ MDB_txn *txn,
+ ID *tmp,
+ ID *visited )
+{
+ struct berval ndn;
+
+ rs->sr_err = LDAP_ALIAS_DEREF_PROBLEM;
+ rs->sr_text = "maximum deref depth exceeded";
+
+ for (;;) {
+ /* Remember the last entry we looked at, so we can
+ * report broken links
+ */
+ *matched = e;
+
+ if (MDB_IDL_N(tmp) >= op->o_bd->be_max_deref_depth) {
+ e = NULL;
+ break;
+ }
+
+ /* If this is part of a subtree or onelevel search,
+ * have we seen this ID before? If so, quit.
+ */
+ if ( visited && mdb_idl_insert( visited, e->e_id ) ) {
+ e = NULL;
+ break;
+ }
+
+ /* If we've seen this ID during this deref iteration,
+ * we've hit a loop.
+ */
+ if ( mdb_idl_insert( tmp, e->e_id ) ) {
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ rs->sr_text = "circular alias";
+ e = NULL;
+ break;
+ }
+
+ /* If there was a problem getting the aliasedObjectName,
+ * get_alias_dn will have set the error status.
+ */
+ if ( get_alias_dn(e, &ndn, &rs->sr_err, &rs->sr_text) ) {
+ e = NULL;
+ break;
+ }
+
+ rs->sr_err = mdb_dn2entry( op, txn, NULL, &ndn, &e, NULL, 0 );
+ if (rs->sr_err) {
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ rs->sr_text = "aliasedObject not found";
+ break;
+ }
+
+ /* Free the previous entry, continue to work with the
+ * one we just retrieved.
+ */
+ mdb_entry_return( op, *matched );
+
+ /* We found a regular entry. Return this to the caller.
+ */
+ if (!is_entry_alias(e)) {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ break;
+ }
+ }
+ return e;
+}
+
+/* Look for and dereference all aliases within the search scope.
+ * Requires "stack" to be able to hold 6 levels of DB_SIZE IDLs.
+ * Of course we're hardcoded to require a minimum of 8 UM_SIZE
+ * IDLs so this is never a problem.
+ */
+static int search_aliases(
+ Operation *op,
+ SlapReply *rs,
+ ID e_id,
+ IdScopes *isc,
+ MDB_cursor *mci,
+ ID *stack )
+{
+ ID *aliases, *curscop, *visited, *newsubs, *oldsubs, *tmp;
+ ID cursora, ida, cursoro, ido;
+ Entry *matched, *a;
+ struct berval bv_alias = BER_BVC( "alias" );
+ AttributeAssertion aa_alias = ATTRIBUTEASSERTION_INIT;
+ Filter af;
+
+ aliases = stack; /* IDL of all aliases in the database */
+ curscop = aliases + MDB_idl_db_size; /* Aliases in the current scope */
+ visited = curscop + MDB_idl_db_size; /* IDs we've seen in this search */
+ newsubs = visited + MDB_idl_db_size; /* New subtrees we've added */
+ oldsubs = newsubs + MDB_idl_db_size; /* Subtrees added previously */
+ tmp = oldsubs + MDB_idl_db_size; /* Scratch space for deref_base() */
+
+ af.f_choice = LDAP_FILTER_EQUALITY;
+ af.f_ava = &aa_alias;
+ af.f_av_desc = slap_schema.si_ad_objectClass;
+ af.f_av_value = bv_alias;
+ af.f_next = NULL;
+
+ /* Find all aliases in database */
+ MDB_IDL_ZERO( aliases );
+ rs->sr_err = mdb_filter_candidates( op, isc->mt, &af, aliases,
+ curscop, visited );
+ if (rs->sr_err != LDAP_SUCCESS || MDB_IDL_IS_ZERO( aliases )) {
+ return rs->sr_err;
+ }
+ if ( op->ors_limit /* isroot == FALSE */ &&
+ op->ors_limit->lms_s_unchecked != -1 &&
+ MDB_IDL_N( aliases ) > (unsigned) op->ors_limit->lms_s_unchecked )
+ {
+ return LDAP_ADMINLIMIT_EXCEEDED;
+ }
+ oldsubs[0] = 1;
+ oldsubs[1] = e_id;
+
+ MDB_IDL_ZERO( visited );
+ MDB_IDL_ZERO( newsubs );
+
+ cursoro = 0;
+ ido = mdb_idl_first( oldsubs, &cursoro );
+
+ for (;;) {
+ /* Set curscop to only the aliases in the current scope. Start with
+ * all the aliases, then get the intersection with the scope.
+ */
+ rs->sr_err = mdb_idscope( op, isc->mt, e_id, aliases, curscop );
+
+ /* Dereference all of the aliases in the current scope. */
+ cursora = 0;
+ for (ida = mdb_idl_first(curscop, &cursora); ida != NOID;
+ ida = mdb_idl_next(curscop, &cursora))
+ {
+ rs->sr_err = mdb_id2entry(op, mci, ida, &a);
+ if (rs->sr_err != LDAP_SUCCESS) {
+ continue;
+ }
+
+ /* This should only happen if the curscop IDL has maxed out and
+ * turned into a range that spans IDs indiscriminately
+ */
+ if (!is_entry_alias(a)) {
+ mdb_entry_return(op, a);
+ continue;
+ }
+
+ /* Actually dereference the alias */
+ MDB_IDL_ZERO(tmp);
+ a = deref_base( op, rs, a, &matched, isc->mt,
+ tmp, visited );
+ if (a) {
+ /* If the target was not already in our current scopes,
+ * make note of it in the newsubs list.
+ */
+ ID2 mid;
+ mid.mid = a->e_id;
+ mid.mval.mv_data = NULL;
+ if (op->ors_scope == LDAP_SCOPE_SUBTREE) {
+ isc->id = a->e_id;
+ /* if ID is a child of any of our current scopes,
+ * ignore it, it's already included.
+ */
+ if (mdb_idscopechk(op, isc))
+ goto skip;
+ }
+ if (mdb_id2l_insert(isc->scopes, &mid) == 0) {
+ mdb_idl_insert(newsubs, a->e_id);
+ }
+skip: mdb_entry_return( op, a );
+
+ } else if (matched) {
+ /* Alias could not be dereferenced, or it deref'd to
+ * an ID we've already seen. Ignore it.
+ */
+ mdb_entry_return( op, matched );
+ rs->sr_text = NULL;
+ rs->sr_err = 0;
+ }
+ }
+ /* If this is a OneLevel search, we're done; oldsubs only had one
+ * ID in it. For a Subtree search, oldsubs may be a list of scope IDs.
+ */
+ if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) break;
+nextido:
+ ido = mdb_idl_next( oldsubs, &cursoro );
+
+ /* If we're done processing the old scopes, did we add any new
+ * scopes in this iteration? If so, go back and do those now.
+ */
+ if (ido == NOID) {
+ if (MDB_IDL_IS_ZERO(newsubs)) break;
+ MDB_IDL_CPY(oldsubs, newsubs);
+ MDB_IDL_ZERO(newsubs);
+ cursoro = 0;
+ ido = mdb_idl_first( oldsubs, &cursoro );
+ }
+
+ /* Find the entry corresponding to the next scope. If it can't
+ * be found, ignore it and move on. This should never happen;
+ * we should never see the ID of an entry that doesn't exist.
+ */
+ {
+ MDB_val edata;
+ rs->sr_err = mdb_id2edata(op, mci, ido, &edata);
+ if ( rs->sr_err != MDB_SUCCESS ) {
+ goto nextido;
+ }
+ e_id = ido;
+ }
+ }
+ return rs->sr_err;
+}
+
+/* Get the next ID from the DB. Used if the candidate list is
+ * a range and simple iteration hits missing entryIDs
+ */
+static int
+mdb_get_nextid(MDB_cursor *mci, ID *cursor)
+{
+ MDB_val key;
+ ID id;
+ int rc;
+
+ id = *cursor + 1;
+ key.mv_data = &id;
+ key.mv_size = sizeof(ID);
+ rc = mdb_cursor_get( mci, &key, NULL, MDB_SET_RANGE );
+ if ( rc )
+ return rc;
+ memcpy( cursor, key.mv_data, sizeof(ID));
+ return 0;
+}
+
+static void scope_chunk_free( void *key, void *data )
+{
+ ID2 *p1, *p2;
+ for (p1 = data; p1; p1 = p2) {
+ p2 = p1[0].mval.mv_data;
+ ber_memfree_x(p1, NULL);
+ }
+}
+
+static ID2 *scope_chunk_get( Operation *op )
+{
+ ID2 *ret = NULL;
+
+ ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get,
+ (void *)&ret, NULL );
+ if ( !ret ) {
+ ret = ch_malloc( MDB_idl_um_size * sizeof( ID2 ));
+ } else {
+ void *r2 = ret[0].mval.mv_data;
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get,
+ r2, scope_chunk_free, NULL, NULL );
+ }
+ return ret;
+}
+
+static void scope_chunk_ret( Operation *op, ID2 *scopes )
+{
+ void *ret = NULL;
+
+ ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)scope_chunk_get,
+ &ret, NULL );
+ scopes[0].mval.mv_data = ret;
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)scope_chunk_get,
+ (void *)scopes, scope_chunk_free, NULL, NULL );
+}
+
+static void *search_stack( Operation *op );
+
+typedef struct ww_ctx {
+ MDB_txn *txn;
+ MDB_cursor *mcd; /* if set, save cursor context */
+ ID key;
+ MDB_val data;
+ int flag;
+ unsigned nentries;
+} ww_ctx;
+
+/* ITS#7904 if we get blocked while writing results to client,
+ * release the current reader txn and reacquire it after we
+ * unblock.
+ * Slight problem - if we're doing a scope-based walk (mdb_dn2id_walk)
+ * to return results, we need to remember the state of the mcd cursor.
+ * If the node that cursor was pointing to gets deleted while we're
+ * blocked, we may be unable to restore the cursor position. In that
+ * case return an LDAP_BUSY error - let the client know this search
+ * couldn't succeed, but might succeed on a retry.
+ */
+static void
+mdb_rtxn_snap( Operation *op, ww_ctx *ww )
+{
+ /* save cursor position and release read txn */
+ if ( ww->mcd ) {
+ MDB_val key, data;
+ mdb_cursor_get( ww->mcd, &key, &data, MDB_GET_CURRENT );
+ memcpy( &ww->key, key.mv_data, sizeof(ID) );
+ ww->data.mv_size = data.mv_size;
+ ww->data.mv_data = op->o_tmpalloc( data.mv_size, op->o_tmpmemctx );
+ memcpy(ww->data.mv_data, data.mv_data, data.mv_size);
+ }
+ mdb_txn_reset( ww->txn );
+ ww->flag = 1;
+}
+
+static void
+mdb_writewait( Operation *op, slap_callback *sc )
+{
+ ww_ctx *ww = sc->sc_private;
+ if ( !ww->flag ) {
+ mdb_rtxn_snap( op, ww );
+ }
+}
+
+static int
+mdb_waitfixup( Operation *op, ww_ctx *ww, MDB_cursor *mci, MDB_cursor *mcd, IdScopes *isc )
+{
+ MDB_val key;
+ int rc = 0;
+ ww->flag = 0;
+ mdb_txn_renew( ww->txn );
+ mdb_cursor_renew( ww->txn, mci );
+ mdb_cursor_renew( ww->txn, mcd );
+
+ key.mv_size = sizeof(ID);
+ if ( ww->mcd ) { /* scope-based search using dn2id_walk */
+ MDB_val data;
+
+ if ( isc->numrdns )
+ mdb_dn2id_wrestore( op, isc );
+
+ key.mv_data = &ww->key;
+ data = ww->data;
+ rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH );
+ if ( rc == MDB_NOTFOUND ) {
+ data = ww->data;
+ rc = mdb_cursor_get( mcd, &key, &data, MDB_GET_BOTH_RANGE );
+ /* the loop will skip this node using NEXT_DUP but we want it
+ * sent, so go back one space first
+ */
+ if ( rc == MDB_SUCCESS )
+ mdb_cursor_get( mcd, &key, &data, MDB_PREV_DUP );
+ else
+ rc = LDAP_BUSY;
+ } else if ( rc ) {
+ rc = LDAP_OTHER;
+ }
+ op->o_tmpfree( ww->data.mv_data, op->o_tmpmemctx );
+ ww->data.mv_data = NULL;
+ } else if ( isc->scopes[0].mid > 1 ) { /* candidate-based search */
+ int i;
+ for ( i=1; i<=isc->scopes[0].mid; i++ ) {
+ if ( !isc->scopes[i].mval.mv_data )
+ continue;
+ key.mv_data = &isc->scopes[i].mid;
+ mdb_cursor_get( mcd, &key, &isc->scopes[i].mval, MDB_SET );
+ }
+ }
+ return rc;
+}
+
+int
+mdb_search( Operation *op, SlapReply *rs )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ ID id, cursor, nsubs, ncand, cscope;
+ ID lastid = NOID;
+ ID *candidates, *iscopes, *c0;
+ ID2 *scopes;
+ void *stack;
+ Entry *e = NULL, *base = NULL;
+ Entry *matched = NULL;
+ AttributeName *attrs;
+ slap_mask_t mask;
+ time_t stoptime;
+ int manageDSAit;
+ int tentries = 0;
+ int admincheck = 0;
+ IdScopes isc;
+ MDB_cursor *mci, *mcd;
+ ww_ctx wwctx;
+ slap_callback cb = { 0 };
+
+ mdb_op_info opinfo = {{{0}}}, *moi = &opinfo;
+ MDB_txn *ltid = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_search) "\n" );
+ attrs = op->oq_search.rs_attrs;
+
+ manageDSAit = get_manageDSAit( op );
+
+ rs->sr_err = mdb_opinfo_get( op, mdb, 1, &moi );
+ switch(rs->sr_err) {
+ case 0:
+ break;
+ default:
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return rs->sr_err;
+ }
+
+ ltid = moi->moi_txn;
+
+ rs->sr_err = mdb_cursor_open( ltid, mdb->mi_id2entry, &mci );
+ if ( rs->sr_err ) {
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return rs->sr_err;
+ }
+
+ rs->sr_err = mdb_cursor_open( ltid, mdb->mi_dn2id, &mcd );
+ if ( rs->sr_err ) {
+ mdb_cursor_close( mci );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return rs->sr_err;
+ }
+
+ scopes = scope_chunk_get( op );
+ candidates = c0 = search_stack( op );
+ iscopes = candidates + MDB_idl_um_size;
+ stack = iscopes + MDB_idl_db_size;
+ /* if candidates already in use, alloc a new array */
+ if ( c0[0] ) {
+ candidates = ch_malloc(( MDB_idl_um_size + MDB_idl_db_size ) * sizeof ( ID ));
+ iscopes = candidates + MDB_idl_um_size;
+ }
+ isc.mt = ltid;
+ isc.mc = mcd;
+ isc.scopes = scopes;
+ isc.oscope = op->ors_scope;
+ isc.sctmp = stack;
+
+ if ( op->ors_deref & LDAP_DEREF_FINDING ) {
+ MDB_IDL_ZERO(candidates);
+ }
+dn2entry_retry:
+ /* get entry with reader lock */
+ rs->sr_err = mdb_dn2entry( op, ltid, mcd, &op->o_req_ndn, &e, &nsubs, 1 );
+
+ switch(rs->sr_err) {
+ case MDB_NOTFOUND:
+ matched = e;
+ e = NULL;
+ break;
+ case 0:
+ break;
+ case LDAP_BUSY:
+ send_ldap_error( op, rs, LDAP_BUSY, "ldap server busy" );
+ goto done;
+ default:
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ goto done;
+ }
+
+ if ( op->ors_deref & LDAP_DEREF_FINDING ) {
+ if ( matched && is_entry_alias( matched )) {
+ struct berval stub;
+
+ stub.bv_val = op->o_req_ndn.bv_val;
+ stub.bv_len = op->o_req_ndn.bv_len - matched->e_nname.bv_len - 1;
+ e = deref_base( op, rs, matched, &matched, ltid,
+ candidates, NULL );
+ if ( e ) {
+ build_new_dn( &op->o_req_ndn, &e->e_nname, &stub,
+ op->o_tmpmemctx );
+ mdb_entry_return(op, e);
+ matched = NULL;
+ goto dn2entry_retry;
+ }
+ } else if ( e && is_entry_alias( e )) {
+ e = deref_base( op, rs, e, &matched, ltid,
+ candidates, NULL );
+ }
+ }
+
+ if ( e == NULL ) {
+ struct berval matched_dn = BER_BVNULL;
+
+ if ( matched != NULL ) {
+ BerVarray erefs = NULL;
+
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, matched,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ ber_dupbv( &matched_dn, &matched->e_name );
+
+ erefs = is_entry_referral( matched )
+ ? get_entry_referrals( op, matched )
+ : NULL;
+ if ( rs->sr_err == MDB_NOTFOUND )
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = matched_dn.bv_val;
+ }
+
+ mdb_entry_return(op, matched);
+ matched = NULL;
+
+ if ( erefs ) {
+ rs->sr_ref = referral_rewrite( erefs, &matched_dn,
+ &op->o_req_dn, op->oq_search.rs_scope );
+ ber_bvarray_free( erefs );
+ }
+
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, op->oq_search.rs_scope );
+ rs->sr_err = rs->sr_ref != NULL ? LDAP_REFERRAL : LDAP_NO_SUCH_OBJECT;
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ if ( !BER_BVISNULL( &matched_dn ) ) {
+ ber_memfree( matched_dn.bv_val );
+ rs->sr_matched = NULL;
+ }
+ goto done;
+ }
+
+ /* NOTE: __NEW__ "search" access is required
+ * on searchBase object */
+ if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_SEARCH, NULL, &mask ) )
+ {
+ if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ mdb_entry_return( op,e);
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral */
+ struct berval matched_dn = BER_BVNULL;
+ BerVarray erefs = NULL;
+
+ ber_dupbv( &matched_dn, &e->e_name );
+ erefs = get_entry_referrals( op, e );
+
+ rs->sr_err = LDAP_REFERRAL;
+
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ if ( erefs ) {
+ rs->sr_ref = referral_rewrite( erefs, &matched_dn,
+ &op->o_req_dn, op->oq_search.rs_scope );
+ ber_bvarray_free( erefs );
+
+ if ( !rs->sr_ref ) {
+ rs->sr_text = "bad_referral object";
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search) ": entry is referral\n" );
+
+ rs->sr_matched = matched_dn.bv_val;
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ ber_memfree( matched_dn.bv_val );
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ mdb_entry_return( op,e);
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* compute it anyway; root does not use it */
+ stoptime = op->o_time + op->ors_tlimit;
+
+ base = e;
+
+ e = NULL;
+
+ /* select candidates */
+ if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) {
+ rs->sr_err = base_candidate( op->o_bd, base, candidates );
+ scopes[0].mid = 0;
+ ncand = 1;
+ } else {
+ if ( op->ors_scope == LDAP_SCOPE_ONELEVEL ) {
+ size_t nkids;
+ MDB_val key, data;
+ key.mv_data = &base->e_id;
+ key.mv_size = sizeof( ID );
+ mdb_cursor_get( mcd, &key, &data, MDB_SET );
+ mdb_cursor_count( mcd, &nkids );
+ nsubs = nkids - 1;
+ } else if ( !base->e_id ) {
+ /* we don't maintain nsubs for entryID 0.
+ * just grab entry count from id2entry stat
+ */
+ MDB_stat ms;
+ mdb_stat( ltid, mdb->mi_id2entry, &ms );
+ nsubs = ms.ms_entries;
+ }
+ MDB_IDL_ZERO( candidates );
+ scopes[0].mid = 1;
+ scopes[1].mid = base->e_id;
+ scopes[1].mval.mv_data = NULL;
+ rs->sr_err = search_candidates( op, rs, base,
+ &isc, mci, candidates, stack );
+
+ if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ) {
+adminlimit:
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ goto done;
+ }
+
+ ncand = MDB_IDL_N( candidates );
+ if ( !base->e_id || ncand == NOID ) {
+ /* grab entry count from id2entry stat
+ */
+ MDB_stat ms;
+ mdb_stat( ltid, mdb->mi_id2entry, &ms );
+ if ( !base->e_id )
+ nsubs = ms.ms_entries;
+ if ( ncand == NOID )
+ ncand = ms.ms_entries;
+ }
+ }
+
+ /* start cursor at beginning of candidates.
+ */
+ cursor = 0;
+
+ if ( candidates[0] == 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search) ": no candidates\n" );
+
+ goto nochange;
+ }
+
+ /* if not root and candidates exceed to-be-checked entries, abort */
+ if ( op->ors_limit /* isroot == FALSE */ &&
+ op->ors_limit->lms_s_unchecked != -1 &&
+ ncand > (unsigned) op->ors_limit->lms_s_unchecked )
+ {
+ admincheck = 1;
+ }
+
+ if ( op->ors_limit == NULL /* isroot == TRUE */ ||
+ !op->ors_limit->lms_s_pr_hide )
+ {
+ tentries = ncand;
+ }
+
+ wwctx.flag = 0;
+ wwctx.nentries = 0;
+ /* If we're running in our own read txn */
+ if ( moi == &opinfo ) {
+ cb.sc_writewait = mdb_writewait;
+ cb.sc_private = &wwctx;
+ wwctx.txn = ltid;
+ wwctx.mcd = NULL;
+ cb.sc_next = op->o_callback;
+ op->o_callback = &cb;
+ }
+
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ PagedResultsState *ps = op->o_pagedresults_state;
+ /* deferred cookie parsing */
+ rs->sr_err = parse_paged_cookie( op, rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ cursor = (ID) ps->ps_cookie;
+ if ( cursor && ps->ps_size == 0 ) {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = "search abandoned by pagedResult size=0";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( admincheck )
+ goto adminlimit;
+
+ id = mdb_idl_first( candidates, &cursor );
+ if ( id == NOID ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search)
+ ": no paged results candidates\n" );
+ send_paged_response( op, rs, &lastid, 0 );
+
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ if ( id == (ID)ps->ps_cookie )
+ id = mdb_idl_next( candidates, &cursor );
+ nsubs = ncand; /* always bypass scope'd search */
+ goto loop_begin;
+ }
+ if ( nsubs < ncand ) {
+ int rc;
+ /* Do scope-based search */
+ if ( admincheck && nsubs > (unsigned) op->ors_limit->lms_s_unchecked )
+ goto adminlimit;
+
+ /* if any alias scopes were set, save them */
+ if (scopes[0].mid > 1) {
+ cursor = 1;
+ for (cscope = 1; cscope <= scopes[0].mid; cscope++) {
+ /* Ignore the original base */
+ if (scopes[cscope].mid == base->e_id)
+ continue;
+ iscopes[cursor++] = scopes[cscope].mid;
+ }
+ iscopes[0] = scopes[0].mid - 1;
+ } else {
+ iscopes[0] = 0;
+ }
+
+ wwctx.mcd = mcd;
+ isc.id = base->e_id;
+ isc.numrdns = 0;
+ rc = mdb_dn2id_walk( op, &isc );
+ if ( rc )
+ id = NOID;
+ else
+ id = isc.id;
+ cscope = 0;
+ } else {
+ if ( admincheck )
+ goto adminlimit;
+ id = mdb_idl_first( candidates, &cursor );
+ }
+
+ while (id != NOID)
+ {
+ int scopeok;
+ MDB_val edata;
+
+loop_begin:
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* mostly needed by internal searches,
+ * e.g. related to syncrepl, for whom
+ * abandon does not get set... */
+ if ( slapd_shutdown ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ send_ldap_disconnect( op, rs );
+ goto done;
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
+ rs->sr_ref = rs->sr_v2ref;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ goto done;
+ }
+
+
+ if ( nsubs < ncand ) {
+ unsigned i;
+ /* Is this entry in the candidate list? */
+ scopeok = 0;
+ if (MDB_IDL_IS_RANGE( candidates )) {
+ if ( id >= MDB_IDL_RANGE_FIRST( candidates ) &&
+ id <= MDB_IDL_RANGE_LAST( candidates ))
+ scopeok = 1;
+ } else {
+ i = mdb_idl_search( candidates, id );
+ if (i <= candidates[0] && candidates[i] == id )
+ scopeok = 1;
+ }
+ if ( scopeok )
+ goto scopeok;
+ goto loop_continue;
+ }
+
+ /* Does this candidate actually satisfy the search scope?
+ */
+ scopeok = 0;
+ isc.numrdns = 0;
+ switch( op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ /* This is always true, yes? */
+ if ( id == base->e_id ) scopeok = 1;
+ break;
+
+#ifdef LDAP_SCOPE_CHILDREN
+ case LDAP_SCOPE_CHILDREN:
+ if ( id == base->e_id ) break;
+ /* Fall-thru */
+#endif
+ case LDAP_SCOPE_SUBTREE:
+ if ( id == base->e_id ) {
+ scopeok = 1;
+ break;
+ }
+ /* Fall-thru */
+ case LDAP_SCOPE_ONELEVEL:
+ if ( id == base->e_id ) break;
+ isc.id = id;
+ isc.nscope = 0;
+ rs->sr_err = mdb_idscopes( op, &isc );
+ if ( rs->sr_err == MDB_SUCCESS ) {
+ if ( isc.nscope )
+ scopeok = 1;
+ } else {
+ if ( rs->sr_err == MDB_NOTFOUND )
+ goto notfound;
+ }
+ break;
+ }
+
+ /* Not in scope, ignore it */
+ if ( !scopeok )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search)
+ ": %ld scope not okay\n",
+ (long) id );
+ goto loop_continue;
+ }
+
+scopeok:
+ if ( id == base->e_id ) {
+ e = base;
+ } else {
+
+ /* get the entry */
+ rs->sr_err = mdb_id2edata( op, mci, id, &edata );
+ if ( rs->sr_err == MDB_NOTFOUND ) {
+notfound:
+ if( nsubs < ncand )
+ goto loop_continue;
+
+ if( !MDB_IDL_IS_RANGE(candidates) ) {
+ /* only complain for non-range IDLs */
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search)
+ ": candidate %ld not found\n",
+ (long) id );
+ } else {
+ /* get the next ID from the DB */
+ rs->sr_err = mdb_get_nextid( mci, &cursor );
+ if ( rs->sr_err == MDB_NOTFOUND ) {
+ break;
+ }
+ if ( rs->sr_err ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error in get_nextid";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ cursor--;
+ }
+
+ goto loop_continue;
+ } else if ( rs->sr_err ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error in mdb_id2edata";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ rs->sr_err = mdb_entry_decode( op, ltid, &edata, id, &e );
+ if ( rs->sr_err ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error in mdb_entry_decode";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ e->e_id = id;
+ e->e_name.bv_val = NULL;
+ e->e_nname.bv_val = NULL;
+ }
+
+ if ( is_entry_subentry( e ) ) {
+ if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) {
+ if(!get_subentries_visibility( op )) {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ } else if ( get_subentries( op ) &&
+ !get_subentries_visibility( op ))
+ {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ } else if ( get_subentries_visibility( op )) {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ /* aliases were already dereferenced in candidate list */
+ if ( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ /* but if the search base is an alias, and we didn't
+ * deref it when finding, return it.
+ */
+ if ( is_entry_alias(e) &&
+ ((op->ors_deref & LDAP_DEREF_FINDING) || e != base ))
+ {
+ goto loop_continue;
+ }
+ }
+
+ if ( !manageDSAit && is_entry_glue( e )) {
+ goto loop_continue;
+ }
+
+ if (e != base) {
+ struct berval pdn, pndn;
+ char *d, *n;
+ int i;
+
+ /* child of base, just append RDNs to base->e_name */
+ if ( nsubs < ncand || isc.scopes[isc.nscope].mid == base->e_id ) {
+ pdn = base->e_name;
+ pndn = base->e_nname;
+ } else {
+ mdb_id2name( op, ltid, &isc.mc, scopes[isc.nscope].mid, &pdn, &pndn );
+ }
+ e->e_name.bv_len = pdn.bv_len;
+ e->e_nname.bv_len = pndn.bv_len;
+ for (i=0; i<isc.numrdns; i++) {
+ e->e_name.bv_len += isc.rdns[i].bv_len + 1;
+ e->e_nname.bv_len += isc.nrdns[i].bv_len + 1;
+ }
+ e->e_name.bv_val = op->o_tmpalloc(e->e_name.bv_len + 1, op->o_tmpmemctx);
+ e->e_nname.bv_val = op->o_tmpalloc(e->e_nname.bv_len + 1, op->o_tmpmemctx);
+ d = e->e_name.bv_val;
+ n = e->e_nname.bv_val;
+ if (nsubs < ncand) {
+ /* RDNs are in top-down order */
+ for (i=isc.numrdns-1; i>=0; i--) {
+ memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len);
+ d += isc.rdns[i].bv_len;
+ *d++ = ',';
+ memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len);
+ n += isc.nrdns[i].bv_len;
+ *n++ = ',';
+ }
+ } else {
+ /* RDNs are in bottom-up order */
+ for (i=0; i<isc.numrdns; i++) {
+ memcpy(d, isc.rdns[i].bv_val, isc.rdns[i].bv_len);
+ d += isc.rdns[i].bv_len;
+ *d++ = ',';
+ memcpy(n, isc.nrdns[i].bv_val, isc.nrdns[i].bv_len);
+ n += isc.nrdns[i].bv_len;
+ *n++ = ',';
+ }
+ }
+
+ if (pdn.bv_len) {
+ memcpy(d, pdn.bv_val, pdn.bv_len+1);
+ memcpy(n, pndn.bv_val, pndn.bv_len+1);
+ } else {
+ *--d = '\0';
+ *--n = '\0';
+ e->e_name.bv_len--;
+ e->e_nname.bv_len--;
+ }
+ if (pndn.bv_val != base->e_nname.bv_val) {
+ op->o_tmpfree(pndn.bv_val, op->o_tmpmemctx);
+ op->o_tmpfree(pdn.bv_val, op->o_tmpmemctx);
+ }
+ }
+
+ /*
+ * if it's a referral, add it to the list of referrals. only do
+ * this for non-base searches, and don't check the filter
+ * explicitly here since it's only a candidate anyway.
+ */
+ if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE
+ && is_entry_referral( e ) )
+ {
+ BerVarray erefs = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL,
+ op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL
+ ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE );
+
+ rs->sr_entry = e;
+ rs->sr_flags = 0;
+
+ send_search_reference( op, rs );
+
+ if (e != base)
+ mdb_entry_return( op, e );
+ rs->sr_entry = NULL;
+ e = NULL;
+
+ ber_bvarray_free( rs->sr_ref );
+ ber_bvarray_free( erefs );
+ rs->sr_ref = NULL;
+
+ goto loop_continue;
+ }
+
+ /* if it matches the filter and scope, send it */
+ rs->sr_err = test_filter( op, e, op->oq_search.rs_filter );
+
+ if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ /* check size limit */
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) {
+ if (e != base)
+ mdb_entry_return( op, e );
+ e = NULL;
+ send_paged_response( op, rs, &lastid, tentries );
+ goto done;
+ }
+ lastid = id;
+ }
+
+ if (e) {
+ /* safe default */
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_ctrls = NULL;
+ rs->sr_entry = e;
+ RS_ASSERT( e->e_private != NULL );
+ rs->sr_flags = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_err = send_search_entry( op, rs );
+ rs->sr_attrs = NULL;
+ rs->sr_entry = NULL;
+ if (e != base)
+ mdb_entry_return( op, e );
+ e = NULL;
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS: /* entry sent ok */
+ break;
+ default: /* entry not sent */
+ break;
+ case LDAP_BUSY:
+ send_ldap_result( op, rs );
+ goto done;
+ case LDAP_UNAVAILABLE:
+ case LDAP_SIZELIMIT_EXCEEDED:
+ if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) {
+ rs->sr_ref = rs->sr_v2ref;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ }
+ goto done;
+ }
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(mdb_search)
+ ": %ld does not match filter\n",
+ (long) id );
+ }
+
+loop_continue:
+ if ( moi == &opinfo && !wwctx.flag && mdb->mi_rtxn_size ) {
+ wwctx.nentries++;
+ if ( wwctx.nentries >= mdb->mi_rtxn_size ) {
+ MDB_envinfo ei;
+ wwctx.nentries = 0;
+ mdb_env_info(mdb->mi_dbenv, &ei);
+ if ( ei.me_last_txnid > mdb_txn_id( ltid ))
+ mdb_rtxn_snap( op, &wwctx );
+ }
+ }
+ if ( wwctx.flag ) {
+ rs->sr_err = mdb_waitfixup( op, &wwctx, mci, mcd, &isc );
+ if ( rs->sr_err ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if( e != NULL ) {
+ if ( e != base )
+ mdb_entry_return( op, e );
+ RS_ASSERT( rs->sr_entry == NULL );
+ e = NULL;
+ rs->sr_entry = NULL;
+ }
+
+ if ( nsubs < ncand ) {
+ int rc = mdb_dn2id_walk( op, &isc );
+ if (rc) {
+ id = NOID;
+ /* We got to the end of a subtree. If there are any
+ * alias scopes left, search them too.
+ */
+ while (iscopes[0] && cscope < iscopes[0]) {
+ cscope++;
+ isc.id = iscopes[cscope];
+ if ( base )
+ mdb_entry_return( op, base );
+ rs->sr_err = mdb_id2entry(op, mci, isc.id, &base);
+ if ( !rs->sr_err ) {
+ mdb_id2name( op, ltid, &isc.mc, isc.id, &base->e_name, &base->e_nname );
+ isc.numrdns = 0;
+ if (isc.oscope == LDAP_SCOPE_ONELEVEL)
+ isc.oscope = LDAP_SCOPE_BASE;
+ rc = mdb_dn2id_walk( op, &isc );
+ if ( !rc ) {
+ id = isc.id;
+ break;
+ }
+ }
+ }
+ } else
+ id = isc.id;
+ } else {
+ id = mdb_idl_next( candidates, &cursor );
+ }
+ }
+
+nochange:
+ rs->sr_ctrls = NULL;
+ rs->sr_ref = rs->sr_v2ref;
+ rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL;
+ rs->sr_rspoid = NULL;
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ send_paged_response( op, rs, NULL, 0 );
+ } else {
+ send_ldap_result( op, rs );
+ }
+
+done:
+ if ( cb.sc_private ) {
+ /* remove our writewait callback */
+ slap_callback **scp = &op->o_callback;
+ while ( *scp ) {
+ if ( *scp == &cb ) {
+ *scp = cb.sc_next;
+ cb.sc_private = NULL;
+ break;
+ }
+ }
+ }
+ mdb_cursor_close( mcd );
+ mdb_cursor_close( mci );
+ if ( moi == &opinfo ) {
+ mdb_txn_reset( moi->moi_txn );
+ LDAP_SLIST_REMOVE( &op->o_extra, &moi->moi_oe, OpExtra, oe_next );
+ } else {
+ moi->moi_ref--;
+ }
+ if( rs->sr_v2ref ) {
+ ber_bvarray_free( rs->sr_v2ref );
+ rs->sr_v2ref = NULL;
+ }
+ if (base)
+ mdb_entry_return( op, base );
+ scope_chunk_ret( op, scopes );
+ if ( candidates != c0 ) {
+ ch_free( candidates );
+ } else {
+ MDB_IDL_ZERO( candidates );
+ }
+
+ return rs->sr_err;
+}
+
+
+static int base_candidate(
+ BackendDB *be,
+ Entry *e,
+ ID *ids )
+{
+ Debug(LDAP_DEBUG_ARGS, "base_candidates: base: \"%s\" (0x%08lx)\n",
+ e->e_nname.bv_val, (long) e->e_id );
+
+ ids[0] = 1;
+ ids[1] = e->e_id;
+ return 0;
+}
+
+/* Look for "objectClass Present" in this filter.
+ * Also count depth of filter tree while we're at it.
+ */
+static int oc_filter(
+ Filter *f,
+ int cur,
+ int *max )
+{
+ int rc = 0;
+
+ assert( f != NULL );
+
+ if( cur > *max ) *max = cur;
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_PRESENT:
+ if (f->f_desc == slap_schema.si_ad_objectClass) {
+ rc = 1;
+ }
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ cur++;
+ for ( f=f->f_and; f; f=f->f_next ) {
+ (void) oc_filter(f, cur, max);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return rc;
+}
+
+typedef struct IDLchunk {
+ unsigned int logn;
+ unsigned int pad;
+} IDLchunk;
+
+static void search_stack_free( void *key, void *data )
+{
+ ber_memfree_x(data, NULL);
+}
+
+static void *search_stack( Operation *op )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ IDLchunk *ic = NULL;
+
+ if ( op->o_threadctx ) {
+ ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack,
+ (void **)&ic, NULL );
+ } else {
+ ic = mdb->mi_search_stack;
+ }
+
+ if ( ic && ic->logn != MDB_idl_logn ) {
+ ber_memfree_x( ic, NULL );
+ ic = NULL;
+ }
+
+ if ( !ic ) {
+ ic = ch_malloc(( mdb->mi_search_stack_depth + 2 ) * (size_t)MDB_idl_um_size
+ * sizeof( ID ) + sizeof( IDLchunk ) );
+ ic->logn = MDB_idl_logn;
+ if ( op->o_threadctx ) {
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack,
+ ic, search_stack_free, NULL, NULL );
+ } else {
+ mdb->mi_search_stack = ic;
+ }
+ ID *idl = (ID *)( ic+1 );
+ MDB_IDL_ZERO( idl );
+ }
+ return ic+1;
+}
+
+static int search_candidates(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ IdScopes *isc,
+ MDB_cursor *mci,
+ ID *ids,
+ ID *stack )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+ int rc, depth = 1;
+ Filter *f, rf, xf, nf, sf;
+ AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT;
+ AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT;
+
+ /*
+ * This routine takes as input a filter (user-filter)
+ * and rewrites it as follows:
+ * (&(scope=DN)[(objectClass=subentry)]
+ * (|[(objectClass=referral)](user-filter))
+ */
+
+ Debug(LDAP_DEBUG_TRACE,
+ "search_candidates: base=\"%s\" (0x%08lx) scope=%d\n",
+ e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope );
+
+ f = op->oq_search.rs_filter;
+
+ /* If the user's filter uses objectClass=*,
+ * these clauses are redundant.
+ */
+ if (!oc_filter(op->oq_search.rs_filter, 1, &depth)
+ && !get_subentries_visibility(op)) {
+ if( !get_manageDSAit(op) && !get_domainScope(op) ) {
+ /* match referral objects */
+ struct berval bv_ref = BER_BVC( "referral" );
+ rf.f_choice = LDAP_FILTER_EQUALITY;
+ rf.f_ava = &aa_ref;
+ rf.f_av_desc = slap_schema.si_ad_objectClass;
+ rf.f_av_value = bv_ref;
+ rf.f_next = f;
+ xf.f_or = &rf;
+ xf.f_choice = LDAP_FILTER_OR;
+ xf.f_next = NULL;
+ f = &xf;
+ depth++;
+ }
+ }
+
+ if( get_subentries_visibility( op ) ) {
+ struct berval bv_subentry = BER_BVC( "subentry" );
+ sf.f_choice = LDAP_FILTER_EQUALITY;
+ sf.f_ava = &aa_subentry;
+ sf.f_av_desc = slap_schema.si_ad_objectClass;
+ sf.f_av_value = bv_subentry;
+ sf.f_next = f;
+ nf.f_choice = LDAP_FILTER_AND;
+ nf.f_and = &sf;
+ nf.f_next = NULL;
+ f = &nf;
+ depth++;
+ }
+
+ /* Allocate IDL stack, plus 1 more for former tmp */
+ if ( depth+1 > mdb->mi_search_stack_depth ) {
+ stack = ch_malloc( (depth + 1) * MDB_idl_um_size * sizeof( ID ) );
+ }
+
+ if( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ rc = search_aliases( op, rs, e->e_id, isc, mci, stack );
+ } else {
+ rc = LDAP_SUCCESS;
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = mdb_filter_candidates( op, isc->mt, f, ids,
+ stack, stack+MDB_idl_um_size );
+ }
+
+ if ( depth+1 > mdb->mi_search_stack_depth ) {
+ ch_free( stack );
+ }
+
+ if( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "mdb_search_candidates: failed (rc=%d)\n",
+ rc );
+
+ } else {
+ Debug(LDAP_DEBUG_TRACE,
+ "mdb_search_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) MDB_IDL_FIRST(ids),
+ (long) MDB_IDL_LAST(ids) );
+ }
+
+ return rc;
+}
+
+static int
+parse_paged_cookie( Operation *op, SlapReply *rs )
+{
+ int rc = LDAP_SUCCESS;
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* this function must be invoked only if the pagedResults
+ * control has been detected, parsed and partially checked
+ * by the frontend */
+ assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED );
+
+ /* cookie decoding/checks deferred to backend... */
+ if ( ps->ps_cookieval.bv_len ) {
+ PagedResultsCookie reqcookie;
+ if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie ));
+
+ if ( reqcookie > ps->ps_cookie ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+
+ } else if ( reqcookie < ps->ps_cookie ) {
+ rs->sr_text = "paged results cookie is invalid or old";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ } else {
+ /* we're going to use ps_cookie */
+ op->o_conn->c_pagedresults_state.ps_cookie = 0;
+ }
+
+done:;
+
+ return rc;
+}
+
+static void
+send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid,
+ int tentries )
+{
+ LDAPControl *ctrls[2];
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie respcookie;
+ struct berval cookie;
+
+ Debug(LDAP_DEBUG_ARGS,
+ "send_paged_response: lastid=0x%08lx nentries=%d\n",
+ lastid ? *lastid : 0, rs->sr_nentries );
+
+ ctrls[1] = NULL;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( lastid ) {
+ respcookie = ( PagedResultsCookie )(*lastid);
+ cookie.bv_len = sizeof( respcookie );
+ cookie.bv_val = (char *)&respcookie;
+
+ } else {
+ respcookie = ( PagedResultsCookie )0;
+ BER_BVSTR( &cookie, "" );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = respcookie;
+ op->o_conn->c_pagedresults_state.ps_count =
+ ((PagedResultsState *)op->o_pagedresults_state)->ps_count +
+ rs->sr_nentries;
+
+ /* return size of 0 -- no estimate */
+ ber_printf( ber, "{iO}", 0, &cookie );
+
+ ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx );
+ if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) {
+ goto done;
+ }
+
+ ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrls[0]->ldctl_iscritical = 0;
+
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+
+done:
+ (void) ber_free_buf( ber );
+}
diff --git a/servers/slapd/back-mdb/tools.c b/servers/slapd/back-mdb/tools.c
new file mode 100644
index 0000000..3a468b2
--- /dev/null
+++ b/servers/slapd/back-mdb/tools.c
@@ -0,0 +1,1741 @@
+/* tools.c - tools for slap tools */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2011-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/errno.h>
+
+#define AVL_INTERNAL
+#include "back-mdb.h"
+#include "idl.h"
+
+#ifdef MDB_TOOL_IDL_CACHING
+static int mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn );
+
+#define IDBLOCK 1024
+
+typedef struct mdb_tool_idl_cache_entry {
+ struct mdb_tool_idl_cache_entry *next;
+ ID ids[IDBLOCK];
+} mdb_tool_idl_cache_entry;
+
+typedef struct mdb_tool_idl_cache {
+ struct berval kstr;
+ mdb_tool_idl_cache_entry *head, *tail;
+ ID first, last;
+ int count;
+ short offset;
+ short flags;
+} mdb_tool_idl_cache;
+#define WAS_FOUND 0x01
+#define WAS_RANGE 0x02
+
+#define MDB_TOOL_IDL_FLUSH(be, txn) mdb_tool_idl_flush(be, txn)
+#else
+#define MDB_TOOL_IDL_FLUSH(be, txn)
+#endif /* MDB_TOOL_IDL_CACHING */
+
+MDB_txn *mdb_tool_txn = NULL;
+
+static MDB_txn *txi = NULL;
+static MDB_cursor *cursor = NULL, *idcursor = NULL;
+static MDB_cursor *mcp = NULL, *mcd = NULL;
+static MDB_val key, data;
+static ID previd = NOID;
+
+static int reindexing;
+
+typedef struct dn_id {
+ ID id;
+ struct berval dn;
+} dn_id;
+
+#define HOLE_SIZE 4096
+static dn_id hbuf[HOLE_SIZE], *holes = hbuf;
+static unsigned nhmax = HOLE_SIZE;
+static unsigned nholes;
+
+static struct berval *tool_base;
+static int tool_scope;
+static Filter *tool_filter;
+static Entry *tool_next_entry;
+
+static ID mdb_tool_ix_id;
+static BackendDB *mdb_tool_ix_be;
+static MDB_txn *mdb_tool_ix_txn;
+static int mdb_tool_index_tcount, mdb_tool_threads;
+static IndexRec *mdb_tool_index_rec;
+static AttrIxInfo **mdb_tool_axinfo;
+static struct mdb_info *mdb_tool_info;
+static ldap_pvt_thread_mutex_t mdb_tool_index_mutex;
+static ldap_pvt_thread_cond_t mdb_tool_index_cond_main;
+static ldap_pvt_thread_cond_t mdb_tool_index_cond_work;
+static void * mdb_tool_index_task( void *ctx, void *ptr );
+
+static int mdb_writes, mdb_writes_per_commit;
+
+/* Number of ops per commit in Quick mode.
+ * Batching speeds writes overall, but too large a
+ * batch will fail with MDB_TXN_FULL.
+ */
+#ifndef MDB_WRITES_PER_COMMIT
+#define MDB_WRITES_PER_COMMIT 500
+#endif
+
+static int
+mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep );
+
+int mdb_tool_entry_open(
+ BackendDB *be, int mode )
+{
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ /* In Quick mode, commit once per 500 entries */
+ mdb_writes = 0;
+ if ( slapMode & SLAP_TOOL_QUICK )
+ mdb_writes_per_commit = MDB_WRITES_PER_COMMIT;
+ else
+ mdb_writes_per_commit = 1;
+
+#ifdef MDB_TOOL_IDL_CACHING /* threaded indexing has no performance advantage */
+ /* Set up for threaded slapindex */
+ if (( slapMode & (SLAP_TOOL_QUICK|SLAP_TOOL_READONLY)) == SLAP_TOOL_QUICK ) {
+ if ( !mdb_tool_info ) {
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ ldap_pvt_thread_mutex_init( &mdb_tool_index_mutex );
+ ldap_pvt_thread_cond_init( &mdb_tool_index_cond_main );
+ ldap_pvt_thread_cond_init( &mdb_tool_index_cond_work );
+ if ( mdb->mi_nattrs ) {
+ int i;
+ mdb_tool_threads = slap_tool_thread_max - 1;
+ if ( mdb_tool_threads > 1 ) {
+ mdb_tool_index_rec = ch_calloc( mdb->mi_nattrs, sizeof( IndexRec ));
+ mdb_tool_axinfo = ch_calloc( mdb_tool_threads, sizeof( AttrIxInfo* ) +
+ sizeof( AttrIxInfo ));
+ mdb_tool_axinfo[0] = (AttrIxInfo *)(mdb_tool_axinfo + mdb_tool_threads);
+ for (i=1; i<mdb_tool_threads; i++)
+ mdb_tool_axinfo[i] = mdb_tool_axinfo[i-1]+1;
+ mdb_tool_index_tcount = mdb_tool_threads - 1;
+ mdb_tool_ix_be = be;
+ for (i=1; i<mdb_tool_threads; i++) {
+ int *ptr = ch_malloc( sizeof( int ));
+ *ptr = i;
+ ldap_pvt_thread_pool_submit( &connection_pool,
+ mdb_tool_index_task, ptr );
+ }
+ mdb_tool_info = mdb;
+ }
+ }
+ }
+ }
+#endif
+
+ return 0;
+}
+
+int mdb_tool_entry_close(
+ BackendDB *be )
+{
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+#ifdef MDB_TOOL_IDL_CACHING
+ if ( mdb_tool_info ) {
+ int i;
+ slapd_shutdown = 1;
+ ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+
+ /* There might still be some threads starting */
+ while ( mdb_tool_index_tcount > 0 ) {
+ ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main,
+ &mdb_tool_index_mutex );
+ }
+
+ mdb_tool_index_tcount = mdb_tool_threads - 1;
+ ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work );
+
+ /* Make sure all threads are stopped */
+ while ( mdb_tool_index_tcount > 0 ) {
+ ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main,
+ &mdb_tool_index_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+
+ mdb_tool_info = NULL;
+ slapd_shutdown = 0;
+ ch_free( mdb_tool_index_rec );
+ mdb_tool_index_tcount = mdb_tool_threads - 1;
+ if (mdb_tool_txn)
+ MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn );
+ for (i=0; i<mdb_tool_threads; i++) {
+ mdb_tool_idl_cache *ic;
+ mdb_tool_idl_cache_entry *ice;
+ while ((ic = mdb_tool_axinfo[i]->ai_clist)) {
+ mdb_tool_axinfo[i]->ai_clist = ic->head;
+ free(ic);
+ }
+ while ((ice = mdb_tool_axinfo[i]->ai_flist)) {
+ mdb_tool_axinfo[i]->ai_flist = ice->next;
+ free(ice);
+ }
+ }
+ }
+#endif
+
+ if( idcursor ) {
+ mdb_cursor_close( idcursor );
+ idcursor = NULL;
+ }
+ if( cursor ) {
+ mdb_cursor_close( cursor );
+ cursor = NULL;
+ }
+ {
+ struct mdb_info *mdb = be->be_private;
+ if ( mdb ) {
+ int i;
+ for (i=0; i<mdb->mi_nattrs; i++)
+ mdb->mi_attrs[i]->ai_cursor = NULL;
+ }
+ }
+ if( mdb_tool_txn ) {
+ int rc;
+ if (( rc = mdb_txn_commit( mdb_tool_txn ))) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_tool_entry_close) ": database %s: "
+ "txn_commit failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ return -1;
+ }
+ mdb_tool_txn = NULL;
+ }
+ if( reindexing ) {
+ struct mdb_info *mdb = be->be_private;
+ if ( !txi ) {
+ int rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &txi );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_close) ": database %s: "
+ "txn_begin failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ return -1;
+ }
+ }
+ mdb_drop( txi, mdb->mi_idxckp, 0 );
+ }
+ if( txi ) {
+ int rc;
+ if (( rc = mdb_txn_commit( txi ))) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_tool_entry_close) ": database %s: "
+ "txn_commit failed: %s (%d)\n",
+ be->be_suffix[0].bv_val, mdb_strerror(rc), rc );
+ return -1;
+ }
+ txi = NULL;
+ }
+
+ if( nholes ) {
+ unsigned i;
+ fprintf( stderr, "Error, entries missing!\n");
+ for (i=0; i<nholes; i++) {
+ fprintf(stderr, " entry %ld: %s\n",
+ holes[i].id, holes[i].dn.bv_val);
+ }
+ nholes = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+ID
+mdb_tool_entry_first_x(
+ BackendDB *be,
+ struct berval *base,
+ int scope,
+ Filter *f )
+{
+ tool_base = base;
+ tool_scope = scope;
+ tool_filter = f;
+
+ return mdb_tool_entry_next( be );
+}
+
+ID mdb_tool_entry_next(
+ BackendDB *be )
+{
+ int rc;
+ ID id;
+ struct mdb_info *mdb;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ mdb = (struct mdb_info *) be->be_private;
+ assert( mdb != NULL );
+
+ if ( !mdb_tool_txn ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn );
+ if ( rc )
+ return NOID;
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor );
+ if ( rc ) {
+ mdb_txn_abort( mdb_tool_txn );
+ return NOID;
+ }
+ }
+
+next:;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_NEXT );
+
+ if( rc ) {
+ return NOID;
+ }
+
+ previd = *(ID *)key.mv_data;
+ id = previd;
+
+ if ( !data.mv_size )
+ goto next;
+
+ if ( tool_filter || tool_base ) {
+ static Operation op = {0};
+ static Opheader ohdr = {0};
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ if ( tool_next_entry ) {
+ mdb_entry_release( &op, tool_next_entry, 0 );
+ tool_next_entry = NULL;
+ }
+
+ rc = mdb_tool_entry_get_int( be, id, &tool_next_entry );
+ if ( rc == LDAP_NO_SUCH_OBJECT ) {
+ goto next;
+ }
+
+ assert( tool_next_entry != NULL );
+
+ if ( tool_filter && test_filter( NULL, tool_next_entry, tool_filter ) != LDAP_COMPARE_TRUE )
+ {
+ mdb_entry_release( &op, tool_next_entry, 0 );
+ tool_next_entry = NULL;
+ goto next;
+ }
+ }
+
+ return id;
+}
+
+ID mdb_tool_dn2id_get(
+ Backend *be,
+ struct berval *dn
+)
+{
+ struct mdb_info *mdb;
+ Operation op = {0};
+ Opheader ohdr = {0};
+ ID id;
+ int rc;
+
+ if ( BER_BVISEMPTY(dn) )
+ return 0;
+
+ mdb = (struct mdb_info *) be->be_private;
+
+ if ( !mdb_tool_txn ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, (slapMode & SLAP_TOOL_READONLY) != 0 ?
+ MDB_RDONLY : 0, &mdb_tool_txn );
+ if ( rc )
+ return NOID;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = mdb_dn2id( &op, mdb_tool_txn, NULL, dn, &id, NULL, NULL, NULL );
+ if ( rc == MDB_NOTFOUND )
+ return NOID;
+
+ return id;
+}
+
+static int
+mdb_tool_entry_get_int( BackendDB *be, ID id, Entry **ep )
+{
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ Entry *e = NULL;
+ struct berval dn = BER_BVNULL, ndn = BER_BVNULL;
+ int rc;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ if ( ( tool_filter || tool_base ) && id == previd && tool_next_entry != NULL ) {
+ *ep = tool_next_entry;
+ tool_next_entry = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ if ( id != previd ) {
+ key.mv_size = sizeof(ID);
+ key.mv_data = &id;
+ rc = mdb_cursor_get( cursor, &key, &data, MDB_SET );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+ if ( !data.mv_size ) {
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+ if ( slapMode & SLAP_TOOL_READONLY ) {
+ rc = mdb_id2name( &op, mdb_tool_txn, &idcursor, id, &dn, &ndn );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ if ( tool_base != NULL ) {
+ if ( !dnIsSuffixScope( &ndn, tool_base, tool_scope ) ) {
+ ch_free( dn.bv_val );
+ ch_free( ndn.bv_val );
+ rc = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+ }
+ }
+ rc = mdb_entry_decode( &op, mdb_tool_txn, &data, id, &e );
+ e->e_id = id;
+ if ( !BER_BVISNULL( &dn )) {
+ e->e_name = dn;
+ e->e_nname = ndn;
+ } else {
+ e->e_name.bv_len = 0;
+ e->e_name.bv_val = NULL;
+ e->e_nname.bv_len = 0;
+ e->e_nname.bv_val = NULL;
+ }
+
+done:
+ if ( e != NULL ) {
+ *ep = e;
+ }
+
+ return rc;
+}
+
+Entry*
+mdb_tool_entry_get( BackendDB *be, ID id )
+{
+ Entry *e = NULL;
+ int rc;
+
+ if ( !mdb_tool_txn ) {
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL,
+ (slapMode & SLAP_TOOL_READONLY) ? MDB_RDONLY : 0, &mdb_tool_txn );
+ if ( rc )
+ return NULL;
+ }
+ if ( !cursor ) {
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &cursor );
+ if ( rc ) {
+ mdb_txn_abort( mdb_tool_txn );
+ mdb_tool_txn = NULL;
+ return NULL;
+ }
+ }
+ (void)mdb_tool_entry_get_int( be, id, &e );
+ return e;
+}
+
+static int mdb_tool_next_id(
+ Operation *op,
+ MDB_txn *tid,
+ Entry *e,
+ struct berval *text,
+ int hole )
+{
+ struct berval dn = e->e_name;
+ struct berval ndn = e->e_nname;
+ struct berval pdn, npdn, nmatched;
+ ID id, pid = 0;
+ int rc;
+
+ if (ndn.bv_len == 0) {
+ e->e_id = 0;
+ return 0;
+ }
+
+ rc = mdb_dn2id( op, tid, mcp, &ndn, &id, NULL, NULL, &nmatched );
+ if ( rc == MDB_NOTFOUND ) {
+ if ( !be_issuffix( op->o_bd, &ndn ) ) {
+ ID eid = e->e_id;
+ dnParent( &ndn, &npdn );
+ if ( nmatched.bv_len != npdn.bv_len ) {
+ dnParent( &dn, &pdn );
+ e->e_name = pdn;
+ e->e_nname = npdn;
+ rc = mdb_tool_next_id( op, tid, e, text, 1 );
+ e->e_name = dn;
+ e->e_nname = ndn;
+ if ( rc ) {
+ return rc;
+ }
+ /* If parent didn't exist, it was created just now
+ * and its ID is now in e->e_id. Make sure the current
+ * entry gets added under the new parent ID.
+ */
+ if ( eid != e->e_id ) {
+ pid = e->e_id;
+ }
+ } else {
+ pid = id;
+ }
+ }
+ rc = mdb_next_id( op->o_bd, idcursor, &e->e_id );
+ if ( rc ) {
+ snprintf( text->bv_val, text->bv_len,
+ "next_id failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> mdb_tool_next_id: %s\n", text->bv_val );
+ return rc;
+ }
+ rc = mdb_dn2id_add( op, mcp, mcd, pid, 1, 1, e );
+ if ( rc ) {
+ snprintf( text->bv_val, text->bv_len,
+ "dn2id_add failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> mdb_tool_next_id: %s\n", text->bv_val );
+ } else if ( hole ) {
+ MDB_val key, data;
+ if ( nholes == nhmax - 1 ) {
+ if ( holes == hbuf ) {
+ holes = ch_malloc( nhmax * sizeof(dn_id) * 2 );
+ AC_MEMCPY( holes, hbuf, sizeof(hbuf) );
+ } else {
+ holes = ch_realloc( holes, nhmax * sizeof(dn_id) * 2 );
+ }
+ nhmax *= 2;
+ }
+ ber_dupbv( &holes[nholes].dn, &ndn );
+ holes[nholes++].id = e->e_id;
+ key.mv_size = sizeof(ID);
+ key.mv_data = &e->e_id;
+ data.mv_size = 0;
+ data.mv_data = NULL;
+ rc = mdb_cursor_put( idcursor, &key, &data, MDB_NOOVERWRITE );
+ if ( rc == MDB_KEYEXIST )
+ rc = 0;
+ if ( rc ) {
+ snprintf( text->bv_val, text->bv_len,
+ "dummy id2entry add failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> mdb_tool_next_id: %s\n", text->bv_val );
+ }
+ }
+ } else if ( !hole ) {
+ unsigned i, j;
+
+ e->e_id = id;
+
+ for ( i=0; i<nholes; i++) {
+ if ( holes[i].id == e->e_id ) {
+ free(holes[i].dn.bv_val);
+ for (j=i;j<nholes;j++) holes[j] = holes[j+1];
+ holes[j].id = 0;
+ nholes--;
+ break;
+ } else if ( holes[i].id > e->e_id ) {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+static int
+mdb_tool_index_add(
+ Operation *op,
+ MDB_txn *txn,
+ Entry *e )
+{
+ struct mdb_info *mdb = (struct mdb_info *) op->o_bd->be_private;
+
+ if ( !mdb->mi_nattrs )
+ return 0;
+
+ if ( mdb_tool_threads > 1 ) {
+ IndexRec *ir;
+ int i, rc;
+ Attribute *a;
+
+ ir = mdb_tool_index_rec;
+ for (i=0; i<mdb->mi_nattrs; i++)
+ ir[i].ir_attrs = NULL;
+
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ rc = mdb_index_recset( mdb, a, a->a_desc->ad_type,
+ &a->a_desc->ad_tags, ir );
+ if ( rc )
+ return rc;
+ }
+ for (i=0; i<mdb->mi_nattrs; i++) {
+ if ( !ir[i].ir_ai )
+ break;
+ rc = mdb_cursor_open( txn, ir[i].ir_ai->ai_dbi,
+ &ir[i].ir_ai->ai_cursor );
+ if ( rc )
+ return rc;
+ }
+ mdb_tool_ix_id = e->e_id;
+ mdb_tool_ix_txn = txn;
+ ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+ /* Wait for all threads to be ready */
+ while ( mdb_tool_index_tcount ) {
+ ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main,
+ &mdb_tool_index_mutex );
+ }
+
+ for ( i=1; i<mdb_tool_threads; i++ )
+ mdb_tool_index_rec[i].ir_i = LDAP_BUSY;
+ mdb_tool_index_tcount = mdb_tool_threads - 1;
+ ldap_pvt_thread_cond_broadcast( &mdb_tool_index_cond_work );
+ ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+
+ return mdb_index_recrun( op, txn, mdb, ir, e->e_id, 0 );
+ } else
+ {
+ return mdb_index_entry_add( op, txn, e );
+ }
+}
+
+static int
+mdb_tool_index_finish()
+{
+ int i, rc = 0;
+ ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+ for ( i=1; i<mdb_tool_threads; i++ ) {
+ if ( mdb_tool_index_rec[i].ir_i == LDAP_BUSY ) {
+ ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_main,
+ &mdb_tool_index_mutex );
+ i--;
+ continue;
+ }
+ if ( mdb_tool_index_rec[i].ir_i ) {
+ rc = mdb_tool_index_rec[i].ir_i;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+ return rc;
+}
+
+ID mdb_tool_entry_put(
+ BackendDB *be,
+ Entry *e,
+ struct berval *text )
+{
+ int rc;
+ struct mdb_info *mdb;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ Debug( LDAP_DEBUG_TRACE, "=> " LDAP_XSTRING(mdb_tool_entry_put)
+ "( %ld, \"%s\" )\n", (long) e->e_id, e->e_dn );
+
+ mdb = (struct mdb_info *) be->be_private;
+
+ if ( !mdb_tool_txn ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_begin failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ return NOID;
+ }
+ }
+ if ( !idcursor ) {
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_id2entry, &idcursor );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "cursor_open failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ return NOID;
+ }
+ if ( !mdb->mi_nextid ) {
+ ID dummy;
+ mdb_next_id( be, idcursor, &dummy );
+ }
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcp );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "cursor_open failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ return NOID;
+ }
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &mcd );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "cursor_open failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ return NOID;
+ }
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ /* add dn2id indices */
+ rc = mdb_tool_next_id( &op, mdb_tool_txn, e, text, 0 );
+ if( rc != 0 ) {
+ goto done;
+ }
+
+ if ( mdb_tool_threads > 1 ) {
+ LDAP_SLIST_INSERT_HEAD( &op.o_extra, &mdb_tool_axinfo[0]->ai_oe, oe_next );
+ }
+ rc = mdb_tool_index_add( &op, mdb_tool_txn, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "index_entry_add failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+
+ /* id2entry index */
+ rc = mdb_id2entry_add( &op, mdb_tool_txn, idcursor, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "id2entry_add failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ if( mdb->mi_nattrs && mdb_tool_threads > 1 )
+ rc = mdb_tool_index_finish();
+
+done:
+ if( rc == 0 ) {
+ mdb_writes++;
+ if ( mdb_writes >= mdb_writes_per_commit ) {
+ unsigned i;
+ MDB_TOOL_IDL_FLUSH( be, mdb_tool_txn );
+ rc = mdb_txn_commit( mdb_tool_txn );
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ mdb->mi_attrs[i]->ai_cursor = NULL;
+ mdb_writes = 0;
+ mdb_tool_txn = NULL;
+ idcursor = NULL;
+ if( rc != 0 ) {
+ mdb->mi_numads = 0;
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ e->e_id = NOID;
+ }
+ }
+
+ } else {
+ unsigned i;
+ mdb_txn_abort( mdb_tool_txn );
+ mdb_tool_txn = NULL;
+ idcursor = NULL;
+ for ( i=0; i<mdb->mi_nattrs; i++ )
+ mdb->mi_attrs[i]->ai_cursor = NULL;
+ mdb_writes = 0;
+ snprintf( text->bv_val, text->bv_len,
+ "txn_aborted! %s (%d)",
+ rc == LDAP_OTHER ? "Internal error" :
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_put) ": %s\n",
+ text->bv_val );
+ e->e_id = NOID;
+ }
+
+ return e->e_id;
+}
+
+static int mdb_dn2id_upgrade( BackendDB *be );
+
+int mdb_tool_entry_reindex(
+ BackendDB *be,
+ ID id,
+ AttributeDescription **adv )
+{
+ struct mdb_info *mi = (struct mdb_info *) be->be_private;
+ int rc;
+ Entry *e;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ Debug( LDAP_DEBUG_ARGS,
+ "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n",
+ (long) id );
+ assert( tool_base == NULL );
+ assert( tool_filter == NULL );
+
+ /* Special: do a dn2id upgrade */
+ if ( adv && adv[0] == slap_schema.si_ad_entryDN ) {
+ /* short-circuit tool_entry_next() */
+ mdb_cursor_get( cursor, &key, &data, MDB_LAST );
+ return mdb_dn2id_upgrade( be );
+ }
+
+ /* No indexes configured, nothing to do. Could return an
+ * error here to shortcut things.
+ */
+ if (!mi->mi_attrs) {
+ return 0;
+ }
+
+ reindexing = 1;
+
+ /* Check for explicit list of attrs to index */
+ if ( adv ) {
+ int i, j, n;
+
+ if ( mi->mi_attrs[0]->ai_desc != adv[0] ) {
+ /* count */
+ for ( n = 0; adv[n]; n++ ) ;
+
+ /* insertion sort */
+ for ( i = 0; i < n; i++ ) {
+ AttributeDescription *ad = adv[i];
+ for ( j = i-1; j>=0; j--) {
+ if ( SLAP_PTRCMP( adv[j], ad ) <= 0 ) break;
+ adv[j+1] = adv[j];
+ }
+ adv[j+1] = ad;
+ }
+ }
+
+ for ( i = 0; adv[i]; i++ ) {
+ if ( mi->mi_attrs[i]->ai_desc != adv[i] ) {
+ for ( j = i+1; j < mi->mi_nattrs; j++ ) {
+ if ( mi->mi_attrs[j]->ai_desc == adv[i] ) {
+ AttrInfo *ai = mi->mi_attrs[i];
+ mi->mi_attrs[i] = mi->mi_attrs[j];
+ mi->mi_attrs[j] = ai;
+ break;
+ }
+ }
+ if ( j == mi->mi_nattrs ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_tool_entry_reindex)
+ ": no index configured for %s\n",
+ adv[i]->ad_cname.bv_val );
+ return -1;
+ }
+ }
+ }
+ mi->mi_nattrs = i;
+ }
+
+ e = mdb_tool_entry_get( be, id );
+
+ if( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_tool_entry_reindex)
+ ": could not locate id=%ld\n",
+ (long) id );
+ return -1;
+ }
+
+ if ( !txi ) {
+ rc = mdb_txn_begin( mi->mi_dbenv, NULL, 0, &txi );
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_reindex) ": "
+ "txn_begin failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto done;
+ }
+ }
+
+ if ( slapMode & SLAP_TRUNCATE_MODE ) {
+ int i;
+ for ( i=0; i < mi->mi_nattrs; i++ ) {
+ rc = mdb_drop( txi, mi->mi_attrs[i]->ai_dbi, 0 );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ LDAP_XSTRING(mdb_tool_entry_reindex)
+ ": (Truncate) mdb_drop(%s) failed: %s (%d)\n",
+ mi->mi_attrs[i]->ai_desc->ad_type->sat_cname.bv_val,
+ mdb_strerror(rc), rc );
+ return -1;
+ }
+ }
+ slapMode ^= SLAP_TRUNCATE_MODE;
+ }
+
+ /*
+ * just (re)add them for now
+ * Use truncate mode to empty/reset index databases
+ */
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> " LDAP_XSTRING(mdb_tool_entry_reindex) "( %ld )\n",
+ (long) id );
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = mdb_tool_index_add( &op, txi, e );
+
+done:
+ if( rc == 0 ) {
+ mdb_writes++;
+ if ( mdb_writes >= mdb_writes_per_commit ) {
+ MDB_val key;
+ unsigned i;
+ MDB_TOOL_IDL_FLUSH( be, txi );
+ rc = mdb_txn_commit( txi );
+ mdb_writes = 0;
+ for ( i=0; i<mi->mi_nattrs; i++ )
+ mi->mi_attrs[i]->ai_cursor = NULL;
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_reindex)
+ ": txn_commit failed: %s (%d)\n",
+ mdb_strerror(rc), rc );
+ e->e_id = NOID;
+ }
+ mdb_cursor_close( cursor );
+ txi = NULL;
+ /* Must close the read txn to allow old pages to be reclaimed. */
+ mdb_txn_abort( mdb_tool_txn );
+ /* and then reopen it so that tool_entry_next still works. */
+ mdb_txn_begin( mi->mi_dbenv, NULL, MDB_RDONLY, &mdb_tool_txn );
+ mdb_cursor_open( mdb_tool_txn, mi->mi_id2entry, &cursor );
+ key.mv_data = &id;
+ key.mv_size = sizeof(ID);
+ mdb_cursor_get( cursor, &key, NULL, MDB_SET );
+ }
+
+ } else {
+ unsigned i;
+ mdb_writes = 0;
+ mdb_cursor_close( cursor );
+ cursor = NULL;
+ mdb_txn_abort( txi );
+ for ( i=0; i<mi->mi_nattrs; i++ )
+ mi->mi_attrs[i]->ai_cursor = NULL;
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_reindex)
+ ": txn_aborted! err=%d\n",
+ rc );
+ e->e_id = NOID;
+ txi = NULL;
+ }
+ mdb_entry_release( &op, e, 0 );
+
+ return rc;
+}
+
+ID mdb_tool_entry_modify(
+ BackendDB *be,
+ Entry *e,
+ struct berval *text )
+{
+ int rc;
+ struct mdb_info *mdb;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ assert ( e->e_id != NOID );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> " LDAP_XSTRING(mdb_tool_entry_modify) "( %ld, \"%s\" )\n",
+ (long) e->e_id, e->e_dn );
+
+ mdb = (struct mdb_info *) be->be_private;
+
+ if( cursor ) {
+ mdb_cursor_close( cursor );
+ cursor = NULL;
+ }
+ if ( !mdb_tool_txn ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_begin failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n",
+ text->bv_val );
+ return NOID;
+ }
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ /* id2entry index */
+ rc = mdb_id2entry_update( &op, mdb_tool_txn, NULL, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "id2entry_update failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+done:
+ if( rc == 0 ) {
+ rc = mdb_txn_commit( mdb_tool_txn );
+ if( rc != 0 ) {
+ mdb->mi_numads = 0;
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": "
+ "%s\n", text->bv_val );
+ e->e_id = NOID;
+ }
+
+ } else {
+ mdb_txn_abort( mdb_tool_txn );
+ snprintf( text->bv_val, text->bv_len,
+ "txn_aborted! %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_modify) ": %s\n",
+ text->bv_val );
+ e->e_id = NOID;
+ }
+ mdb_tool_txn = NULL;
+
+ return e->e_id;
+}
+
+int mdb_tool_entry_delete(
+ BackendDB *be,
+ struct berval *ndn,
+ struct berval *text )
+{
+ int rc;
+ struct mdb_info *mdb;
+ Operation op = {0};
+ Opheader ohdr = {0};
+ Entry *e;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ assert ( ndn != NULL );
+ assert ( ndn->bv_val != NULL );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) "( %s )\n",
+ ndn->bv_val );
+
+ mdb = (struct mdb_info *) be->be_private;
+
+ assert( cursor == NULL );
+ if( cursor ) {
+ mdb_cursor_close( cursor );
+ cursor = NULL;
+ }
+ if( !mdb_tool_txn ) {
+ rc = mdb_txn_begin( mdb->mi_dbenv, NULL, 0, &mdb_tool_txn );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_begin failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ return LDAP_OTHER;
+ }
+ }
+
+ rc = mdb_cursor_open( mdb_tool_txn, mdb->mi_dn2id, &cursor );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "cursor_open failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ return LDAP_OTHER;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = mdb_dn2entry( &op, mdb_tool_txn, cursor, ndn, &e, NULL, 0 );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "dn2entry failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ /* check that we wouldn't orphan any children */
+ rc = mdb_dn2id_children( &op, mdb_tool_txn, e );
+ if( rc != MDB_NOTFOUND ) {
+ switch( rc ) {
+ case 0:
+ snprintf( text->bv_val, text->bv_len,
+ "delete failed:"
+ " subordinate objects must be deleted first");
+ break;
+ default:
+ snprintf( text->bv_val, text->bv_len,
+ "has_children failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ break;
+ }
+ rc = -1;
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ /* delete from dn2id */
+ rc = mdb_dn2id_delete( &op, cursor, e->e_id, 1 );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "dn2id_delete failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ /* deindex values */
+ rc = mdb_index_entry_del( &op, mdb_tool_txn, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "entry_delete failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ /* do the deletion */
+ rc = mdb_id2entry_delete( be, mdb_tool_txn, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "id2entry_update failed: err=%d", rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+done:
+ /* free entry */
+ if( e != NULL ) {
+ mdb_entry_return( &op, e );
+ }
+
+ if( rc == 0 ) {
+ rc = mdb_txn_commit( mdb_tool_txn );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": "
+ "%s\n", text->bv_val );
+ }
+
+ } else {
+ mdb_txn_abort( mdb_tool_txn );
+ snprintf( text->bv_val, text->bv_len,
+ "txn_aborted! %s (%d)",
+ mdb_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> " LDAP_XSTRING(mdb_tool_entry_delete) ": %s\n",
+ text->bv_val );
+ }
+ mdb_tool_txn = NULL;
+ cursor = NULL;
+
+ return rc;
+}
+
+static void *
+mdb_tool_index_task( void *ctx, void *ptr )
+{
+ int base = *(int *)ptr;
+ Operation op = {0};
+ Opheader ohdr = {0};
+ AttrIxInfo ai = {0}, *aio;
+
+ free( ptr );
+ op.o_hdr = &ohdr;
+ op.o_bd = mdb_tool_ix_be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+ aio = mdb_tool_axinfo[base];
+ mdb_tool_axinfo[base] = &ai;
+ LDAP_SLIST_INSERT_HEAD( &op.o_extra, &ai.ai_oe, oe_next );
+ while ( 1 ) {
+ ldap_pvt_thread_mutex_lock( &mdb_tool_index_mutex );
+ mdb_tool_index_tcount--;
+ if ( !mdb_tool_index_tcount )
+ ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main );
+ ldap_pvt_thread_cond_wait( &mdb_tool_index_cond_work,
+ &mdb_tool_index_mutex );
+ if ( slapd_shutdown ) {
+ mdb_tool_index_tcount--;
+ if ( !mdb_tool_index_tcount )
+ ldap_pvt_thread_cond_signal( &mdb_tool_index_cond_main );
+ *aio = ai;
+ mdb_tool_axinfo[base] = aio;
+ ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+ break;
+ }
+ ldap_pvt_thread_mutex_unlock( &mdb_tool_index_mutex );
+ mdb_tool_index_rec[base].ir_i = mdb_index_recrun( &op,
+ mdb_tool_ix_txn,
+ mdb_tool_info, mdb_tool_index_rec, mdb_tool_ix_id, base );
+ }
+
+ return NULL;
+}
+
+#ifdef MDB_TOOL_IDL_CACHING
+static int
+mdb_tool_idl_cmp( const void *v1, const void *v2 )
+{
+ const mdb_tool_idl_cache *c1 = v1, *c2 = v2;
+ int rc;
+
+ if (( rc = c1->kstr.bv_len - c2->kstr.bv_len )) return rc;
+ return memcmp( c1->kstr.bv_val, c2->kstr.bv_val, c1->kstr.bv_len );
+}
+
+static int
+mdb_tool_idl_flush_one( MDB_cursor *mc, AttrIxInfo *ai, mdb_tool_idl_cache *ic )
+{
+ mdb_tool_idl_cache_entry *ice;
+ MDB_val key, data[2];
+ int i, rc;
+ ID id, nid;
+
+ /* Freshly allocated, ignore it */
+ if ( !ic->head && ic->count <= MDB_idl_db_size ) {
+ return 0;
+ }
+
+ key.mv_data = ic->kstr.bv_val;
+ key.mv_size = ic->kstr.bv_len;
+
+ if ( ic->count > MDB_idl_db_size ) {
+ while ( ic->flags & WAS_FOUND ) {
+ rc = mdb_cursor_get( mc, &key, data, MDB_SET );
+ if ( rc ) {
+ /* FIXME: find out why this happens */
+ ic->flags = 0;
+ break;
+ }
+ if ( ic->flags & WAS_RANGE ) {
+ /* Skip lo */
+ rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP );
+
+ /* Get hi */
+ rc = mdb_cursor_get( mc, &key, data, MDB_NEXT_DUP );
+
+ /* Store range hi */
+ data[0].mv_data = &ic->last;
+ rc = mdb_cursor_put( mc, &key, data, MDB_CURRENT );
+ } else {
+ /* Delete old data, replace with range */
+ ic->first = *(ID *)data[0].mv_data;
+ mdb_cursor_del( mc, MDB_NODUPDATA );
+ }
+ break;
+ }
+ if ( !(ic->flags & WAS_RANGE)) {
+ /* range, didn't exist before */
+ nid = 0;
+ data[0].mv_size = sizeof(ID);
+ data[0].mv_data = &nid;
+ rc = mdb_cursor_put( mc, &key, data, 0 );
+ if ( rc == 0 ) {
+ data[0].mv_data = &ic->first;
+ rc = mdb_cursor_put( mc, &key, data, 0 );
+ if ( rc == 0 ) {
+ data[0].mv_data = &ic->last;
+ rc = mdb_cursor_put( mc, &key, data, 0 );
+ }
+ }
+ if ( rc ) {
+ rc = -1;
+ }
+ }
+ } else {
+ /* Normal write */
+ int n;
+
+ data[0].mv_size = sizeof(ID);
+ rc = 0;
+ for ( ice = ic->head, n=0; ice; ice = ice->next, n++ ) {
+ int end;
+ if ( ice->next ) {
+ end = IDBLOCK;
+ } else {
+ end = (ic->count-ic->offset) & (IDBLOCK-1);
+ if ( !end )
+ end = IDBLOCK;
+ }
+ data[1].mv_size = end;
+ data[0].mv_data = ice->ids;
+ rc = mdb_cursor_put( mc, &key, data, MDB_APPENDDUP|MDB_MULTIPLE );
+ if ( rc ) {
+ rc = -1;
+ break;
+ }
+ }
+ if ( ic->head ) {
+ ic->tail->next = ai->ai_flist;
+ ai->ai_flist = ic->head;
+ }
+ }
+ ic->head = ai->ai_clist;
+ ai->ai_clist = ic;
+ return rc;
+}
+
+static int
+mdb_tool_idl_flush_db( MDB_txn *txn, AttrInfo *ai, AttrIxInfo *ax )
+{
+ MDB_cursor *mc;
+ Avlnode *root;
+ int rc;
+
+ mdb_cursor_open( txn, ai->ai_dbi, &mc );
+ root = ldap_tavl_end( ai->ai_root, TAVL_DIR_LEFT );
+ do {
+ rc = mdb_tool_idl_flush_one( mc, ax, root->avl_data );
+ if ( rc != -1 )
+ rc = 0;
+ } while ((root = ldap_tavl_next(root, TAVL_DIR_RIGHT)));
+ mdb_cursor_close( mc );
+
+ return rc;
+}
+
+static int
+mdb_tool_idl_flush( BackendDB *be, MDB_txn *txn )
+{
+ struct mdb_info *mdb = (struct mdb_info *) be->be_private;
+ int rc = 0;
+ unsigned int i, dbi;
+
+ for ( i=0; i < mdb->mi_nattrs; i++ ) {
+ if ( !mdb->mi_attrs[i]->ai_root ) continue;
+ rc = mdb_tool_idl_flush_db( txn, mdb->mi_attrs[i], mdb_tool_axinfo[i % mdb_tool_threads] );
+ ldap_tavl_free(mdb->mi_attrs[i]->ai_root, NULL);
+ mdb->mi_attrs[i]->ai_root = NULL;
+ if ( rc )
+ break;
+ }
+ return rc;
+}
+
+int mdb_tool_idl_add(
+ BackendDB *be,
+ MDB_cursor *mc,
+ struct berval *keys,
+ ID id )
+{
+ MDB_dbi dbi;
+ mdb_tool_idl_cache *ic, itmp;
+ mdb_tool_idl_cache_entry *ice;
+ int i, rc, lcount;
+ AttrIxInfo *ax = (AttrIxInfo *)mc;
+ AttrInfo *ai = (AttrInfo *)ax->ai_ai;
+ mc = ai->ai_cursor;
+
+ dbi = ai->ai_dbi;
+ for (i=0; keys[i].bv_val; i++) {
+ itmp.kstr = keys[i];
+ ic = ldap_tavl_find( ai->ai_root, &itmp, mdb_tool_idl_cmp );
+
+ /* No entry yet, create one */
+ if ( !ic ) {
+ MDB_val key, data;
+ ID nid;
+ int rc;
+
+ if ( ax->ai_clist ) {
+ ic = ax->ai_clist;
+ ax->ai_clist = ic->head;
+ } else {
+ ic = ch_malloc( sizeof( mdb_tool_idl_cache ) + itmp.kstr.bv_len + 4 );
+ }
+ ic->kstr.bv_len = itmp.kstr.bv_len;
+ ic->kstr.bv_val = (char *)(ic+1);
+ memcpy( ic->kstr.bv_val, itmp.kstr.bv_val, ic->kstr.bv_len );
+ ic->head = ic->tail = NULL;
+ ic->last = 0;
+ ic->count = 0;
+ ic->offset = 0;
+ ic->flags = 0;
+ ldap_tavl_insert( &ai->ai_root, ic, mdb_tool_idl_cmp,
+ ldap_avl_dup_error );
+
+ /* load existing key count here */
+ key.mv_size = keys[i].bv_len;
+ key.mv_data = keys[i].bv_val;
+ rc = mdb_cursor_get( mc, &key, &data, MDB_SET );
+ if ( rc == 0 ) {
+ ic->flags |= WAS_FOUND;
+ nid = *(ID *)data.mv_data;
+ if ( nid == 0 ) {
+ ic->count = MDB_idl_db_size+1;
+ ic->flags |= WAS_RANGE;
+ } else {
+ size_t count;
+
+ mdb_cursor_count( mc, &count );
+ ic->count = count;
+ ic->first = nid;
+ ic->offset = count & (IDBLOCK-1);
+ }
+ }
+ }
+ /* are we a range already? */
+ if ( ic->count > MDB_idl_db_size ) {
+ ic->last = id;
+ continue;
+ /* Are we at the limit, and converting to a range? */
+ } else if ( ic->count == MDB_idl_db_size ) {
+ if ( ic->head ) {
+ ic->tail->next = ax->ai_flist;
+ ax->ai_flist = ic->head;
+ }
+ ic->head = ic->tail = NULL;
+ ic->last = id;
+ ic->count++;
+ continue;
+ }
+ /* No free block, create that too */
+ lcount = (ic->count-ic->offset) & (IDBLOCK-1);
+ if ( !ic->tail || lcount == 0) {
+ if ( ax->ai_flist ) {
+ ice = ax->ai_flist;
+ ax->ai_flist = ice->next;
+ } else {
+ ice = ch_malloc( sizeof( mdb_tool_idl_cache_entry ));
+ }
+ ice->next = NULL;
+ if ( !ic->head ) {
+ ic->head = ice;
+ } else {
+ ic->tail->next = ice;
+ }
+ ic->tail = ice;
+ if ( lcount )
+ ice->ids[lcount-1] = 0;
+ if ( !ic->count )
+ ic->first = id;
+ }
+ ice = ic->tail;
+ if (!lcount || ice->ids[lcount-1] != id) {
+ ice->ids[lcount] = id;
+ ic->count++;
+ }
+ }
+
+ return 0;
+}
+#endif /* MDB_TOOL_IDL_CACHING */
+
+/* Upgrade from pre 2.4.34 dn2id format */
+
+#include <ac/unistd.h>
+#include <lutil_meter.h>
+
+#define STACKSIZ 2048
+
+typedef struct rec {
+ ID id;
+ size_t len;
+ char rdn[512];
+} rec;
+
+static int
+mdb_dn2id_upgrade( BackendDB *be ) {
+ struct mdb_info *mi = (struct mdb_info *) be->be_private;
+ MDB_txn *mt;
+ MDB_cursor *mc = NULL;
+ MDB_val key, data;
+ int rc, writes=0, depth=0;
+ int enable_meter = 0;
+ ID id = 0, *num, count = 0;
+ rec *stack;
+ lutil_meter_t meter;
+
+ if (!(mi->mi_flags & MDB_NEED_UPGRADE)) {
+ Debug( LDAP_DEBUG_ANY, "database %s: No upgrade needed.\n",
+ be->be_suffix[0].bv_val );
+ return 0;
+ }
+
+ {
+ MDB_stat st;
+
+ mdb_stat(mdb_cursor_txn(cursor), mi->mi_dbis[MDB_ID2ENTRY], &st);
+ if (!st.ms_entries) {
+ /* Empty DB, nothing to upgrade? */
+ return 0;
+ }
+ if (isatty(2))
+ enable_meter = !lutil_meter_open(&meter,
+ &lutil_meter_text_display,
+ &lutil_meter_linear_estimator,
+ st.ms_entries);
+ }
+
+ num = ch_malloc(STACKSIZ * (sizeof(ID) + sizeof(rec)));
+ stack = (rec *)(num + STACKSIZ);
+
+ rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+
+ key.mv_size = sizeof(ID);
+ /* post-order depth-first update */
+ for(;;) {
+ size_t dkids;
+ unsigned char *ptr;
+
+ /* visit */
+ key.mv_data = &id;
+ stack[depth].id = id;
+ rc = mdb_cursor_get(mc, &key, &data, MDB_SET);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ num[depth] = 1;
+
+ rc = mdb_cursor_count(mc, &dkids);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_count failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ if (dkids > 1) {
+ rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP);
+down:
+ ptr = (unsigned char *)data.mv_data + data.mv_size - sizeof(ID);
+ memcpy(&id, ptr, sizeof(ID));
+ depth++;
+ memcpy(stack[depth].rdn, data.mv_data, data.mv_size);
+ stack[depth].len = data.mv_size;
+ continue;
+ }
+
+
+ /* pop: write updated count, advance to next node */
+pop:
+ /* update superior counts */
+ if (depth)
+ num[depth-1] += num[depth];
+
+ key.mv_data = &id;
+ id = stack[depth-1].id;
+ data.mv_data = stack[depth].rdn;
+ data.mv_size = stack[depth].len;
+ rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(BOTH) failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ data.mv_data = stack[depth].rdn;
+ ptr = (unsigned char *)data.mv_data + data.mv_size;
+ memcpy(ptr, &num[depth], sizeof(ID));
+ data.mv_size += sizeof(ID);
+ rc = mdb_cursor_del(mc, 0);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_del failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ rc = mdb_cursor_put(mc, &key, &data, 0);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_put failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ count++;
+#if 1
+ if (enable_meter)
+ lutil_meter_update(&meter, count, 0);
+#else
+ {
+ int len;
+ ptr = data.mv_data;
+ len = (ptr[0] & 0x7f) << 8 | ptr[1];
+ printf("ID: %zu, %zu, %.*s\n", stack[depth].id, num[depth], len, ptr+2);
+ }
+#endif
+ writes++;
+ if (writes == 1000) {
+ mdb_cursor_close(mc);
+ rc = mdb_txn_commit(mt);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ rc = mdb_txn_begin(mi->mi_dbenv, NULL, 0, &mt);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_begin(2) failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ rc = mdb_cursor_open(mt, mi->mi_dbis[MDB_DN2ID], &mc);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_open(2) failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ rc = mdb_cursor_get(mc, &key, &data, MDB_GET_BOTH);
+ if (rc) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_cursor_get(2) failed, %s (%d)\n",
+ mdb_strerror(rc), rc );
+ goto leave;
+ }
+ writes = 0;
+ }
+ depth--;
+
+ rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT_DUP);
+ if (rc == 0)
+ goto down;
+ rc = 0;
+ if (depth)
+ goto pop;
+ else
+ break;
+ }
+leave:
+ mdb_cursor_close(mc);
+ if (mt) {
+ int r2;
+ r2 = mdb_txn_commit(mt);
+ if (r2) {
+ Debug(LDAP_DEBUG_ANY, "mdb_dn2id_upgrade: mdb_txn_commit(2) failed, %s (%d)\n",
+ mdb_strerror(r2), r2 );
+ if (!rc)
+ rc = r2;
+ }
+ }
+ ch_free(num);
+ if (enable_meter) {
+ lutil_meter_update(&meter, count, 1);
+ lutil_meter_close(&meter);
+ }
+ return rc;
+}
diff --git a/servers/slapd/back-meta/Makefile.in b/servers/slapd/back-meta/Makefile.in
new file mode 100644
index 0000000..05b7ec7
--- /dev/null
+++ b/servers/slapd/back-meta/Makefile.in
@@ -0,0 +1,45 @@
+# Makefile.in for back-meta
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \
+ delete.c modify.c modrdn.c suffixmassage.c map.c \
+ conn.c candidates.c dncache.c
+OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \
+ delete.lo modify.lo modrdn.lo suffixmassage.lo map.lo \
+ conn.lo candidates.lo dncache.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-meta"
+BUILD_MOD = @BUILD_META@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_META@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_meta
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-meta/add.c b/servers/slapd/back-meta/add.c
new file mode 100644
index 0000000..ec75db1
--- /dev/null
+++ b/servers/slapd/back-meta/add.c
@@ -0,0 +1,211 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_add( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt;
+ metaconn_t *mc;
+ int i, candidate = -1;
+ int isupdate;
+ Attribute *a;
+ LDAPMod **attrs;
+ struct berval mdn = BER_BVNULL, mapped;
+ dncookie dc;
+ int msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ Debug(LDAP_DEBUG_ARGS, "==> meta_back_add: %s\n",
+ op->o_req_dn.bv_val );
+
+ /*
+ * get the current connection
+ */
+ mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR );
+ if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ assert( mc->mc_conns[ candidate ].msc_ld != NULL );
+
+ /*
+ * Rewrite the add dn, if needed
+ */
+ mt = mi->mi_targets[ candidate ];
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "addDN";
+
+ if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* Count number of attributes in entry ( +1 ) */
+ for ( i = 1, a = op->ora_e->e_attrs; a; i++, a = a->a_next );
+
+ /* Create array of LDAPMods for ldap_add() */
+ attrs = ch_malloc( sizeof( LDAPMod * )*i );
+
+ dc.ctx = "addAttrDN";
+ isupdate = be_shadow_update( op );
+ for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) {
+ int j, is_oc = 0;
+
+ if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ if ( a->a_desc == slap_schema.si_ad_objectClass
+ || a->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ is_oc = 1;
+ mapped = a->a_desc->ad_cname;
+
+ } else {
+ ldap_back_map( &mt->mt_rwmap.rwm_at,
+ &a->a_desc->ad_cname, &mapped, BACKLDAP_MAP );
+ if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+ continue;
+ }
+ }
+
+ attrs[ i ] = ch_malloc( sizeof( LDAPMod ) );
+ if ( attrs[ i ] == NULL ) {
+ continue;
+ }
+ attrs[ i ]->mod_op = LDAP_MOD_BVALUES;
+ attrs[ i ]->mod_type = mapped.bv_val;
+
+ if ( is_oc ) {
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ )
+ ;
+
+ attrs[ i ]->mod_bvalues =
+ (struct berval **)ch_malloc( ( j + 1 ) *
+ sizeof( struct berval * ) );
+
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); ) {
+ struct ldapmapping *mapping;
+
+ ldap_back_mapping( &mt->mt_rwmap.rwm_oc,
+ &a->a_vals[ j ], &mapping, BACKLDAP_MAP );
+
+ if ( mapping == NULL ) {
+ if ( mt->mt_rwmap.rwm_oc.drop_missing ) {
+ continue;
+ }
+ attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ];
+
+ } else {
+ attrs[ i ]->mod_bvalues[ j ] = &mapping->dst;
+ }
+ j++;
+ }
+ attrs[ i ]->mod_bvalues[ j ] = NULL;
+
+ } else {
+ /*
+ * FIXME: dn-valued attrs should be rewritten
+ * to allow their use in ACLs at the back-ldap
+ * level.
+ */
+ if ( a->a_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ {
+ (void)ldap_dnattr_rewrite( &dc, a->a_vals );
+ if ( a->a_vals == NULL ) {
+ continue;
+ }
+ }
+
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ )
+ ;
+
+ attrs[ i ]->mod_bvalues = ch_malloc( ( j + 1 ) * sizeof( struct berval * ) );
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) {
+ attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ];
+ }
+ attrs[ i ]->mod_bvalues[ j ] = NULL;
+ }
+ i++;
+ }
+ attrs[ i ] = NULL;
+
+retry:;
+ ctrls = op->o_ctrls;
+ if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+ {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_add_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val,
+ attrs, ctrls, NULL, &msgid );
+ rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid,
+ mt->mt_timeout[ SLAP_OP_ADD ], ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+cleanup:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ for ( --i; i >= 0; --i ) {
+ free( attrs[ i ]->mod_bvalues );
+ free( attrs[ i ] );
+ }
+ free( attrs );
+ if ( mdn.bv_val != op->ora_e->e_dn ) {
+ free( mdn.bv_val );
+ BER_BVZERO( &mdn );
+ }
+
+done:;
+ if ( mc ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/back-meta.h b/servers/slapd/back-meta/back-meta.h
new file mode 100644
index 0000000..82b2105
--- /dev/null
+++ b/servers/slapd/back-meta/back-meta.h
@@ -0,0 +1,690 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef SLAPD_LDAP_H
+#error "include servers/slapd/back-ldap/back-ldap.h before this file!"
+#endif /* SLAPD_LDAP_H */
+
+#ifndef SLAPD_META_H
+#define SLAPD_META_H
+
+#define SLAPD_META_CLIENT_PR 1
+
+#include "proto-meta.h"
+
+/* String rewrite library */
+#include "rewrite.h"
+
+LDAP_BEGIN_DECL
+
+/*
+ * Set META_BACK_PRINT_CONNTREE larger than 0 to dump the connection tree (debug only)
+ */
+#ifndef META_BACK_PRINT_CONNTREE
+#define META_BACK_PRINT_CONNTREE 0
+#endif /* !META_BACK_PRINT_CONNTREE */
+
+/* from back-ldap.h before rwm removal */
+struct ldapmap {
+ int drop_missing;
+
+ Avlnode *map;
+ Avlnode *remap;
+};
+
+struct ldapmapping {
+ struct berval src;
+ struct berval dst;
+};
+
+struct ldaprwmap {
+ /*
+ * DN rewriting
+ */
+ struct rewrite_info *rwm_rw;
+ BerVarray rwm_bva_rewrite;
+
+ /*
+ * Attribute/objectClass mapping
+ */
+ struct ldapmap rwm_oc;
+ struct ldapmap rwm_at;
+ BerVarray rwm_bva_map;
+};
+
+/* Whatever context ldap_back_dn_massage needs... */
+typedef struct dncookie {
+ struct metatarget_t *target;
+
+ Connection *conn;
+ char *ctx;
+ SlapReply *rs;
+} dncookie;
+
+int ldap_back_dn_massage(dncookie *dc, struct berval *dn,
+ struct berval *res);
+
+extern int ldap_back_conn_cmp( const void *c1, const void *c2);
+extern int ldap_back_conn_dup( void *c1, void *c2 );
+extern void ldap_back_conn_free( void *c );
+
+/* attributeType/objectClass mapping */
+int mapping_cmp (const void *, const void *);
+int mapping_dup (void *, void *);
+
+void ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping ** );
+int ldap_back_mapping ( struct ldapmap *map, struct berval *s,
+ struct ldapmapping **m, int remap );
+void ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *m,
+ int remap );
+#define BACKLDAP_MAP 0
+#define BACKLDAP_REMAP 1
+char *
+ldap_back_map_filter(
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ struct berval *f,
+ int remap );
+
+int
+ldap_back_map_attrs(
+ Operation *op,
+ struct ldapmap *at_map,
+ AttributeName *a,
+ int remap,
+ char ***mapped_attrs );
+
+extern int
+ldap_back_filter_map_rewrite(
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr,
+ int remap,
+ void *memctx );
+
+/* suffix massaging by means of librewrite */
+extern int
+suffix_massage_config( struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc );
+extern int
+ldap_back_referral_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals,
+ void *memctx );
+extern int
+ldap_dnattr_rewrite(
+ dncookie *dc,
+ BerVarray a_vals );
+extern int
+ldap_dnattr_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals );
+
+/* (end of) from back-ldap.h before rwm removal */
+
+/*
+ * A metasingleconn_t can be in the following, mutually exclusive states:
+ *
+ * - none (0x0U)
+ * - creating META_BACK_FCONN_CREATING
+ * - initialized META_BACK_FCONN_INITED
+ * - binding LDAP_BACK_FCONN_BINDING
+ * - bound/anonymous LDAP_BACK_FCONN_ISBOUND/LDAP_BACK_FCONN_ISANON
+ *
+ * possible modifiers are:
+ *
+ * - privileged LDAP_BACK_FCONN_ISPRIV
+ * - privileged, TLS LDAP_BACK_FCONN_ISTLS
+ * - subjected to idassert LDAP_BACK_FCONN_ISIDASR
+ * - tainted LDAP_BACK_FCONN_TAINTED
+ */
+
+#define META_BACK_FCONN_INITED (0x00100000U)
+#define META_BACK_FCONN_CREATING (0x00200000U)
+
+#define META_BACK_CONN_INITED(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_INITED, (mlc))
+#define META_BACK_CONN_CREATING(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_CREATING, (mlc))
+
+struct metainfo_t;
+
+#define META_NOT_CANDIDATE ((ber_tag_t)0x0)
+#define META_CANDIDATE ((ber_tag_t)0x1)
+#define META_BINDING ((ber_tag_t)0x2)
+#define META_RETRYING ((ber_tag_t)0x4)
+
+typedef struct metasingleconn_t {
+#define META_CND_ISSET(rs,f) ( ( (rs)->sr_tag & (f) ) == (f) )
+#define META_CND_SET(rs,f) ( (rs)->sr_tag |= (f) )
+#define META_CND_CLEAR(rs,f) ( (rs)->sr_tag &= ~(f) )
+
+#define META_CANDIDATE_RESET(rs) ( (rs)->sr_tag = 0 )
+#define META_IS_CANDIDATE(rs) META_CND_ISSET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_SET(rs) META_CND_SET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_CLEAR(rs) META_CND_CLEAR( (rs), META_CANDIDATE )
+#define META_IS_BINDING(rs) META_CND_ISSET( (rs), META_BINDING )
+#define META_BINDING_SET(rs) META_CND_SET( (rs), META_BINDING )
+#define META_BINDING_CLEAR(rs) META_CND_CLEAR( (rs), META_BINDING )
+#define META_IS_RETRYING(rs) META_CND_ISSET( (rs), META_RETRYING )
+#define META_RETRYING_SET(rs) META_CND_SET( (rs), META_RETRYING )
+#define META_RETRYING_CLEAR(rs) META_CND_CLEAR( (rs), META_RETRYING )
+
+ LDAP *msc_ld;
+ time_t msc_time;
+ struct berval msc_bound_ndn;
+ struct berval msc_cred;
+ unsigned msc_mscflags;
+ /* NOTE: lc_lcflags is redefined to msc_mscflags to reuse the macros
+ * defined for back-ldap */
+#define lc_lcflags msc_mscflags
+} metasingleconn_t;
+
+typedef struct metaconn_t {
+ ldapconn_base_t lc_base;
+#define mc_base lc_base
+#define mc_conn mc_base.lcb_conn
+#define mc_local_ndn mc_base.lcb_local_ndn
+#define mc_refcnt mc_base.lcb_refcnt
+#define mc_create_time mc_base.lcb_create_time
+#define mc_time mc_base.lcb_time
+
+ LDAP_TAILQ_ENTRY(metaconn_t) mc_q;
+
+ /* NOTE: msc_mscflags is used to recycle the #define
+ * in metasingleconn_t */
+ unsigned msc_mscflags;
+
+ /*
+ * means that the connection is bound;
+ * of course only one target actually is ...
+ */
+ int mc_authz_target;
+#define META_BOUND_NONE (-1)
+#define META_BOUND_ALL (-2)
+
+ struct metainfo_t *mc_info;
+
+ /* supersedes the connection stuff */
+ metasingleconn_t mc_conns[ 1 ];
+ /* NOTE: mc_conns must be last, because
+ * the required number of conns is malloc'ed
+ * in one block with the metaconn_t structure */
+} metaconn_t;
+
+typedef enum meta_st_t {
+#if 0 /* todo */
+ META_ST_EXACT = LDAP_SCOPE_BASE,
+#endif
+ META_ST_SUBTREE = LDAP_SCOPE_SUBTREE,
+ META_ST_SUBORDINATE = LDAP_SCOPE_SUBORDINATE,
+ META_ST_REGEX /* last + 1 */
+} meta_st_t;
+
+typedef struct metasubtree_t {
+ meta_st_t ms_type;
+ union {
+ struct berval msu_dn;
+ struct {
+ struct berval msr_regex_pattern;
+ regex_t msr_regex;
+ } msu_regex;
+ } ms_un;
+#define ms_dn ms_un.msu_dn
+#define ms_regex ms_un.msu_regex.msr_regex
+#define ms_regex_pattern ms_un.msu_regex.msr_regex_pattern
+
+ struct metasubtree_t *ms_next;
+} metasubtree_t;
+
+typedef struct metafilter_t {
+ struct metafilter_t *mf_next;
+ struct berval mf_regex_pattern;
+ regex_t mf_regex;
+} metafilter_t;
+
+typedef struct metacommon_t {
+ int mc_version;
+ int mc_nretries;
+#define META_RETRY_UNDEFINED (-2)
+#define META_RETRY_FOREVER (-1)
+#define META_RETRY_NEVER (0)
+#define META_RETRY_DEFAULT (10)
+
+ unsigned mc_flags;
+#define META_BACK_CMN_ISSET(mc,f) ( ( (mc)->mc_flags & (f) ) == (f) )
+#define META_BACK_CMN_QUARANTINE(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_QUARANTINE )
+#define META_BACK_CMN_CHASE_REFERRALS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_CHASE_REFERRALS )
+#define META_BACK_CMN_NOREFS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOREFS )
+#define META_BACK_CMN_NOUNDEFFILTER(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOUNDEFFILTER )
+#define META_BACK_CMN_SAVECRED(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_SAVECRED )
+#define META_BACK_CMN_ST_REQUEST(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_ST_REQUEST )
+
+#ifdef SLAPD_META_CLIENT_PR
+ /*
+ * client-side paged results:
+ * -1: accept unsolicited paged results responses
+ * 0: off
+ * >0: always request paged results with size == mt_ps
+ */
+#define META_CLIENT_PR_DISABLE (0)
+#define META_CLIENT_PR_ACCEPT_UNSOLICITED (-1)
+ ber_int_t mc_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ slap_retry_info_t mc_quarantine;
+ time_t mc_network_timeout;
+ struct timeval mc_bind_timeout;
+#define META_BIND_TIMEOUT LDAP_BACK_RESULT_UTIMEOUT
+ time_t mc_timeout[ SLAP_OP_LAST ];
+} metacommon_t;
+
+typedef struct metatarget_t {
+ char *mt_uri;
+ ldap_pvt_thread_mutex_t mt_uri_mutex;
+
+ /* TODO: we might want to enable different strategies
+ * for different targets */
+ LDAP_REBIND_PROC *mt_rebind_f;
+ LDAP_URLLIST_PROC *mt_urllist_f;
+ void *mt_urllist_p;
+
+ metafilter_t *mt_filter;
+ metasubtree_t *mt_subtree;
+ /* F: subtree-include; T: subtree-exclude */
+ int mt_subtree_exclude;
+
+ int mt_scope;
+
+ struct berval mt_psuffix; /* pretty suffix */
+ struct berval mt_nsuffix; /* normalized suffix */
+
+ struct berval mt_binddn;
+ struct berval mt_bindpw;
+
+ /* we only care about the TLS options here */
+ slap_bindconf mt_tls;
+
+ slap_idassert_t mt_idassert;
+#define mt_idassert_mode mt_idassert.si_mode
+#define mt_idassert_authcID mt_idassert.si_bc.sb_authcId
+#define mt_idassert_authcDN mt_idassert.si_bc.sb_binddn
+#define mt_idassert_passwd mt_idassert.si_bc.sb_cred
+#define mt_idassert_authzID mt_idassert.si_bc.sb_authzId
+#define mt_idassert_authmethod mt_idassert.si_bc.sb_method
+#define mt_idassert_sasl_mech mt_idassert.si_bc.sb_saslmech
+#define mt_idassert_sasl_realm mt_idassert.si_bc.sb_realm
+#define mt_idassert_secprops mt_idassert.si_bc.sb_secprops
+#define mt_idassert_tls mt_idassert.si_bc.sb_tls
+#define mt_idassert_flags mt_idassert.si_flags
+#define mt_idassert_authz mt_idassert.si_authz
+
+ struct ldaprwmap mt_rwmap;
+
+ sig_atomic_t mt_isquarantined;
+ ldap_pvt_thread_mutex_t mt_quarantine_mutex;
+
+ metacommon_t mt_mc;
+#define mt_nretries mt_mc.mc_nretries
+#define mt_flags mt_mc.mc_flags
+#define mt_version mt_mc.mc_version
+#define mt_ps mt_mc.mc_ps
+#define mt_network_timeout mt_mc.mc_network_timeout
+#define mt_bind_timeout mt_mc.mc_bind_timeout
+#define mt_timeout mt_mc.mc_timeout
+#define mt_quarantine mt_mc.mc_quarantine
+
+#define META_BACK_TGT_ISSET(mt,f) ( ( (mt)->mt_flags & (f) ) == (f) )
+#define META_BACK_TGT_ISMASK(mt,m,f) ( ( (mt)->mt_flags & (m) ) == (f) )
+
+#define META_BACK_TGT_SAVECRED(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_SAVECRED )
+
+#define META_BACK_TGT_USE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_USE_TLS )
+#define META_BACK_TGT_PROPAGATE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_PROPAGATE_TLS )
+#define META_BACK_TGT_TLS_CRITICAL(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_TLS_CRITICAL )
+
+#define META_BACK_TGT_CHASE_REFERRALS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_CHASE_REFERRALS )
+
+#define META_BACK_TGT_T_F(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F )
+#define META_BACK_TGT_T_F_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER )
+
+#define META_BACK_TGT_ABANDON(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON )
+#define META_BACK_TGT_IGNORE(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE )
+#define META_BACK_TGT_CANCEL(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP )
+#define META_BACK_TGT_CANCEL_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER )
+#define META_BACK_TGT_QUARANTINE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_QUARANTINE )
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define META_BACK_TGT_ST_REQUEST(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_REQUEST )
+#define META_BACK_TGT_ST_RESPONSE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_RESPONSE )
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define META_BACK_TGT_NOREFS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOREFS )
+#define META_BACK_TGT_NOUNDEFFILTER(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOUNDEFFILTER )
+
+ slap_mask_t mt_rep_flags;
+
+} metatarget_t;
+
+typedef struct metadncache_t {
+ ldap_pvt_thread_mutex_t mutex;
+ Avlnode *tree;
+
+#define META_DNCACHE_DISABLED (0)
+#define META_DNCACHE_FOREVER ((time_t)(-1))
+ time_t ttl; /* seconds; 0: no cache, -1: no expiry */
+} metadncache_t;
+
+typedef struct metacandidates_t {
+ int mc_ntargets;
+ SlapReply *mc_candidates;
+} metacandidates_t;
+
+/*
+ * Hook to allow mucking with metainfo_t/metatarget_t when quarantine is over
+ */
+typedef int (*meta_back_quarantine_f)( struct metainfo_t *, int target, void * );
+
+typedef struct metainfo_t {
+ int mi_ntargets;
+ int mi_defaulttarget;
+#define META_DEFAULT_TARGET_NONE (-1)
+
+#define mi_nretries mi_mc.mc_nretries
+#define mi_flags mi_mc.mc_flags
+#define mi_version mi_mc.mc_version
+#define mi_ps mi_mc.mc_ps
+#define mi_network_timeout mi_mc.mc_network_timeout
+#define mi_bind_timeout mi_mc.mc_bind_timeout
+#define mi_timeout mi_mc.mc_timeout
+#define mi_quarantine mi_mc.mc_quarantine
+
+ metatarget_t **mi_targets;
+ metacandidates_t *mi_candidates;
+
+ LDAP_REBIND_PROC *mi_rebind_f;
+ LDAP_URLLIST_PROC *mi_urllist_f;
+
+ metadncache_t mi_cache;
+
+ /* cached connections;
+ * special conns are in tailq rather than in tree */
+ ldap_avl_info_t mi_conninfo;
+ struct {
+ int mic_num;
+ LDAP_TAILQ_HEAD(mc_conn_priv_q, metaconn_t) mic_priv;
+ } mi_conn_priv[ LDAP_BACK_PCONN_LAST ];
+ int mi_conn_priv_max;
+
+ /* NOTE: quarantine uses the connection mutex */
+ meta_back_quarantine_f mi_quarantine_f;
+ void *mi_quarantine_p;
+
+#define li_flags mi_flags
+/* uses flags as defined in <back-ldap/back-ldap.h> */
+#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP
+#define META_BACK_F_ONERR_REPORT (0x02000000U)
+#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT)
+#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U)
+#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */
+
+#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP )
+#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT )
+#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) )
+
+#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND )
+#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS )
+#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON )
+#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON )
+
+#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE )
+
+ time_t mi_conn_ttl;
+ time_t mi_idle_timeout;
+
+ metacommon_t mi_mc;
+ ldap_extra_t *mi_ldap_extra;
+
+} metainfo_t;
+
+typedef enum meta_op_type {
+ META_OP_ALLOW_MULTIPLE = 0,
+ META_OP_REQUIRE_SINGLE,
+ META_OP_REQUIRE_ALL
+} meta_op_type;
+
+SlapReply *
+meta_back_candidates_get( Operation *op );
+
+extern metaconn_t *
+meta_back_getconn(
+ Operation *op,
+ SlapReply *rs,
+ int *candidate,
+ ldap_back_send_t sendok );
+
+extern void
+meta_back_release_conn_lock(
+ metainfo_t *mi,
+ metaconn_t *mc,
+ int dolock );
+#define meta_back_release_conn(mi, mc) meta_back_release_conn_lock( (mi), (mc), 1 )
+
+extern int
+meta_back_retry(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ ldap_back_send_t sendok );
+
+extern void
+meta_back_conn_free(
+ void *v_mc );
+
+#if META_BACK_PRINT_CONNTREE > 0
+extern void
+meta_back_print_conntree(
+ metainfo_t *mi,
+ char *msg );
+#endif
+
+extern int
+meta_back_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock );
+
+extern void
+meta_back_quarantine(
+ Operation *op,
+ SlapReply *rs,
+ int candidate );
+
+extern int
+meta_back_dobind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ ldap_back_send_t sendok );
+
+extern int
+meta_back_single_dobind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ ldap_back_send_t sendok,
+ int retries,
+ int dolock );
+
+extern int
+meta_back_proxy_authz_cred(
+ metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method );
+
+extern int
+meta_back_cancel(
+ metaconn_t *mc,
+ Operation *op,
+ SlapReply *rs,
+ ber_int_t msgid,
+ int candidate,
+ ldap_back_send_t sendok );
+
+extern int
+meta_back_op_result(
+ metaconn_t *mc,
+ Operation *op,
+ SlapReply *rs,
+ int candidate,
+ ber_int_t msgid,
+ time_t timeout,
+ ldap_back_send_t sendok );
+
+extern int
+meta_back_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate,
+ LDAPControl ***pctrls );
+
+extern int
+back_meta_LTX_init_module(
+ int argc,
+ char *argv[] );
+
+extern int
+meta_back_conn_cmp(
+ const void *c1,
+ const void *c2 );
+
+extern int
+meta_back_conndn_cmp(
+ const void *c1,
+ const void *c2 );
+
+extern int
+meta_back_conndn_dup(
+ void *c1,
+ void *c2 );
+
+/*
+ * Candidate stuff
+ */
+extern int
+meta_back_is_candidate(
+ metatarget_t *mt,
+ struct berval *ndn,
+ int scope );
+
+extern int
+meta_back_select_unique_candidate(
+ metainfo_t *mi,
+ struct berval *ndn );
+
+extern int
+meta_clear_unused_candidates(
+ Operation *op,
+ int candidate );
+
+extern int
+meta_clear_one_candidate(
+ Operation *op,
+ metaconn_t *mc,
+ int candidate );
+
+/*
+ * Dn cache stuff (experimental)
+ */
+extern int
+meta_dncache_cmp(
+ const void *c1,
+ const void *c2 );
+
+extern int
+meta_dncache_dup(
+ void *c1,
+ void *c2 );
+
+#define META_TARGET_NONE (-1)
+#define META_TARGET_MULTIPLE (-2)
+extern int
+meta_dncache_get_target(
+ metadncache_t *cache,
+ struct berval *ndn );
+
+extern int
+meta_dncache_update_entry(
+ metadncache_t *cache,
+ struct berval *ndn,
+ int target );
+
+extern int
+meta_dncache_delete_entry(
+ metadncache_t *cache,
+ struct berval *ndn );
+
+extern void
+meta_dncache_free( void *entry );
+
+extern void
+meta_back_map_free( struct ldapmap *lm );
+
+extern int
+meta_subtree_destroy( metasubtree_t *ms );
+
+extern void
+meta_filter_destroy( metafilter_t *mf );
+
+extern int
+meta_target_finish( metainfo_t *mi, metatarget_t *mt,
+ const char *log, char *msg, size_t msize
+);
+
+extern LDAP_REBIND_PROC meta_back_default_rebind;
+extern LDAP_URLLIST_PROC meta_back_default_urllist;
+
+LDAP_END_DECL
+
+#endif /* SLAPD_META_H */
+
diff --git a/servers/slapd/back-meta/bind.c b/servers/slapd/back-meta/bind.c
new file mode 100644
index 0000000..edfabbb
--- /dev/null
+++ b/servers/slapd/back-meta/bind.c
@@ -0,0 +1,1758 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+
+#define AVL_INTERNAL
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+#include "lutil_ldap.h"
+
+static int
+meta_back_proxy_authz_bind(
+ metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock );
+
+static int
+meta_back_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate );
+
+int
+meta_back_bind( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metaconn_t *mc = NULL;
+
+ int rc = LDAP_OTHER,
+ i,
+ gotit = 0,
+ isroot = 0;
+
+ SlapReply *candidates;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS, "%s meta_back_bind: dn=\"%s\".\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ /* the test on the bind method should be superfluous */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case LDAP_SUCCESS:
+ if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) {
+ /* frontend will return success */
+ return rs->sr_err;
+ }
+
+ isroot = 1;
+ /* fallthru */
+
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* be_rootdn_bind() sent result */
+ return rs->sr_err;
+ }
+
+ /* we need meta_back_getconn() not send result even on error,
+ * because we want to intercept the error and make it
+ * invalidCredentials */
+ mc = meta_back_getconn( op, rs, NULL, LDAP_BACK_BIND_DONTSEND );
+ if ( !mc ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_bind: no target " "for dn \"%s\" (%d%s%s).\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ rs->sr_err, rs->sr_text ? ". " : "",
+ rs->sr_text ? rs->sr_text : "" );
+
+ /* FIXME: there might be cases where we don't want
+ * to map the error onto invalidCredentials */
+ switch ( rs->sr_err ) {
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_UNWILLING_TO_PERFORM:
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ rs->sr_text = NULL;
+ break;
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ candidates = meta_back_candidates_get( op );
+
+ /*
+ * Each target is scanned ...
+ */
+ mc->mc_authz_target = META_BOUND_NONE;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+ int lerr;
+
+ /*
+ * Skip non-candidates
+ */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( gotit == 0 ) {
+ /* set rc to LDAP_SUCCESS only if at least
+ * one candidate has been tried */
+ rc = LDAP_SUCCESS;
+ gotit = 1;
+
+ } else if ( !isroot ) {
+ /*
+ * A bind operation is expected to have
+ * ONE CANDIDATE ONLY!
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "### %s meta_back_bind: more than one"
+ " candidate selected...\n",
+ op->o_log_prefix );
+ }
+
+ if ( isroot ) {
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE
+ || BER_BVISNULL( &mt->mt_idassert_authcDN ) )
+ {
+ metasingleconn_t *msc = &mc->mc_conns[ i ];
+
+ /* skip the target if no pseudorootdn is provided */
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ continue;
+ }
+
+
+ (void)meta_back_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 );
+ lerr = rs->sr_err;
+
+ } else {
+ lerr = meta_back_single_bind( op, rs, mc, i );
+ }
+
+ if ( lerr != LDAP_SUCCESS ) {
+ rc = rs->sr_err = lerr;
+
+ /* FIXME: in some cases (e.g. unavailable)
+ * do not assume it's not candidate; rather
+ * mark this as an error to be eventually
+ * reported to client */
+ META_CANDIDATE_CLEAR( &candidates[ i ] );
+ break;
+ }
+ }
+
+ /* must re-insert if local DN changed as result of bind */
+ if ( rc == LDAP_SUCCESS ) {
+ if ( isroot ) {
+ mc->mc_authz_target = META_BOUND_ALL;
+ }
+
+ if ( !LDAP_BACK_PCONN_ISPRIV( mc )
+ && !dn_match( &op->o_req_ndn, &mc->mc_local_ndn ) )
+ {
+ int lerr;
+
+ /* wait for all other ops to release the connection */
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ assert( mc->mc_refcnt == 1 );
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, ">>> meta_back_bind" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ /* delete all cached connections with the current connection */
+ if ( LDAP_BACK_SINGLECONN( mi ) ) {
+ metaconn_t *tmpmc;
+
+ while ( ( tmpmc = ldap_tavl_delete( &mi->mi_conninfo.lai_tree, (caddr_t)mc, meta_back_conn_cmp ) ) != NULL )
+ {
+ assert( !LDAP_BACK_PCONN_ISPRIV( mc ) );
+ Debug( LDAP_DEBUG_TRACE,
+ "=>meta_back_bind: destroying conn %lu (refcnt=%u)\n",
+ mc->mc_conn->c_connid, mc->mc_refcnt );
+
+ if ( tmpmc->mc_refcnt != 0 ) {
+ /* taint it */
+ LDAP_BACK_CONN_TAINTED_SET( tmpmc );
+
+ } else {
+ /*
+ * Needs a test because the handler may be corrupted,
+ * and calling ldap_unbind on a corrupted header results
+ * in a segmentation fault
+ */
+ meta_back_conn_free( tmpmc );
+ }
+ }
+ }
+
+ ber_bvreplace( &mc->mc_local_ndn, &op->o_req_ndn );
+ lerr = ldap_tavl_insert( &mi->mi_conninfo.lai_tree, (caddr_t)mc,
+ meta_back_conndn_cmp, meta_back_conndn_dup );
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, "<<< meta_back_bind" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ if ( lerr == 0 ) {
+#if 0
+ /* NOTE: a connection cannot be privileged
+ * and be in the avl tree at the same time
+ */
+ if ( isroot ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+ LDAP_BACK_PCONN_SET( mc, op );
+ }
+#endif
+ LDAP_BACK_CONN_CACHED_SET( mc );
+
+ } else {
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+ }
+
+ if ( mc != NULL ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ /*
+ * rc is LDAP_SUCCESS if at least one bind succeeded,
+ * err is the last error that occurred during a bind;
+ * if at least (and at most?) one bind succeeds, fine.
+ */
+ if ( rc != LDAP_SUCCESS ) {
+
+ /*
+ * deal with bind failure ...
+ */
+
+ /*
+ * no target was found within the naming context,
+ * so bind must fail with invalid credentials
+ */
+ if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ } else {
+ rs->sr_err = slap_map_api2result( rs );
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+meta_back_bind_op_result(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate,
+ int msgid,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ LDAPMessage *res;
+ struct timeval tv;
+ int rc;
+ int nretries = mt->mt_nretries;
+
+ Debug( LDAP_DEBUG_TRACE,
+ ">>> %s meta_back_bind_op_result[%d]\n",
+ op->o_log_prefix, candidate );
+
+ /* make sure this is clean */
+ assert( rs->sr_ctrls == NULL );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ time_t stoptime = (time_t)(-1),
+ timeout;
+ int timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ const char *timeout_text = "Operation timed out";
+ slap_op_t opidx = slap_req2op( op->o_tag );
+
+ /* since timeout is not specified, compute and use
+ * the one specific to the ongoing operation */
+ if ( opidx == LDAP_REQ_SEARCH ) {
+ if ( op->ors_tlimit <= 0 ) {
+ timeout = 0;
+
+ } else {
+ timeout = op->ors_tlimit;
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ }
+
+ } else {
+ timeout = mt->mt_timeout[ opidx ];
+ }
+
+ /* better than nothing :) */
+ if ( timeout == 0 ) {
+ if ( mi->mi_idle_timeout ) {
+ timeout = mi->mi_idle_timeout;
+
+ } else if ( mi->mi_conn_ttl ) {
+ timeout = mi->mi_conn_ttl;
+ }
+ }
+
+ if ( timeout ) {
+ stoptime = op->o_time + timeout;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+
+ /*
+ * handle response!!!
+ */
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ if ( nretries != META_RETRY_NEVER
+ || ( timeout && slap_get_time() <= stoptime ) )
+ {
+ ldap_pvt_thread_yield();
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ tv = mt->mt_bind_timeout;
+ goto retry;
+ }
+
+ /* don't let anyone else use this handler,
+ * because there's a pending bind that will not
+ * be acknowledged */
+ if ( dolock) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+ assert( LDAP_BACK_CONN_BINDING( msc ) );
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s meta_back_bind_op_result ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ meta_clear_one_candidate( op, mc, candidate );
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ break;
+
+ case -1:
+ ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER,
+ &rs->sr_err );
+
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_back_bind_op_result[%d]: err=%d (%s) nretries=%d.\n",
+ op->o_log_prefix, candidate, rs->sr_err,
+ ldap_err2string(rs->sr_err), nretries );
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+ msc->msc_time = op->o_time;
+ }
+
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+ NULL, NULL, NULL, NULL, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+ break;
+ }
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<<< %s meta_back_bind_op_result[%d] err=%d\n",
+ op->o_log_prefix, candidate, rs->sr_err );
+
+ return rs->sr_err;
+}
+
+/*
+ * meta_back_single_bind
+ *
+ * attempts to perform a bind with creds
+ */
+static int
+meta_back_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL;
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int msgid;
+ dncookie dc;
+ struct berval save_o_dn;
+ int save_o_do_not_cache;
+ LDAPControl **ctrls = NULL;
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "bindDN";
+
+ if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ rs->sr_text = "DN rewrite error";
+ rs->sr_err = LDAP_OTHER;
+ return rs->sr_err;
+ }
+
+ /* don't add proxyAuthz; set the bindDN */
+ save_o_dn = op->o_dn;
+ save_o_do_not_cache = op->o_do_not_cache;
+ op->o_do_not_cache = 1;
+ op->o_dn = op->o_req_dn;
+
+ ctrls = op->o_ctrls;
+ rs->sr_err = meta_back_controls_add( op, rs, mc, candidate, &ctrls );
+ op->o_dn = save_o_dn;
+ op->o_do_not_cache = save_o_do_not_cache;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* FIXME: this fixes the bind problem right now; we need
+ * to use the asynchronous version to get the "matched"
+ * and more in case of failure ... */
+ /* FIXME: should we check if at least some of the op->o_ctrls
+ * can/should be passed? */
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val,
+ LDAP_SASL_SIMPLE, &op->orb_cred,
+ ctrls, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ meta_back_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* If defined, proxyAuthz will be used also when
+ * back-ldap is the authorizing backend; for this
+ * purpose, a successful bind is followed by a
+ * bind with the configured identity assertion */
+ /* NOTE: use with care */
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ meta_back_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 );
+ if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ goto return_results;
+ }
+ goto cache_refresh;
+ }
+
+ ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn );
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ mc->mc_authz_target = candidate;
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &op->orb_cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+
+cache_refresh:;
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED
+ && !BER_BVISEMPTY( &op->o_req_ndn ) )
+ {
+ ( void )meta_dncache_update_entry( &mi->mi_cache,
+ &op->o_req_ndn, candidate );
+ }
+
+return_results:;
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ free( mdn.bv_val );
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ meta_back_quarantine( op, rs, candidate );
+ }
+
+ return rs->sr_err;
+}
+
+/*
+ * meta_back_single_dobind
+ */
+int
+meta_back_single_dobind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ ldap_back_send_t sendok,
+ int nretries,
+ int dolock )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metaconn_t *mc = *mcp;
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int msgid;
+
+ assert( !LDAP_BACK_CONN_ISBOUND( msc ) );
+
+ /* NOTE: this obsoletes pseudorootdn */
+ if ( op->o_conn != NULL &&
+ !op->o_do_not_cache &&
+ ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+ BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+ ( LDAP_BACK_CONN_ISPRIV( mc ) && dn_match( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ) ) ||
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+ {
+ (void)meta_back_proxy_authz_bind( mc, candidate, op, rs, sendok, dolock );
+
+ } else {
+ char *binddn = "";
+ struct berval cred = BER_BVC( "" );
+
+ /* use credentials if available */
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn )
+ && !BER_BVISNULL( &msc->msc_cred ) )
+ {
+ binddn = msc->msc_bound_ndn.bv_val;
+ cred = msc->msc_cred;
+ }
+
+ /* FIXME: should we check if at least some of the op->o_ctrls
+ * can/should be passed? */
+ if(!dolock) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+ binddn, LDAP_SASL_SIMPLE, &cred,
+ NULL, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ if(!dolock) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ rs->sr_err = meta_back_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+
+ /* if bind succeeded, but anonymous, clear msc_bound_ndn */
+ if ( rs->sr_err != LDAP_SUCCESS || binddn[0] == '\0' ) {
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ }
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ meta_back_release_conn_lock( mi, mc, 0 );
+ *mcp = NULL;
+ }
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ meta_back_quarantine( op, rs, candidate );
+ }
+
+ return rs->sr_err;
+}
+
+/*
+ * meta_back_dobind
+ */
+int
+meta_back_dobind(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ ldap_back_send_t sendok )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+
+ int bound = 0,
+ i,
+ isroot = 0;
+
+ SlapReply *candidates;
+
+ if ( be_isroot( op ) ) {
+ isroot = 1;
+ }
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_dobind: conn=%s%s\n",
+ op->o_log_prefix, buf,
+ isroot ? " (isroot)" : "" );
+ }
+
+ /*
+ * all the targets are bound as pseudoroot
+ */
+ if ( mc->mc_authz_target == META_BOUND_ALL ) {
+ bound = 1;
+ goto done;
+ }
+
+ candidates = meta_back_candidates_get( op );
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+ metasingleconn_t *msc = &mc->mc_conns[ i ];
+ int rc;
+
+ /*
+ * Not a candidate
+ */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ assert( msc->msc_ld != NULL );
+
+ /*
+ * If the target is already bound it is skipped
+ */
+
+retry_binding:;
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( LDAP_BACK_CONN_ISBOUND( msc )
+ || ( LDAP_BACK_CONN_ISANON( msc )
+ && mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) )
+ {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ ++bound;
+ continue;
+
+ } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) )
+ {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ ldap_pvt_thread_yield();
+ goto retry_binding;
+
+ }
+
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ rc = meta_back_single_dobind( op, rs, &mc, i,
+ LDAP_BACK_DONTSEND, mt->mt_nretries, 1 );
+ /*
+ * NOTE: meta_back_single_dobind() already retries;
+ * in case of failure, it resets mc...
+ */
+ if ( rc != LDAP_SUCCESS ) {
+ if ( mc == NULL ) {
+ /* meta_back_single_dobind() already sent
+ * response and released connection */
+ goto send_err;
+ }
+
+ if ( rc == LDAP_UNAVAILABLE ) {
+ /* FIXME: meta_back_retry() already re-calls
+ * meta_back_single_dobind() */
+ if ( meta_back_retry( op, rs, &mc, i, sendok ) ) {
+ goto retry_ok;
+ }
+
+ if ( mc != NULL ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ meta_back_release_conn_lock( mi, mc, 0 );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ return 0;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_dobind[%d]: (%s) err=%d (%s).\n",
+ op->o_log_prefix, i,
+ isroot ? op->o_bd->be_rootdn.bv_val : "anonymous",
+ rc, ldap_err2string(rc) );
+
+ /*
+ * null cred bind should always succeed
+ * as anonymous, so a failure means
+ * the target is no longer candidate possibly
+ * due to technical reasons (remote host down?)
+ * so better clear the handle
+ */
+ /* leave the target candidate, but record the error for later use */
+ candidates[ i ].sr_err = rc;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ bound = 0;
+ goto done;
+ }
+
+ continue;
+ } /* else */
+
+retry_ok:;
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_dobind[%d]: "
+ "(%s)\n",
+ op->o_log_prefix, i,
+ isroot ? op->o_bd->be_rootdn.bv_val : "anonymous" );
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ if ( isroot ) {
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ } else {
+ LDAP_BACK_CONN_ISANON_SET( msc );
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ ++bound;
+ }
+
+done:;
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_dobind: conn=%s bound=%d\n",
+ op->o_log_prefix, buf, bound );
+ }
+
+ if ( bound == 0 ) {
+ meta_back_release_conn( mi, mc );
+
+send_err:;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_BUSY;
+ }
+ send_ldap_result( op, rs );
+ }
+
+ return 0;
+ }
+
+ return ( bound > 0 );
+}
+
+/*
+ * meta_back_default_rebind
+ *
+ * This is a callback used for chasing referrals using the same
+ * credentials as the original user on this session.
+ */
+int
+meta_back_default_rebind(
+ LDAP *ld,
+ LDAP_CONST char *url,
+ ber_tag_t request,
+ ber_int_t msgid,
+ void *params )
+{
+ metasingleconn_t *msc = ( metasingleconn_t * )params;
+
+ return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val,
+ LDAP_SASL_SIMPLE, &msc->msc_cred,
+ NULL, NULL, NULL );
+}
+
+/*
+ * meta_back_default_urllist
+ *
+ * This is a callback used for mucking with the urllist
+ */
+int
+meta_back_default_urllist(
+ LDAP *ld,
+ LDAPURLDesc **urllist,
+ LDAPURLDesc **url,
+ void *params )
+{
+ metatarget_t *mt = (metatarget_t *)params;
+ LDAPURLDesc **urltail;
+
+ if ( urllist == url ) {
+ return LDAP_SUCCESS;
+ }
+
+ for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next )
+ /* count */ ;
+
+ *urltail = *urllist;
+ *urllist = *url;
+ *url = NULL;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ }
+
+ ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+ return LDAP_SUCCESS;
+}
+
+int
+meta_back_cancel(
+ metaconn_t *mc,
+ Operation *op,
+ SlapReply *rs,
+ ber_int_t msgid,
+ int candidate,
+ ldap_back_send_t sendok )
+{
+ metainfo_t *mi = (metainfo_t *)op->o_bd->be_private;
+
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ int rc = LDAP_OTHER;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> %s meta_back_cancel[%d] msgid=%d\n",
+ op->o_log_prefix, candidate, msgid );
+
+ /* default behavior */
+ if ( META_BACK_TGT_ABANDON( mt ) ) {
+ rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+ } else if ( META_BACK_TGT_IGNORE( mt ) ) {
+ rc = ldap_pvt_discard( msc->msc_ld, msgid );
+
+ } else if ( META_BACK_TGT_CANCEL( mt ) ) {
+ rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL );
+
+ } else {
+ assert( 0 );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< %s meta_back_cancel[%d] err=%d\n",
+ op->o_log_prefix, candidate, rc );
+
+ return rc;
+}
+
+
+
+/*
+ * FIXME: error return must be handled in a cleaner way ...
+ */
+int
+meta_back_op_result(
+ metaconn_t *mc,
+ Operation *op,
+ SlapReply *rs,
+ int candidate,
+ ber_int_t msgid,
+ time_t timeout,
+ ldap_back_send_t sendok )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+
+ const char *save_text = rs->sr_text,
+ *save_matched = rs->sr_matched;
+ BerVarray save_ref = rs->sr_ref;
+ LDAPControl **save_ctrls = rs->sr_ctrls;
+ void *matched_ctx = NULL;
+
+ char *matched = NULL;
+ char *text = NULL;
+ char **refs = NULL;
+ LDAPControl **ctrls = NULL;
+
+ assert( mc != NULL );
+
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+ rs->sr_ctrls = NULL;
+
+ if ( candidate != META_TARGET_NONE ) {
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ if ( LDAP_ERR_OK( rs->sr_err ) ) {
+ int rc;
+ struct timeval tv;
+ LDAPMessage *res = NULL;
+ time_t stoptime = (time_t)(-1);
+ int timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ const char *timeout_text = "Operation timed out";
+
+ /* if timeout is not specified, compute and use
+ * the one specific to the ongoing operation */
+ if ( timeout == (time_t)(-1) ) {
+ slap_op_t opidx = slap_req2op( op->o_tag );
+
+ if ( opidx == SLAP_OP_SEARCH ) {
+ if ( op->ors_tlimit <= 0 ) {
+ timeout = 0;
+
+ } else {
+ timeout = op->ors_tlimit;
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ }
+
+ } else {
+ timeout = mt->mt_timeout[ opidx ];
+ }
+ }
+
+ /* better than nothing :) */
+ if ( timeout == 0 ) {
+ if ( mi->mi_idle_timeout ) {
+ timeout = mi->mi_idle_timeout;
+
+ } else if ( mi->mi_conn_ttl ) {
+ timeout = mi->mi_conn_ttl;
+ }
+ }
+
+ if ( timeout ) {
+ stoptime = op->o_time + timeout;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ if ( timeout && slap_get_time() > stoptime ) {
+ (void)meta_back_cancel( mc, op, rs, msgid, candidate, sendok );
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ break;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+ ldap_pvt_thread_yield();
+ goto retry;
+
+ case -1:
+ ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE,
+ &rs->sr_err );
+ break;
+
+
+ /* otherwise get the result; if it is not
+ * LDAP_SUCCESS, record it in the reply
+ * structure (this includes
+ * LDAP_COMPARE_{TRUE|FALSE}) */
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+ msc->msc_time = op->o_time;
+ }
+
+ rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+ &matched, &text, &refs, &ctrls, 1 );
+ res = NULL;
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_text = text;
+ } else {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( refs != NULL
+ && refs[ 0 ] != NULL
+ && refs[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_op_result[%d]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ } else {
+ int i;
+
+ for ( i = 0; refs[ i ] != NULL; i++ )
+ /* count */ ;
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0; refs[ i ] != NULL; i++ ) {
+ ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ i ] );
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_op_result[%d]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( ctrls != NULL ) {
+ rs->sr_ctrls = ctrls;
+ }
+ }
+
+ assert( res == NULL );
+ }
+
+ /* if the error in the reply structure is not
+ * LDAP_SUCCESS, try to map it from client
+ * to server error */
+ if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* internal ops ( op->o_conn == NULL )
+ * must not reply to client */
+ if ( op->o_conn && !op->o_do_not_cache && matched ) {
+
+ /* record the (massaged) matched
+ * DN into the reply structure */
+ rs->sr_matched = matched;
+ }
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ meta_back_quarantine( op, rs, candidate );
+ }
+
+ } else {
+ int i,
+ err = rs->sr_err;
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metasingleconn_t *msc = &mc->mc_conns[ i ];
+ char *xtext = NULL;
+ char *xmatched = NULL;
+
+ if ( msc->msc_ld == NULL ) {
+ continue;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rs->sr_err );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ /*
+ * better check the type of error. In some cases
+ * (search ?) it might be better to return a
+ * success if at least one of the targets gave
+ * positive result ...
+ */
+ ldap_get_option( msc->msc_ld,
+ LDAP_OPT_DIAGNOSTIC_MESSAGE, &xtext );
+ if ( xtext != NULL && xtext [ 0 ] == '\0' ) {
+ ldap_memfree( xtext );
+ xtext = NULL;
+ }
+
+ ldap_get_option( msc->msc_ld,
+ LDAP_OPT_MATCHED_DN, &xmatched );
+ if ( xmatched != NULL && xmatched[ 0 ] == '\0' ) {
+ ldap_memfree( xmatched );
+ xmatched = NULL;
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_op_result[%d] " "err=%d text=\"%s\" matched=\"%s\".\n",
+ op->o_log_prefix, i, rs->sr_err,
+ (xtext ? xtext : ""),
+ (xmatched ? xmatched : "") );
+
+ /*
+ * FIXME: need to rewrite "match" (need rwinfo)
+ */
+ switch ( rs->sr_err ) {
+ default:
+ err = rs->sr_err;
+ if ( xtext != NULL ) {
+ if ( text ) {
+ ldap_memfree( text );
+ }
+ text = xtext;
+ xtext = NULL;
+ }
+ if ( xmatched != NULL ) {
+ if ( matched ) {
+ ldap_memfree( matched );
+ }
+ matched = xmatched;
+ xmatched = NULL;
+ }
+ break;
+ }
+
+ if ( xtext ) {
+ ldap_memfree( xtext );
+ }
+
+ if ( xmatched ) {
+ ldap_memfree( xmatched );
+ }
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) {
+ meta_back_quarantine( op, rs, i );
+ }
+ }
+
+ if ( err != LDAP_SUCCESS ) {
+ rs->sr_err = err;
+ }
+ }
+
+ if ( matched != NULL ) {
+ struct berval dn, pdn;
+
+ ber_str2bv( matched, 0, 0, &dn );
+ if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+ ldap_memfree( matched );
+ matched_ctx = op->o_tmpmemctx;
+ matched = pdn.bv_val;
+ }
+ rs->sr_matched = matched;
+ }
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ if ( !( sendok & LDAP_BACK_RETRYING ) ) {
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed";
+ send_ldap_result( op, rs );
+ }
+ }
+
+ } else if ( op->o_conn &&
+ ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) )
+ || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) )
+ {
+ send_ldap_result( op, rs );
+ }
+ if ( matched ) {
+ op->o_tmpfree( (char *)rs->sr_matched, matched_ctx );
+ }
+ if ( text ) {
+ ldap_memfree( text );
+ }
+ if ( rs->sr_ref ) {
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+ if ( refs ) {
+ ber_memvfree( (void **)refs );
+ }
+ if ( ctrls ) {
+ assert( rs->sr_ctrls != NULL );
+ ldap_controls_free( ctrls );
+ }
+
+ rs->sr_text = save_text;
+ rs->sr_matched = save_matched;
+ rs->sr_ref = save_ref;
+ rs->sr_ctrls = save_ctrls;
+
+ return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err );
+}
+
+/*
+ * meta_back_proxy_authz_cred()
+ *
+ * prepares credentials & method for meta_back_proxy_authz_bind();
+ * or, if method is SASL, performs the SASL bind directly.
+ */
+int
+meta_back_proxy_authz_cred(
+ metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method )
+{
+ metainfo_t *mi = (metainfo_t *)op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval ndn;
+ int dobind = 0;
+
+ /* don't proxyAuthz if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+
+ } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+
+ } else {
+ ndn = op->o_ndn;
+ }
+ rs->sr_err = LDAP_SUCCESS;
+
+ /*
+ * FIXME: we need to let clients use proxyAuthz
+ * otherwise we cannot do symmetric pools of servers;
+ * we have to live with the fact that a user can
+ * authorize itself as any ID that is allowed
+ * by the authzTo directive of the "proxyauthzdn".
+ */
+ /*
+ * NOTE: current Proxy Authorization specification
+ * and implementation do not allow proxy authorization
+ * control to be provided with Bind requests
+ */
+ /*
+ * if no bind took place yet, but the connection is bound
+ * and the "proxyauthzdn" is set, then bind as
+ * "proxyauthzdn" and explicitly add the proxyAuthz
+ * control to every operation with the dn bound
+ * to the connection as control value.
+ */
+
+ /* bind as proxyauthzdn only if no idassert mode
+ * is requested, or if the client's identity
+ * is authorized */
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) )
+ {
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ }
+ }
+ break;
+
+ default:
+ /* NOTE: rootdn can always idassert */
+ if ( BER_BVISNULL( &ndn )
+ && mt->mt_idassert_authz == NULL
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+
+ } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz,
+ &authcDN, &authcDN );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+ }
+ }
+
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ break;
+ }
+
+ if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults = NULL;
+ struct berval authzID = BER_BVNULL;
+ int freeauthz = 0;
+
+ /* if SASL supports native authz, prepare for it */
+ if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ authzID = mt->mt_idassert_authzID;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+
+ case LDAP_BACK_IDASSERT_SELF:
+ if ( BER_BVISNULL( &ndn ) ) {
+ /* connection is not authc'd, so don't idassert */
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+ }
+ authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len;
+ authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx );
+ AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ),
+ ndn.bv_val, ndn.bv_len + 1 );
+ freeauthz = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_secprops != NULL ) {
+ rs->sr_err = ldap_set_option( msc->msc_ld,
+ LDAP_OPT_X_SASL_SECPROPS,
+ (void *)mt->mt_idassert_secprops );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+ }
+
+ defaults = lutil_sasl_defaults( msc->msc_ld,
+ mt->mt_idassert_sasl_mech.bv_val,
+ mt->mt_idassert_sasl_realm.bv_val,
+ mt->mt_idassert_authcID.bv_val,
+ mt->mt_idassert_passwd.bv_val,
+ authzID.bv_val );
+ if ( defaults == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+ rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val,
+ mt->mt_idassert_sasl_mech.bv_val, NULL, NULL,
+ LDAP_SASL_QUIET, lutil_sasl_interact,
+ defaults );
+
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+
+ } else {
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+
+ lutil_sasl_freedefs( defaults );
+ if ( freeauthz ) {
+ slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+ }
+
+ goto done;
+#endif /* HAVE_CYRUS_SASL */
+ }
+
+ *method = mt->mt_idassert_authmethod;
+ switch ( mt->mt_idassert_authmethod ) {
+ case LDAP_AUTH_NONE:
+ BER_BVSTR( binddn, "" );
+ BER_BVSTR( bindcred, "" );
+ /* fallthru */
+
+ case LDAP_AUTH_SIMPLE:
+ break;
+
+ default:
+ /* unsupported! */
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ break;
+ }
+
+done:;
+
+ if ( !BER_BVISEMPTY( binddn ) ) {
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+meta_back_proxy_authz_bind(
+ metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ metainfo_t *mi = (metainfo_t *)op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval binddn = BER_BVC( "" ),
+ cred = BER_BVC( "" );
+ int method = LDAP_AUTH_NONE,
+ rc;
+
+ rc = meta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method );
+ if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ int msgid;
+
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+
+ if(!dolock) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+ binddn.bv_val, LDAP_SASL_SIMPLE,
+ &cred, NULL, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ if(!dolock) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ rc = meta_back_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+ if ( rc == LDAP_SUCCESS ) {
+ /* set rebind stuff in case of successful proxyAuthz bind,
+ * so that referral chasing is attempted using the right
+ * identity */
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+ }
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ return LDAP_BACK_CONN_ISBOUND( msc );
+}
+
+/*
+ * Add controls;
+ *
+ * if any needs to be added, it is prepended to existing ones,
+ * in a newly allocated array. The companion function
+ * mi->mi_ldap_extra->controls_free() must be used to restore the original
+ * status of op->o_ctrls.
+ */
+int
+meta_back_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate,
+ LDAPControl ***pctrls )
+{
+ metainfo_t *mi = (metainfo_t *)op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ LDAPControl **ctrls = NULL;
+ /* set to the maximum number of controls this backend can add */
+ LDAPControl c[ 2 ] = {{ 0 }};
+ int n = 0, i, j1 = 0, j2 = 0, skipped = 0;
+
+ *pctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* don't add controls if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __before__ existing ones here */
+
+ /* proxyAuthz for identity assertion */
+ switch ( mi->mi_ldap_extra->proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn,
+ mt->mt_version, &mt->mt_idassert, &c[ j1 ] ) )
+ {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j1++;
+ break;
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ /* session tracking */
+ if ( META_BACK_TGT_ST_REQUEST( mt ) ) {
+ switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j2++;
+ break;
+
+ default:
+ goto done;
+ }
+ }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ /* if nothing to do, just bail out */
+ if ( j1 == 0 && j2 == 0 ) {
+ goto done;
+ }
+
+ assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) );
+
+ if ( op->o_ctrls ) {
+ for ( n = 0; op->o_ctrls[ n ]; n++ )
+ /* just count ctrls */ ;
+ }
+
+ ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ),
+ op->o_tmpmemctx );
+ if ( j1 ) {
+ ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+ *ctrls[ 0 ] = c[ 0 ];
+ for ( i = 1; i < j1; i++ ) {
+ ctrls[ i ] = &ctrls[ 0 ][ i ];
+ *ctrls[ i ] = c[ i ];
+ }
+ }
+
+ i = 0;
+ if ( op->o_ctrls ) {
+ LDAPControl *proxyauthz = ldap_control_find(
+ LDAP_CONTROL_PROXY_AUTHZ, op->o_ctrls, NULL );
+
+ for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+ /* Only replace it if we generated one */
+ if ( j1 && proxyauthz && proxyauthz == op->o_ctrls[ i ] ) {
+ /* Frontend has already checked only one is present */
+ assert( skipped == 0 );
+ skipped++;
+ continue;
+ }
+ ctrls[ i + j1 - skipped ] = op->o_ctrls[ i ];
+ }
+ }
+
+ n += j1 - skipped;
+ if ( j2 ) {
+ ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+ *ctrls[ n ] = c[ j1 ];
+ for ( i = 1; i < j2; i++ ) {
+ ctrls[ n + i ] = &ctrls[ n ][ i ];
+ *ctrls[ n + i ] = c[ i ];
+ }
+ }
+
+ ctrls[ n + j2 ] = NULL;
+
+done:;
+ if ( ctrls == NULL ) {
+ ctrls = op->o_ctrls;
+ }
+
+ *pctrls = ctrls;
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/candidates.c b/servers/slapd/back-meta/candidates.c
new file mode 100644
index 0000000..ffb5992
--- /dev/null
+++ b/servers/slapd/back-meta/candidates.c
@@ -0,0 +1,282 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+/*
+ * The meta-directory has one suffix, called <suffix>.
+ * It handles a pool of target servers, each with a branch suffix
+ * of the form <branch X>,<suffix>, where <branch X> may be empty.
+ *
+ * When the meta-directory receives a request with a request DN that belongs
+ * to a branch, the corresponding target is invoked. When the request DN
+ * does not belong to a specific branch, all the targets that
+ * are compatible with the request DN are selected as candidates, and
+ * the request is spawned to all the candidate targets
+ *
+ * A request is characterized by a request DN. The following cases are
+ * handled:
+ * - the request DN is the suffix: <dn> == <suffix>,
+ * all the targets are candidates (search ...)
+ * - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or
+ * - the request DN is a subtree of a branch suffix:
+ * <dn> == <rdn>,<branch X>,<suffix>,
+ * the target is the only candidate.
+ *
+ * A possible extension will include the handling of multiple suffixes
+ */
+
+static metasubtree_t *
+meta_subtree_match( metatarget_t *mt, struct berval *ndn, int scope )
+{
+ metasubtree_t *ms = mt->mt_subtree;
+
+ for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) ) {
+ return ms;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) &&
+ ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) )
+ {
+ return ms;
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* NOTE: cannot handle scope */
+ if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
+ return ms;
+ }
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * returns 1 if suffix is candidate for dn, otherwise 0
+ *
+ * Note: this function should never be called if dn is the <suffix>.
+ */
+int
+meta_back_is_candidate(
+ metatarget_t *mt,
+ struct berval *ndn,
+ int scope )
+{
+ struct berval rdn;
+ int d = ndn->bv_len - mt->mt_nsuffix.bv_len;
+
+ if ( d >= 0 ) {
+ if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ /*
+ * | match | exclude |
+ * +---------+---------+-------------------+
+ * | T | T | not candidate |
+ * | F | T | continue checking |
+ * +---------+---------+-------------------+
+ * | T | F | candidate |
+ * | F | F | not candidate |
+ * +---------+---------+-------------------+
+ */
+
+ if ( mt->mt_subtree ) {
+ int match = ( meta_subtree_match( mt, ndn, scope ) != NULL );
+
+ if ( !mt->mt_subtree_exclude ) {
+ return match ? META_CANDIDATE : META_NOT_CANDIDATE;
+ }
+
+ if ( match /* && mt->mt_subtree_exclude */ ) {
+ return META_NOT_CANDIDATE;
+ }
+ }
+
+ switch ( mt->mt_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ default:
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( d > 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_ONELEVEL:
+ if ( d > 0 ) {
+ rdn.bv_val = ndn->bv_val;
+ rdn.bv_len = (ber_len_t)d - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_BASE:
+ if ( d == 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+
+ } else /* if ( d < 0 ) */ {
+ if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ switch ( scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ /*
+ * suffix longer than dn, but common part matches
+ */
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_ONELEVEL:
+ rdn.bv_val = mt->mt_nsuffix.bv_val;
+ rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+ }
+
+ return META_NOT_CANDIDATE;
+}
+
+/*
+ * meta_back_select_unique_candidate
+ *
+ * returns the index of the candidate in case it is unique, otherwise
+ * META_TARGET_NONE if none matches, or
+ * META_TARGET_MULTIPLE if more than one matches
+ * Note: ndn MUST be normalized.
+ */
+int
+meta_back_select_unique_candidate(
+ metainfo_t *mi,
+ struct berval *ndn )
+{
+ int i, candidate = META_TARGET_NONE;
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( meta_back_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) {
+ if ( candidate == META_TARGET_NONE ) {
+ candidate = i;
+
+ } else {
+ return META_TARGET_MULTIPLE;
+ }
+ }
+ }
+
+ return candidate;
+}
+
+/*
+ * meta_clear_unused_candidates
+ *
+ * clears all candidates except candidate
+ */
+int
+meta_clear_unused_candidates(
+ Operation *op,
+ int candidate )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ int i;
+ SlapReply *candidates = meta_back_candidates_get( op );
+
+ for ( i = 0; i < mi->mi_ntargets; ++i ) {
+ if ( i == candidate ) {
+ continue;
+ }
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ }
+
+ return 0;
+}
+
+/*
+ * meta_clear_one_candidate
+ *
+ * clears the selected candidate
+ */
+int
+meta_clear_one_candidate(
+ Operation *op,
+ metaconn_t *mc,
+ int candidate )
+{
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ if ( msc->msc_ld != NULL ) {
+
+#ifdef DEBUG_205
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_clear_one_candidate ldap_unbind_ext[%d] mc=%p ld=%p\n",
+ op ? op->o_log_prefix : "", candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+ msc->msc_ld = NULL;
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ msc->msc_mscflags = 0;
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-meta/compare.c b/servers/slapd/back-meta/compare.c
new file mode 100644
index 0000000..f6fd54d
--- /dev/null
+++ b/servers/slapd/back-meta/compare.c
@@ -0,0 +1,154 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_compare( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt;
+ metaconn_t *mc;
+ int rc = 0;
+ int candidate = -1;
+ struct berval mdn = BER_BVNULL;
+ dncookie dc;
+ struct berval mapped_attr = op->orc_ava->aa_desc->ad_cname;
+ struct berval mapped_value = op->orc_ava->aa_value;
+ int msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR );
+ if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ assert( mc->mc_conns[ candidate ].msc_ld != NULL );
+
+ /*
+ * Rewrite the modify dn, if needed
+ */
+ mt = mi->mi_targets[ candidate ];
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "compareDN";
+
+ switch ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ rc = 1;
+ goto cleanup;
+
+ default:
+ break;
+ }
+
+ /*
+ * if attr is objectClass, try to remap the value
+ */
+ if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass ) {
+ ldap_back_map( &mt->mt_rwmap.rwm_oc,
+ &op->orc_ava->aa_value,
+ &mapped_value, BACKLDAP_MAP );
+
+ if ( BER_BVISNULL( &mapped_value ) || BER_BVISEMPTY( &mapped_value ) ) {
+ goto cleanup;
+ }
+
+ /*
+ * else try to remap the attribute
+ */
+ } else {
+ ldap_back_map( &mt->mt_rwmap.rwm_at,
+ &op->orc_ava->aa_desc->ad_cname,
+ &mapped_attr, BACKLDAP_MAP );
+ if ( BER_BVISNULL( &mapped_attr ) || BER_BVISEMPTY( &mapped_attr ) ) {
+ goto cleanup;
+ }
+
+ if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ {
+ dc.ctx = "compareAttrDN";
+
+ switch ( ldap_back_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value ) )
+ {
+ case LDAP_UNWILLING_TO_PERFORM:
+ rc = 1;
+ goto cleanup;
+
+ default:
+ break;
+ }
+ }
+ }
+
+retry:;
+ ctrls = op->o_ctrls;
+ rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_compare_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val,
+ mapped_attr.bv_val, &mapped_value,
+ ctrls, NULL, &msgid );
+
+ rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid,
+ mt->mt_timeout[ SLAP_OP_COMPARE ], ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+cleanup:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ free( mdn.bv_val );
+ }
+
+ if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) {
+ free( mapped_value.bv_val );
+ }
+
+ if ( mc ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/config.c b/servers/slapd/back-meta/config.c
new file mode 100644
index 0000000..6b1e607
--- /dev/null
+++ b/servers/slapd/back-meta/config.c
@@ -0,0 +1,3300 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldif.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+#ifdef LDAP_DEVEL
+#define SLAP_AUTH_DN 1
+#endif
+
+static ConfigDriver meta_back_cf_gen;
+static ConfigLDAPadd meta_ldadd;
+static ConfigCfAdd meta_cfadd;
+
+static int ldap_back_map_config(
+ ConfigArgs *c,
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map );
+
+/* Three sets of enums:
+ * 1) attrs that are only valid in the base config
+ * 2) attrs that are valid in base or target
+ * 3) attrs that are only valid in a target
+ */
+
+/* Base attrs */
+enum {
+ LDAP_BACK_CFG_CONN_TTL = 1,
+ LDAP_BACK_CFG_DNCACHE_TTL,
+ LDAP_BACK_CFG_IDLE_TIMEOUT,
+ LDAP_BACK_CFG_ONERR,
+ LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ LDAP_BACK_CFG_SINGLECONN,
+ LDAP_BACK_CFG_USETEMP,
+ LDAP_BACK_CFG_CONNPOOLMAX,
+ LDAP_BACK_CFG_LAST_BASE
+};
+
+/* Base or target */
+enum {
+ LDAP_BACK_CFG_BIND_TIMEOUT = LDAP_BACK_CFG_LAST_BASE,
+ LDAP_BACK_CFG_CANCEL,
+ LDAP_BACK_CFG_CHASE,
+ LDAP_BACK_CFG_CLIENT_PR,
+ LDAP_BACK_CFG_DEFAULT_T,
+ LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ LDAP_BACK_CFG_NOREFS,
+ LDAP_BACK_CFG_NOUNDEFFILTER,
+ LDAP_BACK_CFG_NRETRIES,
+ LDAP_BACK_CFG_QUARANTINE,
+ LDAP_BACK_CFG_REBIND,
+ LDAP_BACK_CFG_TIMEOUT,
+ LDAP_BACK_CFG_VERSION,
+ LDAP_BACK_CFG_ST_REQUEST,
+ LDAP_BACK_CFG_T_F,
+ LDAP_BACK_CFG_TLS,
+ LDAP_BACK_CFG_LAST_BOTH
+};
+
+/* Target attrs */
+enum {
+ LDAP_BACK_CFG_URI = LDAP_BACK_CFG_LAST_BOTH,
+ LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ LDAP_BACK_CFG_IDASSERT_BIND,
+ LDAP_BACK_CFG_REWRITE,
+ LDAP_BACK_CFG_SUFFIXM,
+ LDAP_BACK_CFG_MAP,
+ LDAP_BACK_CFG_SUBTREE_EX,
+ LDAP_BACK_CFG_SUBTREE_IN,
+ LDAP_BACK_CFG_PSEUDOROOTDN,
+ LDAP_BACK_CFG_PSEUDOROOTPW,
+ LDAP_BACK_CFG_KEEPALIVE,
+ LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ LDAP_BACK_CFG_FILTER,
+
+ LDAP_BACK_CFG_LAST
+};
+
+static ConfigTable metacfg[] = {
+ { "uri", "uri", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_URI,
+ meta_back_cf_gen, "( OLcfgDbAt:0.14 "
+ "NAME 'olcDbURI' "
+ "DESC 'URI (list) for remote DSA' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tls", "what", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TLS,
+ meta_back_cf_gen, "( OLcfgDbAt:3.1 "
+ "NAME 'olcDbStartTLS' "
+ "DESC 'StartTLS' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-bind", "args", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND,
+ meta_back_cf_gen, "( OLcfgDbAt:3.7 "
+ "NAME 'olcDbIDAssertBind' "
+ "DESC 'Remote Identity Assertion administrative identity auth bind configuration' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-authzFrom", "authzRule", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ meta_back_cf_gen, "( OLcfgDbAt:3.9 "
+ "NAME 'olcDbIDAssertAuthzFrom' "
+ "DESC 'Remote Identity Assertion authz rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "rebind-as-user", "true|FALSE", 1, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND,
+ meta_back_cf_gen, "( OLcfgDbAt:3.10 "
+ "NAME 'olcDbRebindAsUser' "
+ "DESC 'Rebind as user' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "chase-referrals", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE,
+ meta_back_cf_gen, "( OLcfgDbAt:3.11 "
+ "NAME 'olcDbChaseReferrals' "
+ "DESC 'Chase referrals' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "t-f-support", "true|FALSE|discover", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_T_F,
+ meta_back_cf_gen, "( OLcfgDbAt:3.12 "
+ "NAME 'olcDbTFSupport' "
+ "DESC 'Absolute filters support' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "timeout", "timeout(list)", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT,
+ meta_back_cf_gen, "( OLcfgDbAt:3.14 "
+ "NAME 'olcDbTimeout' "
+ "DESC 'Per-operation timeouts' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idle-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT,
+ meta_back_cf_gen, "( OLcfgDbAt:3.15 "
+ "NAME 'olcDbIdleTimeout' "
+ "DESC 'connection idle timeout' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "conn-ttl", "ttl", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL,
+ meta_back_cf_gen, "( OLcfgDbAt:3.16 "
+ "NAME 'olcDbConnTtl' "
+ "DESC 'connection ttl' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "network-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ meta_back_cf_gen, "( OLcfgDbAt:3.17 "
+ "NAME 'olcDbNetworkTimeout' "
+ "DESC 'connection network timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "protocol-version", "version", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION,
+ meta_back_cf_gen, "( OLcfgDbAt:3.18 "
+ "NAME 'olcDbProtocolVersion' "
+ "DESC 'protocol version' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "single-conn", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN,
+ meta_back_cf_gen, "( OLcfgDbAt:3.19 "
+ "NAME 'olcDbSingleConn' "
+ "DESC 'cache a single connection per identity' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "cancel", "ABANDON|ignore|exop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CANCEL,
+ meta_back_cf_gen, "( OLcfgDbAt:3.20 "
+ "NAME 'olcDbCancel' "
+ "DESC 'abandon/ignore/exop operations when appropriate' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "quarantine", "retrylist", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+ meta_back_cf_gen, "( OLcfgDbAt:3.21 "
+ "NAME 'olcDbQuarantine' "
+ "DESC 'Quarantine database if connection fails and retry according to rule' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "use-temporary-conn", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP,
+ meta_back_cf_gen, "( OLcfgDbAt:3.22 "
+ "NAME 'olcDbUseTemporaryConn' "
+ "DESC 'Use temporary connections if the cached one is busy' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "conn-pool-max", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX,
+ meta_back_cf_gen, "( OLcfgDbAt:3.23 "
+ "NAME 'olcDbConnectionPoolMax' "
+ "DESC 'Max size of privileged connections pool' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ { "session-tracking-request", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST,
+ meta_back_cf_gen, "( OLcfgDbAt:3.24 "
+ "NAME 'olcDbSessionTrackingRequest' "
+ "DESC 'Add session tracking control to proxied requests' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+ { "norefs", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS,
+ meta_back_cf_gen, "( OLcfgDbAt:3.25 "
+ "NAME 'olcDbNoRefs' "
+ "DESC 'Do not return search reference responses' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "noundeffilter", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER,
+ meta_back_cf_gen, "( OLcfgDbAt:3.26 "
+ "NAME 'olcDbNoUndefFilter' "
+ "DESC 'Do not propagate undefined search filters' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "rewrite", "arglist", 2, 0, STRLENOF( "rewrite" ),
+ ARG_MAGIC|LDAP_BACK_CFG_REWRITE,
+ meta_back_cf_gen, "( OLcfgDbAt:3.101 "
+ "NAME 'olcDbRewrite' "
+ "DESC 'DN rewriting rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "suffixmassage", "virtual> <real", 2, 3, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM,
+ meta_back_cf_gen, NULL, NULL, NULL },
+
+ { "map", "attribute|objectClass> [*|<local>] *|<remote", 3, 4, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_MAP,
+ meta_back_cf_gen, "( OLcfgDbAt:3.102 "
+ "NAME 'olcDbMap' "
+ "DESC 'Map attribute and objectclass names' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+
+ { "subtree-exclude", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX,
+ meta_back_cf_gen, "( OLcfgDbAt:3.103 "
+ "NAME 'olcDbSubtreeExclude' "
+ "DESC 'DN of subtree to exclude from target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "subtree-include", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN,
+ meta_back_cf_gen, "( OLcfgDbAt:3.104 "
+ "NAME 'olcDbSubtreeInclude' "
+ "DESC 'DN of subtree to include in target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "default-target", "[none|<target ID>]", 1, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T,
+ meta_back_cf_gen, "( OLcfgDbAt:3.105 "
+ "NAME 'olcDbDefaultTarget' "
+ "DESC 'Specify the default target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "dncache-ttl", "ttl", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DNCACHE_TTL,
+ meta_back_cf_gen, "( OLcfgDbAt:3.106 "
+ "NAME 'olcDbDnCacheTtl' "
+ "DESC 'dncache ttl' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "bind-timeout", "microseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_ULONG|LDAP_BACK_CFG_BIND_TIMEOUT,
+ meta_back_cf_gen, "( OLcfgDbAt:3.107 "
+ "NAME 'olcDbBindTimeout' "
+ "DESC 'bind timeout' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "onerr", "CONTINUE|report|stop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_ONERR,
+ meta_back_cf_gen, "( OLcfgDbAt:3.108 "
+ "NAME 'olcDbOnErr' "
+ "DESC 'error handling' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "pseudoroot-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ meta_back_cf_gen, "( OLcfgDbAt:3.109 "
+ "NAME 'olcDbPseudoRootBindDefer' "
+ "DESC 'error handling' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "root-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ meta_back_cf_gen, NULL, NULL, NULL },
+ { "pseudorootdn", "dn", 2, 2, 0,
+ ARG_MAGIC|ARG_DN|ARG_QUOTE|LDAP_BACK_CFG_PSEUDOROOTDN,
+ meta_back_cf_gen, NULL, NULL, NULL },
+ { "pseudorootpw", "password", 2, 2, 0,
+ ARG_MAGIC|ARG_STRING|LDAP_BACK_CFG_PSEUDOROOTPW,
+ meta_back_cf_gen, NULL, NULL, NULL },
+ { "nretries", "NEVER|forever|<number>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NRETRIES,
+ meta_back_cf_gen, "( OLcfgDbAt:3.110 "
+ "NAME 'olcDbNretries' "
+ "DESC 'retry handling' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR,
+ meta_back_cf_gen, "( OLcfgDbAt:3.111 "
+ "NAME 'olcDbClientPr' "
+ "DESC 'PagedResults handling' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "", "", 0, 0, 0, ARG_IGNORED,
+ NULL, "( OLcfgDbAt:3.100 NAME 'olcMetaSub' "
+ "DESC 'Placeholder to name a Target entry' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+
+ { "keepalive", "keepalive", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE,
+ meta_back_cf_gen, "( OLcfgDbAt:3.29 "
+ "NAME 'olcDbKeepalive' "
+ "DESC 'TCP keepalive' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "tcp-user-timeout", "milliseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ meta_back_cf_gen, "( OLcfgDbAt:3.30 "
+ "NAME 'olcDbTcpUserTimeout' "
+ "DESC 'TCP User Timeout' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "filter", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_FILTER,
+ meta_back_cf_gen, "( OLcfgDbAt:3.112 "
+ "NAME 'olcDbFilter' "
+ "DESC 'Filter regex pattern to include in target' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define ST_ATTR "$ olcDbSessionTrackingRequest "
+#else
+#define ST_ATTR ""
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define COMMON_ATTRS \
+ "$ olcDbBindTimeout " \
+ "$ olcDbCancel " \
+ "$ olcDbChaseReferrals " \
+ "$ olcDbClientPr " \
+ "$ olcDbDefaultTarget " \
+ "$ olcDbNetworkTimeout " \
+ "$ olcDbNoRefs " \
+ "$ olcDbNoUndefFilter " \
+ "$ olcDbNretries " \
+ "$ olcDbProtocolVersion " \
+ "$ olcDbQuarantine " \
+ "$ olcDbRebindAsUser " \
+ ST_ATTR \
+ "$ olcDbStartTLS " \
+ "$ olcDbTFSupport "
+
+static ConfigOCs metaocs[] = {
+ { "( OLcfgDbOc:3.2 "
+ "NAME 'olcMetaConfig' "
+ "DESC 'Meta backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcDbConnTtl "
+ "$ olcDbDnCacheTtl "
+ "$ olcDbIdleTimeout "
+ "$ olcDbOnErr "
+ "$ olcDbPseudoRootBindDefer "
+ "$ olcDbSingleConn "
+ "$ olcDbUseTemporaryConn "
+ "$ olcDbConnectionPoolMax "
+
+ /* defaults, may be overridden per-target */
+ COMMON_ATTRS
+ ") )",
+ Cft_Database, metacfg, NULL, meta_cfadd },
+ { "( OLcfgDbOc:3.3 "
+ "NAME 'olcMetaTargetConfig' "
+ "DESC 'Meta target configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( olcMetaSub $ olcDbURI ) "
+ "MAY ( olcDbIDAssertAuthzFrom "
+ "$ olcDbIDAssertBind "
+ "$ olcDbMap "
+ "$ olcDbRewrite "
+ "$ olcDbSubtreeExclude "
+ "$ olcDbSubtreeInclude "
+ "$ olcDbTimeout "
+ "$ olcDbKeepalive "
+ "$ olcDbTcpUserTimeout "
+ "$ olcDbFilter "
+
+ /* defaults may be inherited */
+ COMMON_ATTRS
+ ") )",
+ Cft_Misc, metacfg, meta_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int
+meta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c )
+{
+ if ( p->ce_type != Cft_Database || !p->ce_be ||
+ p->ce_be->be_cf_ocs != metaocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ c->be = p->ce_be;
+ return LDAP_SUCCESS;
+}
+
+static int
+meta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c )
+{
+ metainfo_t *mi = ( metainfo_t * )c->be->be_private;
+ struct berval bv;
+ int i;
+
+ bv.bv_val = c->cr_msg;
+ for ( i=0; i<mi->mi_ntargets; i++ ) {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "olcMetaSub=" SLAP_X_ORDERED_FMT "uri", i );
+ c->ca_private = mi->mi_targets[i];
+ c->valx = i;
+ config_build_entry( op, rs, p->e_private, c,
+ &bv, &metaocs[1], NULL );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+meta_rwi_init( struct rewrite_info **rwm_rw )
+{
+ char *rargv[ 3 ];
+
+ *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+ if ( *rwm_rw == NULL ) {
+ return -1;
+ }
+ /*
+ * the filter rewrite as a string must be disabled
+ * by default; it can be re-enabled by adding rules;
+ * this creates an empty rewriteContext
+ */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchFilter";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+ return 0;
+}
+
+static int
+meta_back_new_target(
+ metatarget_t **mtp )
+{
+ metatarget_t *mt;
+
+ *mtp = NULL;
+
+ mt = ch_calloc( sizeof( metatarget_t ), 1 );
+
+ if ( meta_rwi_init( &mt->mt_rwmap.rwm_rw )) {
+ ch_free( mt );
+ return -1;
+ }
+
+ ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex );
+
+ mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY;
+ mt->mt_idassert_authmethod = LDAP_AUTH_NONE;
+ mt->mt_idassert_tls = SB_TLS_DEFAULT;
+
+ /* by default, use proxyAuthz control on each operation */
+ mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+ *mtp = mt;
+
+ return 0;
+}
+
+/* Validation for suffixmassage_config */
+static int
+meta_suffixm_config(
+ ConfigArgs *c,
+ int argc,
+ char **argv,
+ metatarget_t *mt
+)
+{
+ BackendDB *tmp_bd;
+ struct berval dn, nvnc, pvnc, nrnc, prnc;
+ int j, rc;
+
+ /*
+ * syntax:
+ *
+ * suffixmassage <suffix> <massaged suffix>
+ *
+ * the <suffix> field must be defined as a valid suffix
+ * (or suffixAlias?) for the current database;
+ * the <massaged suffix> shouldn't have already been
+ * defined as a valid suffix or suffixAlias for the
+ * current server
+ */
+
+ ber_str2bv( argv[ 1 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" is invalid",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &nvnc, &c->be->be_nsuffix[ 0 ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" must be within the database naming context",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ ber_str2bv( argv[ 2 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "massaged suffix \"%s\" is invalid",
+ argv[2] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ tmp_bd = select_backend( &nrnc, 0 );
+ if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning: <massaged suffix> \"%s\" resolves to this database, in "
+ "\"suffixMassage <suffix> <massaged suffix>\"\n",
+ c->log, prnc.bv_val );
+ }
+
+ /*
+ * The suffix massaging is emulated by means of the
+ * rewrite capabilities
+ */
+ rc = suffix_massage_config( mt->mt_rwmap.rwm_rw,
+ &pvnc, &nvnc, &prnc, &nrnc );
+
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ free( prnc.bv_val );
+ free( nrnc.bv_val );
+
+ return rc;
+}
+
+int
+meta_subtree_free( metasubtree_t *ms )
+{
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ber_memfree( ms->ms_dn.bv_val );
+ break;
+
+ case META_ST_REGEX:
+ regfree( &ms->ms_regex );
+ ber_memfree( ms->ms_regex_pattern.bv_val );
+ break;
+
+ default:
+ return -1;
+ }
+
+ ch_free( ms );
+ return 0;
+}
+
+int
+meta_subtree_destroy( metasubtree_t *ms )
+{
+ if ( ms->ms_next ) {
+ meta_subtree_destroy( ms->ms_next );
+ }
+
+ return meta_subtree_free( ms );
+}
+
+static void
+meta_filter_free( metafilter_t *mf )
+{
+ regfree( &mf->mf_regex );
+ ber_memfree( mf->mf_regex_pattern.bv_val );
+ ch_free( mf );
+}
+
+void
+meta_filter_destroy( metafilter_t *mf )
+{
+ if ( mf->mf_next )
+ meta_filter_destroy( mf->mf_next );
+ meta_filter_free( mf );
+}
+
+static struct berval st_styles[] = {
+ BER_BVC("subtree"),
+ BER_BVC("children"),
+ BER_BVC("regex")
+};
+
+static int
+meta_subtree_unparse(
+ ConfigArgs *c,
+ metatarget_t *mt )
+{
+ metasubtree_t *ms;
+ struct berval bv, *style;
+
+ if ( !mt->mt_subtree )
+ return 1;
+
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude )
+ return 1;
+
+ bv.bv_val = c->cr_msg;
+ for ( ms=mt->mt_subtree; ms; ms=ms->ms_next ) {
+ if (ms->ms_type == META_ST_SUBTREE)
+ style = &st_styles[0];
+ else if ( ms->ms_type == META_ST_SUBORDINATE )
+ style = &st_styles[1];
+ else if ( ms->ms_type == META_ST_REGEX )
+ style = &st_styles[2];
+ else {
+ assert(0);
+ continue;
+ }
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "dn.%s:%s", style->bv_val, ms->ms_dn.bv_val );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ return 0;
+}
+
+static int
+meta_subtree_config(
+ metatarget_t *mt,
+ ConfigArgs *c )
+{
+ meta_st_t type = META_ST_SUBTREE;
+ char *pattern;
+ struct berval ndn = BER_BVNULL;
+ metasubtree_t *ms = NULL;
+
+ if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) {
+ if ( mt->mt_subtree && !mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" );
+ return 1;
+ }
+
+ mt->mt_subtree_exclude = 1;
+
+ } else {
+ if ( mt->mt_subtree && mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" );
+ return 1;
+ }
+ }
+
+ pattern = c->argv[1];
+ if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) {
+ char *style;
+
+ pattern = &pattern[STRLENOF( "dn")];
+
+ if ( pattern[0] == '.' ) {
+ style = &pattern[1];
+
+ if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "subtree" )];
+
+ } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) {
+ type = META_ST_SUBORDINATE;
+ pattern = &style[STRLENOF( "children" )];
+
+ } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "sub" )];
+
+ } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) {
+ type = META_ST_REGEX;
+ pattern = &style[STRLENOF( "regex" )];
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" );
+ return 1;
+ }
+ }
+
+ if ( pattern[0] != ':' ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" );
+ return 1;
+ }
+ pattern++;
+ }
+
+ switch ( type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE: {
+ struct berval dn;
+
+ ber_str2bv( pattern, 0, 0, &dn );
+ if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL )
+ != LDAP_SUCCESS )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern );
+ return 1;
+ }
+
+ if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "DN=\"%s\" is not a subtree of target \"%s\"",
+ pattern, mt->mt_nsuffix.bv_val );
+ ber_memfree( ndn.bv_val );
+ return( 1 );
+ }
+ } break;
+
+ default:
+ /* silence warnings */
+ break;
+ }
+
+ ms = ch_calloc( sizeof( metasubtree_t ), 1 );
+ ms->ms_type = type;
+
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ms->ms_dn = ndn;
+ break;
+
+ case META_ST_REGEX: {
+ int rc;
+
+ rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE );
+ if ( rc != 0 ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+
+ regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) );
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ pattern, regerr );
+ ch_free( ms );
+ return 1;
+ }
+ ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern );
+ } break;
+ }
+
+ if ( mt->mt_subtree == NULL ) {
+ mt->mt_subtree = ms;
+
+ } else {
+ metasubtree_t **msp;
+
+ for ( msp = &mt->mt_subtree; *msp; ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ meta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ meta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ meta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ meta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ meta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ meta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ meta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ meta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_REGEX:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n",
+ c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val );
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* no check possible */
+ break;
+ }
+ break;
+ }
+
+ msp = &(*msp)->ms_next;
+ }
+
+ *msp = ms;
+ }
+
+ return 0;
+}
+
+static slap_verbmasks idassert_mode[] = {
+ { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF },
+ { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS },
+ { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT },
+ { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks tls_mode[] = {
+ { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK },
+ { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS },
+ { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK },
+ { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS },
+ { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS },
+ { BER_BVC( "none" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "yes" ), LDAP_BACK_F_T_F },
+ { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER },
+ { BER_BVC( "no" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks cancel_mode[] = {
+ { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE },
+ { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP },
+ { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER },
+ { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks onerr_mode[] = {
+ { BER_BVC( "stop" ), META_BACK_F_ONERR_STOP },
+ { BER_BVC( "report" ), META_BACK_F_ONERR_REPORT },
+ { BER_BVC( "continue" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+/* see enum in slap.h */
+static slap_cf_aux_table timeout_table[] = {
+ { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL },
+ /* unbind makes no sense */
+ { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL },
+ /* abandon makes little sense */
+#if 0 /* not implemented yet */
+ { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL },
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+static int
+meta_cf_cleanup( ConfigArgs *c )
+{
+ metainfo_t *mi = ( metainfo_t * )c->be->be_private;
+ metatarget_t *mt = c->ca_private;
+
+ return meta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg ));
+}
+
+static int
+meta_back_cf_gen( ConfigArgs *c )
+{
+ metainfo_t *mi = ( metainfo_t * )c->be->be_private;
+ metatarget_t *mt;
+ metacommon_t *mc;
+
+ int i, rc = 0;
+
+ assert( mi != NULL );
+
+ if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) {
+ if ( !mi )
+ return 1;
+
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ mc = &mt->mt_mc;
+ }
+ }
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch( c->type ) {
+ /* Base attrs */
+ case LDAP_BACK_CFG_CONN_TTL:
+ if ( mi->mi_conn_ttl == 0 ) {
+ return 1;
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_conn_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) {
+ return 1;
+ } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ if ( mi->mi_idle_timeout == 0 ) {
+ return 1;
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv );
+ if ( BER_BVISNULL( &bv )) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi );
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ c->value_int = LDAP_BACK_SINGLECONN( mi );
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ c->value_int = LDAP_BACK_USE_TEMPORARIES( mi );
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ c->value_int = mi->mi_conn_priv_max;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ if ( mc->mc_bind_timeout.tv_sec == 0 &&
+ mc->mc_bind_timeout.tv_usec == 0 ) {
+ return 1;
+ } else {
+ c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL +
+ mc->mc_bind_timeout.tv_usec;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CANCEL: {
+ slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2;
+
+ if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ mask &= ~LDAP_BACK_F_CANCEL_EXOP;
+ }
+ enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_CHASE:
+ c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc);
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) {
+ return 1;
+ } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) {
+ BER_BVSTR( &bv, "accept-unsolicited" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE )
+ return 1;
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ if ( mc->mc_network_timeout == 0 ) {
+ return 1;
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ lutil_unparse_time( buf, sizeof( buf ), mc->mc_network_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ c->value_int = META_BACK_CMN_NOREFS(mc);
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc);
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ if ( mc->mc_nretries == META_RETRY_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else if ( mc->mc_nretries == META_RETRY_NEVER ) {
+ BER_BVSTR( &bv, "never" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d",
+ mc->mc_nretries );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( !META_BACK_CMN_QUARANTINE( mc )) {
+ rc = 1;
+ break;
+ }
+ rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv );
+ if ( rc == 0 ) {
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ c->value_int = META_BACK_CMN_SAVECRED(mc);
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( mc->mc_timeout[ i ] != 0 ) {
+ break;
+ }
+ }
+
+ if ( i == SLAP_OP_LAST ) {
+ return 1;
+ }
+
+ BER_BVZERO( &bv );
+ slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table );
+
+ if ( BER_BVISNULL( &bv ) ) {
+ return 1;
+ }
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( mc->mc_version == 0 )
+ return 1;
+ c->value_int = mc->mc_version;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ c->value_int = META_BACK_CMN_ST_REQUEST( mc );
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS: {
+ struct berval bc = BER_BVNULL, bv2;
+
+ if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) {
+ rc = 1;
+ break;
+ }
+ enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv );
+ assert( !BER_BVISNULL( &bv ) );
+
+ if ( mt ) {
+ bindconf_tls_unparse( &mt->mt_tls, &bc );
+ }
+
+ if ( !BER_BVISEMPTY( &bc )) {
+ bv2.bv_len = bv.bv_len + bc.bv_len + 1;
+ bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+ strcpy( bv2.bv_val, bv.bv_val );
+ bv2.bv_val[bv.bv_len] = ' ';
+ strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val );
+ ber_memfree( bc.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv2 );
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI: {
+ char *p2, *p1 = strchr( mt->mt_uri, ' ' );
+ bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ p2 = bv.bv_val;
+ *p2++ = '"';
+ if ( p1 ) {
+ p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri );
+ } else {
+ p2 = lutil_strcopy( p2, mt->mt_uri );
+ }
+ *p2++ = '/';
+ p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val );
+ *p2++ = '"';
+ if ( p1 ) {
+ strcpy( p2, p1 );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+ int i;
+ struct berval bv = BER_BVNULL;
+ char buf[SLAP_TEXT_BUFLEN];
+
+ bvp = &mt->mt_idassert_authz;
+ if ( *bvp == NULL ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ {
+ BER_BVSTR( &bv, "*" );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) {
+ char *ptr;
+ int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len = ((*bvp)[ i ]).bv_len + len;
+ bv.bv_val = ch_realloc( bv.bv_val, bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ if ( bv.bv_val ) {
+ ber_memfree( bv.bv_val );
+ }
+ break;
+ }
+
+ case LDAP_BACK_CFG_IDASSERT_BIND: {
+ int i;
+ struct berval bc = BER_BVNULL;
+ char *ptr;
+
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) {
+ return 1;
+ } else {
+ ber_len_t len;
+
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ break;
+
+ default: {
+ struct berval mode = BER_BVNULL;
+
+ enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode );
+ if ( BER_BVISNULL( &mode ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ ptr = lutil_strcopy( bv.bv_val, "mode=" );
+ ptr = lutil_strcopy( ptr, mode.bv_val );
+ }
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) {
+ len = bv.bv_len + STRLENOF( "authz=native" );
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ (void)lutil_strcopy( ptr, "authz=native" );
+ }
+
+ len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" );
+ /* flags */
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ ptr = lutil_strcopy( ptr, "flags=" );
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ ptr = lutil_strcopy( ptr, "prescriptive" );
+ } else {
+ ptr = lutil_strcopy( ptr, "non-prescriptive" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ ptr = lutil_strcopy( ptr, ",override" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" );
+
+ } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-critical" );
+
+ } else {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" );
+ }
+
+#ifdef SLAP_AUTH_DN
+ switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) {
+ case LDAP_BACK_AUTH_DN_AUTHZID:
+ ptr = lutil_strcopy( ptr, ",dn-authzid" );
+ break;
+
+ case LDAP_BACK_AUTH_DN_WHOAMI:
+ ptr = lutil_strcopy( ptr, ",dn-whoami" );
+ break;
+
+ default:
+#if 0 /* implicit */
+ ptr = lutil_strcopy( ptr, ",dn-none" );
+#endif
+ break;
+ }
+#endif
+
+ bv.bv_len = ( ptr - bv.bv_val );
+ /* end-of-flags */
+ }
+
+ bindconf_unparse( &mt->mt_idassert.si_bc, &bc );
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ ber_len_t len = bv.bv_len + bc.bv_len;
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ assert( bc.bv_val[ 0 ] == ' ' );
+
+ ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val );
+ free( bc.bv_val );
+ bv.bv_len = ptr - bv.bv_val;
+
+ } else {
+ for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bc.bv_len -= i;
+ AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 );
+ }
+
+ bv = bc;
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+
+ break;
+ }
+
+ case LDAP_BACK_CFG_SUFFIXM: /* unused */
+ case LDAP_BACK_CFG_REWRITE:
+ if ( mt->mt_rwmap.rwm_bva_rewrite == NULL ) {
+ rc = 1;
+ } else {
+ rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_rewrite, &c->rvalue_vals );
+ }
+ break;
+
+ case LDAP_BACK_CFG_MAP:
+ if ( mt->mt_rwmap.rwm_bva_map == NULL ) {
+ rc = 1;
+ } else {
+ rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_map, &c->rvalue_vals );
+ }
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ rc = meta_subtree_unparse( c, mt );
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( mt->mt_filter == NULL ) {
+ rc = 1;
+ } else {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next )
+ value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern );
+ }
+ break;
+
+ /* replaced by idassert */
+ case LDAP_BACK_CFG_PSEUDOROOTDN:
+ case LDAP_BACK_CFG_PSEUDOROOTPW:
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ char buf[AC_LINE_MAX];
+ bv.bv_len = AC_LINE_MAX;
+ bv.bv_val = &buf[0];
+ slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1);
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ }
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ c->value_uint = mt->mt_tls.sb_tcp_user_timeout;
+ break;
+
+
+ default:
+ rc = 1;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ /* Base attrs */
+ case LDAP_BACK_CFG_CONN_TTL:
+ mi->mi_conn_ttl = 0;
+ break;
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ mi->mi_idle_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN;
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES;
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ mc->mc_bind_timeout.tv_sec = 0;
+ mc->mc_bind_timeout.tv_usec = 0;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ mc->mc_network_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ mc->mc_nretries = META_RETRY_DEFAULT;
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( META_BACK_CMN_QUARANTINE( mc )) {
+ mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine );
+ mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE;
+ if ( mc == &mt->mt_mc ) {
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ mt->mt_isquarantined = 0;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ mc->mc_timeout[ i ] = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ mc->mc_version = 0;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ if ( mt )
+ bindconf_free( &mt->mt_tls );
+ break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI:
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ mt->mt_uri = NULL;
+ }
+ /* FIXME: should have a way to close all cached
+ * connections associated with this target.
+ */
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+
+ bvp = &mt->mt_idassert_authz;
+ if ( c->valx < 0 ) {
+ if ( *bvp != NULL ) {
+ ber_bvarray_free( *bvp );
+ *bvp = NULL;
+ }
+
+ } else {
+ if ( *bvp == NULL ) {
+ rc = 1;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ )
+ ;
+
+ if ( i >= c->valx ) {
+ rc = 1;
+ break;
+ }
+ ber_memfree( ((*bvp)[ c->valx ]).bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) {
+ (*bvp)[ i ] = (*bvp)[ i + 1 ];
+ }
+ BER_BVZERO( &((*bvp)[ i ]) );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ bindconf_free( &mt->mt_idassert.si_bc );
+ memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) );
+ break;
+
+ case LDAP_BACK_CFG_SUFFIXM: /* unused */
+ case LDAP_BACK_CFG_REWRITE:
+ {
+ if ( c->valx >= 0 ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ );
+
+ if ( c->valx >= i ) {
+ rc = 1;
+ break;
+ }
+
+ ber_memfree( mt->mt_rwmap.rwm_bva_rewrite[ c->valx ].bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ] ); i++ )
+ {
+ mt->mt_rwmap.rwm_bva_rewrite[ i ] = mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ];
+ }
+ BER_BVZERO( &mt->mt_rwmap.rwm_bva_rewrite[ i ] );
+
+ rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+ assert( mt->mt_rwmap.rwm_rw == NULL );
+
+ rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw );
+
+ for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ )
+ {
+ ConfigArgs ca = { 0 };
+
+ ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ if ( !strcasecmp( ca.argv[0], "suffixmassage" )) {
+ rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt );
+ } else {
+ rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+ c->fname, c->lineno, ca.argc, ca.argv );
+ }
+
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ } else if ( mt->mt_rwmap.rwm_rw != NULL ) {
+ if ( mt->mt_rwmap.rwm_bva_rewrite ) {
+ ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite );
+ mt->mt_rwmap.rwm_bva_rewrite = NULL;
+ }
+ if ( mt->mt_rwmap.rwm_rw )
+ rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+
+ meta_rwi_init( &mt->mt_rwmap.rwm_rw );
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_MAP:
+ if ( mt->mt_rwmap.rwm_bva_map ) {
+ ber_bvarray_free( mt->mt_rwmap.rwm_bva_map );
+ mt->mt_rwmap.rwm_bva_map = NULL;
+ }
+ meta_back_map_free( &mt->mt_rwmap.rwm_oc );
+ meta_back_map_free( &mt->mt_rwmap.rwm_at );
+ mt->mt_rwmap.rwm_oc.drop_missing = 0;
+ mt->mt_rwmap.rwm_at.drop_missing = 0;
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) {
+ rc = 1;
+ break;
+ }
+ if ( c->valx < 0 ) {
+ meta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ } else {
+ metasubtree_t *ms, **mprev;
+ for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = ms->ms_next;
+ meta_subtree_free( ms );
+ break;
+ }
+ i++;
+ mprev = &ms->ms_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( c->valx < 0 ) {
+ meta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ } else {
+ metafilter_t *mf, **mprev;
+ for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = mf->mf_next;
+ meta_filter_free( mf );
+ break;
+ }
+ i++;
+ mprev = &mf->mf_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE:
+ mt->mt_tls.sb_keepalive.sk_idle = 0;
+ mt->mt_tls.sb_keepalive.sk_probes = 0;
+ mt->mt_tls.sb_keepalive.sk_interval = 0;
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = 0;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ if ( c->op == SLAP_CONFIG_ADD ) {
+ if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) {
+ /* exclude CFG_URI from this check */
+ if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) {
+ if ( !mi->mi_ntargets ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ if ( mi->mi_ntargets ) {
+ mt = mi->mi_targets[ mi->mi_ntargets-1 ];
+ mc = &mt->mt_mc;
+ } else {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ }
+ }
+ } else {
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ if ( mt )
+ mc = &mt->mt_mc;
+ else
+ mc = NULL;
+ }
+ }
+
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI: {
+ LDAPURLDesc *ludp;
+ struct berval dn;
+ int j;
+
+ char **uris = NULL;
+
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "the suffix must be defined before any target" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ i = mi->mi_ntargets++;
+
+ mi->mi_targets = ( metatarget_t ** )ch_realloc( mi->mi_targets,
+ sizeof( metatarget_t * ) * mi->mi_ntargets );
+ if ( mi->mi_targets == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "out of memory while storing server name"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( meta_back_new_target( &mi->mi_targets[ i ] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to init server"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mt = mi->mi_targets[ i ];
+
+ mt->mt_rebind_f = mi->mi_rebind_f;
+ mt->mt_urllist_f = mi->mi_urllist_f;
+ mt->mt_urllist_p = mt;
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ mt->mt_mc = mi->mi_mc;
+
+ for ( j = 1; j < c->argc; j++ ) {
+ char **tmpuris = ldap_str2charray( c->argv[ j ], "\t" );
+
+ if ( tmpuris == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URIs #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( j == 1 ) {
+ uris = tmpuris;
+
+ } else {
+ ldap_charray_merge( &uris, tmpuris );
+ ldap_charray_free( tmpuris );
+ }
+ }
+
+ for ( j = 0; uris[ j ] != NULL; j++ ) {
+ char *tmpuri = NULL;
+
+ /*
+ * uri MUST be legal!
+ */
+ if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t",
+ LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS
+ || ludp->lud_next != NULL )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URI #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ if ( j == 0 ) {
+
+ /*
+ * uri MUST have the <dn> part!
+ */
+ if ( ludp->lud_dn == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing <naming context> "
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ /*
+ * copies and stores uri and suffix
+ */
+ ber_str2bv( ludp->lud_dn, 0, 0, &dn );
+ rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix,
+ &mt->mt_nsuffix, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "target DN is invalid \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ ludp->lud_dn[ 0 ] = '\0';
+
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_DEFAULT:
+ mt->mt_scope = LDAP_SCOPE_SUBTREE;
+ break;
+
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ mt->mt_scope = ludp->lud_scope;
+ break;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid scope for target \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ } else {
+ /* check all, to apply the scope check on the first one */
+ if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "multiple URIs must have no DN part" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+
+ }
+ }
+
+ tmpuri = ldap_url_list2urls( ludp );
+ ldap_free_urllist( ludp );
+ if ( tmpuri == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+ ldap_memfree( uris[ j ] );
+ uris[ j ] = tmpuri;
+ }
+
+ mt->mt_uri = ldap_charray2str( uris, " " );
+ ldap_charray_free( uris );
+ if ( mt->mt_uri == NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ /*
+ * uri MUST be a branch of suffix!
+ */
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<naming context> of URI must be within the naming context of this database." );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ c->ca_private = mt;
+ config_push_cleanup( c, meta_cf_cleanup );
+ } break;
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* subtree-exclude */
+ if ( meta_subtree_config( mt, c )) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER: {
+ metafilter_t *mf, **m2;
+ mf = ch_calloc( 1, sizeof( metafilter_t ));
+ rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED );
+ if ( rc ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+ regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ c->argv[1], regerr );
+ ch_free( mf );
+ return 1;
+ }
+ ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern );
+ for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next )
+ ;
+ *m2 = mf;
+ } break;
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ /* default target directive */
+ i = mi->mi_ntargets - 1;
+
+ if ( c->argc == 1 ) {
+ if ( i < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" alone must be inside a \"uri\" directive",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_defaulttarget = i;
+
+ } else {
+ if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) {
+ if ( i >= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s none\" should go before uri definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+
+ } else {
+
+ if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0
+ || mi->mi_defaulttarget < 0
+ || mi->mi_defaulttarget >= i - 1 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal target number %d",
+ mi->mi_defaulttarget );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ /* ttl of dn cache */
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+
+ } else {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse dncache ttl \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_cache.ttl = (time_t)t;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+ /* network timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse network timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_network_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+ /* idle timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse idle timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+
+ }
+ mi->mi_idle_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_CONN_TTL: {
+ /* conn ttl */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse conn ttl \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+
+ }
+ mi->mi_conn_ttl = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ /* bind timeout when connecting to ldap servers */
+ mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000;
+ mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000;
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ /* save bind creds for referral rebinds? */
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_SAVECRED;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ i = verb_to_mask( c->argv[1], tls_mode );
+ if ( BER_BVISNULL( &tls_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ mc->mc_flags |= tls_mode[i].mask;
+
+ if ( c->argc > 2 ) {
+ if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( i = 2; i < c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls ))
+ return 1;
+ }
+ bindconf_tls_defaults( &mt->mt_tls );
+ }
+ break;
+
+ case LDAP_BACK_CFG_T_F:
+ i = verb_to_mask( c->argv[1], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ mc->mc_flags |= t_f_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ /* onerr? */
+ i = verb_to_mask( c->argv[1], onerr_mode );
+ if ( BER_BVISNULL( &onerr_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ mi->mi_flags |= onerr_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ /* bind-defer? */
+ if ( c->argc == 1 || c->value_int ) {
+ mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND;
+ } else {
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ }
+ break;
+
+ case LDAP_BACK_CFG_SINGLECONN:
+ /* single-conn? */
+ if ( mi->mi_ntargets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" must appear before target definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( c->value_int ) {
+ mi->mi_flags |= LDAP_BACK_F_SINGLECONN;
+ } else {
+ mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN;
+ }
+ break;
+
+ case LDAP_BACK_CFG_USETEMP:
+ /* use-temporaries? */
+ if ( mi->mi_ntargets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" must appear before target definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( c->value_int ) {
+ mi->mi_flags |= LDAP_BACK_F_USE_TEMPORARIES;
+ } else {
+ mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ /* privileged connections pool max size ? */
+ if ( mi->mi_ntargets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" must appear before target definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN
+ || c->value_int > LDAP_BACK_CONN_PRIV_MAX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid max size " "of privileged "
+ "connections pool \"%s\" "
+ "in \"conn-pool-max <n> "
+ "(must be between %d and %d)\"",
+ c->argv[ 1 ],
+ LDAP_BACK_CONN_PRIV_MIN,
+ LDAP_BACK_CONN_PRIV_MAX );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_conn_priv_max = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ i = verb_to_mask( c->argv[1], cancel_mode );
+ if ( BER_BVISNULL( &cancel_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ mc->mc_flags |= cancel_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) {
+ int j;
+ unsigned u;
+
+ if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; j < SLAP_OP_LAST; j++ ) {
+ mc->mc_timeout[ j ] = u;
+ }
+
+ continue;
+ }
+
+ if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOTDN:
+ /* name to use as pseudo-root dn */
+ /*
+ * exact replacement:
+ *
+
+idassert-bind bindmethod=simple
+ binddn=<pseudorootdn>
+ credentials=<pseudorootpw>
+ mode=none
+ flags=non-prescriptive
+idassert-authzFrom "dn:<rootdn>"
+
+ * so that only when authc'd as <rootdn> the proxying occurs
+ * rebinding as the <pseudorootdn> without proxyAuthz.
+ */
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; "
+ "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n",
+ c->log );
+
+ {
+ char binddn[ SLAP_TEXT_BUFLEN ];
+ char *cargv[] = {
+ "idassert-bind",
+ "bindmethod=simple",
+ NULL,
+ "mode=none",
+ "flags=non-prescriptive",
+ NULL
+ };
+ char **oargv;
+ int oargc;
+ int cargc = 5;
+ int rc;
+
+
+ if ( BER_BVISNULL( &c->be->be_rootndn ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"rootdn\" must be defined first.\n",
+ c->log );
+ return 1;
+ }
+
+ if ( sizeof( binddn ) <= (unsigned) snprintf( binddn,
+ sizeof( binddn ), "binddn=%s", c->argv[ 1 ] ))
+ {
+ Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootdn\" too long.\n",
+ c->log );
+ return 1;
+ }
+ cargv[ 2 ] = binddn;
+
+ oargv = c->argv;
+ oargc = c->argc;
+ c->argv = cargv;
+ c->argc = cargc;
+ rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert );
+ c->argv = oargv;
+ c->argc = oargc;
+ if ( rc == 0 ) {
+ struct berval bv;
+
+ if ( mt->mt_idassert_authz != NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: \"idassert-authzFrom\" already defined (discarded).\n",
+ c->log );
+ ber_bvarray_free( mt->mt_idassert_authz );
+ mt->mt_idassert_authz = NULL;
+ }
+
+ assert( !BER_BVISNULL( &mt->mt_idassert_authcDN ) );
+
+ bv.bv_len = STRLENOF( "dn:" ) + c->be->be_rootndn.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ AC_MEMCPY( bv.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &bv.bv_val[ STRLENOF( "dn:" ) ], c->be->be_rootndn.bv_val, c->be->be_rootndn.bv_len + 1 );
+
+ ber_bvarray_add( &mt->mt_idassert_authz, &bv );
+ }
+
+ return rc;
+ }
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOTPW:
+ /* password to use as pseudo-root */
+ Debug( LDAP_DEBUG_ANY,
+ "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; "
+ "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n",
+ c->log );
+
+ if ( BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"pseudorootdn\" must be defined first.\n",
+ c->log );
+ return 1;
+ }
+
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ memset( mt->mt_idassert_passwd.bv_val, 0,
+ mt->mt_idassert_passwd.bv_len );
+ ber_memfree( mt->mt_idassert_passwd.bv_val );
+ }
+ ber_str2bv( c->argv[ 1 ], 0, 1, &mt->mt_idassert_passwd );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ /* idassert-bind */
+ rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ /* idassert-authzFrom */
+ rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ /* quarantine */
+ if ( META_BACK_CMN_QUARANTINE( mc ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "quarantine already defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( mt ) {
+ mc->mc_quarantine.ri_interval = NULL;
+ mc->mc_quarantine.ri_num = NULL;
+ if ( !META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ }
+
+ if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mc->mc_flags |= LDAP_BACK_F_QUARANTINE;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ /* session tracking request */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_ST_REQUEST;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ }
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_SUFFIXM: /* FALLTHRU */
+ case LDAP_BACK_CFG_REWRITE: {
+ /* rewrite stuff ... */
+ ConfigArgs ca = { 0 };
+ char *line, **argv;
+ struct rewrite_info *rwi;
+ int cnt = 0, argc, ix = c->valx;
+
+ if ( mt->mt_rwmap.rwm_bva_rewrite ) {
+ for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( ix >= cnt || ix < 0 ) {
+ ix = cnt;
+ } else {
+ rwi = mt->mt_rwmap.rwm_rw;
+
+ mt->mt_rwmap.rwm_rw = NULL;
+ rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw );
+
+ /* re-parse all rewrite rules, up to the one
+ * that needs to be added */
+ ca.be = c->be;
+ ca.fname = c->fname;
+ ca.lineno = c->lineno;
+ for ( i = 0; i < ix; i++ ) {
+ ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ config_fp_parse_line( &ca );
+
+ if ( !strcasecmp( ca.argv[0], "suffixmassage" )) {
+ rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt );
+ } else {
+ rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+ c->fname, c->lineno, ca.argc, ca.argv );
+ }
+ assert( rc == 0 );
+ ch_free( ca.tline );
+ }
+ }
+ argc = c->argc;
+ argv = c->argv;
+ if ( c->op != SLAP_CONFIG_ADD ) {
+ argc--;
+ argv++;
+ }
+ /* add the new rule */
+ if ( !strcasecmp( argv[0], "suffixmassage" )) {
+ rc = meta_suffixm_config( c, argc, argv, mt );
+ } else {
+ rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+ c->fname, c->lineno, argc, argv );
+ }
+ if ( rc ) {
+ if ( ix < cnt ) {
+ rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+ mt->mt_rwmap.rwm_rw = rwi;
+ }
+ return 1;
+ }
+ if ( ix < cnt ) {
+ for ( ; i < cnt; i++ ) {
+ ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ config_fp_parse_line( &ca );
+
+ if ( !strcasecmp( ca.argv[0], "suffixmassage" )) {
+ rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt );
+ } else {
+ rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+ c->fname, c->lineno, ca.argc, argv );
+ }
+ assert( rc == 0 );
+ ch_free( ca.tline );
+ }
+ ch_free( ca.argv );
+ }
+
+ /* save the rule info */
+ line = ldap_charray2str( argv, "\" \"" );
+ if ( line != NULL ) {
+ struct berval bv;
+ int len = strlen( argv[ 0 ] );
+
+ ber_str2bv( line, 0, 0, &bv );
+ AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ],
+ bv.bv_len - ( len + 1 ));
+ bv.bv_val[ bv.bv_len - 1] = '"';
+ ber_bvarray_add( &mt->mt_rwmap.rwm_bva_rewrite, &bv );
+ /* move it to the right slot */
+ if ( ix < cnt ) {
+ for ( i=cnt; i>ix; i-- )
+ mt->mt_rwmap.rwm_bva_rewrite[i] = mt->mt_rwmap.rwm_bva_rewrite[i-1];
+ mt->mt_rwmap.rwm_bva_rewrite[i] = bv;
+
+ /* destroy old rules */
+ rewrite_info_delete( &rwi );
+ }
+ }
+ } break;
+
+ case LDAP_BACK_CFG_MAP: {
+ /* objectclass/attribute mapping */
+ ConfigArgs ca = { 0 };
+ char *argv[5], **argvp;
+ struct ldapmap rwm_oc;
+ struct ldapmap rwm_at;
+ int cnt = 0, ix = c->valx;
+
+ if ( mt->mt_rwmap.rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_map[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( ix >= cnt || ix < 0 ) {
+ ix = cnt;
+ } else {
+ rwm_oc = mt->mt_rwmap.rwm_oc;
+ rwm_at = mt->mt_rwmap.rwm_at;
+
+ memset( &mt->mt_rwmap.rwm_oc, 0, sizeof( mt->mt_rwmap.rwm_oc ) );
+ memset( &mt->mt_rwmap.rwm_at, 0, sizeof( mt->mt_rwmap.rwm_at ) );
+
+ /* re-parse all mappings, up to the one
+ * that needs to be added */
+ argv[0] = c->argv[0];
+ ca.fname = c->fname;
+ ca.lineno = c->lineno;
+ for ( i = 0; i < ix; i++ ) {
+ ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val;
+ ca.argc = 0;
+ config_fp_parse_line( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ argvp = ca.argv;
+ ca.argv = argv;
+ ca.argc++;
+ rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc,
+ &mt->mt_rwmap.rwm_at );
+
+ ch_free( ca.tline );
+ ca.tline = NULL;
+ ca.argv = argvp;
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto map_fail;
+ }
+ }
+ }
+ /* add the new mapping */
+ rc = ldap_back_map_config( c, &mt->mt_rwmap.rwm_oc,
+ &mt->mt_rwmap.rwm_at );
+ if ( rc ) {
+ goto map_fail;
+ }
+
+ if ( ix < cnt ) {
+ for ( ; i<cnt ; i++ ) {
+ ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val;
+ ca.argc = 0;
+ config_fp_parse_line( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ argvp = ca.argv;
+ ca.argv = argv;
+ ca.argc++;
+ rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc,
+ &mt->mt_rwmap.rwm_at );
+
+ ch_free( ca.tline );
+ ca.tline = NULL;
+ ca.argv = argvp;
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto map_fail;
+ }
+ }
+ ch_free( ca.argv );
+ }
+
+ /* save the map info */
+ argv[0] = ldap_charray2str( &c->argv[ 1 ], " " );
+ if ( argv[0] != NULL ) {
+ struct berval bv;
+ ber_str2bv( argv[0], 0, 0, &bv );
+ ber_bvarray_add( &mt->mt_rwmap.rwm_bva_map, &bv );
+ /* move it to the right slot */
+ if ( ix < cnt ) {
+ for ( i=cnt; i>ix; i-- )
+ mt->mt_rwmap.rwm_bva_map[i] = mt->mt_rwmap.rwm_bva_map[i-1];
+ mt->mt_rwmap.rwm_bva_map[i] = bv;
+
+ /* destroy old mapping */
+ meta_back_map_free( &rwm_oc );
+ meta_back_map_free( &rwm_at );
+ }
+ }
+ break;
+
+map_fail:;
+ if ( ix < cnt ) {
+ meta_back_map_free( &mt->mt_rwmap.rwm_oc );
+ meta_back_map_free( &mt->mt_rwmap.rwm_at );
+ mt->mt_rwmap.rwm_oc = rwm_oc;
+ mt->mt_rwmap.rwm_at = rwm_at;
+ ch_free( ca.argv );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_NRETRIES: {
+ int nretries = META_RETRY_UNDEFINED;
+
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ nretries = META_RETRY_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) {
+ nretries = META_RETRY_NEVER;
+
+ } else {
+ if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse nretries {never|forever|<retries>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+
+ mc->mc_nretries = nretries;
+ } break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unsupported protocol version \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_version = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ /* do not return search references */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOREFS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ /* do not propagate undefined search filters */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ }
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+
+ } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ ber_str2bv( c->argv[ 1 ], 0, 1, &bv );
+ slap_keepalive_parse( &bv, &mt->mt_tls.sb_keepalive, 0, 0, 0 );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = c->value_uint;
+ break;
+
+ /* anything else */
+ default:
+ return SLAP_CONF_UNKNOWN;
+ }
+
+ return rc;
+}
+
+int
+meta_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( LDAP_BACK_CFG_LAST );
+
+ bi->bi_cf_ocs = metaocs;
+
+ rc = config_register_schema( metacfg, metaocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+ldap_back_map_config(
+ ConfigArgs *c,
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map )
+{
+ struct ldapmap *map;
+ struct ldapmapping *mapping;
+ char *src, *dst;
+ int is_oc = 0;
+
+ if ( strcasecmp( c->argv[ 1 ], "objectclass" ) == 0 ) {
+ map = oc_map;
+ is_oc = 1;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "attribute" ) == 0 ) {
+ map = at_map;
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( !is_oc && map->map == NULL ) {
+ /* only init if required */
+ ldap_back_map_init( map, &mapping );
+ }
+
+ if ( strcmp( c->argv[ 2 ], "*" ) == 0 ) {
+ if ( c->argc < 4 || strcmp( c->argv[ 3 ], "*" ) == 0 ) {
+ map->drop_missing = ( c->argc < 4 );
+ goto success_return;
+ }
+ src = dst = c->argv[ 3 ];
+
+ } else if ( c->argc < 4 ) {
+ src = "";
+ dst = c->argv[ 2 ];
+
+ } else {
+ src = c->argv[ 2 ];
+ dst = ( strcmp( c->argv[ 3 ], "*" ) == 0 ? src : c->argv[ 3 ] );
+ }
+
+ if ( ( map == at_map )
+ && ( strcasecmp( src, "objectclass" ) == 0
+ || strcasecmp( dst, "objectclass" ) == 0 ) )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "objectclass attribute cannot be mapped" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof(struct ldapmapping) );
+ if ( mapping == NULL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "out of memory" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ ber_str2bv( src, 0, 1, &mapping[ 0 ].src );
+ ber_str2bv( dst, 0, 1, &mapping[ 0 ].dst );
+ mapping[ 1 ].src = mapping[ 0 ].dst;
+ mapping[ 1 ].dst = mapping[ 0 ].src;
+
+ /*
+ * schema check
+ */
+ if ( is_oc ) {
+ if ( src[ 0 ] != '\0' ) {
+ if ( oc_bvfind( &mapping[ 0 ].src ) == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning, source objectClass '%s' should be defined in schema\n",
+ c->log, src );
+
+ /*
+ * FIXME: this should become an err
+ */
+ goto error_return;
+ }
+ }
+
+ if ( oc_bvfind( &mapping[ 0 ].dst ) == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning, destination objectClass '%s' is not defined in schema\n",
+ c->log, dst );
+ }
+ } else {
+ int rc;
+ const char *text = NULL;
+ AttributeDescription *ad = NULL;
+
+ if ( src[ 0 ] != '\0' ) {
+ rc = slap_bv2ad( &mapping[ 0 ].src, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning, source attributeType '%s' should be defined in schema\n",
+ c->log, src );
+
+ /*
+ * FIXME: this should become an err
+ */
+ /*
+ * we create a fake "proxied" ad
+ * and add it here.
+ */
+
+ rc = slap_bv2undef_ad( &mapping[ 0 ].src,
+ &ad, &text, SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "source attributeType \"%s\": %d (%s)",
+ src, rc, text ? text : "" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto error_return;
+ }
+ }
+
+ ad = NULL;
+ }
+
+ rc = slap_bv2ad( &mapping[ 0 ].dst, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning, destination attributeType '%s' is not defined in schema\n",
+ c->log, dst );
+
+ /*
+ * we create a fake "proxied" ad
+ * and add it here.
+ */
+
+ rc = slap_bv2undef_ad( &mapping[ 0 ].dst,
+ &ad, &text, SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "destination attributeType \"%s\": %d (%s)\n",
+ dst, rc, text ? text : "" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+
+ if ( (src[ 0 ] != '\0' && ldap_avl_find( map->map, (caddr_t)&mapping[ 0 ], mapping_cmp ) != NULL)
+ || ldap_avl_find( map->remap, (caddr_t)&mapping[ 1 ], mapping_cmp ) != NULL)
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "duplicate mapping found." );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ goto error_return;
+ }
+
+ if ( src[ 0 ] != '\0' ) {
+ ldap_avl_insert( &map->map, (caddr_t)&mapping[ 0 ],
+ mapping_cmp, mapping_dup );
+ }
+ ldap_avl_insert( &map->remap, (caddr_t)&mapping[ 1 ],
+ mapping_cmp, mapping_dup );
+
+success_return:;
+ return 0;
+
+error_return:;
+ if ( mapping ) {
+ ch_free( mapping[ 0 ].src.bv_val );
+ ch_free( mapping[ 0 ].dst.bv_val );
+ ch_free( mapping );
+ }
+
+ return 1;
+}
+
+
+static char *
+suffix_massage_regexize( const char *s )
+{
+ char *res, *ptr;
+ const char *p, *r;
+ int i;
+
+ if ( s[ 0 ] == '\0' ) {
+ return ch_strdup( "^(.+)$" );
+ }
+
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1, i++ )
+ ;
+
+ res = ch_calloc( sizeof( char ),
+ strlen( s )
+ + STRLENOF( "((.+),)?" )
+ + STRLENOF( "[ ]?" ) * i
+ + STRLENOF( "$" ) + 1 );
+
+ ptr = lutil_strcopy( res, "((.+),)?" );
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1 , i++ ) {
+ ptr = lutil_strncopy( ptr, p, r - p + 1 );
+ ptr = lutil_strcopy( ptr, "[ ]?" );
+
+ if ( r[ 1 ] == ' ' ) {
+ r++;
+ }
+ }
+ ptr = lutil_strcopy( ptr, p );
+ ptr[ 0 ] = '$';
+ ptr++;
+ ptr[ 0 ] = '\0';
+
+ return res;
+}
+
+static char *
+suffix_massage_patternize( const char *s, const char *p )
+{
+ ber_len_t len;
+ char *res, *ptr;
+
+ len = strlen( p );
+
+ if ( s[ 0 ] == '\0' ) {
+ len++;
+ }
+
+ res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 );
+ if ( res == NULL ) {
+ return NULL;
+ }
+
+ ptr = lutil_strcopy( res, ( p[ 0 ] == '\0' ? "%2" : "%1" ) );
+ if ( s[ 0 ] == '\0' ) {
+ ptr[ 0 ] = ',';
+ ptr++;
+ }
+ lutil_strcopy( ptr, p );
+
+ return res;
+}
+
+int
+suffix_massage_config(
+ struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc
+)
+{
+ char *rargv[ 5 ];
+ int line = 0;
+
+ rargv[ 0 ] = "rewriteEngine";
+ rargv[ 1 ] = "on";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = suffix_massage_regexize( pvnc->bv_val );
+ rargv[ 2 ] = suffix_massage_patternize( pvnc->bv_val, prnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( pvnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = prnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchEntryDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = suffix_massage_regexize( prnc->bv_val );
+ rargv[ 2 ] = suffix_massage_patternize( prnc->bv_val, pvnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( prnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = pvnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ /* backward compatibility */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchResult";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "matchedDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchAttrDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+ /* NOTE: this corresponds to #undef'ining RWM_REFERRAL_REWRITE;
+ * see servers/slapd/overlays/rwm.h for details */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralAttrDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ return 0;
+}
diff --git a/servers/slapd/back-meta/conn.c b/servers/slapd/back-meta/conn.c
new file mode 100644
index 0000000..91fdad3
--- /dev/null
+++ b/servers/slapd/back-meta/conn.c
@@ -0,0 +1,1893 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+
+#define AVL_INTERNAL
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+/*
+ * meta_back_conndn_cmp
+ *
+ * compares two struct metaconn based on the value of the conn pointer
+ * and of the local DN; used by avl stuff
+ */
+int
+meta_back_conndn_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metaconn_t *mc1 = ( metaconn_t * )c1;
+ metaconn_t *mc2 = ( metaconn_t * )c2;
+ int rc;
+
+ /* If local DNs don't match, it is definitely not a match */
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn );
+ if ( rc == 0 ) {
+ rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn );
+ }
+
+ return rc;
+}
+
+/*
+ * meta_back_conndnmc_cmp
+ *
+ * compares two struct metaconn based on the value of the conn pointer,
+ * the local DN and the struct pointer; used by avl stuff
+ */
+static int
+meta_back_conndnmc_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metaconn_t *mc1 = ( metaconn_t * )c1;
+ metaconn_t *mc2 = ( metaconn_t * )c2;
+ int rc;
+
+ /* If local DNs don't match, it is definitely not a match */
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn );
+ if ( rc == 0 ) {
+ rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn );
+ if ( rc == 0 ) {
+ rc = SLAP_PTRCMP( mc1, mc2 );
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * meta_back_conn_cmp
+ *
+ * compares two struct metaconn based on the value of the conn pointer;
+ * used by avl stuff
+ */
+int
+meta_back_conn_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metaconn_t *mc1 = ( metaconn_t * )c1;
+ metaconn_t *mc2 = ( metaconn_t * )c2;
+
+ /* For shared sessions, conn is NULL. Only explicitly
+ * bound sessions will have non-NULL conn.
+ */
+ return SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn );
+}
+
+/*
+ * meta_back_conndn_dup
+ *
+ * returns -1 in case a duplicate struct metaconn has been inserted;
+ * used by avl stuff
+ */
+int
+meta_back_conndn_dup(
+ void *c1,
+ void *c2 )
+{
+ metaconn_t *mc1 = ( metaconn_t * )c1;
+ metaconn_t *mc2 = ( metaconn_t * )c2;
+
+ /* Cannot have more than one shared session with same DN */
+ if ( mc1->mc_conn == mc2->mc_conn &&
+ dn_match( &mc1->mc_local_ndn, &mc2->mc_local_ndn ) )
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Debug stuff (got it from libavl)
+ */
+#if META_BACK_PRINT_CONNTREE > 0
+static void
+meta_back_print( metaconn_t *mc, char *avlstr )
+{
+ int i;
+
+ fputs( "targets=[", stderr );
+ for ( i = 0; i < mc->mc_info->mi_ntargets; i++ ) {
+ fputc( mc->mc_conns[ i ].msc_ld ? '*' : 'o', stderr);
+ }
+ fputc( ']', stderr );
+
+ fprintf( stderr, " mc=%p local=\"%s\" conn=%p refcnt=%d%s %s\n",
+ (void *)mc,
+ mc->mc_local_ndn.bv_val ? mc->mc_local_ndn.bv_val : "",
+ (void *)mc->mc_conn,
+ mc->mc_refcnt,
+ LDAP_BACK_CONN_TAINTED( mc ) ? " tainted" : "",
+ avlstr );
+}
+
+static void
+meta_back_ravl_print( TAvlnode *root, int depth )
+{
+ int i;
+
+ if ( root == 0 ) {
+ return;
+ }
+
+ meta_back_ravl_print( root->avl_right, depth + 1 );
+
+ for ( i = 0; i < depth; i++ ) {
+ fprintf( stderr, "-" );
+ }
+ fputc( ' ', stderr );
+
+ meta_back_print( (metaconn_t *)root->avl_data,
+ avl_bf2str( root->avl_bf ) );
+
+ meta_back_ravl_print( root->avl_left, depth + 1 );
+}
+
+/* NOTE: duplicate from back-ldap/bind.c */
+static char* priv2str[] = {
+ "privileged",
+ "privileged/TLS",
+ "anonymous",
+ "anonymous/TLS",
+ "bind",
+ "bind/TLS",
+ NULL
+};
+
+void
+meta_back_print_conntree( metainfo_t *mi, char *msg )
+{
+ int c;
+
+ fprintf( stderr, "========> %s\n", msg );
+
+ for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) {
+ int i = 0;
+ metaconn_t *mc;
+
+ fprintf( stderr, " %s[%d]\n", priv2str[ c ], mi->mi_conn_priv[ c ].mic_num );
+
+ LDAP_TAILQ_FOREACH( mc, &mi->mi_conn_priv[ c ].mic_priv, mc_q )
+ {
+ fprintf( stderr, " [%d] ", i );
+ meta_back_print( mc, "" );
+ i++;
+ }
+ }
+
+ if ( mi->mi_conninfo.lai_tree == NULL ) {
+ fprintf( stderr, "\t(empty)\n" );
+
+ } else {
+ meta_back_ravl_print( mi->mi_conninfo.lai_tree, 0 );
+ }
+
+ fprintf( stderr, "<======== %s\n", msg );
+}
+#endif /* META_BACK_PRINT_CONNTREE */
+/*
+ * End of debug stuff
+ */
+
+/*
+ * metaconn_alloc
+ *
+ * Allocates a connection structure, making room for all the referenced targets
+ */
+static metaconn_t *
+metaconn_alloc(
+ Operation *op )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metaconn_t *mc;
+ int ntargets = mi->mi_ntargets;
+
+ assert( ntargets > 0 );
+
+ /* malloc all in one */
+ mc = ( metaconn_t * )ch_calloc( 1, sizeof( metaconn_t )
+ + sizeof( metasingleconn_t ) * ( ntargets - 1 ) );
+ if ( mc == NULL ) {
+ return NULL;
+ }
+
+ mc->mc_info = mi;
+
+ mc->mc_authz_target = META_BOUND_NONE;
+ mc->mc_refcnt = 1;
+
+ return mc;
+}
+
+/*
+ * meta_back_init_one_conn
+ *
+ * Initializes one connection
+ */
+int
+meta_back_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int version;
+ dncookie dc;
+ int isauthz = ( candidate == mc->mc_authz_target );
+ int do_return = 0;
+#ifdef HAVE_TLS
+ int is_ldaps = 0;
+ int do_start_tls = 0;
+#endif /* HAVE_TLS */
+
+ /* if the server is quarantined, and
+ * - the current interval did not expire yet, or
+ * - no more retries should occur,
+ * don't return the connection */
+ if ( mt->mt_isquarantined ) {
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+ int dont_retry = 0;
+
+ if ( mt->mt_quarantine.ri_interval ) {
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+ dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO );
+ if ( dont_retry ) {
+ dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL
+ || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] );
+ if ( !dont_retry ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_init_one_conn[%d]: quarantine " "retry block #%d try #%d.\n",
+ op->o_log_prefix,
+ candidate, ri->ri_idx,
+ ri->ri_count );
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING;
+ }
+
+ }
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+ }
+
+ if ( dont_retry ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ rs->sr_text = "Target is quarantined";
+ send_ldap_result( op, rs );
+ }
+ return rs->sr_err;
+ }
+ }
+
+retry_lock:;
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ /*
+ * Already init'ed
+ */
+ if ( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc ) )
+ {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_CREATING( msc )
+ || LDAP_BACK_CONN_BINDING( msc ) )
+ {
+ if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) {
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ ldap_pvt_thread_yield();
+ goto retry_lock;
+ }
+
+ /* sounds more appropriate */
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "No connections to target are available";
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_INITED( msc ) ) {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else {
+ /*
+ * creating...
+ */
+ META_BACK_CONN_CREATING_SET( msc );
+ }
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ if ( do_return ) {
+ if ( rs->sr_err != LDAP_SUCCESS
+ && op->o_conn
+ && ( sendok & LDAP_BACK_SENDERR ) )
+ {
+ send_ldap_result( op, rs );
+ }
+
+ return rs->sr_err;
+ }
+
+ assert( msc->msc_ld == NULL );
+
+ /*
+ * Attempts to initialize the connection to the target ds
+ */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri );
+#ifdef HAVE_TLS
+ is_ldaps = ldap_is_ldaps_url( mt->mt_uri );
+#endif /* HAVE_TLS */
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto error_return;
+ }
+
+ /*
+ * Set LDAP version. This will always succeed: If the client
+ * bound with a particular version, then so can we.
+ */
+ if ( mt->mt_version != 0 ) {
+ version = mt->mt_version;
+
+ } else if ( op->o_conn->c_protocol != 0 ) {
+ version = op->o_conn->c_protocol;
+
+ } else {
+ version = LDAP_VERSION3;
+ }
+ ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+ ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p );
+
+ /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS,
+ META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF );
+
+ slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive);
+
+ if ( mt->mt_tls.sb_tcp_user_timeout > 0 ) {
+ ldap_set_option( msc->msc_ld, LDAP_OPT_TCP_USER_TIMEOUT,
+ &mt->mt_tls.sb_tcp_user_timeout );
+ }
+
+
+
+#ifdef HAVE_TLS
+ {
+ slap_bindconf *sb = NULL;
+
+ if ( ispriv ) {
+ sb = &mt->mt_idassert.si_bc;
+ } else {
+ sb = &mt->mt_tls;
+ }
+
+ bindconf_tls_set( sb, msc->msc_ld );
+
+ if ( !is_ldaps ) {
+ if ( META_BACK_TGT_USE_TLS( mt )
+ || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) )
+ {
+ do_start_tls = 1;
+ }
+ }
+ }
+
+ /* start TLS ("tls [try-]{start|propagate}" statement) */
+ if ( do_start_tls ) {
+#ifdef SLAP_STARTTLS_ASYNCHRONOUS
+ /*
+ * use asynchronous StartTLS; in case, chase referral
+ * FIXME: OpenLDAP does not return referral on StartTLS yet
+ */
+ int msgid;
+
+ rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ LDAPMessage *res = NULL;
+ int rc, nretries = mt->mt_nretries;
+ struct timeval tv;
+
+ LDAP_BACK_TV_SET( &tv );
+
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case -1:
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Remote server down";
+ break;
+
+ case 0:
+ if ( nretries != 0 ) {
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ LDAP_BACK_TV_SET( &tv );
+ goto retry;
+ }
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Timeout, no more retries";
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+ msc->msc_time = op->o_time;
+ }
+ break;
+ }
+
+ if ( rc == LDAP_RES_EXTENDED ) {
+ struct berval *data = NULL;
+
+ /* NOTE: right now, data is unused, so don't get it */
+ rs->sr_err = ldap_parse_extended_result( msc->msc_ld,
+ res, NULL, NULL /* &data */ , 0 );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int err;
+
+ /* FIXME: matched? referrals? response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ld,
+ res, &err, NULL, NULL, NULL, NULL, 1 );
+ res = NULL;
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = err;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* FIXME: in case a referral
+ * is returned, should we try
+ * using it instead of the
+ * configured URI? */
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = ldap_install_tls( msc->msc_ld );
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ /* FIXME: LDAP_OPERATIONS_ERROR? */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Unwilling to chase referral "
+ "returned by Start TLS exop";
+ }
+
+ if ( data ) {
+ ber_bvfree( data );
+ }
+ }
+
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Unknown response to StartTLS request :"
+ " an ExtendedResponse is expected";
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ }
+#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ /*
+ * use synchronous StartTLS
+ */
+ rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL );
+#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+
+ /* if StartTLS is requested, only attempt it if the URL
+ * is not "ldaps://"; this may occur not only in case
+ * of misconfiguration, but also when used in the chain
+ * overlay, where the "uri" can be parsed out of a referral */
+ if ( rs->sr_err == LDAP_SERVER_DOWN
+ || ( rs->sr_err != LDAP_SUCCESS
+ && META_BACK_TGT_TLS_CRITICAL( mt ) ) )
+ {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s meta_back_init_one_conn(TLS) "
+ "ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ /* need to trash a failed Start TLS */
+ meta_clear_one_candidate( op, mc, candidate );
+ goto error_return;
+ }
+ }
+#endif /* HAVE_TLS */
+
+ /*
+ * Set the network timeout if set
+ */
+ if ( mt->mt_network_timeout != 0 ) {
+ struct timeval network_timeout;
+
+ network_timeout.tv_usec = 0;
+ network_timeout.tv_sec = mt->mt_network_timeout;
+
+ ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT,
+ (void *)&network_timeout );
+ }
+
+ /*
+ * If the connection DN is not null, an attempt to rewrite it is made
+ */
+
+ if ( ispriv ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN );
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd );
+ }
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+
+ } else {
+ ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv );
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ && SLAP_IS_AUTHZ_BACKEND( op )
+ && isauthz )
+ {
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "bindDN";
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ if ( ldap_back_dn_massage( &dc, &op->o_conn->c_dn,
+ &msc->msc_bound_ndn ) )
+ {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s meta_back_init_one_conn(rewrite) "
+ "ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ /* need to trash a connection not fully established */
+ meta_clear_one_candidate( op, mc, candidate );
+ goto error_return;
+ }
+
+ /* copy the DN if needed */
+ if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) {
+ ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn );
+ }
+
+ assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+ } else {
+ ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv );
+ }
+ }
+
+ assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+error_return:;
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+ META_BACK_CONN_CREATING_CLEAR( msc );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ /*
+ * Sets a cookie for the rewrite session
+ */
+ ( void )rewrite_session_init( mt->mt_rwmap.rwm_rw, op->o_conn );
+ META_BACK_CONN_INITED_SET( msc );
+ }
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ /* Get the error message and print it in TRACE mode */
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ Log( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_init_one_conn[%d] failed err=%d text=%s\n",
+ op->o_log_prefix, candidate, rs->sr_err, rs->sr_text );
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return rs->sr_err;
+}
+
+/*
+ * meta_back_retry
+ *
+ * Retries one connection
+ */
+int
+meta_back_retry(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ ldap_back_send_t sendok )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metaconn_t *mc = *mcp;
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int rc = LDAP_UNAVAILABLE,
+ binding,
+ quarantine = 1;
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+
+ assert( !META_BACK_CONN_CREATING( msc ) );
+ binding = LDAP_BACK_CONN_BINDING( msc );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+
+ assert( mc->mc_refcnt > 0 );
+ if ( mc->mc_refcnt == 1 ) {
+ struct berval save_cred;
+
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ /* this lock is required; however,
+ * it's invoked only when logging is on */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_retry[%d]: retrying URI=\"%s\" DN=\"%s\".\n",
+ op->o_log_prefix, candidate, mt->mt_uri,
+ BER_BVISNULL(&msc->msc_bound_ndn) ? "" : msc->msc_bound_ndn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ }
+
+ /* save credentials, if any, for later use;
+ * meta_clear_one_candidate() would free them */
+ save_cred = msc->msc_cred;
+ BER_BVZERO( &msc->msc_cred );
+
+ meta_clear_one_candidate( op, mc, candidate );
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+
+ ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn );
+
+ /* mc here must be the regular mc, reset and ready for init */
+ rc = meta_back_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), sendok, 0 );
+
+ /* restore credentials, if any and if needed;
+ * meta_back_init_one_conn() restores msc_bound_ndn, if any;
+ * if no msc_bound_ndn is restored, destroy credentials */
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn )
+ && BER_BVISNULL( &msc->msc_cred ) )
+ {
+ msc->msc_cred = save_cred;
+
+ } else if ( !BER_BVISNULL( &save_cred ) ) {
+ memset( save_cred.bv_val, 0, save_cred.bv_len );
+ ber_memfree_x( save_cred.bv_val, NULL );
+ }
+
+ /* restore the "binding" flag, in case */
+ if ( binding ) {
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ quarantine = 0;
+ LDAP_BACK_CONN_BINDING_SET( msc ); binding = 1;
+ rc = meta_back_single_dobind( op, rs, mcp, candidate,
+ sendok, mt->mt_nretries, 0 );
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_retry[%d]: "
+ "meta_back_single_dobind=%d\n",
+ op->o_log_prefix, candidate, rc );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) &&
+ !BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+ {
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+
+ } else {
+ LDAP_BACK_CONN_ISANON_SET( msc );
+ }
+
+ /* when bound, dispose of the "binding" flag */
+ if ( binding ) {
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ }
+ }
+ }
+
+#if 0 /* ITS#7591, following stmt drops needed result msgs */
+ /* don't send twice */
+ sendok &= ~LDAP_BACK_SENDERR;
+#endif
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ SlapReply *candidates = meta_back_candidates_get( op );
+
+ candidates[ candidate ].sr_err = rc;
+
+ if ( *mcp != NULL ) {
+ if ( mc->mc_refcnt == 1 ) {
+ if ( binding ) {
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ }
+ (void)meta_clear_one_candidate( op, mc, candidate );
+ }
+
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ /* only release if mandatory; otherwise
+ * let the caller do what's best before
+ * releasing */
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ meta_back_release_conn_lock( mi, mc, 0 );
+ *mcp = NULL;
+
+ } else {
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, ">>> meta_back_retry" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ /* FIXME: could be done better, reworking meta_back_release_conn_lock() */
+ if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ if ( mc->mc_q.tqe_prev != NULL ) {
+ assert( LDAP_BACK_CONN_CACHED( mc ) );
+ assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 );
+ LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv,
+ mc, mc_q );
+ mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--;
+ LDAP_TAILQ_ENTRY_INIT( mc, mc_q );
+
+ } else {
+ assert( !LDAP_BACK_CONN_CACHED( mc ) );
+ }
+
+ } else {
+ /* FIXME: check if in tree, for consistency? */
+ (void)ldap_tavl_delete( &mi->mi_conninfo.lai_tree,
+ ( caddr_t )mc, meta_back_conndnmc_cmp );
+ }
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, "<<< meta_back_retry" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ }
+ }
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ rs->sr_err = rc;
+ rs->sr_text = "Unable to retry";
+ send_ldap_result( op, rs );
+ }
+ }
+
+ if ( quarantine && META_BACK_TGT_QUARANTINE( mt ) ) {
+ meta_back_quarantine( op, rs, candidate );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ return rc == LDAP_SUCCESS ? 1 : 0;
+}
+
+/*
+ * callback for unique candidate selection
+ */
+static int
+meta_back_conn_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ ((long *)op->o_callback->sc_private)[0] = (long)op->o_private;
+ break;
+
+ case REP_SEARCHREF:
+ case REP_RESULT:
+ break;
+
+ default:
+ return rs->sr_err;
+ }
+
+ return 0;
+}
+
+
+static int
+meta_back_get_candidate(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ long candidate;
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ candidate = meta_back_select_unique_candidate( mi, ndn );
+
+ /*
+ * if any is found, inits the connection
+ */
+ if ( candidate == META_TARGET_NONE ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "No suitable candidate target found";
+
+ } else if ( candidate == META_TARGET_MULTIPLE ) {
+ Operation op2 = *op;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb2 = { 0 };
+ int rc;
+
+ /* try to get a unique match for the request ndn
+ * among the multiple candidates available */
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_req_dn = *ndn;
+ op2.o_req_ndn = *ndn;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_attrs = slap_anlist_no_attrs;
+ op2.ors_attrsonly = 0;
+ op2.ors_limit = NULL;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+
+ op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
+ op2.ors_filterstr = *slap_filterstr_objectClass_pres;
+
+ op2.o_callback = &cb2;
+ cb2.sc_response = meta_back_conn_cb;
+ cb2.sc_private = (void *)&candidate;
+
+ rc = op->o_bd->be_search( &op2, &rs2 );
+
+ switch ( rs2.sr_err ) {
+ case LDAP_SUCCESS:
+ default:
+ rs->sr_err = rs2.sr_err;
+ break;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ /* if multiple candidates can serve the operation,
+ * and a default target is defined, and it is
+ * a candidate, try using it (FIXME: YMMV) */
+ if ( mi->mi_defaulttarget != META_DEFAULT_TARGET_NONE
+ && meta_back_is_candidate( mi->mi_targets[ mi->mi_defaulttarget ],
+ ndn, op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_BASE ) )
+ {
+ candidate = mi->mi_defaulttarget;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+
+ } else {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Unable to select unique candidate target";
+ }
+ break;
+ }
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ return candidate;
+}
+
+static void *meta_back_candidates_dummy;
+
+static void
+meta_back_candidates_keyfree(
+ void *key,
+ void *data )
+{
+ metacandidates_t *mc = (metacandidates_t *)data;
+
+ ber_memfree_x( mc->mc_candidates, NULL );
+ ber_memfree_x( data, NULL );
+}
+
+SlapReply *
+meta_back_candidates_get( Operation *op )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metacandidates_t *mc;
+
+ if ( op->o_threadctx ) {
+ void *data = NULL;
+
+ ldap_pvt_thread_pool_getkey( op->o_threadctx,
+ &meta_back_candidates_dummy, &data, NULL );
+ mc = (metacandidates_t *)data;
+
+ } else {
+ mc = mi->mi_candidates;
+ }
+
+ if ( mc == NULL ) {
+ mc = ch_calloc( sizeof( metacandidates_t ), 1 );
+ mc->mc_ntargets = mi->mi_ntargets;
+ mc->mc_candidates = ch_calloc( sizeof( SlapReply ), mc->mc_ntargets );
+ if ( op->o_threadctx ) {
+ void *data = NULL;
+
+ data = (void *)mc;
+ ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ &meta_back_candidates_dummy, data,
+ meta_back_candidates_keyfree,
+ NULL, NULL );
+
+ } else {
+ mi->mi_candidates = mc;
+ }
+
+ } else if ( mc->mc_ntargets < mi->mi_ntargets ) {
+ /* NOTE: in the future, may want to allow back-config
+ * to add/remove targets from back-meta... */
+ mc->mc_candidates = ch_realloc( mc->mc_candidates,
+ sizeof( SlapReply ) * mi->mi_ntargets );
+ memset( &mc->mc_candidates[ mc->mc_ntargets ], 0,
+ sizeof( SlapReply ) * ( mi->mi_ntargets - mc->mc_ntargets ) );
+ mc->mc_ntargets = mi->mi_ntargets;
+ }
+
+ return mc->mc_candidates;
+}
+
+/*
+ * meta_back_getconn
+ *
+ * Prepares the connection structure
+ *
+ * RATIONALE:
+ *
+ * - determine what DN is being requested:
+ *
+ * op requires candidate checks
+ *
+ * add unique parent of o_req_ndn
+ * bind unique^*[/all] o_req_ndn [no check]
+ * compare unique^+ o_req_ndn
+ * delete unique o_req_ndn
+ * modify unique o_req_ndn
+ * search any o_req_ndn
+ * modrdn unique[, unique] o_req_ndn[, orr_nnewSup]
+ *
+ * - for ops that require the candidate to be unique, in case of multiple
+ * occurrences an internal search with sizeLimit=1 is performed
+ * if a unique candidate can actually be determined. If none is found,
+ * the operation aborts; if multiple are found, the default target
+ * is used if defined and candidate; otherwise the operation aborts.
+ *
+ * *^note: actually, the bind operation is handled much like a search;
+ * i.e. the bind is broadcast to all candidate targets.
+ *
+ * +^note: actually, the compare operation is handled much like a search;
+ * i.e. the compare is broadcast to all candidate targets, while checking
+ * that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is
+ * returned.
+ */
+metaconn_t *
+meta_back_getconn(
+ Operation *op,
+ SlapReply *rs,
+ int *candidate,
+ ldap_back_send_t sendok )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metaconn_t *mc = NULL,
+ mc_curr = {{ 0 }};
+ int cached = META_TARGET_NONE,
+ i = META_TARGET_NONE,
+ err = LDAP_SUCCESS,
+ new_conn = 0,
+ ncandidates = 0;
+
+
+ meta_op_type op_type = META_OP_REQUIRE_SINGLE;
+ enum {
+ META_DNTYPE_ENTRY,
+ META_DNTYPE_PARENT,
+ META_DNTYPE_NEWPARENT
+ } dn_type = META_DNTYPE_ENTRY;
+ struct berval ndn = op->o_req_ndn,
+ pndn;
+
+ SlapReply *candidates = meta_back_candidates_get( op );
+
+ /* Internal searches are privileged and shared. So is root. */
+ if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) )
+ || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) )
+ || op->o_do_not_cache || be_isroot( op ) )
+ {
+ LDAP_BACK_CONN_ISPRIV_SET( &mc_curr );
+ mc_curr.mc_local_ndn = op->o_bd->be_rootndn;
+ LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op );
+
+ } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ BER_BVSTR( &mc_curr.mc_local_ndn, "" );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+
+ } else {
+ mc_curr.mc_local_ndn = op->o_ndn;
+
+ /* Explicit binds must not be shared */
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ || op->o_tag == LDAP_REQ_BIND
+ || SLAP_IS_AUTHZ_BACKEND( op ) )
+ {
+ mc_curr.mc_conn = op->o_conn;
+
+ } else {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+ }
+ }
+
+ /* Explicit Bind requests always get their own conn */
+ if ( sendok & LDAP_BACK_BINDING ) {
+ mc_curr.mc_conn = op->o_conn;
+
+ } else {
+ /* Searches for a metaconn in the avl tree */
+retry_lock:;
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( LDAP_BACK_PCONN_ISPRIV( &mc_curr ) ) {
+ /* lookup a conn that's not binding */
+ LDAP_TAILQ_FOREACH( mc,
+ &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv,
+ mc_q )
+ {
+ if ( !LDAP_BACK_CONN_BINDING( mc ) && mc->mc_refcnt == 0 ) {
+ break;
+ }
+ }
+
+ if ( mc != NULL ) {
+ /* move to tail of queue */
+ if ( mc != LDAP_TAILQ_LAST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv,
+ mc_conn_priv_q ) )
+ {
+ LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv,
+ mc, mc_q );
+ LDAP_TAILQ_ENTRY_INIT( mc, mc_q );
+ LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv,
+ mc, mc_q );
+ }
+
+ } else if ( !LDAP_BACK_USE_TEMPORARIES( mi )
+ && mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_num == mi->mi_conn_priv_max )
+ {
+ mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv );
+ }
+
+
+ } else {
+ mc = (metaconn_t *)ldap_tavl_find( mi->mi_conninfo.lai_tree,
+ (caddr_t)&mc_curr, meta_back_conndn_cmp );
+ }
+
+ if ( mc ) {
+ /* catch taint errors */
+ assert( !LDAP_BACK_CONN_TAINTED( mc ) );
+
+ /* Don't reuse connections while they're still binding
+ * NOTE: only makes sense for binds */
+ if ( LDAP_BACK_CONN_BINDING( mc ) ) {
+ if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ ldap_pvt_thread_yield();
+ goto retry_lock;
+ }
+
+ /* release conn, and create a temporary */
+ mc = NULL;
+
+ } else {
+ if ( mc->mc_refcnt == 0 && (( mi->mi_conn_ttl != 0 && op->o_time > mc->mc_create_time + mi->mi_conn_ttl )
+ || ( mi->mi_idle_timeout != 0 && op->o_time > mc->mc_time + mi->mi_idle_timeout )) )
+ {
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi,
+ ">>> meta_back_getconn(expired)" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ /* don't let anyone else use this expired connection */
+ if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ if ( mc->mc_q.tqe_prev != NULL ) {
+ assert( LDAP_BACK_CONN_CACHED( mc ) );
+ assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 );
+ LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv,
+ mc, mc_q );
+ mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--;
+ LDAP_TAILQ_ENTRY_INIT( mc, mc_q );
+
+ } else {
+ assert( !LDAP_BACK_CONN_CACHED( mc ) );
+ }
+
+ } else {
+ (void)ldap_tavl_delete( &mi->mi_conninfo.lai_tree,
+ (caddr_t)mc, meta_back_conndnmc_cmp );
+ }
+
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi,
+ "<<< meta_back_getconn(expired)" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_getconn: mc=%p conn=%s expired (tainted).\n",
+ op->o_log_prefix, (void *)mc, buf );
+ }
+ }
+
+ mc->mc_refcnt++;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ /* if we go to selection, the entry must not exist,
+ * and we must be able to resolve the parent */
+ dn_type = META_DNTYPE_PARENT;
+ dnParent( &ndn, &pndn );
+ break;
+
+ case LDAP_REQ_MODRDN:
+ /* if nnewSuperior is not NULL, it must resolve
+ * to the same candidate as the req_ndn */
+ if ( op->orr_nnewSup ) {
+ dn_type = META_DNTYPE_NEWPARENT;
+ }
+ break;
+
+ case LDAP_REQ_BIND:
+ /* if bound as rootdn, the backend must bind to all targets
+ * with the administrative identity
+ * (unless pseoudoroot-bind-defer is TRUE) */
+ if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) {
+ op_type = META_OP_REQUIRE_ALL;
+ }
+ break;
+
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODIFY:
+ /* just a unique candidate */
+ break;
+
+ case LDAP_REQ_SEARCH:
+ /* allow multiple candidates for the searchBase */
+ op_type = META_OP_ALLOW_MULTIPLE;
+ break;
+
+ default:
+ /* right now, just break (exop?) */
+ break;
+ }
+
+ /*
+ * require all connections ...
+ */
+ if ( op_type == META_OP_REQUIRE_ALL ) {
+
+ /* Looks like we didn't get a bind. Open a new session... */
+ if ( mc == NULL ) {
+ assert( new_conn == 0 );
+ mc = metaconn_alloc( op );
+ mc->mc_conn = mc_curr.mc_conn;
+ ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn );
+ new_conn = 1;
+ if ( sendok & LDAP_BACK_BINDING ) {
+ LDAP_BACK_CONN_BINDING_SET( mc );
+ }
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ } else if ( 0 ) {
+ /* TODO: if any of the connections is binding,
+ * release mc and create a new one */
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ candidates[ i ].sr_err = meta_back_init_one_conn( op,
+ rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) {
+ LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] );
+ }
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ err = candidates[ i ].sr_err;
+ continue;
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ if ( new_conn ) {
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ } else {
+ meta_back_release_conn( mi, mc );
+ }
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+
+ return NULL;
+ }
+
+ goto done;
+ }
+
+ /*
+ * looks in cache, if any
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ cached = i = meta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn );
+ }
+
+ if ( op_type == META_OP_REQUIRE_SINGLE ) {
+ metatarget_t *mt = NULL;
+ metasingleconn_t *msc = NULL;
+
+ int j;
+
+ for ( j = 0; j < mi->mi_ntargets; j++ ) {
+ META_CANDIDATE_RESET( &candidates[ j ] );
+ }
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ if ( i == META_TARGET_NONE ) {
+ i = meta_back_get_candidate( op, rs, &ndn );
+
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) {
+ i = meta_back_get_candidate( op, rs, &pndn );
+ }
+
+ if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) {
+ if ( mc != NULL ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+
+ return NULL;
+ }
+ }
+
+ if ( dn_type == META_DNTYPE_NEWPARENT && meta_back_get_candidate( op, rs, op->orr_nnewSup ) != i )
+ {
+ if ( mc != NULL ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Cross-target rename not supported";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+
+ return NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "==>meta_back_getconn: got target=%d for ndn=\"%s\" from cache\n",
+ i, op->o_req_ndn.bv_val );
+
+ if ( mc == NULL ) {
+ /* Retries searching for a metaconn in the avl tree
+ * the reason is that the connection might have been
+ * created by meta_back_get_candidate() */
+ if ( !( sendok & LDAP_BACK_BINDING ) ) {
+retry_lock2:;
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ mc = (metaconn_t *)ldap_tavl_find( mi->mi_conninfo.lai_tree,
+ (caddr_t)&mc_curr, meta_back_conndn_cmp );
+ if ( mc != NULL ) {
+ /* catch taint errors */
+ assert( !LDAP_BACK_CONN_TAINTED( mc ) );
+
+ /* Don't reuse connections while they're still binding */
+ if ( META_BACK_CONN_CREATING( &mc->mc_conns[ i ] )
+ || LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) )
+ {
+ if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ ldap_pvt_thread_yield();
+ goto retry_lock2;
+ }
+
+ mc = NULL;
+
+ } else {
+ mc->mc_refcnt++;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ /* Looks like we didn't get a bind. Open a new session... */
+ if ( mc == NULL ) {
+ assert( new_conn == 0 );
+ mc = metaconn_alloc( op );
+ mc->mc_conn = mc_curr.mc_conn;
+ ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn );
+ new_conn = 1;
+ if ( sendok & LDAP_BACK_BINDING ) {
+ LDAP_BACK_CONN_BINDING_SET( mc );
+ }
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+ }
+ }
+
+ /*
+ * Clear all other candidates
+ */
+ ( void )meta_clear_unused_candidates( op, i );
+
+ mt = mi->mi_targets[ i ];
+ msc = &mc->mc_conns[ i ];
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd. In case of error, meta_back_init_one_conn
+ * sends the appropriate result.
+ */
+ err = meta_back_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn );
+ if ( err != LDAP_SUCCESS ) {
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ if ( new_conn ) {
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ } else {
+ meta_back_release_conn( mi, mc );
+ }
+ return NULL;
+ }
+
+ if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) {
+ LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] );
+ }
+
+ candidates[ i ].sr_err = LDAP_SUCCESS;
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ if ( candidate ) {
+ *candidate = i;
+ }
+
+ /*
+ * if no unique candidate ...
+ */
+ } else {
+
+ /* Looks like we didn't get a bind. Open a new session... */
+ if ( mc == NULL ) {
+ assert( new_conn == 0 );
+ mc = metaconn_alloc( op );
+ mc->mc_conn = mc_curr.mc_conn;
+ ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn );
+ new_conn = 1;
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+
+ META_CANDIDATE_RESET( &candidates[ i ] );
+
+ if ( i == cached
+ || meta_back_is_candidate( mt, &op->o_req_ndn,
+ op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) )
+ {
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ int lerr = meta_back_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ candidates[ i ].sr_err = lerr;
+ if ( lerr == LDAP_SUCCESS ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d]\n",
+ op->o_log_prefix, i );
+
+ } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+
+ Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d] %s\n",
+ op->o_log_prefix, i,
+ mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" );
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ if ( new_conn ) {
+ ( void )meta_clear_one_candidate( op, mc, i );
+ }
+ /* leave the target candidate, but record the error for later use */
+ err = lerr;
+
+ if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) {
+ Log( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_getconn[%d] quarantined err=%d text=%s\n",
+ op->o_log_prefix, i, lerr, rs->sr_text );
+
+ } else {
+ Log( LDAP_DEBUG_ANY, ldap_syslog, "%s: meta_back_getconn[%d] failed err=%d text=%s\n",
+ op->o_log_prefix, i, lerr, rs->sr_text );
+ }
+
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ if ( new_conn ) {
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ } else {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return NULL;
+ }
+
+ continue;
+ }
+
+ } else {
+ if ( new_conn ) {
+ ( void )meta_clear_one_candidate( op, mc, i );
+ }
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ if ( new_conn ) {
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ } else {
+ meta_back_release_conn( mi, mc );
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+ }
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+
+ return NULL;
+ }
+ }
+
+done:;
+ /* clear out meta_back_init_one_conn non-fatal errors */
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+
+ /* touch the timestamp */
+ if ( mi->mi_idle_timeout != 0 ) {
+ mc->mc_time = op->o_time;
+ }
+
+ if ( new_conn ) {
+ if ( mi->mi_conn_ttl ) {
+ mc->mc_create_time = op->o_time;
+ }
+
+ /*
+ * Inserts the newly created metaconn in the avl tree
+ */
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, ">>> meta_back_getconn" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ err = 0;
+ if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ if ( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num < mi->mi_conn_priv_max ) {
+ LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q );
+ mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num++;
+ LDAP_BACK_CONN_CACHED_SET( mc );
+
+ } else {
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ }
+ rs->sr_err = 0;
+
+ } else if ( !( sendok & LDAP_BACK_BINDING ) ) {
+ err = ldap_tavl_insert( &mi->mi_conninfo.lai_tree, ( caddr_t )mc,
+ meta_back_conndn_cmp, meta_back_conndn_dup );
+ LDAP_BACK_CONN_CACHED_SET( mc );
+ }
+
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, "<<< meta_back_getconn" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ /*
+ * Err could be -1 in case a duplicate metaconn is inserted
+ */
+ switch ( err ) {
+ case 0:
+ break;
+
+ case -1:
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ /* duplicate: free and try to get the newly created one */
+ if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( mi ) ) {
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ new_conn = 0;
+ goto retry_lock;
+ }
+
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ break;
+
+ default:
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_getconn: candidates=%d conn=%s insert failed\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ mc->mc_refcnt = 0;
+ meta_back_conn_free( mc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Proxy bind collision";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ return NULL;
+ }
+ }
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_getconn: candidates=%d conn=%s inserted\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ } else {
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s meta_back_getconn: candidates=%d conn=%s fetched\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+ }
+
+ return mc;
+}
+
+void
+meta_back_release_conn_lock(
+ metainfo_t *mi,
+ metaconn_t *mc,
+ int dolock )
+{
+ assert( mc != NULL );
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ }
+ assert( mc->mc_refcnt > 0 );
+ mc->mc_refcnt--;
+ /* NOTE: the connection is removed if either it is tainted
+ * or if it is shared and no one else is using it. This needs
+ * to occur because for intrinsic reasons cached connections
+ * that are not privileged would live forever and pollute
+ * the connection space (and eat up resources). Maybe this
+ * should be configurable... */
+ if ( LDAP_BACK_CONN_TAINTED( mc ) || !LDAP_BACK_CONN_CACHED( mc ) ) {
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, ">>> meta_back_release_conn" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ if ( mc->mc_q.tqe_prev != NULL ) {
+ assert( LDAP_BACK_CONN_CACHED( mc ) );
+ assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 );
+ LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q );
+ mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--;
+ LDAP_TAILQ_ENTRY_INIT( mc, mc_q );
+
+ } else {
+ assert( !LDAP_BACK_CONN_CACHED( mc ) );
+ }
+
+ } else if ( LDAP_BACK_CONN_CACHED( mc ) ) {
+ metaconn_t *tmpmc;
+
+ tmpmc = ldap_tavl_delete( &mi->mi_conninfo.lai_tree,
+ ( caddr_t )mc, meta_back_conndnmc_cmp );
+
+ /* Overparanoid, but useful... */
+ assert( tmpmc == NULL || tmpmc == mc );
+ }
+
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, "<<< meta_back_release_conn" );
+#endif /* META_BACK_PRINT_CONNTREE */
+
+ if ( mc->mc_refcnt == 0 ) {
+ meta_back_conn_free( mc );
+ mc = NULL;
+ }
+ }
+
+ if ( mc != NULL && LDAP_BACK_CONN_BINDING( mc ) ) {
+ LDAP_BACK_CONN_BINDING_CLEAR( mc );
+ }
+
+ if ( dolock ) {
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+}
+
+void
+meta_back_quarantine(
+ Operation *op,
+ SlapReply *rs,
+ int candidate )
+{
+ metainfo_t *mi = (metainfo_t *)op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ time_t new_last = slap_get_time();
+
+ switch ( mt->mt_isquarantined ) {
+ case LDAP_BACK_FQ_NO:
+ if ( ri->ri_last == new_last ) {
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_quarantine[%d]: enter.\n",
+ op->o_log_prefix, candidate );
+
+ ri->ri_idx = 0;
+ ri->ri_count = 0;
+ break;
+
+ case LDAP_BACK_FQ_RETRYING:
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_back_quarantine[%d]: block #%d try #%d failed.\n",
+ op->o_log_prefix, candidate, ri->ri_idx,
+ ri->ri_count );
+
+ ++ri->ri_count;
+ if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER
+ && ri->ri_count == ri->ri_num[ ri->ri_idx ] )
+ {
+ ri->ri_count = 0;
+ ++ri->ri_idx;
+ }
+ break;
+
+ default:
+ goto done;
+ }
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_YES;
+ ri->ri_last = new_last;
+
+ } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_quarantine[%d]: exit.\n",
+ op->o_log_prefix, candidate );
+
+ if ( mi->mi_quarantine_f ) {
+ (void)mi->mi_quarantine_f( mi, candidate,
+ mi->mi_quarantine_p );
+ }
+
+ ri->ri_count = 0;
+ ri->ri_idx = 0;
+ mt->mt_isquarantined = LDAP_BACK_FQ_NO;
+ }
+
+done:;
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+}
diff --git a/servers/slapd/back-meta/delete.c b/servers/slapd/back-meta/delete.c
new file mode 100644
index 0000000..bec437a
--- /dev/null
+++ b/servers/slapd/back-meta/delete.c
@@ -0,0 +1,103 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_delete( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt;
+ metaconn_t *mc = NULL;
+ int candidate = -1;
+ struct berval mdn = BER_BVNULL;
+ dncookie dc;
+ int msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR );
+ if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ assert( mc->mc_conns[ candidate ].msc_ld != NULL );
+
+ /*
+ * Rewrite the compare dn, if needed
+ */
+ mt = mi->mi_targets[ candidate ];
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "deleteDN";
+
+ if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+retry:;
+ ctrls = op->o_ctrls;
+ if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+ {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_delete_ext( mc->mc_conns[ candidate ].msc_ld,
+ mdn.bv_val, ctrls, NULL, &msgid );
+ rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid,
+ mt->mt_timeout[ SLAP_OP_DELETE ], ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+cleanup:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ free( mdn.bv_val );
+ BER_BVZERO( &mdn );
+ }
+
+ if ( mc ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/dncache.c b/servers/slapd/back-meta/dncache.c
new file mode 100644
index 0000000..a3e7958
--- /dev/null
+++ b/servers/slapd/back-meta/dncache.c
@@ -0,0 +1,235 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+/*
+ * The dncache, at present, maps an entry to the target that holds it.
+ */
+
+typedef struct metadncacheentry_t {
+ struct berval dn;
+ int target;
+
+ time_t lastupdated;
+} metadncacheentry_t;
+
+/*
+ * meta_dncache_cmp
+ *
+ * compares two struct metadncacheentry; used by avl stuff
+ * FIXME: modify avl stuff to delete an entry based on cmp
+ * (e.g. when ttl expired?)
+ */
+int
+meta_dncache_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ber_bvcmp( &cc1->dn, &cc2->dn);
+}
+
+/*
+ * meta_dncache_dup
+ *
+ * returns -1 in case a duplicate struct metadncacheentry has been inserted;
+ * used by avl stuff
+ */
+int
+meta_dncache_dup(
+ void *c1,
+ void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0;
+}
+
+/*
+ * meta_dncache_get_target
+ *
+ * returns the target a dn belongs to, or -1 in case the dn is not
+ * in the cache
+ */
+int
+meta_dncache_get_target(
+ metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t tmp_entry,
+ *entry;
+ int target = META_TARGET_NONE;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, meta_dncache_cmp );
+
+ if ( entry != NULL ) {
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl < 0 ) {
+ target = entry->target;
+
+ } else {
+ if ( entry->lastupdated+cache->ttl > slap_get_time() ) {
+ target = entry->target;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return target;
+}
+
+/*
+ * meta_dncache_update_entry
+ *
+ * updates target and lastupdated of a struct metadncacheentry if exists,
+ * otherwise it gets created; returns -1 in case of error
+ */
+int
+meta_dncache_update_entry(
+ metadncache_t *cache,
+ struct berval *ndn,
+ int target )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+ time_t curr_time = 0L;
+ int err = 0;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl > 0 ) {
+ curr_time = slap_get_time();
+ }
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, meta_dncache_cmp );
+
+ if ( entry != NULL ) {
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ } else {
+ entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 );
+ if ( entry == NULL ) {
+ err = -1;
+ goto error_return;
+ }
+
+ entry->dn.bv_len = ndn->bv_len;
+ entry->dn.bv_val = (char *)&entry[ 1 ];
+ AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len );
+ entry->dn.bv_val[ ndn->bv_len ] = '\0';
+
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ err = ldap_avl_insert( &cache->tree, ( caddr_t )entry,
+ meta_dncache_cmp, meta_dncache_dup );
+ }
+
+error_return:;
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return err;
+}
+
+/*
+ * meta_dncache_update_entry
+ *
+ * updates target and lastupdated of a struct metadncacheentry if exists,
+ * otherwise it gets created; returns -1 in case of error
+ */
+int
+meta_dncache_delete_entry(
+ metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ldap_avl_delete( &cache->tree, ( caddr_t )&tmp_entry,
+ meta_dncache_cmp );
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ if ( entry != NULL ) {
+ meta_dncache_free( ( void * )entry );
+ }
+
+ return 0;
+}
+
+/*
+ * meta_dncache_free
+ *
+ * frees an entry
+ *
+ */
+void
+meta_dncache_free(
+ void *e )
+{
+ free( e );
+}
+
diff --git a/servers/slapd/back-meta/init.c b/servers/slapd/back-meta/init.c
new file mode 100644
index 0000000..8639109
--- /dev/null
+++ b/servers/slapd/back-meta/init.c
@@ -0,0 +1,473 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_open(
+ BackendInfo *bi )
+{
+ /* FIXME: need to remove the pagedResults, and likely more... */
+ bi->bi_controls = slap_known_controls;
+
+ return 0;
+}
+
+int
+meta_back_initialize(
+ BackendInfo *bi )
+{
+ bi->bi_flags =
+#if 0
+ /* this is not (yet) set essentially because back-meta does not
+ * directly support extended operations... */
+#ifdef LDAP_DYNAMIC_OBJECTS
+ /* this is set because all the support a proxy has to provide
+ * is the capability to forward the refresh exop, and to
+ * pass thru entries that contain the dynamicObject class
+ * and the entryTtl attribute */
+ SLAP_BFLAG_DYNAMIC |
+#endif /* LDAP_DYNAMIC_OBJECTS */
+#endif
+
+ /* back-meta recognizes RFC4525 increment;
+ * let the remote server complain, if needed (ITS#5912) */
+ SLAP_BFLAG_INCREMENT;
+
+ bi->bi_open = meta_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = meta_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = meta_back_db_open;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = meta_back_db_destroy;
+
+ bi->bi_op_bind = meta_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = meta_back_search;
+ bi->bi_op_compare = meta_back_compare;
+ bi->bi_op_modify = meta_back_modify;
+ bi->bi_op_modrdn = meta_back_modrdn;
+ bi->bi_op_add = meta_back_add;
+ bi->bi_op_delete = meta_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = meta_back_conn_destroy;
+
+ return meta_back_init_cf( bi );
+}
+
+int
+meta_back_db_init(
+ Backend *be,
+ ConfigReply *cr)
+{
+ metainfo_t *mi;
+ int i;
+ BackendInfo *bi;
+
+ bi = backend_info( "ldap" );
+ if ( !bi || !bi->bi_extra ) {
+ Debug( LDAP_DEBUG_ANY,
+ "meta_back_db_init: needs back-ldap\n" );
+ return 1;
+ }
+
+ mi = ch_calloc( 1, sizeof( metainfo_t ) );
+ if ( mi == NULL ) {
+ return -1;
+ }
+
+ /* set default flags */
+ mi->mi_flags =
+ META_BACK_F_DEFER_ROOTDN_BIND
+ | META_BACK_F_PROXYAUTHZ_ALWAYS
+ | META_BACK_F_PROXYAUTHZ_ANON
+ | META_BACK_F_PROXYAUTHZ_NOANON;
+
+ /*
+ * At present the default is no default target;
+ * this may change
+ */
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ mi->mi_bind_timeout.tv_sec = 0;
+ mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT;
+
+ mi->mi_rebind_f = meta_back_default_rebind;
+ mi->mi_urllist_f = meta_back_default_urllist;
+
+ ldap_pvt_thread_mutex_init( &mi->mi_conninfo.lai_mutex );
+ ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex );
+
+ /* safe default */
+ mi->mi_nretries = META_RETRY_DEFAULT;
+ mi->mi_version = LDAP_VERSION3;
+
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ mi->mi_conn_priv[ i ].mic_num = 0;
+ LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv );
+ }
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT;
+
+ mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra;
+
+ be->be_private = mi;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return 0;
+}
+
+int
+meta_target_finish(
+ metainfo_t *mi,
+ metatarget_t *mt,
+ const char *log,
+ char *msg,
+ size_t msize
+)
+{
+ slap_bindconf sb = { BER_BVNULL };
+ struct berval mapped;
+ int rc;
+
+ ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = mt->mt_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+ LDAP_FEATURE_ABSOLUTE_FILTERS );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_T_F;
+ }
+ }
+
+ if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+ LDAP_EXOP_CANCEL );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP;
+ }
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )
+ || mt->mt_idassert_authz != NULL )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ snprintf( msg, msize,
+ "%s: inconsistent idassert configuration "
+ "(likely authz=\"*\" used with \"non-prescriptive\" flag)",
+ log );
+ Debug( LDAP_DEBUG_ANY, "%s (target %s)\n",
+ msg, mt->mt_uri );
+ return 1;
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON;
+ }
+
+ BER_BVZERO( &mapped );
+ ldap_back_map( &mt->mt_rwmap.rwm_at,
+ &slap_schema.si_ad_entryDN->ad_cname, &mapped,
+ BACKLDAP_REMAP );
+ if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+ mt->mt_rep_flags |= REP_NO_ENTRYDN;
+ }
+
+ BER_BVZERO( &mapped );
+ ldap_back_map( &mt->mt_rwmap.rwm_at,
+ &slap_schema.si_ad_subschemaSubentry->ad_cname, &mapped,
+ BACKLDAP_REMAP );
+ if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+ mt->mt_rep_flags |= REP_NO_SUBSCHEMA;
+ }
+
+ return 0;
+}
+
+int
+meta_back_db_open(
+ Backend *be,
+ ConfigReply *cr )
+{
+ metainfo_t *mi = (metainfo_t *)be->be_private;
+ char msg[SLAP_TEXT_BUFLEN];
+
+ int i, rc;
+
+ if ( mi->mi_ntargets == 0 ) {
+ /* Dynamically added, nothing to check here until
+ * some targets get added
+ */
+ if ( slapMode & SLAP_SERVER_RUNNING )
+ return 0;
+
+ Debug( LDAP_DEBUG_ANY,
+ "meta_back_db_open: no targets defined\n" );
+ return 1;
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( meta_target_finish( mi, mt,
+ "meta_back_db_open", msg, sizeof( msg )))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * meta_back_conn_free()
+ *
+ * actually frees a connection; the reference count must be 0,
+ * and it must not (or no longer) be in the cache.
+ */
+void
+meta_back_conn_free(
+ void *v_mc )
+{
+ metaconn_t *mc = v_mc;
+ int ntargets;
+
+ assert( mc != NULL );
+ assert( mc->mc_refcnt == 0 );
+
+ /* at least one must be present... */
+ ntargets = mc->mc_info->mi_ntargets;
+ assert( ntargets > 0 );
+
+ for ( ; ntargets--; ) {
+ (void)meta_clear_one_candidate( NULL, mc, ntargets );
+ }
+
+ if ( !BER_BVISNULL( &mc->mc_local_ndn ) ) {
+ free( mc->mc_local_ndn.bv_val );
+ }
+
+ free( mc );
+}
+
+static void
+mapping_free(
+ void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+ ch_free( mapping->src.bv_val );
+ ch_free( mapping->dst.bv_val );
+ ch_free( mapping );
+}
+
+static void
+mapping_dst_free(
+ void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+
+ if ( BER_BVISEMPTY( &mapping->dst ) ) {
+ mapping_free( &mapping[ -1 ] );
+ }
+}
+
+void
+meta_back_map_free( struct ldapmap *lm )
+{
+ ldap_avl_free( lm->remap, mapping_dst_free );
+ ldap_avl_free( lm->map, mapping_free );
+ lm->remap = NULL;
+ lm->map = NULL;
+}
+
+static void
+target_free(
+ metatarget_t *mt )
+{
+ if ( mt->mt_uri ) {
+ free( mt->mt_uri );
+ ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex );
+ }
+ if ( mt->mt_subtree ) {
+ meta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ }
+ if ( mt->mt_filter ) {
+ meta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ }
+ if ( !BER_BVISNULL( &mt->mt_psuffix ) ) {
+ free( mt->mt_psuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) {
+ free( mt->mt_nsuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_binddn ) ) {
+ free( mt->mt_binddn.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_bindpw ) ) {
+ free( mt->mt_bindpw.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) {
+ ch_free( mt->mt_idassert_authcID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ch_free( mt->mt_idassert_authcDN.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ ch_free( mt->mt_idassert_passwd.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) {
+ ch_free( mt->mt_idassert_authzID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) {
+ ch_free( mt->mt_idassert_sasl_mech.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) {
+ ch_free( mt->mt_idassert_sasl_realm.bv_val );
+ }
+ if ( mt->mt_idassert_authz != NULL ) {
+ ber_bvarray_free( mt->mt_idassert_authz );
+ }
+ if ( mt->mt_rwmap.rwm_rw ) {
+ rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+ if ( mt->mt_rwmap.rwm_bva_rewrite )
+ ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite );
+ }
+ meta_back_map_free( &mt->mt_rwmap.rwm_oc );
+ meta_back_map_free( &mt->mt_rwmap.rwm_at );
+ ber_bvarray_free( mt->mt_rwmap.rwm_bva_map );
+
+ free( mt );
+}
+
+int
+meta_back_db_destroy(
+ Backend *be,
+ ConfigReply *cr )
+{
+ metainfo_t *mi;
+
+ if ( be->be_private ) {
+ int i;
+
+ mi = ( metainfo_t * )be->be_private;
+
+ /*
+ * Destroy the connection tree
+ */
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+
+ if ( mi->mi_conninfo.lai_tree ) {
+ ldap_tavl_free( mi->mi_conninfo.lai_tree, meta_back_conn_free );
+ }
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ while ( !LDAP_TAILQ_EMPTY( &mi->mi_conn_priv[ i ].mic_priv ) ) {
+ metaconn_t *mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ i ].mic_priv );
+
+ LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ i ].mic_priv, mc, mc_q );
+ meta_back_conn_free( mc );
+ }
+ }
+
+ /*
+ * Destroy the per-target stuff (assuming there's at
+ * least one ...)
+ */
+ if ( mi->mi_targets != NULL ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num )
+ {
+ mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ }
+
+ target_free( mt );
+ }
+
+ free( mi->mi_targets );
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex );
+ if ( mi->mi_cache.tree ) {
+ ldap_avl_free( mi->mi_cache.tree, meta_dncache_free );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex );
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_conninfo.lai_mutex );
+
+ if ( mi->mi_candidates != NULL ) {
+ ber_memfree_x( mi->mi_candidates, NULL );
+ }
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine );
+ }
+ }
+
+ free( be->be_private );
+ return 0;
+}
+
+#if SLAPD_META == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( meta )
+
+#endif /* SLAPD_META == SLAPD_MOD_DYNAMIC */
+
+
diff --git a/servers/slapd/back-meta/map.c b/servers/slapd/back-meta/map.c
new file mode 100644
index 0000000..6591341
--- /dev/null
+++ b/servers/slapd/back-meta/map.c
@@ -0,0 +1,924 @@
+/* map.c - ldap backend mapping routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+/* This is an altered version */
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources,
+ * credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users
+ * ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ *
+ *
+ *
+ * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it>
+ *
+ * This software is being modified by Pierangelo Masarati.
+ * The previously reported conditions apply to the modified code as well.
+ * Changes in the original code are highlighted where required.
+ * Credits for the original code go to the author, Howard Chu.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+mapping_cmp ( const void *c1, const void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+ int rc = map1->src.bv_len - map2->src.bv_len;
+ if (rc) return rc;
+ return ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) );
+}
+
+int
+mapping_dup ( void *c1, void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+
+ return ( ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) == 0 ) ? -1 : 0 );
+}
+
+void
+ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping **m )
+{
+ struct ldapmapping *mapping;
+
+ assert( m != NULL );
+
+ *m = NULL;
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof( struct ldapmapping ) );
+ if ( mapping == NULL ) {
+ return;
+ }
+
+ ber_str2bv( "objectclass", STRLENOF("objectclass"), 1, &mapping[0].src);
+ ber_dupbv( &mapping[0].dst, &mapping[0].src );
+ mapping[1].src = mapping[0].src;
+ mapping[1].dst = mapping[0].dst;
+
+ ldap_avl_insert( &lm->map, (caddr_t)&mapping[0],
+ mapping_cmp, mapping_dup );
+ ldap_avl_insert( &lm->remap, (caddr_t)&mapping[1],
+ mapping_cmp, mapping_dup );
+ *m = mapping;
+}
+
+int
+ldap_back_mapping ( struct ldapmap *map, struct berval *s, struct ldapmapping **m,
+ int remap )
+{
+ Avlnode *tree;
+ struct ldapmapping fmapping;
+
+ assert( m != NULL );
+
+ /* let special attrnames slip through (ITS#5760) */
+ if ( bvmatch( s, slap_bv_no_attrs )
+ || bvmatch( s, slap_bv_all_user_attrs )
+ || bvmatch( s, slap_bv_all_operational_attrs ) )
+ {
+ *m = NULL;
+ return 0;
+ }
+
+ if ( remap == BACKLDAP_REMAP ) {
+ tree = map->remap;
+
+ } else {
+ tree = map->map;
+ }
+
+ fmapping.src = *s;
+ *m = (struct ldapmapping *)ldap_avl_find( tree, (caddr_t)&fmapping, mapping_cmp );
+ if ( *m == NULL ) {
+ return map->drop_missing;
+ }
+
+ return 0;
+}
+
+void
+ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *bv,
+ int remap )
+{
+ struct ldapmapping *mapping;
+ int drop_missing;
+
+ /* map->map may be NULL when mapping is configured,
+ * but map->remap can't */
+ if ( map->remap == NULL ) {
+ *bv = *s;
+ return;
+ }
+
+ BER_BVZERO( bv );
+ drop_missing = ldap_back_mapping( map, s, &mapping, remap );
+ if ( mapping != NULL ) {
+ if ( !BER_BVISNULL( &mapping->dst ) ) {
+ *bv = mapping->dst;
+ }
+ return;
+ }
+
+ if ( !drop_missing ) {
+ *bv = *s;
+ }
+}
+
+int
+ldap_back_map_attrs(
+ Operation *op,
+ struct ldapmap *at_map,
+ AttributeName *an,
+ int remap,
+ char ***mapped_attrs )
+{
+ int i, x, j;
+ char **na;
+ struct berval mapped;
+
+ if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ i = 0;
+ if ( an != NULL ) {
+ for ( ; !BER_BVISNULL( &an[i].an_name ); i++ )
+ /* */ ;
+ }
+
+ x = 0;
+ if ( op->o_bd->be_extra_anlist != NULL ) {
+ for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ )
+ /* */ ;
+ }
+
+ assert( i > 0 || x > 0 );
+
+ na = (char **)ber_memcalloc_x( i + x + 1, sizeof(char *), op->o_tmpmemctx );
+ if ( na == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_NO_MEMORY;
+ }
+
+ j = 0;
+ if ( i > 0 ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ ldap_back_map( at_map, &an[i].an_name, &mapped, remap );
+ if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) {
+ na[j++] = mapped.bv_val;
+ }
+ }
+ }
+
+ if ( x > 0 ) {
+ for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) {
+ if ( op->o_bd->be_extra_anlist[x].an_desc &&
+ ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, an ) )
+ {
+ continue;
+ }
+
+ ldap_back_map( at_map, &op->o_bd->be_extra_anlist[x].an_name, &mapped, remap );
+ if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) {
+ na[j++] = mapped.bv_val;
+ }
+ }
+ }
+
+ if ( j == 0 && ( i > 0 || x > 0 ) ) {
+ na[j++] = LDAP_NO_ATTRS;
+ }
+ na[j] = NULL;
+
+ *mapped_attrs = na;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+map_attr_value(
+ dncookie *dc,
+ AttributeDescription *ad,
+ struct berval *mapped_attr,
+ struct berval *value,
+ struct berval *mapped_value,
+ int remap,
+ void *memctx )
+{
+ struct berval vtmp;
+ int freeval = 0;
+
+ ldap_back_map( &dc->target->mt_rwmap.rwm_at, &ad->ad_cname, mapped_attr, remap );
+ if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) {
+#if 0
+ /*
+ * FIXME: are we sure we need to search oc_map if at_map fails?
+ */
+ ldap_back_map( &dc->target->mt_rwmap.rwm_oc, &ad->ad_cname, mapped_attr, remap );
+ if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) {
+ *mapped_attr = ad->ad_cname;
+ }
+#endif
+ if ( dc->target->mt_rwmap.rwm_at.drop_missing ) {
+ return -1;
+ }
+
+ *mapped_attr = ad->ad_cname;
+ }
+
+ if ( value == NULL ) {
+ return 0;
+ }
+
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ {
+ dncookie fdc = *dc;
+
+ fdc.ctx = "searchFilterAttrDN";
+
+ switch ( ldap_back_dn_massage( &fdc, value, &vtmp ) ) {
+ case LDAP_SUCCESS:
+ if ( vtmp.bv_val != value->bv_val ) {
+ freeval = 1;
+ }
+ break;
+
+ case LDAP_UNWILLING_TO_PERFORM:
+ return -1;
+
+ case LDAP_OTHER:
+ return -1;
+ }
+
+ } else if ( ad->ad_type->sat_equality &&
+ ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER )
+ {
+ if ( ad->ad_type->sat_equality->smr_normalize(
+ (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX),
+ NULL, NULL, value, &vtmp, memctx ) )
+ {
+ return -1;
+ }
+ freeval = 2;
+
+ } else if ( ad == slap_schema.si_ad_objectClass || ad == slap_schema.si_ad_structuralObjectClass ) {
+ ldap_back_map( &dc->target->mt_rwmap.rwm_oc, value, &vtmp, remap );
+ if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) {
+ vtmp = *value;
+ }
+
+ } else {
+ vtmp = *value;
+ }
+
+ filter_escape_value_x( &vtmp, mapped_value, memctx );
+
+ switch ( freeval ) {
+ case 1:
+ ber_memfree( vtmp.bv_val );
+ break;
+ case 2:
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+ }
+
+ return 0;
+}
+
+static int
+ldap_back_int_filter_map_rewrite(
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr,
+ int remap,
+ void *memctx )
+{
+ int i;
+ Filter *p;
+ struct berval atmp,
+ vtmp,
+ *tmp;
+ static struct berval
+ /* better than nothing... */
+ ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+ ber_bvtf_false = BER_BVC( "(|)" ),
+ /* better than nothing... */
+ ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+ ber_bvtf_true = BER_BVC( "(&)" ),
+#if 0
+ /* no longer needed; preserved for completeness */
+ ber_bvundefined = BER_BVC( "(?=undefined)" ),
+#endif
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" );
+ ber_len_t len;
+
+ assert( fstr != NULL );
+ BER_BVZERO( fstr );
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, memctx );
+ return LDAP_OTHER;
+ }
+
+ switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) {
+ case LDAP_FILTER_EQUALITY:
+ if ( map_attr_value( dc, f->f_av_desc, &atmp,
+ &f->f_av_value, &vtmp, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len
+ + ( sizeof("(=)") - 1 );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ if ( map_attr_value( dc, f->f_av_desc, &atmp,
+ &f->f_av_value, &vtmp, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len
+ + ( sizeof("(>=)") - 1 );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ if ( map_attr_value( dc, f->f_av_desc, &atmp,
+ &f->f_av_value, &vtmp, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len
+ + ( sizeof("(<=)") - 1 );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ if ( map_attr_value( dc, f->f_av_desc, &atmp,
+ &f->f_av_value, &vtmp, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len
+ + ( sizeof("(~=)") - 1 );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ if ( map_attr_value( dc, f->f_sub_desc, &atmp,
+ NULL, NULL, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ /* cannot be a DN ... */
+
+ fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */
+ if ( !fstr->bv_val ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ char *tmp;
+
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &vtmp, memctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ tmp = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+ if ( !tmp ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+ fstr->bv_val = tmp;
+
+ snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3,
+ /* "(attr=" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ char *tmp;
+
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i], &vtmp, memctx );
+
+ fstr->bv_len += vtmp.bv_len + 1;
+ tmp = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+ if ( !tmp ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+ fstr->bv_val = tmp;
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ char *tmp;
+
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &vtmp, memctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ tmp = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+ if ( !tmp ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+ fstr->bv_val = tmp;
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ if ( map_attr_value( dc, f->f_desc, &atmp,
+ NULL, NULL, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF( "(%)" );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128? */
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ int rc;
+
+ len = fstr->bv_len;
+
+ rc = ldap_back_int_filter_map_rewrite( dc, p, &vtmp, remap, memctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+ /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT:
+ if ( f->f_mr_desc ) {
+ if ( map_attr_value( dc, f->f_mr_desc, &atmp,
+ &f->f_mr_value, &vtmp, remap, memctx ) )
+ {
+ goto computed;
+ }
+
+ } else {
+ BER_BVSTR( &atmp, "" );
+ filter_escape_value_x( &f->f_mr_value, &vtmp, memctx );
+ }
+
+ /* FIXME: cleanup (less ?: operators...) */
+ fstr->bv_len = atmp.bv_len +
+ ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+ ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+ vtmp.bv_len + ( STRLENOF( "(:=)" ) );
+ fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+ if ( !fstr->bv_val ) {
+ ber_memfree_x( vtmp.bv_val, memctx );
+ return LDAP_NO_MEMORY;
+ }
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ atmp.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ /* FIXME: treat UNDEFINED as FALSE */
+ case SLAPD_COMPARE_UNDEFINED:
+computed:;
+ if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) {
+ return LDAP_COMPARE_FALSE;
+ }
+ /* fallthru */
+
+ case LDAP_COMPARE_FALSE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_false;
+ break;
+ }
+ tmp = &ber_bvfalse;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_true;
+ break;
+ }
+
+ tmp = &ber_bvtrue;
+ break;
+
+ default:
+ tmp = &ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, tmp, memctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, memctx );
+ break;
+ }
+
+ return 0;
+}
+
+int
+ldap_back_filter_map_rewrite(
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr,
+ int remap,
+ void *memctx )
+{
+ int rc;
+ dncookie fdc;
+ struct berval ftmp;
+ static char *dmy = "";
+
+ rc = ldap_back_int_filter_map_rewrite( dc, f, fstr, remap, memctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fdc = *dc;
+ ftmp = *fstr;
+
+ fdc.ctx = "searchFilter";
+
+ switch ( rewrite_session( fdc.target->mt_rwmap.rwm_rw, fdc.ctx,
+ ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : dmy ),
+ fdc.conn, &fstr->bv_val ) )
+ {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( fstr ) ) {
+ fstr->bv_len = strlen( fstr->bv_val );
+
+ } else {
+ *fstr = ftmp;
+ }
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ fdc.ctx, BER_BVISNULL( &ftmp ) ? "" : ftmp.bv_val,
+ BER_BVISNULL( fstr ) ? "" : fstr->bv_val );
+ rc = LDAP_SUCCESS;
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ fdc.rs->sr_text = "Operation not allowed";
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_OTHER;
+ fdc.rs->sr_text = "Rewrite error";
+ }
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ if ( fstr->bv_val == dmy ) {
+ BER_BVZERO( fstr );
+
+ } else if ( fstr->bv_val != ftmp.bv_val ) {
+ /* NOTE: need to realloc mapped filter on slab
+ * and free the original one, until librewrite
+ * becomes slab-aware
+ */
+ ber_dupbv_x( &ftmp, fstr, memctx );
+ ch_free( fstr->bv_val );
+ *fstr = ftmp;
+ }
+
+ return rc;
+}
+
+int
+ldap_back_referral_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals,
+ void *memctx
+)
+{
+ int i, last;
+
+ assert( dc != NULL );
+ assert( a_vals != NULL );
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ )
+ ;
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) {
+ struct berval dn,
+ olddn = BER_BVNULL;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ rc = ldap_back_dn_massage( dc, &olddn, &dn );
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ber_memfree( a_vals[ i ].bv_val );
+ if ( last > i ) {
+ a_vals[ i ] = a_vals[ last ];
+ }
+ BER_BVZERO( &a_vals[ last ] );
+ last--;
+ i--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val )
+ {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ free( dn.bv_val );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ break;
+ }
+
+ ber_memfree_x( a_vals[ i ].bv_val, memctx );
+ ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], memctx );
+ ber_memfree( newurl );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ break;
+ }
+
+ ldap_free_urldesc( ludp );
+ }
+
+ return 0;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+ldap_dnattr_rewrite(
+ dncookie *dc,
+ BerVarray a_vals
+)
+{
+ struct berval bv;
+ int i, last;
+
+ assert( a_vals != NULL );
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+ ;
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( a_vals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ last--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &bv ) && bv.bv_val != a_vals[i].bv_val ) {
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = bv;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int
+ldap_dnattr_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals
+)
+{
+ struct berval bv;
+ int i, last;
+
+ assert( a_vals != NULL );
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+ ;
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ber_memfree( a_vals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ last--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &bv ) && a_vals[i].bv_val != bv.bv_val ) {
+ ber_memfree( a_vals[i].bv_val );
+ a_vals[i] = bv;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-meta/modify.c b/servers/slapd/back-meta/modify.c
new file mode 100644
index 0000000..459e835
--- /dev/null
+++ b/servers/slapd/back-meta/modify.c
@@ -0,0 +1,210 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_modify( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt;
+ metaconn_t *mc;
+ int rc = 0;
+ LDAPMod **modv = NULL;
+ LDAPMod *mods = NULL;
+ Modifications *ml;
+ int candidate = -1, i;
+ int isupdate;
+ struct berval mdn = BER_BVNULL;
+ struct berval mapped;
+ dncookie dc;
+ int msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+
+ mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR );
+ if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ assert( mc->mc_conns[ candidate ].msc_ld != NULL );
+
+ /*
+ * Rewrite the modify dn, if needed
+ */
+ mt = mi->mi_targets[ candidate ];
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "modifyDN";
+
+ if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next )
+ ;
+
+ modv = ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) + i*sizeof( LDAPMod ) );
+ mods = (LDAPMod *)&modv[ i + 1 ];
+
+ dc.ctx = "modifyAttrDN";
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ int j, is_oc = 0;
+
+ if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ if ( ml->sml_desc == slap_schema.si_ad_objectClass
+ || ml->sml_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ is_oc = 1;
+ mapped = ml->sml_desc->ad_cname;
+
+ } else {
+ ldap_back_map( &mt->mt_rwmap.rwm_at,
+ &ml->sml_desc->ad_cname, &mapped,
+ BACKLDAP_MAP );
+ if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+ continue;
+ }
+ }
+
+ modv[ i ] = &mods[ i ];
+ mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES;
+ mods[ i ].mod_type = mapped.bv_val;
+
+ /*
+ * FIXME: dn-valued attrs should be rewritten
+ * to allow their use in ACLs at the back-ldap
+ * level.
+ */
+ if ( ml->sml_values != NULL ) {
+ if ( is_oc ) {
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+ ;
+ mods[ i ].mod_bvalues =
+ (struct berval **)ch_malloc( ( j + 1 ) *
+ sizeof( struct berval * ) );
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); ) {
+ struct ldapmapping *mapping;
+
+ ldap_back_mapping( &mt->mt_rwmap.rwm_oc,
+ &ml->sml_values[ j ], &mapping, BACKLDAP_MAP );
+
+ if ( mapping == NULL ) {
+ if ( mt->mt_rwmap.rwm_oc.drop_missing ) {
+ continue;
+ }
+ mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ];
+
+ } else {
+ mods[ i ].mod_bvalues[ j ] = &mapping->dst;
+ }
+ j++;
+ }
+ mods[ i ].mod_bvalues[ j ] = NULL;
+
+ } else {
+ if ( ml->sml_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ {
+ ( void )ldap_dnattr_rewrite( &dc, ml->sml_values );
+ if ( ml->sml_values == NULL ) {
+ continue;
+ }
+ }
+
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+ ;
+ mods[ i ].mod_bvalues =
+ (struct berval **)ch_malloc( ( j + 1 ) *
+ sizeof( struct berval * ) );
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+ mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ];
+ }
+ mods[ i ].mod_bvalues[ j ] = NULL;
+ }
+
+ } else {
+ mods[ i ].mod_bvalues = NULL;
+ }
+
+ i++;
+ }
+ modv[ i ] = 0;
+
+retry:;
+ ctrls = op->o_ctrls;
+ rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_modify_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val,
+ modv, ctrls, NULL, &msgid );
+ rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid,
+ mt->mt_timeout[ SLAP_OP_MODIFY ], ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+cleanup:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ free( mdn.bv_val );
+ BER_BVZERO( &mdn );
+ }
+ if ( modv != NULL ) {
+ for ( i = 0; modv[ i ]; i++ ) {
+ ch_free( modv[ i ]->mod_bvalues );
+ }
+ }
+ ch_free( modv );
+
+ if ( mc ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/modrdn.c b/servers/slapd/back-meta/modrdn.c
new file mode 100644
index 0000000..cf6e1be
--- /dev/null
+++ b/servers/slapd/back-meta/modrdn.c
@@ -0,0 +1,177 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_modrdn( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt;
+ metaconn_t *mc;
+ int candidate = -1;
+ struct berval mdn = BER_BVNULL,
+ mnewSuperior = BER_BVNULL;
+ dncookie dc;
+ int msgid;
+ ldap_back_send_t retrying = LDAP_BACK_RETRYING;
+ LDAPControl **ctrls = NULL;
+ struct berval newrdn = BER_BVNULL;
+
+ mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR );
+ if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) {
+ return rs->sr_err;
+ }
+
+ assert( mc->mc_conns[ candidate ].msc_ld != NULL );
+
+ mt = mi->mi_targets[ candidate ];
+ dc.target = mt;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+
+ if ( op->orr_newSup ) {
+
+ /*
+ * NOTE: the newParent, if defined, must be on the
+ * same target as the entry to be renamed. This check
+ * has been anticipated in meta_back_getconn()
+ */
+ /*
+ * FIXME: one possibility is to delete the entry
+ * from one target and add it to the other;
+ * unfortunately we'd need write access to both,
+ * which is nearly impossible; for administration
+ * needs, the rootdn of the metadirectory could
+ * be mapped to an administrative account on each
+ * target (the binddn?); we'll see.
+ */
+ /*
+ * NOTE: we need to port the identity assertion
+ * feature from back-ldap
+ */
+
+ /* needs LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ /* op->o_protocol cannot be anything but LDAPv3,
+ * otherwise wouldn't be here */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /*
+ * Rewrite the new superior, if defined and required
+ */
+ dc.ctx = "newSuperiorDN";
+ if ( ldap_back_dn_massage( &dc, op->orr_newSup, &mnewSuperior ) ) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Rewrite the modrdn dn, if required
+ */
+ dc.ctx = "modrDN";
+ if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* NOTE: we need to copy the newRDN in case it was formed
+ * from a DN by simply changing the length (ITS#5397) */
+ newrdn = op->orr_newrdn;
+ if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) {
+ ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+ }
+
+retry:;
+ ctrls = op->o_ctrls;
+ if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+ {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = ldap_rename( mc->mc_conns[ candidate ].msc_ld,
+ mdn.bv_val, newrdn.bv_val,
+ mnewSuperior.bv_val, op->orr_deleteoldrdn,
+ ctrls, NULL, &msgid );
+ rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid,
+ mt->mt_timeout[ SLAP_OP_MODRDN ], ( LDAP_BACK_SENDRESULT | retrying ) );
+ if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) {
+ retrying &= ~LDAP_BACK_RETRYING;
+ if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) {
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+ }
+
+cleanup:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ free( mdn.bv_val );
+ BER_BVZERO( &mdn );
+ }
+
+ if ( !BER_BVISNULL( &mnewSuperior )
+ && mnewSuperior.bv_val != op->orr_newSup->bv_val )
+ {
+ free( mnewSuperior.bv_val );
+ BER_BVZERO( &mnewSuperior );
+ }
+
+ if ( newrdn.bv_val != op->orr_newrdn.bv_val ) {
+ op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( mc ) {
+ meta_back_release_conn( mi, mc );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-meta/proto-meta.h b/servers/slapd/back-meta/proto-meta.h
new file mode 100644
index 0000000..f6c16b2
--- /dev/null
+++ b/servers/slapd/back-meta/proto-meta.h
@@ -0,0 +1,54 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef PROTO_META_H
+#define PROTO_META_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init meta_back_initialize;
+
+extern BI_open meta_back_open;
+extern BI_close meta_back_close;
+extern BI_destroy meta_back_destroy;
+
+extern BI_db_init meta_back_db_init;
+extern BI_db_open meta_back_db_open;
+extern BI_db_destroy meta_back_db_destroy;
+extern BI_db_config meta_back_db_config;
+
+extern BI_op_bind meta_back_bind;
+extern BI_op_search meta_back_search;
+extern BI_op_compare meta_back_compare;
+extern BI_op_modify meta_back_modify;
+extern BI_op_modrdn meta_back_modrdn;
+extern BI_op_add meta_back_add;
+extern BI_op_delete meta_back_delete;
+extern BI_op_abandon meta_back_abandon;
+
+extern BI_connection_destroy meta_back_conn_destroy;
+
+int meta_back_init_cf( BackendInfo *bi );
+
+LDAP_END_DECL
+
+#endif /* PROTO_META_H */
diff --git a/servers/slapd/back-meta/search.c b/servers/slapd/back-meta/search.c
new file mode 100644
index 0000000..cfd2f4b
--- /dev/null
+++ b/servers/slapd/back-meta/search.c
@@ -0,0 +1,2431 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+/* IGNORE means that target does not (no longer) participate
+ * in the search;
+ * NOTREADY means the search on that target has not been initialized yet
+ */
+#define META_MSGID_IGNORE (-1)
+#define META_MSGID_NEED_BIND (-2)
+#define META_MSGID_CONNECTING (-3)
+
+static int
+meta_send_entry(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int i,
+ LDAPMessage *e );
+
+typedef enum meta_search_candidate_t {
+ META_SEARCH_UNDEFINED = -2,
+ META_SEARCH_ERR = -1,
+ META_SEARCH_NOT_CANDIDATE,
+ META_SEARCH_CANDIDATE,
+ META_SEARCH_BINDING,
+ META_SEARCH_NEED_BIND,
+ META_SEARCH_CONNECTING
+} meta_search_candidate_t;
+
+/*
+ * meta_search_dobind_init()
+ *
+ * initiates bind for a candidate target of a search.
+ */
+static meta_search_candidate_t
+meta_search_dobind_init(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ SlapReply *candidates )
+{
+ metaconn_t *mc = *mcp;
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ struct berval binddn = msc->msc_bound_ndn,
+ cred = msc->msc_cred;
+ int method;
+
+ int rc;
+
+ meta_search_candidate_t retcode;
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> meta_search_dobind_init[%d]\n",
+ op->o_log_prefix, candidate );
+
+ /*
+ * all the targets are already bound as pseudoroot
+ */
+ if ( mc->mc_authz_target == META_BOUND_ALL ) {
+ return META_SEARCH_CANDIDATE;
+ }
+
+ retcode = META_SEARCH_BINDING;
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+ /* already bound (or anonymous) */
+
+#ifdef DEBUG_205
+ int bound = 0;
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ bound = 1;
+ }
+
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_search_dobind_init[%d] mc=%p ld=%p%s DN=\"%s\"\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld, bound ? " bound" : " anonymous",
+ bound == 0 ? "" : msc->msc_bound_ndn.bv_val );
+#endif /* DEBUG_205 */
+
+ retcode = META_SEARCH_CANDIDATE;
+
+ } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) {
+ /* another thread is binding the target for this conn; wait */
+
+#ifdef DEBUG_205
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_search_dobind_init[%d] mc=%p ld=%p needbind\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+ retcode = META_SEARCH_NEED_BIND;
+
+ } else {
+ /* we'll need to bind the target for this conn */
+
+#ifdef DEBUG_205
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_search_dobind_init[%d] mc=%p ld=%p binding\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ if ( msc->msc_ld == NULL ) {
+ /* for some reason (e.g. because formerly in "binding"
+ * state, with eventual connection expiration or invalidation)
+ * it was not initialized as expected */
+
+ Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p ld=NULL\n",
+ op->o_log_prefix, candidate, (void *)mc );
+
+ rc = meta_back_init_one_conn( op, rs, *mcp, candidate,
+ LDAP_BACK_CONN_ISPRIV( *mcp ), LDAP_BACK_DONTSEND, 0 );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( msc->msc_ld != NULL );
+ break;
+
+ case LDAP_SERVER_DOWN:
+ case LDAP_UNAVAILABLE:
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ goto down;
+
+ default:
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ goto other;
+ }
+ }
+
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ if ( retcode != META_SEARCH_BINDING ) {
+ return retcode;
+ }
+
+ /* NOTE: this obsoletes pseudorootdn */
+ if ( op->o_conn != NULL &&
+ !op->o_do_not_cache &&
+ ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+ BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+ {
+ rc = meta_back_proxy_authz_cred( mc, candidate, op, rs, LDAP_BACK_DONTSEND, &binddn, &cred, &method );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+ case LDAP_UNAVAILABLE:
+ goto down;
+ default:
+ goto other;
+ }
+
+ /* NOTE: we copy things here, even if bind didn't succeed yet,
+ * because the connection is not shared until bind is over */
+ if ( !BER_BVISNULL( &binddn ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+ if ( META_BACK_TGT_SAVECRED( mt ) && !BER_BVISNULL( &cred ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ }
+ }
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ /* apparently, idassert was configured with SASL bind,
+ * so bind occurred inside meta_back_proxy_authz_cred() */
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ return META_SEARCH_CANDIDATE;
+ }
+
+ /* paranoid */
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+ /* do a simple bind with binddn, cred */
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ assert( msc->msc_ld != NULL );
+
+ /* connect must be async only the first time... */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON );
+
+retry:;
+ if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) {
+ /* bind anonymously? */
+ Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p: "
+ "non-empty dn with empty cred; binding anonymously\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ cred = slap_empty_bv;
+
+ } else if ( BER_BVISEMPTY( &binddn ) && !BER_BVISEMPTY( &cred ) ) {
+ /* error */
+ Debug( LDAP_DEBUG_ANY, "%s meta_search_dobind_init[%d] mc=%p: "
+ "empty dn with non-empty cred: error\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ rc = LDAP_OTHER;
+ goto other;
+ }
+
+ rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred,
+ NULL, NULL, &candidates[ candidate ].sr_msgid );
+
+#ifdef DEBUG_205
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_search_dobind_init[%d] mc=%p ld=%p rc=%d\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)mc->mc_conns[candidate].msc_ld, rc );
+#endif /* DEBUG_205 */
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( candidates[ candidate ].sr_msgid >= 0 );
+ META_BINDING_SET( &candidates[ candidate ] );
+ return META_SEARCH_BINDING;
+
+ case LDAP_X_CONNECTING:
+ /* must retry, same conn */
+ candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING;
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ return META_SEARCH_CONNECTING;
+
+ case LDAP_SERVER_DOWN:
+down:;
+ /* This is the worst thing that could happen:
+ * the search will wait until the retry is over. */
+ if ( !META_IS_RETRYING( &candidates[ candidate ] ) ) {
+ META_RETRYING_SET( &candidates[ candidate ] );
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+
+ assert( mc->mc_refcnt > 0 );
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ /* this lock is required; however,
+ * it's invoked only when logging is on */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_search_dobind_init[%d]: retrying URI=\"%s\" DN=\"%s\".\n",
+ op->o_log_prefix, candidate, mt->mt_uri,
+ BER_BVISNULL(&msc->msc_bound_ndn) ? "" : msc->msc_bound_ndn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ }
+
+ meta_clear_one_candidate( op, mc, candidate );
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+
+ ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn );
+
+ /* mc here must be the regular mc, reset and ready for init */
+ rc = meta_back_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+ if ( rc == LDAP_SUCCESS ) {
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ if ( rc == LDAP_SUCCESS ) {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ binddn = msc->msc_bound_ndn;
+ cred = msc->msc_cred;
+ goto retry;
+ }
+ }
+
+ if ( *mcp == NULL ) {
+ retcode = META_SEARCH_ERR;
+ rc = LDAP_UNAVAILABLE;
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ break;
+ }
+ /* fall thru */
+
+ default:
+other:;
+ /* convert rc to the correct LDAP error and send it back to the client:
+ assign the error to rs, so we can use it as argument to slap_map_api2result
+ and then assign the output back to rs->sr_err */
+ rs->sr_err = rc;
+ rs->sr_err = slap_map_api2result( rs );
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ meta_clear_one_candidate( op, mc, candidate );
+ candidates[ candidate ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ meta_back_release_conn_lock( mi, mc, 0 );
+ *mcp = NULL;
+
+ retcode = META_SEARCH_ERR;
+
+ } else {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ break;
+ }
+
+ return retcode;
+}
+
+static meta_search_candidate_t
+meta_search_dobind_result(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t **mcp,
+ int candidate,
+ SlapReply *candidates,
+ LDAPMessage *res )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metaconn_t *mc = *mcp;
+ metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE;
+ int rc;
+
+ assert( msc->msc_ld != NULL );
+
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ld, res,
+ &candidates[ candidate ].sr_err,
+ NULL, NULL, NULL, NULL, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ candidates[ candidate ].sr_err = rc;
+ }
+ rc = slap_map_api2result( &candidates[ candidate ] );
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ if ( rc != LDAP_SUCCESS ) {
+ meta_clear_one_candidate( op, mc, candidate );
+ candidates[ candidate ].sr_err = rc;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ meta_back_release_conn_lock( mi, mc, 0 );
+ *mcp = NULL;
+ retcode = META_SEARCH_ERR;
+ rs->sr_err = rc;
+ }
+
+ } else {
+ /* FIXME: check if bound as idassert authcDN! */
+ if ( BER_BVISNULL( &msc->msc_bound_ndn )
+ || BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( msc );
+
+ } else {
+ if ( META_BACK_TGT_SAVECRED( mt ) &&
+ !BER_BVISNULL( &msc->msc_cred ) &&
+ !BER_BVISEMPTY( &msc->msc_cred ) )
+ {
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+ retcode = META_SEARCH_CANDIDATE;
+
+ /* connect must be async */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_OFF );
+ }
+
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ META_BINDING_CLEAR( &candidates[ candidate ] );
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ return retcode;
+}
+
+static meta_search_candidate_t
+meta_back_search_start(
+ Operation *op,
+ SlapReply *rs,
+ dncookie *dc,
+ metaconn_t **mcp,
+ int candidate,
+ SlapReply *candidates,
+ struct berval *prcookie,
+ ber_int_t prsize )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metatarget_t *mt = mi->mi_targets[ candidate ];
+ metasingleconn_t *msc = &(*mcp)->mc_conns[ candidate ];
+ struct berval realbase = op->o_req_dn;
+ int realscope = op->ors_scope;
+ struct berval mbase = BER_BVNULL;
+ struct berval mfilter = BER_BVNULL;
+ char **mapped_attrs = NULL;
+ int rc;
+ meta_search_candidate_t retcode;
+ struct timeval tv, *tvp = NULL;
+ int nretries = 1;
+ LDAPControl **ctrls = NULL;
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl **save_ctrls = NULL;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ /* this should not happen; just in case... */
+ if ( msc->msc_ld == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: meta_back_search_start candidate=%d ld=NULL%s.\n",
+ op->o_log_prefix, candidate,
+ META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" );
+ candidates[ candidate ].sr_err = LDAP_OTHER;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ return META_SEARCH_ERR;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ return META_SEARCH_NOT_CANDIDATE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> meta_back_search_start[%d]\n", op->o_log_prefix, candidate );
+
+ /*
+ * modifies the base according to the scope, if required
+ */
+ if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) {
+ switch ( op->ors_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ /*
+ * make the target suffix the new base
+ * FIXME: this is very forgiving, because
+ * "illegal" searchBases may be turned
+ * into the suffix of the target; however,
+ * the requested searchBase already passed
+ * thru the candidate analyzer...
+ */
+ if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) {
+ realbase = mt->mt_nsuffix;
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ }
+
+ } else {
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_ONELEVEL:
+ {
+ struct berval rdn = mt->mt_nsuffix;
+ rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn )
+ && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) )
+ {
+ /*
+ * if there is exactly one level,
+ * make the target suffix the new
+ * base, and make scope "base"
+ */
+ realbase = mt->mt_nsuffix;
+ if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) {
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ } else {
+ realscope = LDAP_SCOPE_SUBTREE;
+ }
+ } else {
+ realscope = LDAP_SCOPE_BASE;
+ }
+ break;
+ } /* else continue with the next case */
+ }
+
+ case LDAP_SCOPE_BASE:
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /* check filter expression */
+ if ( mt->mt_filter ) {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) {
+ if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 )
+ break;
+ }
+ /* nothing matched, this target is no longer a candidate */
+ if ( !mf ) {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /* initiate dobind */
+ retcode = meta_search_dobind_init( op, rs, mcp, candidate, candidates );
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%d]=%d\n", op->o_log_prefix, candidate, retcode );
+
+ if ( retcode != META_SEARCH_CANDIDATE ) {
+ goto doreturn;
+ }
+
+ /*
+ * Rewrite the search base, if required
+ */
+ dc->target = mt;
+ dc->ctx = "searchBase";
+ switch ( ldap_back_dn_massage( dc, &realbase, &mbase ) ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_UNWILLING_TO_PERFORM:
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Operation not allowed";
+ send_ldap_result( op, rs );
+ retcode = META_SEARCH_ERR;
+ goto doreturn;
+
+ default:
+
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+
+ /*
+ * Maps filter
+ */
+ rc = ldap_back_filter_map_rewrite( dc, op->ors_filter,
+ &mfilter, BACKLDAP_MAP, op->o_tmpmemctx );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_COMPARE_FALSE:
+ default:
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+
+ /*
+ * Maps required attributes
+ */
+ rc = ldap_back_map_attrs( op, &mt->mt_rwmap.rwm_at,
+ op->ors_attrs, BACKLDAP_MAP, &mapped_attrs );
+ if ( rc != LDAP_SUCCESS ) {
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ tv.tv_sec = op->ors_tlimit > 0 ? op->ors_tlimit : 1;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ }
+
+#ifdef SLAPD_META_CLIENT_PR
+ save_ctrls = op->o_ctrls;
+ {
+ LDAPControl *pr_c = NULL;
+ int i = 0, nc = 0;
+
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[i] != NULL; i++ );
+ nc = i;
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL );
+ }
+
+ if ( pr_c != NULL ) nc--;
+ if ( mt->mt_ps > 0 || prcookie != NULL ) nc++;
+
+ if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) {
+ int src = 0, dst = 0;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval val = BER_BVNULL;
+ ber_len_t len;
+
+ len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl );
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ struct berval nullcookie = BER_BVNULL;
+ ber_tag_t tag;
+
+ if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps;
+ if ( prcookie == NULL ) prcookie = &nullcookie;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ tag = ber_printf( ber, "{iO}", prsize, prcookie );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ tag = ber_flatten2( ber, &val, 0 );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ len += val.bv_len + 1;
+ }
+
+ op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx );
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[ src ] != NULL; src++ ) {
+ if ( save_ctrls[ src ] != pr_c ) {
+ op->o_ctrls[ dst ] = save_ctrls[ src ];
+ dst++;
+ }
+ }
+ }
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ];
+
+ op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ op->o_ctrls[ dst ]->ldctl_iscritical = 1;
+
+ op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ];
+ AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 );
+ op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len;
+ dst++;
+
+ (void)ber_free_buf( ber );
+ }
+
+ op->o_ctrls[ dst ] = NULL;
+ }
+done_pr:;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+retry:;
+ ctrls = op->o_ctrls;
+ if ( meta_back_controls_add( op, rs, *mcp, candidate, &ctrls )
+ != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+
+ /*
+ * Starts the search
+ */
+ assert( msc->msc_ld != NULL );
+ rc = ldap_pvt_search( msc->msc_ld,
+ mbase.bv_val, realscope, mfilter.bv_val,
+ mapped_attrs, op->ors_attrsonly,
+ ctrls, NULL, tvp, op->ors_slimit, op->ors_deref,
+ &candidates[ candidate ].sr_msgid );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ break;
+
+ case LDAP_SERVER_DOWN:
+ if ( nretries && meta_back_retry( op, rs, mcp, candidate, LDAP_BACK_DONTSEND ) ) {
+ nretries = 0;
+ /* if the identity changed, there might be need to re-authz */
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+ goto retry;
+ }
+
+ if ( *mcp == NULL ) {
+ retcode = META_SEARCH_ERR;
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ break;
+ }
+ /* fall thru */
+
+ default:
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+
+done:;
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+#ifdef SLAPD_META_CLIENT_PR
+ if ( save_ctrls != op->o_ctrls ) {
+ op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+ op->o_ctrls = save_ctrls;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ if ( mapped_attrs ) {
+ ber_memfree_x( mapped_attrs, op->o_tmpmemctx );
+ }
+ if ( mfilter.bv_val != op->ors_filterstr.bv_val ) {
+ ber_memfree_x( mfilter.bv_val, op->o_tmpmemctx );
+ }
+ if ( mbase.bv_val != realbase.bv_val ) {
+ free( mbase.bv_val );
+ }
+
+doreturn:;
+ Debug( LDAP_DEBUG_TRACE, "%s <<< meta_back_search_start[%d]=%d\n", op->o_log_prefix, candidate, retcode );
+
+ return retcode;
+}
+
+int
+meta_back_search( Operation *op, SlapReply *rs )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ metaconn_t *mc;
+ struct timeval save_tv = { 0, 0 },
+ tv;
+ time_t stoptime = (time_t)(-1),
+ lastres_time = slap_get_time(),
+ timeout = 0;
+ int rc = 0, sres = LDAP_SUCCESS;
+ char *matched = NULL;
+ int last = 0, ncandidates = 0,
+ initial_candidates = 0, candidate_match = 0,
+ needbind = 0;
+ ldap_back_send_t sendok = LDAP_BACK_SENDERR;
+ long i;
+ dncookie dc;
+ int is_ok = 0;
+ void *savepriv;
+ SlapReply *candidates = NULL;
+ int do_taint = 0;
+
+ rs_assert_ready( rs );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */
+
+ /*
+ * controls are set in ldap_back_dobind()
+ *
+ * FIXME: in case of values return filter, we might want
+ * to map attrs and maybe rewrite value
+ */
+getconn:;
+ mc = meta_back_getconn( op, rs, NULL, sendok );
+ if ( !mc ) {
+ return rs->sr_err;
+ }
+
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+
+ if ( candidates == NULL ) candidates = meta_back_candidates_get( op );
+ /*
+ * Inits searches
+ */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /* reset sr_msgid; it is used in most loops
+ * to check if that target is still to be considered */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+
+ /* a target is marked as candidate by meta_back_getconn();
+ * if for any reason (an error, it's over or so) it is
+ * no longer active, sr_msgid is set to META_MSGID_IGNORE
+ * but it remains candidate, which means it has been active
+ * at some point during the operation. This allows to
+ * use its response code and more to compute the final
+ * response */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ candidates[ i ].sr_matched = NULL;
+ candidates[ i ].sr_text = NULL;
+ candidates[ i ].sr_ref = NULL;
+ candidates[ i ].sr_ctrls = NULL;
+ candidates[ i ].sr_nentries = 0;
+
+ /* get largest timeout among candidates */
+ if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]
+ && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout )
+ {
+ timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ];
+ }
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] )
+ || candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ continue;
+ }
+
+ switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
+ {
+ case META_SEARCH_NOT_CANDIDATE:
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ break;
+
+ case META_SEARCH_NEED_BIND:
+ ++needbind;
+ /* fallthru */
+
+ case META_SEARCH_CONNECTING:
+ case META_SEARCH_CANDIDATE:
+ case META_SEARCH_BINDING:
+ candidates[ i ].sr_type = REP_INTERMEDIATE;
+ ++ncandidates;
+ break;
+
+ case META_SEARCH_ERR:
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ rc = -1;
+ goto finish;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ if ( ncandidates > 0 && needbind == ncandidates ) {
+ /*
+ * give up the second time...
+ *
+ * NOTE: this should not occur the second time, since a fresh
+ * connection has ben created; however, targets may also
+ * need bind because the bind timed out or so.
+ */
+ if ( sendok & LDAP_BACK_BINDING ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_search: unable to initialize conn\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "unable to initialize connection to remote targets";
+ send_ldap_result( op, rs );
+ rc = -1;
+ goto finish;
+ }
+
+ /* FIXME: better create a separate connection? */
+ sendok |= LDAP_BACK_BINDING;
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "*** %s drop mc=%p create new connection\n",
+ op->o_log_prefix, (void *)mc );
+#endif /* DEBUG_205 */
+
+ meta_back_release_conn( mi, mc );
+ mc = NULL;
+
+ needbind = 0;
+ ncandidates = 0;
+
+ goto getconn;
+ }
+
+ initial_candidates = ncandidates;
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char cnd[ SLAP_TEXT_BUFLEN ];
+ int c;
+
+ for ( c = 0; c < mi->mi_ntargets; c++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ c ] ) ) {
+ cnd[ c ] = '*';
+ } else {
+ cnd[ c ] = ' ';
+ }
+ }
+ cnd[ c ] = '\0';
+
+ Debug( LDAP_DEBUG_TRACE, "%s meta_back_search: ncandidates=%d "
+ "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd );
+ }
+
+ if ( initial_candidates == 0 ) {
+ /* NOTE: here we are not sending any matchedDN;
+ * this is intended, because if the back-meta
+ * is serving this search request, but no valid
+ * candidate could be looked up, it means that
+ * there is a hole in the mapping of the targets
+ * and thus no knowledge of any remote superior
+ * is available */
+ Debug( LDAP_DEBUG_ANY, "%s meta_back_search: "
+ "base=\"%s\" scope=%d: "
+ "no candidate could be selected\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ op->ors_scope );
+
+ /* FIXME: we're sending the first error we encounter;
+ * maybe we should pick the worst... */
+ rc = LDAP_NO_SUCH_OBJECT;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ rc = candidates[ i ].sr_err;
+ break;
+ }
+ }
+
+ send_ldap_error( op, rs, rc, NULL );
+
+ goto finish;
+ }
+
+ /* We pull apart the ber result, stuff it into a slapd entry, and
+ * let send_search_entry stuff it back into ber format. Slow & ugly,
+ * but this is necessary for version matching, and for ACL processing.
+ */
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ stoptime = op->o_time + op->ors_tlimit;
+ }
+
+ /*
+ * In case there are no candidates, no cycle takes place...
+ *
+ * FIXME: we might use a queue, to better balance the load
+ * among the candidates
+ */
+ for ( rc = 0; ncandidates > 0; ) {
+ int gotit = 0,
+ doabandon = 0,
+ alreadybound = ncandidates;
+
+ /* check timeout */
+ if ( timeout && lastres_time > 0
+ && ( slap_get_time() - lastres_time ) > timeout )
+ {
+ doabandon = 1;
+ rs->sr_text = "Operation timed out";
+ rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ doabandon = 1;
+ rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ meta_search_candidate_t retcode = META_SEARCH_UNDEFINED;
+ metasingleconn_t *msc = &mc->mc_conns[ i ];
+ LDAPMessage *res = NULL, *msg;
+
+ /* if msgid is invalid, don't ldap_result() */
+ if ( candidates[ i ].sr_msgid == META_MSGID_IGNORE ) {
+ continue;
+ }
+
+ /* if target still needs bind, retry */
+ if ( candidates[ i ].sr_msgid == META_MSGID_NEED_BIND
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ /* initiate dobind */
+ retcode = meta_search_dobind_init( op, rs, &mc, i, candidates );
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%ld]=%d\n",
+ op->o_log_prefix, i, retcode );
+
+ switch ( retcode ) {
+ case META_SEARCH_NEED_BIND:
+ alreadybound--;
+ /* fallthru */
+
+ case META_SEARCH_CONNECTING:
+ case META_SEARCH_BINDING:
+ break;
+
+ case META_SEARCH_ERR:
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+ /* fallthru */
+
+ case META_SEARCH_NOT_CANDIDATE:
+ /*
+ * When no candidates are left,
+ * the outer cycle finishes
+ */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+ break;
+
+ case META_SEARCH_CANDIDATE:
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
+ {
+ case META_SEARCH_CANDIDATE:
+ assert( candidates[ i ].sr_msgid >= 0 );
+ break;
+
+ case META_SEARCH_ERR:
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+ /* fallthru */
+
+ case META_SEARCH_NOT_CANDIDATE:
+ /* means that meta_back_search_start()
+ * failed but onerr == continue */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+ break;
+
+ default:
+ /* impossible */
+ assert( 0 );
+ break;
+ }
+ break;
+
+ default:
+ /* impossible */
+ assert( 0 );
+ break;
+ }
+ continue;
+ }
+
+ /* check for abandon */
+ if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) {
+ break;
+ }
+
+#ifdef DEBUG_205
+ if ( msc->msc_ld == NULL ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ Debug(LDAP_DEBUG_ANY,
+ "!!! %s meta_back_search[%ld] mc=%p msgid=%d%s%s%s\n\n",
+ op->o_log_prefix, (long)i, (void *)mc,
+ candidates[i].sr_msgid,
+ META_IS_BINDING(&candidates[i]) ? " binding" : "",
+ LDAP_BACK_CONN_BINDING(&mc->mc_conns[i]) ? " connbinding" : "",
+ META_BACK_CONN_CREATING(&mc->mc_conns[i]) ? " conncreating" : "" );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+#endif /* DEBUG_205 */
+
+ /*
+ * FIXME: handle time limit as well?
+ * Note that target servers are likely
+ * to handle it, so at some time we'll
+ * get a LDAP_TIMELIMIT_EXCEEDED from
+ * one of them ...
+ */
+ tv = save_tv;
+ rc = ldap_result( msc->msc_ld, candidates[ i ].sr_msgid,
+ LDAP_MSG_RECEIVED, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ /* FIXME: res should not need to be freed */
+ assert( res == NULL );
+ continue;
+
+ case -1:
+really_bad:;
+ /* something REALLY bad happened! */
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ candidates[ i ].sr_type = REP_RESULT;
+
+ if ( meta_back_retry( op, rs, &mc, i, LDAP_BACK_DONTSEND ) ) {
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) )
+ {
+ /* means that failed but onerr == continue */
+ case META_SEARCH_NOT_CANDIDATE:
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+
+ assert( ncandidates > 0 );
+ --ncandidates;
+
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+ /* fall thru */
+
+ case META_SEARCH_CANDIDATE:
+ /* get back into business... */
+ continue;
+
+ case META_SEARCH_BINDING:
+ case META_SEARCH_CONNECTING:
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_UNDEFINED:
+ assert( 0 );
+
+ default:
+ /* unrecoverable error */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ rc = rs->sr_err = LDAP_OTHER;
+ goto finish;
+ }
+ }
+
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ goto finish;
+ }
+ }
+
+ /*
+ * When no candidates are left,
+ * the outer cycle finishes
+ */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+ rs->sr_err = candidates[ i ].sr_err;
+ continue;
+
+ default:
+ lastres_time = slap_get_time();
+
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < lastres_time ) {
+ msc->msc_time = lastres_time;
+ }
+ break;
+ }
+
+ for ( msg = ldap_first_message( msc->msc_ld, res );
+ msg != NULL;
+ msg = ldap_next_message( msc->msc_ld, msg ) )
+ {
+ rc = ldap_msgtype( msg );
+ if ( rc == LDAP_RES_SEARCH_ENTRY ) {
+ LDAPMessage *e;
+
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+
+ /* count entries returned by target */
+ candidates[ i ].sr_nentries++;
+
+ is_ok++;
+
+ e = ldap_first_entry( msc->msc_ld, msg );
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ rs->sr_err = meta_send_entry( op, rs, mc, i, e );
+
+ switch ( rs->sr_err ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ rs->sr_err = LDAP_SUCCESS;
+ ldap_msgfree( res );
+ res = NULL;
+ goto finish;
+
+ case LDAP_UNAVAILABLE:
+ rs->sr_err = LDAP_OTHER;
+ ldap_msgfree( res );
+ res = NULL;
+ goto finish;
+ }
+ op->o_private = savepriv;
+
+ /* don't wait any longer... */
+ gotit = 1;
+ save_tv.tv_sec = 0;
+ save_tv.tv_usec = 0;
+
+ } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) {
+ char **references = NULL;
+ int cnt;
+
+ if ( META_BACK_TGT_NOREFS( mi->mi_targets[ i ] ) ) {
+ continue;
+ }
+
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+
+ is_ok++;
+
+ rc = ldap_parse_reference( msc->msc_ld, msg,
+ &references, &rs->sr_ctrls, 0 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ if ( references == NULL ) {
+ continue;
+ }
+
+ dc.ctx = "referralDN";
+
+ /* FIXME: merge all and return at the end */
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &rs->sr_ref[ cnt ] );
+
+ ( void )ldap_back_referral_result_rewrite( &dc, rs->sr_ref,
+ op->o_tmpmemctx );
+
+ if ( rs->sr_ref != NULL && !BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) {
+ /* ignore return value by now */
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ ( void )send_search_reference( op, rs );
+ op->o_private = savepriv;
+
+ ber_bvarray_free_x( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+
+ /* cleanup */
+ if ( references ) {
+ ber_memvfree( (void **)references );
+ }
+
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+
+ } else if ( rc == LDAP_RES_INTERMEDIATE ) {
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+
+ /* FIXME: response controls
+ * are passed without checks */
+ rs->sr_err = ldap_parse_intermediate( msc->msc_ld,
+ msg,
+ (char **)&rs->sr_rspoid,
+ &rs->sr_rspdata,
+ &rs->sr_ctrls,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_type = REP_RESULT;
+ ldap_msgfree( res );
+ res = NULL;
+ goto really_bad;
+ }
+
+ slap_send_ldap_intermediate( op, rs );
+
+ if ( rs->sr_rspoid != NULL ) {
+ ber_memfree( (char *)rs->sr_rspoid );
+ rs->sr_rspoid = NULL;
+ }
+
+ if ( rs->sr_rspdata != NULL ) {
+ ber_bvfree( rs->sr_rspdata );
+ rs->sr_rspdata = NULL;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+
+ } else if ( rc == LDAP_RES_SEARCH_RESULT ) {
+ char **references = NULL;
+ LDAPControl **ctrls = NULL;
+
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+
+ /* NOTE: ignores response controls
+ * (and intermediate response controls
+ * as well, except for those with search
+ * references); this may not be correct,
+ * but if they're not ignored then
+ * back-meta would need to merge them
+ * consistently (think of pagedResults...)
+ */
+ /* FIXME: response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ld,
+ msg,
+ &candidates[ i ].sr_err,
+ (char **)&candidates[ i ].sr_matched,
+ (char **)&candidates[ i ].sr_text,
+ &references,
+ &ctrls /* &candidates[ i ].sr_ctrls (unused) */ ,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_err = rs->sr_err;
+ sres = slap_map_api2result( &candidates[ i ] );
+ candidates[ i ].sr_type = REP_RESULT;
+ ldap_msgfree( res );
+ res = NULL;
+ goto really_bad;
+ }
+
+ rs->sr_err = candidates[ i ].sr_err;
+
+ /* massage matchedDN if need be */
+ if ( candidates[ i ].sr_matched != NULL ) {
+ struct berval match, mmatch;
+
+ ber_str2bv( candidates[ i ].sr_matched,
+ 0, 0, &match );
+ candidates[ i ].sr_matched = NULL;
+
+ dc.ctx = "matchedDN";
+ dc.target = mi->mi_targets[ i ];
+ if ( !ldap_back_dn_massage( &dc, &match, &mmatch ) ) {
+ if ( mmatch.bv_val == match.bv_val ) {
+ candidates[ i ].sr_matched
+ = ch_strdup( mmatch.bv_val );
+
+ } else {
+ candidates[ i ].sr_matched = mmatch.bv_val;
+ }
+
+ candidate_match++;
+ }
+ ldap_memfree( match.bv_val );
+ }
+
+ /* add references to array */
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( references != NULL
+ && references[ 0 ] != NULL
+ && references[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_search[%ld]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ } else {
+ BerVarray sr_ref;
+ int cnt;
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &sr_ref[ cnt ] );
+
+ ( void )ldap_back_referral_result_rewrite( &dc, sr_ref,
+ op->o_tmpmemctx );
+
+ if ( rs->sr_v2ref == NULL ) {
+ rs->sr_v2ref = sr_ref;
+
+ } else {
+ for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) {
+ ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ ber_memfree_x( sr_ref, op->o_tmpmemctx );
+ }
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_search[%ld]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ /* cleanup */
+ ber_memvfree( (void **)references );
+
+ sres = slap_map_api2result( rs );
+
+ if ( LogTest( LDAP_DEBUG_TRACE | LDAP_DEBUG_ANY ) ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ),
+ "%s meta_back_search[%ld] "
+ "match=\"%s\" err=%ld",
+ op->o_log_prefix, i,
+ candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+ (long) candidates[ i ].sr_err );
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s.\n", buf );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s (%s) text=\"%s\".\n",
+ buf, ldap_err2string( candidates[ i ].sr_err ),
+ candidates[ i ].sr_text ? candidates[i].sr_text : "" );
+ }
+ }
+
+ switch ( sres ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* is_ok is touched any time a valid
+ * (even intermediate) result is
+ * returned; as a consequence, if
+ * a candidate returns noSuchObject
+ * it is ignored and the candidate
+ * is simply demoted. */
+ if ( is_ok ) {
+ sres = LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_SUCCESS:
+ if ( ctrls != NULL && ctrls[0] != NULL ) {
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl *pr_c;
+
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL );
+ if ( pr_c != NULL ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_int_t prsize;
+ struct berval prcookie;
+
+ /* unsolicited, do not accept */
+ if ( mi->mi_targets[i]->mt_ps == 0 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER );
+
+ tag = ber_scanf( ber, "{im}", &prsize, &prcookie );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ /* more pages? new search request */
+ if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) {
+ if ( mi->mi_targets[i]->mt_ps > 0 ) {
+ /* ignore size if specified */
+ prsize = 0;
+
+ } else if ( prsize == 0 ) {
+ /* guess the page size from the entries returned so far */
+ prsize = candidates[ i ].sr_nentries;
+ }
+
+ candidates[ i ].sr_nentries = 0;
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_INTERMEDIATE;
+
+ assert( candidates[ i ].sr_matched == NULL );
+ assert( candidates[ i ].sr_text == NULL );
+ assert( candidates[ i ].sr_ref == NULL );
+
+ switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, &prcookie, prsize ) )
+ {
+ case META_SEARCH_CANDIDATE:
+ assert( candidates[ i ].sr_msgid >= 0 );
+ ldap_controls_free( ctrls );
+ goto free_message;
+
+ case META_SEARCH_ERR:
+err_pr:;
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ ldap_controls_free( ctrls );
+ goto finish;
+ }
+ /* fallthru */
+
+ case META_SEARCH_NOT_CANDIDATE:
+ /* means that meta_back_search_start()
+ * failed but onerr == continue */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+ break;
+
+ default:
+ /* impossible */
+ assert( 0 );
+ break;
+ }
+ break;
+ }
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+ }
+ /* fallthru */
+
+ case LDAP_REFERRAL:
+ is_ok++;
+ break;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ /* if a target returned sizelimitExceeded
+ * and the entry count is equal to the
+ * proxy's limit, the target would have
+ * returned more, and the error must be
+ * propagated to the client; otherwise,
+ * the target enforced a limit lower
+ * than what requested by the proxy;
+ * ignore it */
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( rs->sr_nentries == op->ors_slimit
+ || META_BACK_ONERR_STOP( mi ) )
+ {
+ const char *save_text;
+got_err:
+ save_text = rs->sr_text;
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ rs->sr_text = candidates[ i ].sr_text;
+ send_ldap_result( op, rs );
+ rs->sr_text = save_text;
+ op->o_private = savepriv;
+ ldap_msgfree( res );
+ res = NULL;
+ ldap_controls_free( ctrls );
+ goto finish;
+ }
+ break;
+
+ default:
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) )
+ goto got_err;
+ break;
+ }
+
+ ldap_controls_free( ctrls );
+ last = i;
+ rc = 0;
+
+ /*
+ * When no candidates are left,
+ * the outer cycle finishes
+ */
+ assert( ncandidates > 0 );
+ --ncandidates;
+
+ } else if ( rc == LDAP_RES_BIND ) {
+ meta_search_candidate_t retcode;
+
+ retcode = meta_search_dobind_result( op, rs, &mc, i, candidates, msg );
+ if ( retcode == META_SEARCH_CANDIDATE ) {
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ retcode = meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 );
+ }
+
+ switch ( retcode ) {
+ case META_SEARCH_CANDIDATE:
+ break;
+
+ /* means that failed but onerr == continue */
+ case META_SEARCH_NOT_CANDIDATE:
+ case META_SEARCH_ERR:
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ savepriv = op->o_private;
+ op->o_private = (void *)i;
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ ldap_msgfree( res );
+ res = NULL;
+ goto finish;
+ }
+ goto free_message;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_back_search[%ld]: "
+ "unrecognized response message tag=%d\n",
+ op->o_log_prefix,
+ i, rc );
+
+ ldap_msgfree( res );
+ res = NULL;
+ goto really_bad;
+ }
+ }
+
+free_message:;
+ ldap_msgfree( res );
+ res = NULL;
+ }
+
+ /* check for abandon */
+ if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( candidates[ i ].sr_msgid >= 0
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ if ( META_IS_BINDING( &candidates[ i ] )
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] )
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ /* if still binding, destroy */
+
+#ifdef DEBUG_205
+ Debug(LDAP_DEBUG_ANY,
+ "### %s meta_back_search(abandon) " "ldap_unbind_ext[%ld] mc=%p ld=%p\n",
+ op->o_log_prefix,
+ i, (void *)mc,
+ (void *)mc->mc_conns[i].msc_ld );
+#endif /* DEBUG_205 */
+
+ meta_clear_one_candidate( op, mc, i );
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ META_BINDING_CLEAR( &candidates[ i ] );
+
+ } else {
+ (void)meta_back_cancel( mc, op, rs,
+ candidates[ i ].sr_msgid, i,
+ LDAP_BACK_DONTSEND );
+ }
+
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ assert( ncandidates > 0 );
+ --ncandidates;
+ }
+ }
+
+ if ( op->o_abandon ) {
+ rc = SLAPD_ABANDON;
+ }
+
+ /* let send_ldap_result play cleanup handlers (ITS#4645) */
+ break;
+ }
+
+ /* if no entry was found during this loop,
+ * set a minimal timeout */
+ if ( ncandidates > 0 && gotit == 0 ) {
+ if ( save_tv.tv_sec == 0 && save_tv.tv_usec == 0 ) {
+ save_tv.tv_usec = LDAP_BACK_RESULT_UTIMEOUT/initial_candidates;
+
+ /* arbitrarily limit to something between 1 and 2 minutes */
+ } else if ( ( stoptime == -1 && save_tv.tv_sec < 60 )
+ || save_tv.tv_sec < ( stoptime - slap_get_time() ) / ( 2 * ncandidates ) )
+ {
+ /* double the timeout */
+ lutil_timermul( &save_tv, 2, &save_tv );
+ }
+
+ if ( alreadybound == 0 ) {
+ tv = save_tv;
+ (void)select( 0, NULL, NULL, NULL, &tv );
+
+ } else {
+ ldap_pvt_thread_yield();
+ }
+ }
+ }
+
+ if ( rc == -1 ) {
+ /*
+ * FIXME: need a better strategy to handle errors
+ */
+ if ( mc ) {
+ rc = meta_back_op_result( mc, op, rs, META_TARGET_NONE,
+ -1, stoptime != -1 ? (stoptime - slap_get_time()) : 0,
+ LDAP_BACK_SENDERR );
+ } else {
+ rc = rs->sr_err;
+ }
+ goto finish;
+ }
+
+ /*
+ * Rewrite the matched portion of the search base, if required
+ *
+ * FIXME: only the last one gets caught!
+ */
+ savepriv = op->o_private;
+ op->o_private = (void *)(long)mi->mi_ntargets;
+ if ( candidate_match > 0 ) {
+ struct berval pmatched = BER_BVNULL;
+
+ /* we use the first one */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_matched != NULL )
+ {
+ struct berval bv, pbv;
+ int rc;
+
+ /* if we got success, and this target
+ * returned noSuchObject, and its suffix
+ * is a superior of the searchBase,
+ * ignore the matchedDN */
+ if ( sres == LDAP_SUCCESS
+ && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT
+ && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len )
+ {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ continue;
+ }
+
+ ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv );
+ rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx );
+
+ if ( rc == LDAP_SUCCESS ) {
+
+ /* NOTE: if they all are superiors
+ * of the baseDN, the shorter is also
+ * superior of the longer... */
+ if ( pbv.bv_len > pmatched.bv_len ) {
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx );
+ }
+ pmatched = pbv;
+ op->o_private = (void *)i;
+
+ } else {
+ op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( candidates[ i ].sr_matched != NULL ) {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ matched = pmatched.bv_val;
+ }
+
+ } else if ( sres == LDAP_NO_SUCH_OBJECT ) {
+ matched = op->o_bd->be_suffix[ 0 ].bv_val;
+ }
+
+ /*
+ * In case we returned at least one entry, we return LDAP_SUCCESS
+ * otherwise, the latter error code we got
+ */
+
+ if ( sres == LDAP_SUCCESS ) {
+ if ( rs->sr_v2ref ) {
+ sres = LDAP_REFERRAL;
+ }
+
+ if ( META_BACK_ONERR_REPORT( mi ) ) {
+ /*
+ * Report errors, if any
+ *
+ * FIXME: we should handle error codes and return the more
+ * important/reasonable
+ */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( candidates[ i ].sr_err != LDAP_SUCCESS
+ && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT )
+ {
+ sres = candidates[ i ].sr_err;
+ break;
+ }
+ }
+ }
+ }
+
+ rs->sr_err = sres;
+ rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched );
+ rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL );
+ send_ldap_result( op, rs );
+ op->o_private = savepriv;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+
+finish:;
+ if ( matched && matched != op->o_bd->be_suffix[ 0 ].bv_val ) {
+ op->o_tmpfree( matched, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_v2ref ) {
+ ber_bvarray_free_x( rs->sr_v2ref, op->o_tmpmemctx );
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( mc ) {
+ if ( META_IS_BINDING( &candidates[ i ] )
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] )
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+ {
+ assert( candidates[ i ].sr_msgid >= 0
+ || candidates[ i ].sr_msgid == META_MSGID_CONNECTING );
+ assert( mc->mc_conns[ i ].msc_ld != NULL );
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s meta_back_search(cleanup) "
+ "ldap_unbind_ext[%ld] ld=%p\n",
+ op->o_log_prefix, i, (void *)mc->mc_conns[i].msc_ld );
+#endif /* DEBUG_205 */
+
+ /* if still binding, destroy */
+ meta_clear_one_candidate( op, mc, i );
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ META_BINDING_CLEAR( &candidates[ i ] );
+
+ } else if ( candidates[ i ].sr_msgid >= 0 ) {
+ (void)meta_back_cancel( mc, op, rs,
+ candidates[ i ].sr_msgid, i,
+ LDAP_BACK_DONTSEND );
+ }
+ }
+
+ if ( candidates[ i ].sr_matched ) {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ }
+
+ if ( candidates[ i ].sr_text ) {
+ ldap_memfree( (char *)candidates[ i ].sr_text );
+ candidates[ i ].sr_text = NULL;
+ }
+
+ if ( candidates[ i ].sr_ref ) {
+ ber_bvarray_free( candidates[ i ].sr_ref );
+ candidates[ i ].sr_ref = NULL;
+ }
+
+ if ( candidates[ i ].sr_ctrls ) {
+ ldap_controls_free( candidates[ i ].sr_ctrls );
+ candidates[ i ].sr_ctrls = NULL;
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) {
+ meta_back_quarantine( op, &candidates[ i ], i );
+ }
+
+ /* only in case of timelimit exceeded, if the timelimit exceeded because
+ * one contacted target never responded, invalidate the connection
+ * NOTE: should we quarantine the target as well? right now, the connection
+ * is invalidated; the next time it will be recreated and the target
+ * will be quarantined if it cannot be contacted */
+ if ( mi->mi_idle_timeout != 0
+ && rs->sr_err == LDAP_TIMELIMIT_EXCEEDED
+ && op->o_time > mc->mc_conns[ i ].msc_time )
+ {
+ /* don't let anyone else use this expired connection */
+ do_taint++;
+ }
+ }
+
+ if ( mc ) {
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+ if ( do_taint ) {
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+ }
+ meta_back_release_conn_lock( mi, mc, 0 );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+meta_send_entry(
+ Operation *op,
+ SlapReply *rs,
+ metaconn_t *mc,
+ int target,
+ LDAPMessage *e )
+{
+ metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private;
+ struct berval a, mapped;
+ int check_duplicate_attrs = 0;
+ int check_sorted_attrs = 0;
+ Entry ent = { 0 };
+ BerElement ber = *ldap_get_message_ber( e );
+ Attribute *attr, **attrp;
+ struct berval bdn,
+ dn = BER_BVNULL;
+ const char *text;
+ dncookie dc;
+ ber_len_t len;
+ int rc;
+
+ if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) {
+ return LDAP_OTHER;
+ }
+
+ if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ /*
+ * Rewrite the dn of the result, if needed
+ */
+ dc.target = mi->mi_targets[ target ];
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "searchResult";
+
+ rs->sr_err = ldap_back_dn_massage( &dc, &bdn, &dn );
+ if ( rs->sr_err != LDAP_SUCCESS) {
+ return rs->sr_err;
+ }
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ *
+ * FIXME: should we log anything, or delegate to dnNormalize?
+ */
+ rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname,
+ op->o_tmpmemctx );
+ if ( dn.bv_val != bdn.bv_val ) {
+ free( dn.bv_val );
+ }
+ BER_BVZERO( &dn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_send_entry(\"%s\"): "
+ "invalid DN syntax\n",
+ op->o_log_prefix, ent.e_name.bv_val );
+ rc = LDAP_INVALID_DN_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * cache dn
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ ( void )meta_dncache_update_entry( &mi->mi_cache,
+ &ent.e_nname, target );
+ }
+
+ attrp = &ent.e_attrs;
+
+ dc.ctx = "searchAttrDN";
+ while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) {
+ int last = 0;
+ slap_syntax_validate_func *validate;
+ slap_syntax_transform_func *pretty;
+
+ if ( ber_pvt_ber_remaining( &ber ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s meta_send_entry(\"%s\"): "
+ "unable to parse attr \"%s\".\n",
+ op->o_log_prefix, ent.e_name.bv_val, a.bv_val );
+
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( ber_pvt_ber_remaining( &ber ) == 0 ) {
+ break;
+ }
+
+ ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_at,
+ &a, &mapped, BACKLDAP_REMAP );
+ if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ continue;
+ }
+ if ( mapped.bv_val != a.bv_val ) {
+ /* will need to check for duplicate attrs */
+ check_duplicate_attrs++;
+ }
+ attr = attr_alloc( NULL );
+ if ( attr == NULL ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ if ( slap_bv2ad( &mapped, &attr->a_desc, &text )
+ != LDAP_SUCCESS) {
+ if ( slap_bv2undef_ad( &mapped, &attr->a_desc, &text,
+ SLAP_AD_PROXIED ) != LDAP_SUCCESS )
+ {
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_send_entry(\"%s\"): " "slap_bv2undef_ad(%s): %s\n",
+ op->o_log_prefix, ent.e_name.bv_val,
+ mapped.bv_val, text );
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ attr_free( attr );
+ continue;
+ }
+ }
+
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
+ check_sorted_attrs = 1;
+
+ /* no subschemaSubentry */
+ if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry
+ || attr->a_desc == slap_schema.si_ad_entryDN )
+ {
+
+ /*
+ * We eat target's subschemaSubentry because
+ * a search for this value is likely not
+ * to resolve to the appropriate backend;
+ * later, the local subschemaSubentry is
+ * added.
+ *
+ * We also eat entryDN because the frontend
+ * will reattach it without checking if already
+ * present...
+ */
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ attr_free(attr);
+ continue;
+ }
+
+ if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR
+ || attr->a_vals == NULL )
+ {
+ attr->a_vals = (struct berval *)&slap_dummy_bv;
+
+ } else {
+ for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last )
+ ;
+ }
+ attr->a_numvals = last;
+
+ validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate;
+ pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( !validate && !pretty ) {
+ attr_free( attr );
+ goto next_attr;
+ }
+
+ if ( attr->a_desc == slap_schema.si_ad_objectClass
+ || attr->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ struct berval *bv;
+
+ for ( bv = attr->a_vals; !BER_BVISNULL( bv ); bv++ ) {
+ ObjectClass *oc;
+
+ ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_oc,
+ bv, &mapped, BACKLDAP_REMAP );
+ if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0') {
+remove_oc:;
+ free( bv->bv_val );
+ BER_BVZERO( bv );
+ if ( --last < 0 ) {
+ break;
+ }
+ *bv = attr->a_vals[ last ];
+ BER_BVZERO( &attr->a_vals[ last ] );
+ bv--;
+
+ } else if ( mapped.bv_val != bv->bv_val ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &attr->a_vals[ i ] ); i++ ) {
+ if ( &attr->a_vals[ i ] == bv ) {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &mapped, &attr->a_vals[ i ] ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( !BER_BVISNULL( &attr->a_vals[ i ] ) ) {
+ goto remove_oc;
+ }
+
+ ber_bvreplace( bv, &mapped );
+
+ } else if ( ( oc = oc_bvfind_undef( bv ) ) == NULL ) {
+ goto remove_oc;
+
+ } else {
+ ber_bvreplace( bv, &oc->soc_cname );
+ }
+ }
+ /*
+ * It is necessary to try to rewrite attributes with
+ * dn syntax because they might be used in ACLs as
+ * members of groups; since ACLs are applied to the
+ * rewritten stuff, no dn-based subecj clause could
+ * be used at the ldap backend side (see
+ * http://www.OpenLDAP.org/faq/data/cache/452.html)
+ * The problem can be overcome by moving the dn-based
+ * ACLs to the target directory server, and letting
+ * everything pass thru the ldap backend.
+ */
+ } else {
+ int i;
+
+ if ( attr->a_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ {
+ ldap_dnattr_result_rewrite( &dc, attr->a_vals );
+
+ } else if ( attr->a_desc == slap_schema.si_ad_ref ) {
+ ldap_back_referral_result_rewrite( &dc, attr->a_vals, NULL );
+
+ }
+
+ for ( i = 0; i < last; i++ ) {
+ struct berval pval;
+ int rc;
+
+ if ( pretty ) {
+ rc = ordered_value_pretty( attr->a_desc,
+ &attr->a_vals[i], &pval, NULL );
+
+ } else {
+ rc = ordered_value_validate( attr->a_desc,
+ &attr->a_vals[i], 0 );
+ }
+
+ if ( rc ) {
+ ber_memfree( attr->a_vals[i].bv_val );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ continue;
+ }
+
+ if ( pretty ) {
+ ber_memfree( attr->a_vals[i].bv_val );
+ attr->a_vals[i] = pval;
+ }
+ }
+
+ if ( last == 0 && attr->a_vals != &slap_dummy_bv ) {
+ attr_free( attr );
+ goto next_attr;
+ }
+ }
+
+ if ( last && attr->a_desc->ad_type->sat_equality &&
+ attr->a_desc->ad_type->sat_equality->smr_normalize )
+ {
+ int i;
+
+ attr->a_nvals = ch_malloc( ( last + 1 ) * sizeof( struct berval ) );
+ for ( i = 0; i<last; i++ ) {
+ /* if normalizer fails, drop this value */
+ if ( ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ attr->a_desc,
+ attr->a_desc->ad_type->sat_equality,
+ &attr->a_vals[i], &attr->a_nvals[i],
+ NULL )) {
+ ber_memfree( attr->a_vals[i].bv_val );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ }
+ }
+ BER_BVZERO( &attr->a_nvals[i] );
+ if ( last == 0 ) {
+ attr_free( attr );
+ goto next_attr;
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ attr->a_numvals = last;
+ *attrp = attr;
+ attrp = &attr->a_next;
+next_attr:;
+ }
+
+ /* only check if some mapping occurred */
+ if ( check_duplicate_attrs ) {
+ Attribute **ap;
+
+ for ( ap = &ent.e_attrs; *ap != NULL; ap = &(*ap)->a_next ) {
+ Attribute **tap;
+
+ for ( tap = &(*ap)->a_next; *tap != NULL; ) {
+ if ( (*tap)->a_desc == (*ap)->a_desc ) {
+ Entry e = { 0 };
+ Modification mod = { 0 };
+ const char *text = NULL;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ Attribute *next = (*tap)->a_next;
+
+ BER_BVSTR( &e.e_name, "" );
+ BER_BVSTR( &e.e_nname, "" );
+ e.e_attrs = *ap;
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = (*ap)->a_desc;
+ mod.sm_type = mod.sm_desc->ad_cname;
+ mod.sm_numvals = (*ap)->a_numvals;
+ mod.sm_values = (*tap)->a_vals;
+ if ( (*tap)->a_nvals != (*tap)->a_vals ) {
+ mod.sm_nvalues = (*tap)->a_nvals;
+ }
+
+ (void)modify_add_values( &e, &mod,
+ /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+
+ /* should not insert new attrs! */
+ assert( e.e_attrs == *ap );
+
+ attr_free( *tap );
+ *tap = next;
+
+ } else {
+ tap = &(*tap)->a_next;
+ }
+ }
+ }
+ }
+
+ /* Check for sorted attributes */
+ if ( check_sorted_attrs ) {
+ for ( attr = ent.e_attrs; attr; attr = attr->a_next ) {
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ while ( attr->a_numvals > 1 ) {
+ int i;
+ int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx );
+ if ( rc != LDAP_TYPE_OR_VALUE_EXISTS )
+ break;
+
+ /* Strip duplicate values */
+ if ( attr->a_nvals != attr->a_vals )
+ ber_memfree( attr->a_nvals[i].bv_val );
+ ber_memfree( attr->a_vals[i].bv_val );
+ attr->a_numvals--;
+ if ( (unsigned)i < attr->a_numvals ) {
+ attr->a_vals[i] = attr->a_vals[attr->a_numvals];
+ if ( attr->a_nvals != attr->a_vals )
+ attr->a_nvals[i] = attr->a_nvals[attr->a_numvals];
+ }
+ BER_BVZERO(&attr->a_vals[attr->a_numvals]);
+ if ( attr->a_nvals != attr->a_vals )
+ BER_BVZERO(&attr->a_nvals[attr->a_numvals]);
+ }
+ attr->a_flags |= SLAP_ATTR_SORTED_VALS;
+ }
+ }
+ }
+
+ ldap_get_entry_controls( mc->mc_conns[target].msc_ld,
+ e, &rs->sr_ctrls );
+ rs->sr_entry = &ent;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags;
+ rs->sr_err = LDAP_SUCCESS;
+ rc = send_search_entry( op, rs );
+ switch ( rc ) {
+ case LDAP_UNAVAILABLE:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+done:;
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ if ( !BER_BVISNULL( &ent.e_name ) ) {
+ free( ent.e_name.bv_val );
+ BER_BVZERO( &ent.e_name );
+ }
+ if ( !BER_BVISNULL( &ent.e_nname ) ) {
+ free( ent.e_nname.bv_val );
+ BER_BVZERO( &ent.e_nname );
+ }
+ entry_clean( &ent );
+
+ return rc;
+}
+
diff --git a/servers/slapd/back-meta/suffixmassage.c b/servers/slapd/back-meta/suffixmassage.c
new file mode 100644
index 0000000..8d0b5e7
--- /dev/null
+++ b/servers/slapd/back-meta/suffixmassage.c
@@ -0,0 +1,110 @@
+/* suffixmassage.c - massages ldap backend dns */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+/* This is an altered version */
+
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it>
+ *
+ * Module back-ldap, originally developed by Howard Chu
+ *
+ * has been modified by Pierangelo Masarati. The original copyright
+ * notice has been maintained.
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources,
+ * credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users
+ * ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+ldap_back_dn_massage(
+ dncookie *dc,
+ struct berval *dn,
+ struct berval *res )
+{
+ int rc = 0;
+ static char *dmy = "";
+
+ switch ( rewrite_session( dc->target->mt_rwmap.rwm_rw, dc->ctx,
+ ( dn->bv_val ? dn->bv_val : dmy ),
+ dc->conn, &res->bv_val ) )
+ {
+ case REWRITE_REGEXEC_OK:
+ if ( res->bv_val != NULL ) {
+ res->bv_len = strlen( res->bv_val );
+ } else {
+ *res = *dn;
+ }
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ dc->ctx,
+ BER_BVISNULL( dn ) ? "" : dn->bv_val,
+ BER_BVISNULL( res ) ? "" : res->bv_val );
+ rc = LDAP_SUCCESS;
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ dc->rs->sr_text = "Operation not allowed";
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_OTHER;
+ dc->rs->sr_text = "Rewrite error";
+ }
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ if ( res->bv_val == dmy ) {
+ BER_BVZERO( res );
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/back-meta/unbind.c b/servers/slapd/back-meta/unbind.c
new file mode 100644
index 0000000..6c2928d
--- /dev/null
+++ b/servers/slapd/back-meta/unbind.c
@@ -0,0 +1,89 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-meta.h"
+
+int
+meta_back_conn_destroy(
+ Backend *be,
+ Connection *conn )
+{
+ metainfo_t *mi = ( metainfo_t * )be->be_private;
+ metaconn_t *mc,
+ mc_curr = {{ 0 }};
+ int i;
+
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=>meta_back_conn_destroy: fetching conn=%ld DN=\"%s\"\n",
+ conn->c_connid,
+ BER_BVISNULL( &conn->c_ndn ) ? "" : conn->c_ndn.bv_val );
+
+ mc_curr.mc_conn = conn;
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex );
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, ">>> meta_back_conn_destroy" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ while ( ( mc = ldap_tavl_delete( &mi->mi_conninfo.lai_tree, ( caddr_t )&mc_curr, meta_back_conn_cmp ) ) != NULL )
+ {
+ assert( !LDAP_BACK_PCONN_ISPRIV( mc ) );
+ Debug( LDAP_DEBUG_TRACE,
+ "=>meta_back_conn_destroy: destroying conn %lu "
+ "refcnt=%d flags=0x%08x\n",
+ mc->mc_conn->c_connid, mc->mc_refcnt, mc->msc_mscflags );
+
+ if ( mc->mc_refcnt > 0 ) {
+ /* someone else might be accessing the connection;
+ * mark for deletion */
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ LDAP_BACK_CONN_TAINTED_SET( mc );
+
+ } else {
+ meta_back_conn_free( mc );
+ }
+ }
+#if META_BACK_PRINT_CONNTREE > 0
+ meta_back_print_conntree( mi, "<<< meta_back_conn_destroy" );
+#endif /* META_BACK_PRINT_CONNTREE */
+ ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex );
+
+ /*
+ * Cleanup rewrite session
+ */
+ for ( i = 0; i < mi->mi_ntargets; ++i ) {
+ rewrite_session_delete( mi->mi_targets[ i ]->mt_rwmap.rwm_rw, conn );
+ }
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-monitor/Makefile.in b/servers/slapd/back-monitor/Makefile.in
new file mode 100644
index 0000000..e3b30a0
--- /dev/null
+++ b/servers/slapd/back-monitor/Makefile.in
@@ -0,0 +1,49 @@
+# Makefile.in for back-monitor
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c search.c compare.c modify.c bind.c \
+ operational.c \
+ cache.c entry.c \
+ backend.c database.c thread.c conn.c rww.c log.c \
+ operation.c sent.c listener.c time.c overlay.c
+OBJS = init.lo search.lo compare.lo modify.lo bind.lo \
+ operational.lo \
+ cache.lo entry.lo \
+ backend.lo database.lo thread.lo conn.lo rww.lo log.lo \
+ operation.lo sent.lo listener.lo time.lo overlay.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-monitor"
+BUILD_MOD = yes
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(yes_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_monitor
+
+XINCPATH = -I.. -I$(srcdir)/.. -I$(srcdir)/../slapi
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-monitor/README b/servers/slapd/back-monitor/README
new file mode 100644
index 0000000..38dc360
--- /dev/null
+++ b/servers/slapd/back-monitor/README
@@ -0,0 +1,243 @@
+MONITOR BACKEND
+
+ NAME: back-monitor
+
+ Backend for monitoring the server's activity.
+
+
+
+COMPILE AND CONFIGURATION OPTIONS
+
+It must be explicitly enabled by configuring with
+
+ --enable-monitor
+
+set; then it must be activated by placing in slapd.conf the database
+configure directive
+
+ database monitor
+
+The suffix "cn=Monitor" is implicitly activated (it cannot be given
+as a suffix of the database as usually done for conventional backends).
+Note that the "cn=Monitor" naming context appears in the rootDSE
+in the attribute monitorContext
+
+A bind operation is provided; at present it allows to bind as the
+backend rootdn. As a result, the backend supports the rootdn/rootpw
+directives (only simple bind at present).
+
+
+
+NAMING CONTEXT AND TREE STRUCTURE
+
+The backend naming context is "cn=Monitor"; the first level entries
+represent the monitored subsystems. It is implemented in a modular way,
+to ease the addition of new subsystems.
+
+
+
+SCHEMA
+
+All the subsystems get a default "cn" attribute, represented by the
+subsystem's name, and they all have "top", "monitor" and "extensibleObject"
+objectclasses.
+"extensibleObject" is used, and the "description" attribute
+is used to hold the monitor information of each entry.
+
+
+
+FUNCTIONALITY
+
+Most of the subsystems contain an additional depth level, represented
+by detailed item monitoring.
+All the entries undergo an update operation, if a related method is
+defined, prior to being returned. Moreover, there's a mechanism to
+allow volatile entries to be defined, and generated on the fly when
+requested. As an instance, the connection statistics are updated
+at each request, while each active connection data is created on the
+fly.
+
+One nice feature of this solution is that granular ACLs can be applied
+to each entry.
+
+
+
+OPERATIONS
+
+The backend currently supports:
+
+ bind
+ compare
+ modify
+ search
+
+
+
+SUBSYSTEMS
+
+Currently some subsystems are partially supported. "Partially"
+means their entries are correctly generated, but sometimes only
+partially useful information is provided.
+
+The subsystems are:
+
+ Backends
+ Connections
+ Databases
+ Listener
+ Log
+ Operations
+ Overlays
+ SASL
+ Statistics
+ Threads
+ Time
+ TLS
+ Read/Write Waiters
+
+
+
+BACKENDS SUBSYSTEMS
+
+The main entry contains the type of backends enabled at compile time;
+the subentries, for each backend, contain the type of the backend.
+It should also contain the modules that have been loaded if dynamic
+backends are enabled.
+
+
+
+CONNECTIONS
+
+The main entry is empty; it should contain some statistics on the number
+of connections.
+Dynamic subentries are created for each open connection, with stats on
+the activity on that connection (the format will be detailed later).
+There are two special subentries that show the number of total and
+current connections respectively.
+
+
+
+DATABASES SUBSYSTEM
+
+The main entry contains the naming context of each configured database;
+the subentries contain, for each database, the type and the naming
+context.
+
+
+
+LISTENER SUBSYSTEM
+
+It contains the description of the devices the server is currently
+listening on
+
+
+
+LOG SUBSYSTEM
+
+It contains the currently active log items. The "Log" subsystem allows
+user modify operations on the "description" attribute, whose values MUST
+be in the list of admittable log switches:
+
+ Trace
+ Packets
+ Args
+ Conns
+ BER
+ Filter
+ Config (useless)
+ ACL
+ Stats
+ Stats2
+ Shell
+ Parse
+ Cache (deprecated)
+ Index
+
+These values can be added, replaced or deleted; they affect what
+messages are sent to the syslog device.
+
+
+
+OPERATIONS SUBSYSTEM
+
+It shows some statistics on the operations performed by the server:
+
+ Initiated
+ Completed
+
+and for each operation type, i.e.:
+
+ Bind
+ Unbind
+ Add
+ Delete
+ Modrdn
+ Modify
+ Compare
+ Search
+ Abandon
+ Extended
+
+
+
+OVERLAYS SUBSYSTEM
+
+The main entry contains the type of overlays available at run-time;
+the subentries, for each overlay, contain the type of the overlay.
+It should also contain the modules that have been loaded if dynamic
+overlays are enabled.
+
+
+
+SASL
+
+Currently empty.
+
+
+
+STATISTICS SUBSYSTEM
+
+It shows some statistics on the data sent by the server:
+
+ Bytes
+ PDU
+ Entries
+ Referrals
+
+
+
+THREADS SUBSYSTEM
+
+It contains the maximum number of threads enabled at startup and the
+current backload.
+
+
+
+TIME SUBSYSTEM
+
+It contains two subentries with the start time and the current time
+of the server.
+
+
+
+TLS
+
+Currently empty.
+
+
+
+READ/WRITE WAITERS SUBSYSTEM
+
+It contains the number of current read waiters.
+
+
+
+NOTES
+
+This document is in a very early stage of maturity and will
+probably be rewritten many times before the monitor backend is released.
+
+
+
+AUTHOR: Pierangelo Masarati <ando@OpenLDAP.org>
+
diff --git a/servers/slapd/back-monitor/back-monitor.h b/servers/slapd/back-monitor/back-monitor.h
new file mode 100644
index 0000000..27966d7
--- /dev/null
+++ b/servers/slapd/back-monitor/back-monitor.h
@@ -0,0 +1,334 @@
+/* back-monitor.h - ldap monitor back-end header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef _BACK_MONITOR_H_
+#define _BACK_MONITOR_H_
+
+#include <ldap_pvt.h>
+#include <ldap_pvt_thread.h>
+#include <ldap_avl.h>
+#include <slap.h>
+
+LDAP_BEGIN_DECL
+
+/* define if si_ad_labeledURI is removed from slap_schema */
+#undef MONITOR_DEFINE_LABELEDURI
+
+typedef struct monitor_callback_t {
+ int (*mc_update)( Operation *op, SlapReply *rs, Entry *e, void *priv );
+ /* update callback
+ for user-defined entries */
+ int (*mc_modify)( Operation *op, SlapReply *rs, Entry *e, void *priv );
+ /* modify callback
+ for user-defined entries */
+ int (*mc_free)( Entry *e, void **priv );
+ /* delete callback
+ for user-defined entries */
+ void (*mc_dispose)( void **priv );
+ /* dispose callback
+ to dispose of the callback
+ private data itself */
+ void *mc_private; /* opaque pointer to
+ private data */
+ struct monitor_callback_t *mc_next;
+} monitor_callback_t;
+
+
+typedef struct monitor_entry_t {
+ ldap_pvt_thread_mutex_t mp_mutex; /* entry mutex */
+ Entry *mp_next; /* pointer to next sibling */
+ Entry *mp_children; /* pointer to first child */
+ Entry *mp_last; /* pointer to last child */
+ struct monitor_subsys_t *mp_info; /* subsystem info */
+#define mp_type mp_info->mss_type
+ unsigned long mp_flags; /* flags */
+
+#define MONITOR_F_NONE 0x0000U
+#define MONITOR_F_SUB 0x0001U /* subentry of subsystem */
+#define MONITOR_F_PERSISTENT 0x0010U /* persistent entry */
+#define MONITOR_F_PERSISTENT_CH 0x0020U /* subsystem generates
+ persistent entries */
+#define MONITOR_F_VOLATILE 0x0040U /* volatile entry */
+#define MONITOR_F_VOLATILE_CH 0x0080U /* subsystem generates
+ volatile entries */
+#define MONITOR_F_EXTERNAL 0x0100U /* externally added - don't free */
+/* NOTE: flags with 0xF0000000U mask are reserved for subsystem internals */
+
+ struct monitor_callback_t *mp_cb; /* callback sequence */
+ void *mp_private;
+} monitor_entry_t;
+
+struct entry_limbo_t; /* in init.c */
+
+typedef struct monitor_info_t {
+
+ /*
+ * Internal data
+ *
+ * Lock order:
+ * - cache first, then entry
+ * - DIT in preorder DFS
+ */
+ Avlnode *mi_cache;
+ ldap_pvt_thread_mutex_t mi_cache_lock;
+
+ /*
+ * Config parameters
+ */
+ struct berval mi_startTime; /* don't free it! */
+ struct berval mi_creatorsName; /* don't free it! */
+ struct berval mi_ncreatorsName; /* don't free it! */
+
+ /*
+ * Specific schema entities
+ */
+ ObjectClass *mi_oc_monitor;
+ ObjectClass *mi_oc_monitorServer;
+ ObjectClass *mi_oc_monitorContainer;
+ ObjectClass *mi_oc_monitorCounterObject;
+ ObjectClass *mi_oc_monitorOperation;
+ ObjectClass *mi_oc_monitorConnection;
+ ObjectClass *mi_oc_managedObject;
+ ObjectClass *mi_oc_monitoredObject;
+
+ AttributeDescription *mi_ad_monitoredInfo;
+ AttributeDescription *mi_ad_managedInfo;
+ AttributeDescription *mi_ad_monitorCounter;
+ AttributeDescription *mi_ad_monitorOpCompleted;
+ AttributeDescription *mi_ad_monitorOpInitiated;
+ AttributeDescription *mi_ad_monitorConnectionNumber;
+ AttributeDescription *mi_ad_monitorConnectionAuthzDN;
+ AttributeDescription *mi_ad_monitorConnectionLocalAddress;
+ AttributeDescription *mi_ad_monitorConnectionPeerAddress;
+ AttributeDescription *mi_ad_monitorTimestamp;
+ AttributeDescription *mi_ad_monitorOverlay;
+ AttributeDescription *mi_ad_monitorConnectionProtocol;
+ AttributeDescription *mi_ad_monitorConnectionOpsReceived;
+ AttributeDescription *mi_ad_monitorConnectionOpsExecuting;
+ AttributeDescription *mi_ad_monitorConnectionOpsPending;
+ AttributeDescription *mi_ad_monitorConnectionOpsCompleted;
+ AttributeDescription *mi_ad_monitorConnectionGet;
+ AttributeDescription *mi_ad_monitorConnectionRead;
+ AttributeDescription *mi_ad_monitorConnectionWrite;
+ AttributeDescription *mi_ad_monitorConnectionMask;
+ AttributeDescription *mi_ad_monitorConnectionListener;
+ AttributeDescription *mi_ad_monitorConnectionPeerDomain;
+ AttributeDescription *mi_ad_monitorConnectionStartTime;
+ AttributeDescription *mi_ad_monitorConnectionActivityTime;
+ AttributeDescription *mi_ad_monitorIsShadow;
+ AttributeDescription *mi_ad_monitorUpdateRef;
+ AttributeDescription *mi_ad_monitorRuntimeConfig;
+ AttributeDescription *mi_ad_monitorSuperiorDN;
+ AttributeDescription *mi_ad_monitorConnectionOpsAsync;
+ AttributeDescription *mi_ad_monitorLogLevel;
+ AttributeDescription *mi_ad_monitorDebugLevel;
+
+ /*
+ * Generic description attribute
+ */
+ AttributeDescription *mi_ad_readOnly;
+ AttributeDescription *mi_ad_restrictedOperation;
+
+ struct entry_limbo_t *mi_entry_limbo;
+} monitor_info_t;
+
+/*
+ * DNs
+ */
+
+enum {
+ SLAPD_MONITOR_BACKEND = 0,
+ SLAPD_MONITOR_CONN,
+ SLAPD_MONITOR_DATABASE,
+ SLAPD_MONITOR_LISTENER,
+ SLAPD_MONITOR_LOG,
+ SLAPD_MONITOR_OPS,
+ SLAPD_MONITOR_OVERLAY,
+ SLAPD_MONITOR_SASL,
+ SLAPD_MONITOR_SENT,
+ SLAPD_MONITOR_THREAD,
+ SLAPD_MONITOR_TIME,
+ SLAPD_MONITOR_TLS,
+ SLAPD_MONITOR_RWW,
+
+ SLAPD_MONITOR_LAST
+};
+
+#define SLAPD_MONITOR_AT "cn"
+
+#define SLAPD_MONITOR_BACKEND_NAME "Backends"
+#define SLAPD_MONITOR_BACKEND_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_BACKEND_NAME
+#define SLAPD_MONITOR_BACKEND_DN \
+ SLAPD_MONITOR_BACKEND_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_CONN_NAME "Connections"
+#define SLAPD_MONITOR_CONN_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_CONN_NAME
+#define SLAPD_MONITOR_CONN_DN \
+ SLAPD_MONITOR_CONN_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_DATABASE_NAME "Databases"
+#define SLAPD_MONITOR_DATABASE_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_DATABASE_NAME
+#define SLAPD_MONITOR_DATABASE_DN \
+ SLAPD_MONITOR_DATABASE_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_LISTENER_NAME "Listeners"
+#define SLAPD_MONITOR_LISTENER_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_LISTENER_NAME
+#define SLAPD_MONITOR_LISTENER_DN \
+ SLAPD_MONITOR_LISTENER_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_LOG_NAME "Log"
+#define SLAPD_MONITOR_LOG_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_LOG_NAME
+#define SLAPD_MONITOR_LOG_DN \
+ SLAPD_MONITOR_LOG_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_OPS_NAME "Operations"
+#define SLAPD_MONITOR_OPS_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_OPS_NAME
+#define SLAPD_MONITOR_OPS_DN \
+ SLAPD_MONITOR_OPS_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_OVERLAY_NAME "Overlays"
+#define SLAPD_MONITOR_OVERLAY_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_OVERLAY_NAME
+#define SLAPD_MONITOR_OVERLAY_DN \
+ SLAPD_MONITOR_OVERLAY_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_SASL_NAME "SASL"
+#define SLAPD_MONITOR_SASL_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_SASL_NAME
+#define SLAPD_MONITOR_SASL_DN \
+ SLAPD_MONITOR_SASL_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_SENT_NAME "Statistics"
+#define SLAPD_MONITOR_SENT_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_SENT_NAME
+#define SLAPD_MONITOR_SENT_DN \
+ SLAPD_MONITOR_SENT_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_THREAD_NAME "Threads"
+#define SLAPD_MONITOR_THREAD_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_THREAD_NAME
+#define SLAPD_MONITOR_THREAD_DN \
+ SLAPD_MONITOR_THREAD_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_TIME_NAME "Time"
+#define SLAPD_MONITOR_TIME_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_TIME_NAME
+#define SLAPD_MONITOR_TIME_DN \
+ SLAPD_MONITOR_TIME_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_TLS_NAME "TLS"
+#define SLAPD_MONITOR_TLS_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_TLS_NAME
+#define SLAPD_MONITOR_TLS_DN \
+ SLAPD_MONITOR_TLS_RDN "," SLAPD_MONITOR_DN
+
+#define SLAPD_MONITOR_RWW_NAME "Waiters"
+#define SLAPD_MONITOR_RWW_RDN \
+ SLAPD_MONITOR_AT "=" SLAPD_MONITOR_RWW_NAME
+#define SLAPD_MONITOR_RWW_DN \
+ SLAPD_MONITOR_RWW_RDN "," SLAPD_MONITOR_DN
+
+typedef struct monitor_subsys_t {
+ char *mss_name;
+ struct berval mss_rdn;
+ struct berval mss_dn;
+ struct berval mss_ndn;
+ struct berval mss_desc[ 3 ];
+ int mss_flags;
+#define MONITOR_F_OPENED 0x10000000U
+
+#define MONITOR_HAS_VOLATILE_CH( mp ) \
+ ( ( mp )->mp_flags & MONITOR_F_VOLATILE_CH )
+#define MONITOR_HAS_CHILDREN( mp ) \
+ ( ( mp )->mp_children || MONITOR_HAS_VOLATILE_CH( mp ) )
+
+ /* initialize entry and subentries */
+ int ( *mss_open )( BackendDB *, struct monitor_subsys_t *ms );
+ /* destroy structure */
+ int ( *mss_destroy )( BackendDB *, struct monitor_subsys_t *ms );
+ /* update existing dynamic entry and subentries */
+ int ( *mss_update )( Operation *, SlapReply *, Entry * );
+ /* create new dynamic subentries */
+ int ( *mss_create )( Operation *, SlapReply *,
+ struct berval *ndn, Entry *, Entry ** );
+ /* modify entry and subentries */
+ int ( *mss_modify )( Operation *, SlapReply *, Entry * );
+
+ void *mss_private;
+} monitor_subsys_t;
+
+extern BackendDB *be_monitor;
+
+/* increase this bufsize if entries in string form get too big */
+#define BACKMONITOR_BUFSIZE 8192
+
+typedef int (monitor_cbfunc)( struct berval *ndn, monitor_callback_t *cb,
+ struct berval *base, int scope, struct berval *filter );
+
+typedef int (monitor_cbafunc)( struct berval *ndn, Attribute *a,
+ monitor_callback_t *cb,
+ struct berval *base, int scope, struct berval *filter );
+
+typedef struct monitor_extra_t {
+ int (*is_configured)(void);
+ monitor_subsys_t * (*get_subsys)( const char *name );
+ monitor_subsys_t * (*get_subsys_by_dn)( struct berval *ndn, int sub );
+
+ int (*register_subsys)( monitor_subsys_t *ms );
+ int (*register_backend)( BackendInfo *bi );
+ int (*register_database)( BackendDB *be, struct berval *ndn_out );
+ int (*register_overlay_info)( slap_overinst *on );
+ int (*register_overlay)( BackendDB *be, slap_overinst *on, struct berval *ndn_out );
+ int (*register_entry)( Entry *e, monitor_callback_t *cb,
+ monitor_subsys_t *ms, unsigned long flags );
+ int (*register_entry_parent)( Entry *e, monitor_callback_t *cb,
+ monitor_subsys_t *ms, unsigned long flags,
+ struct berval *base, int scope, struct berval *filter );
+ monitor_cbafunc *register_entry_attrs;
+ monitor_cbfunc *register_entry_callback;
+
+ int (*unregister_entry)( struct berval *ndn );
+ monitor_cbfunc *unregister_entry_parent;
+ monitor_cbafunc *unregister_entry_attrs;
+ monitor_cbfunc *unregister_entry_callback;
+ Entry * (*entry_stub)( struct berval *pdn,
+ struct berval *pndn,
+ struct berval *rdn,
+ ObjectClass *oc,
+ struct berval *create,
+ struct berval *modify );
+ monitor_entry_t * (*entrypriv_create)( void );
+ int (*register_subsys_late)( monitor_subsys_t *ms );
+ Entry * (*entry_get_unlocked)( struct berval *ndn );
+} monitor_extra_t;
+
+LDAP_END_DECL
+
+#include "proto-back-monitor.h"
+
+#endif /* _back_monitor_h_ */
+
diff --git a/servers/slapd/back-monitor/backend.c b/servers/slapd/back-monitor/backend.c
new file mode 100644
index 0000000..d3afdf1
--- /dev/null
+++ b/servers/slapd/back-monitor/backend.c
@@ -0,0 +1,152 @@
+/* backend.c - deals with backend subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+/*
+ * initializes backend subentries
+ */
+int
+monitor_subsys_backend_init(
+ BackendDB *be,
+ monitor_subsys_t *ms
+)
+{
+ monitor_info_t *mi;
+ Entry *e_backend;
+ int i;
+ monitor_entry_t *mp;
+ monitor_subsys_t *ms_database;
+ BackendInfo *bi;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME );
+ if ( ms_database == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_backend_init: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_DATABASE_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_backend ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_backend_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ i = -1;
+ LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) {
+ char buf[ BACKMONITOR_BUFSIZE ];
+ BackendDB *be;
+ struct berval bv;
+ int j;
+ Entry *e;
+
+ i++;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Backend %d", i );
+ bv.bv_val = buf;
+
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_backend_init: "
+ "unable to create entry \"cn=Backend %d,%s\"\n",
+ i, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ ber_str2bv( bi->bi_type, 0, 0, &bv );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo,
+ &bv, NULL );
+ attr_merge_normalize_one( e_backend, mi->mi_ad_monitoredInfo,
+ &bv, NULL );
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorRuntimeConfig,
+ bi->bi_cf_ocs == NULL ? (struct berval *)&slap_false_bv :
+ (struct berval *)&slap_true_bv, NULL );
+
+ if ( bi->bi_controls ) {
+ int j;
+
+ for ( j = 0; bi->bi_controls[ j ]; j++ ) {
+ ber_str2bv( bi->bi_controls[ j ], 0, 0, &bv );
+ attr_merge_one( e, slap_schema.si_ad_supportedControl,
+ &bv, &bv );
+ }
+ }
+
+ j = -1;
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ char buf[ SLAP_LDAPDN_MAXLEN ];
+ struct berval dn;
+
+ j++;
+
+ if ( be->bd_info != bi ) {
+ continue;
+ }
+
+ snprintf( buf, sizeof( buf ), "cn=Database %d,%s",
+ j, ms_database->mss_dn.bv_val );
+
+ ber_str2bv( buf, 0, 0, &dn );
+ attr_merge_normalize_one( e, slap_schema.si_ad_seeAlso,
+ &dn, NULL );
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags | MONITOR_F_SUB;
+
+ if ( monitor_cache_add( mi, e, e_backend ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_backend_init: "
+ "unable to add entry \"cn=Backend %d,%s\"\n",
+ i,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_backend );
+
+ return( 0 );
+}
+
diff --git a/servers/slapd/back-monitor/bind.c b/servers/slapd/back-monitor/bind.c
new file mode 100644
index 0000000..718c001
--- /dev/null
+++ b/servers/slapd/back-monitor/bind.c
@@ -0,0 +1,48 @@
+/* bind.c - monitor backend bind routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <slap.h>
+#include "back-monitor.h"
+
+/*
+ * At present, only rootdn can bind with simple bind
+ */
+
+int
+monitor_back_bind( Operation *op, SlapReply *rs )
+{
+ Debug(LDAP_DEBUG_ARGS, "==> monitor_back_bind: dn: %s\n",
+ op->o_req_dn.bv_val );
+
+ if ( be_isroot_pw( op ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-monitor/cache.c b/servers/slapd/back-monitor/cache.c
new file mode 100644
index 0000000..990c88e
--- /dev/null
+++ b/servers/slapd/back-monitor/cache.c
@@ -0,0 +1,486 @@
+/* cache.c - routines to maintain an in-core cache of entries */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+
+#include "slap.h"
+
+#include "back-monitor.h"
+
+/*
+ * The cache maps DNs to Entries.
+ * Each entry, on turn, holds the list of its children in the e_private field.
+ * This is used by search operation to perform onelevel and subtree candidate
+ * selection.
+ */
+typedef struct monitor_cache_t {
+ struct berval mc_ndn;
+ Entry *mc_e;
+} monitor_cache_t;
+
+/*
+ * compares entries based on the dn
+ */
+int
+monitor_cache_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ monitor_cache_t *cc1 = ( monitor_cache_t * )c1;
+ monitor_cache_t *cc2 = ( monitor_cache_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ber_bvcmp( &cc1->mc_ndn, &cc2->mc_ndn );
+}
+
+/*
+ * checks for duplicate entries
+ */
+int
+monitor_cache_dup(
+ void *c1,
+ void *c2 )
+{
+ monitor_cache_t *cc1 = ( monitor_cache_t * )c1;
+ monitor_cache_t *cc2 = ( monitor_cache_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ber_bvcmp( &cc1->mc_ndn, &cc2->mc_ndn ) == 0 ? -1 : 0;
+}
+
+/*
+ * adds an entry to the cache and inits the mutex
+ */
+int
+monitor_cache_add(
+ monitor_info_t *mi,
+ Entry *e,
+ Entry *parent )
+{
+ monitor_cache_t tmp_mc, *mc, *pmc = NULL;
+ Entry **ep = NULL, *prev = NULL;
+ int rc = -1;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnParent( &e->e_nname, &tmp_mc.mc_ndn );
+
+ mc = ( monitor_cache_t * )ch_malloc( sizeof( monitor_cache_t ) );
+ mc->mc_ndn = e->e_nname;
+ mc->mc_e = e;
+
+ if ( parent ) {
+ /* Shortcut, but follow lock order as a fallback */
+ if ( ldap_pvt_thread_mutex_trylock( &mi->mi_cache_lock ) ) {
+ monitor_cache_release( mi, parent );
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache_lock );
+ monitor_cache_lock( parent );
+ }
+ } else {
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache_lock );
+ }
+
+ /* Allow database root be added */
+ if ( parent == NULL && mi->mi_cache != NULL ) {
+ pmc = ldap_avl_find( mi->mi_cache, &tmp_mc, monitor_cache_cmp );
+ if ( pmc == NULL ) {
+ goto done;
+ }
+ parent = pmc->mc_e;
+ monitor_cache_lock( parent );
+ }
+
+ rc = ldap_avl_insert( &mi->mi_cache, mc,
+ monitor_cache_cmp, monitor_cache_dup );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if ( parent != NULL ) {
+ monitor_entry_t *mp = parent->e_private;
+
+ if ( mp->mp_children ) {
+ monitor_entry_t *tail;
+
+ monitor_cache_lock( mp->mp_last );
+ tail = mp->mp_last->e_private;
+ tail->mp_next = e;
+ monitor_cache_release( mi, mp->mp_last );
+ mp->mp_last = e;
+ } else {
+ mp->mp_children = mp->mp_last = e;
+ }
+ }
+
+done:
+ if ( pmc != NULL ) {
+ monitor_cache_release( mi, parent );
+ }
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache_lock );
+
+ if ( rc != LDAP_SUCCESS ) {
+ ch_free( mc );
+ }
+ return rc;
+}
+
+/*
+ * locks the entry (no r/w)
+ */
+int
+monitor_cache_lock(
+ Entry *e )
+{
+ monitor_entry_t *mp;
+
+ assert( e != NULL );
+ assert( e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )e->e_private;
+ ldap_pvt_thread_mutex_lock( &mp->mp_mutex );
+
+ return( 0 );
+}
+
+/*
+ * tries to lock the entry (no r/w)
+ */
+int
+monitor_cache_trylock(
+ Entry *e )
+{
+ monitor_entry_t *mp;
+
+ assert( e != NULL );
+ assert( e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )e->e_private;
+ return ldap_pvt_thread_mutex_trylock( &mp->mp_mutex );
+}
+
+/*
+ * gets an entry from the cache based on the normalized dn
+ * with mutex locked
+ */
+int
+monitor_cache_get(
+ monitor_info_t *mi,
+ struct berval *ndn,
+ Entry **ep )
+{
+ monitor_cache_t tmp_mc, *mc;
+
+ assert( mi != NULL );
+ assert( ndn != NULL );
+ assert( ep != NULL );
+
+ *ep = NULL;
+
+ tmp_mc.mc_ndn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache_lock );
+ mc = ( monitor_cache_t * )ldap_avl_find( mi->mi_cache,
+ ( caddr_t )&tmp_mc, monitor_cache_cmp );
+
+ if ( mc != NULL ) {
+ /* entry is returned with mutex locked */
+ monitor_cache_lock( mc->mc_e );
+ *ep = mc->mc_e;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache_lock );
+
+ return ( *ep == NULL ? -1 : 0 );
+}
+
+/*
+ * gets an entry from the cache based on the normalized dn
+ * with mutex locked
+ */
+int
+monitor_cache_remove(
+ monitor_info_t *mi,
+ struct berval *ndn,
+ Entry **ep )
+{
+ monitor_cache_t tmp_mc, *mc;
+ struct berval pndn;
+
+ assert( mi != NULL );
+ assert( ndn != NULL );
+ assert( ep != NULL );
+
+ *ep = NULL;
+
+ dnParent( ndn, &pndn );
+
+retry:;
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache_lock );
+
+ tmp_mc.mc_ndn = *ndn;
+ mc = ( monitor_cache_t * )ldap_avl_find( mi->mi_cache,
+ ( caddr_t )&tmp_mc, monitor_cache_cmp );
+
+ if ( mc != NULL ) {
+ monitor_cache_t *pmc;
+
+ tmp_mc.mc_ndn = pndn;
+ pmc = ( monitor_cache_t * )ldap_avl_find( mi->mi_cache,
+ ( caddr_t )&tmp_mc, monitor_cache_cmp );
+ if ( pmc != NULL ) {
+ monitor_entry_t *mp = (monitor_entry_t *)mc->mc_e->e_private,
+ *pmp = (monitor_entry_t *)pmc->mc_e->e_private;
+ Entry **entryp, *prev = NULL;
+
+ monitor_cache_lock( pmc->mc_e );
+
+ for ( entryp = &pmp->mp_children; *entryp != NULL; ) {
+ monitor_entry_t *next = (monitor_entry_t *)(*entryp)->e_private;
+
+ monitor_cache_lock( *entryp );
+ if ( next == mp ) {
+ if ( mc->mc_e == pmp->mp_last ) {
+ pmp->mp_last = prev;
+ }
+ *entryp = next->mp_next;
+ entryp = NULL;
+ break;
+ }
+
+ if ( prev != NULL ) {
+ monitor_cache_release( mi, prev );
+ }
+ prev = *entryp;
+ entryp = &next->mp_next;
+ }
+ if ( prev ) {
+ monitor_cache_release( mi, prev );
+ }
+
+ if ( entryp != NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_cache_remove(\"%s\"): "
+ "not in parent's list\n",
+ ndn->bv_val );
+ }
+
+ /* either succeeded, and the entry is no longer
+ * in its parent's list, or failed, and the
+ * entry is neither mucked with nor returned */
+ monitor_cache_release( mi, pmc->mc_e );
+
+ if ( entryp == NULL ) {
+ monitor_cache_t *tmpmc;
+
+ tmp_mc.mc_ndn = *ndn;
+ tmpmc = ldap_avl_delete( &mi->mi_cache,
+ ( caddr_t )&tmp_mc, monitor_cache_cmp );
+ assert( tmpmc == mc );
+
+ *ep = mc->mc_e;
+ ch_free( mc );
+ mc = NULL;
+
+ /* NOTE: we destroy the mutex, but otherwise
+ * leave the private data around; specifically,
+ * callbacks need be freed by someone else */
+
+ ldap_pvt_thread_mutex_destroy( &mp->mp_mutex );
+ mp->mp_next = NULL;
+ mp->mp_children = NULL;
+ mp->mp_last = NULL;
+ }
+
+ }
+
+ if ( mc ) {
+ monitor_cache_release( mi, mc->mc_e );
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache_lock );
+
+ return ( *ep == NULL ? -1 : 0 );
+}
+
+/*
+ * If the entry exists in cache, it is returned in locked status;
+ * otherwise, if the parent exists, if it may generate volatile
+ * descendants an attempt to generate the required entry is
+ * performed and, if successful, the entry is returned
+ */
+int
+monitor_cache_dn2entry(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry **ep,
+ Entry **matched )
+{
+ monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private;
+ int rc;
+ struct berval p_ndn = BER_BVNULL;
+ Entry *e_parent;
+ monitor_entry_t *mp;
+
+ assert( mi != NULL );
+ assert( ndn != NULL );
+ assert( ep != NULL );
+ assert( matched != NULL );
+
+ *matched = NULL;
+
+ if ( !dnIsSuffix( ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) {
+ return( -1 );
+ }
+
+ rc = monitor_cache_get( mi, ndn, ep );
+ if ( !rc && *ep != NULL ) {
+ return( 0 );
+ }
+
+ /* try with parent/ancestors */
+ if ( BER_BVISNULL( ndn ) ) {
+ BER_BVSTR( &p_ndn, "" );
+
+ } else {
+ dnParent( ndn, &p_ndn );
+ }
+
+ rc = monitor_cache_dn2entry( op, rs, &p_ndn, &e_parent, matched );
+ if ( rc || e_parent == NULL ) {
+ return( -1 );
+ }
+
+ mp = ( monitor_entry_t * )e_parent->e_private;
+ rc = -1;
+ if ( mp->mp_flags & MONITOR_F_VOLATILE_CH ) {
+ /* parent entry generates volatile children */
+ rc = monitor_entry_create( op, rs, ndn, e_parent, ep );
+ }
+
+ if ( !rc ) {
+ monitor_cache_lock( *ep );
+ monitor_cache_release( mi, e_parent );
+
+ } else {
+ *matched = e_parent;
+ }
+
+ return( rc );
+}
+
+/*
+ * releases the lock of the entry; if it is marked as volatile, it is
+ * destroyed.
+ */
+int
+monitor_cache_release(
+ monitor_info_t *mi,
+ Entry *e )
+{
+ monitor_entry_t *mp;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+ assert( e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )e->e_private;
+
+ if ( mp->mp_flags & MONITOR_F_VOLATILE ) {
+ ldap_pvt_thread_mutex_unlock( &mp->mp_mutex );
+ ldap_pvt_thread_mutex_destroy( &mp->mp_mutex );
+ ch_free( mp );
+ e->e_private = NULL;
+ entry_free( e );
+
+ return( 0 );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mp->mp_mutex );
+
+ return( 0 );
+}
+
+static void
+monitor_entry_destroy( void *v_mc )
+{
+ monitor_cache_t *mc = (monitor_cache_t *)v_mc;
+
+ if ( mc->mc_e != NULL ) {
+ monitor_entry_t *mp;
+
+ assert( mc->mc_e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )mc->mc_e->e_private;
+
+ if ( mp->mp_cb ) {
+ monitor_callback_t *cb;
+
+ for ( cb = mp->mp_cb; cb != NULL; ) {
+ monitor_callback_t *next = cb->mc_next;
+
+ if ( cb->mc_free ) {
+ (void)cb->mc_free( mc->mc_e, &cb->mc_private );
+ }
+ ch_free( mp->mp_cb );
+
+ cb = next;
+ }
+ }
+
+ ldap_pvt_thread_mutex_destroy( &mp->mp_mutex );
+
+ ch_free( mp );
+ mc->mc_e->e_private = NULL;
+ entry_free( mc->mc_e );
+ }
+
+ ch_free( mc );
+}
+
+int
+monitor_cache_destroy(
+ monitor_info_t *mi )
+{
+ if ( mi->mi_cache ) {
+ ldap_avl_free( mi->mi_cache, monitor_entry_destroy );
+ }
+
+ return 0;
+}
+
+int monitor_back_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ return monitor_cache_release( mi, e );
+}
diff --git a/servers/slapd/back-monitor/compare.c b/servers/slapd/back-monitor/compare.c
new file mode 100644
index 0000000..e17b4f1
--- /dev/null
+++ b/servers/slapd/back-monitor/compare.c
@@ -0,0 +1,76 @@
+/* compare.c - monitor backend compare routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <slap.h>
+#include "back-monitor.h"
+
+int
+monitor_back_compare( Operation *op, SlapReply *rs )
+{
+ monitor_info_t *mi = ( monitor_info_t * ) op->o_bd->be_private;
+ Entry *e, *matched = NULL;
+ int rc;
+
+ /* get entry with reader lock */
+ monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched );
+ if ( e == NULL ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ if ( matched ) {
+ if ( !access_allowed_mask( op, matched,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL, NULL ) )
+ {
+ /* do nothing */ ;
+ } else {
+ rs->sr_matched = matched->e_dn;
+ }
+ }
+ send_ldap_result( op, rs );
+ if ( matched ) {
+ monitor_cache_release( mi, matched );
+ rs->sr_matched = NULL;
+ }
+
+ return rs->sr_err;
+ }
+
+ monitor_entry_update( op, rs, e );
+ rs->sr_err = slap_compare_entry( op, e, op->orc_ava );
+ rc = rs->sr_err;
+ switch ( rc ) {
+ case LDAP_COMPARE_FALSE:
+ case LDAP_COMPARE_TRUE:
+ rc = LDAP_SUCCESS;
+ break;
+ }
+
+ send_ldap_result( op, rs );
+ rs->sr_err = rc;
+
+ monitor_cache_release( mi, e );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-monitor/conn.c b/servers/slapd/back-monitor/conn.c
new file mode 100644
index 0000000..adfb626
--- /dev/null
+++ b/servers/slapd/back-monitor/conn.c
@@ -0,0 +1,526 @@
+/* conn.c - deal with connection subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "back-monitor.h"
+
+static int
+monitor_subsys_conn_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+static int
+monitor_subsys_conn_create(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry *e_parent,
+ Entry **ep );
+
+int
+monitor_subsys_conn_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+ Entry *e, *e_conn;
+ monitor_entry_t *mp;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ struct berval bv;
+
+ assert( be != NULL );
+
+ ms->mss_update = monitor_subsys_conn_update;
+ ms->mss_create = monitor_subsys_conn_create;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ mp = ( monitor_entry_t * )e_conn->e_private;
+
+ /*
+ * Max file descriptors
+ */
+ BER_BVSTR( &bv, "cn=Max File Descriptors" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitorCounterObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to create entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ if ( dtblsize ) {
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", dtblsize );
+
+ } else {
+ BER_BVSTR( &bv, "0" );
+ }
+ attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+ mp->mp_flags &= ~MONITOR_F_VOLATILE_CH;
+
+ if ( monitor_cache_add( mi, e, e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to add entry \"cn=Total,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /*
+ * Total conns
+ */
+ BER_BVSTR( &bv, "cn=Total" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitorCounterObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to create entry \"cn=Total,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ BER_BVSTR( &bv, "-1" );
+ attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+ mp->mp_flags &= ~MONITOR_F_VOLATILE_CH;
+
+ if ( monitor_cache_add( mi, e, e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to add entry \"cn=Total,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /*
+ * Current conns
+ */
+ BER_BVSTR( &bv, "cn=Current" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitorCounterObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to create entry \"cn=Current,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ BER_BVSTR( &bv, "0" );
+ attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+ mp->mp_flags &= ~MONITOR_F_VOLATILE_CH;
+
+ if ( monitor_cache_add( mi, e, e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_init: "
+ "unable to add entry \"cn=Current,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ monitor_cache_release( mi, e_conn );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_conn_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+
+ long n = -1;
+ static struct berval total_bv = BER_BVC( "cn=total" ),
+ current_bv = BER_BVC( "cn=current" );
+ struct berval rdn;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnRdn( &e->e_nname, &rdn );
+
+ if ( dn_match( &rdn, &total_bv ) ) {
+ n = connections_nextid() - SLAPD_SYNC_SYNCCONN_OFFSET;
+
+ } else if ( dn_match( &rdn, &current_bv ) ) {
+ Connection *c;
+ ber_socket_t connindex;
+
+ for ( n = 0, c = connection_first( &connindex );
+ c != NULL;
+ n++, c = connection_next( c, &connindex ) )
+ {
+ /* Ignore outbound connections */
+ if ( c->c_conn_state == SLAP_C_CLIENT )
+ n--;
+ }
+ connection_done( c );
+ }
+
+ if ( n != -1 ) {
+ Attribute *a;
+ char buf[LDAP_PVT_INTTYPE_CHARS(long)];
+ ber_len_t len;
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter );
+ if ( a == NULL ) {
+ return( -1 );
+ }
+
+ snprintf( buf, sizeof( buf ), "%ld", n );
+ len = strlen( buf );
+ if ( len > a->a_vals[ 0 ].bv_len ) {
+ a->a_vals[ 0 ].bv_val = ber_memrealloc( a->a_vals[ 0 ].bv_val, len + 1 );
+ }
+ a->a_vals[ 0 ].bv_len = len;
+ AC_MEMCPY( a->a_vals[ 0 ].bv_val, buf, len + 1 );
+
+ /* FIXME: touch modifyTimestamp? */
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+conn_create(
+ monitor_info_t *mi,
+ Connection *c,
+ Entry **ep,
+ monitor_subsys_t *ms )
+{
+ monitor_entry_t *mp;
+ struct tm tm;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ char buf2[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ char buf3[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+
+ struct berval bv, ctmbv, mtmbv;
+ struct berval bv_unknown= BER_BVC("unknown");
+
+ Entry *e;
+
+ assert( c != NULL );
+ assert( ep != NULL );
+
+ ldap_pvt_gmtime( &c->c_starttime, &tm );
+
+ ctmbv.bv_len = lutil_gentime( buf2, sizeof( buf2 ), &tm );
+ ctmbv.bv_val = buf2;
+
+ ldap_pvt_gmtime( &c->c_activitytime, &tm );
+ mtmbv.bv_len = lutil_gentime( buf3, sizeof( buf3 ), &tm );
+ mtmbv.bv_val = buf3;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ),
+ "cn=Connection %ld", c->c_connid );
+ bv.bv_val = buf;
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitorConnection, &ctmbv, &mtmbv );
+
+ if ( e == NULL) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_conn_create: "
+ "unable to create entry "
+ "\"cn=Connection %ld,%s\"\n",
+ c->c_connid,
+ ms->mss_dn.bv_val );
+ return( -1 );
+ }
+
+#ifdef MONITOR_LEGACY_CONN
+ /* NOTE: this will disappear, as the exploded data
+ * has been moved to dedicated attributes */
+ bv.bv_len = snprintf( buf, sizeof( buf ),
+ "%ld "
+ ": %ld "
+ ": %ld/%ld/%ld/%ld "
+ ": %ld/%ld/%ld "
+ ": %s%s%s%s%s%s "
+ ": %s "
+ ": %s "
+ ": %s "
+ ": %s "
+ ": %s "
+ ": %s "
+ ": %s",
+ c->c_connid,
+ (long) c->c_protocol,
+ c->c_n_ops_received, c->c_n_ops_executing,
+ c->c_n_ops_pending, c->c_n_ops_completed,
+
+ /* add low-level counters here */
+ c->c_n_get, c->c_n_read, c->c_n_write,
+
+ c->c_currentber ? "r" : "",
+ c->c_writewaiter ? "w" : "",
+ LDAP_STAILQ_EMPTY( &c->c_ops ) ? "" : "x",
+ LDAP_STAILQ_EMPTY( &c->c_pending_ops ) ? "" : "p",
+ connection_state2str( c->c_conn_state ),
+ c->c_sasl_bind_in_progress ? "S" : "",
+
+ c->c_dn.bv_len ? c->c_dn.bv_val : SLAPD_ANONYMOUS,
+
+ c->c_listener_url.bv_val,
+ BER_BVISNULL( &c->c_peer_domain )
+ ? "" : c->c_peer_domain.bv_val,
+ BER_BVISNULL( &c->c_peer_name )
+ ? "" : c->c_peer_name.bv_val,
+ c->c_sock_name.bv_val,
+
+ buf2,
+ buf3 );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL );
+#endif /* MONITOR_LEGACY_CONN */
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", c->c_connid );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionNumber, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", (long) c->c_protocol );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionProtocol, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_received );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionOpsReceived, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_executing );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionOpsExecuting, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_pending );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionOpsPending, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_completed );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionOpsCompleted, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_ops_async );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionOpsAsync, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_get );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionGet, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_read );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionRead, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%ld", c->c_n_write );
+ attr_merge_one( e, mi->mi_ad_monitorConnectionWrite, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%s%s%s%s%s%s",
+ c->c_currentber ? "r" : "",
+ c->c_writewaiter ? "w" : "",
+ LDAP_STAILQ_EMPTY( &c->c_ops ) ? "" : "x",
+ LDAP_STAILQ_EMPTY( &c->c_pending_ops ) ? "" : "p",
+ connection_state2str( c->c_conn_state ),
+ c->c_sasl_bind_in_progress ? "S" : "" );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionMask, &bv, NULL );
+
+ attr_merge_one( e, mi->mi_ad_monitorConnectionAuthzDN,
+ &c->c_dn, &c->c_ndn );
+
+ /* NOTE: client connections leave the c_peer_* fields NULL */
+ assert( !BER_BVISNULL( &c->c_listener_url ) );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionListener,
+ &c->c_listener_url, NULL );
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionPeerDomain,
+ BER_BVISNULL( &c->c_peer_domain ) ? &bv_unknown : &c->c_peer_domain,
+ NULL );
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionPeerAddress,
+ BER_BVISNULL( &c->c_peer_name ) ? &bv_unknown : &c->c_peer_name,
+ NULL );
+
+ assert( !BER_BVISNULL( &c->c_sock_name ) );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionLocalAddress,
+ &c->c_sock_name, NULL );
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionStartTime, &ctmbv, NULL );
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionActivityTime, &mtmbv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return LDAP_OTHER;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = MONITOR_F_SUB | MONITOR_F_VOLATILE;
+
+ *ep = e;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+monitor_subsys_conn_create(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry *e_parent,
+ Entry **ep )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+
+ int rc = SLAP_CB_CONTINUE;
+ monitor_subsys_t *ms;
+
+ assert( mi != NULL );
+ assert( e_parent != NULL );
+ assert( ep != NULL );
+
+ ms = (( monitor_entry_t *)e_parent->e_private)->mp_info;
+
+ *ep = NULL;
+
+ if ( ndn == NULL ) {
+ Connection *c;
+ ber_socket_t connindex;
+ Entry *e = NULL,
+ *e_tmp = NULL;
+
+ /* create all the children of e_parent */
+ for ( c = connection_first( &connindex );
+ c != NULL;
+ c = connection_next( c, &connindex ) )
+ {
+ monitor_entry_t *mp;
+
+ /* ignore outbound for now, nothing to show */
+ if ( c->c_conn_state == SLAP_C_CLIENT )
+ continue;
+
+ if ( conn_create( mi, c, &e, ms ) != SLAP_CB_CONTINUE
+ || e == NULL )
+ {
+ for ( ; e_tmp != NULL; ) {
+ mp = ( monitor_entry_t * )e_tmp->e_private;
+ e = mp->mp_next;
+
+ ch_free( mp );
+ e_tmp->e_private = NULL;
+ entry_free( e_tmp );
+
+ e_tmp = e;
+ }
+ rc = rs->sr_err = LDAP_OTHER;
+ break;
+ }
+ mp = ( monitor_entry_t * )e->e_private;
+ mp->mp_next = e_tmp;
+ e_tmp = e;
+ }
+ connection_done( c );
+ *ep = e;
+
+ } else {
+ Connection *c;
+ ber_socket_t connindex;
+ unsigned long connid;
+ char *next = NULL;
+ static struct berval nconn_bv = BER_BVC( "cn=connection " );
+
+ rc = LDAP_NO_SUCH_OBJECT;
+
+ /* create exactly the required entry;
+ * the normalized DN must start with "cn=connection ",
+ * followed by the connection id, followed by
+ * the RDN separator "," */
+ if ( ndn->bv_len <= nconn_bv.bv_len
+ || strncmp( ndn->bv_val, nconn_bv.bv_val, nconn_bv.bv_len ) != 0 )
+ {
+ return -1;
+ }
+
+ connid = strtol( &ndn->bv_val[ nconn_bv.bv_len ], &next, 10 );
+ if ( next[ 0 ] != ',' ) {
+ return ( rs->sr_err = LDAP_OTHER );
+ }
+
+ for ( c = connection_first( &connindex );
+ c != NULL;
+ c = connection_next( c, &connindex ) )
+ {
+ if ( c->c_connid == connid ) {
+ rc = conn_create( mi, c, ep, ms );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ rs->sr_err = rc;
+
+ } else if ( *ep == NULL ) {
+ rc = rs->sr_err = LDAP_OTHER;
+ }
+
+ break;
+ }
+ }
+
+ connection_done( c );
+ }
+
+ return rc;
+}
+
diff --git a/servers/slapd/back-monitor/database.c b/servers/slapd/back-monitor/database.c
new file mode 100644
index 0000000..dc1100d
--- /dev/null
+++ b/servers/slapd/back-monitor/database.c
@@ -0,0 +1,1000 @@
+/* database.c - deals with database subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+#if defined(LDAP_SLAPI)
+#include "slapi.h"
+static int monitor_back_add_plugin( monitor_info_t *mi, Backend *be, Entry *e );
+#endif /* defined(LDAP_SLAPI) */
+
+static int
+monitor_subsys_database_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+static struct restricted_ops_t {
+ struct berval op;
+ unsigned int tag;
+} restricted_ops[] = {
+ { BER_BVC( "add" ), SLAP_RESTRICT_OP_ADD },
+ { BER_BVC( "bind" ), SLAP_RESTRICT_OP_BIND },
+ { BER_BVC( "compare" ), SLAP_RESTRICT_OP_COMPARE },
+ { BER_BVC( "delete" ), SLAP_RESTRICT_OP_DELETE },
+ { BER_BVC( "extended" ), SLAP_RESTRICT_OP_EXTENDED },
+ { BER_BVC( "modify" ), SLAP_RESTRICT_OP_MODIFY },
+ { BER_BVC( "rename" ), SLAP_RESTRICT_OP_RENAME },
+ { BER_BVC( "search" ), SLAP_RESTRICT_OP_SEARCH },
+ { BER_BVNULL, 0 }
+}, restricted_exops[] = {
+ { BER_BVC( LDAP_EXOP_START_TLS ), SLAP_RESTRICT_EXOP_START_TLS },
+ { BER_BVC( LDAP_EXOP_MODIFY_PASSWD ), SLAP_RESTRICT_EXOP_MODIFY_PASSWD },
+ { BER_BVC( LDAP_EXOP_WHO_AM_I ), SLAP_RESTRICT_EXOP_WHOAMI },
+ { BER_BVC( LDAP_EXOP_CANCEL ), SLAP_RESTRICT_EXOP_CANCEL },
+ { BER_BVNULL, 0 }
+};
+
+static int
+init_readOnly( monitor_info_t *mi, Entry *e, slap_mask_t restrictops )
+{
+ struct berval *tf = ( ( restrictops & SLAP_RESTRICT_OP_MASK ) == SLAP_RESTRICT_OP_WRITES ) ?
+ (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv;
+
+ return attr_merge_one( e, mi->mi_ad_readOnly, tf, NULL );
+}
+
+static int
+init_restrictedOperation( monitor_info_t *mi, Entry *e, slap_mask_t restrictops )
+{
+ int i, rc;
+
+ for ( i = 0; restricted_ops[ i ].op.bv_val; i++ ) {
+ if ( restrictops & restricted_ops[ i ].tag ) {
+ rc = attr_merge_one( e, mi->mi_ad_restrictedOperation,
+ &restricted_ops[ i ].op,
+ &restricted_ops[ i ].op );
+ if ( rc ) {
+ return rc;
+ }
+ }
+ }
+
+ for ( i = 0; restricted_exops[ i ].op.bv_val; i++ ) {
+ if ( restrictops & restricted_exops[ i ].tag ) {
+ rc = attr_merge_one( e, mi->mi_ad_restrictedOperation,
+ &restricted_exops[ i ].op,
+ &restricted_exops[ i ].op );
+ if ( rc ) {
+ return rc;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+monitor_subsys_overlay_init_one(
+ monitor_info_t *mi,
+ BackendDB *be,
+ monitor_subsys_t *ms,
+ monitor_subsys_t *ms_overlay,
+ slap_overinst *on,
+ Entry *e_database,
+ Entry **ep_overlay )
+{
+ char buf[ BACKMONITOR_BUFSIZE ];
+ int j, o;
+ Entry *e_overlay;
+ slap_overinst *on2;
+ slap_overinfo *oi = NULL;
+ BackendInfo *bi;
+ monitor_entry_t *mp_overlay;
+ struct berval bv;
+
+ assert( overlay_is_over( be ) );
+
+ oi = (slap_overinfo *)be->bd_info->bi_private;
+ bi = oi->oi_orig;
+
+ /* find the overlay number, o */
+ for ( o = 0, on2 = oi->oi_list; on2 && on2 != on; on2 = on2->on_next, o++ )
+ ;
+
+ if ( on2 == NULL ) {
+ return -1;
+ }
+
+ /* find the overlay type number, j */
+ for ( on2 = overlay_next( NULL ), j = 0; on2; on2 = overlay_next( on2 ), j++ ) {
+ if ( on2->on_bi.bi_type == on->on_bi.bi_type ) {
+ break;
+ }
+ }
+ assert( on2 != NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d", o );
+ bv.bv_val = buf;
+
+ e_overlay = monitor_entry_stub( &e_database->e_name, &e_database->e_nname, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+
+ if ( e_overlay == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_overlay_init_one: "
+ "unable to create entry "
+ "\"cn=Overlay %d,%s\"\n",
+ o, e_database->e_name.bv_val );
+ return( -1 );
+ }
+ ber_str2bv( on->on_bi.bi_type, 0, 0, &bv );
+ attr_merge_normalize_one( e_overlay, mi->mi_ad_monitoredInfo, &bv, NULL );
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d,%s",
+ j, ms_overlay->mss_dn.bv_val );
+ bv.bv_val = buf;
+ attr_merge_normalize_one( e_overlay, slap_schema.si_ad_seeAlso,
+ &bv, NULL );
+
+ if ( SLAP_MONITOR( be ) ) {
+ attr_merge( e_overlay, slap_schema.si_ad_monitorContext,
+ be->be_suffix, be->be_nsuffix );
+
+ } else {
+ attr_merge( e_overlay, slap_schema.si_ad_namingContexts,
+ be->be_suffix, be->be_nsuffix );
+ }
+
+ mp_overlay = monitor_entrypriv_create();
+ if ( mp_overlay == NULL ) {
+ return -1;
+ }
+ e_overlay->e_private = ( void * )mp_overlay;
+ mp_overlay->mp_info = ms;
+ mp_overlay->mp_flags = ms->mss_flags | MONITOR_F_SUB;
+
+ if ( monitor_cache_add( mi, e_overlay, e_database ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_overlay_init_one: "
+ "unable to add entry "
+ "\"cn=Overlay %d,%s\"\n",
+ o, e_database->e_name.bv_val );
+ return -1;
+ }
+
+ *ep_overlay = e_overlay;
+ return 0;
+}
+
+static int
+monitor_subsys_database_init_one(
+ monitor_info_t *mi,
+ BackendDB *be,
+ monitor_subsys_t *ms,
+ monitor_subsys_t *ms_backend,
+ monitor_subsys_t *ms_overlay,
+ struct berval *rdn,
+ Entry *e_database,
+ struct slap_overinst *overlay,
+ Entry **ep )
+{
+ char buf[ BACKMONITOR_BUFSIZE ];
+ int j;
+ slap_overinfo *oi = NULL;
+ BackendInfo *bi, *bi2;
+ Entry *e;
+ monitor_entry_t *mp;
+ char *rdnval = strchr( rdn->bv_val, '=' ) + 1;
+ struct berval bv;
+
+ bi = be->bd_info;
+
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init_one: "
+ "missing suffix for %s\n",
+ rdnval );
+ return( -1 );
+ }
+
+ if ( overlay_is_over( be ) ) {
+ oi = (slap_overinfo *)be->bd_info->bi_private;
+ bi = oi->oi_orig;
+ }
+
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, rdn,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init_one: "
+ "unable to create entry \"%s,%s\"\n",
+ rdn->bv_val, ms->mss_dn.bv_val );
+ return( -1 );
+ }
+
+ ber_str2bv( bi->bi_type, 0, 0, &bv );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL );
+ attr_merge_one( e, mi->mi_ad_monitorIsShadow,
+ SLAP_SHADOW( be ) ? (struct berval *)&slap_true_bv :
+ (struct berval *)&slap_false_bv, NULL );
+
+ if ( SLAP_MONITOR( be ) ) {
+ attr_merge( e, slap_schema.si_ad_monitorContext,
+ be->be_suffix, be->be_nsuffix );
+ attr_merge( e_database, slap_schema.si_ad_monitorContext,
+ be->be_suffix, be->be_nsuffix );
+
+ } else {
+ attr_merge( e, slap_schema.si_ad_namingContexts,
+ be->be_suffix, be->be_nsuffix );
+ attr_merge( e_database, slap_schema.si_ad_namingContexts,
+ be->be_suffix, be->be_nsuffix );
+
+ if ( SLAP_GLUE_SUBORDINATE( be ) ) {
+ BackendDB *sup_be = select_backend( &be->be_nsuffix[ 0 ], 1 );
+ if ( sup_be == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init: "
+ "unable to get superior for %s\n",
+ be->be_suffix[ 0 ].bv_val );
+
+ } else {
+ attr_merge( e, mi->mi_ad_monitorSuperiorDN,
+ sup_be->be_suffix, sup_be->be_nsuffix );
+ }
+ }
+ }
+
+ (void)init_readOnly( mi, e, be->be_restrictops );
+ (void)init_restrictedOperation( mi, e, be->be_restrictops );
+
+ if ( SLAP_SHADOW( be ) && be->be_update_refs ) {
+ attr_merge_normalize( e, mi->mi_ad_monitorUpdateRef,
+ be->be_update_refs, NULL );
+ }
+
+ if ( oi != NULL ) {
+ slap_overinst *on = oi->oi_list,
+ *on1 = on;
+
+ for ( ; on; on = on->on_next ) {
+ slap_overinst *on2;
+
+ for ( on2 = on1; on2 != on; on2 = on2->on_next ) {
+ if ( on2->on_bi.bi_type == on->on_bi.bi_type ) {
+ break;
+ }
+ }
+
+ if ( on2 != on ) {
+ break;
+ }
+
+ ber_str2bv( on->on_bi.bi_type, 0, 0, &bv );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorOverlay,
+ &bv, NULL );
+
+ /* find the overlay number, j */
+ for ( on2 = overlay_next( NULL ), j = 0; on2; on2 = overlay_next( on2 ), j++ ) {
+ if ( on2->on_bi.bi_type == on->on_bi.bi_type ) {
+ break;
+ }
+ }
+ assert( on2 != NULL );
+
+ snprintf( buf, sizeof( buf ),
+ "cn=Overlay %d,%s",
+ j, ms_overlay->mss_dn.bv_val );
+ ber_str2bv( buf, 0, 0, &bv );
+ attr_merge_normalize_one( e,
+ slap_schema.si_ad_seeAlso,
+ &bv, NULL );
+ }
+ }
+
+ j = -1;
+ LDAP_STAILQ_FOREACH( bi2, &backendInfo, bi_next ) {
+ j++;
+ if ( bi2->bi_type == bi->bi_type ) {
+ snprintf( buf, sizeof( buf ),
+ "cn=Backend %d,%s",
+ j, ms_backend->mss_dn.bv_val );
+ bv.bv_val = buf;
+ bv.bv_len = strlen( buf );
+ attr_merge_normalize_one( e,
+ slap_schema.si_ad_seeAlso,
+ &bv, NULL );
+ break;
+ }
+ }
+ /* we must find it! */
+ assert( j >= 0 );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags
+ | MONITOR_F_SUB;
+ mp->mp_private = be;
+
+ if ( monitor_cache_add( mi, e, e_database ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init_one: "
+ "unable to add entry \"%s,%s\"\n",
+ rdn->bv_val, ms->mss_dn.bv_val );
+ return( -1 );
+ }
+
+#if defined(LDAP_SLAPI)
+ monitor_back_add_plugin( mi, be, e );
+#endif /* defined(LDAP_SLAPI) */
+
+ if ( oi != NULL ) {
+ Entry *e_overlay;
+ slap_overinst *on = oi->oi_list;
+
+ for ( ; on; on = on->on_next ) {
+ monitor_subsys_overlay_init_one( mi, be,
+ ms, ms_overlay, on, e, &e_overlay );
+ if ( overlay == on ) {
+ *ep = e_overlay;
+ }
+ }
+ }
+ if ( overlay == NULL ) {
+ *ep = e;
+ }
+
+ return 0;
+}
+
+static int
+monitor_back_register_database_and_overlay(
+ BackendDB *be,
+ struct slap_overinst *on,
+ struct berval *ndn_out )
+{
+ monitor_info_t *mi;
+ Entry *e_database, *e = NULL;
+ int i, rc;
+ monitor_entry_t *mp;
+ monitor_subsys_t *ms_backend,
+ *ms_database,
+ *ms_overlay;
+ struct berval bv;
+ char buf[ BACKMONITOR_BUFSIZE ];
+
+ assert( be_monitor != NULL );
+
+ if ( !monitor_subsys_is_opened() ) {
+ if ( on ) {
+ return monitor_back_register_overlay_limbo( be, on, ndn_out );
+
+ } else {
+ return monitor_back_register_database_limbo( be, ndn_out );
+ }
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ ms_backend = monitor_back_get_subsys( SLAPD_MONITOR_BACKEND_NAME );
+ if ( ms_backend == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_database: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_BACKEND_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME );
+ if ( ms_database == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_database: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_DATABASE_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ ms_overlay = monitor_back_get_subsys( SLAPD_MONITOR_OVERLAY_NAME );
+ if ( ms_overlay == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_database: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_OVERLAY_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ if ( monitor_cache_get( mi, &ms_database->mss_ndn, &e_database ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init: "
+ "unable to get entry \"%s\"\n",
+ ms_database->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /* FIXME: It's only safe since we're paused */
+ mp = ( monitor_entry_t * )e_database->e_private;
+ for ( i = -1, e = mp->mp_children; e; i++ ) {
+ mp = ( monitor_entry_t * )e->e_private;
+
+ assert( mp != NULL );
+ if ( mp->mp_private == be->bd_self ) {
+ rc = 0;
+ goto done;
+ }
+ e = mp->mp_next;
+ }
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Database %d", i );
+ if ( bv.bv_len >= sizeof( buf ) ) {
+ rc = -1;
+ goto done;
+ }
+
+ rc = monitor_subsys_database_init_one( mi, be,
+ ms_database, ms_backend, ms_overlay, &bv, e_database, on, &e );
+ if ( rc != 0 ) {
+ goto done;
+ }
+
+done:;
+ monitor_cache_release( mi, e_database );
+ if ( rc == 0 && ndn_out && e ) {
+ *ndn_out = e->e_nname;
+ }
+
+ return rc;
+}
+
+int
+monitor_back_register_database(
+ BackendDB *be,
+ struct berval *ndn_out )
+{
+ return monitor_back_register_database_and_overlay( be, NULL, ndn_out );
+}
+
+int
+monitor_back_register_overlay(
+ BackendDB *be,
+ struct slap_overinst *on,
+ struct berval *ndn_out )
+{
+ return monitor_back_register_database_and_overlay( be, on, ndn_out );
+}
+
+int
+monitor_subsys_database_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+ Entry *e_database, *e;
+ int i, rc;
+ monitor_subsys_t *ms_backend,
+ *ms_overlay;
+ struct berval bv;
+
+ assert( be != NULL );
+
+ ms->mss_modify = monitor_subsys_database_modify;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ ms_backend = monitor_back_get_subsys( SLAPD_MONITOR_BACKEND_NAME );
+ if ( ms_backend == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_BACKEND_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ ms_overlay = monitor_back_get_subsys( SLAPD_MONITOR_OVERLAY_NAME );
+ if ( ms_overlay == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_OVERLAY_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_database ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_database_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ (void)init_readOnly( mi, e_database, frontendDB->be_restrictops );
+ (void)init_restrictedOperation( mi, e_database, frontendDB->be_restrictops );
+
+ BER_BVSTR( &bv, "cn=Frontend" );
+ rc = monitor_subsys_database_init_one( mi, frontendDB,
+ ms, ms_backend, ms_overlay, &bv, e_database, NULL, &e );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ i = -1;
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ char buf[ BACKMONITOR_BUFSIZE ];
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Database %d", ++i );
+ if ( bv.bv_len >= sizeof( buf ) ) {
+ return -1;
+ }
+
+ rc = monitor_subsys_database_init_one( mi, be,
+ ms, ms_backend, ms_overlay, &bv, e_database, NULL, &e );
+ if ( rc != 0 ) {
+ return rc;
+ }
+ }
+
+ monitor_cache_release( mi, e_database );
+
+ return( 0 );
+}
+
+/*
+ * v: array of values
+ * cur: must not contain the tags corresponding to the values in v
+ * delta: will contain the tags corresponding to the values in v
+ */
+static int
+value_mask( BerVarray v, slap_mask_t cur, slap_mask_t *delta )
+{
+ for ( ; !BER_BVISNULL( v ); v++ ) {
+ struct restricted_ops_t *rops;
+ int i;
+
+ if ( OID_LEADCHAR( v->bv_val[ 0 ] ) ) {
+ rops = restricted_exops;
+
+ } else {
+ rops = restricted_ops;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &rops[ i ].op ); i++ ) {
+ if ( ber_bvstrcasecmp( v, &rops[ i ].op ) != 0 ) {
+ continue;
+ }
+
+ if ( rops[ i ].tag & *delta ) {
+ return LDAP_OTHER;
+ }
+
+ if ( rops[ i ].tag & cur ) {
+ return LDAP_OTHER;
+ }
+
+ cur |= rops[ i ].tag;
+ *delta |= rops[ i ].tag;
+
+ break;
+ }
+
+ if ( BER_BVISNULL( &rops[ i ].op ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+monitor_subsys_database_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private;
+ int rc = LDAP_OTHER;
+ Attribute *save_attrs, *a;
+ Modifications *ml;
+ Backend *be;
+ int ro_gotval = 1, i, n;
+ slap_mask_t rp_add = 0, rp_delete = 0, rp_cur;
+ struct berval *tf;
+
+ i = sscanf( e->e_nname.bv_val, "cn=database %d,", &n );
+ if ( i != 1 ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( n < 0 || n >= nBackendDB ) {
+ rs->sr_text = "invalid database index";
+ return ( rs->sr_err = LDAP_NO_SUCH_OBJECT );
+ }
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( n == 0 ) {
+ break;
+ }
+ n--;
+ }
+ /* do not allow some changes on back-monitor (needs work)... */
+ if ( SLAP_MONITOR( be ) ) {
+ rs->sr_text = "no modifications allowed to monitor database entry";
+ return ( rs->sr_err = LDAP_UNWILLING_TO_PERFORM );
+ }
+
+ rp_cur = be->be_restrictops;
+
+ save_attrs = e->e_attrs;
+ e->e_attrs = attrs_dup( e->e_attrs );
+
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ Modification *mod = &ml->sml_mod;
+
+ if ( mod->sm_desc == mi->mi_ad_readOnly ) {
+ int val = -1;
+
+ if ( mod->sm_values ) {
+ if ( !BER_BVISNULL( &mod->sm_values[ 1 ] ) ) {
+ rs->sr_text = "attempting to modify multiple values of single-valued attribute";
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( bvmatch( &slap_true_bv, mod->sm_values )) {
+ val = 1;
+
+ } else if ( bvmatch( &slap_false_bv, mod->sm_values )) {
+ val = 0;
+
+ } else {
+ assert( 0 );
+ rc = rs->sr_err = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ }
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_DELETE:
+ if ( ro_gotval < 1 ) {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ ro_gotval--;
+
+ if ( val == 0 && ( rp_cur & SLAP_RESTRICT_OP_WRITES ) == SLAP_RESTRICT_OP_WRITES ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ if ( val == 1 && ( rp_cur & SLAP_RESTRICT_OP_WRITES ) != SLAP_RESTRICT_OP_WRITES ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ break;
+
+ case LDAP_MOD_REPLACE:
+ ro_gotval = 0;
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ if ( ro_gotval > 0 ) {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ ro_gotval++;
+
+ if ( val == 1 ) {
+ rp_add |= (~rp_cur) & SLAP_RESTRICT_OP_WRITES;
+ rp_cur |= SLAP_RESTRICT_OP_WRITES;
+ rp_delete &= ~SLAP_RESTRICT_OP_WRITES;
+
+ } else if ( val == 0 ) {
+ rp_delete |= rp_cur & SLAP_RESTRICT_OP_WRITES;
+ rp_cur &= ~SLAP_RESTRICT_OP_WRITES;
+ rp_add &= ~SLAP_RESTRICT_OP_WRITES;
+ }
+ break;
+
+ default:
+ rc = rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ } else if ( mod->sm_desc == mi->mi_ad_restrictedOperation ) {
+ slap_mask_t mask = 0;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_DELETE:
+ if ( mod->sm_values == NULL ) {
+ rp_delete = rp_cur;
+ rp_cur = 0;
+ rp_add = 0;
+ break;
+ }
+ rc = value_mask( mod->sm_values, ~rp_cur, &mask );
+ if ( rc == LDAP_SUCCESS ) {
+ rp_delete |= mask;
+ rp_add &= ~mask;
+ rp_cur &= ~mask;
+
+ } else if ( rc == LDAP_OTHER ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ break;
+
+ case LDAP_MOD_REPLACE:
+ rp_delete = rp_cur;
+ rp_cur = 0;
+ rp_add = 0;
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ rc = value_mask( mod->sm_values, rp_cur, &mask );
+ if ( rc == LDAP_SUCCESS ) {
+ rp_add |= mask;
+ rp_cur |= mask;
+ rp_delete &= ~mask;
+
+ } else if ( rc == LDAP_OTHER ) {
+ rc = rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ break;
+
+ default:
+ rc = rs->sr_err = LDAP_OTHER;
+ break;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ } else if ( is_at_operational( mod->sm_desc->ad_type )) {
+ /* accept all operational attributes */
+ attr_delete( &e->e_attrs, mod->sm_desc );
+ rc = attr_merge( e, mod->sm_desc, mod->sm_values,
+ mod->sm_nvalues );
+ if ( rc ) {
+ rc = rs->sr_err = LDAP_OTHER;
+ break;
+ }
+
+ } else {
+ rc = rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ break;
+ }
+ }
+
+ /* sanity checks: */
+ if ( ro_gotval < 1 ) {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( ( rp_cur & SLAP_RESTRICT_OP_EXTENDED ) && ( rp_cur & SLAP_RESTRICT_EXOP_MASK ) ) {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( rp_delete & rp_add ) {
+ rc = rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ /* check current value of readOnly */
+ if ( ( rp_cur & SLAP_RESTRICT_OP_WRITES ) == SLAP_RESTRICT_OP_WRITES ) {
+ tf = (struct berval *)&slap_true_bv;
+
+ } else {
+ tf = (struct berval *)&slap_false_bv;
+ }
+
+ a = attr_find( e->e_attrs, mi->mi_ad_readOnly );
+ if ( a == NULL ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( !bvmatch( &a->a_vals[ 0 ], tf ) ) {
+ attr_delete( &e->e_attrs, mi->mi_ad_readOnly );
+ rc = attr_merge_one( e, mi->mi_ad_readOnly, tf, tf );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ if ( rp_delete ) {
+ if ( rp_delete == be->be_restrictops ) {
+ attr_delete( &e->e_attrs, mi->mi_ad_restrictedOperation );
+
+ } else {
+ a = attr_find( e->e_attrs, mi->mi_ad_restrictedOperation );
+ if ( a == NULL ) {
+ rc = rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &restricted_ops[ i ].op ); i++ ) {
+ if ( rp_delete & restricted_ops[ i ].tag ) {
+ int j;
+
+ for ( j = 0; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ) {
+ int k;
+
+ if ( !bvmatch( &a->a_nvals[ j ], &restricted_ops[ i ].op ) ) {
+ continue;
+ }
+
+ ch_free( a->a_vals[ j ].bv_val );
+ ch_free( a->a_nvals[ j ].bv_val );
+
+ for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) {
+ a->a_vals[ k - 1 ] = a->a_vals[ k ];
+ a->a_nvals[ k - 1 ] = a->a_nvals[ k ];
+ }
+
+ BER_BVZERO( &a->a_vals[ k - 1 ] );
+ BER_BVZERO( &a->a_nvals[ k - 1 ] );
+ a->a_numvals--;
+ }
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &restricted_exops[ i ].op ); i++ ) {
+ if ( rp_delete & restricted_exops[ i ].tag ) {
+ int j;
+
+ for ( j = 0; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ) {
+ int k;
+
+ if ( !bvmatch( &a->a_nvals[ j ], &restricted_exops[ i ].op ) ) {
+ continue;
+ }
+
+ ch_free( a->a_vals[ j ].bv_val );
+ ch_free( a->a_nvals[ j ].bv_val );
+
+ for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) {
+ a->a_vals[ k - 1 ] = a->a_vals[ k ];
+ a->a_nvals[ k - 1 ] = a->a_nvals[ k ];
+ }
+
+ BER_BVZERO( &a->a_vals[ k - 1 ] );
+ BER_BVZERO( &a->a_nvals[ k - 1 ] );
+ a->a_numvals--;
+ }
+ }
+ }
+
+ if ( a->a_vals == NULL ) {
+ assert( a->a_numvals == 0 );
+
+ attr_delete( &e->e_attrs, mi->mi_ad_restrictedOperation );
+ }
+ }
+ }
+
+ if ( rp_add ) {
+ for ( i = 0; !BER_BVISNULL( &restricted_ops[ i ].op ); i++ ) {
+ if ( rp_add & restricted_ops[ i ].tag ) {
+ attr_merge_one( e, mi->mi_ad_restrictedOperation,
+ &restricted_ops[ i ].op,
+ &restricted_ops[ i ].op );
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &restricted_exops[ i ].op ); i++ ) {
+ if ( rp_add & restricted_exops[ i ].tag ) {
+ attr_merge_one( e, mi->mi_ad_restrictedOperation,
+ &restricted_exops[ i ].op,
+ &restricted_exops[ i ].op );
+ }
+ }
+ }
+ }
+
+ be->be_restrictops = rp_cur;
+
+done:;
+ if ( rc == LDAP_SUCCESS ) {
+ attrs_free( save_attrs );
+ rc = SLAP_CB_CONTINUE;
+
+ } else {
+ Attribute *tmp = e->e_attrs;
+ e->e_attrs = save_attrs;
+ attrs_free( tmp );
+ }
+ return rc;
+}
+
+#if defined(LDAP_SLAPI)
+static int
+monitor_back_add_plugin( monitor_info_t *mi, Backend *be, Entry *e_database )
+{
+ Slapi_PBlock *pCurrentPB;
+ int i, rc = LDAP_SUCCESS;
+
+ if ( slapi_int_pblock_get_first( be, &pCurrentPB ) != LDAP_SUCCESS ) {
+ /*
+ * LDAP_OTHER is returned if no plugins are installed
+ */
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ i = 0;
+ do {
+ Slapi_PluginDesc *srchdesc;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ struct berval bv;
+
+ rc = slapi_pblock_get( pCurrentPB, SLAPI_PLUGIN_DESCRIPTION,
+ &srchdesc );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+ if ( srchdesc ) {
+ snprintf( buf, sizeof(buf),
+ "plugin %d name: %s; "
+ "vendor: %s; "
+ "version: %s; "
+ "description: %s",
+ i,
+ srchdesc->spd_id,
+ srchdesc->spd_vendor,
+ srchdesc->spd_version,
+ srchdesc->spd_description );
+ } else {
+ snprintf( buf, sizeof(buf),
+ "plugin %d name: <no description available>", i );
+ }
+
+ ber_str2bv( buf, 0, 0, &bv );
+ attr_merge_normalize_one( e_database,
+ mi->mi_ad_monitoredInfo, &bv, NULL );
+
+ i++;
+
+ } while ( ( slapi_int_pblock_get_next( &pCurrentPB ) == LDAP_SUCCESS )
+ && ( pCurrentPB != NULL ) );
+
+done:
+ return rc;
+}
+#endif /* defined(LDAP_SLAPI) */
diff --git a/servers/slapd/back-monitor/entry.c b/servers/slapd/back-monitor/entry.c
new file mode 100644
index 0000000..027dcc3
--- /dev/null
+++ b/servers/slapd/back-monitor/entry.c
@@ -0,0 +1,236 @@
+/* entry.c - monitor backend entry handling routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <slap.h>
+#include "back-monitor.h"
+
+int
+monitor_entry_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e
+)
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ monitor_entry_t *mp;
+
+ int rc = SLAP_CB_CONTINUE;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+ assert( e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )e->e_private;
+
+ if ( mp->mp_cb ) {
+ struct monitor_callback_t *mc;
+
+ for ( mc = mp->mp_cb; mc; mc = mc->mc_next ) {
+ if ( mc->mc_update ) {
+ rc = mc->mc_update( op, rs, e, mc->mc_private );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ break;
+ }
+ }
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE && mp->mp_info && mp->mp_info->mss_update ) {
+ rc = mp->mp_info->mss_update( op, rs, e );
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = LDAP_SUCCESS;
+ }
+
+ return rc;
+}
+
+int
+monitor_entry_create(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry *e_parent,
+ Entry **ep )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ monitor_entry_t *mp;
+
+ int rc = SLAP_CB_CONTINUE;
+
+ assert( mi != NULL );
+ assert( e_parent != NULL );
+ assert( e_parent->e_private != NULL );
+ assert( ep != NULL );
+
+ mp = ( monitor_entry_t * )e_parent->e_private;
+
+ if ( mp->mp_info && mp->mp_info->mss_create ) {
+ rc = mp->mp_info->mss_create( op, rs, ndn, e_parent, ep );
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = LDAP_SUCCESS;
+ }
+
+ return rc;
+}
+
+int
+monitor_entry_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e
+)
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ monitor_entry_t *mp;
+
+ int rc = SLAP_CB_CONTINUE;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+ assert( e->e_private != NULL );
+
+ mp = ( monitor_entry_t * )e->e_private;
+
+ if ( mp->mp_cb ) {
+ struct monitor_callback_t *mc;
+
+ for ( mc = mp->mp_cb; mc; mc = mc->mc_next ) {
+ if ( mc->mc_modify ) {
+ rc = mc->mc_modify( op, rs, e, mc->mc_private );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ break;
+ }
+ }
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE && mp->mp_info && mp->mp_info->mss_modify ) {
+ rc = mp->mp_info->mss_modify( op, rs, e );
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = LDAP_SUCCESS;
+ }
+
+ return rc;
+}
+
+int
+monitor_entry_test_flags(
+ monitor_entry_t *mp,
+ int cond
+)
+{
+ assert( mp != NULL );
+
+ return( ( mp->mp_flags & cond ) || ( mp->mp_info->mss_flags & cond ) );
+}
+
+monitor_entry_t *
+monitor_back_entrypriv_create( void )
+{
+ monitor_entry_t *mp;
+
+ mp = ( monitor_entry_t * )ch_calloc( sizeof( monitor_entry_t ), 1 );
+
+ mp->mp_next = NULL;
+ mp->mp_children = NULL;
+ mp->mp_info = NULL;
+ mp->mp_flags = MONITOR_F_NONE;
+ mp->mp_cb = NULL;
+
+ ldap_pvt_thread_mutex_init( &mp->mp_mutex );
+
+ return mp;
+}
+
+Entry *
+monitor_entry_stub(
+ struct berval *pdn,
+ struct berval *pndn,
+ struct berval *rdn,
+ ObjectClass *oc,
+ struct berval *create,
+ struct berval *modify
+)
+{
+ monitor_info_t *mi;
+ AttributeDescription *nad = NULL;
+ Entry *e;
+ struct berval nat;
+ char *ptr;
+ const char *text;
+ int rc;
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ nat = *rdn;
+ ptr = strchr( nat.bv_val, '=' );
+ nat.bv_len = ptr - nat.bv_val;
+ rc = slap_bv2ad( &nat, &nad, &text );
+ if ( rc )
+ return NULL;
+
+ e = entry_alloc();
+ if ( e ) {
+ struct berval nrdn;
+
+ rdnNormalize( 0, NULL, NULL, rdn, &nrdn, NULL );
+ build_new_dn( &e->e_name, pdn, rdn, NULL );
+ build_new_dn( &e->e_nname, pndn, &nrdn, NULL );
+ ber_memfree( nrdn.bv_val );
+ nat.bv_val = ptr + 1;
+ nat.bv_len = rdn->bv_len - ( nat.bv_val - rdn->bv_val );
+ attr_merge_normalize_one( e, slap_schema.si_ad_objectClass,
+ &oc->soc_cname, NULL );
+ attr_merge_normalize_one( e, slap_schema.si_ad_structuralObjectClass,
+ &oc->soc_cname, NULL );
+ attr_merge_normalize_one( e, nad, &nat, NULL );
+ attr_merge_one( e, slap_schema.si_ad_creatorsName, &mi->mi_creatorsName,
+ &mi->mi_ncreatorsName );
+ attr_merge_one( e, slap_schema.si_ad_modifiersName, &mi->mi_creatorsName,
+ &mi->mi_ncreatorsName );
+ attr_merge_normalize_one( e, slap_schema.si_ad_createTimestamp,
+ create ? create : &mi->mi_startTime, NULL );
+ attr_merge_normalize_one( e, slap_schema.si_ad_modifyTimestamp,
+ modify ? modify : &mi->mi_startTime, NULL );
+ }
+ return e;
+}
+
+Entry *
+monitor_entry_get_unlocked(
+ struct berval *ndn
+)
+{
+ monitor_info_t *mi = ( monitor_info_t * )be_monitor->be_private;
+ Entry *ret = NULL;
+
+ if ( !monitor_cache_get( mi, ndn, &ret ))
+ monitor_cache_release( mi, ret );
+ return ret;
+}
diff --git a/servers/slapd/back-monitor/init.c b/servers/slapd/back-monitor/init.c
new file mode 100644
index 0000000..af79fb4
--- /dev/null
+++ b/servers/slapd/back-monitor/init.c
@@ -0,0 +1,2567 @@
+/* init.c - initialize monitor backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include <lutil.h>
+#include "slap.h"
+#include "slap-config.h"
+#include "lber_pvt.h"
+#include "back-monitor.h"
+
+#include "slap-config.h"
+
+#undef INTEGRATE_CORE_SCHEMA
+
+/*
+ * used by many functions to add description to entries
+ *
+ * WARNING: be_monitor may change as new databases are added,
+ * so it should not be used outside monitor_back_db_init()
+ * until monitor_back_db_open is called.
+ */
+BackendDB *be_monitor;
+
+static struct monitor_subsys_t **monitor_subsys;
+static int monitor_subsys_opened;
+static monitor_info_t monitor_info;
+static const monitor_extra_t monitor_extra = {
+ monitor_back_is_configured,
+ monitor_back_get_subsys,
+ monitor_back_get_subsys_by_dn,
+
+ monitor_back_register_subsys,
+ monitor_back_register_backend,
+ monitor_back_register_database,
+ monitor_back_register_overlay_info,
+ monitor_back_register_overlay,
+ monitor_back_register_entry,
+ monitor_back_register_entry_parent,
+ monitor_back_register_entry_attrs,
+ monitor_back_register_entry_callback,
+
+ monitor_back_unregister_entry,
+ monitor_back_unregister_entry_parent,
+ monitor_back_unregister_entry_attrs,
+ monitor_back_unregister_entry_callback,
+
+ monitor_back_entry_stub,
+ monitor_back_entrypriv_create,
+ monitor_back_register_subsys_late,
+ monitor_back_entry_get_unlocked
+};
+
+
+/*
+ * subsystem data
+ *
+ * the known subsystems are added to the subsystems
+ * array at backend initialization; other subsystems
+ * may be added by calling monitor_back_register_subsys()
+ * before the database is opened (e.g. by other backends
+ * or by overlays or modules).
+ */
+static struct monitor_subsys_t known_monitor_subsys[] = {
+ {
+ SLAPD_MONITOR_BACKEND_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about available backends." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_backend_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_CONN_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about connections." ),
+ BER_BVNULL },
+ MONITOR_F_VOLATILE_CH,
+ monitor_subsys_conn_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_DATABASE_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about configured databases." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_database_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_LISTENER_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about active listeners." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_listener_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_LOG_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about logging." ),
+ BER_BVC( "Set the \"monitorLogLevel\" or \"monitorDebugLevel\" attributes to the desired levels." ),
+ BER_BVNULL },
+ MONITOR_F_NONE,
+ monitor_subsys_log_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL, /* modify */
+ }, {
+ SLAPD_MONITOR_OPS_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about performed operations." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_ops_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL, /* modify */
+ }, {
+ SLAPD_MONITOR_OVERLAY_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about available overlays." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_overlay_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL, /* modify */
+ }, {
+ SLAPD_MONITOR_SASL_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about SASL." ),
+ BER_BVNULL },
+ MONITOR_F_NONE,
+ NULL, /* init */
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_SENT_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains statistics." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_sent_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL, /* modify */
+ }, {
+ SLAPD_MONITOR_THREAD_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about threads." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_thread_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_TIME_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about time." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_time_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL, /* modify */
+ }, {
+ SLAPD_MONITOR_TLS_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about TLS." ),
+ BER_BVNULL },
+ MONITOR_F_NONE,
+ NULL, /* init */
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, {
+ SLAPD_MONITOR_RWW_NAME,
+ BER_BVNULL, BER_BVNULL, BER_BVNULL,
+ { BER_BVC( "This subsystem contains information about read/write waiters." ),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ monitor_subsys_rww_init,
+ NULL, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ }, { NULL }
+};
+
+int
+monitor_subsys_is_opened( void )
+{
+ return monitor_subsys_opened;
+}
+
+int
+monitor_back_register_subsys(
+ monitor_subsys_t *ms )
+{
+ int i = 0;
+
+ if ( monitor_subsys ) {
+ for ( ; monitor_subsys[ i ] != NULL; i++ )
+ /* just count'em */ ;
+ }
+
+ monitor_subsys = ch_realloc( monitor_subsys,
+ ( 2 + i ) * sizeof( monitor_subsys_t * ) );
+
+ if ( monitor_subsys == NULL ) {
+ return -1;
+ }
+
+ monitor_subsys[ i ] = ms;
+ monitor_subsys[ i + 1 ] = NULL;
+
+ /* if a subsystem is registered __AFTER__ subsystem
+ * initialization (depending on the sequence the databases
+ * are listed in slapd.conf), init it */
+ if ( monitor_subsys_is_opened() ) {
+
+ /* FIXME: this should only be possible
+ * if be_monitor is already initialized */
+ assert( be_monitor != NULL );
+
+ if ( ms->mss_open && ( *ms->mss_open )( be_monitor, ms ) ) {
+ return -1;
+ }
+
+ ms->mss_flags |= MONITOR_F_OPENED;
+ }
+
+ return 0;
+}
+
+enum {
+ LIMBO_ENTRY,
+ LIMBO_ENTRY_PARENT,
+ LIMBO_ATTRS,
+ LIMBO_CB,
+ LIMBO_BACKEND,
+ LIMBO_DATABASE,
+ LIMBO_OVERLAY_INFO,
+ LIMBO_OVERLAY,
+ LIMBO_SUBSYS,
+
+ LIMBO_LAST
+};
+
+typedef struct entry_limbo_t {
+ int el_type;
+ BackendInfo *el_bi;
+ BackendDB *el_be;
+ slap_overinst *el_on;
+ Entry *el_e;
+ Attribute *el_a;
+ struct berval *el_ndn;
+ struct berval el_nbase;
+ int el_scope;
+ struct berval el_filter;
+ monitor_callback_t *el_cb;
+ monitor_subsys_t *el_mss;
+ unsigned long el_flags;
+ struct entry_limbo_t *el_next;
+} entry_limbo_t;
+
+int
+monitor_back_is_configured( void )
+{
+ return be_monitor != NULL;
+}
+
+int
+monitor_back_register_subsys_late(
+ monitor_subsys_t *ms )
+{
+ entry_limbo_t **elpp, el = { 0 };
+ monitor_info_t *mi;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_subsys_late: "
+ "monitor database not configured.\n" );
+ return -1;
+ }
+
+ /* everything is ready, can register already */
+ if ( monitor_subsys_is_opened() ) {
+ return monitor_back_register_subsys( ms );
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+
+ el.el_type = LIMBO_SUBSYS;
+
+ el.el_mss = ms;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+
+ el.el_next = NULL;
+ **elpp = el;
+
+ return 0;
+}
+
+int
+monitor_back_register_backend(
+ BackendInfo *bi )
+{
+ return -1;
+}
+
+int
+monitor_back_register_overlay_info(
+ slap_overinst *on )
+{
+ return -1;
+}
+
+int
+monitor_back_register_backend_limbo(
+ BackendInfo *bi )
+{
+ return -1;
+}
+
+int
+monitor_back_register_database_limbo(
+ BackendDB *be,
+ struct berval *ndn_out )
+{
+ entry_limbo_t **elpp, el = { 0 };
+ monitor_info_t *mi;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_database_limbo: "
+ "monitor database not configured.\n" );
+ return -1;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+
+ el.el_type = LIMBO_DATABASE;
+
+ el.el_be = be->bd_self;
+ el.el_ndn = ndn_out;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+
+ el.el_next = NULL;
+ **elpp = el;
+
+ return 0;
+}
+
+int
+monitor_back_register_overlay_info_limbo(
+ slap_overinst *on )
+{
+ return -1;
+}
+
+int
+monitor_back_register_overlay_limbo(
+ BackendDB *be,
+ struct slap_overinst *on,
+ struct berval *ndn_out )
+{
+ entry_limbo_t **elpp, el = { 0 };
+ monitor_info_t *mi;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_overlay_limbo: "
+ "monitor database not configured.\n" );
+ return -1;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+
+ el.el_type = LIMBO_OVERLAY;
+
+ el.el_be = be->bd_self;
+ el.el_on = on;
+ el.el_ndn = ndn_out;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+
+ el.el_next = NULL;
+ **elpp = el;
+
+ return 0;
+}
+
+int
+monitor_back_register_entry(
+ Entry *e,
+ monitor_callback_t *cb,
+ monitor_subsys_t *mss,
+ unsigned long flags )
+{
+ monitor_info_t *mi;
+ int rc = 0;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "monitor database not configured.\n",
+ e->e_name.bv_val );
+ return -1;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+ assert( e->e_private == NULL );
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e_parent = NULL, *e_new = NULL;
+ struct berval pdn = BER_BVNULL;
+ monitor_entry_t *mp = NULL,
+ *mp_parent = NULL;
+
+ if ( monitor_cache_get( mi, &e->e_nname, &e_parent ) == 0 ) {
+ /* entry exists */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "entry exists\n",
+ e->e_name.bv_val );
+ monitor_cache_release( mi, e_parent );
+ return -1;
+ }
+
+ dnParent( &e->e_nname, &pdn );
+ if ( monitor_cache_get( mi, &pdn, &e_parent ) != 0 ) {
+ /* parent does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "parent \"%s\" not found\n",
+ e->e_name.bv_val, pdn.bv_val );
+ return -1;
+ }
+
+ assert( e_parent->e_private != NULL );
+ mp_parent = ( monitor_entry_t * )e_parent->e_private;
+
+ if ( mp_parent->mp_flags & MONITOR_F_VOLATILE ) {
+ /* entry is volatile; cannot append children */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "parent \"%s\" is volatile\n",
+ e->e_name.bv_val, e_parent->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "monitor_entrypriv_create() failed\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ e_new = entry_dup( e );
+ if ( e_new == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "entry_dup() failed\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ e_new->e_private = ( void * )mp;
+ if ( mss != NULL ) {
+ mp->mp_info = mss;
+ mp->mp_flags = flags;
+
+ } else {
+ mp->mp_info = mp_parent->mp_info;
+ mp->mp_flags = mp_parent->mp_flags | MONITOR_F_SUB;
+ }
+ mp->mp_cb = cb;
+
+ if ( monitor_cache_add( mi, e_new, e_parent ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "unable to add entry\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+done:;
+ if ( rc ) {
+ if ( mp ) {
+ ch_free( mp );
+ }
+ if ( e_new ) {
+ e_new->e_private = NULL;
+ entry_free( e_new );
+ }
+ }
+
+ if ( e_parent ) {
+ monitor_cache_release( mi, e_parent );
+ }
+
+ } else {
+ entry_limbo_t **elpp, el = { 0 };
+
+ el.el_type = LIMBO_ENTRY;
+
+ el.el_e = entry_dup( e );
+ if ( el.el_e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "entry_dup() failed\n",
+ e->e_name.bv_val );
+ return -1;
+ }
+
+ el.el_cb = cb;
+ el.el_mss = mss;
+ el.el_flags = flags;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+ if ( *elpp == NULL ) {
+ el.el_e->e_private = NULL;
+ entry_free( el.el_e );
+ return -1;
+ }
+
+ el.el_next = NULL;
+ **elpp = el;
+ }
+
+ return rc;
+}
+
+int
+monitor_back_register_entry_parent(
+ Entry *e,
+ monitor_callback_t *cb,
+ monitor_subsys_t *mss,
+ unsigned long flags,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ monitor_info_t *mi;
+ struct berval ndn = BER_BVNULL;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(base=\"%s\" scope=%s filter=\"%s\"): "
+ "monitor database not configured.\n",
+ BER_BVISNULL( nbase ) ? "" : nbase->bv_val,
+ ldap_pvt_scope2str( scope ),
+ BER_BVISNULL( filter ) ? "" : filter->bv_val );
+ return -1;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+ assert( e->e_private == NULL );
+
+ if ( BER_BVISNULL( filter ) ) {
+ /* need a filter */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"\"): "
+ "need a valid filter\n" );
+ return -1;
+ }
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e_parent = NULL, *e_new = NULL;
+ struct berval e_name = BER_BVNULL,
+ e_nname = BER_BVNULL;
+ monitor_entry_t *mp = NULL,
+ *mp_parent = NULL;
+ int rc = 0;
+
+ if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"\"): "
+ "base=\"%s\" scope=%s filter=\"%s\": "
+ "unable to find entry\n",
+ nbase->bv_val ? nbase->bv_val : "\"\"",
+ ldap_pvt_scope2str( scope ),
+ filter->bv_val );
+ return -1;
+ }
+
+ if ( monitor_cache_get( mi, &ndn, &e_parent ) != 0 ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"%s\"): "
+ "parent entry does not exist\n",
+ ndn.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ assert( e_parent->e_private != NULL );
+ mp_parent = ( monitor_entry_t * )e_parent->e_private;
+
+ if ( mp_parent->mp_flags & MONITOR_F_VOLATILE ) {
+ /* entry is volatile; cannot append callback */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"%s\"): "
+ "entry is volatile\n",
+ e_parent->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ build_new_dn( &e_name, &e_parent->e_name, &e->e_name, NULL );
+ build_new_dn( &e_nname, &e_parent->e_nname, &e->e_nname, NULL );
+
+ if ( monitor_cache_get( mi, &e_nname, &e_new ) == 0 ) {
+ /* entry already exists */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"%s\"): "
+ "entry already exists\n",
+ e_name.bv_val );
+ monitor_cache_release( mi, e_new );
+ e_new = NULL;
+ rc = -1;
+ goto done;
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_parent(\"%s\"): "
+ "monitor_entrypriv_create() failed\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ e_new = entry_dup( e );
+ if ( e_new == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "entry_dup() failed\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+ ch_free( e_new->e_name.bv_val );
+ ch_free( e_new->e_nname.bv_val );
+ e_new->e_name = e_name;
+ e_new->e_nname = e_nname;
+
+ e_new->e_private = ( void * )mp;
+ if ( mss != NULL ) {
+ mp->mp_info = mss;
+ mp->mp_flags = flags;
+
+ } else {
+ mp->mp_info = mp_parent->mp_info;
+ mp->mp_flags = mp_parent->mp_flags | MONITOR_F_SUB;
+ }
+ mp->mp_cb = cb;
+
+ if ( monitor_cache_add( mi, e_new, e_parent ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "unable to add entry\n",
+ e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+done:;
+ if ( !BER_BVISNULL( &ndn ) ) {
+ ch_free( ndn.bv_val );
+ }
+
+ if ( rc ) {
+ if ( mp ) {
+ ch_free( mp );
+ }
+ if ( e_new ) {
+ e_new->e_private = NULL;
+ entry_free( e_new );
+ }
+ }
+
+ if ( e_parent ) {
+ monitor_cache_release( mi, e_parent );
+ }
+
+ } else {
+ entry_limbo_t **elpp = NULL, el = { 0 };
+
+ el.el_type = LIMBO_ENTRY_PARENT;
+
+ el.el_e = entry_dup( e );
+ if ( el.el_e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry(\"%s\"): "
+ "entry_dup() failed\n",
+ e->e_name.bv_val );
+ goto done_limbo;
+ }
+
+ if ( !BER_BVISNULL( nbase ) ) {
+ ber_dupbv( &el.el_nbase, nbase );
+ }
+
+ el.el_scope = scope;
+ if ( !BER_BVISNULL( filter ) ) {
+ ber_dupbv( &el.el_filter, filter );
+ }
+
+ el.el_cb = cb;
+ el.el_mss = mss;
+ el.el_flags = flags;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+ if ( *elpp == NULL ) {
+ goto done_limbo;
+ }
+
+done_limbo:;
+ if ( *elpp != NULL ) {
+ el.el_next = NULL;
+ **elpp = el;
+
+ } else {
+ if ( !BER_BVISNULL( &el.el_filter ) ) {
+ ch_free( el.el_filter.bv_val );
+ }
+ if ( !BER_BVISNULL( &el.el_nbase ) ) {
+ ch_free( el.el_nbase.bv_val );
+ }
+ entry_free( el.el_e );
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+monitor_search2ndn_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ struct berval *ndn = op->o_callback->sc_private;
+
+ if ( !BER_BVISNULL( ndn ) ) {
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ ch_free( ndn->bv_val );
+ BER_BVZERO( ndn );
+ return rs->sr_err;
+ }
+
+ ber_dupbv( ndn, &rs->sr_entry->e_nname );
+ }
+
+ return 0;
+}
+
+int
+monitor_search2ndn(
+ struct berval *nbase,
+ int scope,
+ struct berval *filter,
+ struct berval *ndn )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+ SlapReply rs = { REP_RESULT };
+ slap_callback cb = { NULL, monitor_search2ndn_cb, NULL, NULL };
+ int rc;
+
+ BER_BVZERO( ndn );
+
+ if ( be_monitor == NULL ) {
+ return -1;
+ }
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+
+ /* use global malloc for now */
+ if ( op->o_tmpmemctx ) {
+ op->o_tmpmemctx = NULL;
+ }
+ op->o_tmpmfuncs = &ch_mfuncs;
+
+ op->o_bd = be_monitor;
+ if ( nbase == NULL || BER_BVISNULL( nbase ) ) {
+ ber_dupbv_x( &op->o_req_dn, &op->o_bd->be_suffix[ 0 ],
+ op->o_tmpmemctx );
+ ber_dupbv_x( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ],
+ op->o_tmpmemctx );
+
+ } else {
+ if ( dnPrettyNormal( NULL, nbase, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx ) ) {
+ return -1;
+ }
+ }
+
+ op->o_callback = &cb;
+ cb.sc_private = (void *)ndn;
+
+ op->ors_scope = scope;
+ op->ors_filter = str2filter_x( op, filter->bv_val );
+ if ( op->ors_filter == NULL ) {
+ rc = LDAP_OTHER;
+ goto cleanup;
+ }
+ ber_dupbv_x( &op->ors_filterstr, filter, op->o_tmpmemctx );
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->ors_attrsonly = 0;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = 1;
+ op->ors_limit = NULL;
+ op->ors_deref = LDAP_DEREF_NEVER;
+
+ op->o_nocaching = 1;
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+
+ op->o_dn = be_monitor->be_rootdn;
+ op->o_ndn = be_monitor->be_rootndn;
+
+ rc = op->o_bd->be_search( op, &rs );
+
+cleanup:;
+ if ( op->ors_filter != NULL ) {
+ filter_free_x( op, op->ors_filter, 1 );
+ }
+ if ( !BER_BVISNULL( &op->ors_filterstr ) ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op->o_req_dn ) ) {
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ if ( BER_BVISNULL( ndn ) ) {
+ rc = -1;
+ }
+ break;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ default:
+ if ( !BER_BVISNULL( ndn ) ) {
+ ber_memfree( ndn->bv_val );
+ BER_BVZERO( ndn );
+ }
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+int
+monitor_back_register_entry_attrs(
+ struct berval *ndn_in,
+ Attribute *a,
+ monitor_callback_t *cb,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ monitor_info_t *mi;
+ struct berval ndn = BER_BVNULL;
+ char *fname = ( a == NULL ? "callback" : "attrs" );
+ struct berval empty_bv = BER_BVC("");
+
+ if ( nbase == NULL ) nbase = &empty_bv;
+ if ( filter == NULL ) filter = &empty_bv;
+
+ if ( be_monitor == NULL ) {
+ Debug(LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(base=\"%s\" scope=%s filter=\"%s\"): " "monitor database not configured.\n\n",
+ fname, BER_BVISNULL(nbase) ? "" : nbase->bv_val,
+ ldap_pvt_scope2str(scope),
+ BER_BVISNULL(filter) ? "" : filter->bv_val );
+
+ return -1;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+
+ if ( ndn_in != NULL ) {
+ ndn = *ndn_in;
+ }
+
+ if ( a == NULL && cb == NULL ) {
+ /* nothing to do */
+ return -1;
+ }
+
+ if ( ( ndn_in == NULL || BER_BVISNULL( &ndn ) )
+ && BER_BVISNULL( filter ) )
+ {
+ /* need a filter */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(\"\"): "
+ "need a valid filter\n",
+ fname );
+ return -1;
+ }
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e = NULL;
+ Attribute **atp = NULL;
+ monitor_entry_t *mp = NULL;
+ monitor_callback_t **mcp = NULL;
+ int rc = 0;
+ int freeit = 0;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) {
+ Debug(LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(\"\"): " "base=\"%s\" scope=%s filter=\"%s\": " "unable to find entry\n\n",
+ fname,
+ nbase->bv_val ? nbase->bv_val : "\"\"",
+ ldap_pvt_scope2str(scope),
+ filter->bv_val );
+ return -1;
+ }
+
+ freeit = 1;
+ }
+
+ if ( monitor_cache_get( mi, &ndn, &e ) != 0 ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(\"%s\"): "
+ "entry does not exist\n",
+ fname, ndn.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ assert( e->e_private != NULL );
+ mp = ( monitor_entry_t * )e->e_private;
+
+ if ( mp->mp_flags & MONITOR_F_VOLATILE ) {
+ /* entry is volatile; cannot append callback */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(\"%s\"): "
+ "entry is volatile\n",
+ fname, e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+
+ if ( a ) {
+ for ( atp = &e->e_attrs; *atp; atp = &(*atp)->a_next )
+ /* just get to last */ ;
+
+ for ( ; a != NULL; a = a->a_next ) {
+ assert( a->a_desc != NULL );
+ assert( a->a_vals != NULL );
+
+ if ( attr_find( e->e_attrs, a->a_desc ) ) {
+ attr_merge( e, a->a_desc, a->a_vals,
+ a->a_nvals == a->a_vals ? NULL : a->a_nvals );
+
+ } else {
+ *atp = attr_dup( a );
+ if ( *atp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_register_entry_%s(\"%s\"): "
+ "attr_dup() failed\n",
+ fname, e->e_name.bv_val );
+ rc = -1;
+ goto done;
+ }
+ atp = &(*atp)->a_next;
+ }
+ }
+ }
+
+ if ( cb ) {
+ for ( mcp = &mp->mp_cb; *mcp; mcp = &(*mcp)->mc_next )
+ /* go to tail */ ;
+
+ /* NOTE: we do not clear cb->mc_next, so this function
+ * can be used to append a list of callbacks */
+ (*mcp) = cb;
+ }
+
+done:;
+ if ( rc ) {
+ if ( atp && *atp ) {
+ attrs_free( *atp );
+ *atp = NULL;
+ }
+ }
+
+ if ( freeit ) {
+ ber_memfree( ndn.bv_val );
+ }
+
+ if ( e ) {
+ monitor_cache_release( mi, e );
+ }
+
+ } else {
+ entry_limbo_t **elpp, el = { 0 };
+
+ el.el_type = LIMBO_ATTRS;
+ el.el_ndn = ndn_in;
+ if ( !BER_BVISNULL( nbase ) ) {
+ ber_dupbv( &el.el_nbase, nbase);
+ }
+ el.el_scope = scope;
+ if ( !BER_BVISNULL( filter ) ) {
+ ber_dupbv( &el.el_filter, filter );
+ }
+
+ el.el_a = attrs_dup( a );
+ el.el_cb = cb;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ /* go to last */;
+
+ *elpp = (entry_limbo_t *)ch_malloc( sizeof( entry_limbo_t ) );
+ if ( *elpp == NULL ) {
+ if ( !BER_BVISNULL( &el.el_filter ) ) {
+ ch_free( el.el_filter.bv_val );
+ }
+ if ( el.el_a != NULL ) {
+ attrs_free( el.el_a );
+ }
+ if ( !BER_BVISNULL( &el.el_nbase ) ) {
+ ch_free( &el.el_nbase.bv_val );
+ }
+ return -1;
+ }
+
+ el.el_next = NULL;
+ **elpp = el;
+ }
+
+ return 0;
+}
+
+int
+monitor_back_register_entry_callback(
+ struct berval *ndn,
+ monitor_callback_t *cb,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ return monitor_back_register_entry_attrs( ndn, NULL, cb,
+ nbase, scope, filter );
+}
+
+/*
+ * TODO: add corresponding calls to remove installed callbacks, entries
+ * and so, in case the entity that installed them is removed (e.g. a
+ * database, via back-config)
+ */
+int
+monitor_back_unregister_entry(
+ struct berval *ndn )
+{
+ monitor_info_t *mi;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry(\"%s\"): "
+ "monitor database not configured.\n",
+ ndn->bv_val );
+
+ return -1;
+ }
+
+ /* entry will be regularly freed, and resources released
+ * according to callbacks */
+ if ( slapd_shutdown ) {
+ return 0;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e = NULL;
+ monitor_entry_t *mp = NULL;
+ monitor_callback_t *cb = NULL;
+
+ if ( monitor_cache_remove( mi, ndn, &e ) != 0 ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry(\"%s\"): "
+ "entry removal failed.\n",
+ ndn->bv_val );
+ return -1;
+ }
+
+ mp = (monitor_entry_t *)e->e_private;
+ assert( mp != NULL );
+
+ for ( cb = mp->mp_cb; cb != NULL; ) {
+ monitor_callback_t *next = cb->mc_next;
+
+ if ( cb->mc_free ) {
+ (void)cb->mc_free( e, &cb->mc_private );
+ }
+ ch_free( cb );
+
+ cb = next;
+ }
+
+ ch_free( mp );
+ e->e_private = NULL;
+ entry_free( e );
+
+ } else {
+ entry_limbo_t **elpp;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ {
+ entry_limbo_t *elp = *elpp;
+
+ if ( elp->el_type == LIMBO_ENTRY
+ && dn_match( ndn, &elp->el_e->e_nname ) )
+ {
+ monitor_callback_t *cb, *next;
+
+ for ( cb = elp->el_cb; cb; cb = next ) {
+ /* FIXME: call callbacks? */
+ next = cb->mc_next;
+ if ( cb->mc_dispose ) {
+ cb->mc_dispose( &cb->mc_private );
+ }
+ ch_free( cb );
+ }
+ assert( elp->el_e != NULL );
+ elp->el_e->e_private = NULL;
+ entry_free( elp->el_e );
+ *elpp = elp->el_next;
+ ch_free( elp );
+ elpp = NULL;
+ break;
+ }
+ }
+
+ if ( elpp != NULL ) {
+ /* not found! where did it go? */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+monitor_back_unregister_entry_parent(
+ struct berval *nrdn,
+ monitor_callback_t *target_cb,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ monitor_info_t *mi;
+ struct berval ndn = BER_BVNULL;
+
+ if ( be_monitor == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_parent(base=\"%s\" scope=%s filter=\"%s\"): "
+ "monitor database not configured.\n",
+ BER_BVISNULL( nbase ) ? "" : nbase->bv_val,
+ ldap_pvt_scope2str( scope ),
+ BER_BVISNULL( filter ) ? "" : filter->bv_val );
+
+ return -1;
+ }
+
+ /* entry will be regularly freed, and resources released
+ * according to callbacks */
+ if ( slapd_shutdown ) {
+ return 0;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+
+ if ( ( nrdn == NULL || BER_BVISNULL( nrdn ) )
+ && BER_BVISNULL( filter ) )
+ {
+ /* need a filter */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_parent(\"\"): "
+ "need a valid filter\n" );
+ return -1;
+ }
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e = NULL;
+ monitor_entry_t *mp = NULL;
+
+ if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_parent(\"\"): "
+ "base=\"%s\" scope=%s filter=\"%s\": "
+ "unable to find entry\n",
+ nbase->bv_val ? nbase->bv_val : "\"\"",
+ ldap_pvt_scope2str( scope ),
+ filter->bv_val );
+ return -1;
+ }
+
+ if ( monitor_cache_remove( mi, &ndn, &e ) != 0 ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry(\"%s\"): "
+ "entry removal failed.\n",
+ ndn.bv_val );
+ ber_memfree( ndn.bv_val );
+ return -1;
+ }
+ ber_memfree( ndn.bv_val );
+
+ mp = (monitor_entry_t *)e->e_private;
+ assert( mp != NULL );
+
+ if ( target_cb != NULL ) {
+ monitor_callback_t **cbp;
+
+ for ( cbp = &mp->mp_cb; *cbp != NULL; cbp = &(*cbp)->mc_next ) {
+ if ( *cbp == target_cb ) {
+ if ( (*cbp)->mc_free ) {
+ (void)(*cbp)->mc_free( e, &(*cbp)->mc_private );
+ }
+ *cbp = (*cbp)->mc_next;
+ ch_free( target_cb );
+ break;
+ }
+ }
+ }
+
+
+ ch_free( mp );
+ e->e_private = NULL;
+ entry_free( e );
+
+ } else {
+ entry_limbo_t **elpp;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ {
+ entry_limbo_t *elp = *elpp;
+
+ if ( elp->el_type == LIMBO_ENTRY_PARENT
+ && dn_match( nrdn, &elp->el_e->e_nname )
+ && dn_match( nbase, &elp->el_nbase )
+ && scope == elp->el_scope
+ && bvmatch( filter, &elp->el_filter ) )
+ {
+ monitor_callback_t *cb, *next;
+
+ for ( cb = elp->el_cb; cb; cb = next ) {
+ /* FIXME: call callbacks? */
+ next = cb->mc_next;
+ if ( cb->mc_dispose ) {
+ cb->mc_dispose( &cb->mc_private );
+ }
+ ch_free( cb );
+ }
+ assert( elp->el_e != NULL );
+ elp->el_e->e_private = NULL;
+ entry_free( elp->el_e );
+ if ( !BER_BVISNULL( &elp->el_nbase ) ) {
+ ch_free( elp->el_nbase.bv_val );
+ }
+ if ( !BER_BVISNULL( &elp->el_filter ) ) {
+ ch_free( elp->el_filter.bv_val );
+ }
+ *elpp = elp->el_next;
+ ch_free( elp );
+ elpp = NULL;
+ break;
+ }
+ }
+
+ if ( elpp != NULL ) {
+ /* not found! where did it go? */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+monitor_back_unregister_entry_attrs(
+ struct berval *ndn_in,
+ Attribute *target_a,
+ monitor_callback_t *target_cb,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ monitor_info_t *mi;
+ struct berval ndn = BER_BVNULL;
+ char *fname = ( target_a == NULL ? "callback" : "attrs" );
+
+ if ( be_monitor == NULL ) {
+ Debug(LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_%s(base=\"%s\" scope=%s filter=\"%s\"): " "monitor database not configured.\n\n",
+ fname, BER_BVISNULL(nbase) ? "" : nbase->bv_val,
+ ldap_pvt_scope2str(scope),
+ BER_BVISNULL(filter) ? "" : filter->bv_val );
+
+ return -1;
+ }
+
+ /* entry will be regularly freed, and resources released
+ * according to callbacks */
+ if ( slapd_shutdown ) {
+ return 0;
+ }
+
+ mi = ( monitor_info_t * )be_monitor->be_private;
+
+ assert( mi != NULL );
+
+ if ( ndn_in != NULL ) {
+ ndn = *ndn_in;
+ }
+
+ if ( target_a == NULL && target_cb == NULL ) {
+ /* nothing to do */
+ return -1;
+ }
+
+ if ( ( ndn_in == NULL || BER_BVISNULL( &ndn ) )
+ && BER_BVISNULL( filter ) )
+ {
+ /* need a filter */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_%s(\"\"): "
+ "need a valid filter\n",
+ fname );
+ return -1;
+ }
+
+ if ( monitor_subsys_is_opened() ) {
+ Entry *e = NULL;
+ monitor_entry_t *mp = NULL;
+ int freeit = 0;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ if ( monitor_search2ndn( nbase, scope, filter, &ndn ) ) {
+ Debug(LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry_%s(\"\"): " "base=\"%s\" scope=%d filter=\"%s\": " "unable to find entry\n\n",
+ fname,
+ nbase->bv_val ? nbase->bv_val : "\"\"",
+ scope, filter->bv_val );
+ return -1;
+ }
+
+ freeit = 1;
+ }
+
+ if ( monitor_cache_get( mi, &ndn, &e ) != 0 ) {
+ /* entry does not exist */
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_unregister_entry(\"%s\"): "
+ "entry removal failed.\n",
+ ndn.bv_val );
+ return -1;
+ }
+
+ mp = (monitor_entry_t *)e->e_private;
+ assert( mp != NULL );
+
+ if ( target_cb != NULL ) {
+ monitor_callback_t **cbp;
+
+ for ( cbp = &mp->mp_cb; *cbp != NULL; cbp = &(*cbp)->mc_next ) {
+ if ( *cbp == target_cb ) {
+ if ( (*cbp)->mc_free ) {
+ (void)(*cbp)->mc_free( e, &(*cbp)->mc_private );
+ }
+ *cbp = (*cbp)->mc_next;
+ ch_free( target_cb );
+ break;
+ }
+ }
+ }
+
+ if ( target_a != NULL ) {
+ Attribute *a;
+
+ for ( a = target_a; a != NULL; a = a->a_next ) {
+ Modification mod = { 0 };
+ const char *text;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ mod.sm_op = LDAP_MOD_DELETE;
+ mod.sm_desc = a->a_desc;
+ mod.sm_values = a->a_vals;
+ mod.sm_nvalues = a->a_nvals;
+
+ (void)modify_delete_values( e, &mod, 1,
+ &text, textbuf, sizeof( textbuf ) );
+ }
+ }
+
+ if ( freeit ) {
+ ber_memfree( ndn.bv_val );
+ }
+
+ monitor_cache_release( mi, e );
+
+ } else {
+ entry_limbo_t **elpp;
+
+ for ( elpp = &mi->mi_entry_limbo;
+ *elpp;
+ elpp = &(*elpp)->el_next )
+ {
+ entry_limbo_t *elp = *elpp;
+
+ if ( elp->el_type == LIMBO_ATTRS
+ && dn_match( nbase, &elp->el_nbase )
+ && scope == elp->el_scope
+ && bvmatch( filter, &elp->el_filter ) )
+ {
+ monitor_callback_t *cb, *next;
+
+ for ( cb = elp->el_cb; cb; cb = next ) {
+ /* FIXME: call callbacks? */
+ next = cb->mc_next;
+ if ( cb->mc_dispose ) {
+ cb->mc_dispose( &cb->mc_private );
+ }
+ ch_free( cb );
+ }
+ assert( elp->el_e == NULL );
+ if ( elp->el_a != NULL ) {
+ attrs_free( elp->el_a );
+ }
+ if ( !BER_BVISNULL( &elp->el_nbase ) ) {
+ ch_free( elp->el_nbase.bv_val );
+ }
+ if ( !BER_BVISNULL( &elp->el_filter ) ) {
+ ch_free( elp->el_filter.bv_val );
+ }
+ *elpp = elp->el_next;
+ ch_free( elp );
+ elpp = NULL;
+ break;
+ }
+ }
+
+ if ( elpp != NULL ) {
+ /* not found! where did it go? */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+monitor_back_unregister_entry_callback(
+ struct berval *ndn,
+ monitor_callback_t *cb,
+ struct berval *nbase,
+ int scope,
+ struct berval *filter )
+{
+ /* TODO: lookup entry (by ndn, if not NULL, and/or by callback);
+ * unregister the callback; if a is not null, unregister the
+ * given attrs. In any case, call cb->cb_free */
+ return monitor_back_unregister_entry_attrs( ndn,
+ NULL, cb, nbase, scope, filter );
+}
+
+monitor_subsys_t *
+monitor_back_get_subsys( const char *name )
+{
+ if ( monitor_subsys != NULL ) {
+ int i;
+
+ for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) {
+ if ( strcasecmp( monitor_subsys[ i ]->mss_name, name ) == 0 ) {
+ return monitor_subsys[ i ];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+monitor_subsys_t *
+monitor_back_get_subsys_by_dn(
+ struct berval *ndn,
+ int sub )
+{
+ if ( monitor_subsys != NULL ) {
+ int i;
+
+ if ( sub ) {
+ for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) {
+ if ( dnIsSuffix( ndn, &monitor_subsys[ i ]->mss_ndn ) ) {
+ return monitor_subsys[ i ];
+ }
+ }
+
+ } else {
+ for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) {
+ if ( dn_match( ndn, &monitor_subsys[ i ]->mss_ndn ) ) {
+ return monitor_subsys[ i ];
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+int
+monitor_back_initialize(
+ BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_MANAGEDSAIT,
+ NULL
+ };
+
+ static ConfigTable monitorcfg[] = {
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+ };
+
+ static ConfigOCs monitorocs[] = {
+ { "( OLcfgDbOc:4.1 "
+ "NAME 'olcMonitorConfig' "
+ "DESC 'Monitor backend configuration' "
+ "SUP olcDatabaseConfig "
+ ")",
+ Cft_Database, monitorcfg },
+ { NULL, 0, NULL }
+ };
+
+ struct m_s {
+ char *schema;
+ slap_mask_t flags;
+ int offset;
+ } moc[] = {
+ { "( 1.3.6.1.4.1.4203.666.3.16.1 "
+ "NAME 'monitor' "
+ "DESC 'OpenLDAP system monitoring' "
+ "SUP top STRUCTURAL "
+ "MUST cn "
+ "MAY ( "
+ "description "
+ "$ seeAlso "
+ "$ labeledURI "
+ "$ monitoredInfo "
+ "$ managedInfo "
+ "$ monitorOverlay "
+ ") )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitor) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.2 "
+ "NAME 'monitorServer' "
+ "DESC 'Server monitoring root entry' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitorServer) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.3 "
+ "NAME 'monitorContainer' "
+ "DESC 'monitor container class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitorContainer) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.4 "
+ "NAME 'monitorCounterObject' "
+ "DESC 'monitor counter class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitorCounterObject) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.5 "
+ "NAME 'monitorOperation' "
+ "DESC 'monitor operation class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitorOperation) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.6 "
+ "NAME 'monitorConnection' "
+ "DESC 'monitor connection class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitorConnection) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.7 "
+ "NAME 'managedObject' "
+ "DESC 'monitor managed entity class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_managedObject) },
+ { "( 1.3.6.1.4.1.4203.666.3.16.8 "
+ "NAME 'monitoredObject' "
+ "DESC 'monitor monitored entity class' "
+ "SUP monitor STRUCTURAL )", SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(monitor_info_t, mi_oc_monitoredObject) },
+ { NULL, 0, -1 }
+ }, mat[] = {
+ { "( 1.3.6.1.4.1.4203.666.1.55.1 "
+ "NAME 'monitoredInfo' "
+ "DESC 'monitored info' "
+ /* "SUP name " */
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitoredInfo) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.2 "
+ "NAME 'managedInfo' "
+ "DESC 'monitor managed info' "
+ "SUP name )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_managedInfo) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.3 "
+ "NAME 'monitorCounter' "
+ "DESC 'monitor counter' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorCounter) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.4 "
+ "NAME 'monitorOpCompleted' "
+ "DESC 'monitor completed operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorOpCompleted) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.5 "
+ "NAME 'monitorOpInitiated' "
+ "DESC 'monitor initiated operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorOpInitiated) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.6 "
+ "NAME 'monitorConnectionNumber' "
+ "DESC 'monitor connection number' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionNumber) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.7 "
+ "NAME 'monitorConnectionAuthzDN' "
+ "DESC 'monitor connection authorization DN' "
+ /* "SUP distinguishedName " */
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionAuthzDN) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.8 "
+ "NAME 'monitorConnectionLocalAddress' "
+ "DESC 'monitor connection local address' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionLocalAddress) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.9 "
+ "NAME 'monitorConnectionPeerAddress' "
+ "DESC 'monitor connection peer address' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionPeerAddress) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.10 "
+ "NAME 'monitorTimestamp' "
+ "DESC 'monitor timestamp' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorTimestamp) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.11 "
+ "NAME 'monitorOverlay' "
+ "DESC 'name of overlays defined for a given database' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorOverlay) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.12 "
+ "NAME 'readOnly' "
+ "DESC 'read/write status of a given database' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_readOnly) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.13 "
+ "NAME 'restrictedOperation' "
+ "DESC 'name of restricted operation for a given database' "
+ "SUP managedInfo )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_restrictedOperation ) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.14 "
+ "NAME 'monitorConnectionProtocol' "
+ "DESC 'monitor connection protocol' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionProtocol) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.15 "
+ "NAME 'monitorConnectionOpsReceived' "
+ "DESC 'monitor number of operations received by the connection' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionOpsReceived) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.16 "
+ "NAME 'monitorConnectionOpsExecuting' "
+ "DESC 'monitor number of operations in execution within the connection' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionOpsExecuting) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.17 "
+ "NAME 'monitorConnectionOpsPending' "
+ "DESC 'monitor number of pending operations within the connection' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionOpsPending) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.18 "
+ "NAME 'monitorConnectionOpsCompleted' "
+ "DESC 'monitor number of operations completed within the connection' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionOpsCompleted) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.19 "
+ "NAME 'monitorConnectionGet' "
+ "DESC 'number of times connection_get() was called so far' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionGet) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.20 "
+ "NAME 'monitorConnectionRead' "
+ "DESC 'number of times connection_read() was called so far' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionRead) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.21 "
+ "NAME 'monitorConnectionWrite' "
+ "DESC 'number of times connection_write() was called so far' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionWrite) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.22 "
+ "NAME 'monitorConnectionMask' "
+ "DESC 'monitor connection mask' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionMask) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.23 "
+ "NAME 'monitorConnectionListener' "
+ "DESC 'monitor connection listener' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionListener) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.24 "
+ "NAME 'monitorConnectionPeerDomain' "
+ "DESC 'monitor connection peer domain' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionPeerDomain) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.25 "
+ "NAME 'monitorConnectionStartTime' "
+ "DESC 'monitor connection start time' "
+ "SUP monitorTimestamp "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionStartTime) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.26 "
+ "NAME 'monitorConnectionActivityTime' "
+ "DESC 'monitor connection activity time' "
+ "SUP monitorTimestamp "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionActivityTime) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.27 "
+ "NAME 'monitorIsShadow' "
+ "DESC 'TRUE if the database is shadow' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorIsShadow) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.28 "
+ "NAME 'monitorUpdateRef' "
+ "DESC 'update referral for shadow databases' "
+ "SUP monitoredInfo "
+ "SINGLE-VALUE "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorUpdateRef) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.29 "
+ "NAME 'monitorRuntimeConfig' "
+ "DESC 'TRUE if component allows runtime configuration' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE "
+ "USAGE dSAOperation )", SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorRuntimeConfig) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.30 "
+ "NAME 'monitorSuperiorDN' "
+ "DESC 'monitor superior DN' "
+ /* "SUP distinguishedName " */
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorSuperiorDN) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.31 "
+ "NAME 'monitorConnectionOpsAsync' "
+ "DESC 'monitor number of asynchronous operations in execution within the connection' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorConnectionOpsAsync) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.32 "
+ "NAME 'monitorLogLevel' "
+ "DESC 'current slapd log level' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorLogLevel) },
+ { "( 1.3.6.1.4.1.4203.666.1.55.33 "
+ "NAME 'monitorDebugLevel' "
+ "DESC 'current slapd debug level' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "USAGE dSAOperation )", SLAP_AT_FINAL|SLAP_AT_HIDE,
+ offsetof(monitor_info_t, mi_ad_monitorDebugLevel) },
+ { NULL, 0, -1 }
+ };
+
+ static struct {
+ char *name;
+ char *oid;
+ } s_oid[] = {
+ { "olmAttributes", "1.3.6.1.4.1.4203.666.1.55" },
+ { "olmSubSystemAttributes", "olmAttributes:0" },
+ { "olmGenericAttributes", "olmSubSystemAttributes:0" },
+ { "olmDatabaseAttributes", "olmSubSystemAttributes:1" },
+ { "olmOverlayAttributes", "olmSubSystemAttributes:2" },
+ { "olmModuleAttributes", "olmSubSystemAttributes:3" },
+
+ /* for example, back-mdb specific attrs
+ * are in "olmDatabaseAttributes:12"
+ *
+ * NOTE: developers, please record here OID assignments
+ * for other modules */
+
+ { "olmObjectClasses", "1.3.6.1.4.1.4203.666.3.16" },
+ { "olmSubSystemObjectClasses", "olmObjectClasses:0" },
+ { "olmGenericObjectClasses", "olmSubSystemObjectClasses:0" },
+ { "olmDatabaseObjectClasses", "olmSubSystemObjectClasses:1" },
+ { "olmOverlayObjectClasses", "olmSubSystemObjectClasses:2" },
+ { "olmModuleObjectClasses", "olmSubSystemObjectClasses:3" },
+
+ /* for example, back-mdb specific objectClasses
+ * are in "olmDatabaseObjectClasses:12"
+ *
+ * NOTE: developers, please record here OID assignments
+ * for other modules */
+
+ { NULL }
+ };
+
+ int i, rc;
+ monitor_info_t *mi = &monitor_info;
+ ConfigArgs c;
+ char *argv[ 3 ];
+
+ argv[ 0 ] = "monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+
+ for ( i = 0; s_oid[ i ].name; i++ ) {
+ argv[ 1 ] = s_oid[ i ].name;
+ argv[ 2 ] = s_oid[ i ].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_initialize: unable to add "
+ "objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid );
+ return 1;
+ }
+ }
+
+ /* schema integration */
+ for ( i = 0; mat[ i ].schema; i++ ) {
+ int code;
+ AttributeDescription **ad =
+ ((AttributeDescription **)&(((char *)mi)[ mat[ i ].offset ]));
+
+ *ad = NULL;
+ code = register_at( mat[ i ].schema, ad, 0 );
+
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_db_init: register_at failed\n" );
+ return -1;
+ }
+ (*ad)->ad_type->sat_flags |= mat[ i ].flags;
+ }
+
+ for ( i = 0; moc[ i ].schema; i++ ) {
+ int code;
+ ObjectClass **Oc =
+ ((ObjectClass **)&(((char *)mi)[ moc[ i ].offset ]));
+
+ code = register_oc( moc[ i ].schema, Oc, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_back_db_init: register_oc failed\n" );
+ return -1;
+ }
+ (*Oc)->soc_flags |= moc[ i ].flags;
+ }
+
+ bi->bi_controls = controls;
+
+ bi->bi_init = 0;
+ bi->bi_open = 0;
+ bi->bi_config = monitor_back_config;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = monitor_back_db_init;
+#if 0
+ bi->bi_db_config = monitor_back_db_config;
+#endif
+ bi->bi_db_open = monitor_back_db_open;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = monitor_back_db_destroy;
+
+ bi->bi_op_bind = monitor_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = monitor_back_search;
+ bi->bi_op_compare = monitor_back_compare;
+ bi->bi_op_modify = monitor_back_modify;
+ bi->bi_op_modrdn = 0;
+ bi->bi_op_add = 0;
+ bi->bi_op_delete = 0;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_entry_release_rw = monitor_back_release;
+ bi->bi_chk_referrals = 0;
+ bi->bi_operational = monitor_back_operational;
+
+ /*
+ * hooks for slap tools
+ */
+ bi->bi_tool_entry_open = 0;
+ bi->bi_tool_entry_close = 0;
+ bi->bi_tool_entry_first = 0;
+ bi->bi_tool_entry_first_x = 0;
+ bi->bi_tool_entry_next = 0;
+ bi->bi_tool_entry_get = 0;
+ bi->bi_tool_entry_put = 0;
+ bi->bi_tool_entry_reindex = 0;
+ bi->bi_tool_sync = 0;
+ bi->bi_tool_dn2id_get = 0;
+ bi->bi_tool_entry_modify = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ bi->bi_extra = (void *)&monitor_extra;
+
+ /*
+ * configuration objectClasses (fake)
+ */
+ bi->bi_cf_ocs = monitorocs;
+
+ rc = config_register_schema( monitorcfg, monitorocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return 0;
+}
+
+int
+monitor_back_db_init(
+ BackendDB *be,
+ ConfigReply *c)
+{
+ int rc;
+ struct berval dn = BER_BVC( SLAPD_MONITOR_DN ),
+ pdn,
+ ndn;
+ BackendDB *be2;
+
+ monitor_subsys_t *ms;
+
+ /*
+ * database monitor can be defined once only
+ */
+ if ( be_monitor != NULL ) {
+ if (c) {
+ snprintf(c->msg, sizeof(c->msg),"only one monitor database allowed");
+ }
+ return( -1 );
+ }
+ be_monitor = be;
+
+ /*
+ * register subsys
+ */
+ for ( ms = known_monitor_subsys; ms->mss_name != NULL; ms++ ) {
+ if ( monitor_back_register_subsys( ms ) ) {
+ return -1;
+ }
+ }
+
+ /* indicate system schema supported */
+ SLAP_BFLAGS(be) |= SLAP_BFLAG_MONITOR;
+
+ rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to normalize/pretty monitor DN \"%s\" (%d)\n",
+ dn.bv_val, rc );
+ return -1;
+ }
+
+ ber_bvarray_add( &be->be_suffix, &pdn );
+ ber_bvarray_add( &be->be_nsuffix, &ndn );
+
+ /* NOTE: only one monitor database is allowed,
+ * so we use static storage */
+ ldap_pvt_thread_mutex_init( &monitor_info.mi_cache_lock );
+
+ be->be_private = &monitor_info;
+
+ be2 = select_backend( &ndn, 0 );
+ if ( be2 != be ) {
+ char *type = be2->bd_info->bi_type;
+
+ if ( overlay_is_over( be2 ) ) {
+ slap_overinfo *oi = (slap_overinfo *)be2->bd_info->bi_private;
+ type = oi->oi_orig->bi_type;
+ }
+
+ if (c) {
+ snprintf(c->msg, sizeof(c->msg),
+ "\"monitor\" database serving namingContext \"%s\" "
+ "is hidden by \"%s\" database serving namingContext \"%s\".\n",
+ pdn.bv_val, type, be2->be_nsuffix[ 0 ].bv_val );
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+monitor_back_destroy_limbo_entry(
+ entry_limbo_t *el,
+ int dispose )
+{
+ if ( el->el_e ) {
+ entry_free( el->el_e );
+ }
+ if ( el->el_a ) {
+ attrs_free( el->el_a );
+ }
+ if ( !BER_BVISNULL( &el->el_nbase ) ) {
+ ber_memfree( el->el_nbase.bv_val );
+ }
+ if ( !BER_BVISNULL( &el->el_filter ) ) {
+ ber_memfree( el->el_filter.bv_val );
+ }
+
+ /* NOTE: callbacks are not copied; so only free them
+ * if disposing of */
+ if ( el->el_cb && dispose != 0 ) {
+ monitor_callback_t *next;
+
+ for ( ; el->el_cb; el->el_cb = next ) {
+ next = el->el_cb->mc_next;
+ if ( el->el_cb->mc_dispose ) {
+ el->el_cb->mc_dispose( &el->el_cb->mc_private );
+ }
+ ch_free( el->el_cb );
+ }
+ }
+
+ ch_free( el );
+}
+
+int
+monitor_back_db_open(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ monitor_info_t *mi = (monitor_info_t *)be->be_private;
+ struct monitor_subsys_t **ms;
+ Entry *e, *root;
+ monitor_entry_t *mp;
+ int i;
+ struct berval bv, rdn = BER_BVC(SLAPD_MONITOR_DN);
+ struct tm tms;
+ static char tmbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval desc[] = {
+ BER_BVC("This subtree contains monitoring/managing objects."),
+ BER_BVC("This object contains information about this server."),
+ BER_BVC("Most of the information is held in operational"
+ " attributes, which must be explicitly requested."),
+ BER_BVNULL };
+
+ int retcode = 0;
+
+ assert( be_monitor != NULL );
+ if ( be != be_monitor ) {
+ be_monitor = be;
+ }
+
+ /*
+ * Start
+ */
+ ldap_pvt_gmtime( &starttime, &tms );
+ lutil_gentime( tmbuf, sizeof(tmbuf), &tms );
+
+ mi->mi_startTime.bv_val = tmbuf;
+ mi->mi_startTime.bv_len = strlen( tmbuf );
+
+ if ( BER_BVISEMPTY( &be->be_rootdn ) ) {
+ BER_BVSTR( &mi->mi_creatorsName, SLAPD_ANONYMOUS );
+ BER_BVSTR( &mi->mi_ncreatorsName, SLAPD_ANONYMOUS );
+ } else {
+ mi->mi_creatorsName = be->be_rootdn;
+ mi->mi_ncreatorsName = be->be_rootndn;
+ }
+
+ /*
+ * creates the "cn=Monitor" entry
+ */
+ e = monitor_entry_stub( NULL, NULL, &rdn, mi->mi_oc_monitorServer,
+ NULL, NULL );
+
+ if ( e == NULL) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to create \"%s\" entry\n",
+ SLAPD_MONITOR_DN );
+ return( -1 );
+ }
+
+ attr_merge_normalize( e, slap_schema.si_ad_description, desc, NULL );
+
+ bv.bv_val = strchr( (char *) Versionstr, '$' );
+ if ( bv.bv_val != NULL ) {
+ char *end;
+
+ bv.bv_val++;
+ for ( ; bv.bv_val[ 0 ] == ' '; bv.bv_val++ )
+ ;
+
+ end = strchr( bv.bv_val, '$' );
+ if ( end != NULL ) {
+ end--;
+
+ for ( ; end > bv.bv_val && end[ 0 ] == ' '; end-- )
+ ;
+
+ end++;
+
+ bv.bv_len = end - bv.bv_val;
+
+ } else {
+ bv.bv_len = strlen( bv.bv_val );
+ }
+
+ if ( attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo,
+ &bv, NULL ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to add monitoredInfo to \"%s\" entry\n",
+ SLAPD_MONITOR_DN );
+ return( -1 );
+ }
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+
+ if ( monitor_cache_add( mi, e, NULL ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to add entry \"%s\" to cache\n",
+ SLAPD_MONITOR_DN );
+ return -1;
+ }
+ root = e;
+
+ /*
+ * Create all the subsystem specific entries
+ */
+ for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) {
+ int len = strlen( monitor_subsys[ i ]->mss_name );
+ struct berval dn;
+ int rc;
+
+ dn.bv_len = len + sizeof( "cn=" ) - 1;
+ dn.bv_val = ch_calloc( sizeof( char ), dn.bv_len + 1 );
+ strcpy( dn.bv_val, "cn=" );
+ strcat( dn.bv_val, monitor_subsys[ i ]->mss_name );
+ rc = dnPretty( NULL, &dn, &monitor_subsys[ i ]->mss_rdn, NULL );
+ free( dn.bv_val );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor RDN \"%s\" is invalid\n",
+ dn.bv_val );
+ return( -1 );
+ }
+
+ e = monitor_entry_stub( &root->e_name, &root->e_nname,
+ &monitor_subsys[ i ]->mss_rdn, mi->mi_oc_monitorContainer,
+ NULL, NULL );
+
+ if ( e == NULL) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to create \"%s\" entry\n",
+ monitor_subsys[ i ]->mss_dn.bv_val );
+ return( -1 );
+ }
+ monitor_subsys[i]->mss_dn = e->e_name;
+ monitor_subsys[i]->mss_ndn = e->e_nname;
+
+ if ( !BER_BVISNULL( &monitor_subsys[ i ]->mss_desc[ 0 ] ) ) {
+ attr_merge_normalize( e, slap_schema.si_ad_description,
+ monitor_subsys[ i ]->mss_desc, NULL );
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = monitor_subsys[ i ];
+ mp->mp_flags = monitor_subsys[ i ]->mss_flags;
+
+ if ( monitor_cache_add( mi, e, root ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unable to add entry \"%s\" to cache\n",
+ monitor_subsys[ i ]->mss_dn.bv_val );
+ return -1;
+ }
+ }
+
+ assert( be != NULL );
+
+ be->be_private = mi;
+
+ /*
+ * opens the monitor backend subsystems
+ */
+ for ( ms = monitor_subsys; ms[ 0 ] != NULL; ms++ ) {
+ if ( ms[ 0 ]->mss_open && ms[ 0 ]->mss_open( be, ms[ 0 ] ) ) {
+ return( -1 );
+ }
+ ms[ 0 ]->mss_flags |= MONITOR_F_OPENED;
+ }
+
+ monitor_subsys_opened = 1;
+
+ if ( mi->mi_entry_limbo ) {
+ entry_limbo_t *el = mi->mi_entry_limbo;
+
+ for ( ; el; ) {
+ entry_limbo_t *tmp;
+ int rc;
+
+ switch ( el->el_type ) {
+ case LIMBO_ENTRY:
+ rc = monitor_back_register_entry(
+ el->el_e,
+ el->el_cb,
+ el->el_mss,
+ el->el_flags );
+ break;
+
+ case LIMBO_ENTRY_PARENT:
+ rc = monitor_back_register_entry_parent(
+ el->el_e,
+ el->el_cb,
+ el->el_mss,
+ el->el_flags,
+ &el->el_nbase,
+ el->el_scope,
+ &el->el_filter );
+ break;
+
+
+ case LIMBO_ATTRS:
+ rc = monitor_back_register_entry_attrs(
+ el->el_ndn,
+ el->el_a,
+ el->el_cb,
+ &el->el_nbase,
+ el->el_scope,
+ &el->el_filter );
+ break;
+
+ case LIMBO_CB:
+ rc = monitor_back_register_entry_callback(
+ el->el_ndn,
+ el->el_cb,
+ &el->el_nbase,
+ el->el_scope,
+ &el->el_filter );
+ break;
+
+ case LIMBO_BACKEND:
+ rc = monitor_back_register_backend( el->el_bi );
+ break;
+
+ case LIMBO_DATABASE:
+ rc = monitor_back_register_database( el->el_be, el->el_ndn );
+ break;
+
+ case LIMBO_OVERLAY_INFO:
+ rc = monitor_back_register_overlay_info( el->el_on );
+ break;
+
+ case LIMBO_OVERLAY:
+ rc = monitor_back_register_overlay( el->el_be, el->el_on, el->el_ndn );
+ break;
+
+ case LIMBO_SUBSYS:
+ rc = monitor_back_register_subsys( el->el_mss );
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ tmp = el;
+ el = el->el_next;
+ monitor_back_destroy_limbo_entry( tmp, rc );
+
+ if ( rc != 0 ) {
+ /* try all, but report error at end */
+ retcode = 1;
+ }
+ }
+
+ mi->mi_entry_limbo = NULL;
+ }
+
+ return retcode;
+}
+
+int
+monitor_back_config(
+ BackendInfo *bi,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ /*
+ * eventually, will hold backend specific configuration parameters
+ */
+ return SLAP_CONF_UNKNOWN;
+}
+
+#if 0
+int
+monitor_back_db_config(
+ Backend *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ monitor_info_t *mi = ( monitor_info_t * )be->be_private;
+
+ /*
+ * eventually, will hold database specific configuration parameters
+ */
+ return SLAP_CONF_UNKNOWN;
+}
+#endif
+
+int
+monitor_back_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ monitor_info_t *mi = ( monitor_info_t * )be->be_private;
+
+ if ( mi == NULL ) {
+ return -1;
+ }
+
+ /*
+ * FIXME: destroys all the data
+ */
+ /* NOTE: mi points to static storage; don't free it */
+
+ (void)monitor_cache_destroy( mi );
+
+ if ( monitor_subsys ) {
+ int i;
+
+ for ( i = 0; monitor_subsys[ i ] != NULL; i++ ) {
+ if ( !BER_BVISNULL( &monitor_subsys[ i ]->mss_rdn ) ) {
+ ch_free( monitor_subsys[ i ]->mss_rdn.bv_val );
+ }
+
+ if ( monitor_subsys[ i ]->mss_destroy ) {
+ monitor_subsys[ i ]->mss_destroy( be, monitor_subsys[ i ] );
+ }
+ }
+
+ ch_free( monitor_subsys );
+ }
+
+ if ( mi->mi_entry_limbo ) {
+ entry_limbo_t *el = mi->mi_entry_limbo;
+
+ for ( ; el; ) {
+ entry_limbo_t *tmp = el;
+ el = el->el_next;
+ monitor_back_destroy_limbo_entry( tmp, 1 );
+ }
+ }
+
+ ldap_pvt_thread_mutex_destroy( &monitor_info.mi_cache_lock );
+
+ be->be_private = NULL;
+
+ return 0;
+}
diff --git a/servers/slapd/back-monitor/listener.c b/servers/slapd/back-monitor/listener.c
new file mode 100644
index 0000000..18e5d01
--- /dev/null
+++ b/servers/slapd/back-monitor/listener.c
@@ -0,0 +1,131 @@
+/* listener.c - deals with listener subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+int
+monitor_subsys_listener_init(
+ BackendDB *be,
+ monitor_subsys_t *ms
+)
+{
+ monitor_info_t *mi;
+ Entry *e_listener;
+ int i;
+ monitor_entry_t *mp;
+ Listener **l;
+
+ assert( be != NULL );
+
+ if ( ( l = slapd_get_listeners() ) == NULL ) {
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_listener_init: "
+ "unable to get listeners\n" );
+ return( -1 );
+ }
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_listener ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_listener_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ for ( i = 0; l[ i ]; i++ ) {
+ char buf[ BACKMONITOR_BUFSIZE ];
+ Entry *e;
+ struct berval bv;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ),
+ "cn=Listener %d", i );
+ bv.bv_val = buf;
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_listener_init: "
+ "unable to create entry \"cn=Listener %d,%s\"\n",
+ i, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ attr_merge_normalize_one( e, mi->mi_ad_monitorConnectionLocalAddress,
+ &l[ i ]->sl_name, NULL );
+
+ attr_merge_normalize_one( e, slap_schema.si_ad_labeledURI,
+ &l[ i ]->sl_url, NULL );
+
+#ifdef HAVE_TLS
+ if ( l[ i ]->sl_is_tls ) {
+ struct berval bv;
+
+ BER_BVSTR( &bv, "TLS" );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo,
+ &bv, NULL );
+ }
+#endif /* HAVE_TLS */
+#ifdef LDAP_CONNECTIONLESS
+ if ( l[ i ]->sl_is_udp ) {
+ struct berval bv;
+
+ BER_BVSTR( &bv, "UDP" );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo,
+ &bv, NULL );
+ }
+#endif /* HAVE_TLS */
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags
+ | MONITOR_F_SUB;
+
+ if ( monitor_cache_add( mi, e, e_listener ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_listener_init: "
+ "unable to add entry \"cn=Listener %d,%s\"\n",
+ i, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_listener );
+
+ return( 0 );
+}
+
diff --git a/servers/slapd/back-monitor/log.c b/servers/slapd/back-monitor/log.c
new file mode 100644
index 0000000..c3d7d53
--- /dev/null
+++ b/servers/slapd/back-monitor/log.c
@@ -0,0 +1,468 @@
+/* log.c - deal with log subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include <lber_pvt.h>
+#include "lutil.h"
+#include "ldif.h"
+#include "back-monitor.h"
+
+static int
+monitor_subsys_log_open(
+ BackendDB *be,
+ monitor_subsys_t *ms );
+
+static int
+monitor_subsys_log_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+/*
+ * log mutex
+ */
+ldap_pvt_thread_mutex_t monitor_log_mutex;
+
+static int add_values( Operation *op, Entry *e, Modification *mod, int *newlevel );
+static int delete_values( Operation *op, Entry *e, Modification *mod, int *newlevel );
+static int replace_values( Operation *op, Entry *e, Modification *mod, int *newlevel );
+
+/*
+ * initializes log subentry
+ */
+int
+monitor_subsys_log_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ ldap_pvt_thread_mutex_init( &monitor_log_mutex );
+ ms->mss_modify = monitor_subsys_log_modify;
+
+ return monitor_subsys_log_open( be, ms );
+}
+
+/*
+ * opens log subentry
+ */
+int
+monitor_subsys_log_open(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ BerVarray bva = NULL;
+ monitor_info_t *mi = ( monitor_info_t * )be->be_private;
+ Entry *e = NULL;
+
+ if ( loglevel2bvarray( slap_debug_get(), &bva ) == 0 && bva != NULL ) {
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_log_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ ber_bvarray_free( bva );
+ return( -1 );
+ }
+
+ attr_merge_normalize( e, mi->mi_ad_monitorDebugLevel, bva, NULL );
+ ber_bvarray_free( bva );
+ bva = NULL;
+ }
+
+ if ( loglevel2bvarray( slap_syslog_get(), &bva ) == 0 && bva != NULL ) {
+ if ( !e && monitor_cache_get( mi, &ms->mss_ndn, &e ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_log_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ ber_bvarray_free( bva );
+ return( -1 );
+ }
+
+ attr_merge_normalize( e, mi->mi_ad_monitorLogLevel, bva, NULL );
+ ber_bvarray_free( bva );
+ }
+
+ if ( e )
+ monitor_cache_release( mi, e );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_log_modify(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ int rc = LDAP_OTHER;
+ int newdebug, newsyslog, *newptr;
+ Attribute *save_attrs;
+ Modifications *modlist = op->orm_modlist;
+ Modifications *ml;
+
+ ldap_pvt_thread_mutex_lock( &monitor_log_mutex );
+
+ newdebug = slap_debug_get();
+ newsyslog = slap_syslog_get();
+
+ save_attrs = e->e_attrs;
+ e->e_attrs = attrs_dup( e->e_attrs );
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ Modification *mod = &ml->sml_mod;
+
+ /*
+ * accept all operational attributes;
+ * this includes modifiersName and modifyTimestamp
+ * if lastmod is "on"
+ */
+ if ( is_at_operational( mod->sm_desc->ad_type ) ) {
+ ( void ) attr_delete( &e->e_attrs, mod->sm_desc );
+ rc = rs->sr_err = attr_merge( e, mod->sm_desc,
+ mod->sm_values, mod->sm_nvalues );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ continue;
+
+ /*
+ * only the monitorDebugLevel and monitorLogLevel attributes can be modified
+ */
+ } else {
+ if ( mod->sm_desc == mi->mi_ad_monitorDebugLevel ) {
+ newptr = &newdebug;
+ } else if ( mod->sm_desc == mi->mi_ad_monitorLogLevel ) {
+ newptr = &newsyslog;
+ } else {
+ rc = rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ break;
+ }
+ }
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ rc = add_values( op, e, mod, newptr );
+ break;
+
+ case LDAP_MOD_DELETE:
+ rc = delete_values( op, e, mod, newptr );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ rc = replace_values( op, e, mod, newptr );
+ break;
+
+ default:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ break;
+ }
+ }
+
+ /* set the new debug level */
+ if ( rc == LDAP_SUCCESS ) {
+ const char *text;
+ static char textbuf[ BACKMONITOR_BUFSIZE ];
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ rc = rs->sr_err = SLAPD_ABANDON;
+
+ goto cleanup;
+ }
+
+ /* check that the entry still obeys the schema */
+ rc = entry_schema_check( op, e, save_attrs, 0, 0, NULL,
+ &text, textbuf, sizeof( textbuf ) );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ goto cleanup;
+ }
+
+ /*
+ * Do we need to protect this with a mutex?
+ */
+ slap_syslog_set( newsyslog );
+ slap_debug_set( newdebug );
+ }
+
+cleanup:;
+ if ( rc == LDAP_SUCCESS ) {
+ attrs_free( save_attrs );
+
+ } else {
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &monitor_log_mutex );
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ return rc;
+}
+
+static int
+check_constraints( Modification *mod, int *newlevel )
+{
+ int i;
+
+ if ( mod->sm_nvalues != NULL ) {
+ ber_bvarray_free( mod->sm_nvalues );
+ mod->sm_nvalues = NULL;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) {
+ int l;
+ struct berval bv;
+
+ if ( str2loglevel( mod->sm_values[ i ].bv_val, &l ) ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ if ( loglevel2bv( l, &bv ) ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ assert( bv.bv_len == mod->sm_values[ i ].bv_len );
+
+ AC_MEMCPY( mod->sm_values[ i ].bv_val,
+ bv.bv_val, bv.bv_len );
+
+ *newlevel |= l;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+add_values( Operation *op, Entry *e, Modification *mod, int *newlevel )
+{
+ Attribute *a;
+ int i, rc;
+ MatchingRule *mr = mod->sm_desc->ad_type->sat_equality;
+
+ assert( mod->sm_values != NULL );
+
+ rc = check_constraints( mod, newlevel );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ a = attr_find( e->e_attrs, mod->sm_desc );
+
+ if ( a != NULL ) {
+ /* "managedInfo" SHOULD have appropriate rules ... */
+ if ( mr == NULL || !mr->smr_match ) {
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) {
+ int rc;
+ int j;
+ const char *text = NULL;
+ struct berval asserted;
+
+ rc = asserted_value_validate_normalize(
+ mod->sm_desc, mr, SLAP_MR_EQUALITY,
+ &mod->sm_values[ i ], &asserted, &text,
+ op->o_tmpmemctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) {
+ int match;
+ int rc = value_match( &match, mod->sm_desc, mr,
+ 0, &a->a_nvals[ j ], &asserted, &text );
+
+ if ( rc == LDAP_SUCCESS && match == 0 ) {
+ free( asserted.bv_val );
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ }
+
+ free( asserted.bv_val );
+ }
+ }
+
+ /* no - add them */
+ rc = attr_merge_normalize( e, mod->sm_desc, mod->sm_values,
+ op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+delete_values( Operation *op, Entry *e, Modification *mod, int *newlevel )
+{
+ int i, j, k, found, rc, nl = 0;
+ Attribute *a;
+ MatchingRule *mr = mod->sm_desc->ad_type->sat_equality;
+
+ /* delete the entire attribute */
+ if ( mod->sm_values == NULL ) {
+ int rc = attr_delete( &e->e_attrs, mod->sm_desc );
+
+ if ( rc ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+
+ } else {
+ *newlevel = 0;
+ rc = LDAP_SUCCESS;
+ }
+ return rc;
+ }
+
+ rc = check_constraints( mod, &nl );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ *newlevel &= ~nl;
+
+ if ( mr == NULL || !mr->smr_match ) {
+ /* disallow specific attributes from being deleted if
+ * no equality rule */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ /* delete specific values - find the attribute first */
+ if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) {
+ return( LDAP_NO_SUCH_ATTRIBUTE );
+ }
+
+ /* find each value to delete */
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[ i ] ); i++ ) {
+ int rc;
+ const char *text = NULL;
+
+ struct berval asserted;
+
+ rc = asserted_value_validate_normalize(
+ mod->sm_desc, mr, SLAP_MR_EQUALITY,
+ &mod->sm_values[ i ], &asserted, &text,
+ op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) return rc;
+
+ found = 0;
+ for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) {
+ int match;
+ int rc = value_match( &match, mod->sm_desc, mr,
+ 0, &a->a_nvals[ j ], &asserted, &text );
+
+ if( rc == LDAP_SUCCESS && match != 0 ) {
+ continue;
+ }
+
+ /* found a matching value */
+ found = 1;
+
+ /* delete it */
+ if ( a->a_nvals != a->a_vals ) {
+ free( a->a_nvals[ j ].bv_val );
+ for ( k = j + 1; !BER_BVISNULL( &a->a_nvals[ k ] ); k++ ) {
+ a->a_nvals[ k - 1 ] = a->a_nvals[ k ];
+ }
+ BER_BVZERO( &a->a_nvals[ k - 1 ] );
+ }
+
+ free( a->a_vals[ j ].bv_val );
+ for ( k = j + 1; !BER_BVISNULL( &a->a_vals[ k ] ); k++ ) {
+ a->a_vals[ k - 1 ] = a->a_vals[ k ];
+ }
+ BER_BVZERO( &a->a_vals[ k - 1 ] );
+ a->a_numvals--;
+
+ break;
+ }
+
+ free( asserted.bv_val );
+
+ /* looked through them all w/o finding it */
+ if ( ! found ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ /* if no values remain, delete the entire attribute */
+ if ( BER_BVISNULL( &a->a_vals[ 0 ] ) ) {
+ assert( a->a_numvals == 0 );
+
+ /* should already be zero */
+ *newlevel = 0;
+
+ if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+replace_values( Operation *op, Entry *e, Modification *mod, int *newlevel )
+{
+ int rc;
+
+ if ( mod->sm_values != NULL ) {
+ *newlevel = 0;
+ rc = check_constraints( mod, newlevel );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ rc = attr_delete( &e->e_attrs, mod->sm_desc );
+
+ if ( rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE ) {
+ return rc;
+ }
+
+ if ( mod->sm_values != NULL ) {
+ rc = attr_merge_normalize( e, mod->sm_desc, mod->sm_values,
+ op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-monitor/modify.c b/servers/slapd/back-monitor/modify.c
new file mode 100644
index 0000000..bc6543f
--- /dev/null
+++ b/servers/slapd/back-monitor/modify.c
@@ -0,0 +1,90 @@
+/* modify.c - monitor backend modify routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+#include "proto-back-monitor.h"
+
+int
+monitor_back_modify( Operation *op, SlapReply *rs )
+{
+ int rc = 0;
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ Entry *matched;
+ Entry *e;
+
+ Debug(LDAP_DEBUG_ARGS, "monitor_back_modify:\n" );
+
+ /* acquire and lock entry */
+ monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched );
+ if ( e == NULL ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ if ( matched ) {
+ if ( !access_allowed_mask( op, matched,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL, NULL ) )
+ {
+ /* do nothing */ ;
+ } else {
+ rs->sr_matched = matched->e_dn;
+ }
+ }
+ send_ldap_result( op, rs );
+ if ( matched != NULL ) {
+ rs->sr_matched = NULL;
+ monitor_cache_release( mi, matched );
+ }
+ return rs->sr_err;
+ }
+
+ if ( !acl_check_modlist( op, e, op->orm_modlist )) {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+
+ } else {
+ assert( !SLAP_SHADOW( op->o_bd ) );
+ slap_mods_opattrs( op, &op->orm_modlist, 0 );
+
+ rc = monitor_entry_modify( op, rs, e );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ if ( !access_allowed_mask( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL, NULL ) )
+ {
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+
+ rs->sr_err = rc;
+ send_ldap_result( op, rs );
+
+ monitor_cache_release( mi, e );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-monitor/operation.c b/servers/slapd/back-monitor/operation.c
new file mode 100644
index 0000000..921ee95
--- /dev/null
+++ b/servers/slapd/back-monitor/operation.c
@@ -0,0 +1,237 @@
+/* operation.c - deal with operation subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+#include "lber_pvt.h"
+
+struct monitor_ops_t {
+ struct berval rdn;
+ struct berval nrdn;
+} monitor_op[] = {
+ { BER_BVC( "cn=Bind" ), BER_BVNULL },
+ { BER_BVC( "cn=Unbind" ), BER_BVNULL },
+ { BER_BVC( "cn=Search" ), BER_BVNULL },
+ { BER_BVC( "cn=Compare" ), BER_BVNULL },
+ { BER_BVC( "cn=Modify" ), BER_BVNULL },
+ { BER_BVC( "cn=Modrdn" ), BER_BVNULL },
+ { BER_BVC( "cn=Add" ), BER_BVNULL },
+ { BER_BVC( "cn=Delete" ), BER_BVNULL },
+ { BER_BVC( "cn=Abandon" ), BER_BVNULL },
+ { BER_BVC( "cn=Extended" ), BER_BVNULL },
+ { BER_BVNULL, BER_BVNULL }
+};
+
+static int
+monitor_subsys_ops_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms );
+
+static int
+monitor_subsys_ops_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+int
+monitor_subsys_ops_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+
+ Entry *e_op;
+ monitor_entry_t *mp;
+ int i;
+ struct berval bv_zero = BER_BVC( "0" );
+
+ assert( be != NULL );
+
+ ms->mss_destroy = monitor_subsys_ops_destroy;
+ ms->mss_update = monitor_subsys_ops_update;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi,
+ &ms->mss_ndn, &e_op ) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_ops_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ attr_merge_one( e_op, mi->mi_ad_monitorOpInitiated, &bv_zero, NULL );
+ attr_merge_one( e_op, mi->mi_ad_monitorOpCompleted, &bv_zero, NULL );
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ struct berval rdn;
+ Entry *e;
+ struct berval bv;
+
+ /*
+ * Initiated ops
+ */
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &monitor_op[i].rdn,
+ mi->mi_oc_monitorOperation, NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ monitor_op[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ BER_BVSTR( &bv, "0" );
+ attr_merge_one( e, mi->mi_ad_monitorOpInitiated, &bv, NULL );
+ attr_merge_one( e, mi->mi_ad_monitorOpCompleted, &bv, NULL );
+
+ /* steal normalized RDN */
+ dnRdn( &e->e_nname, &rdn );
+ ber_dupbv( &monitor_op[ i ].nrdn, &rdn );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_op ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_ops_init: "
+ "unable to add entry \"%s,%s\"\n",
+ monitor_op[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_op );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_ops_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ int i;
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( !BER_BVISNULL( &monitor_op[ i ].nrdn ) ) {
+ ch_free( monitor_op[ i ].nrdn.bv_val );
+ }
+ }
+
+ return 0;
+}
+
+static int
+monitor_subsys_ops_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+
+ ldap_pvt_mp_t nInitiated = LDAP_PVT_MP_INIT,
+ nCompleted = LDAP_PVT_MP_INIT;
+ struct berval rdn;
+ int i;
+ Attribute *a;
+ slap_counters_t *sc;
+ static struct berval bv_ops = BER_BVC( "cn=operations" );
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnRdn( &e->e_nname, &rdn );
+
+ if ( dn_match( &rdn, &bv_ops ) ) {
+ ldap_pvt_mp_init( nInitiated );
+ ldap_pvt_mp_init( nCompleted );
+
+ ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex );
+ ldap_pvt_mp_add( nInitiated, slap_counters.sc_ops_initiated );
+ ldap_pvt_mp_add( nCompleted, slap_counters.sc_ops_completed );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( nInitiated, sc->sc_ops_initiated );
+ ldap_pvt_mp_add( nCompleted, sc->sc_ops_completed );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex );
+
+ } else {
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( dn_match( &rdn, &monitor_op[ i ].nrdn ) )
+ {
+ ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex );
+ ldap_pvt_mp_init_set( nInitiated, slap_counters.sc_ops_initiated_[ i ] );
+ ldap_pvt_mp_init_set( nCompleted, slap_counters.sc_ops_completed_[ i ] );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( nInitiated, sc->sc_ops_initiated_[ i ] );
+ ldap_pvt_mp_add( nCompleted, sc->sc_ops_completed_[ i ] );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex );
+ break;
+ }
+ }
+
+ if ( i == SLAP_OP_LAST ) {
+ /* not found ... */
+ return( 0 );
+ }
+ }
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorOpInitiated );
+ assert ( a != NULL );
+
+ /* NOTE: no minus sign is allowed in the counters... */
+ UI2BV( &a->a_vals[ 0 ], nInitiated );
+ ldap_pvt_mp_clear( nInitiated );
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorOpCompleted );
+ assert ( a != NULL );
+
+ /* NOTE: no minus sign is allowed in the counters... */
+ UI2BV( &a->a_vals[ 0 ], nCompleted );
+ ldap_pvt_mp_clear( nCompleted );
+
+ /* FIXME: touch modifyTimestamp? */
+
+ return SLAP_CB_CONTINUE;
+}
+
diff --git a/servers/slapd/back-monitor/operational.c b/servers/slapd/back-monitor/operational.c
new file mode 100644
index 0000000..994b254
--- /dev/null
+++ b/servers/slapd/back-monitor/operational.c
@@ -0,0 +1,72 @@
+/* operational.c - monitor backend operational attributes function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+#include "proto-back-monitor.h"
+
+/*
+ * sets the supported operational attributes (if required)
+ */
+
+int
+monitor_back_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ Attribute **ap;
+
+ assert( rs->sr_entry != NULL );
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) {
+ break;
+ }
+ }
+
+ if ( *ap == NULL &&
+ attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL &&
+ ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) )
+ {
+ int hs;
+ monitor_entry_t *mp;
+
+ mp = ( monitor_entry_t * )rs->sr_entry->e_private;
+
+ assert( mp != NULL );
+
+ hs = MONITOR_HAS_CHILDREN( mp );
+ *ap = slap_operational_hasSubordinate( hs );
+ assert( *ap != NULL );
+ ap = &(*ap)->a_next;
+ }
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-monitor/overlay.c b/servers/slapd/back-monitor/overlay.c
new file mode 100644
index 0000000..aac6767
--- /dev/null
+++ b/servers/slapd/back-monitor/overlay.c
@@ -0,0 +1,133 @@
+/* overlay.c - deals with overlay subsystem */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+/*
+ * initializes overlay subentries
+ */
+int
+monitor_subsys_overlay_init(
+ BackendDB *be,
+ monitor_subsys_t *ms
+)
+{
+ monitor_info_t *mi;
+ Entry *e_overlay;
+ int i;
+ monitor_entry_t *mp;
+ slap_overinst *on;
+ monitor_subsys_t *ms_database;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ ms_database = monitor_back_get_subsys( SLAPD_MONITOR_DATABASE_NAME );
+ if ( ms_database == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_backend_init: "
+ "unable to get "
+ "\"" SLAPD_MONITOR_DATABASE_NAME "\" "
+ "subsystem\n" );
+ return -1;
+ }
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_overlay ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_overlay_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ for ( on = overlay_next( NULL ), i = 0; on; on = overlay_next( on ), i++ ) {
+ char buf[ BACKMONITOR_BUFSIZE ];
+ struct berval bv;
+ int j;
+ Entry *e;
+ BackendDB *be;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), "cn=Overlay %d", i );
+ bv.bv_val = buf;
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_overlay_init: "
+ "unable to create entry \"cn=Overlay %d,%s\"\n",
+ i, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ ber_str2bv( on->on_bi.bi_type, 0, 0, &bv );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL );
+ attr_merge_normalize_one( e, mi->mi_ad_monitorRuntimeConfig,
+ on->on_bi.bi_cf_ocs ? (struct berval *)&slap_true_bv :
+ (struct berval *)&slap_false_bv, NULL );
+
+ attr_merge_normalize_one( e_overlay, mi->mi_ad_monitoredInfo,
+ &bv, NULL );
+
+ j = -1;
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ char buf[ SLAP_LDAPDN_MAXLEN ];
+ struct berval dn;
+
+ j++;
+ if ( !overlay_is_inst( be, on->on_bi.bi_type ) ) {
+ continue;
+ }
+
+ snprintf( buf, sizeof( buf ), "cn=Database %d,%s",
+ j, ms_database->mss_dn.bv_val );
+
+ ber_str2bv( buf, 0, 0, &dn );
+ attr_merge_normalize_one( e, slap_schema.si_ad_seeAlso,
+ &dn, NULL );
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags
+ | MONITOR_F_SUB;
+
+ if ( monitor_cache_add( mi, e, e_overlay ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_overlay_init: "
+ "unable to add entry \"cn=Overlay %d,%s\"\n",
+ i, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_overlay );
+
+ return( 0 );
+}
+
diff --git a/servers/slapd/back-monitor/proto-back-monitor.h b/servers/slapd/back-monitor/proto-back-monitor.h
new file mode 100644
index 0000000..662ac6d
--- /dev/null
+++ b/servers/slapd/back-monitor/proto-back-monitor.h
@@ -0,0 +1,343 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef _PROTO_BACK_MONITOR
+#define _PROTO_BACK_MONITOR
+
+#include <ldap_cdefs.h>
+
+LDAP_BEGIN_DECL
+
+/*
+ * backends
+ */
+int
+monitor_subsys_backend_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * cache
+ */
+extern int
+monitor_cache_cmp LDAP_P((
+ const void *c1,
+ const void *c2 ));
+extern int
+monitor_cache_dup LDAP_P((
+ void *c1,
+ void *c2 ));
+extern int
+monitor_cache_add LDAP_P((
+ monitor_info_t *mi,
+ Entry *e,
+ Entry *parent ));
+extern int
+monitor_cache_get LDAP_P((
+ monitor_info_t *mi,
+ struct berval *ndn,
+ Entry **ep ));
+extern int
+monitor_cache_remove LDAP_P((
+ monitor_info_t *mi,
+ struct berval *ndn,
+ Entry **ep ));
+extern int
+monitor_cache_dn2entry LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry **ep,
+ Entry **matched ));
+extern int
+monitor_cache_lock LDAP_P((
+ Entry *e ));
+extern int
+monitor_cache_release LDAP_P((
+ monitor_info_t *mi,
+ Entry *e ));
+
+extern int
+monitor_cache_destroy LDAP_P((
+ monitor_info_t *mi ));
+
+extern int
+monitor_back_release(
+ Operation *op,
+ Entry *e,
+ int rw );
+
+/*
+ * connections
+ */
+extern int
+monitor_subsys_conn_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * databases
+ */
+extern int
+monitor_subsys_database_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * entry
+ */
+extern int
+monitor_entry_update LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ Entry *e ));
+extern int
+monitor_entry_create LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn,
+ Entry *e_parent,
+ Entry **ep ));
+extern int
+monitor_entry_modify LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ Entry *e ));
+extern int
+monitor_entry_test_flags LDAP_P((
+ monitor_entry_t *mp,
+ int cond ));
+extern monitor_entry_t *
+monitor_back_entrypriv_create LDAP_P((
+ void ));
+extern Entry *
+monitor_back_entry_stub LDAP_P((
+ struct berval *pdn,
+ struct berval *pndn,
+ struct berval *rdn,
+ ObjectClass *oc,
+ struct berval *create,
+ struct berval *modify ));
+extern Entry *
+monitor_back_entry_get_unlocked LDAP_P((
+ struct berval *ndn ));
+
+#define monitor_entrypriv_create monitor_back_entrypriv_create
+#define monitor_entry_stub monitor_back_entry_stub
+#define monitor_entry_get_unlocked monitor_back_entry_get_unlocked
+
+/*
+ * init
+ */
+extern int
+monitor_subsys_is_opened LDAP_P((
+ void ));
+extern int
+monitor_back_register_subsys LDAP_P((
+ monitor_subsys_t *ms ));
+extern int
+monitor_back_register_subsys_late LDAP_P((
+ monitor_subsys_t *ms ));
+extern int
+monitor_back_register_backend LDAP_P((
+ BackendInfo *bi ));
+extern int
+monitor_back_register_database LDAP_P((
+ BackendDB *be,
+ struct berval *ndn ));
+extern int
+monitor_back_register_overlay_info LDAP_P((
+ slap_overinst *on ));
+extern int
+monitor_back_register_overlay LDAP_P((
+ BackendDB *be,
+ struct slap_overinst *on,
+ struct berval *ndn_out ));
+extern int
+monitor_back_register_backend_limbo LDAP_P((
+ BackendInfo *bi ));
+extern int
+monitor_back_register_database_limbo LDAP_P((
+ BackendDB *be,
+ struct berval *ndn_out ));
+extern int
+monitor_back_register_overlay_info_limbo LDAP_P((
+ slap_overinst *on ));
+extern int
+monitor_back_register_overlay_limbo LDAP_P((
+ BackendDB *be,
+ struct slap_overinst *on,
+ struct berval *ndn_out ));
+extern monitor_subsys_t *
+monitor_back_get_subsys LDAP_P((
+ const char *name ));
+extern monitor_subsys_t *
+monitor_back_get_subsys_by_dn LDAP_P((
+ struct berval *ndn,
+ int sub ));
+extern int
+monitor_back_is_configured LDAP_P(( void ));
+extern int
+monitor_back_register_entry LDAP_P((
+ Entry *e,
+ monitor_callback_t *cb,
+ monitor_subsys_t *mss,
+ unsigned long flags ));
+extern int
+monitor_back_register_entry_parent LDAP_P((
+ Entry *e,
+ monitor_callback_t *cb,
+ monitor_subsys_t *mss,
+ unsigned long flags,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+extern int
+monitor_search2ndn LDAP_P((
+ struct berval *base,
+ int scope,
+ struct berval *filter,
+ struct berval *ndn ));
+extern int
+monitor_back_register_entry_attrs LDAP_P((
+ struct berval *ndn,
+ Attribute *a,
+ monitor_callback_t *cb,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+extern int
+monitor_back_register_entry_callback LDAP_P((
+ struct berval *ndn,
+ monitor_callback_t *cb,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+extern int
+monitor_back_unregister_entry LDAP_P((
+ struct berval *ndn ));
+extern int
+monitor_back_unregister_entry_parent LDAP_P((
+ struct berval *nrdn,
+ monitor_callback_t *target_cb,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+extern int
+monitor_back_unregister_entry_attrs LDAP_P((
+ struct berval *ndn,
+ Attribute *a,
+ monitor_callback_t *cb,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+extern int
+monitor_back_unregister_entry_callback LDAP_P((
+ struct berval *ndn,
+ monitor_callback_t *cb,
+ struct berval *base,
+ int scope,
+ struct berval *filter ));
+
+/*
+ * listener
+ */
+extern int
+monitor_subsys_listener_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * log
+ */
+extern int
+monitor_subsys_log_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * operations
+ */
+extern int
+monitor_subsys_ops_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * overlay
+ */
+extern int
+monitor_subsys_overlay_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * sent
+ */
+extern int
+monitor_subsys_sent_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * threads
+ */
+extern int
+monitor_subsys_thread_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * time
+ */
+extern int monitor_subsys_time_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * waiters
+ */
+extern int
+monitor_subsys_rww_init LDAP_P((
+ BackendDB *be,
+ monitor_subsys_t *ms ));
+
+/*
+ * former external.h
+ */
+
+extern BI_init monitor_back_initialize;
+
+extern BI_db_init monitor_back_db_init;
+extern BI_db_open monitor_back_db_open;
+extern BI_config monitor_back_config;
+extern BI_db_destroy monitor_back_db_destroy;
+extern BI_db_config monitor_back_db_config;
+
+extern BI_op_search monitor_back_search;
+extern BI_op_compare monitor_back_compare;
+extern BI_op_modify monitor_back_modify;
+extern BI_op_bind monitor_back_bind;
+extern BI_operational monitor_back_operational;
+
+LDAP_END_DECL
+
+#endif /* _PROTO_BACK_MONITOR */
+
diff --git a/servers/slapd/back-monitor/rww.c b/servers/slapd/back-monitor/rww.c
new file mode 100644
index 0000000..db1b318
--- /dev/null
+++ b/servers/slapd/back-monitor/rww.c
@@ -0,0 +1,225 @@
+/* readw.c - deal with read waiters subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "back-monitor.h"
+
+static int
+monitor_subsys_rww_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms );
+
+static int
+monitor_subsys_rww_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+enum {
+ MONITOR_RWW_READ = 0,
+ MONITOR_RWW_WRITE,
+
+ MONITOR_RWW_LAST
+};
+
+static struct monitor_rww_t {
+ struct berval rdn;
+ struct berval nrdn;
+} monitor_rww[] = {
+ { BER_BVC("cn=Read"), BER_BVNULL },
+ { BER_BVC("cn=Write"), BER_BVNULL },
+ { BER_BVNULL, BER_BVNULL }
+};
+
+int
+monitor_subsys_rww_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+
+ Entry *e_conn;
+ monitor_entry_t *mp;
+ int i;
+
+ assert( be != NULL );
+
+ ms->mss_destroy = monitor_subsys_rww_destroy;
+ ms->mss_update = monitor_subsys_rww_update;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_rww_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ for ( i = 0; i < MONITOR_RWW_LAST; i++ ) {
+ struct berval nrdn, bv;
+ Entry *e;
+
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &monitor_rww[i].rdn,
+ mi->mi_oc_monitorCounterObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_rww_init: "
+ "unable to create entry \"cn=Read,%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /* steal normalized RDN */
+ dnRdn( &e->e_nname, &nrdn );
+ ber_dupbv( &monitor_rww[ i ].nrdn, &nrdn );
+
+ BER_BVSTR( &bv, "0" );
+ attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_conn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_rww_init: "
+ "unable to add entry \"%s,%s\"\n",
+ monitor_rww[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_conn );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_rww_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ int i;
+
+ for ( i = 0; i < MONITOR_RWW_LAST; i++ ) {
+ ber_memfree_x( monitor_rww[ i ].nrdn.bv_val, NULL );
+ }
+
+ return 0;
+}
+
+static int
+monitor_subsys_rww_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = (monitor_info_t *)op->o_bd->be_private;
+ Connection *c;
+ ber_socket_t connindex;
+ long nconns, nwritewaiters, nreadwaiters;
+
+ int i;
+ struct berval nrdn;
+
+ Attribute *a;
+ char buf[LDAP_PVT_INTTYPE_CHARS(long)];
+ long num = 0;
+ ber_len_t len;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnRdn( &e->e_nname, &nrdn );
+
+ for ( i = 0; !BER_BVISNULL( &monitor_rww[ i ].nrdn ); i++ ) {
+ if ( dn_match( &nrdn, &monitor_rww[ i ].nrdn ) ) {
+ break;
+ }
+ }
+
+ if ( i == MONITOR_RWW_LAST ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ nconns = nwritewaiters = nreadwaiters = 0;
+ for ( c = connection_first( &connindex );
+ c != NULL;
+ c = connection_next( c, &connindex ), nconns++ )
+ {
+ if ( c->c_writewaiter ) {
+ nwritewaiters++;
+ }
+
+ /* FIXME: ?!? */
+ if ( c->c_currentber != NULL ) {
+ nreadwaiters++;
+ }
+ }
+ connection_done(c);
+
+ switch ( i ) {
+ case MONITOR_RWW_READ:
+ num = nreadwaiters;
+ break;
+
+ case MONITOR_RWW_WRITE:
+ num = nwritewaiters;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ snprintf( buf, sizeof( buf ), "%ld", num );
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter );
+ assert( a != NULL );
+ len = strlen( buf );
+ if ( len > a->a_vals[ 0 ].bv_len ) {
+ a->a_vals[ 0 ].bv_val = ber_memrealloc( a->a_vals[ 0 ].bv_val, len + 1 );
+ if ( BER_BVISNULL( &a->a_vals[ 0 ] ) ) {
+ BER_BVZERO( &a->a_vals[ 0 ] );
+ return SLAP_CB_CONTINUE;
+ }
+ }
+ AC_MEMCPY( a->a_vals[ 0 ].bv_val, buf, len + 1 );
+ a->a_vals[ 0 ].bv_len = len;
+
+ /* FIXME: touch modifyTimestamp? */
+
+ return SLAP_CB_CONTINUE;
+}
+
diff --git a/servers/slapd/back-monitor/search.c b/servers/slapd/back-monitor/search.c
new file mode 100644
index 0000000..f58ff11
--- /dev/null
+++ b/servers/slapd/back-monitor/search.c
@@ -0,0 +1,275 @@
+/* search.c - monitor backend search function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+#include "proto-back-monitor.h"
+
+static void
+monitor_find_children(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e_parent,
+ Entry **nonv,
+ Entry **vol
+)
+{
+ monitor_entry_t *mp;
+
+ mp = ( monitor_entry_t * )e_parent->e_private;
+ *nonv = mp->mp_children;
+
+ if ( MONITOR_HAS_VOLATILE_CH( mp ) ) {
+ monitor_entry_create( op, rs, NULL, e_parent, vol );
+ }
+}
+
+static int
+monitor_send_children(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e_nonvolatile,
+ Entry *e_ch,
+ int sub )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ Entry *e,
+ *e_tmp;
+ monitor_entry_t *mp;
+ int rc,
+ nonvolatile = 0;
+
+ e = e_nonvolatile;
+
+ /* no volatile entries? */
+ if ( e_ch == NULL ) {
+ /* no persistent entries? return */
+ if ( e == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* volatile entries */
+ } else {
+ /* if no persistent, return only volatile */
+ if ( e == NULL ) {
+ e = e_ch;
+
+ /* else append persistent to volatile */
+ } else {
+ e_tmp = e_ch;
+ do {
+ mp = ( monitor_entry_t * )e_tmp->e_private;
+ e_tmp = mp->mp_next;
+
+ if ( e_tmp == NULL ) {
+ mp->mp_next = e;
+ break;
+ }
+ } while ( e_tmp );
+ e = e_ch;
+ }
+ }
+
+ /* return entries */
+ for ( ; e != NULL; e = e_tmp ) {
+ Entry *sub_nv = NULL, *sub_ch = NULL, *locked = e;
+
+ monitor_cache_lock( e );
+ monitor_entry_update( op, rs, e );
+
+ if ( e == e_nonvolatile )
+ nonvolatile = 1;
+
+ mp = ( monitor_entry_t * )e->e_private;
+ e_tmp = mp->mp_next;
+
+ if ( op->o_abandon ) {
+ rc = SLAPD_ABANDON;
+ goto freeout;
+ }
+
+ if ( sub )
+ monitor_find_children( op, rs, e, &sub_nv, &sub_ch );
+
+ rc = test_filter( op, e, op->oq_search.rs_filter );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = e;
+ rc = send_search_entry( op, rs );
+ if ( rc ) {
+ for ( e = sub_ch; e != NULL; e = sub_nv ) {
+ mp = ( monitor_entry_t * )e->e_private;
+ sub_nv = mp->mp_next;
+ monitor_cache_lock( e );
+ monitor_cache_release( mi, e );
+ }
+ goto freeout;
+ }
+ }
+ if ( sub_nv == NULL ) {
+ monitor_cache_release( mi, locked );
+ locked = NULL;
+ }
+
+ if ( sub ) {
+ rc = monitor_send_children( op, rs, sub_nv, sub_ch, sub );
+ if ( rc ) {
+freeout:
+ if ( locked ) {
+ monitor_cache_release( mi, locked );
+ }
+ if ( nonvolatile == 0 ) {
+ for ( ; e_tmp != NULL; ) {
+ mp = ( monitor_entry_t * )e_tmp->e_private;
+ e = e_tmp;
+ e_tmp = mp->mp_next;
+ monitor_cache_lock( e );
+ monitor_cache_release( mi, e );
+
+ if ( e_tmp == e_nonvolatile ) {
+ break;
+ }
+ }
+ }
+
+ return( rc );
+ }
+ }
+ if ( locked ) {
+ monitor_cache_release( mi, locked );
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+monitor_back_search( Operation *op, SlapReply *rs )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+ Entry *e = NULL, *matched = NULL;
+ Entry *e_nv = NULL, *e_ch = NULL;
+ slap_mask_t mask;
+
+ Debug( LDAP_DEBUG_TRACE, "=> monitor_back_search\n" );
+
+
+ /* get entry with reader lock */
+ monitor_cache_dn2entry( op, rs, &op->o_req_ndn, &e, &matched );
+ if ( e == NULL ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ if ( matched ) {
+ if ( !access_allowed_mask( op, matched,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL, NULL ) )
+ {
+ /* do nothing */ ;
+ } else {
+ rs->sr_matched = matched->e_dn;
+ }
+ }
+
+ send_ldap_result( op, rs );
+ if ( matched ) {
+ monitor_cache_release( mi, matched );
+ rs->sr_matched = NULL;
+ }
+
+ return rs->sr_err;
+ }
+
+ /* NOTE: __NEW__ "search" access is required
+ * on searchBase object */
+ if ( !access_allowed_mask( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_SEARCH, NULL, &mask ) )
+ {
+ monitor_cache_release( mi, e );
+
+ if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+ }
+
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ switch ( op->oq_search.rs_scope ) {
+ case LDAP_SCOPE_BASE:
+ monitor_entry_update( op, rs, e );
+ rc = test_filter( op, e, op->oq_search.rs_filter );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = e;
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ send_search_entry( op, rs );
+ rs->sr_entry = NULL;
+ } else {
+ monitor_cache_release( mi, e );
+ }
+ rc = LDAP_SUCCESS;
+ break;
+
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBORDINATE:
+ monitor_find_children( op, rs, e, &e_nv, &e_ch );
+ rc = monitor_send_children( op, rs, e_nv, e_ch,
+ op->oq_search.rs_scope == LDAP_SCOPE_SUBORDINATE );
+ monitor_cache_release( mi, e );
+ break;
+
+ case LDAP_SCOPE_SUBTREE:
+ monitor_entry_update( op, rs, e );
+ monitor_find_children( op, rs, e, &e_nv, &e_ch );
+ rc = test_filter( op, e, op->oq_search.rs_filter );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = e;
+ send_search_entry( op, rs );
+ rs->sr_entry = NULL;
+ }
+
+ rc = monitor_send_children( op, rs, e_nv, e_ch, 1 );
+ monitor_cache_release( mi, e );
+ break;
+
+ default:
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ monitor_cache_release( mi, e );
+ }
+
+ rs->sr_attrs = NULL;
+ rs->sr_err = rc;
+ if ( rs->sr_err != SLAPD_ABANDON ) {
+ send_ldap_result( op, rs );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-monitor/sent.c b/servers/slapd/back-monitor/sent.c
new file mode 100644
index 0000000..24cc3b6
--- /dev/null
+++ b/servers/slapd/back-monitor/sent.c
@@ -0,0 +1,234 @@
+/* sent.c - deal with data sent subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+static int
+monitor_subsys_sent_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms );
+
+static int
+monitor_subsys_sent_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+enum {
+ MONITOR_SENT_BYTES = 0,
+ MONITOR_SENT_PDU,
+ MONITOR_SENT_ENTRIES,
+ MONITOR_SENT_REFERRALS,
+
+ MONITOR_SENT_LAST
+};
+
+struct monitor_sent_t {
+ struct berval rdn;
+ struct berval nrdn;
+} monitor_sent[] = {
+ { BER_BVC("cn=Bytes"), BER_BVNULL },
+ { BER_BVC("cn=PDU"), BER_BVNULL },
+ { BER_BVC("cn=Entries"), BER_BVNULL },
+ { BER_BVC("cn=Referrals"), BER_BVNULL },
+ { BER_BVNULL, BER_BVNULL }
+};
+
+int
+monitor_subsys_sent_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+
+ Entry *e_sent;
+ monitor_entry_t *mp;
+ int i;
+
+ assert( be != NULL );
+
+ ms->mss_destroy = monitor_subsys_sent_destroy;
+ ms->mss_update = monitor_subsys_sent_update;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_sent ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_sent_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ for ( i = 0; i < MONITOR_SENT_LAST; i++ ) {
+ struct berval nrdn, bv;
+ Entry *e;
+
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn,
+ &monitor_sent[i].rdn, mi->mi_oc_monitorCounterObject,
+ NULL, NULL );
+
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_sent_init: "
+ "unable to create entry \"%s,%s\"\n",
+ monitor_sent[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /* steal normalized RDN */
+ dnRdn( &e->e_nname, &nrdn );
+ ber_dupbv( &monitor_sent[ i ].nrdn, &nrdn );
+
+ BER_BVSTR( &bv, "0" );
+ attr_merge_one( e, mi->mi_ad_monitorCounter, &bv, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_sent ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_sent_init: "
+ "unable to add entry \"%s,%s\"\n",
+ monitor_sent[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_sent );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_sent_destroy(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ int i;
+
+ for ( i = 0; i < MONITOR_SENT_LAST; i++ ) {
+ if ( !BER_BVISNULL( &monitor_sent[ i ].nrdn ) ) {
+ ch_free( monitor_sent[ i ].nrdn.bv_val );
+ }
+ }
+
+ return 0;
+}
+
+static int
+monitor_subsys_sent_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t *)op->o_bd->be_private;
+
+ struct berval nrdn;
+ ldap_pvt_mp_t n;
+ Attribute *a;
+ slap_counters_t *sc;
+ int i;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnRdn( &e->e_nname, &nrdn );
+
+ for ( i = 0; i < MONITOR_SENT_LAST; i++ ) {
+ if ( dn_match( &nrdn, &monitor_sent[ i ].nrdn ) ) {
+ break;
+ }
+ }
+
+ if ( i == MONITOR_SENT_LAST ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ ldap_pvt_thread_mutex_lock(&slap_counters.sc_mutex);
+ switch ( i ) {
+ case MONITOR_SENT_ENTRIES:
+ ldap_pvt_mp_init_set( n, slap_counters.sc_entries );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( n, sc->sc_entries );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ break;
+
+ case MONITOR_SENT_REFERRALS:
+ ldap_pvt_mp_init_set( n, slap_counters.sc_refs );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( n, sc->sc_refs );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ break;
+
+ case MONITOR_SENT_PDU:
+ ldap_pvt_mp_init_set( n, slap_counters.sc_pdu );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( n, sc->sc_pdu );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ break;
+
+ case MONITOR_SENT_BYTES:
+ ldap_pvt_mp_init_set( n, slap_counters.sc_bytes );
+ for ( sc = slap_counters.sc_next; sc; sc = sc->sc_next ) {
+ ldap_pvt_thread_mutex_lock( &sc->sc_mutex );
+ ldap_pvt_mp_add( n, sc->sc_bytes );
+ ldap_pvt_thread_mutex_unlock( &sc->sc_mutex );
+ }
+ break;
+
+ default:
+ assert(0);
+ }
+ ldap_pvt_thread_mutex_unlock(&slap_counters.sc_mutex);
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorCounter );
+ assert( a != NULL );
+
+ /* NOTE: no minus sign is allowed in the counters... */
+ UI2BV( &a->a_vals[ 0 ], n );
+ ldap_pvt_mp_clear( n );
+
+ /* FIXME: touch modifyTimestamp? */
+
+ return SLAP_CB_CONTINUE;
+}
+
diff --git a/servers/slapd/back-monitor/thread.c b/servers/slapd/back-monitor/thread.c
new file mode 100644
index 0000000..a29a875
--- /dev/null
+++ b/servers/slapd/back-monitor/thread.c
@@ -0,0 +1,344 @@
+/* thread.c - deal with thread subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-monitor.h"
+
+#include <ldap_rq.h>
+
+typedef enum {
+ MT_UNKNOWN,
+ MT_RUNQUEUE,
+ MT_TASKLIST,
+
+ MT_LAST
+} monitor_thread_t;
+
+static struct {
+ struct berval rdn;
+ struct berval desc;
+ struct berval nrdn;
+ ldap_pvt_thread_pool_param_t param;
+ monitor_thread_t mt;
+} mt[] = {
+ { BER_BVC( "cn=Max" ),
+ BER_BVC("Maximum number of threads as configured"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_MAX, MT_UNKNOWN },
+ { BER_BVC( "cn=Max Pending" ),
+ BER_BVC("Maximum number of pending threads"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_MAX_PENDING, MT_UNKNOWN },
+ { BER_BVC( "cn=Open" ),
+ BER_BVC("Number of open threads"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_OPEN, MT_UNKNOWN },
+ { BER_BVC( "cn=Starting" ),
+ BER_BVC("Number of threads being started"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_STARTING, MT_UNKNOWN },
+ { BER_BVC( "cn=Active" ),
+ BER_BVC("Number of active threads"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_ACTIVE, MT_UNKNOWN },
+ { BER_BVC( "cn=Pending" ),
+ BER_BVC("Number of pending threads"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_PENDING, MT_UNKNOWN },
+ { BER_BVC( "cn=Backload" ),
+ BER_BVC("Number of active plus pending threads"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_BACKLOAD, MT_UNKNOWN },
+#if 0 /* not meaningful right now */
+ { BER_BVC( "cn=Active Max" ),
+ BER_BVNULL,
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_ACTIVE_MAX, MT_UNKNOWN },
+ { BER_BVC( "cn=Pending Max" ),
+ BER_BVNULL,
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_PENDING_MAX, MT_UNKNOWN },
+ { BER_BVC( "cn=Backload Max" ),
+ BER_BVNULL,
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_BACKLOAD_MAX,MT_UNKNOWN },
+#endif
+ { BER_BVC( "cn=State" ),
+ BER_BVC("Thread pool state"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_STATE, MT_UNKNOWN },
+
+ { BER_BVC( "cn=Runqueue" ),
+ BER_BVC("Queue of running threads - besides those handling operations"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN, MT_RUNQUEUE },
+ { BER_BVC( "cn=Tasklist" ),
+ BER_BVC("List of running plus standby threads - besides those handling operations"),
+ BER_BVNULL, LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN, MT_TASKLIST },
+
+ { BER_BVNULL }
+};
+
+static int
+monitor_subsys_thread_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+/*
+ * initializes log subentry
+ */
+int
+monitor_subsys_thread_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+ monitor_entry_t *mp;
+ Entry *e, *e_thread;
+ int i;
+
+ ms->mss_update = monitor_subsys_thread_update;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi, &ms->mss_ndn, &e_thread ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_thread_init: unable to get entry \"%s\"\n",
+ ms->mss_dn.bv_val );
+ return( -1 );
+ }
+
+ for ( i = 0; !BER_BVISNULL( &mt[ i ].rdn ); i++ ) {
+ static char buf[ BACKMONITOR_BUFSIZE ];
+ int count = -1;
+ char *state = NULL;
+ struct berval bv = BER_BVNULL;
+
+ /*
+ * Max
+ */
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn,
+ &mt[ i ].rdn,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_thread_init: "
+ "unable to create entry \"%s,%s\"\n",
+ mt[ i ].rdn.bv_val,
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /* NOTE: reference to the normalized DN of the entry,
+ * under the assumption it's not modified */
+ dnRdn( &e->e_nname, &mt[ i ].nrdn );
+
+ switch ( mt[ i ].param ) {
+ case LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN:
+ break;
+
+ case LDAP_PVT_THREAD_POOL_PARAM_STATE:
+ if ( ldap_pvt_thread_pool_query( &connection_pool,
+ mt[ i ].param, (void *)&state ) == 0 )
+ {
+ ber_str2bv( state, 0, 0, &bv );
+
+ } else {
+ BER_BVSTR( &bv, "unknown" );
+ }
+ break;
+
+ default:
+ /* NOTE: in case of error, it'll be set to -1 */
+ (void)ldap_pvt_thread_pool_query( &connection_pool,
+ mt[ i ].param, (void *)&count );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", count );
+ break;
+ }
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo, &bv, NULL );
+ }
+
+ if ( !BER_BVISNULL( &mt[ i ].desc ) ) {
+ attr_merge_normalize_one( e,
+ slap_schema.si_ad_description,
+ &mt[ i ].desc, NULL );
+ }
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_thread ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_thread_init: "
+ "unable to add entry \"%s,%s\"\n",
+ mt[ i ].rdn.bv_val,
+ ms->mss_dn.bv_val );
+ return( -1 );
+ }
+ }
+
+ monitor_cache_release( mi, e_thread );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_thread_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ Attribute *a;
+ BerVarray vals = NULL;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ struct berval rdn, bv;
+ int which, i;
+ struct re_s *re;
+ int count = -1;
+ char *state = NULL;
+
+ assert( mi != NULL );
+
+ dnRdn( &e->e_nname, &rdn );
+
+ for ( i = 0; !BER_BVISNULL( &mt[ i ].nrdn ); i++ ) {
+ if ( dn_match( &mt[ i ].nrdn, &rdn ) ) {
+ break;
+ }
+ }
+
+ which = i;
+ if ( BER_BVISNULL( &mt[ which ].nrdn ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitoredInfo );
+
+ switch ( mt[ which ].param ) {
+ case LDAP_PVT_THREAD_POOL_PARAM_UNKNOWN:
+ switch ( mt[ which ].mt ) {
+ case MT_RUNQUEUE:
+ if ( a != NULL ) {
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvarray_free( a->a_nvals );
+ }
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+
+ i = 0;
+ bv.bv_val = buf;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ LDAP_STAILQ_FOREACH( re, &slapd_rq.run_list, rnext ) {
+ bv.bv_len = snprintf( buf, sizeof( buf ), "{%d}%s(%s)",
+ i, re->tname, re->tspec );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ value_add_one( &vals, &bv );
+ }
+ i++;
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ if ( vals ) {
+ attr_merge_normalize( e, mi->mi_ad_monitoredInfo, vals, NULL );
+ ber_bvarray_free( vals );
+
+ } else {
+ attr_delete( &e->e_attrs, mi->mi_ad_monitoredInfo );
+ }
+ break;
+
+ case MT_TASKLIST:
+ if ( a != NULL ) {
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvarray_free( a->a_nvals );
+ }
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+
+ i = 0;
+ bv.bv_val = buf;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ LDAP_STAILQ_FOREACH( re, &slapd_rq.task_list, tnext ) {
+ bv.bv_len = snprintf( buf, sizeof( buf ), "{%d}%s(%s)",
+ i, re->tname, re->tspec );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ value_add_one( &vals, &bv );
+ }
+ i++;
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ if ( vals ) {
+ attr_merge_normalize( e, mi->mi_ad_monitoredInfo, vals, NULL );
+ ber_bvarray_free( vals );
+
+ } else {
+ attr_delete( &e->e_attrs, mi->mi_ad_monitoredInfo );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ break;
+
+ case LDAP_PVT_THREAD_POOL_PARAM_STATE:
+ if ( a == NULL ) {
+ return rs->sr_err = LDAP_OTHER;
+ }
+ if ( ldap_pvt_thread_pool_query( &connection_pool,
+ mt[ i ].param, (void *)&state ) == 0 )
+ {
+ ber_str2bv( state, 0, 0, &bv );
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+ break;
+
+ default:
+ if ( a == NULL ) {
+ return rs->sr_err = LDAP_OTHER;
+ }
+ if ( ldap_pvt_thread_pool_query( &connection_pool,
+ mt[ i ].param, (void *)&count ) == 0 )
+ {
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", count );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+ }
+ break;
+ }
+
+ /* FIXME: touch modifyTimestamp? */
+
+ return SLAP_CB_CONTINUE;
+}
diff --git a/servers/slapd/back-monitor/time.c b/servers/slapd/back-monitor/time.c
new file mode 100644
index 0000000..53e0ac6
--- /dev/null
+++ b/servers/slapd/back-monitor/time.c
@@ -0,0 +1,234 @@
+/* time.c - deal with time subsystem */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+
+#include "slap.h"
+#include <lutil.h>
+#include "proto-slap.h"
+#include "back-monitor.h"
+
+static int
+monitor_subsys_time_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e );
+
+int
+monitor_subsys_time_init(
+ BackendDB *be,
+ monitor_subsys_t *ms )
+{
+ monitor_info_t *mi;
+
+ Entry *e, *e_time;
+ monitor_entry_t *mp;
+ struct berval bv, value;
+
+ assert( be != NULL );
+
+ ms->mss_update = monitor_subsys_time_update;
+
+ mi = ( monitor_info_t * )be->be_private;
+
+ if ( monitor_cache_get( mi,
+ &ms->mss_ndn, &e_time ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to get entry \"%s\"\n",
+ ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ BER_BVSTR( &bv, "cn=Start" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to create entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ attr_merge_normalize_one( e, mi->mi_ad_monitorTimestamp,
+ &mi->mi_startTime, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_time ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to add entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /*
+ * Current
+ */
+ BER_BVSTR( &bv, "cn=Current" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to create entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ attr_merge_normalize_one( e, mi->mi_ad_monitorTimestamp,
+ &mi->mi_startTime, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_time ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to add entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ /*
+ * Uptime
+ */
+ BER_BVSTR( &bv, "cn=Uptime" );
+ e = monitor_entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv,
+ mi->mi_oc_monitoredObject, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to create entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+ BER_BVSTR( &value, "0" );
+ attr_merge_normalize_one( e, mi->mi_ad_monitoredInfo,
+ &value, NULL );
+
+ mp = monitor_entrypriv_create();
+ if ( mp == NULL ) {
+ return -1;
+ }
+ e->e_private = ( void * )mp;
+ mp->mp_info = ms;
+ mp->mp_flags = ms->mss_flags \
+ | MONITOR_F_SUB | MONITOR_F_PERSISTENT;
+
+ if ( monitor_cache_add( mi, e, e_time ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "monitor_subsys_time_init: "
+ "unable to add entry \"%s,%s\"\n",
+ bv.bv_val, ms->mss_ndn.bv_val );
+ return( -1 );
+ }
+
+ monitor_cache_release( mi, e_time );
+
+ return( 0 );
+}
+
+static int
+monitor_subsys_time_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e )
+{
+ monitor_info_t *mi = ( monitor_info_t * )op->o_bd->be_private;
+ static struct berval bv_current = BER_BVC( "cn=current" ),
+ bv_uptime = BER_BVC( "cn=uptime" );
+ struct berval rdn;
+
+ assert( mi != NULL );
+ assert( e != NULL );
+
+ dnRdn( &e->e_nname, &rdn );
+
+ if ( dn_match( &rdn, &bv_current ) ) {
+ struct tm tm;
+ char tmbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ Attribute *a;
+ ber_len_t len;
+ time_t currtime;
+
+ currtime = slap_get_time();
+
+ ldap_pvt_gmtime( &currtime, &tm );
+ lutil_gentime( tmbuf, sizeof( tmbuf ), &tm );
+
+ len = strlen( tmbuf );
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitorTimestamp );
+ if ( a == NULL ) {
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ assert( len == a->a_vals[ 0 ].bv_len );
+ AC_MEMCPY( a->a_vals[ 0 ].bv_val, tmbuf, len );
+
+ /* FIXME: touch modifyTimestamp? */
+
+ } else if ( dn_match( &rdn, &bv_uptime ) ) {
+ Attribute *a;
+ double diff;
+ char buf[ BACKMONITOR_BUFSIZE ];
+ struct berval bv;
+
+ a = attr_find( e->e_attrs, mi->mi_ad_monitoredInfo );
+ if ( a == NULL ) {
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ diff = difftime( slap_get_time(), starttime );
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu",
+ (unsigned long) diff );
+ bv.bv_val = buf;
+
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvreplace( &a->a_nvals[ 0 ], &bv );
+ }
+
+ /* FIXME: touch modifyTimestamp? */
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
diff --git a/servers/slapd/back-null/Makefile.in b/servers/slapd/back-null/Makefile.in
new file mode 100644
index 0000000..707c780
--- /dev/null
+++ b/servers/slapd/back-null/Makefile.in
@@ -0,0 +1,41 @@
+# Makefile.in for back-null
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = null.c
+OBJS = null.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-null"
+BUILD_MOD = @BUILD_NULL@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_NULL@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_null
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-null/README b/servers/slapd/back-null/README
new file mode 100644
index 0000000..300afd9
--- /dev/null
+++ b/servers/slapd/back-null/README
@@ -0,0 +1 @@
+The Null Backend is described in the slapd-null(5) manual page.
diff --git a/servers/slapd/back-null/null.c b/servers/slapd/back-null/null.c
new file mode 100644
index 0000000..c8d3292
--- /dev/null
+++ b/servers/slapd/back-null/null.c
@@ -0,0 +1,472 @@
+/* null.c - the null backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Hallvard Furuseth for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "slap-config.h"
+
+typedef struct null_info {
+ int ni_bind_allowed;
+ int ni_dosearch;
+ ID ni_nextid;
+ Entry *ni_entry;
+} null_info;
+
+static ConfigTable nullcfg[] = {
+ { "bind", "true|FALSE", 1, 2, 0, ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(null_info, ni_bind_allowed),
+ "( OLcfgDbAt:8.1 NAME 'olcDbBindAllowed' "
+ "DESC 'Allow binds to this database' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "dosearch", "true|FALSE", 1, 2, 0, ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(null_info, ni_dosearch),
+ "( OLcfgDbAt:8.2 NAME 'olcDbDoSearch' "
+ "DESC 'Return an entry on searches' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs nullocs[] = {
+ { "( OLcfgDbOc:8.1 "
+ "NAME 'olcNullConfig' "
+ "DESC 'Null backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcDbBindAllowed $ olcDbDoSearch ) )",
+ Cft_Database, nullcfg },
+ { NULL, 0, NULL }
+};
+
+
+static int
+null_back_db_open( BackendDB *be, ConfigReply *cr )
+{
+ struct null_info *ni = (struct null_info *) be->be_private;
+ struct berval bv[2];
+ AttributeDescription *ad = NULL;
+ const char *text;
+ Entry *e;
+
+ if ( ni->ni_dosearch ) {
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &be->be_suffix[0] );
+ ber_dupbv( &e->e_nname, &be->be_nsuffix[0] );
+
+ dnRdn( &e->e_nname, &bv[0] );
+ bv[1].bv_val = strchr(bv[0].bv_val, '=') + 1;
+ bv[1].bv_len = bv[0].bv_len - (bv[1].bv_val -
+ bv[0].bv_val);
+ bv[0].bv_len -= bv[1].bv_len + 1;
+ slap_bv2ad( &bv[0], &ad, &text );
+ attr_merge_one( e, ad, &bv[1], NULL );
+
+ ber_str2bv("extensibleObject", 0, 0, &bv[0]);
+ attr_merge_one( e, slap_schema.si_ad_objectClass, &bv[0], NULL);
+ ni->ni_entry = e;
+ }
+ return 0;
+}
+
+/* LDAP operations */
+
+static int
+null_back_bind( Operation *op, SlapReply *rs )
+{
+ struct null_info *ni = (struct null_info *) op->o_bd->be_private;
+
+ if ( ni->ni_bind_allowed || be_isroot_pw( op ) ) {
+ /* front end will send result on success (0) */
+ return LDAP_SUCCESS;
+ }
+
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+}
+
+
+static int
+null_back_respond( Operation *op, SlapReply *rs, int rc )
+{
+ LDAPControl ctrl[SLAP_MAX_RESPONSE_CONTROLS], *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int c = 0;
+
+ BerElementBuffer ps_berbuf;
+ BerElement *ps_ber = NULL;
+ LDAPControl **preread_ctrl = NULL,
+ **postread_ctrl = NULL;
+
+ rs->sr_err = LDAP_OTHER;
+
+ /* this comes first, as in case of assertion failure
+ * any further processing must stop */
+ if ( get_assert( op ) ) {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto respond;
+ }
+
+ if ( op->o_preread ) {
+ Entry e = { 0 };
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_RENAME:
+ case LDAP_REQ_DELETE:
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+
+ preread_ctrl = &ctrls[c];
+ *preread_ctrl = NULL;
+
+ if ( slap_read_controls( op, rs, &e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ preread_ctrl = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- null_back_respond: pre-read "
+ "failed!\n" );
+
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto respond;
+ }
+
+ } else {
+ c++;
+ }
+ break;
+ }
+ }
+
+ if ( op->o_postread ) {
+ Entry e = { 0 };
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_RENAME:
+ if ( op->o_tag == LDAP_REQ_ADD ) {
+ e.e_name = op->ora_e->e_name;
+ e.e_nname = op->ora_e->e_nname;
+
+ } else {
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ }
+
+ postread_ctrl = &ctrls[c];
+ *postread_ctrl = NULL;
+
+ if ( slap_read_controls( op, rs, &e,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ postread_ctrl = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- null_back_respond: post-read "
+ "failed!\n" );
+
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto respond;
+ }
+
+ } else {
+ c++;
+ }
+ break;
+ }
+ }
+
+ if ( op->o_noop ) {
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_RENAME:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_EXTENDED:
+ rc = LDAP_X_NO_OPERATION;
+ break;
+ }
+ }
+
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ struct berval cookie = BER_BVC( "" );
+
+ /* should not be here... */
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ ctrl[c].ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrl[c].ldctl_iscritical = 0;
+
+ ps_ber = (BerElement *)&ps_berbuf;
+ ber_init2( ps_ber, NULL, LBER_USE_DER );
+
+ /* return size of 0 -- no estimate */
+ ber_printf( ps_ber, "{iO}", 0, &cookie );
+
+ if ( ber_flatten2( ps_ber, &ctrl[c].ldctl_value, 0 ) == -1 ) {
+ goto done;
+ }
+
+ ctrls[c] = &ctrl[c];
+ c++;
+ }
+
+ /* terminate controls array */
+ ctrls[c] = NULL;
+ rs->sr_ctrls = ctrls;
+ rs->sr_err = rc;
+
+respond:;
+ send_ldap_result( op, rs );
+ rs->sr_ctrls = NULL;
+
+done:;
+ if ( ps_ber != NULL ) {
+ (void) ber_free_buf( ps_ber );
+ }
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+/* add, delete, modify, modrdn, search */
+static int
+null_back_success( Operation *op, SlapReply *rs )
+{
+ return null_back_respond( op, rs, LDAP_SUCCESS );
+}
+
+/* compare */
+static int
+null_back_false( Operation *op, SlapReply *rs )
+{
+ return null_back_respond( op, rs, LDAP_COMPARE_FALSE );
+}
+
+static int
+null_back_search( Operation *op, SlapReply *rs )
+{
+ struct null_info *ni = (struct null_info *) op->o_bd->be_private;
+
+ if ( ni->ni_entry ) {
+ rs->sr_entry = ni->ni_entry;
+ rs->sr_flags = 0;
+
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ send_search_entry( op, rs );
+ }
+ return null_back_respond( op, rs, LDAP_SUCCESS );
+}
+
+/* for overlays */
+static int
+null_back_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ /* don't admit the object isn't there */
+ return oc || at ? LDAP_NO_SUCH_ATTRIBUTE : LDAP_BUSY;
+}
+
+static int
+null_back_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ /* we reuse our entry, don't free it */
+ return 0;
+}
+
+/* Slap tools */
+
+static int
+null_tool_entry_open( BackendDB *be, int mode )
+{
+ return 0;
+}
+
+static int
+null_tool_entry_close( BackendDB *be )
+{
+ assert( be != NULL );
+ return 0;
+}
+
+static ID
+null_tool_entry_first_x( BackendDB *be, struct berval *base, int scope, Filter *f )
+{
+ return NOID;
+}
+
+static ID
+null_tool_entry_next( BackendDB *be )
+{
+ return NOID;
+}
+
+static Entry *
+null_tool_entry_get( BackendDB *be, ID id )
+{
+ assert( slapMode & SLAP_TOOL_MODE );
+ return NULL;
+}
+
+static ID
+null_tool_entry_put( BackendDB *be, Entry *e, struct berval *text )
+{
+ assert( slapMode & SLAP_TOOL_MODE );
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ e->e_id = ((struct null_info *) be->be_private)->ni_nextid++;
+ return e->e_id;
+}
+
+
+/* Setup */
+
+static int
+null_back_db_init( BackendDB *be, ConfigReply *cr )
+{
+ struct null_info *ni = ch_calloc( 1, sizeof(struct null_info) );
+ ni->ni_bind_allowed = 0;
+ ni->ni_nextid = 1;
+ be->be_private = ni;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+ return 0;
+}
+
+static int
+null_back_db_destroy( Backend *be, ConfigReply *cr )
+{
+ struct null_info *ni = be->be_private;
+
+ if ( ni->ni_entry ) {
+ entry_free( ni->ni_entry );
+ ni->ni_entry = NULL;
+ }
+ free( be->be_private );
+ return 0;
+}
+
+
+int
+null_back_initialize( BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_ASSERT,
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_NOOP,
+ LDAP_CONTROL_PAGEDRESULTS,
+ LDAP_CONTROL_SUBENTRIES,
+ LDAP_CONTROL_PRE_READ,
+ LDAP_CONTROL_POST_READ,
+ LDAP_CONTROL_X_PERMISSIVE_MODIFY,
+ NULL
+ };
+
+ Debug( LDAP_DEBUG_TRACE,
+ "null_back_initialize: initialize null backend\n" );
+
+ bi->bi_flags |=
+ SLAP_BFLAG_INCREMENT |
+ SLAP_BFLAG_SUBENTRIES |
+ SLAP_BFLAG_ALIASES |
+ SLAP_BFLAG_REFERRALS;
+
+ bi->bi_controls = controls;
+
+ bi->bi_open = 0;
+ bi->bi_close = 0;
+ bi->bi_config = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = null_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = null_back_db_open;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = null_back_db_destroy;
+
+ bi->bi_op_bind = null_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = null_back_search;
+ bi->bi_op_compare = null_back_false;
+ bi->bi_op_modify = null_back_success;
+ bi->bi_op_modrdn = null_back_success;
+ bi->bi_op_add = null_back_success;
+ bi->bi_op_delete = null_back_success;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ bi->bi_entry_get_rw = null_back_entry_get;
+ bi->bi_entry_release_rw = null_back_entry_release;
+
+ bi->bi_tool_entry_open = null_tool_entry_open;
+ bi->bi_tool_entry_close = null_tool_entry_close;
+ bi->bi_tool_entry_first = backend_tool_entry_first;
+ bi->bi_tool_entry_first_x = null_tool_entry_first_x;
+ bi->bi_tool_entry_next = null_tool_entry_next;
+ bi->bi_tool_entry_get = null_tool_entry_get;
+ bi->bi_tool_entry_put = null_tool_entry_put;
+
+ bi->bi_cf_ocs = nullocs;
+ return config_register_schema( nullcfg, nullocs );
+}
+
+#if SLAPD_NULL == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( null )
+
+#endif /* SLAPD_NULL == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-passwd/Makefile.in b/servers/slapd/back-passwd/Makefile.in
new file mode 100644
index 0000000..937cf88
--- /dev/null
+++ b/servers/slapd/back-passwd/Makefile.in
@@ -0,0 +1,41 @@
+# Makefile.in for back-passwd
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = search.c config.c init.c
+OBJS = search.lo config.lo init.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-passwd"
+BUILD_MOD = @BUILD_PASSWD@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_PASSWD@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_passwd
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-passwd/back-passwd.h b/servers/slapd/back-passwd/back-passwd.h
new file mode 100644
index 0000000..d1957cc
--- /dev/null
+++ b/servers/slapd/back-passwd/back-passwd.h
@@ -0,0 +1,31 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _BACK_PASSWD_H
+#define _BACK_PASSWD_H
+
+#include "proto-passwd.h"
+
+LDAP_BEGIN_DECL
+
+extern ldap_pvt_thread_mutex_t passwd_mutex;
+
+extern BI_destroy passwd_back_destroy;
+
+extern BI_op_search passwd_back_search;
+
+LDAP_END_DECL
+
+#endif /* _BACK_PASSWD_H */
diff --git a/servers/slapd/back-passwd/config.c b/servers/slapd/back-passwd/config.c
new file mode 100644
index 0000000..9168e4b
--- /dev/null
+++ b/servers/slapd/back-passwd/config.c
@@ -0,0 +1,73 @@
+/* config.c - passwd backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by the University of Michigan
+ * (as part of U-MICH LDAP).
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "back-passwd.h"
+#include "slap-config.h"
+
+static ConfigTable passwdcfg[] = {
+ { "file", "filename", 2, 2, 0,
+#ifdef HAVE_SETPWFILE
+ ARG_STRING|ARG_OFFSET, NULL,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgDbAt:9.1 NAME 'olcPasswdFile' "
+ "DESC 'File containing passwd records' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs passwdocs[] = {
+ { "( OLcfgDbOc:9.1 "
+ "NAME 'olcPasswdConfig' "
+ "DESC 'Passwd backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY olcPasswdFile )",
+ Cft_Database, passwdcfg },
+ { NULL, 0, NULL }
+};
+
+int
+passwd_back_init_cf( BackendInfo *bi )
+{
+ bi->bi_cf_ocs = passwdocs;
+ return config_register_schema( passwdcfg, passwdocs );
+}
diff --git a/servers/slapd/back-passwd/init.c b/servers/slapd/back-passwd/init.c
new file mode 100644
index 0000000..b855c15
--- /dev/null
+++ b/servers/slapd/back-passwd/init.c
@@ -0,0 +1,122 @@
+/* init.c - initialize passwd backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-passwd.h"
+
+ldap_pvt_thread_mutex_t passwd_mutex;
+
+AttributeDescription *ad_sn;
+AttributeDescription *ad_desc;
+
+static BI_db_init passwd_back_db_init;
+
+int
+passwd_back_initialize(
+ BackendInfo *bi
+)
+{
+ ldap_pvt_thread_mutex_init( &passwd_mutex );
+
+ bi->bi_open = passwd_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = passwd_back_destroy;
+
+ bi->bi_db_init = passwd_back_db_init;
+ bi->bi_db_config = 0;
+ bi->bi_db_open = 0;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = 0;
+
+ bi->bi_op_bind = 0;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = passwd_back_search;
+ bi->bi_op_compare = 0;
+ bi->bi_op_modify = 0;
+ bi->bi_op_modrdn = 0;
+ bi->bi_op_add = 0;
+ bi->bi_op_delete = 0;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ return passwd_back_init_cf( bi );
+}
+
+int
+passwd_back_open(
+ BackendInfo *bi
+)
+{
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( "sn", &ad_sn, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "passwd_back_open: "
+ "slap_str2ad(\"%s\") returned %d: %s\n",
+ "sn", rc, text );
+ return -1;
+ }
+ rc = slap_str2ad( "description", &ad_desc, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "passwd_back_open: "
+ "slap_str2ad(\"%s\") returned %d: %s\n",
+ "description", rc, text );
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+passwd_back_destroy(
+ BackendInfo *bi
+)
+{
+ ldap_pvt_thread_mutex_destroy( &passwd_mutex );
+ return 0;
+}
+
+static int
+passwd_back_db_init(
+ Backend *be,
+ struct config_reply_s *cr
+)
+{
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+ return 0;
+}
+
+#if SLAPD_PASSWD == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( passwd )
+
+#endif /* SLAPD_PASSWD == SLAPD_MOD_DYNAMIC */
+
diff --git a/servers/slapd/back-passwd/proto-passwd.h b/servers/slapd/back-passwd/proto-passwd.h
new file mode 100644
index 0000000..96bc73e
--- /dev/null
+++ b/servers/slapd/back-passwd/proto-passwd.h
@@ -0,0 +1,33 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef PROTO_PASSWD_H
+#define PROTO_PASSWD_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init passwd_back_initialize;
+extern BI_open passwd_back_open;
+extern BI_destroy passwd_back_destroy;
+extern BI_op_search passwd_back_search;
+
+extern int passwd_back_init_cf( BackendInfo *bi );
+
+extern AttributeDescription *ad_sn;
+extern AttributeDescription *ad_desc;
+
+LDAP_END_DECL
+
+#endif /* PROTO_PASSWD_H */
diff --git a/servers/slapd/back-passwd/search.c b/servers/slapd/back-passwd/search.c
new file mode 100644
index 0000000..19d847c
--- /dev/null
+++ b/servers/slapd/back-passwd/search.c
@@ -0,0 +1,381 @@
+/* search.c - /etc/passwd backend search function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by the University of Michigan
+ * (as part of U-MICH LDAP). Additional significant contributors
+ * include:
+ * Hallvard B. Furuseth
+ * Howard Chu
+ * Kurt D. Zeilenga
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include <pwd.h>
+
+#include "slap.h"
+#include "back-passwd.h"
+
+static void pw_start( Backend *be );
+
+static int pw2entry(
+ Backend *be,
+ struct passwd *pw,
+ Entry *ep );
+
+int
+passwd_back_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct passwd *pw;
+ time_t stoptime = (time_t)-1;
+
+ LDAPRDN rdn = NULL;
+ struct berval parent = BER_BVNULL;
+
+ AttributeDescription *ad_objectClass = slap_schema.si_ad_objectClass;
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ stoptime = op->o_time + op->ors_tlimit;
+ }
+
+ /* Handle a query for the base of this backend */
+ if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ struct berval val;
+
+ rs->sr_matched = op->o_req_dn.bv_val;
+
+ if( op->ors_scope != LDAP_SCOPE_ONELEVEL ) {
+ AttributeDescription *desc = NULL;
+ char *next;
+ Entry e = { 0 };
+
+ /* Create an entry corresponding to the base DN */
+ e.e_name.bv_val = ch_strdup( op->o_req_dn.bv_val );
+ e.e_name.bv_len = op->o_req_dn.bv_len;
+ e.e_nname.bv_val = ch_strdup( op->o_req_ndn.bv_val );
+ e.e_nname.bv_len = op->o_req_ndn.bv_len;
+
+ /* Use the first attribute of the DN
+ * as an attribute within the entry itself.
+ */
+ if( ldap_bv2rdn( &op->o_req_dn, &rdn, &next,
+ LDAP_DN_FORMAT_LDAP ) )
+ {
+ rs->sr_err = LDAP_INVALID_DN_SYNTAX;
+ goto done;
+ }
+
+ if( slap_bv2ad( &rdn[0]->la_attr, &desc, &rs->sr_text )) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ ldap_rdnfree(rdn);
+ goto done;
+ }
+
+ attr_merge_normalize_one( &e, desc, &rdn[0]->la_value, NULL );
+
+ ldap_rdnfree(rdn);
+ rdn = NULL;
+
+ /* Every entry needs an objectclass. We don't really
+ * know if our hardcoded choice here agrees with the
+ * DN that was configured for this backend, but it's
+ * better than nothing.
+ *
+ * should be a configurable item
+ */
+ BER_BVSTR( &val, "organizationalUnit" );
+ attr_merge_one( &e, ad_objectClass, &val, NULL );
+
+ if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = &e;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_attrs = NULL;
+ }
+
+ entry_clean( &e );
+ }
+
+ if ( op->ors_scope != LDAP_SCOPE_BASE ) {
+ /* check all our "children" */
+
+ ldap_pvt_thread_mutex_lock( &passwd_mutex );
+ pw_start( op->o_bd );
+ for ( pw = getpwent(); pw != NULL; pw = getpwent() ) {
+ Entry e = { 0 };
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ endpwent();
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ return( SLAPD_ABANDON );
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ send_ldap_error( op, rs, LDAP_TIMELIMIT_EXCEEDED, NULL );
+ endpwent();
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ return( 0 );
+ }
+
+ if ( pw2entry( op->o_bd, pw, &e ) ) {
+ rs->sr_err = LDAP_OTHER;
+ endpwent();
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ goto done;
+ }
+
+ if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+ /* check size limit */
+ if ( --op->ors_slimit == -1 ) {
+ send_ldap_error( op, rs, LDAP_SIZELIMIT_EXCEEDED, NULL );
+ endpwent();
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ return( 0 );
+ }
+
+ rs->sr_entry = &e;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ }
+
+ entry_clean( &e );
+ }
+ endpwent();
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ }
+
+ } else {
+ char *next;
+ Entry e = { 0 };
+ int rc;
+
+ if (! be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ dnParent( &op->o_req_ndn, &parent );
+ }
+
+ /* This backend is only one layer deep. Don't answer requests for
+ * anything deeper than that.
+ */
+ if( !be_issuffix( op->o_bd, &parent ) ) {
+ int i;
+ for( i=0; op->o_bd->be_nsuffix[i].bv_val != NULL; i++ ) {
+ if( dnIsSuffix( &op->o_req_ndn, &op->o_bd->be_nsuffix[i] ) ) {
+ rs->sr_matched = op->o_bd->be_suffix[i].bv_val;
+ break;
+ }
+ }
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto done;
+ }
+
+ if( op->ors_scope == LDAP_SCOPE_ONELEVEL ) {
+ goto done;
+ }
+
+ if ( ldap_bv2rdn( &op->o_req_dn, &rdn, &next,
+ LDAP_DN_FORMAT_LDAP ))
+ {
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ ldap_pvt_thread_mutex_lock( &passwd_mutex );
+ pw_start( op->o_bd );
+ pw = getpwnam( rdn[0]->la_value.bv_val );
+ if ( pw == NULL ) {
+ rs->sr_matched = parent.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ goto done;
+ }
+
+ rc = pw2entry( op->o_bd, pw, &e );
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ if ( rc ) {
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( test_filter( op, &e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+ rs->sr_entry = &e;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ }
+
+ entry_clean( &e );
+ }
+
+done:
+ if( rs->sr_err != LDAP_NO_SUCH_OBJECT ) rs->sr_matched = NULL;
+ send_ldap_result( op, rs );
+
+ if( rdn != NULL ) ldap_rdnfree( rdn );
+
+ return( 0 );
+}
+
+static void
+pw_start(
+ Backend *be
+)
+{
+ endpwent();
+
+#ifdef HAVE_SETPWFILE
+ if ( be->be_private != NULL ) {
+ (void) setpwfile( (char *) be->be_private );
+ }
+#endif /* HAVE_SETPWFILE */
+}
+
+static int
+pw2entry( Backend *be, struct passwd *pw, Entry *e )
+{
+ size_t pwlen;
+ struct berval val;
+ struct berval bv;
+
+ int rc;
+
+ /*
+ * from pw we get pw_name and make it cn
+ * give it an objectclass of person.
+ */
+
+ pwlen = strlen( pw->pw_name );
+ val.bv_len = STRLENOF("uid=,") + ( pwlen + be->be_suffix[0].bv_len );
+ val.bv_val = ch_malloc( val.bv_len + 1 );
+
+ /* rdn attribute type should be a configurable item */
+ sprintf( val.bv_val, "uid=%s,%s",
+ pw->pw_name, be->be_suffix[0].bv_val );
+
+ rc = dnNormalize( 0, NULL, NULL, &val, &bv, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ free( val.bv_val );
+ return( -1 );
+ }
+
+ e->e_name = val;
+ e->e_nname = bv;
+
+ e->e_attrs = NULL;
+
+ /* objectclasses should be configurable items */
+ BER_BVSTR( &val, "person" );
+ attr_merge_one( e, slap_schema.si_ad_objectClass, &val, NULL );
+
+ BER_BVSTR( &val, "uidObject" );
+ attr_merge_one( e, slap_schema.si_ad_objectClass, &val, NULL );
+
+ val.bv_val = pw->pw_name;
+ val.bv_len = pwlen;
+ attr_merge_normalize_one( e, slap_schema.si_ad_uid, &val, NULL ); /* required by uidObject */
+ attr_merge_normalize_one( e, slap_schema.si_ad_cn, &val, NULL ); /* required by person */
+ attr_merge_normalize_one( e, ad_sn, &val, NULL ); /* required by person */
+
+#ifdef HAVE_STRUCT_PASSWD_PW_GECOS
+ /*
+ * if gecos is present, add user's full name as a cn. first process it
+ * according to standard BSD usage. If the processed cn has
+ * a space, use the tail as the surname.
+ */
+ if (pw->pw_gecos[0]) {
+ char *s;
+ char buf[1024];
+
+ ber_str2bv( pw->pw_gecos, 0, 0, &val );
+ attr_merge_normalize_one( e, ad_desc, &val, NULL );
+
+ s = strchr( val.bv_val, (unsigned char)',' );
+ if ( s ) {
+ *s = '\0';
+ val.bv_len = s - val.bv_val;
+ }
+
+ s = strchr( val.bv_val, (unsigned char)'&' );
+ if ( s ) {
+ unsigned r = sizeof buf;
+ /* if name with expanded `&` fits in buf */
+ if ( val.bv_len + pwlen <= r ) {
+ char * d = buf;
+
+ for (;;) {
+ size_t const i = s - val.bv_val;
+ memcpy( d, val.bv_val, i );
+ d += i;
+ memcpy( d, pw->pw_name, pwlen );
+ *d = TOUPPER((unsigned char)*pw->pw_name);
+ d += pwlen;
+ r -= pwlen + i;
+ val.bv_len -= i + 1;
+ val.bv_val = s + 1;
+
+ s = strchr( val.bv_val, (unsigned char)'&' );
+ if (!(s && ( val.bv_len + pwlen <= r )))
+ break;
+ }
+ strcpy( d, val.bv_val );
+ val.bv_len = d - buf + val.bv_len;
+ val.bv_val = buf;
+ } // 1st fits
+ } // 1st &
+
+ if ( val.bv_len && strcasecmp( val.bv_val, pw->pw_name ) ) {
+ attr_merge_normalize_one( e, slap_schema.si_ad_cn, &val, NULL );
+ }
+
+ if ( ( s = strrchr(val.bv_val, ' ' ) ) ) {
+ ber_str2bv( s + 1, 0, 0, &val );
+ attr_merge_normalize_one( e, ad_sn, &val, NULL );
+ }
+ }
+#endif /* HAVE_STRUCT_PASSWD_PW_GECOS */
+
+ return( 0 );
+}
diff --git a/servers/slapd/back-perl/Makefile.in b/servers/slapd/back-perl/Makefile.in
new file mode 100644
index 0000000..3fed1e3
--- /dev/null
+++ b/servers/slapd/back-perl/Makefile.in
@@ -0,0 +1,46 @@
+# Makefile.in for back-perl
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## Portions Copyright 1999 John C. Quillan.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c search.c close.c config.c bind.c compare.c \
+ modify.c add.c modrdn.c delete.c
+OBJS = init.lo search.lo close.lo config.lo bind.lo compare.lo \
+ modify.lo add.lo modrdn.lo delete.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-perl"
+BUILD_MOD = @BUILD_PERL@
+PERL_CPPFLAGS = @PERL_CPPFLAGS@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_PERL@_DEFS)
+MOD_LIBS = @MOD_PERL_LDFLAGS@
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_perl
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(PERL_CPPFLAGS) $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-perl/README b/servers/slapd/back-perl/README
new file mode 100644
index 0000000..1e14a30
--- /dev/null
+++ b/servers/slapd/back-perl/README
@@ -0,0 +1,24 @@
+Differences from 2.0 Perl API:
+
+- Perl 5.6 is supported
+
+- backend methods return actual LDAP result codes, not
+ true/false; this gives the Perl module finer control
+ of the error returned to the client
+
+- a filterSearchResults configuration file directive was
+ added to tell the backend glue that the results returned
+ from the Perl module are candidates only
+
+- the "init" method is called after the backend has been
+ initialized - this lets you do some initialization after
+ *all* configuration file directives have been read
+
+- the interface for the search method is improved to
+ pass the scope, dereferencing policy, size limit, etc.
+ See SampleLDAP.pm for details.
+
+These changes were sponsored by myinternet Limited.
+
+Luke Howard <lukeh@padl.com>
+
diff --git a/servers/slapd/back-perl/SampleLDAP.pm b/servers/slapd/back-perl/SampleLDAP.pm
new file mode 100644
index 0000000..91e9ae3
--- /dev/null
+++ b/servers/slapd/back-perl/SampleLDAP.pm
@@ -0,0 +1,171 @@
+# This is a sample Perl module for the OpenLDAP server slapd.
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## Portions Copyright 1999 John C. Quillan.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+# Usage: Add something like this to slapd.conf:
+#
+# database perl
+# suffix "o=AnyOrg,c=US"
+# perlModulePath /directory/containing/this/module
+# perlModule SampleLDAP
+#
+# See the slapd-perl(5) manual page for details.
+#
+# This demo module keeps an in-memory hash {"DN" => "LDIF entry", ...}
+# built in sub add{} & co. The data is lost when slapd shuts down.
+
+package SampleLDAP;
+use strict;
+use warnings;
+use POSIX;
+
+$SampleLDAP::VERSION = '1.01';
+
+sub new {
+ my $class = shift;
+
+ my $this = {};
+ bless $this, $class;
+ print {*STDERR} "Here in new\n";
+ print {*STDERR} 'Posix Var ' . BUFSIZ . ' and ' . FILENAME_MAX . "\n";
+ return $this;
+}
+
+sub init {
+ return 0;
+}
+
+sub search {
+ my $this = shift;
+ my ( $base, $scope, $deref, $sizeLim, $timeLim, $filterStr, $attrOnly,
+ @attrs )
+ = @_;
+ print {*STDERR} "====$filterStr====\n";
+ $filterStr =~ s/\(|\)//gm;
+ $filterStr =~ s/=/: /m;
+
+ my @match_dn = ();
+ for my $dn ( keys %{$this} ) {
+ if ( $this->{$dn} =~ /$filterStr/imx ) {
+ push @match_dn, $dn;
+ last if ( scalar @match_dn == $sizeLim );
+
+ }
+ }
+
+ my @match_entries = ();
+
+ for my $dn (@match_dn) {
+ push @match_entries, $this->{$dn};
+ }
+
+ return ( 0, @match_entries );
+
+}
+
+sub compare {
+ my $this = shift;
+ my ( $dn, $avaStr ) = @_;
+ my $rc = 5; # LDAP_COMPARE_FALSE
+
+ $avaStr =~ s/=/: /m;
+
+ if ( $this->{$dn} =~ /$avaStr/im ) {
+ $rc = 6; # LDAP_COMPARE_TRUE
+ }
+
+ return $rc;
+}
+
+sub modify {
+ my $this = shift;
+
+ my ( $dn, @list ) = @_;
+
+ while ( @list > 0 ) {
+ my $action = shift @list;
+ my $key = shift @list;
+ my $value = shift @list;
+
+ if ( $action eq 'ADD' ) {
+ $this->{$dn} .= "$key: $value\n";
+
+ }
+ elsif ( $action eq 'DELETE' ) {
+ $this->{$dn} =~ s/^$key:\s*$value\n//im;
+
+ }
+ elsif ( $action eq 'REPLACE' ) {
+ $this->{$dn} =~ s/$key: .*$/$key: $value/im;
+ }
+ }
+
+ return 0;
+}
+
+sub add {
+ my $this = shift;
+
+ my ($entryStr) = @_;
+
+ my ($dn) = ( $entryStr =~ /dn:\s(.*)$/m );
+
+ #
+ # This needs to be here until a normalized dn is
+ # passed to this routine.
+ #
+ $dn = uc $dn;
+ $dn =~ s/\s*//gm;
+
+ $this->{$dn} = $entryStr;
+
+ return 0;
+}
+
+sub modrdn {
+ my $this = shift;
+
+ my ( $dn, $newdn, $delFlag ) = @_;
+
+ $this->{$newdn} = $this->{$dn};
+
+ if ($delFlag) {
+ delete $this->{$dn};
+ }
+ return 0;
+
+}
+
+sub delete {
+ my $this = shift;
+
+ my ($dn) = @_;
+
+ print {*STDERR} "XXXXXX $dn XXXXXXX\n";
+ delete $this->{$dn};
+ return 0;
+}
+
+sub config {
+ my $this = shift;
+
+ my (@args) = @_;
+ local $, = ' - ';
+ print {*STDERR} @args;
+ print {*STDERR} "\n";
+ return 0;
+}
+
+1;
diff --git a/servers/slapd/back-perl/add.c b/servers/slapd/back-perl/add.c
new file mode 100644
index 0000000..2e6cd5c
--- /dev/null
+++ b/servers/slapd/back-perl/add.c
@@ -0,0 +1,62 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+
+int
+perl_back_add(
+ Operation *op,
+ SlapReply *rs )
+{
+ PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private;
+ int len;
+ int count;
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+ ldap_pvt_thread_mutex_lock( &entry2str_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( entry2str( op->ora_e, &len ), 0 )));
+
+ PUTBACK;
+
+ count = call_method("add", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in back_add\n");
+ }
+
+ rs->sr_err = POPi;
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_ANY, "Perl ADD\n" );
+ return( 0 );
+}
diff --git a/servers/slapd/back-perl/asperl_undefs.h b/servers/slapd/back-perl/asperl_undefs.h
new file mode 100644
index 0000000..80a9243
--- /dev/null
+++ b/servers/slapd/back-perl/asperl_undefs.h
@@ -0,0 +1,38 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* This file is probably obsolete. If it is not, */
+/* #inclusion of it may have to be moved. See ITS#2513. */
+
+/* This file is necessary because both PERL headers */
+/* and OpenLDAP define a number of macros without */
+/* checking whether they're already defined */
+
+#ifndef ASPERL_UNDEFS_H
+#define ASPERL_UNDEFS_H
+
+/* ActiveState Win32 PERL port support */
+/* set in ldap/include/portable.h */
+# ifdef HAVE_WIN32_ASPERL
+/* The following macros are undefined to prevent */
+/* redefinition in PERL headers*/
+# undef gid_t
+# undef uid_t
+# undef mode_t
+# undef caddr_t
+# undef WIN32_LEAN_AND_MEAN
+# endif
+#endif
+
diff --git a/servers/slapd/back-perl/bind.c b/servers/slapd/back-perl/bind.c
new file mode 100644
index 0000000..915c911
--- /dev/null
+++ b/servers/slapd/back-perl/bind.c
@@ -0,0 +1,80 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+
+
+/**********************************************************
+ *
+ * Bind
+ *
+ **********************************************************/
+int
+perl_back_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ int count;
+
+ PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private;
+
+ /* allow rootdn as a means to auth without the need to actually
+ * contact the proxied DSA */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ return rs->sr_err;
+ }
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(SP);
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len)));
+ XPUSHs(sv_2mortal(newSVpv( op->orb_cred.bv_val , op->orb_cred.bv_len)));
+ PUTBACK;
+
+ count = call_method("bind", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in back_bind\n");
+ }
+
+ rs->sr_err = POPi;
+
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ Debug( LDAP_DEBUG_ANY, "Perl BIND returned 0x%04x\n", rs->sr_err );
+
+ /* frontend will send result on success (0) */
+ if( rs->sr_err != LDAP_SUCCESS )
+ send_ldap_result( op, rs );
+
+ return ( rs->sr_err );
+}
diff --git a/servers/slapd/back-perl/close.c b/servers/slapd/back-perl/close.c
new file mode 100644
index 0000000..88b0a2f
--- /dev/null
+++ b/servers/slapd/back-perl/close.c
@@ -0,0 +1,59 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+#include "../slap-config.h"
+/**********************************************************
+ *
+ * Close
+ *
+ **********************************************************/
+
+int
+perl_back_close(
+ BackendInfo *bd
+)
+{
+ perl_destruct(PERL_INTERPRETER);
+ perl_free(PERL_INTERPRETER);
+ PERL_INTERPRETER = NULL;
+#ifdef PERL_SYS_TERM
+ PERL_SYS_TERM();
+#endif
+
+ ldap_pvt_thread_mutex_destroy( &perl_interpreter_mutex );
+
+ return 0;
+}
+
+int
+perl_back_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ PerlBackend *pb = be->be_private;
+
+ ch_free( pb->pb_module_name );
+ ber_bvarray_free( pb->pb_module_path );
+ ber_bvarray_free( pb->pb_module_config );
+
+ free( be->be_private );
+ be->be_private = NULL;
+
+ return 0;
+}
diff --git a/servers/slapd/back-perl/compare.c b/servers/slapd/back-perl/compare.c
new file mode 100644
index 0000000..08e62c9
--- /dev/null
+++ b/servers/slapd/back-perl/compare.c
@@ -0,0 +1,80 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+#include "lutil.h"
+
+/**********************************************************
+ *
+ * Compare
+ *
+ **********************************************************/
+
+int
+perl_back_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ int count, avalen;
+ char *avastr;
+
+ PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private;
+
+ avalen = op->orc_ava->aa_desc->ad_cname.bv_len + 1 +
+ op->orc_ava->aa_value.bv_len;
+ avastr = ch_malloc( avalen + 1 );
+
+ lutil_strcopy( lutil_strcopy( lutil_strcopy( avastr,
+ op->orc_ava->aa_desc->ad_cname.bv_val ), "=" ),
+ op->orc_ava->aa_value.bv_val );
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len)));
+ XPUSHs(sv_2mortal(newSVpv( avastr , avalen)));
+ PUTBACK;
+
+ count = call_method("compare", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in back_compare\n");
+ }
+
+ rs->sr_err = POPi;
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ ch_free( avastr );
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_ANY, "Perl COMPARE\n" );
+
+ return (0);
+}
+
diff --git a/servers/slapd/back-perl/config.c b/servers/slapd/back-perl/config.c
new file mode 100644
index 0000000..21f198b
--- /dev/null
+++ b/servers/slapd/back-perl/config.c
@@ -0,0 +1,256 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+#include "../slap-config.h"
+
+static ConfigDriver perl_cf;
+
+enum {
+ PERL_MODULE = 1,
+ PERL_PATH,
+ PERL_CONFIG
+};
+
+static ConfigTable perlcfg[] = {
+ { "perlModule", "module", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|PERL_MODULE, perl_cf,
+ "( OLcfgDbAt:11.1 NAME 'olcPerlModule' "
+ "DESC 'Perl module name' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "perlModulePath", "path", 2, 2, 0,
+ ARG_MAGIC|PERL_PATH, perl_cf,
+ "( OLcfgDbAt:11.2 NAME 'olcPerlModulePath' "
+ "DESC 'Perl module path' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "filterSearchResults", "on|off", 2, 2, 0, ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(PerlBackend, pb_filter_search_results),
+ "( OLcfgDbAt:11.3 NAME 'olcPerlFilterSearchResults' "
+ "DESC 'Filter search results before returning to client' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "perlModuleConfig", "args", 2, 0, 0,
+ ARG_MAGIC|PERL_CONFIG, perl_cf,
+ "( OLcfgDbAt:11.4 NAME 'olcPerlModuleConfig' "
+ "DESC 'Perl module config directives' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs perlocs[] = {
+ { "( OLcfgDbOc:11.1 "
+ "NAME 'olcDbPerlConfig' "
+ "DESC 'Perl DB configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST ( olcPerlModulePath $ olcPerlModule ) "
+ "MAY ( olcPerlFilterSearchResults $ olcPerlModuleConfig ) )",
+ Cft_Database, perlcfg, NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs ovperlocs[] = {
+ { "( OLcfgDbOc:11.2 "
+ "NAME 'olcovPerlConfig' "
+ "DESC 'Perl overlay configuration' "
+ "SUP olcOverlayConfig "
+ "MUST ( olcPerlModulePath $ olcPerlModule ) "
+ "MAY ( olcPerlFilterSearchResults $ olcPerlModuleConfig ) )",
+ Cft_Overlay, perlcfg, NULL, NULL },
+ { NULL }
+};
+
+/**********************************************************
+ *
+ * Config
+ *
+ **********************************************************/
+int
+perl_back_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ int rc = config_generic_wrapper( be, fname, lineno, argc, argv );
+ /* backward compatibility: map unknown directives to perlModuleConfig */
+ if ( rc == SLAP_CONF_UNKNOWN ) {
+ char **av = ch_malloc( (argc+2) * sizeof(char *));
+ int i;
+ av[0] = "perlModuleConfig";
+ av++;
+ for ( i=0; i<argc; i++ )
+ av[i] = argv[i];
+ av[i] = NULL;
+ av--;
+ rc = config_generic_wrapper( be, fname, lineno, argc+1, av );
+ ch_free( av );
+ }
+ return rc;
+}
+
+static int
+perl_cf(
+ ConfigArgs *c
+)
+{
+ PerlBackend *pb = (PerlBackend *) c->be->be_private;
+ SV* loc_sv;
+ int count ;
+ int args;
+ int rc = 0;
+ char eval_str[EVAL_BUF_SIZE];
+ struct berval bv;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c-> type ) {
+ case PERL_MODULE:
+ if ( !pb->pb_module_name )
+ return 1;
+ c->value_string = ch_strdup( pb->pb_module_name );
+ break;
+ case PERL_PATH:
+ if ( !pb->pb_module_path )
+ return 1;
+ ber_bvarray_dup_x( &c->rvalue_vals, pb->pb_module_path, NULL );
+ break;
+ case PERL_CONFIG:
+ if ( !pb->pb_module_config )
+ return 1;
+ ber_bvarray_dup_x( &c->rvalue_vals, pb->pb_module_config, NULL );
+ break;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* FIXME: none of this affects the state of the perl
+ * interpreter at all. We should probably destroy it
+ * and recreate it...
+ */
+ switch( c-> type ) {
+ case PERL_MODULE:
+ ch_free( pb->pb_module_name );
+ pb->pb_module_name = NULL;
+ break;
+ case PERL_PATH:
+ if ( c->valx < 0 ) {
+ ber_bvarray_free( pb->pb_module_path );
+ pb->pb_module_path = NULL;
+ } else {
+ int i = c->valx;
+ ch_free( pb->pb_module_path[i].bv_val );
+ for (; pb->pb_module_path[i].bv_val; i++ )
+ pb->pb_module_path[i] = pb->pb_module_path[i+1];
+ }
+ break;
+ case PERL_CONFIG:
+ if ( c->valx < 0 ) {
+ ber_bvarray_free( pb->pb_module_config );
+ pb->pb_module_config = NULL;
+ } else {
+ int i = c->valx;
+ ch_free( pb->pb_module_config[i].bv_val );
+ for (; pb->pb_module_config[i].bv_val; i++ )
+ pb->pb_module_config[i] = pb->pb_module_config[i+1];
+ }
+ break;
+ }
+ } else {
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ switch( c->type ) {
+ case PERL_MODULE:
+ snprintf( eval_str, EVAL_BUF_SIZE, "use %s;", c->argv[1] );
+ eval_pv( eval_str, 0 );
+
+ if (SvTRUE(ERRSV)) {
+ STRLEN len;
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: error %s",
+ c->log, SvPV(ERRSV, len ));
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ rc = 1;
+ } else {
+ dSP; ENTER; SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVpv(c->argv[1], 0)));
+ PUTBACK;
+
+ count = call_method("new", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in config\n") ;
+ }
+
+ pb->pb_obj_ref = newSVsv(POPs);
+
+ PUTBACK; FREETMPS; LEAVE ;
+ pb->pb_module_name = ch_strdup( c->argv[1] );
+ }
+ break;
+
+ case PERL_PATH:
+ snprintf( eval_str, EVAL_BUF_SIZE, "push @INC, '%s';", c->argv[1] );
+ loc_sv = eval_pv( eval_str, 0 );
+ /* XXX loc_sv return value is ignored. */
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ value_add_one( &pb->pb_module_path, &bv );
+ break;
+
+ case PERL_CONFIG: {
+ dSP ; ENTER ; SAVETMPS;
+
+ PUSHMARK(sp) ;
+ XPUSHs( pb->pb_obj_ref );
+
+ /* Put all arguments on the perl stack */
+ for( args = 1; args < c->argc; args++ )
+ XPUSHs(sv_2mortal(newSVpv(c->argv[args], 0)));
+
+ ber_str2bv( c->line + STRLENOF("perlModuleConfig "), 0, 0, &bv );
+ value_add_one( &pb->pb_module_config, &bv );
+
+ PUTBACK ;
+
+ count = call_method("config", G_SCALAR);
+
+ SPAGAIN ;
+
+ if (count != 1) {
+ croak("Big trouble in config\n") ;
+ }
+
+ rc = POPi;
+
+ PUTBACK ; FREETMPS ; LEAVE ;
+ }
+ break;
+ }
+ }
+ return rc;
+}
+
+int
+perl_back_init_cf( BackendInfo *bi )
+{
+ bi->bi_cf_ocs = perlocs;
+
+ return config_register_schema( perlcfg, perlocs );
+}
diff --git a/servers/slapd/back-perl/delete.c b/servers/slapd/back-perl/delete.c
new file mode 100644
index 0000000..68c1b3a
--- /dev/null
+++ b/servers/slapd/back-perl/delete.c
@@ -0,0 +1,59 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+
+int
+perl_back_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private;
+ int count;
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len )));
+
+ PUTBACK;
+
+ count = call_method("delete", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in perl-back_delete\n");
+ }
+
+ rs->sr_err = POPi;
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_ANY, "Perl DELETE\n" );
+ return( 0 );
+}
diff --git a/servers/slapd/back-perl/init.c b/servers/slapd/back-perl/init.c
new file mode 100644
index 0000000..644c855
--- /dev/null
+++ b/servers/slapd/back-perl/init.c
@@ -0,0 +1,176 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+#include "../slap-config.h"
+
+#ifdef PERL_SYS_INIT3
+#include <ac/unistd.h> /* maybe get environ */
+extern char **environ;
+#endif
+
+static void perl_back_xs_init LDAP_P((PERL_BACK_XS_INIT_PARAMS));
+EXT void boot_DynaLoader LDAP_P((PERL_BACK_BOOT_DYNALOADER_PARAMS));
+
+PerlInterpreter *PERL_INTERPRETER = NULL;
+ldap_pvt_thread_mutex_t perl_interpreter_mutex;
+
+
+/**********************************************************
+ *
+ * Init
+ *
+ **********************************************************/
+
+int
+perl_back_initialize(
+ BackendInfo *bi
+)
+{
+ char *embedding[] = { "", "-e", "0", NULL }, **argv = embedding;
+ int argc = 3;
+#ifdef PERL_SYS_INIT3
+ char **env = environ;
+#else
+ char **env = NULL;
+#endif
+
+ bi->bi_open = NULL;
+ bi->bi_config = 0;
+ bi->bi_close = perl_back_close;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = perl_back_db_init;
+ bi->bi_db_config = perl_back_db_config;
+ bi->bi_db_open = perl_back_db_open;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = perl_back_db_destroy;
+
+ bi->bi_op_bind = perl_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = perl_back_search;
+ bi->bi_op_compare = perl_back_compare;
+ bi->bi_op_modify = perl_back_modify;
+ bi->bi_op_modrdn = perl_back_modrdn;
+ bi->bi_op_add = perl_back_add;
+ bi->bi_op_delete = perl_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ /* injecting code from perl_back_open, because using function reference (bi->bi_open) is not functional */
+ Debug( LDAP_DEBUG_TRACE, "perl backend open\n" );
+
+ if( PERL_INTERPRETER != NULL ) {
+ Debug( LDAP_DEBUG_ANY, "perl backend open: already opened\n" );
+ return 1;
+ }
+
+ ldap_pvt_thread_mutex_init( &perl_interpreter_mutex );
+
+#ifdef PERL_SYS_INIT3
+ PERL_SYS_INIT3(&argc, &argv, &env);
+#endif
+ PERL_INTERPRETER = perl_alloc();
+ perl_construct(PERL_INTERPRETER);
+#ifdef PERL_EXIT_DESTRUCT_END
+ PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+#endif
+ perl_parse(PERL_INTERPRETER, perl_back_xs_init, argc, argv, env);
+ perl_run(PERL_INTERPRETER);
+ return perl_back_init_cf( bi );
+}
+
+int
+perl_back_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ be->be_private = (PerlBackend *) ch_malloc( sizeof(PerlBackend) );
+ memset( be->be_private, '\0', sizeof(PerlBackend));
+
+ ((PerlBackend *)be->be_private)->pb_filter_search_results = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "perl backend db init\n" );
+
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return 0;
+}
+
+int
+perl_back_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ int count;
+ int return_code;
+
+ PerlBackend *perl_back = (PerlBackend *) be->be_private;
+
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs( perl_back->pb_obj_ref );
+
+ PUTBACK;
+
+ count = call_method("init", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in perl_back_db_open\n");
+ }
+
+ return_code = POPi;
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ return return_code;
+}
+
+
+static void
+perl_back_xs_init(PERL_BACK_XS_INIT_PARAMS)
+{
+ char *file = __FILE__;
+ dXSUB_SYS;
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+}
+
+#if SLAPD_PERL == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( perl )
+
+#endif /* SLAPD_PERL == SLAPD_MOD_DYNAMIC */
+
+
diff --git a/servers/slapd/back-perl/modify.c b/servers/slapd/back-perl/modify.c
new file mode 100644
index 0000000..94fed62
--- /dev/null
+++ b/servers/slapd/back-perl/modify.c
@@ -0,0 +1,97 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+#include <ac/string.h>
+
+int
+perl_back_modify(
+ Operation *op,
+ SlapReply *rs )
+{
+ PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private;
+ Modifications *modlist = op->orm_modlist;
+ int count;
+ int i;
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , 0)));
+
+ for (; modlist != NULL; modlist = modlist->sml_next ) {
+ Modification *mods = &modlist->sml_mod;
+
+ switch ( mods->sm_op & ~LDAP_MOD_BVALUES ) {
+ case LDAP_MOD_ADD:
+ XPUSHs(sv_2mortal(newSVpv("ADD", STRLENOF("ADD") )));
+ break;
+
+ case LDAP_MOD_DELETE:
+ XPUSHs(sv_2mortal(newSVpv("DELETE", STRLENOF("DELETE") )));
+ break;
+
+ case LDAP_MOD_REPLACE:
+ XPUSHs(sv_2mortal(newSVpv("REPLACE", STRLENOF("REPLACE") )));
+ break;
+ }
+
+
+ XPUSHs(sv_2mortal(newSVpv( mods->sm_desc->ad_cname.bv_val,
+ mods->sm_desc->ad_cname.bv_len )));
+
+ for ( i = 0;
+ mods->sm_values != NULL && mods->sm_values[i].bv_val != NULL;
+ i++ )
+ {
+ XPUSHs(sv_2mortal(newSVpv( mods->sm_values[i].bv_val, mods->sm_values[i].bv_len )));
+ }
+
+ /* Fix delete attrib without value. */
+ if ( i == 0) {
+ XPUSHs(sv_newmortal());
+ }
+ }
+
+ PUTBACK;
+
+ count = call_method("modify", G_SCALAR);
+
+ SPAGAIN;
+
+ if (count != 1) {
+ croak("Big trouble in back_modify\n");
+ }
+
+ rs->sr_err = POPi;
+
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_ANY, "Perl MODIFY\n" );
+ return( 0 );
+}
+
diff --git a/servers/slapd/back-perl/modrdn.c b/servers/slapd/back-perl/modrdn.c
new file mode 100644
index 0000000..4f2dc81
--- /dev/null
+++ b/servers/slapd/back-perl/modrdn.c
@@ -0,0 +1,63 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+
+int
+perl_back_modrdn(
+ Operation *op,
+ SlapReply *rs )
+{
+ PerlBackend *perl_back = (PerlBackend *) op->o_bd->be_private;
+ int count;
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp) ;
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_dn.bv_val , op->o_req_dn.bv_len )));
+ XPUSHs(sv_2mortal(newSVpv( op->orr_newrdn.bv_val , op->orr_newrdn.bv_len )));
+ XPUSHs(sv_2mortal(newSViv( op->orr_deleteoldrdn )));
+ if ( op->orr_newSup != NULL ) {
+ XPUSHs(sv_2mortal(newSVpv( op->orr_newSup->bv_val , op->orr_newSup->bv_len )));
+ }
+ PUTBACK ;
+
+ count = call_method("modrdn", G_SCALAR);
+
+ SPAGAIN ;
+
+ if (count != 1) {
+ croak("Big trouble in back_modrdn\n") ;
+ }
+
+ rs->sr_err = POPi;
+
+ PUTBACK; FREETMPS; LEAVE ;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_ANY, "Perl MODRDN\n" );
+ return( 0 );
+}
diff --git a/servers/slapd/back-perl/perl_back.h b/servers/slapd/back-perl/perl_back.h
new file mode 100644
index 0000000..9446c2d
--- /dev/null
+++ b/servers/slapd/back-perl/perl_back.h
@@ -0,0 +1,82 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef PERL_BACK_H
+#define PERL_BACK_H 1
+
+#include <EXTERN.h>
+#include <perl.h>
+#undef _ /* #defined by both Perl and ac/localize.h */
+#include "asperl_undefs.h"
+
+#include "portable.h"
+
+#include "slap.h"
+
+LDAP_BEGIN_DECL
+
+/*
+ * From Apache mod_perl: test for Perl version.
+ */
+
+#if defined(pTHX_) || (PERL_REVISION > 5 || (PERL_REVISION == 5 && PERL_VERSION >= 6))
+#define PERL_IS_5_6
+#endif
+
+#define EVAL_BUF_SIZE 500
+
+extern ldap_pvt_thread_mutex_t perl_interpreter_mutex;
+
+#ifdef PERL_IS_5_6
+/* We should be using the PL_errgv, I think */
+/* All the old style variables are prefixed with PL_ now */
+# define errgv PL_errgv
+# define na PL_na
+#else
+# define call_method(m, f) perl_call_method(m, f)
+# define eval_pv(m, f) perl_eval_pv(m, f)
+# define ERRSV GvSV(errgv)
+#endif
+
+#if defined( HAVE_WIN32_ASPERL ) || defined( USE_ITHREADS )
+/* pTHX is needed often now */
+# define PERL_INTERPRETER my_perl
+# define PERL_BACK_XS_INIT_PARAMS pTHX
+# define PERL_BACK_BOOT_DYNALOADER_PARAMS pTHX, CV *cv
+#else
+# define PERL_INTERPRETER perl_interpreter
+# define PERL_BACK_XS_INIT_PARAMS void
+# define PERL_BACK_BOOT_DYNALOADER_PARAMS CV *cv
+# define PERL_SET_CONTEXT(i)
+#endif
+
+extern PerlInterpreter *PERL_INTERPRETER;
+
+
+typedef struct perl_backend_instance {
+ char *pb_module_name;
+ BerVarray pb_module_path;
+ BerVarray pb_module_config;
+ SV *pb_obj_ref;
+ int pb_filter_search_results;
+} PerlBackend;
+
+LDAP_END_DECL
+
+#include "proto-perl.h"
+
+#endif /* PERL_BACK_H */
diff --git a/servers/slapd/back-perl/proto-perl.h b/servers/slapd/back-perl/proto-perl.h
new file mode 100644
index 0000000..b6cb387
--- /dev/null
+++ b/servers/slapd/back-perl/proto-perl.h
@@ -0,0 +1,43 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef PROTO_PERL_H
+#define PROTO_PERL_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init perl_back_initialize;
+
+extern BI_close perl_back_close;
+
+extern BI_db_init perl_back_db_init;
+extern BI_db_open perl_back_db_open;
+extern BI_db_destroy perl_back_db_destroy;
+extern BI_db_config perl_back_db_config;
+
+extern BI_op_bind perl_back_bind;
+extern BI_op_search perl_back_search;
+extern BI_op_compare perl_back_compare;
+extern BI_op_modify perl_back_modify;
+extern BI_op_modrdn perl_back_modrdn;
+extern BI_op_add perl_back_add;
+extern BI_op_delete perl_back_delete;
+
+extern int perl_back_init_cf( BackendInfo *bi );
+LDAP_END_DECL
+
+#endif /* PROTO_PERL_H */
diff --git a/servers/slapd/back-perl/search.c b/servers/slapd/back-perl/search.c
new file mode 100644
index 0000000..7566b06
--- /dev/null
+++ b/servers/slapd/back-perl/search.c
@@ -0,0 +1,122 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 John C. Quillan.
+ * Portions Copyright 2002 myinternet Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "perl_back.h"
+
+/**********************************************************
+ *
+ * Search
+ *
+ **********************************************************/
+int
+perl_back_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ PerlBackend *perl_back = (PerlBackend *)op->o_bd->be_private;
+ int count ;
+ AttributeName *an;
+ Entry *e;
+ char *buf;
+ int i;
+
+ PERL_SET_CONTEXT( PERL_INTERPRETER );
+ ldap_pvt_thread_mutex_lock( &perl_interpreter_mutex );
+
+ {
+ dSP; ENTER; SAVETMPS;
+
+ PUSHMARK(sp) ;
+ XPUSHs( perl_back->pb_obj_ref );
+ XPUSHs(sv_2mortal(newSVpv( op->o_req_ndn.bv_val , op->o_req_ndn.bv_len)));
+ XPUSHs(sv_2mortal(newSViv( op->ors_scope )));
+ XPUSHs(sv_2mortal(newSViv( op->ors_deref )));
+ XPUSHs(sv_2mortal(newSViv( op->ors_slimit )));
+ XPUSHs(sv_2mortal(newSViv( op->ors_tlimit )));
+ XPUSHs(sv_2mortal(newSVpv( op->ors_filterstr.bv_val , op->ors_filterstr.bv_len)));
+ XPUSHs(sv_2mortal(newSViv( op->ors_attrsonly )));
+
+ for ( an = op->ors_attrs; an && an->an_name.bv_val; an++ ) {
+ XPUSHs(sv_2mortal(newSVpv( an->an_name.bv_val , an->an_name.bv_len)));
+ }
+ PUTBACK;
+
+ count = call_method("search", G_ARRAY );
+
+ SPAGAIN;
+
+ if (count < 1) {
+ croak("Big trouble in back_search\n") ;
+ }
+
+ if ( count > 1 ) {
+
+ for ( i = 1; i < count; i++ ) {
+
+ buf = POPp;
+
+ if ( (e = str2entry( buf )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "str2entry(%s) failed\n", buf );
+
+ } else {
+ int send_entry;
+
+ if (perl_back->pb_filter_search_results)
+ send_entry = (test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE);
+ else
+ send_entry = 1;
+
+ if (send_entry) {
+ rs->sr_entry = e;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_err = send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_attrs = NULL;
+ rs->sr_entry = NULL;
+ if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED || rs->sr_err == LDAP_BUSY ) {
+ goto done;
+ }
+ }
+
+ entry_free( e );
+ }
+ }
+ }
+
+ /*
+ * We grab the return code last because the stack comes
+ * from perl in reverse order.
+ *
+ * ex perl: return ( 0, $res_1, $res_2 );
+ *
+ * ex stack: <$res_2> <$res_1> <0>
+ */
+
+ rs->sr_err = POPi;
+
+done:;
+ PUTBACK; FREETMPS; LEAVE;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &perl_interpreter_mutex );
+
+ send_ldap_result( op, rs );
+
+ return 0;
+}
diff --git a/servers/slapd/back-relay/Makefile.in b/servers/slapd/back-relay/Makefile.in
new file mode 100644
index 0000000..5f111bc
--- /dev/null
+++ b/servers/slapd/back-relay/Makefile.in
@@ -0,0 +1,41 @@
+# Makefile.in for back-relay
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c op.c
+OBJS = init.lo op.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-relay"
+BUILD_MOD = @BUILD_RELAY@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_RELAY@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(REWRITE)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(REWRITE)
+
+LIBBASE = back_relay
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-relay/README b/servers/slapd/back-relay/README
new file mode 100644
index 0000000..81f152c
--- /dev/null
+++ b/servers/slapd/back-relay/README
@@ -0,0 +1,83 @@
+Relay backend sets up a relay virtual database that allows
+to access other databases in the same instance of slapd
+through different naming contexts and remapping attribute
+values.
+
+The DN rewrite, filter rewrite and attributeType/objectClass
+mapping is done by means of the rewrite-remap overlay.
+
+The database containing the real naming context can be
+explicitly selected by means of the "relay" directive,
+which must contain the naming context of the target
+database. This also causes the rewrite-remap overlay
+to be automatically instantiated. If the optional keyword
+"massage" is present, the rewrite-remap overlay is
+automatically configured to map the virtual to the real
+naming context and vice-versa.
+
+Otherwise, the rewrite-remap overlay must be explicitly
+instantiated, by using the "overlay" directive, as
+illustrated below. This allows much more freedom in target
+database selection and DN rewriting.
+
+If the "relay" directive is not present, the backend is
+not bound to a single target database; on the contrary,
+the target database is selected on a per-operation basis.
+
+This allows, for instance, to relay one database for
+authentication and anotheir for search/modify, or allows
+to use one target for persons and another for groups
+and so on.
+
+To summarize: the "relay" directive:
+- explicitly bounds the database to a single database
+ holding the real naming context;
+- automatically instantiates the rewrite-remap overlay;
+- automatically configures the naming context massaging
+ if the optional "massage" keyword is added
+
+If the "relay" directive is not used, the rewrite-remap
+overlay must be explicitly instantiated and the massaging
+must be configured, either by using the "suffixmassage"
+directive, or by issuing more sophisticate rewrite
+instructions.
+
+AttributeType/objectClass mapping must be explicitly
+required.
+
+Note that the rewrite-remap overlay is not complete nor
+production- ready yet.
+Examples are given of all the suggested usages.
+
+# automatically massage from virtual to real naming context
+database relay
+suffix "dc=virtual,dc=naming,dc=context"
+relay "dc=real,dc=naming,dc=context" massage
+
+# explicitly massage (same as above)
+database relay
+suffix "dc=virtual,dc=naming,dc=context"
+relay "dc=real,dc=naming,dc=context"
+suffixmassage "dc=virtual,dc=naming,dc=context" \
+ "dc=real,dc=naming,dc=context"
+
+# explicitly massage (same as above, but dynamic backend resolution)
+database relay
+suffix "dc=virtual,dc=naming,dc=context"
+overlay rewrite-remap
+suffixmassage "dc=virtual,dc=naming,dc=context" \
+ "dc=real,dc=naming,dc=context"
+
+# old fashioned suffixalias, applied also to DN-valued attributes
+# from virtual to real naming context, but not the reverse...
+database relay
+suffix "dc=virtual,dc=naming,dc=context"
+relay "dc=real,dc=naming,dc=context"
+rewriteContext default
+rewriteRule "(.*)dc=virtual,dc=naming,dc=context$" \
+ "$1dc=real,dc=naming,dc=context"
+rewriteContext searchFilter
+rewriteContext searchResult
+rewriteContext searchResultAttrDN
+rewriteContext matchedDN
+
diff --git a/servers/slapd/back-relay/back-relay.h b/servers/slapd/back-relay/back-relay.h
new file mode 100644
index 0000000..e5c0e17
--- /dev/null
+++ b/servers/slapd/back-relay/back-relay.h
@@ -0,0 +1,49 @@
+/* back-relay.h - relay backend header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef SLAPD_RELAY_H
+#define SLAPD_RELAY_H
+
+#include "proto-back-relay.h"
+
+LDAP_BEGIN_DECL
+
+typedef enum relay_operation_e {
+ relay_op_entry_get = op_last,
+ relay_op_entry_release,
+ relay_op_has_subordinates,
+ relay_op_last
+} relay_operation_t;
+
+typedef struct relay_back_info {
+ BackendDB *ri_bd;
+ struct berval ri_realsuffix;
+ int ri_massage;
+} relay_back_info;
+
+/* Pad relay_back_info if needed to create valid OpExtra key addresses */
+#define RELAY_INFO_SIZE \
+ (sizeof(relay_back_info) > (size_t) relay_op_last ? \
+ sizeof(relay_back_info) : (size_t) relay_op_last )
+
+LDAP_END_DECL
+
+#endif /* SLAPD_RELAY_H */
diff --git a/servers/slapd/back-relay/init.c b/servers/slapd/back-relay/init.c
new file mode 100644
index 0000000..efad958
--- /dev/null
+++ b/servers/slapd/back-relay/init.c
@@ -0,0 +1,255 @@
+/* init.c - initialize relay backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "back-relay.h"
+
+static ConfigDriver relay_back_cf;
+
+static ConfigTable relaycfg[] = {
+ { "relay", "relay", 2, 2, 0,
+ ARG_MAGIC|ARG_DN|ARG_QUOTE,
+ relay_back_cf, "( OLcfgDbAt:5.1 "
+ "NAME 'olcRelay' "
+ "DESC 'Relay DN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs relayocs[] = {
+ { "( OLcfgDbOc:5.1 "
+ "NAME 'olcRelayConfig' "
+ "DESC 'Relay backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcRelay "
+ ") )",
+ Cft_Database, relaycfg},
+ { NULL, 0, NULL }
+};
+
+static int
+relay_back_cf( ConfigArgs *c )
+{
+ relay_back_info *ri = ( relay_back_info * )c->be->be_private;
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( ri != NULL && !BER_BVISNULL( &ri->ri_realsuffix ) ) {
+ value_add_one( &c->rvalue_vals, &ri->ri_realsuffix );
+ return 0;
+ }
+ return 1;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) {
+ ch_free( ri->ri_realsuffix.bv_val );
+ BER_BVZERO( &ri->ri_realsuffix );
+ ri->ri_bd = NULL;
+ return 0;
+ }
+ return 1;
+
+ } else {
+ BackendDB *bd;
+
+ assert( ri != NULL );
+ assert( BER_BVISNULL( &ri->ri_realsuffix ) );
+
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "\"relay\" directive "
+ "must appear after \"suffix\"" );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ goto relay_done;
+ }
+
+ if ( !BER_BVISNULL( &c->be->be_nsuffix[ 1 ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "relaying of multiple suffix "
+ "database not supported" );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ goto relay_done;
+ }
+
+ bd = select_backend( &c->value_ndn, 1 );
+ if ( bd == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "cannot find database "
+ "of relay dn \"%s\" "
+ "in \"olcRelay <dn>\"\n",
+ c->value_dn.bv_val );
+ Log( LDAP_DEBUG_CONFIG, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+
+ } else if ( bd->be_private == c->be->be_private ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "relay dn \"%s\" would call self "
+ "in \"relay <dn>\" line\n",
+ c->value_dn.bv_val );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ goto relay_done;
+ }
+
+ ri->ri_realsuffix = c->value_ndn;
+ BER_BVZERO( &c->value_ndn );
+
+relay_done:;
+ ch_free( c->value_dn.bv_val );
+ ch_free( c->value_ndn.bv_val );
+ }
+
+ return rc;
+}
+
+int
+relay_back_initialize( BackendInfo *bi )
+{
+ bi->bi_init = 0;
+ bi->bi_open = 0;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = relay_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = relay_back_db_open;
+#if 0
+ bi->bi_db_close = relay_back_db_close;
+#endif
+ bi->bi_db_destroy = relay_back_db_destroy;
+
+ bi->bi_op_bind = relay_back_op_bind;
+ bi->bi_op_search = relay_back_op_search;
+ bi->bi_op_compare = relay_back_op_compare;
+ bi->bi_op_modify = relay_back_op_modify;
+ bi->bi_op_modrdn = relay_back_op_modrdn;
+ bi->bi_op_add = relay_back_op_add;
+ bi->bi_op_delete = relay_back_op_delete;
+ bi->bi_extended = relay_back_op_extended;
+ bi->bi_entry_release_rw = relay_back_entry_release_rw;
+ bi->bi_entry_get_rw = relay_back_entry_get_rw;
+ bi->bi_operational = relay_back_operational;
+ bi->bi_has_subordinates = relay_back_has_subordinates;
+
+ bi->bi_cf_ocs = relayocs;
+
+ return config_register_schema( relaycfg, relayocs );
+}
+
+int
+relay_back_db_init( Backend *be, ConfigReply *cr)
+{
+ relay_back_info *ri;
+
+ be->be_private = NULL;
+
+ ri = (relay_back_info *) ch_calloc( 1, RELAY_INFO_SIZE );
+ if ( ri == NULL ) {
+ return -1;
+ }
+
+ ri->ri_bd = NULL;
+ BER_BVZERO( &ri->ri_realsuffix );
+ ri->ri_massage = 0;
+
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ be->be_private = (void *)ri;
+
+ return 0;
+}
+
+int
+relay_back_db_open( Backend *be, ConfigReply *cr )
+{
+ relay_back_info *ri = (relay_back_info *)be->be_private;
+
+ assert( ri != NULL );
+
+ if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) {
+ ri->ri_bd = select_backend( &ri->ri_realsuffix, 1 );
+
+ /* must be there: it was during config! */
+ if ( ri->ri_bd == NULL ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "cannot find database "
+ "of relay dn \"%s\" "
+ "in \"olcRelay <dn>\"\n",
+ ri->ri_realsuffix.bv_val );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "relay_back_db_open: %s.\n", cr->msg );
+
+ return 1;
+ }
+
+ /* inherit controls */
+ AC_MEMCPY( be->bd_self->be_ctrls, ri->ri_bd->be_ctrls, sizeof( be->be_ctrls ) );
+
+ } else {
+ /* inherit all? */
+ AC_MEMCPY( be->bd_self->be_ctrls, frontendDB->be_ctrls, sizeof( be->be_ctrls ) );
+ }
+
+ return 0;
+}
+
+int
+relay_back_db_close( Backend *be, ConfigReply *cr )
+{
+ return 0;
+}
+
+int
+relay_back_db_destroy( Backend *be, ConfigReply *cr)
+{
+ relay_back_info *ri = (relay_back_info *)be->be_private;
+
+ if ( ri ) {
+ if ( !BER_BVISNULL( &ri->ri_realsuffix ) ) {
+ ch_free( ri->ri_realsuffix.bv_val );
+ }
+ ch_free( ri );
+ }
+
+ return 0;
+}
+
+#if SLAPD_RELAY == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( relay )
+
+#endif /* SLAPD_RELAY == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-relay/op.c b/servers/slapd/back-relay/op.c
new file mode 100644
index 0000000..40e9e88
--- /dev/null
+++ b/servers/slapd/back-relay/op.c
@@ -0,0 +1,333 @@
+/* op.c - relay backend operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "slap.h"
+#include "back-relay.h"
+
+/* Results when no real database (.rf_bd) or operation handler (.rf_op) */
+static const struct relay_fail_modes_s {
+ slap_mask_t rf_bd, rf_op;
+#define RB_ERR_MASK 0x0000FFFFU /* bitmask for default return value */
+#define RB_BDERR 0x80000000U /* use .rf_bd's default return value */
+#define RB_OPERR 0x40000000U /* set rs->sr_err = .rf_op return value */
+#define RB_REF 0x20000000U /* use default_referral if available */
+#define RB_SEND 0x10000000U /* send result; RB_??ERR is also set */
+#define RB_SENDREF 0/*unused*/ /* like RB_SEND when referral found */
+#define RB_NO_BIND (RB_OPERR | LDAP_INVALID_CREDENTIALS)
+#define RB_NOT_SUPP (RB_OPERR | LDAP_UNWILLING_TO_PERFORM)
+#define RB_NO_OBJ (RB_REF | LDAP_NO_SUCH_OBJECT)
+#define RB_CHK_REF (RB_REF | RB_SENDREF | LDAP_SUCCESS)
+} relay_fail_modes[relay_op_last] = {
+ /* .rf_bd is unused when zero, otherwise both fields have RB_BDERR */
+# define RB_OP(b, o) { (b) | RB_BD2ERR(b), (o) | RB_BD2ERR(b) }
+# define RB_BD2ERR(b) ((b) ? RB_BDERR : 0)
+ /* indexed by slap_operation_t: */
+ RB_OP(RB_NO_BIND|RB_SEND, RB_NO_BIND |RB_SEND), /* Bind */
+ RB_OP(0, LDAP_SUCCESS), /* Unbind: unused */
+ RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Search */
+ RB_OP(RB_NO_OBJ |RB_SEND, SLAP_CB_CONTINUE), /* Compare */
+ RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Modify */
+ RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Modrdn */
+ RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Add */
+ RB_OP(RB_NO_OBJ |RB_SEND, RB_NOT_SUPP |RB_SEND), /* Delete */
+ RB_OP(0, LDAP_SUCCESS), /* Abandon:unused */
+ RB_OP(RB_NO_OBJ, RB_NOT_SUPP), /* Extended */
+ RB_OP(0, SLAP_CB_CONTINUE), /* Cancel: unused */
+ RB_OP(0, LDAP_SUCCESS), /* operational */
+ RB_OP(RB_CHK_REF, LDAP_SUCCESS), /* chk_referrals:unused*/
+ RB_OP(0, SLAP_CB_CONTINUE),/* chk_controls:unused */
+ /* additional relay_operation_t indexes from back-relay.h: */
+ RB_OP(0, 0/*unused*/), /* entry_get = op_last */
+ RB_OP(0, 0/*unused*/), /* entry_release */
+ RB_OP(0, 0/*unused*/), /* has_subordinates */
+};
+
+/*
+ * Callbacks: Caller changed op->o_bd from Relay to underlying
+ * BackendDB. sc_response sets it to Relay BackendDB, sc_cleanup puts
+ * back underlying BackendDB. Caller will restore Relay BackendDB.
+ */
+
+typedef struct relay_callback {
+ slap_callback rcb_sc;
+ BackendDB *rcb_bd;
+} relay_callback;
+
+static int
+relay_back_cleanup_cb( Operation *op, SlapReply *rs )
+{
+ op->o_bd = ((relay_callback *) op->o_callback)->rcb_bd;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+relay_back_response_cb( Operation *op, SlapReply *rs )
+{
+ relay_callback *rcb = (relay_callback *) op->o_callback;
+
+ rcb->rcb_sc.sc_cleanup = relay_back_cleanup_cb;
+ rcb->rcb_bd = op->o_bd;
+ op->o_bd = op->o_callback->sc_private;
+ return SLAP_CB_CONTINUE;
+}
+
+#define relay_back_add_cb( rcb, op ) { \
+ (rcb)->rcb_sc.sc_next = (op)->o_callback; \
+ (rcb)->rcb_sc.sc_response = relay_back_response_cb; \
+ (rcb)->rcb_sc.sc_cleanup = 0; \
+ (rcb)->rcb_sc.sc_writewait = 0; \
+ (rcb)->rcb_sc.sc_private = (op)->o_bd; \
+ (op)->o_callback = (slap_callback *) (rcb); \
+}
+
+#define relay_back_remove_cb( rcb, op ) { \
+ slap_callback **sc = &(op)->o_callback; \
+ for ( ;; sc = &(*sc)->sc_next ) \
+ if ( *sc == (slap_callback *) (rcb) ) { \
+ *sc = (*sc)->sc_next; break; \
+ } else if ( *sc == NULL ) break; \
+}
+
+/*
+ * Select the backend database with the operation's DN. On failure,
+ * set/send results depending on operation type <which>'s fail_modes.
+ */
+static BackendDB *
+relay_back_select_backend( Operation *op, SlapReply *rs, int which )
+{
+ OpExtra *oex;
+ char *key = (char *) op->o_bd->be_private;
+ BackendDB *bd = ((relay_back_info *) key)->ri_bd;
+ slap_mask_t fail_mode = relay_fail_modes[which].rf_bd;
+ int useDN = 0, rc = ( fail_mode & RB_ERR_MASK );
+
+ if ( bd == NULL && !BER_BVISNULL( &op->o_req_ndn ) ) {
+ useDN = 1;
+ bd = select_backend( &op->o_req_ndn, 1 );
+ }
+
+ if ( bd != NULL ) {
+ key += which; /* <relay, op type> key from RELAY_WRAP_OP() */
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == key )
+ break;
+ }
+ if ( oex == NULL ) {
+ return bd;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s: back-relay for DN=\"%s\" would call self.\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ } else if ( useDN && ( fail_mode & RB_REF ) && default_referral ) {
+ rc = LDAP_REFERRAL;
+
+ /* if we set sr_err to LDAP_REFERRAL, we must provide one */
+ rs->sr_ref = referral_rewrite(
+ default_referral, NULL, &op->o_req_dn,
+ op->o_tag == LDAP_REQ_SEARCH ?
+ op->ors_scope : LDAP_SCOPE_DEFAULT );
+ if ( rs->sr_ref != NULL ) {
+ rs->sr_flags |= REP_REF_MUSTBEFREED;
+ } else {
+ rs->sr_ref = default_referral;
+ }
+
+ if ( fail_mode & RB_SENDREF )
+ fail_mode = (RB_BDERR | RB_SEND);
+ }
+
+ if ( fail_mode & RB_BDERR ) {
+ rs->sr_err = rc;
+ if ( fail_mode & RB_SEND ) {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Forward <act> on <op> to database <bd>, with <relay, op type>-specific
+ * key in op->o_extra so relay_back_select_backend() can catch recursion.
+ */
+#define RELAY_WRAP_OP( op, bd, which, act ) { \
+ OpExtraDB wrap_oex; \
+ BackendDB *const wrap_bd = (op)->o_bd; \
+ wrap_oex.oe_db = wrap_bd; \
+ wrap_oex.oe.oe_key = (char *) wrap_bd->be_private + (which); \
+ LDAP_SLIST_INSERT_HEAD( &(op)->o_extra, &wrap_oex.oe, oe_next ); \
+ (op)->o_bd = (bd); \
+ act; \
+ (op)->o_bd = wrap_bd; \
+ LDAP_SLIST_REMOVE( &(op)->o_extra, &wrap_oex.oe, OpExtra, oe_next ); \
+}
+
+/*
+ * Forward backend function #<which> on <op> to operation DN's database
+ * like RELAY_WRAP_OP, after setting up callbacks. If no database or no
+ * backend function, set/send results depending on <which>'s fail_modes.
+ */
+static int
+relay_back_op( Operation *op, SlapReply *rs, int which )
+{
+ BackendDB *bd;
+ BackendInfo *bi;
+ slap_mask_t fail_mode = relay_fail_modes[which].rf_op;
+ int rc = ( fail_mode & RB_ERR_MASK );
+
+ bd = relay_back_select_backend( op, rs, which );
+ if ( bd == NULL ) {
+ if ( fail_mode & RB_BDERR )
+ return rs->sr_err; /* sr_err was set above */
+
+ } else if ( (&( bi = bd->bd_info )->bi_op_bind)[which] ) {
+ relay_callback rcb;
+
+ relay_back_add_cb( &rcb, op );
+ RELAY_WRAP_OP( op, bd, which, {
+ rc = (&bi->bi_op_bind)[which]( op, rs );
+ });
+ relay_back_remove_cb( &rcb, op );
+ if ( which == op_bind && rc == LDAP_SUCCESS )
+ op->o_bd = bd;
+
+ } else if ( fail_mode & RB_OPERR ) {
+ rs->sr_err = rc;
+ if ( rc == LDAP_UNWILLING_TO_PERFORM ) {
+ rs->sr_text = "operation not supported within naming context";
+ }
+
+ if ( fail_mode & RB_SEND ) {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return rc;
+}
+
+
+int
+relay_back_op_bind( Operation *op, SlapReply *rs )
+{
+ /* allow rootdn as a means to auth without the need to actually
+ * contact the proxied DSA */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ return rs->sr_err;
+ }
+
+ return relay_back_op( op, rs, op_bind );
+}
+
+#define RELAY_DEFOP(func, which) \
+ int func( Operation *op, SlapReply *rs ) \
+ { return relay_back_op( op, rs, which ); }
+
+RELAY_DEFOP( relay_back_op_search, op_search )
+RELAY_DEFOP( relay_back_op_compare, op_compare )
+RELAY_DEFOP( relay_back_op_modify, op_modify )
+RELAY_DEFOP( relay_back_op_modrdn, op_modrdn )
+RELAY_DEFOP( relay_back_op_add, op_add )
+RELAY_DEFOP( relay_back_op_delete, op_delete )
+RELAY_DEFOP( relay_back_op_extended, op_extended )
+RELAY_DEFOP( relay_back_operational, op_aux_operational )
+
+/* Abandon, Cancel, Unbind and some DN-less calls like be_connection_init
+ * need no extra handling: slapd already calls them for all databases.
+ */
+
+
+int
+relay_back_entry_release_rw( Operation *op, Entry *e, int rw )
+{
+ BackendDB *bd;
+ int rc = LDAP_UNWILLING_TO_PERFORM;
+
+ bd = relay_back_select_backend( op, NULL, relay_op_entry_release );
+ if ( bd && bd->be_release ) {
+ RELAY_WRAP_OP( op, bd, relay_op_entry_release, {
+ rc = bd->be_release( op, e, rw );
+ });
+ } else if ( e->e_private == NULL ) {
+ entry_free( e );
+ rc = LDAP_SUCCESS;
+ }
+
+ return rc;
+}
+
+int
+relay_back_entry_get_rw( Operation *op, struct berval *ndn,
+ ObjectClass *oc, AttributeDescription *at, int rw, Entry **e )
+{
+ BackendDB *bd;
+ int rc = LDAP_NO_SUCH_OBJECT;
+
+ bd = relay_back_select_backend( op, NULL, relay_op_entry_get );
+ if ( bd && bd->be_fetch ) {
+ RELAY_WRAP_OP( op, bd, relay_op_entry_get, {
+ rc = bd->be_fetch( op, ndn, oc, at, rw, e );
+ });
+ }
+
+ return rc;
+}
+
+#if 0 /* Give the RB_SENDREF flag a nonzero value if implementing this */
+/*
+ * NOTE: even the existence of this function is questionable: we cannot
+ * pass the bi_chk_referrals() call thru the rwm overlay because there
+ * is no way to rewrite the req_dn back; but then relay_back_chk_referrals()
+ * is passing the target database a DN that likely does not belong to its
+ * naming context... mmmh.
+ */
+RELAY_DEFOP( relay_back_chk_referrals, op_aux_chk_referrals )
+#endif /*0*/
+
+int
+relay_back_has_subordinates( Operation *op, Entry *e, int *hasSubs )
+{
+ BackendDB *bd;
+ int rc = LDAP_OTHER;
+
+ bd = relay_back_select_backend( op, NULL, relay_op_has_subordinates );
+ if ( bd && bd->be_has_subordinates ) {
+ RELAY_WRAP_OP( op, bd, relay_op_has_subordinates, {
+ rc = bd->be_has_subordinates( op, e, hasSubs );
+ });
+ }
+
+ return rc;
+}
+
+
+/*
+ * FIXME: must implement tools as well
+ */
diff --git a/servers/slapd/back-relay/proto-back-relay.h b/servers/slapd/back-relay/proto-back-relay.h
new file mode 100644
index 0000000..854be4e
--- /dev/null
+++ b/servers/slapd/back-relay/proto-back-relay.h
@@ -0,0 +1,52 @@
+/* proto-back-relay.h - relay backend header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef PROTO_BACK_RELAY
+#define PROTO_BACK_RELAY
+
+#include <ldap_cdefs.h>
+
+LDAP_BEGIN_DECL
+
+extern BI_init relay_back_initialize;
+
+extern BI_db_init relay_back_db_init;
+extern BI_db_open relay_back_db_open;
+extern BI_db_close relay_back_db_close;
+extern BI_db_destroy relay_back_db_destroy;
+
+extern BI_op_bind relay_back_op_bind;
+extern BI_op_search relay_back_op_search;
+extern BI_op_compare relay_back_op_compare;
+extern BI_op_modify relay_back_op_modify;
+extern BI_op_modrdn relay_back_op_modrdn;
+extern BI_op_add relay_back_op_add;
+extern BI_op_delete relay_back_op_delete;
+extern BI_op_extended relay_back_op_extended;
+extern BI_entry_release_rw relay_back_entry_release_rw;
+extern BI_entry_get_rw relay_back_entry_get_rw;
+extern BI_operational relay_back_operational;
+extern BI_has_subordinates relay_back_has_subordinates;
+
+LDAP_END_DECL
+
+#endif /* PROTO_BACK_RELAY */
+
diff --git a/servers/slapd/back-sock/Makefile.in b/servers/slapd/back-sock/Makefile.in
new file mode 100644
index 0000000..1db07bf
--- /dev/null
+++ b/servers/slapd/back-sock/Makefile.in
@@ -0,0 +1,47 @@
+# Makefile.in for back-sock
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2007-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was initially developed by Brian Candler for inclusion
+## in OpenLDAP Software.
+
+SRCS = init.c config.c opensock.c search.c bind.c unbind.c add.c \
+ delete.c modify.c modrdn.c compare.c result.c extended.c
+OBJS = init.lo config.lo opensock.lo search.lo bind.lo unbind.lo add.lo \
+ delete.lo modify.lo modrdn.lo compare.lo result.lo extended.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-sock"
+BUILD_MOD = @BUILD_SOCK@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_SOCK@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_sock
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-sock/add.c b/servers/slapd/back-sock/add.c
new file mode 100644
index 0000000..0e96d95
--- /dev/null
+++ b/servers/slapd/back-sock/add.c
@@ -0,0 +1,69 @@
+/* add.c - sock backend add function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_add(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ FILE *fp;
+ int len;
+
+ if ( ! access_allowed( op, op->oq_add.rs_e,
+ entry, NULL, ACL_WADD, NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the add process */
+ fprintf( fp, "ADD\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ ldap_pvt_thread_mutex_lock( &entry2str_mutex );
+ fprintf( fp, "%s", entry2str( op->oq_add.rs_e, &len ) );
+ ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
+ fprintf (fp, "\n" );
+
+ /* read in the result and send it along */
+ sock_read_and_send_results( op, rs, fp );
+
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/back-sock.h b/servers/slapd/back-sock/back-sock.h
new file mode 100644
index 0000000..55dbe75
--- /dev/null
+++ b/servers/slapd/back-sock/back-sock.h
@@ -0,0 +1,61 @@
+/* sock.h - socket backend header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef SLAPD_SOCK_H
+#define SLAPD_SOCK_H
+
+#include "proto-sock.h"
+
+LDAP_BEGIN_DECL
+
+struct sockinfo {
+ const char *si_sockpath;
+ slap_mask_t si_extensions;
+ slap_mask_t si_ops; /* overlay: operations to act on */
+ slap_mask_t si_resps; /* overlay: responses to forward */
+ regex_t si_dnpat; /* overlay: DN pattern to match */
+ struct berval si_dnpatstr;
+};
+
+#define SOCK_EXT_BINDDN 1
+#define SOCK_EXT_PEERNAME 2
+#define SOCK_EXT_SSF 4
+#define SOCK_EXT_CONNID 8
+
+extern FILE *opensock LDAP_P((
+ const char *sockpath));
+
+extern void sock_print_suffixes LDAP_P((
+ FILE *fp,
+ BackendDB *bd));
+
+extern void sock_print_conn LDAP_P((
+ FILE *fp,
+ Connection *conn,
+ struct sockinfo *si));
+
+extern int sock_read_and_send_results LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ FILE *fp));
+
+LDAP_END_DECL
+
+#endif
diff --git a/servers/slapd/back-sock/bind.c b/servers/slapd/back-sock/bind.c
new file mode 100644
index 0000000..8e8ec97
--- /dev/null
+++ b/servers/slapd/back-sock/bind.c
@@ -0,0 +1,80 @@
+/* bind.c - sock backend bind function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ Entry e;
+ FILE *fp;
+ int rc;
+
+ e.e_id = NOID;
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ e.e_attrs = NULL;
+ e.e_ocflags = 0;
+ e.e_bv.bv_len = 0;
+ e.e_bv.bv_val = NULL;
+ e.e_private = NULL;
+
+ if ( ! access_allowed( op, &e,
+ entry, NULL, ACL_AUTH, NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the bind process */
+ fprintf( fp, "BIND\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val );
+ fprintf( fp, "method: %d\n", op->oq_bind.rb_method );
+ fprintf( fp, "credlen: %lu\n", op->oq_bind.rb_cred.bv_len );
+ fprintf( fp, "cred: %s\n", op->oq_bind.rb_cred.bv_val ); /* XXX */
+ fprintf( fp, "\n" );
+
+ /* read in the results and send them along */
+ rc = sock_read_and_send_results( op, rs, fp );
+ fclose( fp );
+
+ return( rc );
+}
diff --git a/servers/slapd/back-sock/compare.c b/servers/slapd/back-sock/compare.c
new file mode 100644
index 0000000..735e688
--- /dev/null
+++ b/servers/slapd/back-sock/compare.c
@@ -0,0 +1,88 @@
+/* compare.c - sock backend compare function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-sock.h"
+#include "ldif.h"
+
+int
+sock_back_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ Entry e;
+ FILE *fp;
+ char *text;
+
+ e.e_id = NOID;
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ e.e_attrs = NULL;
+ e.e_ocflags = 0;
+ e.e_bv.bv_len = 0;
+ e.e_bv.bv_val = NULL;
+ e.e_private = NULL;
+
+ if ( ! access_allowed( op, &e,
+ entry, NULL, ACL_COMPARE, NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the compare process */
+ fprintf( fp, "COMPARE\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val );
+ /* could be binary */
+ text = ldif_put_wrap( LDIF_PUT_VALUE,
+ op->orc_ava->aa_desc->ad_cname.bv_val,
+ op->orc_ava->aa_value.bv_val,
+ op->orc_ava->aa_value.bv_len, LDIF_LINE_WIDTH_MAX );
+ if ( text ) {
+ fprintf( fp, "%s\n", text );
+ ber_memfree( text );
+ } else {
+ fprintf( fp, "\n\n" );
+ }
+
+ /* read in the result and send it along */
+ sock_read_and_send_results( op, rs, fp );
+
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/config.c b/servers/slapd/back-sock/config.c
new file mode 100644
index 0000000..e6d3183
--- /dev/null
+++ b/servers/slapd/back-sock/config.c
@@ -0,0 +1,452 @@
+/* config.c - sock backend configuration file routine */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software. Dynamic config support by Howard Chu.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "back-sock.h"
+
+static ConfigDriver bs_cf_gen;
+static int sock_over_setup();
+static slap_response sock_over_response;
+
+enum {
+ BS_EXT = 1,
+ BS_OPS,
+ BS_RESP,
+ BS_DNPAT
+};
+
+/* The number of overlay-only config attrs */
+#define NUM_OV_ATTRS 3
+
+static ConfigTable bscfg[] = {
+ { "sockops", "ops", 2, 0, 0, ARG_MAGIC|BS_OPS,
+ bs_cf_gen, "( OLcfgDbAt:7.3 NAME 'olcOvSocketOps' "
+ "DESC 'Operation types to forward' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "sockresps", "resps", 2, 0, 0, ARG_MAGIC|BS_RESP,
+ bs_cf_gen, "( OLcfgDbAt:7.4 NAME 'olcOvSocketResps' "
+ "DESC 'Response types to forward' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "sockdnpat", "regexp", 2, 2, 0, ARG_MAGIC|BS_DNPAT,
+ bs_cf_gen, "( OLcfgDbAt:7.5 NAME 'olcOvSocketDNpat' "
+ "DESC 'DN pattern to match' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+
+ { "socketpath", "pathname", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct sockinfo, si_sockpath),
+ "( OLcfgDbAt:7.1 NAME 'olcDbSocketPath' "
+ "DESC 'Pathname for Unix domain socket' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "extensions", "ext", 2, 0, 0, ARG_MAGIC|BS_EXT,
+ bs_cf_gen, "( OLcfgDbAt:7.2 NAME 'olcDbSocketExtensions' "
+ "DESC 'binddn, peername, or ssf' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL }
+};
+
+static ConfigOCs bsocs[] = {
+ { "( OLcfgDbOc:7.1 "
+ "NAME 'olcDbSocketConfig' "
+ "DESC 'Socket backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST olcDbSocketPath "
+ "MAY olcDbSocketExtensions )",
+ Cft_Database, bscfg+NUM_OV_ATTRS },
+ { NULL, 0, NULL }
+};
+
+static ConfigOCs osocs[] = {
+ { "( OLcfgDbOc:7.2 "
+ "NAME 'olcOvSocketConfig' "
+ "DESC 'Socket overlay configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcDbSocketPath "
+ "MAY ( olcDbSocketExtensions $ "
+ " olcOvSocketOps $ olcOvSocketResps $ "
+ " olcOvSocketDNpat ) )",
+ Cft_Overlay, bscfg },
+ { NULL, 0, NULL }
+};
+
+#define SOCK_OP_BIND 0x001
+#define SOCK_OP_UNBIND 0x002
+#define SOCK_OP_SEARCH 0x004
+#define SOCK_OP_COMPARE 0x008
+#define SOCK_OP_MODIFY 0x010
+#define SOCK_OP_MODRDN 0x020
+#define SOCK_OP_ADD 0x040
+#define SOCK_OP_DELETE 0x080
+#define SOCK_OP_EXTENDED 0x100
+
+#define SOCK_REP_RESULT 0x001
+#define SOCK_REP_SEARCH 0x002
+
+static slap_verbmasks bs_exts[] = {
+ { BER_BVC("binddn"), SOCK_EXT_BINDDN },
+ { BER_BVC("peername"), SOCK_EXT_PEERNAME },
+ { BER_BVC("ssf"), SOCK_EXT_SSF },
+ { BER_BVC("connid"), SOCK_EXT_CONNID },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks ov_ops[] = {
+ { BER_BVC("bind"), SOCK_OP_BIND },
+ { BER_BVC("unbind"), SOCK_OP_UNBIND },
+ { BER_BVC("search"), SOCK_OP_SEARCH },
+ { BER_BVC("compare"), SOCK_OP_COMPARE },
+ { BER_BVC("modify"), SOCK_OP_MODIFY },
+ { BER_BVC("modrdn"), SOCK_OP_MODRDN },
+ { BER_BVC("add"), SOCK_OP_ADD },
+ { BER_BVC("delete"), SOCK_OP_DELETE },
+ { BER_BVC("extended"), SOCK_OP_EXTENDED },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks ov_resps[] = {
+ { BER_BVC("result"), SOCK_REP_RESULT },
+ { BER_BVC("search"), SOCK_REP_SEARCH },
+ { BER_BVNULL, 0 }
+};
+
+static int
+bs_cf_gen( ConfigArgs *c )
+{
+ struct sockinfo *si;
+ int rc;
+
+ if ( c->be && c->table == Cft_Database )
+ si = c->be->be_private;
+ else if ( c->bi )
+ si = c->bi->bi_private;
+ else
+ return ARG_BAD_CONF;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case BS_EXT:
+ return mask_to_verbs( bs_exts, si->si_extensions, &c->rvalue_vals );
+ case BS_OPS:
+ return mask_to_verbs( ov_ops, si->si_ops, &c->rvalue_vals );
+ case BS_RESP:
+ return mask_to_verbs( ov_resps, si->si_resps, &c->rvalue_vals );
+ case BS_DNPAT:
+ if ( BER_BVISEMPTY( &si->si_dnpatstr ) )
+ return 1;
+ value_add_one( &c->rvalue_vals, &si->si_dnpatstr );
+ return 0;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case BS_EXT:
+ if ( c->valx < 0 ) {
+ si->si_extensions = 0;
+ rc = 0;
+ } else {
+ slap_mask_t dels = 0;
+ rc = verbstring_to_mask( bs_exts, c->line, ' ', &dels );
+ if ( rc == 0 )
+ si->si_extensions &= ~dels;
+ }
+ return rc;
+ case BS_OPS:
+ if ( c->valx < 0 ) {
+ si->si_ops = 0;
+ rc = 0;
+ } else {
+ slap_mask_t dels = 0;
+ rc = verbstring_to_mask( ov_ops, c->line, ' ', &dels );
+ if ( rc == 0 )
+ si->si_ops &= ~dels;
+ }
+ return rc;
+ case BS_RESP:
+ if ( c->valx < 0 ) {
+ si->si_resps = 0;
+ rc = 0;
+ } else {
+ slap_mask_t dels = 0;
+ rc = verbstring_to_mask( ov_resps, c->line, ' ', &dels );
+ if ( rc == 0 )
+ si->si_resps &= ~dels;
+ }
+ return rc;
+ case BS_DNPAT:
+ regfree( &si->si_dnpat );
+ ch_free( si->si_dnpatstr.bv_val );
+ BER_BVZERO( &si->si_dnpatstr );
+ return 0;
+ }
+
+ } else {
+ switch( c->type ) {
+ case BS_EXT: {
+ slap_mask_t adds = 0;
+ if ( verbs_to_mask( c->argc, c->argv, bs_exts, &adds ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ /* Tolerate overlaps in slapd.conf */
+ if ( c->op != SLAP_CONFIG_ADD && adds & si->si_extensions ) {
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ si->si_extensions |= adds;
+ return 0;
+ }
+ case BS_OPS: {
+ slap_mask_t adds = 0;
+ if ( verbs_to_mask( c->argc, c->argv, ov_ops, &adds ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ /* Tolerate overlaps in slapd.conf */
+ if ( c->op != SLAP_CONFIG_ADD && adds & si->si_ops ) {
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ si->si_ops |= adds;
+ return 0;
+ }
+ case BS_RESP: {
+ slap_mask_t adds = 0;
+ if ( verbs_to_mask( c->argc, c->argv, ov_resps, &adds ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ /* Tolerate overlaps in slapd.conf */
+ if ( c->op != SLAP_CONFIG_ADD && adds & si->si_resps ) {
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ si->si_resps |= adds;
+ return 0;
+ }
+ case BS_DNPAT:
+ if ( !regcomp( &si->si_dnpat, c->argv[1], REG_EXTENDED|REG_ICASE|REG_NOSUB )) {
+ ber_str2bv( c->argv[1], 0, 1, &si->si_dnpatstr );
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ }
+ return 1;
+}
+
+int
+sock_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+ bi->bi_cf_ocs = bsocs;
+
+ rc = config_register_schema( bscfg, bsocs );
+ if ( !rc )
+ rc = sock_over_setup();
+ return rc;
+}
+
+/* sock overlay wrapper */
+static slap_overinst sockover;
+
+static int sock_over_db_init( Backend *be, struct config_reply_s *cr );
+static int sock_over_db_destroy( Backend *be, struct config_reply_s *cr );
+
+static BI_op_bind *sockfuncs[] = {
+ sock_back_bind,
+ sock_back_unbind,
+ sock_back_search,
+ sock_back_compare,
+ sock_back_modify,
+ sock_back_modrdn,
+ sock_back_add,
+ sock_back_delete,
+ 0, /* abandon not supported */
+ sock_back_extended
+};
+
+static const int sockopflags[] = {
+ SOCK_OP_BIND,
+ SOCK_OP_UNBIND,
+ SOCK_OP_SEARCH,
+ SOCK_OP_COMPARE,
+ SOCK_OP_MODIFY,
+ SOCK_OP_MODRDN,
+ SOCK_OP_ADD,
+ SOCK_OP_DELETE,
+ 0, /* abandon not supported */
+ SOCK_OP_EXTENDED
+};
+
+static int sock_over_op(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ void *private = op->o_bd->be_private;
+ slap_callback *sc;
+ struct sockinfo *si;
+ slap_operation_t which;
+
+ switch (op->o_tag) {
+ case LDAP_REQ_BIND: which = op_bind; break;
+ case LDAP_REQ_UNBIND: which = op_unbind; break;
+ case LDAP_REQ_SEARCH: which = op_search; break;
+ case LDAP_REQ_COMPARE: which = op_compare; break;
+ case LDAP_REQ_MODIFY: which = op_modify; break;
+ case LDAP_REQ_MODRDN: which = op_modrdn; break;
+ case LDAP_REQ_ADD: which = op_add; break;
+ case LDAP_REQ_DELETE: which = op_delete; break;
+ case LDAP_REQ_EXTENDED: which = op_extended; break;
+ default:
+ return SLAP_CB_CONTINUE;
+ }
+ si = on->on_bi.bi_private;
+ if ( !(si->si_ops & sockopflags[which]))
+ return SLAP_CB_CONTINUE;
+
+ if ( !BER_BVISEMPTY( &si->si_dnpatstr ) &&
+ regexec( &si->si_dnpat, op->o_req_ndn.bv_val, 0, NULL, 0 ))
+ return SLAP_CB_CONTINUE;
+
+ op->o_bd->be_private = si;
+ sc = op->o_callback;
+ op->o_callback = NULL;
+ sockfuncs[which]( op, rs );
+ op->o_bd->be_private = private;
+ op->o_callback = sc;
+ return rs->sr_err;
+}
+
+static int
+sock_over_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ struct sockinfo *si = (struct sockinfo *)on->on_bi.bi_private;
+ FILE *fp;
+
+ if ( rs->sr_type == REP_RESULT ) {
+ if ( !( si->si_resps & SOCK_REP_RESULT ))
+ return SLAP_CB_CONTINUE;
+ } else if ( rs->sr_type == REP_SEARCH ) {
+ if ( !( si->si_resps & SOCK_REP_SEARCH ))
+ return SLAP_CB_CONTINUE;
+ } else
+ return SLAP_CB_CONTINUE;
+
+ if (( fp = opensock( si->si_sockpath )) == NULL )
+ return SLAP_CB_CONTINUE;
+
+ if ( rs->sr_type == REP_RESULT ) {
+ /* write out the result */
+ fprintf( fp, "RESULT\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ fprintf( fp, "code: %d\n", rs->sr_err );
+ if ( rs->sr_matched )
+ fprintf( fp, "matched: %s\n", rs->sr_matched );
+ if (rs->sr_text )
+ fprintf( fp, "info: %s\n", rs->sr_text );
+ } else {
+ /* write out the search entry */
+ int len;
+ fprintf( fp, "ENTRY\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ ldap_pvt_thread_mutex_lock( &entry2str_mutex );
+ fprintf( fp, "%s", entry2str( rs->sr_entry, &len ) );
+ ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
+ }
+ fprintf( fp, "\n" );
+ fclose( fp );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+sock_over_setup()
+{
+ int rc;
+
+ sockover.on_bi.bi_type = "sock";
+ sockover.on_bi.bi_db_init = sock_over_db_init;
+ sockover.on_bi.bi_db_destroy = sock_over_db_destroy;
+
+ sockover.on_bi.bi_op_bind = sock_over_op;
+ sockover.on_bi.bi_op_unbind = sock_over_op;
+ sockover.on_bi.bi_op_search = sock_over_op;
+ sockover.on_bi.bi_op_compare = sock_over_op;
+ sockover.on_bi.bi_op_modify = sock_over_op;
+ sockover.on_bi.bi_op_modrdn = sock_over_op;
+ sockover.on_bi.bi_op_add = sock_over_op;
+ sockover.on_bi.bi_op_delete = sock_over_op;
+ sockover.on_bi.bi_extended = sock_over_op;
+ sockover.on_response = sock_over_response;
+
+ sockover.on_bi.bi_cf_ocs = osocs;
+
+ rc = config_register_schema( bscfg, osocs );
+ if ( rc ) return rc;
+
+ return overlay_register( &sockover );
+}
+
+static int
+sock_over_db_init(
+ Backend *be,
+ struct config_reply_s *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ void *private = be->be_private;
+ void *cf_ocs = be->be_cf_ocs;
+ int rc;
+
+ be->be_private = NULL;
+ rc = sock_back_db_init( be, cr );
+ on->on_bi.bi_private = be->be_private;
+ be->be_private = private;
+ be->be_cf_ocs = cf_ocs;
+ return rc;
+}
+
+static int
+sock_over_db_destroy(
+ Backend *be,
+ struct config_reply_s *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ void *private = be->be_private;
+ int rc;
+
+ be->be_private = on->on_bi.bi_private;
+ rc = sock_back_db_destroy( be, cr );
+ on->on_bi.bi_private = be->be_private;
+ be->be_private = private;
+ return rc;
+}
diff --git a/servers/slapd/back-sock/delete.c b/servers/slapd/back-sock/delete.c
new file mode 100644
index 0000000..89a268f
--- /dev/null
+++ b/servers/slapd/back-sock/delete.c
@@ -0,0 +1,75 @@
+/* delete.c - sock backend delete function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ Entry e;
+ FILE *fp;
+
+ e.e_id = NOID;
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ e.e_attrs = NULL;
+ e.e_ocflags = 0;
+ e.e_bv.bv_len = 0;
+ e.e_bv.bv_val = NULL;
+ e.e_private = NULL;
+
+ if ( ! access_allowed( op, &e,
+ entry, NULL, ACL_WDEL, NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the delete process */
+ fprintf( fp, "DELETE\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val );
+ fprintf( fp, "\n" );
+
+ /* read in the results and send them along */
+ sock_read_and_send_results( op, rs, fp );
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/extended.c b/servers/slapd/back-sock/extended.c
new file mode 100644
index 0000000..e065761
--- /dev/null
+++ b/servers/slapd/back-sock/extended.c
@@ -0,0 +1,76 @@
+/* extended.c - sock backend extended routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+#include "lutil.h"
+
+int
+sock_back_extended( Operation *op, SlapReply *rs )
+{
+ int rc;
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ FILE *fp;
+ struct berval b64;
+
+ Debug( LDAP_DEBUG_ARGS, "==> sock_back_extended(%s, %s)\n",
+ op->ore_reqoid.bv_val, op->o_req_dn.bv_val );
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the extended process */
+ fprintf( fp, "EXTENDED\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "oid: %s\n", op->ore_reqoid.bv_val );
+
+ if (op->ore_reqdata) {
+
+ b64.bv_len = LUTIL_BASE64_ENCODE_LEN( op->ore_reqdata->bv_len ) + 1;
+ b64.bv_val = op->o_tmpalloc( b64.bv_len + 1, op->o_tmpmemctx );
+
+ rc = lutil_b64_ntop(
+ (unsigned char *) op->ore_reqdata->bv_val, op->ore_reqdata->bv_len,
+ b64.bv_val, b64.bv_len );
+
+ b64.bv_len = rc;
+ assert( strlen(b64.bv_val) == b64.bv_len );
+
+ fprintf( fp, "value: %s\n", b64.bv_val );
+
+ op->o_tmpfree( b64.bv_val, op->o_tmpmemctx );
+
+ }
+
+ fprintf( fp, "\n" );
+
+ /* read in the results and send them along */
+ rc = sock_read_and_send_results( op, rs, fp );
+ fclose( fp );
+
+ return( rc );
+}
diff --git a/servers/slapd/back-sock/init.c b/servers/slapd/back-sock/init.c
new file mode 100644
index 0000000..02b7bc8
--- /dev/null
+++ b/servers/slapd/back-sock/init.c
@@ -0,0 +1,97 @@
+/* init.c - initialize sock backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_initialize(
+ BackendInfo *bi
+)
+{
+ bi->bi_open = 0;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = sock_back_db_init;
+ bi->bi_db_config = 0;
+ bi->bi_db_open = 0;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = sock_back_db_destroy;
+
+ bi->bi_op_bind = sock_back_bind;
+ bi->bi_op_unbind = sock_back_unbind;
+ bi->bi_op_search = sock_back_search;
+ bi->bi_op_compare = sock_back_compare;
+ bi->bi_op_modify = sock_back_modify;
+ bi->bi_op_modrdn = sock_back_modrdn;
+ bi->bi_op_add = sock_back_add;
+ bi->bi_op_delete = sock_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = sock_back_extended;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ return sock_back_init_cf( bi );
+}
+
+int
+sock_back_db_init(
+ Backend *be,
+ struct config_reply_s *cr
+)
+{
+ struct sockinfo *si;
+
+ si = (struct sockinfo *) ch_calloc( 1, sizeof(struct sockinfo) );
+
+ be->be_private = si;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return si == NULL;
+}
+
+int
+sock_back_db_destroy(
+ Backend *be,
+ struct config_reply_s *cr
+)
+{
+ free( be->be_private );
+ return 0;
+}
+
+#if SLAPD_SOCK == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( sock )
+
+#endif /* SLAPD_SOCK == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-sock/modify.c b/servers/slapd/back-sock/modify.c
new file mode 100644
index 0000000..a5ec012
--- /dev/null
+++ b/servers/slapd/back-sock/modify.c
@@ -0,0 +1,117 @@
+/* modify.c - sock backend modify function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "back-sock.h"
+#include "ldif.h"
+
+int
+sock_back_modify(
+ Operation *op,
+ SlapReply *rs )
+{
+ Modification *mod;
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ Modifications *ml = op->orm_modlist;
+ Entry e;
+ FILE *fp;
+ int i;
+
+ e.e_id = NOID;
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ e.e_attrs = NULL;
+ e.e_ocflags = 0;
+ e.e_bv.bv_len = 0;
+ e.e_bv.bv_val = NULL;
+ e.e_private = NULL;
+
+ if ( ! access_allowed( op, &e,
+ entry, NULL, ACL_WRITE, NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the modify process */
+ fprintf( fp, "MODIFY\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val );
+ for ( ; ml != NULL; ml = ml->sml_next ) {
+ mod = &ml->sml_mod;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ fprintf( fp, "add: %s\n", mod->sm_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_MOD_DELETE:
+ fprintf( fp, "delete: %s\n", mod->sm_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ fprintf( fp, "replace: %s\n", mod->sm_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ fprintf( fp, "increment: %s\n", mod->sm_desc->ad_cname.bv_val );
+ break;
+ }
+
+ if( mod->sm_values != NULL ) {
+ for ( i = 0; mod->sm_values[i].bv_val != NULL; i++ ) {
+ char *text = ldif_put_wrap( LDIF_PUT_VALUE,
+ mod->sm_desc->ad_cname.bv_val,
+ mod->sm_values[i].bv_val,
+ mod->sm_values[i].bv_len, LDIF_LINE_WIDTH_MAX );
+ if ( text ) {
+ fprintf( fp, "%s", text );
+ ber_memfree( text );
+ } else {
+ break;
+ }
+ }
+ }
+
+ fprintf( fp, "-\n" );
+ }
+ fprintf( fp, "\n" );
+
+ /* read in the results and send them along */
+ sock_read_and_send_results( op, rs, fp );
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/modrdn.c b/servers/slapd/back-sock/modrdn.c
new file mode 100644
index 0000000..c5a9195
--- /dev/null
+++ b/servers/slapd/back-sock/modrdn.c
@@ -0,0 +1,81 @@
+/* modrdn.c - sock backend modrdn function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_modrdn(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ Entry e;
+ FILE *fp;
+
+ e.e_id = NOID;
+ e.e_name = op->o_req_dn;
+ e.e_nname = op->o_req_ndn;
+ e.e_attrs = NULL;
+ e.e_ocflags = 0;
+ e.e_bv.bv_len = 0;
+ e.e_bv.bv_val = NULL;
+ e.e_private = NULL;
+
+ if ( ! access_allowed( op, &e, entry, NULL,
+ op->oq_modrdn.rs_newSup ? ACL_WDEL : ACL_WRITE,
+ NULL ) )
+ {
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, NULL );
+ return -1;
+ }
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the modrdn process */
+ fprintf( fp, "MODRDN\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "dn: %s\n", op->o_req_dn.bv_val );
+ fprintf( fp, "newrdn: %s\n", op->oq_modrdn.rs_newrdn.bv_val );
+ fprintf( fp, "deleteoldrdn: %d\n", op->oq_modrdn.rs_deleteoldrdn ? 1 : 0 );
+ if ( op->oq_modrdn.rs_newSup != NULL ) {
+ fprintf( fp, "newSuperior: %s\n", op->oq_modrdn.rs_newSup->bv_val );
+ }
+ fprintf( fp, "\n" );
+
+ /* read in the results and send them along */
+ sock_read_and_send_results( op, rs, fp );
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/opensock.c b/servers/slapd/back-sock/opensock.c
new file mode 100644
index 0000000..9b4826d
--- /dev/null
+++ b/servers/slapd/back-sock/opensock.c
@@ -0,0 +1,71 @@
+/* opensock.c - open a unix domain socket */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+/*
+ * FIXME: count the number of concurrent open sockets (since each thread
+ * may open one). Perhaps block here if a soft limit is reached, and fail
+ * if a hard limit reached
+ */
+
+FILE *
+opensock(
+ const char *sockpath
+)
+{
+ int fd;
+ FILE *fp;
+ struct sockaddr_un sockun;
+
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if ( fd < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "socket create failed\n" );
+ return( NULL );
+ }
+
+ sockun.sun_family = AF_UNIX;
+ sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+ sockpath);
+ if ( connect( fd, (struct sockaddr *)&sockun, sizeof(sockun) ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "socket connect(%s) failed\n",
+ sockpath ? sockpath : "<null>" );
+ close( fd );
+ return( NULL );
+ }
+
+ if ( ( fp = fdopen( fd, "r+" ) ) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "fdopen failed\n" );
+ close( fd );
+ return( NULL );
+ }
+
+ return( fp );
+}
diff --git a/servers/slapd/back-sock/proto-sock.h b/servers/slapd/back-sock/proto-sock.h
new file mode 100644
index 0000000..0808329
--- /dev/null
+++ b/servers/slapd/back-sock/proto-sock.h
@@ -0,0 +1,49 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef _PROTO_SOCK_H
+#define _PROTO_SOCK_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init sock_back_initialize;
+
+extern BI_open sock_back_open;
+extern BI_close sock_back_close;
+extern BI_destroy sock_back_destroy;
+
+extern BI_db_init sock_back_db_init;
+extern BI_db_destroy sock_back_db_destroy;
+
+extern BI_op_bind sock_back_bind;
+extern BI_op_unbind sock_back_unbind;
+extern BI_op_search sock_back_search;
+extern BI_op_compare sock_back_compare;
+extern BI_op_modify sock_back_modify;
+extern BI_op_modrdn sock_back_modrdn;
+extern BI_op_add sock_back_add;
+extern BI_op_delete sock_back_delete;
+
+extern BI_op_extended sock_back_extended;
+
+extern int sock_back_init_cf( BackendInfo *bi );
+
+LDAP_END_DECL
+
+#endif /* _PROTO_SOCK_H */
diff --git a/servers/slapd/back-sock/result.c b/servers/slapd/back-sock/result.c
new file mode 100644
index 0000000..55a4060
--- /dev/null
+++ b/servers/slapd/back-sock/result.c
@@ -0,0 +1,168 @@
+/* result.c - sock backend result reading function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+/*
+ * FIXME: make a RESULT section compulsory from the socket response.
+ * Otherwise, a partial/aborted response is treated as 'success'.
+ * This is a divergence from the back-shell protocol, but makes things
+ * more robust.
+ */
+
+int
+sock_read_and_send_results(
+ Operation *op,
+ SlapReply *rs,
+ FILE *fp )
+{
+ int bsize, len;
+ char *buf, *bp;
+ char line[BUFSIZ];
+ char ebuf[128];
+
+ (void) fflush(fp);
+ /* read in the result and send it along */
+ buf = (char *) ch_malloc( BUFSIZ );
+ buf[0] = '\0';
+ bsize = BUFSIZ;
+ bp = buf;
+ while ( !feof(fp) ) {
+ errno = 0;
+ if ( fgets( line, sizeof(line), fp ) == NULL ) {
+ int saved_errno = errno;
+ if ( errno == EINTR ) continue;
+
+ Debug( LDAP_DEBUG_ANY, "sock: fgets failed: %s (%d)\n",
+ AC_STRERROR_R(saved_errno, ebuf, sizeof ebuf), saved_errno );
+ break;
+ }
+
+ Debug( LDAP_DEBUG_SHELL, "sock search reading line (%s)\n",
+ line );
+
+ /* ignore lines beginning with # (LDIFv1 comments) */
+ if ( *line == '#' ) {
+ continue;
+ }
+
+ /* ignore lines beginning with DEBUG: */
+ if ( strncasecmp( line, "DEBUG:", 6 ) == 0 ) {
+ continue;
+ }
+
+ if ( strncasecmp( line, "CONTINUE", 8 ) == 0 ) {
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ /* Only valid when operating as an overlay! */
+ assert( si->si_ops != 0 );
+ rs->sr_err = SLAP_CB_CONTINUE;
+ goto skip;
+ }
+
+ len = strlen( line );
+ while ( bp + len + 1 - buf > bsize ) {
+ size_t offset = bp - buf;
+ bsize += BUFSIZ;
+ buf = (char *) ch_realloc( buf, bsize );
+ bp = &buf[offset];
+ }
+ strcpy( bp, line );
+ bp += len;
+
+ /* line marked the end of an entry or result */
+ if ( *line == '\n' ) {
+ if ( strncasecmp( buf, "RESULT", 6 ) == 0 ) {
+ break;
+ }
+
+ if ( (rs->sr_entry = str2entry( buf )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "str2entry(%s) failed\n",
+ buf );
+ } else {
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ rs->sr_flags = REP_ENTRY_MODIFIABLE;
+ send_search_entry( op, rs );
+ entry_free( rs->sr_entry );
+ rs->sr_attrs = NULL;
+ }
+
+ bp = buf;
+ }
+ }
+ (void) str2result( buf, &rs->sr_err, (char **)&rs->sr_matched, (char **)&rs->sr_text );
+
+ /* otherwise, front end will send this result */
+ if ( rs->sr_err != 0 || op->o_tag != LDAP_REQ_BIND ) {
+ send_ldap_result( op, rs );
+ }
+
+skip:
+ ch_free( buf );
+
+ return( rs->sr_err );
+}
+
+void
+sock_print_suffixes(
+ FILE *fp,
+ Backend *be
+)
+{
+ int i;
+
+ for ( i = 0; be->be_suffix[i].bv_val != NULL; i++ ) {
+ fprintf( fp, "suffix: %s\n", be->be_suffix[i].bv_val );
+ }
+}
+
+void
+sock_print_conn(
+ FILE *fp,
+ Connection *conn,
+ struct sockinfo *si
+)
+{
+ if ( conn == NULL ) return;
+
+ if( si->si_extensions & SOCK_EXT_BINDDN ) {
+ fprintf( fp, "binddn: %s\n",
+ conn->c_dn.bv_len ? conn->c_dn.bv_val : "" );
+ }
+ if( si->si_extensions & SOCK_EXT_PEERNAME ) {
+ fprintf( fp, "peername: %s\n",
+ conn->c_peer_name.bv_len ? conn->c_peer_name.bv_val : "" );
+ }
+ if( si->si_extensions & SOCK_EXT_SSF ) {
+ fprintf( fp, "ssf: %d\n", conn->c_ssf );
+ }
+ if( si->si_extensions & SOCK_EXT_CONNID ) {
+ fprintf( fp, "connid: %lu\n", conn->c_connid );
+ }
+}
diff --git a/servers/slapd/back-sock/search.c b/servers/slapd/back-sock/search.c
new file mode 100644
index 0000000..9812e99
--- /dev/null
+++ b/servers/slapd/back-sock/search.c
@@ -0,0 +1,74 @@
+/* search.c - sock backend search function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+/*
+ * FIXME: add a filterSearchResults option like back-perl has
+ */
+
+int
+sock_back_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ FILE *fp;
+ AttributeName *an;
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the search process */
+ fprintf( fp, "SEARCH\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "base: %s\n", op->o_req_dn.bv_val );
+ fprintf( fp, "scope: %d\n", op->oq_search.rs_scope );
+ fprintf( fp, "deref: %d\n", op->oq_search.rs_deref );
+ fprintf( fp, "sizelimit: %d\n", op->oq_search.rs_slimit );
+ fprintf( fp, "timelimit: %d\n", op->oq_search.rs_tlimit );
+ fprintf( fp, "filter: %s\n", op->oq_search.rs_filterstr.bv_val );
+ fprintf( fp, "attrsonly: %d\n", op->oq_search.rs_attrsonly ? 1 : 0 );
+ fprintf( fp, "attrs:%s", op->oq_search.rs_attrs == NULL ? " all" : "" );
+ for ( an = op->oq_search.rs_attrs; an && an->an_name.bv_val; an++ ) {
+ fprintf( fp, " %s", an->an_name.bv_val );
+ }
+ fprintf( fp, "\n\n" ); /* end of attr line plus blank line */
+
+ /* read in the results and send them along */
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ sock_read_and_send_results( op, rs, fp );
+
+ fclose( fp );
+ return( 0 );
+}
diff --git a/servers/slapd/back-sock/searchexample.conf b/servers/slapd/back-sock/searchexample.conf
new file mode 100644
index 0000000..842d6aa
--- /dev/null
+++ b/servers/slapd/back-sock/searchexample.conf
@@ -0,0 +1,23 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2007-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was initially developed by Brian Candler for inclusion
+## in OpenLDAP Software.
+
+include /usr/local/etc/openldap/schema/core.schema
+
+database sock
+suffix "dc=example,dc=com"
+socketpath /tmp/example.sock
diff --git a/servers/slapd/back-sock/searchexample.pl b/servers/slapd/back-sock/searchexample.pl
new file mode 100644
index 0000000..f867005
--- /dev/null
+++ b/servers/slapd/back-sock/searchexample.pl
@@ -0,0 +1,90 @@
+#!/usr/bin/perl -w -T
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2007-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was initially developed by Brian Candler for inclusion
+## in OpenLDAP Software.
+
+# See: http://search.cpan.org/dist/Net-Server/
+
+package ExampleDB;
+
+use strict;
+use vars qw(@ISA);
+use Net::Server::PreFork; # any personality will do
+
+@ISA = qw(Net::Server::PreFork);
+
+ExampleDB->run(
+ port=>"/tmp/example.sock|unix"
+ #conf_file=>"/etc/example.conf"
+);
+exit;
+
+### over-ridden subs below
+# The protocol is the same as back-shell
+
+sub process_request {
+ my $self = shift;
+
+ eval {
+
+ local $SIG{ALRM} = sub { die "Timed Out!\n" };
+ my $timeout = 30; # give the user 30 seconds to type a line
+ alarm($timeout);
+
+ my $request = <STDIN>;
+
+ if ($request eq "SEARCH\n") {
+ my %req = ();
+ while (my $line = <STDIN>) {
+ chomp($line);
+ last if $line eq "";
+ if ($line =~ /^([^:]+):\s*(.*)$/) { # FIXME: handle base64 encoded
+ $req{$1} = $2;
+ }
+ }
+ #sleep(2); # to test concurrency
+ print "dn: cn=test, dc=example, dc=com\n";
+ print "cn: test\n";
+ print "objectclass: cnobject\n";
+ print "\n";
+ print "RESULT\n";
+ print "code: 0\n";
+ print "info: answered by process $$\n";
+ }
+ else {
+ print "RESULT\n";
+ print "code: 53\n"; # unwillingToPerform
+ print "info: I don't implement $request";
+ }
+
+ };
+
+ return unless $@;
+ if( $@=~/timed out/i ){
+ print "RESULT\n";
+ print "code: 3\n"; # timeLimitExceeded
+ print "info: Timed out\n";
+ }
+ else {
+ print "RESULT\n";
+ print "code: 1\n"; # operationsError
+ print "info: $@\n"; # FIXME: remove CR/LF
+ }
+
+}
+
+1;
diff --git a/servers/slapd/back-sock/unbind.c b/servers/slapd/back-sock/unbind.c
new file mode 100644
index 0000000..0b349e1
--- /dev/null
+++ b/servers/slapd/back-sock/unbind.c
@@ -0,0 +1,57 @@
+/* unbind.c - sock backend unbind function */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2007-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Brian Candler for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "back-sock.h"
+
+int
+sock_back_unbind(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ struct sockinfo *si = (struct sockinfo *) op->o_bd->be_private;
+ FILE *fp;
+
+ if ( (fp = opensock( si->si_sockpath )) == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "could not open socket" );
+ return( -1 );
+ }
+
+ /* write out the request to the unbind process */
+ fprintf( fp, "UNBIND\n" );
+ fprintf( fp, "msgid: %ld\n", (long) op->o_msgid );
+ sock_print_conn( fp, op->o_conn, si );
+ sock_print_suffixes( fp, op->o_bd );
+ fprintf( fp, "\n" );
+
+ /* no response to unbind */
+ fclose( fp );
+
+ return 0;
+}
diff --git a/servers/slapd/back-sql/Makefile.in b/servers/slapd/back-sql/Makefile.in
new file mode 100644
index 0000000..b90bb31
--- /dev/null
+++ b/servers/slapd/back-sql/Makefile.in
@@ -0,0 +1,45 @@
+# Makefile.in for back-sql
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c config.c search.c bind.c compare.c operational.c \
+ entry-id.c schema-map.c sql-wrap.c modify.c util.c \
+ add.c delete.c modrdn.c api.c
+OBJS = init.lo config.lo search.lo bind.lo compare.lo operational.lo \
+ entry-id.lo schema-map.lo sql-wrap.lo modify.lo util.lo \
+ add.lo delete.lo modrdn.lo api.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-sql"
+BUILD_MOD = @BUILD_SQL@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_SQL@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(SLAPD_SQL_LIBS)
+
+LIBBASE = back_sql
+
+XINCPATH = -I.. -I$(srcdir)/.. $(SLAPD_SQL_INCLUDES)
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-sql/add.c b/servers/slapd/back-sql/add.c
new file mode 100644
index 0000000..3098521
--- /dev/null
+++ b/servers/slapd/back-sql/add.c
@@ -0,0 +1,1560 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+#ifdef BACKSQL_SYNCPROV
+#include <lutil.h>
+#endif /* BACKSQL_SYNCPROV */
+
+const char * processable_op_attrs[] = {
+ "pwdAccountLockedTime",
+ "pwdChangedTime",
+ "pwdFailureTime",
+ "pwdGraceUseTime",
+ "pwdHistory",
+ "pwdPolicySubentry",
+ "pwdReset",
+ "entryUUID"
+};
+
+#define processable_op_attrs_length (sizeof (processable_op_attrs) / sizeof (const char *))
+
+static int indexOf(const char *array[], int array_size, const char * value) {
+ for (int i = 0; i < array_size; ++i) {
+ if(strcmp(array[i], value) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static int is_processable_opattr(const char * attr) {
+ return indexOf(processable_op_attrs, processable_op_attrs_length, attr) >= 0;
+}
+
+#define backsql_opattr_skip(ad) \
+ (is_at_operational( (ad)->ad_type ) && (ad) != slap_schema.si_ad_ref )
+
+/*
+ * Skip:
+ * - null values (e.g. delete modification)
+ * - single occurrence of objectClass, because it is already used
+ * to determine how to build the SQL entry
+ * - operational attributes (except those in processable_op_attrs)
+ * - empty attributes
+ */
+#define backsql_attr_skip(ad, vals) \
+ ( \
+ ( ( (ad) == slap_schema.si_ad_objectClass \
+ && (vals) && BER_BVISNULL( &((vals)[ 1 ]) ) ) \
+ || backsql_opattr_skip( (ad) ) \
+ || ( (vals) && BER_BVISNULL( &((vals)[ 0 ]) ) ) \
+ ) && !is_processable_opattr( ad->ad_cname.bv_val ) )
+
+int
+backsql_modify_delete_all_values(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *e_id,
+ backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ RETCODE rc;
+ SQLHSTMT asth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+
+ assert( at != NULL );
+ if ( at->bam_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "missing attribute value delete procedure "
+ "for attr \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ rc = backsql_Prepare( dbh, &asth, at->bam_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error preparing attribute value select query "
+ "\"%s\"\n",
+ at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamID( asth, 1, SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding key value parameter "
+ "to attribute value select query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ rc = SQLExecute( asth );
+ if ( !BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error executing attribute value select query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings_x( asth, &row, op->o_tmpmemctx );
+ for ( rc = SQLFetch( asth );
+ BACKSQL_SUCCESS( rc );
+ rc = SQLFetch( asth ) )
+ {
+ int i;
+ /* first parameter no, parameter order */
+ SQLUSMALLINT pno = 0,
+ po = 0;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ for ( i = 0; i < row.ncols; i++ ) {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ ber_len_t col_len;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error preparing attribute value "
+ "delete procedure "
+ "\"%s\"\n",
+ at->bam_delete_proc );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values() "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ col_len = strlen( row.cols[ i ] );
+ rc = backsql_BindParamStr( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, row.cols[ i ], col_len );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "arg(%d)=%s; executing \"%s\"\n",
+ pno + 2 - po, row.cols[ i ],
+ at->bam_delete_proc );
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "delete_proc "
+ "execution failed (rc=%d, prc=%d)\n",
+ rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ }
+ rs->sr_text = op->o_req_dn.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:;
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ return rs->sr_err;
+}
+
+int
+backsql_modify_internal(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ backsql_entryID *e_id,
+ Modifications *modlist )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ RETCODE rc;
+ Modifications *ml;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modify_internal(): "
+ "traversing modifications list\n" );
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ AttributeDescription *ad;
+ int sm_op;
+ static char *sm_ops[] = { "add", "delete", "replace", "increment", NULL };
+
+ BerVarray sm_values;
+#if 0
+ /* NOTE: some day we'll have to pass
+ * the normalized values as well */
+ BerVarray sm_nvalues;
+#endif
+ backsql_at_map_rec *at = NULL;
+ struct berval *at_val;
+ int i;
+
+ ad = ml->sml_mod.sm_desc;
+ sm_op = ( ml->sml_mod.sm_op & LDAP_MOD_OP );
+ sm_values = ml->sml_mod.sm_values;
+#if 0
+ sm_nvalues = ml->sml_mod.sm_nvalues;
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "modifying attribute \"%s\" (%s) according to "
+ "mappings for objectClass \"%s\"\n",
+ ad->ad_cname.bv_val, sm_ops[ sm_op ], BACKSQL_OC_NAME( oc ) );
+
+ if ( backsql_attr_skip( ad, sm_values ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "skipping attribute \"%s\"\n",
+ ad->ad_cname.bv_val, 0, 0 );
+
+ continue;
+ }
+
+ at = backsql_ad2at( oc, ad );
+ if ( at == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "attribute \"%s\" is not registered "
+ "in objectClass \"%s\"\n",
+ ad->ad_cname.bv_val, BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ continue;
+ }
+
+ switch ( sm_op ) {
+ case LDAP_MOD_REPLACE: {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "replacing values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( at->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add procedure is not defined "
+ "for attribute \"%s\" "
+ "- unable to perform replacements\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ if ( at->bam_delete_proc == NULL ) {
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\" "
+ "- adding only\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ goto add_only;
+ }
+
+del_all:
+ rs->sr_err = backsql_modify_delete_all_values( op, rs, dbh, e_id, at );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ /* LDAP_MOD_DELETE gets here if all values must be deleted */
+ if ( sm_op == LDAP_MOD_DELETE ) {
+ break;
+ }
+ }
+
+ /*
+ * PASSTHROUGH - to add new attributes -- do NOT add break
+ */
+ case LDAP_MOD_ADD:
+ /* case SLAP_MOD_SOFTADD: */
+ /* case SLAP_MOD_ADD_IF_NOT_PRESENT: */
+add_only:;
+ if ( at->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "adding new values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ /* can't add a NULL val array */
+ assert( sm_values != NULL );
+
+ for ( i = 0, at_val = sm_values;
+ !BER_BVISNULL( at_val );
+ i++, at_val++ )
+ {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ /* first parameter position, parameter order */
+ SQLUSMALLINT pno = 0,
+ po;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_add_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error preparing add query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+
+ if ( BACKSQL_IS_ADD( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_ADD( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ rc = backsql_BindParamBerVal( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=\"%s\"; executing \"%s\"\n",
+ pno + 2 - po, at_val->bv_val,
+ at->bam_add_proc );
+
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add_proc execution failed "
+ "(rc=%d, prc=%d)\n",
+ rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ return rs->sr_err;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) )
+ {
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ break;
+
+ case LDAP_MOD_DELETE:
+ /* case SLAP_MOD_SOFTDEL: */
+ if ( at->bam_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ if ( sm_values == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "no values given to delete "
+ "for attribute \"%s\" "
+ "-- deleting all values\n",
+ at->bam_ad->ad_cname.bv_val );
+ goto del_all;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "deleting values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ for ( i = 0, at_val = sm_values;
+ !BER_BVISNULL( at_val );
+ i++, at_val++ )
+ {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ /* first parameter position, parameter order */
+ SQLUSMALLINT pno = 0,
+ po;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error preparing delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ rc = backsql_BindParamBerVal( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "executing \"%s\"\n",
+ at->bam_delete_proc );
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete_proc execution "
+ "failed (rc=%d, prc=%d)\n",
+ rc, prc );
+
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ goto done;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env,
+ dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ goto done;
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "increment not supported yet\n" );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+ break;
+ }
+ }
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modify_internal(): %d%s%s\n",
+ rs->sr_err,
+ rs->sr_text ? ": " : "",
+ rs->sr_text ? rs->sr_text : "" );
+
+ /*
+ * FIXME: should fail in case one change fails?
+ */
+ return rs->sr_err;
+}
+
+static int
+backsql_add_attr(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ Attribute *at,
+ backsql_key_t new_keyval )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ backsql_at_map_rec *at_rec = NULL;
+ struct berval *at_val;
+ unsigned long i;
+ RETCODE rc;
+ SQLUSMALLINT currpos;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+
+ at_rec = backsql_ad2at( oc, at->a_desc );
+
+ if ( at_rec == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): "
+ "attribute \"%s\" is not registered "
+ "in objectclass \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ at->a_desc->ad_cname.bv_val,
+ BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ if ( at_rec->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): "
+ "add procedure is not defined "
+ "for attribute \"%s\" "
+ "of structuralObjectClass \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ at->a_desc->ad_cname.bv_val,
+ BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ for ( i = 0, at_val = &at->a_vals[ i ];
+ !BER_BVISNULL( at_val );
+ i++, at_val = &at->a_vals[ i ] )
+ {
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+ /* first parameter #, parameter order */
+ SQLUSMALLINT pno, po;
+
+ /*
+ * Do not deal with the objectClass that is used
+ * to build the entry
+ */
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ if ( dn_match( at_val, &oc->bom_oc->soc_cname ) )
+ {
+ continue;
+ }
+ }
+
+ rc = backsql_Prepare( dbh, &sth, at_rec->bam_add_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ if ( BACKSQL_IS_ADD( at_rec->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding output parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ } else {
+ pno = 0;
+ }
+
+ po = ( BACKSQL_IS_ADD( at_rec->bam_param_order ) ) > 0;
+ currpos = pno + 1 + po;
+ rc = backsql_BindParamNumID( sth, currpos,
+ SQL_PARAM_INPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding keyval parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ currpos = pno + 2 - po;
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+
+ rc = backsql_BindParamBerVal( sth, currpos, SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding value parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+#ifdef LDAP_DEBUG
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_add_attr(\"%s\"): " "executing \"%s\" val[%lu], id=" BACKSQL_IDNUMFMT "\n",
+ op->ora_e->e_name.bv_val, at_rec->bam_add_proc,
+ i, new_keyval );
+#endif
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(\"%s\"): "
+ "add_proc execution failed (rc=%d, prc=%d)\n",
+ op->ora_e->e_name.bv_val, rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = op->ora_e->e_name.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ return rs->sr_err;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = op->ora_e->e_name.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ return rs->sr_err;
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+backsql_add( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ backsql_key_t new_keyval = 0;
+ RETCODE rc;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ Entry p = { 0 }, *e = NULL;
+ Attribute *at,
+ *at_objectClass = NULL;
+ ObjectClass *soc = NULL;
+ struct berval scname = BER_BVNULL;
+ struct berval pdn;
+ struct berval realdn = BER_BVNULL;
+ int colnum;
+ slap_mask_t mask;
+
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+
+#ifdef BACKSQL_SYNCPROV
+ /*
+ * NOTE: fake successful result to force contextCSN to be bumped up
+ */
+ if ( op->o_sync ) {
+ char buf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ struct berval csn;
+
+ csn.bv_val = buf;
+ csn.bv_len = sizeof( buf );
+ slap_get_csn( op, &csn, 1 );
+
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+
+ slap_graduate_commit_csn( op );
+
+ return 0;
+ }
+#endif /* BACKSQL_SYNCPROV */
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_add(\"%s\")\n",
+ op->ora_e->e_name.bv_val );
+
+ /* check schema */
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ rs->sr_err = entry_schema_check( op, op->ora_e, NULL, 0, 1, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ op->ora_e->e_name.bv_val );
+ e = NULL;
+ goto done;
+ }
+ }
+
+ slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "assertion control failed -- aborting\n",
+ op->ora_e->e_name.bv_val );
+ e = NULL;
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto done;
+ }
+
+ /* search structuralObjectClass */
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ if ( at->a_desc == slap_schema.si_ad_structuralObjectClass ) {
+ break;
+ }
+ }
+
+ /* there must exist */
+ if ( at == NULL ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ const char *text;
+
+ /* search structuralObjectClass */
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ break;
+ }
+ }
+
+ if ( at == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "no objectClass\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ e = NULL;
+ goto done;
+ }
+
+ rs->sr_err = structural_class( at->a_vals, &soc, NULL,
+ &text, buf, sizeof( buf ), op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "%s (%d)\n",
+ op->ora_e->e_name.bv_val, text, rs->sr_err );
+ e = NULL;
+ goto done;
+ }
+ scname = soc->soc_cname;
+
+ } else {
+ scname = at->a_vals[0];
+ }
+
+ /* I guess we should play with sub/supertypes to find a suitable oc */
+ oc = backsql_name2oc( bi, &scname );
+
+ if ( oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "cannot map structuralObjectClass \"%s\" -- aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ if ( oc->bom_create_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create procedure is not defined "
+ "for structuralObjectClass \"%s\" - aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+
+ } else if ( BACKSQL_CREATE_NEEDS_SELECT( bi )
+ && oc->bom_create_keyval == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create procedure needs select procedure, "
+ "but none is defined for structuralObjectClass \"%s\" "
+ "- aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /* check write access */
+ if ( !access_allowed_mask( op, op->ora_e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_WADD, NULL, &mask ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = op->ora_e;
+ goto done;
+ }
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "could not get connection handle - exiting\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Check if entry exists
+ *
+ * NOTE: backsql_api_dn2odbc() is called explicitly because
+ * we need the mucked DN to pass it to the create procedure.
+ */
+ realdn = op->ora_e->e_name;
+ if ( backsql_api_dn2odbc( op, rs, &realdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ op->ora_e->e_name.bv_val, realdn.bv_val );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rs->sr_err = backsql_dn2id( op, rs, dbh, &realdn, NULL, 0, 0 );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "entry exists\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ e = op->ora_e;
+ goto done;
+ }
+
+ /*
+ * Get the parent dn and see if the corresponding entry exists.
+ */
+ if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) {
+ pdn = slap_empty_bv;
+
+ } else {
+ dnParent( &op->ora_e->e_nname, &pdn );
+
+ /*
+ * Get the parent
+ */
+ bsi.bsi_e = &p;
+ rs->sr_err = backsql_init_search( &bsi, &pdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "could not retrieve addDN parent "
+ "\"%s\" ID - %s matched=\"%s\"\n",
+ pdn.bv_val,
+ rs->sr_err == LDAP_REFERRAL ? "referral" : "no such entry",
+ rs->sr_matched ? rs->sr_matched : "(null)" );
+ e = &p;
+ goto done;
+ }
+
+ /* check "children" pseudo-attribute access to parent */
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children,
+ NULL, ACL_WADD, NULL ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &p;
+ goto done;
+ }
+ }
+
+ /*
+ * create_proc is executed; if expect_return is set, then
+ * an output parameter is bound, which should contain
+ * the id of the added row; otherwise the procedure
+ * is expected to return the id as the first column of a select
+ */
+ rc = backsql_Prepare( dbh, &sth, oc->bom_create_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ colnum = 1;
+ if ( BACKSQL_IS_ADD( oc->bom_expect_return ) ) {
+ rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_OUTPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding keyval parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+ colnum++;
+ }
+
+ if ( oc->bom_create_hint ) {
+ at = attr_find( op->ora_e->e_attrs, oc->bom_create_hint );
+ if ( at && at->a_vals ) {
+ backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT,
+ at->a_vals[0].bv_val,
+ at->a_vals[0].bv_len );
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "create_proc hint: param = '%s'\n",
+ at->a_vals[0].bv_val );
+
+ } else {
+ backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT,
+ "", 0 );
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "create_proc hint (%s) not available\n",
+ oc->bom_create_hint->ad_cname.bv_val );
+ }
+ colnum++;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): executing \"%s\"\n",
+ op->ora_e->e_name.bv_val, oc->bom_create_proc );
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc execution failed\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ /* FIXME: after SQLExecute(), the row is already inserted
+ * (at least with PostgreSQL and unixODBC); needs investigation */
+
+ if ( !BACKSQL_IS_ADD( oc->bom_expect_return ) ) {
+ SWORD ncols;
+ SQLLEN value_len;
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rc = backsql_Prepare( dbh, &sth, oc->bom_create_keyval, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ }
+
+ /*
+ * the query to know the id of the inserted entry
+ * must be embedded in the create procedure
+ */
+ rc = SQLNumResultCols( sth, &ncols );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result evaluation failed\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+
+ } else if ( ncols != 1 ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result is bogus (ncols=%d)\n",
+ op->ora_e->e_name.bv_val, ncols );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+#if 0
+ {
+ SQLCHAR colname[ 64 ];
+ SQLSMALLINT name_len, col_type, col_scale, col_null;
+ UDWORD col_prec;
+
+ /*
+ * FIXME: check whether col_type is compatible,
+ * if it can be null and so on ...
+ */
+ rc = SQLDescribeCol( sth, (SQLUSMALLINT)1,
+ &colname[ 0 ],
+ (SQLUINTEGER)( sizeof( colname ) - 1 ),
+ &name_len, &col_type,
+ &col_prec, &col_scale, &col_null );
+ }
+#endif
+
+ rc = SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_ULONG,
+ (SQLPOINTER)&new_keyval,
+ (SQLINTEGER)sizeof( new_keyval ),
+ &value_len );
+
+ rc = SQLFetch( sth );
+
+ if ( value_len <= 0 ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result is empty?\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc returned keyval=" BACKSQL_IDNUMFMT "\n",
+ op->ora_e->e_name.bv_val, new_keyval );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_insentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realdn );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding DN parameter for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamNumID( sth, 2, SQL_PARAM_INPUT, &oc->bom_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding objectClass ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &bsi.bsi_base_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding parent ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamNumID( sth, 4, SQL_PARAM_INPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding entry ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_add(): executing \"%s\" for dn=\"%s\" oc_map_id=" BACKSQL_IDNUMFMT " p_id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDNUMFMT "\n",
+ bi->sql_insentry_stmt, op->ora_e->e_name.bv_val,
+ oc->bom_id, BACKSQL_IDARG(bsi.bsi_base_id.eid_id),
+ new_keyval );
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "could not insert ldap_entries record\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ /*
+ * execute delete_proc to delete data added !!!
+ */
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(): "
+ "adding attribute \"%s\"\n",
+ at->a_desc->ad_cname.bv_val );
+
+ /*
+ * Skip:
+ * - the first occurrence of objectClass, which is used
+ * to determine how to build the SQL entry (FIXME ?!?)
+ * - operational attributes
+ * - empty attributes (FIXME ?!?)
+ */
+ if ( backsql_attr_skip( at->a_desc, at->a_vals ) ) {
+ continue;
+ }
+
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ at_objectClass = at;
+ continue;
+ }
+
+ rs->sr_err = backsql_add_attr( op, rs, dbh, oc, at, new_keyval );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = op->ora_e;
+ goto done;
+ }
+ }
+
+ if ( at_objectClass ) {
+ rs->sr_err = backsql_add_attr( op, rs, dbh, oc,
+ at_objectClass, new_keyval );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = op->ora_e;
+ goto done;
+ }
+ }
+
+done:;
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+ /*
+ * FIXME: NOOP does not work for add -- it works for all
+ * the other operations, and I don't get the reason :(
+ *
+ * hint: there might be some autocommit in Postgres
+ * so that when the unique id of the key table is
+ * automatically increased, there's no rollback.
+ * We might implement a "rollback" procedure consisting
+ * in deleting that row.
+ */
+
+ if ( e != NULL ) {
+ int disclose = 1;
+
+ if ( e == op->ora_e && !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ /* mask already collected */
+ disclose = 0;
+
+ } else if ( e == &p && !access_allowed( op, &p,
+ slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ disclose = 0;
+ }
+
+ if ( disclose == 0 ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &realdn )
+ && realdn.bv_val != op->ora_e->e_name.bv_val )
+ {
+ ch_free( realdn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_add(\"%s\"): %d \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ rs->sr_err,
+ rs->sr_text ? rs->sr_text : "" );
+
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/api.c b/servers/slapd/back-sql/api.c
new file mode 100644
index 0000000..7ef1d9f
--- /dev/null
+++ b/servers/slapd/back-sql/api.c
@@ -0,0 +1,211 @@
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+static backsql_api *backsqlapi;
+
+int
+backsql_api_config( backsql_info *bi, const char *name, int argc, char *argv[] )
+{
+ backsql_api *ba;
+
+ assert( bi != NULL );
+ assert( name != NULL );
+
+ for ( ba = backsqlapi; ba; ba = ba->ba_next ) {
+ if ( strcasecmp( name, ba->ba_name ) == 0 ) {
+ backsql_api *ba2;
+
+ ba2 = ch_malloc( sizeof( backsql_api ) );
+ *ba2 = *ba;
+
+ if ( ba2->ba_config ) {
+ if ( ( *ba2->ba_config )( ba2, argc, argv ) ) {
+ ch_free( ba2 );
+ return 1;
+ }
+ ba2->ba_argc = argc;
+ if ( argc ) {
+ int i;
+ ba2->ba_argv = ch_malloc( argc * sizeof(char *));
+ for ( i=0; i<argc; i++ )
+ ba2->ba_argv[i] = ch_strdup( argv[i] );
+ }
+ }
+
+ ba2->ba_next = bi->sql_api;
+ bi->sql_api = ba2;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int
+backsql_api_destroy( backsql_info *bi )
+{
+ backsql_api *ba;
+
+ assert( bi != NULL );
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_destroy ) {
+ (void)( *ba->ba_destroy )( ba );
+ }
+ }
+
+ return 0;
+}
+
+int
+backsql_api_register( backsql_api *ba )
+{
+ backsql_api *ba2;
+
+ assert( ba != NULL );
+ assert( ba->ba_private == NULL );
+
+ if ( ba->ba_name == NULL ) {
+ fprintf( stderr, "API module has no name\n" );
+ exit(EXIT_FAILURE);
+ }
+
+ for ( ba2 = backsqlapi; ba2; ba2 = ba2->ba_next ) {
+ if ( strcasecmp( ba->ba_name, ba2->ba_name ) == 0 ) {
+ fprintf( stderr, "API module \"%s\" already defined\n", ba->ba_name );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ ba->ba_next = backsqlapi;
+ backsqlapi = ba;
+
+ return 0;
+}
+
+int
+backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ backsql_api *ba;
+ int rc;
+ struct berval bv;
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ ber_dupbv( &bv, dn );
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_dn2odbc ) {
+ /*
+ * The dn2odbc() helper is supposed to rewrite
+ * the contents of bv, freeing the original value
+ * with ch_free() if required and replacing it
+ * with a newly allocated one using ch_malloc()
+ * or companion functions.
+ *
+ * NOTE: it is supposed to __always__ free
+ * the value of bv in case of error, and reset
+ * it with BER_BVZERO() .
+ */
+ rc = ( *ba->ba_dn2odbc )( op, rs, &bv );
+
+ if ( rc ) {
+ /* in case of error, dn2odbc() must cleanup */
+ assert( BER_BVISNULL( &bv ) );
+
+ return rc;
+ }
+ }
+ }
+
+ assert( !BER_BVISNULL( &bv ) );
+
+ *dn = bv;
+
+ return 0;
+}
+
+int
+backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ backsql_api *ba;
+ int rc;
+ struct berval bv;
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ ber_dupbv( &bv, dn );
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_dn2odbc ) {
+ rc = ( *ba->ba_odbc2dn )( op, rs, &bv );
+ /*
+ * The odbc2dn() helper is supposed to rewrite
+ * the contents of bv, freeing the original value
+ * with ch_free() if required and replacing it
+ * with a newly allocated one using ch_malloc()
+ * or companion functions.
+ *
+ * NOTE: it is supposed to __always__ free
+ * the value of bv in case of error, and reset
+ * it with BER_BVZERO() .
+ */
+ if ( rc ) {
+ /* in case of error, odbc2dn() must cleanup */
+ assert( BER_BVISNULL( &bv ) );
+
+ return rc;
+ }
+ }
+ }
+
+ assert( !BER_BVISNULL( &bv ) );
+
+ *dn = bv;
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-sql/back-sql.h b/servers/slapd/back-sql/back-sql.h
new file mode 100644
index 0000000..556ea6f
--- /dev/null
+++ b/servers/slapd/back-sql/back-sql.h
@@ -0,0 +1,631 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Mararati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+/*
+ * The following changes have been addressed:
+ *
+ * Enhancements:
+ * - re-styled code for better readability
+ * - upgraded backend API to reflect recent changes
+ * - LDAP schema is checked when loading SQL/LDAP mapping
+ * - AttributeDescription/ObjectClass pointers used for more efficient
+ * mapping lookup
+ * - bervals used where string length is required often
+ * - atomized write operations by committing at the end of each operation
+ * and defaulting connection closure to rollback
+ * - added LDAP access control to write operations
+ * - fully implemented modrdn (with rdn attrs change, deleteoldrdn,
+ * access check, parent/children check and more)
+ * - added parent access control, children control to delete operation
+ * - added structuralObjectClass operational attribute check and
+ * value return on search
+ * - added hasSubordinate operational attribute on demand
+ * - search limits are appropriately enforced
+ * - function backsql_strcat() has been made more efficient
+ * - concat function has been made configurable by means of a pattern
+ * - added config switches:
+ * - fail_if_no_mapping write operations fail if there is no mapping
+ * - has_ldapinfo_dn_ru overrides autodetect
+ * - concat_pattern a string containing two '?' is used
+ * (note that "?||?" should be more portable
+ * than builtin function "CONCAT(?,?)")
+ * - strcast_func cast of string constants in "SELECT DISTINCT
+ * statements (needed by PostgreSQL)
+ * - upper_needs_cast cast the argument of upper when required
+ * (basically when building dn substring queries)
+ * - added noop control
+ * - added values return filter control
+ * - hasSubordinate can be used in search filters (with limitations)
+ * - eliminated oc->name; use oc->oc->soc_cname instead
+ *
+ * Todo:
+ * - add security checks for SQL statements that can be injected (?)
+ * - re-test with previously supported RDBMs
+ * - replace dn_ru and so with normalized dn (no need for upper() and so
+ * in dn match)
+ * - implement a backsql_normalize() function to replace the upper()
+ * conversion routines
+ * - note that subtree deletion, subtree renaming and so could be easily
+ * implemented (rollback and consistency checks are available :)
+ * - implement "lastmod" and other operational stuff (ldap_entries table ?)
+ * - check how to allow multiple operations with one statement, to remove
+ * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?)
+ */
+/*
+ * Improvements submitted by (ITS#3432)
+ *
+ * 1. id_query.patch applied (with changes)
+ * 2. shortcut.patch applied (reworked)
+ * 3. create_hint.patch applied
+ * 4. count_query.patch applied (reworked)
+ * 5. returncodes.patch applied (with sanity checks)
+ * 6. connpool.patch under evaluation
+ * 7. modoc.patch under evaluation (requires
+ * manageDSAit and "manage"
+ * access privileges)
+ * 8. miscfixes.patch applied (reworked; other
+ * operations need to load the
+ * entire entry for ACL purposes;
+ * see ITS#3480, now fixed)
+ *
+ * original description:
+
+ Changes that were made to the SQL backend.
+
+The patches were made against 2.2.18 and can be applied individually,
+but would best be applied in the numerical order of the file names.
+A synopsis of each patch is given here:
+
+
+1. Added an option to set SQL query for the "id_query" operation.
+
+2. Added an option to the SQL backend called "use_subtree_shortcut".
+When a search is performed, the SQL query includes a WHERE clause
+which says the DN must be "LIKE %<searchbase>". The LIKE operation
+can be slow in an RDBM. This shortcut option says that if the
+searchbase of the LDAP search is the root DN of the SQL backend,
+and thus all objects will match the LIKE operator, do not include
+the "LIKE %<searchbase>" clause in the SQL query (it is replaced
+instead by the always true "1=1" clause to keep the "AND"'s
+working correctly). This option is off by default, and should be
+turned on only if all objects to be found in the RDBM are under the
+same root DN. Multiple backends working within the same RDBM table
+space would encounter problems. LDAP searches whose searchbase are
+not at the root DN will bypass this shortcut and employ the LIKE
+clause.
+
+3. Added a "create_hint" column to ldap_oc_mappings table. Allows
+taking the value of an attr named in "create_hint" and passing it to
+the create_proc procedure. This is necessary for when an objectClass's
+table is partition indexed by some indexing column and thus the value
+in that indexing column cannot change after the row is created. The
+value for the indexed column is passed into the create_proc, which
+uses it to fill in the indexed column as the new row is created.
+
+4. When loading the values of an attribute, the count(*) of the number
+of values is fetched first and memory is allocated for the array of
+values and normalized values. The old system of loading the values one
+by one and running realloc() on the array of values and normalized
+values each time was badly fragmenting memory. The array of values and
+normalized values would be side by side in memory, and realloc()'ing
+them over and over would force them to leapfrog each other through all
+of available memory. Attrs with a large number of values could not be
+loaded without crashing the slapd daemon.
+
+5. Added code to interpret the value returned by stored procedures
+which have expect_return set. Returned value is interpreted as an LDAP
+return code. This allows the distinction between the SQL failing to
+execute and the SQL running to completion and returning an error code
+which can indicate a policy violation.
+
+6. Added RDBM connection pooling. Once an operation is finished the
+connection to the RDBM is returned to a pool rather than closing.
+Allows the next operation to skip the initialization and authentication
+phases of contacting the RDBM. Also, if licensing with ODBC places
+a limit on the number of connections, an LDAP thread can block waiting
+for another thread to finish, so that no LDAP errors are returned
+for having more LDAP connections than allowed RDBM connections. An
+RDBM connection which receives an SQL error is marked as "tainted"
+so that it will be closed rather than returned to the pool.
+ Also, RDBM connections must be bound to a given LDAP connection AND
+operation number, and NOT just the connection number. Asynchronous
+LDAP clients can have multiple simultaneous LDAP operations which
+should not share the same RDBM connection. A given LDAP operation can
+even make multiple SQL operations (e.g. a BIND operation which
+requires SASL to perform an LDAP search to convert the SASL ID to an
+LDAP DN), so each RDBM connection now has a refcount that must reach
+zero before the connection is returned to the free pool.
+
+7. Added ability to change the objectClass of an object. Required
+considerable work to copy all attributes out of old object and into
+new object. Does a schema check before proceeding. Creates a new
+object, fills it in, deletes the old object, then changes the
+oc_map_id and keyval of the entry in the "ldap_entries" table.
+
+8. Generic fixes. Includes initializing pointers before they
+get used in error branch cases, pointer checks before dereferencing,
+resetting a return code to success after a COMPARE op, sealing
+memory leaks, and in search.c, changing some of the "1=1" tests to
+"2=2", "3=3", etc so that when reading slapd trace output, the
+location in the source code where the x=x test was added to the SQL
+can be easily distinguished.
+ */
+
+#ifndef __BACKSQL_H__
+#define __BACKSQL_H__
+
+/* former sql-types.h */
+#include <sql.h>
+#include <sqlext.h>
+
+typedef struct {
+ SWORD ncols;
+ BerVarray col_names;
+ UDWORD *col_prec;
+ SQLSMALLINT *col_type;
+ char **cols;
+ SQLLEN *value_len;
+} BACKSQL_ROW_NTS;
+
+/*
+ * Better use the standard length of 8192 (as of slap.h)?
+ *
+ * NOTE: must be consistent with definition in ldap_entries table
+ */
+/* #define BACKSQL_MAX_DN_LEN SLAP_LDAPDN_MAXLEN */
+#define BACKSQL_MAX_DN_LEN 255
+
+/*
+ * define to enable very extensive trace logging (debug only)
+ */
+#undef BACKSQL_TRACE
+
+/*
+ * define if using MS SQL and workaround needed (see sql-wrap.c)
+ */
+#undef BACKSQL_MSSQL_WORKAROUND
+
+/*
+ * define to enable values counting for attributes
+ */
+#define BACKSQL_COUNTQUERY
+
+/*
+ * define to enable prettification/validation of values
+ */
+#define BACKSQL_PRETTY_VALIDATE
+
+/*
+ * define to enable varchars as unique keys in user tables
+ *
+ * by default integers are used (and recommended)
+ * for performances. Integers are used anyway in back-sql
+ * related tables.
+ */
+#undef BACKSQL_ARBITRARY_KEY
+
+/*
+ * type used for keys
+ */
+#if defined(HAVE_LONG_LONG) && defined(SQL_C_UBIGINT) && \
+ ( defined(HAVE_STRTOULL) || defined(HAVE_STRTOUQ) )
+typedef unsigned long long backsql_key_t;
+#define BACKSQL_C_NUMID SQL_C_UBIGINT
+#define BACKSQL_IDNUMFMT "%llu"
+#define BACKSQL_STR2ID lutil_atoullx
+#else /* ! HAVE_LONG_LONG || ! SQL_C_UBIGINT */
+typedef unsigned long backsql_key_t;
+#define BACKSQL_C_NUMID SQL_C_ULONG
+#define BACKSQL_IDNUMFMT "%lu"
+#define BACKSQL_STR2ID lutil_atoulx
+#endif /* ! HAVE_LONG_LONG */
+
+/*
+ * define to enable support for syncprov overlay
+ */
+#define BACKSQL_SYNCPROV
+
+/*
+ * define to the appropriate aliasing string
+ *
+ * some RDBMSes tolerate (or require) that " AS " is not used
+ * when aliasing tables/columns
+ */
+#define BACKSQL_ALIASING "AS "
+/* #define BACKSQL_ALIASING "" */
+
+/*
+ * define to the appropriate quoting char
+ *
+ * some RDBMSes tolerate/require that the aliases be enclosed
+ * in quotes. This is especially true for those that do not
+ * allow keywords used as aliases.
+ */
+#define BACKSQL_ALIASING_QUOTE ""
+/* #define BACKSQL_ALIASING_QUOTE "\"" */
+/* #define BACKSQL_ALIASING_QUOTE "'" */
+
+/*
+ * API
+ *
+ * a simple mechanism to allow DN mucking between the LDAP
+ * and the stored string representation.
+ */
+typedef struct backsql_api {
+ char *ba_name;
+ int (*ba_config)( struct backsql_api *self, int argc, char *argv[] );
+ int (*ba_destroy)( struct backsql_api *self );
+
+ int (*ba_dn2odbc)( Operation *op, SlapReply *rs, struct berval *dn );
+ int (*ba_odbc2dn)( Operation *op, SlapReply *rs, struct berval *dn );
+
+ void *ba_private;
+ struct backsql_api *ba_next;
+ char **ba_argv;
+ int ba_argc;
+} backsql_api;
+
+/*
+ * "structural" objectClass mapping structure
+ */
+typedef struct backsql_oc_map_rec {
+ /*
+ * Structure of corresponding LDAP objectClass definition
+ */
+ ObjectClass *bom_oc;
+#define BACKSQL_OC_NAME(ocmap) ((ocmap)->bom_oc->soc_cname.bv_val)
+
+ struct berval bom_keytbl;
+ struct berval bom_keycol;
+ /* expected to return keyval of newly created entry */
+ char *bom_create_proc;
+ /* in case create_proc does not return the keyval of the newly
+ * created row */
+ char *bom_create_keyval;
+ /* supposed to expect keyval as parameter and delete
+ * all the attributes as well */
+ char *bom_delete_proc;
+ /* flags whether delete_proc is a function (whether back-sql
+ * should bind first parameter as output for return code) */
+ int bom_expect_return;
+ backsql_key_t bom_id;
+ Avlnode *bom_attrs;
+ AttributeDescription *bom_create_hint;
+} backsql_oc_map_rec;
+
+/*
+ * attributeType mapping structure
+ */
+typedef struct backsql_at_map_rec {
+ /* Description of corresponding LDAP attribute type */
+ AttributeDescription *bam_ad;
+ AttributeDescription *bam_true_ad;
+ /* ObjectClass if bam_ad is objectClass */
+ ObjectClass *bam_oc;
+
+ struct berval bam_from_tbls;
+ struct berval bam_join_where;
+ struct berval bam_sel_expr;
+
+ /* TimesTen, or, if a uppercase function is defined,
+ * an uppercased version of bam_sel_expr */
+ struct berval bam_sel_expr_u;
+
+ /* supposed to expect 2 binded values: entry keyval
+ * and attr. value to add, like "add_name(?,?,?)" */
+ char *bam_add_proc;
+ /* supposed to expect 2 binded values: entry keyval
+ * and attr. value to delete */
+ char *bam_delete_proc;
+ /* for optimization purposes attribute load query
+ * is preconstructed from parts on schemamap load time */
+ char *bam_query;
+#ifdef BACKSQL_COUNTQUERY
+ char *bam_countquery;
+#endif /* BACKSQL_COUNTQUERY */
+ /* following flags are bitmasks (first bit used for add_proc,
+ * second - for delete_proc) */
+ /* order of parameters for procedures above;
+ * 1 means "data then keyval", 0 means "keyval then data" */
+ int bam_param_order;
+ /* flags whether one or more of procedures is a function
+ * (whether back-sql should bind first parameter as output
+ * for return code) */
+ int bam_expect_return;
+
+ /* next mapping for attribute */
+ struct backsql_at_map_rec *bam_next;
+} backsql_at_map_rec;
+
+#define BACKSQL_AT_MAP_REC_INIT { NULL, NULL, BER_BVC(""), BER_BVC(""), BER_BVNULL, BER_BVNULL, NULL, NULL, NULL, 0, 0, NULL }
+
+/* define to uppercase filters only if the matching rule requires it
+ * (currently broken) */
+/* #define BACKSQL_UPPERCASE_FILTER */
+
+#define BACKSQL_AT_CANUPPERCASE(at) ( !BER_BVISNULL( &(at)->bam_sel_expr_u ) )
+
+/* defines to support bitmasks above */
+#define BACKSQL_ADD 0x1
+#define BACKSQL_DEL 0x2
+
+#define BACKSQL_IS_ADD(x) ( ( BACKSQL_ADD & (x) ) == BACKSQL_ADD )
+#define BACKSQL_IS_DEL(x) ( ( BACKSQL_DEL & (x) ) == BACKSQL_DEL )
+
+#define BACKSQL_NCMP(v1,v2) ber_bvcmp((v1),(v2))
+
+#define BACKSQL_CONCAT
+/*
+ * berbuf structure: a berval with a buffer size associated
+ */
+typedef struct berbuf {
+ struct berval bb_val;
+ ber_len_t bb_len;
+} BerBuffer;
+
+#define BB_NULL { BER_BVNULL, 0 }
+
+/*
+ * Entry ID structure
+ */
+typedef struct backsql_entryID {
+ /* #define BACKSQL_ARBITRARY_KEY to allow a non-numeric key.
+ * It is required by some special applications that use
+ * strings as keys for the main table.
+ * In this case, #define BACKSQL_MAX_KEY_LEN consistently
+ * with the key size definition */
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval eid_id;
+ struct berval eid_keyval;
+#define BACKSQL_MAX_KEY_LEN 64
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ /* The original numeric key is maintained as default. */
+ backsql_key_t eid_id;
+ backsql_key_t eid_keyval;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ backsql_key_t eid_oc_id;
+ backsql_oc_map_rec *eid_oc;
+ struct berval eid_dn;
+ struct berval eid_ndn;
+ struct backsql_entryID *eid_next;
+} backsql_entryID;
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_ENTRYID_INIT { BER_BVNULL, BER_BVNULL, 0, NULL, BER_BVNULL, BER_BVNULL, NULL }
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_ENTRYID_INIT { 0, 0, 0, NULL, BER_BVNULL, BER_BVNULL, NULL }
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+/* the function must collect the entry associated to nbase */
+#define BACKSQL_ISF_GET_ID 0x1U
+#define BACKSQL_ISF_GET_ENTRY ( 0x2U | BACKSQL_ISF_GET_ID )
+#define BACKSQL_ISF_GET_OC ( 0x4U | BACKSQL_ISF_GET_ID )
+#define BACKSQL_ISF_MATCHED 0x8U
+#define BACKSQL_IS_GET_ID(f) \
+ ( ( (f) & BACKSQL_ISF_GET_ID ) == BACKSQL_ISF_GET_ID )
+#define BACKSQL_IS_GET_ENTRY(f) \
+ ( ( (f) & BACKSQL_ISF_GET_ENTRY ) == BACKSQL_ISF_GET_ENTRY )
+#define BACKSQL_IS_GET_OC(f) \
+ ( ( (f) & BACKSQL_ISF_GET_OC ) == BACKSQL_ISF_GET_OC )
+#define BACKSQL_IS_MATCHED(f) \
+ ( ( (f) & BACKSQL_ISF_MATCHED ) == BACKSQL_ISF_MATCHED )
+typedef struct backsql_srch_info {
+ Operation *bsi_op;
+ SlapReply *bsi_rs;
+
+ unsigned bsi_flags;
+#define BSQL_SF_NONE 0x0000U
+#define BSQL_SF_ALL_USER 0x0001U
+#define BSQL_SF_ALL_OPER 0x0002U
+#define BSQL_SF_ALL_ATTRS (BSQL_SF_ALL_USER|BSQL_SF_ALL_OPER)
+#define BSQL_SF_FILTER_HASSUBORDINATE 0x0010U
+#define BSQL_SF_FILTER_ENTRYUUID 0x0020U
+#define BSQL_SF_FILTER_ENTRYCSN 0x0040U
+#define BSQL_SF_RETURN_ENTRYUUID (BSQL_SF_FILTER_ENTRYUUID << 8)
+#define BSQL_ISF(bsi, f) ( ( (bsi)->bsi_flags & f ) == f )
+#define BSQL_ISF_ALL_USER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_USER)
+#define BSQL_ISF_ALL_OPER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_OPER)
+#define BSQL_ISF_ALL_ATTRS(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_ATTRS)
+
+ struct berval *bsi_base_ndn;
+ int bsi_use_subtree_shortcut;
+ backsql_entryID bsi_base_id;
+ int bsi_scope;
+/* BACKSQL_SCOPE_BASE_LIKE can be set by API in ors_scope
+ * whenever the search base DN contains chars that cannot
+ * be mapped into the charset used in the RDBMS; so they're
+ * turned into '%' and an approximate ('LIKE') condition
+ * is used */
+#define BACKSQL_SCOPE_BASE_LIKE ( LDAP_SCOPE_BASE | 0x1000 )
+ Filter *bsi_filter;
+ time_t bsi_stoptime;
+
+ backsql_entryID *bsi_id_list,
+ **bsi_id_listtail,
+ *bsi_c_eid;
+ int bsi_n_candidates;
+ int bsi_status;
+
+ backsql_oc_map_rec *bsi_oc;
+ struct berbuf bsi_sel,
+ bsi_from,
+ bsi_join_where,
+ bsi_flt_where;
+ ObjectClass *bsi_filter_oc;
+ SQLHDBC bsi_dbh;
+ AttributeName *bsi_attrs;
+
+ Entry *bsi_e;
+} backsql_srch_info;
+
+/*
+ * Backend private data structure
+ */
+typedef struct backsql_info {
+ char *sql_dbhost;
+ int sql_dbport;
+ char *sql_dbuser;
+ char *sql_dbpasswd;
+ char *sql_dbname;
+
+ /*
+ * SQL condition for subtree searches differs in syntax:
+ * "LIKE CONCAT('%',?)" or "LIKE '%'+?" or "LIKE '%'||?"
+ * or smtg else
+ */
+ struct berval sql_subtree_cond;
+ struct berval sql_children_cond;
+ struct berval sql_dn_match_cond;
+ char *sql_oc_query;
+ char *sql_at_query;
+ char *sql_insentry_stmt;
+ char *sql_delentry_stmt;
+ char *sql_renentry_stmt;
+ char *sql_delobjclasses_stmt;
+ char *sql_id_query;
+ char *sql_has_children_query;
+ char *sql_list_children_query;
+
+ MatchingRule *sql_caseIgnoreMatch;
+ MatchingRule *sql_telephoneNumberMatch;
+
+ struct berval sql_upper_func;
+ struct berval sql_upper_func_open;
+ struct berval sql_upper_func_close;
+ struct berval sql_strcast_func;
+ BerVarray sql_concat_func;
+ char *sql_concat_patt;
+
+ struct berval sql_aliasing;
+ struct berval sql_aliasing_quote;
+ struct berval sql_dn_oc_aliasing;
+
+ AttributeName *sql_anlist;
+
+ unsigned int sql_flags;
+#define BSQLF_SCHEMA_LOADED 0x0001
+#define BSQLF_UPPER_NEEDS_CAST 0x0002
+#define BSQLF_CREATE_NEEDS_SELECT 0x0004
+#define BSQLF_FAIL_IF_NO_MAPPING 0x0008
+#define BSQLF_HAS_LDAPINFO_DN_RU 0x0010
+#define BSQLF_DONTCHECK_LDAPINFO_DN_RU 0x0020
+#define BSQLF_USE_REVERSE_DN 0x0040
+#define BSQLF_ALLOW_ORPHANS 0x0080
+#define BSQLF_USE_SUBTREE_SHORTCUT 0x0100
+#define BSQLF_FETCH_ALL_USERATTRS 0x0200
+#define BSQLF_FETCH_ALL_OPATTRS 0x0400
+#define BSQLF_FETCH_ALL_ATTRS (BSQLF_FETCH_ALL_USERATTRS|BSQLF_FETCH_ALL_OPATTRS)
+#define BSQLF_CHECK_SCHEMA 0x0800
+#define BSQLF_AUTOCOMMIT_ON 0x1000
+
+#define BACKSQL_ISF(si, f) \
+ (((si)->sql_flags & f) == f)
+
+#define BACKSQL_SCHEMA_LOADED(si) \
+ BACKSQL_ISF(si, BSQLF_SCHEMA_LOADED)
+#define BACKSQL_UPPER_NEEDS_CAST(si) \
+ BACKSQL_ISF(si, BSQLF_UPPER_NEEDS_CAST)
+#define BACKSQL_CREATE_NEEDS_SELECT(si) \
+ BACKSQL_ISF(si, BSQLF_CREATE_NEEDS_SELECT)
+#define BACKSQL_FAIL_IF_NO_MAPPING(si) \
+ BACKSQL_ISF(si, BSQLF_FAIL_IF_NO_MAPPING)
+#define BACKSQL_HAS_LDAPINFO_DN_RU(si) \
+ BACKSQL_ISF(si, BSQLF_HAS_LDAPINFO_DN_RU)
+#define BACKSQL_DONTCHECK_LDAPINFO_DN_RU(si) \
+ BACKSQL_ISF(si, BSQLF_DONTCHECK_LDAPINFO_DN_RU)
+#define BACKSQL_USE_REVERSE_DN(si) \
+ BACKSQL_ISF(si, BSQLF_USE_REVERSE_DN)
+#define BACKSQL_CANUPPERCASE(si) \
+ (!BER_BVISNULL( &(si)->sql_upper_func ))
+#define BACKSQL_ALLOW_ORPHANS(si) \
+ BACKSQL_ISF(si, BSQLF_ALLOW_ORPHANS)
+#define BACKSQL_USE_SUBTREE_SHORTCUT(si) \
+ BACKSQL_ISF(si, BSQLF_USE_SUBTREE_SHORTCUT)
+#define BACKSQL_FETCH_ALL_USERATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_USERATTRS)
+#define BACKSQL_FETCH_ALL_OPATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_OPATTRS)
+#define BACKSQL_FETCH_ALL_ATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_ATTRS)
+#define BACKSQL_CHECK_SCHEMA(si) \
+ BACKSQL_ISF(si, BSQLF_CHECK_SCHEMA)
+#define BACKSQL_AUTOCOMMIT_ON(si) \
+ BACKSQL_ISF(si, BSQLF_AUTOCOMMIT_ON)
+
+ Entry *sql_baseObject;
+ char *sql_base_ob_file;
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_BASEOBJECT_IDSTR "baseObject"
+#define BACKSQL_BASEOBJECT_KEYVAL BACKSQL_BASEOBJECT_IDSTR
+#define BACKSQL_IS_BASEOBJECT_ID(id) (bvmatch((id), &backsql_baseObject_bv))
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_BASEOBJECT_ID 0
+#define BACKSQL_BASEOBJECT_IDSTR LDAP_XSTRING(BACKSQL_BASEOBJECT_ID)
+#define BACKSQL_BASEOBJECT_KEYVAL 0
+#define BACKSQL_IS_BASEOBJECT_ID(id) (*(id) == BACKSQL_BASEOBJECT_ID)
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_BASEOBJECT_OC 0
+
+ Avlnode *sql_db_conns;
+ SQLHDBC sql_dbh;
+ ldap_pvt_thread_mutex_t sql_dbconn_mutex;
+ Avlnode *sql_oc_by_oc;
+ Avlnode *sql_oc_by_id;
+ ldap_pvt_thread_mutex_t sql_schema_mutex;
+ SQLHENV sql_db_env;
+
+ backsql_api *sql_api;
+} backsql_info;
+
+#define BACKSQL_SUCCESS( rc ) \
+ ( (rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO )
+
+#define BACKSQL_AVL_STOP 0
+#define BACKSQL_AVL_CONTINUE 1
+
+/* see ldap.h for the meaning of the macros and of the values */
+#define BACKSQL_LEGAL_ERROR( rc ) \
+ ( LDAP_RANGE( (rc), 0x00, 0x0e ) \
+ || LDAP_ATTR_ERROR( (rc) ) \
+ || LDAP_NAME_ERROR( (rc) ) \
+ || LDAP_SECURITY_ERROR( (rc) ) \
+ || LDAP_SERVICE_ERROR( (rc) ) \
+ || LDAP_UPDATE_ERROR( (rc) ) )
+#define BACKSQL_SANITIZE_ERROR( rc ) \
+ ( BACKSQL_LEGAL_ERROR( (rc) ) ? (rc) : LDAP_OTHER )
+
+#define BACKSQL_IS_BINARY(ct) \
+ ( (ct) == SQL_BINARY \
+ || (ct) == SQL_VARBINARY \
+ || (ct) == SQL_LONGVARBINARY)
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_IDFMT "%s"
+#define BACKSQL_IDARG(arg) ((arg).bv_val)
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_IDFMT BACKSQL_IDNUMFMT
+#define BACKSQL_IDARG(arg) (arg)
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+#endif /* __BACKSQL_H__ */
+
diff --git a/servers/slapd/back-sql/bind.c b/servers/slapd/back-sql/bind.c
new file mode 100644
index 0000000..ac78fc8
--- /dev/null
+++ b/servers/slapd/back-sql/bind.c
@@ -0,0 +1,115 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_bind( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ Entry e = { 0 };
+ Attribute *a;
+ backsql_srch_info bsi = { 0 };
+ AttributeName anlist[2];
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_bind()\n" );
+
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* in case of success, front end will send result;
+ * otherwise, be_rootdn_bind() did */
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_bind(%d)\n",
+ rs->sr_err );
+ return rs->sr_err;
+ }
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_bind(): "
+ "could not get connection handle - exiting\n" );
+
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto error_return;
+ }
+
+ anlist[0].an_name = slap_schema.si_ad_userPassword->ad_cname;
+ anlist[0].an_desc = slap_schema.si_ad_userPassword;
+ anlist[1].an_name.bv_val = NULL;
+
+ bsi.bsi_e = &e;
+ rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, anlist,
+ BACKSQL_ISF_GET_ENTRY );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_bind(): "
+ "could not retrieve bindDN ID - no such entry\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+ a = attr_find( e.e_attrs, slap_schema.si_ad_userPassword );
+ if ( a == NULL ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+ if ( slap_passwd_check( op, &e, a, &op->oq_bind.rb_cred,
+ &rs->sr_text ) != 0 )
+ {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+error_return:;
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &e.e_nname ) ) {
+ backsql_entry_clean( op, &e );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
+ Debug( LDAP_DEBUG_TRACE,"<==backsql_bind()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/compare.c b/servers/slapd/back-sql/compare.c
new file mode 100644
index 0000000..d457085
--- /dev/null
+++ b/servers/slapd/back-sql/compare.c
@@ -0,0 +1,194 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_compare( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ Entry e = { 0 };
+ Attribute *a = NULL;
+ backsql_srch_info bsi = { 0 };
+ int rc;
+ int manageDSAit = get_manageDSAit( op );
+ AttributeName anlist[2];
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_compare()\n" );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_compare(): "
+ "could not get connection handle - exiting\n" );
+
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto return_results;
+ }
+
+ anlist[ 0 ].an_name = op->oq_compare.rs_ava->aa_desc->ad_cname;
+ anlist[ 0 ].an_desc = op->oq_compare.rs_ava->aa_desc;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+
+ /*
+ * Get the entry
+ */
+ bsi.bsi_e = &e;
+ rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, anlist,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_compare(): "
+ "could not retrieve compareDN ID - no such entry\n" );
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &e, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ if ( is_at_operational( op->oq_compare.rs_ava->aa_desc->ad_type ) ) {
+ SlapReply nrs = { REP_SEARCH };
+ Attribute **ap;
+
+ for ( ap = &e.e_attrs; *ap; ap = &(*ap)->a_next )
+ ;
+
+ nrs.sr_attrs = anlist;
+ nrs.sr_entry = &e;
+ nrs.sr_attr_flags = SLAP_OPATTRS_NO;
+ nrs.sr_operational_attrs = NULL;
+
+ rs->sr_err = backsql_operational( op, &nrs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ *ap = nrs.sr_operational_attrs;
+ }
+
+ if ( ! access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc,
+ &op->oq_compare.rs_ava->aa_value,
+ ACL_COMPARE, NULL ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc );
+ a != NULL;
+ a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) )
+ {
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->oq_compare.rs_ava->aa_value, NULL,
+ op->o_tmpmemctx ) == 0 )
+ {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+
+return_results:;
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ break;
+
+ default:
+ if ( !BER_BVISNULL( &e.e_nname ) &&
+ ! access_allowed( op, &e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ }
+ break;
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_matched ) {
+ rs->sr_matched = NULL;
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &e.e_nname ) ) {
+ backsql_entry_clean( op, &e );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_compare()\n" );
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ return LDAP_SUCCESS;
+
+ default:
+ return rs->sr_err;
+ }
+}
+
diff --git a/servers/slapd/back-sql/config.c b/servers/slapd/back-sql/config.c
new file mode 100644
index 0000000..51a6be3
--- /dev/null
+++ b/servers/slapd/back-sql/config.c
@@ -0,0 +1,778 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+#include <sys/types.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "ldif.h"
+#include "lutil.h"
+#include "proto-sql.h"
+
+static int
+create_baseObject(
+ BackendDB *be,
+ const char *fname,
+ int lineno );
+
+static int
+read_baseObject(
+ BackendDB *be,
+ const char *fname );
+
+static ConfigDriver sql_cf_gen;
+
+enum {
+ BSQL_CONCAT_PATT = 1,
+ BSQL_CREATE_NEEDS_SEL,
+ BSQL_UPPER_NEEDS_CAST,
+ BSQL_HAS_LDAPINFO_DN_RU,
+ BSQL_FAIL_IF_NO_MAPPING,
+ BSQL_ALLOW_ORPHANS,
+ BSQL_BASE_OBJECT,
+ BSQL_LAYER,
+ BSQL_SUBTREE_SHORTCUT,
+ BSQL_FETCH_ALL_ATTRS,
+ BSQL_FETCH_ATTRS,
+ BSQL_CHECK_SCHEMA,
+ BSQL_ALIASING_KEYWORD,
+ BSQL_AUTOCOMMIT
+};
+
+static ConfigTable sqlcfg[] = {
+ { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbhost),
+ "( OLcfgDbAt:6.1 NAME 'olcDbHost' "
+ "DESC 'Hostname of SQL server' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbname),
+ "( OLcfgDbAt:6.2 NAME 'olcDbName' "
+ "DESC 'Name of SQL database' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbuser),
+ "( OLcfgDbAt:6.3 NAME 'olcDbUser' "
+ "DESC 'Username for SQL session' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbpasswd", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbpasswd),
+ "( OLcfgDbAt:6.4 NAME 'olcDbPass' "
+ "DESC 'Password for SQL session' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "concat_pattern", "pattern", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_CONCAT_PATT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.20 NAME 'olcSqlConcatPattern' "
+ "DESC 'Pattern used to concatenate strings' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "subtree_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_subtree_cond),
+ "( OLcfgDbAt:6.21 NAME 'olcSqlSubtreeCond' "
+ "DESC 'Where-clause template for a subtree search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "children_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_children_cond),
+ "( OLcfgDbAt:6.22 NAME 'olcSqlChildrenCond' "
+ "DESC 'Where-clause template for a children search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dn_match_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dn_match_cond),
+ "( OLcfgDbAt:6.23 NAME 'olcSqlDnMatchCond' "
+ "DESC 'Where-clause template for a DN match search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "oc_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_oc_query),
+ "( OLcfgDbAt:6.24 NAME 'olcSqlOcQuery' "
+ "DESC 'Query used to collect objectClass mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "at_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_at_query),
+ "( OLcfgDbAt:6.25 NAME 'olcSqlAtQuery' "
+ "DESC 'Query used to collect attributeType mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "insentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_insentry_stmt),
+ "( OLcfgDbAt:6.26 NAME 'olcSqlInsEntryStmt' "
+ "DESC 'Statement used to insert a new entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "create_needs_select", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_CREATE_NEEDS_SEL, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.27 NAME 'olcSqlCreateNeedsSelect' "
+ "DESC 'Whether entry creation needs a subsequent select' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "upper_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_upper_func),
+ "( OLcfgDbAt:6.28 NAME 'olcSqlUpperFunc' "
+ "DESC 'Function that converts a value to uppercase' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "upper_needs_cast", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_UPPER_NEEDS_CAST, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.29 NAME 'olcSqlUpperNeedsCast' "
+ "DESC 'Whether olcSqlUpperFunc needs an explicit cast' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "strcast_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_strcast_func),
+ "( OLcfgDbAt:6.30 NAME 'olcSqlStrcastFunc' "
+ "DESC 'Function that converts a value to a string' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "delentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_delentry_stmt),
+ "( OLcfgDbAt:6.31 NAME 'olcSqlDelEntryStmt' "
+ "DESC 'Statement used to delete an existing entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "renentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_renentry_stmt),
+ "( OLcfgDbAt:6.32 NAME 'olcSqlRenEntryStmt' "
+ "DESC 'Statement used to rename an entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "delobjclasses_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_delobjclasses_stmt),
+ "( OLcfgDbAt:6.33 NAME 'olcSqlDelObjclassesStmt' "
+ "DESC 'Statement used to delete the ID of an entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "has_ldapinfo_dn_ru", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_HAS_LDAPINFO_DN_RU, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.34 NAME 'olcSqlHasLDAPinfoDnRu' "
+ "DESC 'Whether the dn_ru column is present' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fail_if_no_mapping", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_FAIL_IF_NO_MAPPING, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.35 NAME 'olcSqlFailIfNoMapping' "
+ "DESC 'Whether to fail on unknown attribute mappings' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "allow_orphans", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_ALLOW_ORPHANS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.36 NAME 'olcSqlAllowOrphans' "
+ "DESC 'Whether to allow adding entries with no parent' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "baseobject", "[file]", 1, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_BASE_OBJECT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.37 NAME 'olcSqlBaseObject' "
+ "DESC 'Manage an in-memory baseObject entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sqllayer", "name", 2, 0, 0,
+ ARG_MAGIC|BSQL_LAYER, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.38 NAME 'olcSqlLayer' "
+ "DESC 'Helper used to map DNs between LDAP and SQL' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "use_subtree_shortcut", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_SUBTREE_SHORTCUT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.39 NAME 'olcSqlUseSubtreeShortcut' "
+ "DESC 'Collect all entries when searchBase is DB suffix' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fetch_all_attrs", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_FETCH_ALL_ATTRS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.40 NAME 'olcSqlFetchAllAttrs' "
+ "DESC 'Require all attributes to always be loaded' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fetch_attrs", "attrlist", 2, 0, 0,
+ ARG_MAGIC|BSQL_FETCH_ATTRS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.41 NAME 'olcSqlFetchAttrs' "
+ "DESC 'Set of attributes to always fetch' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "check_schema", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_CHECK_SCHEMA, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.42 NAME 'olcSqlCheckSchema' "
+ "DESC 'Check schema after modifications' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "aliasing_keyword", "string", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_ALIASING_KEYWORD, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.43 NAME 'olcSqlAliasingKeyword' "
+ "DESC 'The aliasing keyword' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "aliasing_quote", "string", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_aliasing_quote),
+ "( OLcfgDbAt:6.44 NAME 'olcSqlAliasingQuote' "
+ "DESC 'Quoting char of the aliasing keyword' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "autocommit", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_AUTOCOMMIT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.45 NAME 'olcSqlAutocommit' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "id_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_id_query),
+ "( OLcfgDbAt:6.46 NAME 'olcSqlIdQuery' "
+ "DESC 'Query used to collect entryID mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs sqlocs[] = {
+ {
+ "( OLcfgDbOc:6.1 "
+ "NAME 'olcSqlConfig' "
+ "DESC 'SQL backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST olcDbName "
+ "MAY ( olcDbHost $ olcDbUser $ olcDbPass $ olcSqlConcatPattern $ "
+ "olcSqlSubtreeCond $ olcsqlChildrenCond $ olcSqlDnMatchCond $ "
+ "olcSqlOcQuery $ olcSqlAtQuery $ olcSqlInsEntryStmt $ "
+ "olcSqlCreateNeedsSelect $ olcSqlUpperFunc $ olcSqlUpperNeedsCast $ "
+ "olcSqlStrCastFunc $ olcSqlDelEntryStmt $ olcSqlRenEntryStmt $ "
+ "olcSqlDelObjClassesStmt $ olcSqlHasLDAPInfoDnRu $ "
+ "olcSqlFailIfNoMapping $ olcSqlAllowOrphans $ olcSqlBaseObject $ "
+ "olcSqlLayer $ olcSqlUseSubtreeShortcut $ olcSqlFetchAllAttrs $ "
+ "olcSqlFetchAttrs $ olcSqlCheckSchema $ olcSqlAliasingKeyword $ "
+ "olcSqlAliasingQuote $ olcSqlAutocommit $ olcSqlIdQuery ) )",
+ Cft_Database, sqlcfg },
+ { NULL, Cft_Abstract, NULL }
+};
+
+static int
+sql_cf_gen( ConfigArgs *c )
+{
+ backsql_info *bi = (backsql_info *)c->be->be_private;
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case BSQL_CONCAT_PATT:
+ if ( bi->sql_concat_patt ) {
+ c->value_string = ch_strdup( bi->sql_concat_patt );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_CREATE_NEEDS_SEL:
+ if ( bi->sql_flags & BSQLF_CREATE_NEEDS_SELECT )
+ c->value_int = 1;
+ break;
+ case BSQL_UPPER_NEEDS_CAST:
+ if ( bi->sql_flags & BSQLF_UPPER_NEEDS_CAST )
+ c->value_int = 1;
+ break;
+ case BSQL_HAS_LDAPINFO_DN_RU:
+ if ( !(bi->sql_flags & BSQLF_DONTCHECK_LDAPINFO_DN_RU) )
+ return 1;
+ if ( bi->sql_flags & BSQLF_HAS_LDAPINFO_DN_RU )
+ c->value_int = 1;
+ break;
+ case BSQL_FAIL_IF_NO_MAPPING:
+ if ( bi->sql_flags & BSQLF_FAIL_IF_NO_MAPPING )
+ c->value_int = 1;
+ break;
+ case BSQL_ALLOW_ORPHANS:
+ if ( bi->sql_flags & BSQLF_ALLOW_ORPHANS )
+ c->value_int = 1;
+ break;
+ case BSQL_SUBTREE_SHORTCUT:
+ if ( bi->sql_flags & BSQLF_USE_SUBTREE_SHORTCUT )
+ c->value_int = 1;
+ break;
+ case BSQL_FETCH_ALL_ATTRS:
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_ATTRS )
+ c->value_int = 1;
+ break;
+ case BSQL_CHECK_SCHEMA:
+ if ( bi->sql_flags & BSQLF_CHECK_SCHEMA )
+ c->value_int = 1;
+ break;
+ case BSQL_AUTOCOMMIT:
+ if ( bi->sql_flags & BSQLF_AUTOCOMMIT_ON )
+ c->value_int = 1;
+ break;
+ case BSQL_BASE_OBJECT:
+ if ( bi->sql_base_ob_file ) {
+ c->value_string = ch_strdup( bi->sql_base_ob_file );
+ } else if ( bi->sql_baseObject ) {
+ c->value_string = ch_strdup( "TRUE" );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_LAYER:
+ if ( bi->sql_api ) {
+ backsql_api *ba;
+ struct berval bv;
+ char *ptr;
+ int i;
+ for ( ba = bi->sql_api; ba; ba = ba->ba_next ) {
+ bv.bv_len = strlen( ba->ba_name );
+ if ( ba->ba_argc ) {
+ for ( i = 0; i<ba->ba_argc; i++ )
+ bv.bv_len += strlen( ba->ba_argv[i] ) + 3;
+ }
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( bv.bv_val, ba->ba_name );
+ if ( ba->ba_argc ) {
+ for ( i = 0; i<ba->ba_argc; i++ ) {
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, ba->ba_argv[i] );
+ *ptr++ = '"';
+ }
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_ALIASING_KEYWORD:
+ if ( !BER_BVISNULL( &bi->sql_aliasing )) {
+ struct berval bv;
+ bv = bi->sql_aliasing;
+ bv.bv_len--;
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_FETCH_ATTRS:
+ if ( bi->sql_anlist ||
+ ( bi->sql_flags & (BSQLF_FETCH_ALL_USERATTRS|
+ BSQLF_FETCH_ALL_OPATTRS)))
+ {
+ char buf[BUFSIZ*2], *ptr;
+ struct berval bv;
+# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr))
+ ptr = buf;
+ if ( bi->sql_anlist ) {
+ ptr = anlist_unparse( bi->sql_anlist, ptr, WHATSLEFT );
+ if ( ptr == NULL )
+ return 1;
+ }
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_USERATTRS ) {
+ if ( WHATSLEFT <= STRLENOF( ",*" )) return 1;
+ if ( ptr != buf ) *ptr++ = ',';
+ *ptr++ = '*';
+ }
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_OPATTRS ) {
+ if ( WHATSLEFT <= STRLENOF( ",+" )) return 1;
+ if ( ptr != buf ) *ptr++ = ',';
+ *ptr++ = '+';
+ }
+ bv.bv_val = buf;
+ bv.bv_len = ptr - buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */
+ return -1;
+ }
+
+ switch( c->type ) {
+ case BSQL_CONCAT_PATT:
+ if ( backsql_split_pattern( c->argv[ 1 ], &bi->sql_concat_func, 2 ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: unable to parse pattern \"%s\"",
+ c->log, c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ return -1;
+ }
+ bi->sql_concat_patt = c->value_string;
+ break;
+ case BSQL_CREATE_NEEDS_SEL:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_CREATE_NEEDS_SELECT;
+ else
+ bi->sql_flags &= ~BSQLF_CREATE_NEEDS_SELECT;
+ break;
+ case BSQL_UPPER_NEEDS_CAST:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_UPPER_NEEDS_CAST;
+ else
+ bi->sql_flags &= ~BSQLF_UPPER_NEEDS_CAST;
+ break;
+ case BSQL_HAS_LDAPINFO_DN_RU:
+ bi->sql_flags |= BSQLF_DONTCHECK_LDAPINFO_DN_RU;
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU;
+ else
+ bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU;
+ break;
+ case BSQL_FAIL_IF_NO_MAPPING:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_FAIL_IF_NO_MAPPING;
+ else
+ bi->sql_flags &= ~BSQLF_FAIL_IF_NO_MAPPING;
+ break;
+ case BSQL_ALLOW_ORPHANS:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_ALLOW_ORPHANS;
+ else
+ bi->sql_flags &= ~BSQLF_ALLOW_ORPHANS;
+ break;
+ case BSQL_SUBTREE_SHORTCUT:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT;
+ else
+ bi->sql_flags &= ~BSQLF_USE_SUBTREE_SHORTCUT;
+ break;
+ case BSQL_FETCH_ALL_ATTRS:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_FETCH_ALL_ATTRS;
+ else
+ bi->sql_flags &= ~BSQLF_FETCH_ALL_ATTRS;
+ break;
+ case BSQL_CHECK_SCHEMA:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_CHECK_SCHEMA;
+ else
+ bi->sql_flags &= ~BSQLF_CHECK_SCHEMA;
+ break;
+ case BSQL_AUTOCOMMIT:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_AUTOCOMMIT_ON;
+ else
+ bi->sql_flags &= ~BSQLF_AUTOCOMMIT_ON;
+ break;
+ case BSQL_BASE_OBJECT:
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: suffix must be set", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( bi->sql_baseObject ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: "
+ "\"baseObject\" already provided (will be overwritten)\n",
+ c->log );
+ entry_free( bi->sql_baseObject );
+ }
+ if ( c->argc == 2 && !strcmp( c->argv[1], "TRUE" ))
+ c->argc = 1;
+ switch( c->argc ) {
+ case 1:
+ return create_baseObject( c->be, c->fname, c->lineno );
+
+ case 2:
+ rc = read_baseObject( c->be, c->argv[ 1 ] );
+ if ( rc == 0 ) {
+ ch_free( bi->sql_base_ob_file );
+ bi->sql_base_ob_file = c->value_string;
+ }
+ return rc;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: trailing values in directive", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ return 1;
+ }
+ break;
+ case BSQL_LAYER:
+ if ( backsql_api_config( bi, c->argv[ 1 ], c->argc - 2, &c->argv[ 2 ] ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: unable to load sql layer", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s \"%s\"\n",
+ c->cr_msg, c->argv[1] );
+ return 1;
+ }
+ break;
+ case BSQL_ALIASING_KEYWORD:
+ if ( ! BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ch_free( bi->sql_aliasing.bv_val );
+ }
+
+ ber_str2bv( c->argv[ 1 ], strlen( c->argv[ 1 ] ) + 1, 1,
+ &bi->sql_aliasing );
+ /* add a trailing space... */
+ bi->sql_aliasing.bv_val[ bi->sql_aliasing.bv_len - 1] = ' ';
+ break;
+ case BSQL_FETCH_ATTRS: {
+ char *str, *s, *next;
+ const char *delimstr = ",";
+
+ str = ch_strdup( c->argv[ 1 ] );
+ for ( s = ldap_pvt_strtok( str, delimstr, &next );
+ s != NULL;
+ s = ldap_pvt_strtok( NULL, delimstr, &next ) )
+ {
+ if ( strlen( s ) == 1 ) {
+ if ( *s == '*' ) {
+ bi->sql_flags |= BSQLF_FETCH_ALL_USERATTRS;
+ c->argv[ 1 ][ s - str ] = ',';
+
+ } else if ( *s == '+' ) {
+ bi->sql_flags |= BSQLF_FETCH_ALL_OPATTRS;
+ c->argv[ 1 ][ s - str ] = ',';
+ }
+ }
+ }
+ ch_free( str );
+ bi->sql_anlist = str2anlist( bi->sql_anlist, c->argv[ 1 ], delimstr );
+ if ( bi->sql_anlist == NULL ) {
+ return -1;
+ }
+ }
+ break;
+ }
+ return rc;
+}
+
+/*
+ * Read the entries specified in fname and merge the attributes
+ * to the user defined baseObject entry. Note that if we find any errors
+ * what so ever, we will discard the entire entries, print an
+ * error message and return.
+ */
+static int
+read_baseObject(
+ BackendDB *be,
+ const char *fname )
+{
+ backsql_info *bi = (backsql_info *)be->be_private;
+ LDIFFP *fp;
+ int rc = 0, lmax = 0, ldifrc;
+ unsigned long lineno = 0;
+ char *buf = NULL;
+
+ assert( fname != NULL );
+
+ fp = ldif_open( fname, "r" );
+ if ( fp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "could not open back-sql baseObject "
+ "attr file \"%s\" - absolute path?\n",
+ fname );
+ perror( fname );
+ return LDAP_OTHER;
+ }
+
+ bi->sql_baseObject = entry_alloc();
+ if ( bi->sql_baseObject == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "read_baseObject_file: entry_alloc failed" );
+ ldif_close( fp );
+ return LDAP_NO_MEMORY;
+ }
+ bi->sql_baseObject->e_name = be->be_suffix[0];
+ bi->sql_baseObject->e_nname = be->be_nsuffix[0];
+ bi->sql_baseObject->e_attrs = NULL;
+
+ while (( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) {
+ Entry *e = str2entry( buf );
+ Attribute *a;
+
+ if( e == NULL ) {
+ fprintf( stderr, "back-sql baseObject: "
+ "could not parse entry (line=%lu)\n",
+ lineno );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /* make sure the DN is the database's suffix */
+ if ( !be_issuffix( be, &e->e_nname ) ) {
+ fprintf( stderr,
+ "back-sql: invalid baseObject - "
+ "dn=\"%s\" (line=%lu)\n",
+ e->e_name.bv_val, lineno );
+ entry_free( e );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /*
+ * we found a valid entry, so walk thru all the attributes in the
+ * entry, and add each attribute type and description to baseObject
+ */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if ( attr_merge( bi->sql_baseObject, a->a_desc,
+ a->a_vals,
+ ( a->a_nvals == a->a_vals ) ?
+ NULL : a->a_nvals ) )
+ {
+ rc = LDAP_OTHER;
+ break;
+ }
+ }
+
+ entry_free( e );
+ if ( rc ) {
+ break;
+ }
+ }
+
+ if ( ldifrc < 0 )
+ rc = LDAP_OTHER;
+
+ if ( rc ) {
+ entry_free( bi->sql_baseObject );
+ bi->sql_baseObject = NULL;
+ }
+
+ ch_free( buf );
+
+ ldif_close( fp );
+
+ Debug( LDAP_DEBUG_CONFIG, "back-sql baseObject file \"%s\" read.\n",
+ fname );
+
+ return rc;
+}
+
+static int
+create_baseObject(
+ BackendDB *be,
+ const char *fname,
+ int lineno )
+{
+ backsql_info *bi = (backsql_info *)be->be_private;
+ LDAPRDN rdn;
+ char *p;
+ int rc, iAVA;
+ char buf[1024];
+
+ snprintf( buf, sizeof(buf),
+ "dn: %s\n"
+ "objectClass: extensibleObject\n"
+ "description: builtin baseObject for back-sql\n"
+ "description: all entries mapped "
+ "in table \"ldap_entries\" "
+ "must have "
+ "\"" BACKSQL_BASEOBJECT_IDSTR "\" "
+ "in the \"parent\" column",
+ be->be_suffix[0].bv_val );
+
+ bi->sql_baseObject = str2entry( buf );
+ if ( bi->sql_baseObject == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): "
+ "unable to parse baseObject entry\n",
+ fname, lineno );
+ return 1;
+ }
+
+ if ( BER_BVISEMPTY( &be->be_suffix[ 0 ] ) ) {
+ return 0;
+ }
+
+ rc = ldap_bv2rdn( &be->be_suffix[ 0 ], &rdn, (char **)&p,
+ LDAP_DN_FORMAT_LDAP );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): unable to extract RDN " "from baseObject DN \"%s\" (%d: %s)\n",
+ fname, lineno, be->be_suffix[0].bv_val, rc,
+ ldap_err2string(rc) );
+ return 1;
+ }
+
+ for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
+ LDAPAVA *ava = rdn[ iAVA ];
+ AttributeDescription *ad = NULL;
+ slap_syntax_transform_func *transf = NULL;
+ struct berval bv = BER_BVNULL;
+ const char *text = NULL;
+
+ assert( ava != NULL );
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): AttributeDescription of naming " "attribute #%d from baseObject " "DN \"%s\": %d: %s\n",
+ fname, lineno, iAVA, be->be_suffix[0].bv_val,
+ rc, ldap_err2string(rc) );
+ return 1;
+ }
+
+ transf = ad->ad_type->sat_syntax->ssyn_pretty;
+ if ( transf ) {
+ /*
+ * transform value by pretty function
+ * if value is empty, use empty_bv
+ */
+ rc = ( *transf )( ad->ad_type->sat_syntax,
+ ava->la_value.bv_len
+ ? &ava->la_value
+ : (struct berval *) &slap_empty_bv,
+ &bv, NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): " "prettying of attribute #%d " "from baseObject " "DN \"%s\" failed: %d: %s\n",
+ fname, lineno, iAVA,
+ be->be_suffix[0].bv_val, rc,
+ ldap_err2string(rc) );
+ return 1;
+ }
+ }
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) {
+ ber_memfree( ava->la_value.bv_val );
+ }
+ ava->la_value = bv;
+ ava->la_flags |= LDAP_AVA_FREE_VALUE;
+ }
+
+ attr_merge_normalize_one( bi->sql_baseObject,
+ ad, &ava->la_value, NULL );
+ }
+
+ ldap_rdnfree( rdn );
+
+ return 0;
+}
+
+int backsql_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ bi->bi_cf_ocs = sqlocs;
+ rc = config_register_schema( sqlcfg, sqlocs );
+ if ( rc ) return rc;
+ return 0;
+}
diff --git a/servers/slapd/back-sql/delete.c b/servers/slapd/back-sql/delete.c
new file mode 100644
index 0000000..489d4d3
--- /dev/null
+++ b/servers/slapd/back-sql/delete.c
@@ -0,0 +1,627 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+typedef struct backsql_delete_attr_t {
+ Operation *op;
+ SlapReply *rs;
+ SQLHDBC dbh;
+ backsql_entryID *e_id;
+} backsql_delete_attr_t;
+
+static int
+backsql_delete_attr_f( void *v_at, void *v_bda )
+{
+ backsql_at_map_rec *at = (backsql_at_map_rec *)v_at;
+ backsql_delete_attr_t *bda = (backsql_delete_attr_t *)v_bda;
+ int rc;
+
+ rc = backsql_modify_delete_all_values( bda->op,
+ bda->rs, bda->dbh, bda->e_id, at );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return BACKSQL_AVL_STOP;
+ }
+
+ return BACKSQL_AVL_CONTINUE;
+}
+
+static int
+backsql_delete_all_attrs(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *eid )
+{
+ backsql_delete_attr_t bda;
+ int rc;
+
+ bda.op = op;
+ bda.rs = rs;
+ bda.dbh = dbh;
+ bda.e_id = eid;
+
+ rc = ldap_avl_apply( eid->eid_oc->bom_attrs, backsql_delete_attr_f, &bda,
+ BACKSQL_AVL_STOP, AVL_INORDER );
+ if ( rc == BACKSQL_AVL_STOP ) {
+ return rs->sr_err;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+backsql_delete_int(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ SQLHSTMT *sthp,
+ backsql_entryID *eid,
+ Entry **ep )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ int prc = LDAP_SUCCESS;
+ /* first parameter no */
+ SQLUSMALLINT pno = 0;
+
+ sth = *sthp;
+
+ /* ldap_avl_apply ... */
+ rs->sr_err = backsql_delete_all_attrs( op, rs, dbh, eid );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = backsql_Prepare( dbh, &sth, eid->eid_oc->bom_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( eid->eid_oc->bom_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding output parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+ }
+
+ rc = backsql_BindParamID( sth, pno + 1, SQL_PARAM_INPUT, &eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding keyval parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "delete_proc execution failed (rc=%d, prc=%d)\n",
+ rc, prc );
+
+
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* delete "auxiliary" objectClasses, if any... */
+ rc = backsql_Prepare( dbh, &sth, bi->sql_delobjclasses_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing ldap_entry_objclasses delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding auxiliary objectClasses "
+ "entry ID parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ switch ( rc ) {
+ case SQL_NO_DATA:
+ /* apparently there were no "auxiliary" objectClasses
+ * for this entry... */
+ case SQL_SUCCESS:
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "failed to delete record from ldap_entry_objclasses\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* delete entry... */
+ rc = backsql_Prepare( dbh, &sth, bi->sql_delentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing ldap_entries delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding entry ID parameter "
+ "for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "failed to delete record from ldap_entries\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = LDAP_SUCCESS;
+ *ep = NULL;
+
+done:;
+ *sthp = sth;
+
+ return rs->sr_err;
+}
+
+typedef struct backsql_tree_delete_t {
+ Operation *btd_op;
+ int btd_rc;
+ backsql_entryID *btd_eid;
+} backsql_tree_delete_t;
+
+static int
+backsql_tree_delete_search_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ backsql_tree_delete_t *btd;
+ backsql_entryID *eid;
+
+ btd = (backsql_tree_delete_t *)op->o_callback->sc_private;
+
+ if ( !access_allowed( btd->btd_op, rs->sr_entry,
+ slap_schema.si_ad_entry, NULL, ACL_WDEL, NULL )
+ || !access_allowed( btd->btd_op, rs->sr_entry,
+ slap_schema.si_ad_children, NULL, ACL_WDEL, NULL ) )
+ {
+ btd->btd_rc = LDAP_INSUFFICIENT_ACCESS;
+ return rs->sr_err = LDAP_UNAVAILABLE;
+ }
+
+ assert( rs->sr_entry != NULL );
+ assert( rs->sr_entry->e_private != NULL );
+
+ eid = (backsql_entryID *)rs->sr_entry->e_private;
+ assert( eid->eid_oc != NULL );
+ if ( eid->eid_oc == NULL || eid->eid_oc->bom_delete_proc == NULL ) {
+ btd->btd_rc = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err = LDAP_UNAVAILABLE;
+ }
+
+ eid = backsql_entryID_dup( eid, op->o_tmpmemctx );
+ eid->eid_next = btd->btd_eid;
+ btd->btd_eid = eid;
+ }
+
+ return 0;
+}
+
+static int
+backsql_tree_delete(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ SQLHSTMT *sthp )
+{
+ Operation op2 = *op;
+ slap_callback sc = { 0 };
+ SlapReply rs2 = { REP_RESULT };
+ backsql_tree_delete_t btd = { 0 };
+
+ int rc;
+
+ /*
+ * - perform an internal subtree search as the rootdn
+ * - for each entry
+ * - check access
+ * - check objectClass and delete method(s)
+ * - for each entry
+ * - delete
+ * - if successful, commit
+ */
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_protocol = LDAP_VERSION3;
+
+ btd.btd_op = op;
+ sc.sc_private = &btd;
+ sc.sc_response = backsql_tree_delete_search_cb;
+ op2.o_callback = &sc;
+
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op2.ors_scope = LDAP_SCOPE_SUBTREE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
+ op2.ors_filterstr = *slap_filterstr_objectClass_pres;
+ op2.ors_attrs = slap_anlist_all_attributes;
+ op2.ors_attrsonly = 0;
+
+ rc = op->o_bd->be_search( &op2, &rs2 );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = rs->sr_err = btd.btd_rc;
+ rs->sr_text = "subtree delete not possible";
+ send_ldap_result( op, rs );
+ goto clean;
+ }
+
+ for ( ; btd.btd_eid != NULL;
+ btd.btd_eid = backsql_free_entryID( btd.btd_eid,
+ 1, op->o_tmpmemctx ) )
+ {
+ Entry *e = (void *)0xbad;
+ rc = backsql_delete_int( op, rs, dbh, sthp, btd.btd_eid, &e );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ }
+
+clean:;
+ for ( ; btd.btd_eid != NULL;
+ btd.btd_eid = backsql_free_entryID( btd.btd_eid,
+ 1, op->o_tmpmemctx ) )
+ ;
+
+ return rc;
+}
+
+int
+backsql_delete( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ backsql_entryID e_id = { 0 };
+ Entry d = { 0 }, p = { 0 }, *e = NULL;
+ struct berval pdn = BER_BVNULL;
+ int manageDSAit = get_manageDSAit( op );
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_delete(): deleting entry \"%s\"\n",
+ op->o_req_ndn.bv_val );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "could not get connection handle - exiting\n" );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Get the entry
+ */
+ bsi.bsi_e = &d;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+ e = &d;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
+ "could not retrieve deleteDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &d.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &d;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &d, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &d;
+ goto done;
+ }
+
+ if ( !access_allowed( op, &d, slap_schema.si_ad_entry,
+ NULL, ACL_WDEL, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "no write access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &d;
+ goto done;
+ }
+
+ rs->sr_err = backsql_has_children( op, dbh, &op->o_req_ndn );
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_FALSE:
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ if ( get_treeDelete( op ) ) {
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "entry \"%s\" has children\n",
+ op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subordinate objects must be deleted first";
+ /* fallthru */
+
+ default:
+ e = &d;
+ goto done;
+ }
+
+ assert( bsi.bsi_base_id.eid_oc != NULL );
+ oc = bsi.bsi_base_id.eid_oc;
+ if ( oc->bom_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "delete procedure is not defined "
+ "for this objectclass - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Get the parent
+ */
+ e_id = bsi.bsi_base_id;
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ dnParent( &op->o_req_ndn, &pdn );
+ bsi.bsi_e = &p;
+ rs->sr_err = backsql_init_search( &bsi, &pdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_no_attrs,
+ BACKSQL_ISF_GET_ENTRY );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
+ "could not retrieve deleteDN ID "
+ "- no such entry\n" );
+ e = &p;
+ goto done;
+ }
+
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ /* check parent for "children" acl */
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children,
+ NULL, ACL_WDEL, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "no write access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &p;
+ goto done;
+
+ }
+ }
+
+ e = &d;
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ if ( get_treeDelete( op ) ) {
+ backsql_tree_delete( op, rs, dbh, &sth );
+ if ( rs->sr_err == LDAP_OTHER || rs->sr_err == LDAP_SUCCESS )
+ {
+ e = NULL;
+ }
+
+ } else
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+ {
+ backsql_delete_int( op, rs, dbh, &sth, &e_id, &e );
+ }
+
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_delete()\n" );
+
+ if ( !BER_BVISNULL( &e_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &d.e_nname ) ) {
+ backsql_entry_clean( op, &d );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/docs/bugs b/servers/slapd/back-sql/docs/bugs
new file mode 100644
index 0000000..4b2f3c7
--- /dev/null
+++ b/servers/slapd/back-sql/docs/bugs
@@ -0,0 +1,16 @@
+1) driver name comparison for MS SQL Server workaround is really kinda dirty
+ hack, but for now i don't know how to code it more carefully
+2) another dirty hack: length of LONGVARCHAR and LONGVARBINARY fields is
+ currently set to MAX_ATTR_LEN. Maybe such fields must be handled with
+ SQLGetData() instead of SQLBindCol(), but it is said in documentation,
+ that it is guaranteed to work only when such column goes after last bound
+ column. Or should we get ALL columns with SQLGetData (then something like
+ _SQLFetchAsStrings() wrapper would do SQLGetData() for all columns)...
+3) in some cases (particularly, when using OpenLink Generic ODBC driver with
+ MS SQL Server), it returns "Function sequence error" after all records are
+ fetched. I really don't know what it means, and after all
+ - it works with any other driver I tried
+4) ldapsearch sometimes refuses to show some attributes ("NOT PRINTABLE" diags)
+ on Win32 (on linux everything's fine)
+5) back-sql crashes on invalid filters (to be fixed ASAP)
+ \ No newline at end of file
diff --git a/servers/slapd/back-sql/docs/concept b/servers/slapd/back-sql/docs/concept
new file mode 100644
index 0000000..ed29047
--- /dev/null
+++ b/servers/slapd/back-sql/docs/concept
@@ -0,0 +1 @@
+The SQL backend is described in the slapd-sql(5) manual page.
diff --git a/servers/slapd/back-sql/docs/install b/servers/slapd/back-sql/docs/install
new file mode 100644
index 0000000..230bf0a
--- /dev/null
+++ b/servers/slapd/back-sql/docs/install
@@ -0,0 +1,86 @@
+PLEASE READ THIS WHOLE FILE AND CONCEPT, BECAUSE THEY COVER SEVERAL STICKY
+ISSUES THAT YOU WILL PROBABLY STUMBLE ACROSS ANYWAY
+
+1. Build
+To build slapd with back-sql under Unix you need to build and install
+iODBC 2.50.3 (later versions should probably work, but not earlier),
+or unixODBC (you will have to change -liodbc to -lodbc then).
+Then, at top of OpenLDAP source tree, run
+"configure <other options you need> --enable-sql", then "make" -
+this should build back-sql-enabled slapd, provided that you have iODBC/unixODBC
+libraries and include files in include/library paths, "make install"...
+In other words, follow installation procedure described in OpenLDAP
+Administrators Guide, adding --enable-sql option to configure, and
+having iODBC/unixODBC libraries installed an accessible by compiler.
+
+Under Win32/MSVC++, I modified the workspace so that back-sql is built into
+slapd automatically, since MS ODBC manager, odbc32.dll, is included in
+standard library pack, and it does no bad even if you don't plan to use it.
+I also could provide precompiled executables for those who don't have MSVC.
+Note that Win32 port of OpenLDAP itself is experimental, and thus doesn't
+provide very convenient build environment (yet).
+
+2. Tune datasources and slapd.conf
+Next, you need to define ODBC datasource with data you want to publish
+with help of back-sql. Assuming that you have your data in some SQL-compliant
+RDBMS, and have installed proper ODBC driver for this RDBMS, this is as simple
+as adding a record into odbc.ini (for iODBC/unixODBC), or using ODBC wizard in
+Control Panel (for odbc32).
+Next, you need to add appropriate "database" record to your slapd.conf.
+See samples provided in "back-sql/RDBMS_DEPENDENT/" subdirectory.
+
+Several things worth noting about ODBC:
+- "dbname" directive stands for ODBC datasource name (DSN),
+ not the name of your database in RDBMS context
+- ODBC under Unix is not so common as under Windows, so you could have
+ problems with Unix drivers for your RDBMS. Visit http://www.openlinksw.com,
+ they provide a multitier solution which allows connecting to DBMSes on
+ different platforms, proxying and other connectivity and integration issues.
+ They also support iODBC, and have good free customer service through
+ newsserver (at news.openlinksw.com).
+ Also worth noting are: ODBC-ODBC bridge by EasySoft (which was claimed
+ by several people to be far more effective and stable than OpenLink),
+ OpenRDA package etc.
+- be careful defining RDBMS connection parameters, you'll probably need only
+ "dbname" directive - all the rest can be defined in datasource. Every other
+ directive is used to override value stored in datasource definition.
+ Maybe you will want to use dbuser/dbpasswd to override credentials defined in datasource
+- full list of configuration directives supported is available in file "guide",
+ you may also analyze output of 'slapd -d 5' to find out some useful
+ directives for redefining default queries
+
+3. Creating and using back-sql metatables
+Read the file "concept" to understand, what metainformation you need to add,
+and what for... ;)
+See SQL scripts and slapd.conf files in samples directory.
+Find subdirectory in "rdbms_depend/" corresponding to your RDBMS (Oracle,
+MS SQL Server and mySQL are listed there currently), or copy and edit
+any of these to conform to SQL dialect of your RDBMS (please be sure to send
+me scripts and notes for new RDBMSes ;).
+
+Execute "backsql_create.sql" from that subdirectory (or edited one),
+so that the tables it creates appear in the same
+context with the data you want to export through LDAP (under same DB/user,
+or whatever is needed in RDBMS you use). You can use something like
+"mysql < xxx.sql" for mySQL, Query Analyzer+Open query file for MS SQL,
+sqlplus and "@xxx.sql" for Oracle.
+
+You may well want to try it with test data first, and see how metatables
+are used. Create test data and metadata by running testdb_create.sql,
+testdb_data.sql, and testdb_metadata.sql scripts (again, adopted for your
+RDBMS, and in the same context as metatables you created before), and
+tune slapd.conf to use your test DB.
+
+4. Testing
+To diagnose back-sql, run slapd with debug level TRACE ("slapd -d 5" will go).
+Then, use some LDAP client to query corresponding subtree (for test database,
+you could for instance search one level from "o=sql,c=RU"). I personally used
+saucer, which is included in OpenLDAP package (it builds automatically under
+Unix/GNU configure and for MSVC I added appropriate project to workspace).
+And also Java LDAP browser-editor (see link somewhere on OpenLDAP site) to
+test ADD/DELETE/MODIFY operations on Oracle and MS SQL.
+
+See file "platforms" if you encounter connection problems - you may find
+a hint for your RDBMS or OS there. If you are stuck - please contact me at
+mit@openldap.org, or (better) post an issue through OpenLDAP's Issue Tracking
+System (see http:/www.openldap.org/its).
diff --git a/servers/slapd/back-sql/docs/platforms b/servers/slapd/back-sql/docs/platforms
new file mode 100644
index 0000000..65e326a
--- /dev/null
+++ b/servers/slapd/back-sql/docs/platforms
@@ -0,0 +1,8 @@
+Platforms and configurations it has been tested on:
+
+General:
+ - ODBC managers: iODBC,unixODBC under unixes, odbc32.dll under Win32 family
+ - OSes: Linux/glibc, FreeBSD, OpenBSD, Solaris 2.6, Win98, WinNT, Win2000 server
+ - RDBMSes: Oracle 7/8/8i, MS SQL Server 6.5/7, mySQL
+ - access suites: OpenLink DAS, EasySoft OOB, various win32 drivers
+
diff --git a/servers/slapd/back-sql/docs/todo b/servers/slapd/back-sql/docs/todo
new file mode 100644
index 0000000..9d8736f
--- /dev/null
+++ b/servers/slapd/back-sql/docs/todo
@@ -0,0 +1,12 @@
+1) must add alias handling
+2) [sizelimit moved to frontend]
+ must set time limit when preparing all queries, and check size limit
+3) there was expressed a need to have access to IP in while constructing
+ queries, to have response alter in accordance to client IP. Will add
+ preprocessor for values in metatables, which would substitute things
+ like "$IP$".
+4) must handle NOT filters (see ITS#2652)
+5) must map attribute types and syntaxes between LDAP and SQL types (e.g.
+ use BLOBs for octet streams)
+6) must define another mech to add auxiliary objectClass to all entries
+ according to ldap_at_mappings (ldap_entry_objclasses has limitations)
diff --git a/servers/slapd/back-sql/entry-id.c b/servers/slapd/back-sql/entry-id.c
new file mode 100644
index 0000000..c2d78a7
--- /dev/null
+++ b/servers/slapd/back-sql/entry-id.c
@@ -0,0 +1,1092 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+#ifdef BACKSQL_ARBITRARY_KEY
+struct berval backsql_baseObject_bv = BER_BVC( BACKSQL_BASEOBJECT_IDSTR );
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+backsql_entryID *
+backsql_entryID_dup( backsql_entryID *src, void *ctx )
+{
+ backsql_entryID *dst;
+
+ if ( src == NULL ) return NULL;
+
+ dst = slap_sl_calloc( 1, sizeof( backsql_entryID ), ctx );
+ ber_dupbv_x( &dst->eid_ndn, &src->eid_ndn, ctx );
+ if ( src->eid_dn.bv_val == src->eid_ndn.bv_val ) {
+ dst->eid_dn = dst->eid_ndn;
+ } else {
+ ber_dupbv_x( &dst->eid_dn, &src->eid_dn, ctx );
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_dupbv_x( &dst->eid_id, &src->eid_id, ctx );
+ ber_dupbv_x( &dst->eid_keyval, &src->eid_keyval, ctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ dst->eid_id = src->eid_id;
+ dst->eid_keyval = src->eid_keyval;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ dst->eid_oc = src->eid_oc;
+ dst->eid_oc_id = src->eid_oc_id;
+
+ return dst;
+}
+
+backsql_entryID *
+backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx )
+{
+ backsql_entryID *next;
+
+ assert( id != NULL );
+
+ next = id->eid_next;
+
+ if ( !BER_BVISNULL( &id->eid_ndn ) ) {
+ if ( !BER_BVISNULL( &id->eid_dn )
+ && id->eid_dn.bv_val != id->eid_ndn.bv_val )
+ {
+ slap_sl_free( id->eid_dn.bv_val, ctx );
+ BER_BVZERO( &id->eid_dn );
+ }
+
+ slap_sl_free( id->eid_ndn.bv_val, ctx );
+ BER_BVZERO( &id->eid_ndn );
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ if ( !BER_BVISNULL( &id->eid_id ) ) {
+ slap_sl_free( id->eid_id.bv_val, ctx );
+ BER_BVZERO( &id->eid_id );
+ }
+
+ if ( !BER_BVISNULL( &id->eid_keyval ) ) {
+ slap_sl_free( id->eid_keyval.bv_val, ctx );
+ BER_BVZERO( &id->eid_keyval );
+ }
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+ if ( freeit ) {
+ slap_sl_free( id, ctx );
+ }
+
+ return next;
+}
+
+/*
+ * NOTE: the dn must be normalized
+ */
+int
+backsql_dn2id(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ struct berval *ndn,
+ backsql_entryID *id,
+ int matched,
+ int muck )
+{
+ backsql_info *bi = op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row = { 0 };
+ RETCODE rc;
+ int res;
+ struct berval realndn = BER_BVNULL;
+
+ /* TimesTen */
+ char upperdn[ BACKSQL_MAX_DN_LEN + 1 ];
+ struct berval tbbDN;
+ int i, j;
+
+ /*
+ * NOTE: id can be NULL; in this case, the function
+ * simply checks whether the DN can be successfully
+ * turned into an ID, returning LDAP_SUCCESS for
+ * positive cases, or the most appropriate error
+ */
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_dn2id(\"%s\")%s%s\n",
+ ndn->bv_val, id == NULL ? " (no ID expected)" : "",
+ matched ? " matched expected" : "" );
+
+ if ( id ) {
+ /* NOTE: trap inconsistencies */
+ assert( BER_BVISNULL( &id->eid_ndn ) );
+ }
+
+ if ( ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): DN length=%ld "
+ "exceeds max DN length %d:\n",
+ ndn->bv_val, ndn->bv_len, BACKSQL_MAX_DN_LEN );
+ return LDAP_OTHER;
+ }
+
+ /* return baseObject if available and matches */
+ /* FIXME: if ndn is already mucked, we cannot check this */
+ if ( bi->sql_baseObject != NULL &&
+ dn_match( ndn, &bi->sql_baseObject->e_nname ) )
+ {
+ if ( id != NULL ) {
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_dupbv_x( &id->eid_id, &backsql_baseObject_bv,
+ op->o_tmpmemctx );
+ ber_dupbv_x( &id->eid_keyval, &backsql_baseObject_bv,
+ op->o_tmpmemctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ id->eid_id = BACKSQL_BASEOBJECT_ID;
+ id->eid_keyval = BACKSQL_BASEOBJECT_KEYVAL;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ id->eid_oc_id = BACKSQL_BASEOBJECT_OC;
+
+ ber_dupbv_x( &id->eid_ndn, &bi->sql_baseObject->e_nname,
+ op->o_tmpmemctx );
+ ber_dupbv_x( &id->eid_dn, &bi->sql_baseObject->e_name,
+ op->o_tmpmemctx );
+
+ id->eid_next = NULL;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ /* begin TimesTen */
+ assert( bi->sql_id_query != NULL );
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): id_query \"%s\"\n",
+ ndn->bv_val, bi->sql_id_query );
+ rc = backsql_Prepare( dbh, &sth, bi->sql_id_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "error preparing SQL:\n %s",
+ ndn->bv_val, bi->sql_id_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ realndn = *ndn;
+ if ( muck ) {
+ if ( backsql_api_dn2odbc( op, rs, &realndn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ ndn->bv_val, realndn.bv_val );
+ res = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+ if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
+ /*
+ * Prepare an upper cased, byte reversed version
+ * that can be searched using indexes
+ */
+
+ for ( i = 0, j = realndn.bv_len - 1; realndn.bv_val[ i ]; i++, j--)
+ {
+ upperdn[ i ] = realndn.bv_val[ j ];
+ }
+ upperdn[ i ] = '\0';
+ ldap_pvt_str2upper( upperdn );
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "upperdn=\"%s\"\n",
+ ndn->bv_val, upperdn );
+ ber_str2bv( upperdn, 0, 0, &tbbDN );
+
+ } else {
+ if ( BACKSQL_USE_REVERSE_DN( bi ) ) {
+ AC_MEMCPY( upperdn, realndn.bv_val, realndn.bv_len + 1 );
+ ldap_pvt_str2upper( upperdn );
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "upperdn=\"%s\"\n",
+ ndn->bv_val, upperdn );
+ ber_str2bv( upperdn, 0, 0, &tbbDN );
+
+ } else {
+ tbbDN = realndn;
+ }
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &tbbDN );
+ if ( rc != SQL_SUCCESS) {
+ /* end TimesTen */
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "error binding dn=\"%s\" parameter:\n",
+ ndn->bv_val, tbbDN.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "error executing query (\"%s\", \"%s\"):\n",
+ ndn->bv_val, bi->sql_id_query, tbbDN.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx );
+ rc = SQLFetch( sth );
+ if ( BACKSQL_SUCCESS( rc ) ) {
+#ifdef LDAP_DEBUG
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): id=%s keyval=%s oc_id=%s dn=%s\n",
+ ndn->bv_val, row.cols[0], row.cols[1], row.cols[2],
+ row.cols[3] );
+#endif /* LDAP_DEBUG */
+
+ res = LDAP_SUCCESS;
+ if ( id != NULL ) {
+ struct berval dn;
+
+ id->eid_next = NULL;
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_str2bv_x( row.cols[ 0 ], 0, 1, &id->eid_id,
+ op->o_tmpmemctx );
+ ber_str2bv_x( row.cols[ 1 ], 0, 1, &id->eid_keyval,
+ op->o_tmpmemctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ if ( BACKSQL_STR2ID( &id->eid_id, row.cols[ 0 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+ if ( BACKSQL_STR2ID( &id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ if ( BACKSQL_STR2ID( &id->eid_oc_id, row.cols[ 2 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ ber_str2bv( row.cols[ 3 ], 0, 0, &dn );
+
+ if ( backsql_api_odbc2dn( op, rs, &dn ) ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ res = dnPrettyNormal( NULL, &dn,
+ &id->eid_dn, &id->eid_ndn,
+ op->o_tmpmemctx );
+ if ( res != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "dnPrettyNormal failed (%d: %s)\n",
+ realndn.bv_val, res,
+ ldap_err2string( res ) );
+
+ /* cleanup... */
+ (void)backsql_free_entryID( id, 0, op->o_tmpmemctx );
+ }
+
+ if ( dn.bv_val != row.cols[ 3 ] ) {
+ free( dn.bv_val );
+ }
+ }
+
+ } else {
+ res = LDAP_NO_SUCH_OBJECT;
+ if ( matched ) {
+ struct berval pdn = *ndn;
+
+ /*
+ * Look for matched
+ */
+ rs->sr_matched = NULL;
+ while ( !be_issuffix( op->o_bd, &pdn ) ) {
+ char *matchedDN = NULL;
+
+ dnParent( &pdn, &pdn );
+
+ /*
+ * Empty DN ("") defaults to LDAP_SUCCESS
+ */
+ rs->sr_err = backsql_dn2id( op, rs, dbh, &pdn, id, 0, 1 );
+ switch ( rs->sr_err ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* try another one */
+ break;
+
+ case LDAP_SUCCESS:
+ matchedDN = pdn.bv_val;
+ /* fail over to next case */
+
+ default:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_matched = matchedDN;
+ goto done;
+ }
+ }
+ }
+ }
+
+done:;
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_dn2id(\"%s\"): err=%d\n",
+ ndn->bv_val, res );
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ if ( !BER_BVISNULL( &realndn ) && realndn.bv_val != ndn->bv_val ) {
+ ch_free( realndn.bv_val );
+ }
+
+ return res;
+}
+
+int
+backsql_count_children(
+ Operation *op,
+ SQLHDBC dbh,
+ struct berval *dn,
+ unsigned long *nchildren )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+ RETCODE rc;
+ int res = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_count_children(): dn=\"%s\"\n",
+ dn->bv_val );
+
+ if ( dn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_count_children(): DN \"%s\" (%ld bytes) "
+ "exceeds max DN length (%d):\n",
+ dn->bv_val, dn->bv_len, BACKSQL_MAX_DN_LEN );
+ return LDAP_OTHER;
+ }
+
+ /* begin TimesTen */
+ assert( bi->sql_has_children_query != NULL );
+ Debug(LDAP_DEBUG_TRACE, "children id query \"%s\"\n",
+ bi->sql_has_children_query );
+ rc = backsql_Prepare( dbh, &sth, bi->sql_has_children_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_count_children(): error preparing SQL:\n%s",
+ bi->sql_has_children_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, dn );
+ if ( rc != SQL_SUCCESS) {
+ /* end TimesTen */
+ Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): "
+ "error binding dn=\"%s\" parameter:\n",
+ dn->bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): "
+ "error executing query (\"%s\", \"%s\"):\n",
+ bi->sql_has_children_query, dn->bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx );
+
+ rc = SQLFetch( sth );
+ if ( BACKSQL_SUCCESS( rc ) ) {
+ char *end;
+
+ *nchildren = strtol( row.cols[ 0 ], &end, 0 );
+ if ( end == row.cols[ 0 ] ) {
+ res = LDAP_OTHER;
+
+ } else {
+ switch ( end[ 0 ] ) {
+ case '\0':
+ break;
+
+ case '.': {
+ unsigned long ul;
+
+ /* FIXME: braindead RDBMSes return
+ * a fractional number from COUNT!
+ */
+ if ( lutil_atoul( &ul, end + 1 ) != 0 || ul != 0 ) {
+ res = LDAP_OTHER;
+ }
+ } break;
+
+ default:
+ res = LDAP_OTHER;
+ }
+ }
+
+ } else {
+ res = LDAP_OTHER;
+ }
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_count_children(): %lu\n",
+ *nchildren );
+
+ return res;
+}
+
+int
+backsql_has_children(
+ Operation *op,
+ SQLHDBC dbh,
+ struct berval *dn )
+{
+ unsigned long nchildren;
+ int rc;
+
+ rc = backsql_count_children( op, dbh, dn, &nchildren );
+
+ if ( rc == LDAP_SUCCESS ) {
+ return nchildren > 0 ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ }
+
+ return rc;
+}
+
+static int
+backsql_get_attr_vals( void *v_at, void *v_bsi )
+{
+ backsql_at_map_rec *at = v_at;
+ backsql_srch_info *bsi = v_bsi;
+ backsql_info *bi;
+ RETCODE rc;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+ unsigned long i,
+ k = 0,
+ oldcount = 0,
+ res = 0;
+#ifdef BACKSQL_COUNTQUERY
+ unsigned count,
+ j,
+ append = 0;
+ SQLLEN countsize = sizeof( count );
+ Attribute *attr = NULL;
+
+ slap_mr_normalize_func *normfunc = NULL;
+#endif /* BACKSQL_COUNTQUERY */
+#ifdef BACKSQL_PRETTY_VALIDATE
+ slap_syntax_validate_func *validate = NULL;
+ slap_syntax_transform_func *pretty = NULL;
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+ assert( at != NULL );
+ assert( bsi != NULL );
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_get_attr_vals(): "
+ "oc=\"%s\" attr=\"%s\" keyval=" BACKSQL_IDFMT "\n",
+ BACKSQL_OC_NAME( bsi->bsi_oc ), at->bam_ad->ad_cname.bv_val,
+ BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) );
+
+ bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ validate = at->bam_true_ad->ad_type->sat_syntax->ssyn_validate;
+ pretty = at->bam_true_ad->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( validate == NULL && pretty == NULL ) {
+ return 1;
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( at->bam_true_ad->ad_type->sat_equality ) {
+ normfunc = at->bam_true_ad->ad_type->sat_equality->smr_normalize;
+ }
+
+ /* Count how many rows will be returned. This avoids memory
+ * fragmentation that can result from loading the values in
+ * one by one and using realloc()
+ */
+ rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_countquery, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error preparing count query: %s\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ return 1;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT,
+ &bsi->bsi_c_eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error binding key value parameter\n" );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ rc = SQLExecute( sth );
+ if ( ! BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error executing attribute count query '%s'\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_LONG,
+ (SQLPOINTER)&count,
+ (SQLINTEGER)sizeof( count ),
+ &countsize );
+
+ rc = SQLFetch( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error fetch results of count query: %s\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "number of values in query: %u\n", count );
+ SQLFreeStmt( sth, SQL_DROP );
+ if ( count == 0 ) {
+ return 1;
+ }
+
+ attr = attr_find( bsi->bsi_e->e_attrs, at->bam_true_ad );
+ if ( attr != NULL ) {
+ BerVarray tmp;
+
+ if ( attr->a_vals != NULL ) {
+ oldcount = attr->a_numvals;
+ }
+
+ tmp = ch_realloc( attr->a_vals, ( oldcount + count + 1 ) * sizeof( struct berval ) );
+ if ( tmp == NULL ) {
+ return 1;
+ }
+ attr->a_vals = tmp;
+ memset( &attr->a_vals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) );
+
+ if ( normfunc ) {
+ tmp = ch_realloc( attr->a_nvals, ( oldcount + count + 1 ) * sizeof( struct berval ) );
+ if ( tmp == NULL ) {
+ return 1;
+ }
+ attr->a_nvals = tmp;
+ memset( &attr->a_nvals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) );
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+ attr->a_numvals += count;
+
+ } else {
+ append = 1;
+
+ /* Make space for the array of values */
+ attr = attr_alloc( at->bam_true_ad );
+ attr->a_numvals = count;
+ attr->a_vals = ch_calloc( count + 1, sizeof( struct berval ) );
+ if ( attr->a_vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "Out of memory!\n" );
+ ch_free( attr );
+ return 1;
+ }
+ if ( normfunc ) {
+ attr->a_nvals = ch_calloc( count + 1, sizeof( struct berval ) );
+ if ( attr->a_nvals == NULL ) {
+ ch_free( attr->a_vals );
+ ch_free( attr );
+ return 1;
+
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error preparing query: %s\n", at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT,
+ &bsi->bsi_c_eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error binding key value parameter\n" );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "query=\"%s\" keyval=" BACKSQL_IDFMT "\n", at->bam_query,
+ BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) );
+#endif /* BACKSQL_TRACE */
+
+ rc = SQLExecute( sth );
+ if ( ! BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error executing attribute query \"%s\"\n",
+ at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx );
+#ifdef BACKSQL_COUNTQUERY
+ j = oldcount;
+#endif /* BACKSQL_COUNTQUERY */
+ for ( rc = SQLFetch( sth ), k = 0;
+ BACKSQL_SUCCESS( rc );
+ rc = SQLFetch( sth ), k++ )
+ {
+ for ( i = 0; i < (unsigned long)row.ncols; i++ ) {
+
+ if ( row.value_len[ i ] > 0 ) {
+ struct berval bv;
+ int retval;
+#ifdef BACKSQL_TRACE
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ retval = slap_bv2ad( &row.col_names[ i ], &ad, &text );
+ if ( retval != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "unable to find AttributeDescription %s "
+ "in schema (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ row.col_names[ i ].bv_val, retval );
+ res = 1;
+ goto done;
+ }
+
+ if ( ad != at->bam_ad ) {
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "column name %s differs from "
+ "AttributeDescription %s\n",
+ bsi->bsi_e->e_name.bv_val,
+ ad->ad_cname.bv_val,
+ at->bam_ad->ad_cname.bv_val );
+ res = 1;
+ goto done;
+ }
+#endif /* BACKSQL_TRACE */
+
+ /* ITS#3386, ITS#3113 - 20070308
+ * If a binary is fetched?
+ * must use the actual size read
+ * from the database.
+ */
+ if ( BACKSQL_IS_BINARY( row.col_type[ i ] ) ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "column name %s: data is binary; "
+ "using database size %ld\n",
+ bsi->bsi_e->e_name.bv_val,
+ ad->ad_cname.bv_val,
+ row.value_len[ i ] );
+#endif /* BACKSQL_TRACE */
+ bv.bv_val = row.cols[ i ];
+ bv.bv_len = row.value_len[ i ];
+
+ } else {
+ ber_str2bv( row.cols[ i ], 0, 0, &bv );
+ }
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ struct berval pbv;
+
+ retval = pretty( at->bam_true_ad->ad_type->sat_syntax,
+ &bv, &pbv, bsi->bsi_op->o_tmpmemctx );
+ bv = pbv;
+
+ } else {
+ retval = validate( at->bam_true_ad->ad_type->sat_syntax,
+ &bv );
+ }
+
+ if ( retval != LDAP_SUCCESS ) {
+ /* FIXME: we're ignoring invalid values,
+ * but we're accepting the attributes;
+ * should we fail at all? */
+ Debug(LDAP_DEBUG_TRACE,
+ "==>backsql_get_attr_vals(\"%s\"): " "unable to %s value #%lu " "of AttributeDescription %s (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ pretty ? "prettify" : "validate",
+ k - oldcount,
+ at->bam_ad->ad_cname.bv_val,
+ retval );
+ continue;
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifndef BACKSQL_COUNTQUERY
+ (void)backsql_entry_addattr( bsi->bsi_e,
+ at->bam_true_ad, &bv,
+ bsi->bsi_op->o_tmpmemctx );
+
+#else /* BACKSQL_COUNTQUERY */
+ if ( normfunc ) {
+ struct berval nbv;
+
+ retval = (*normfunc)( SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ at->bam_true_ad->ad_type->sat_syntax,
+ at->bam_true_ad->ad_type->sat_equality,
+ &bv, &nbv,
+ bsi->bsi_op->o_tmpmemctx );
+
+ if ( retval != LDAP_SUCCESS ) {
+ /* FIXME: we're ignoring invalid values,
+ * but we're accepting the attributes;
+ * should we fail at all? */
+ Debug(LDAP_DEBUG_TRACE,
+ "==>backsql_get_attr_vals(\"%s\"): " "unable to normalize value #%lu " "of AttributeDescription %s (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ k - oldcount,
+ at->bam_ad->ad_cname.bv_val,
+ retval );
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ bsi->bsi_op->o_tmpfree( bv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+ continue;
+ }
+ ber_dupbv( &attr->a_nvals[ j ], &nbv );
+ bsi->bsi_op->o_tmpfree( nbv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+
+ ber_dupbv( &attr->a_vals[ j ], &bv );
+
+ assert( j < oldcount + count );
+ j++;
+#endif /* BACKSQL_COUNTQUERY */
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ bsi->bsi_op->o_tmpfree( bv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "prec=%d\n",
+ (int)row.col_prec[ i ] );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "NULL value "
+ "in this row for attribute \"%s\"\n",
+ row.col_names[ i ].bv_val );
+#endif /* BACKSQL_TRACE */
+ }
+ }
+ }
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( BER_BVISNULL( &attr->a_vals[ 0 ] ) ) {
+ /* don't leave around attributes with no values */
+ attr_free( attr );
+
+ } else if ( append ) {
+ Attribute **ap;
+
+ for ( ap = &bsi->bsi_e->e_attrs; (*ap) != NULL; ap = &(*ap)->a_next )
+ /* goto last */ ;
+ *ap = attr;
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ SQLFreeStmt( sth, SQL_DROP );
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_get_attr_vals()\n" );
+
+ if ( at->bam_next ) {
+ res = backsql_get_attr_vals( at->bam_next, v_bsi );
+ } else {
+ res = 1;
+ }
+
+#ifdef BACKSQL_TRACE
+done:;
+#endif /* BACKSQL_TRACE */
+ backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx );
+
+ return res;
+}
+
+int
+backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *eid )
+{
+ Operation *op = bsi->bsi_op;
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int i;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_id2entry()\n" );
+
+ assert( bsi->bsi_e != NULL );
+
+ memset( bsi->bsi_e, 0, sizeof( Entry ) );
+
+ if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) {
+ (void)entry_dup2( bsi->bsi_e, bi->sql_baseObject );
+ goto done;
+ }
+
+ bsi->bsi_e->e_attrs = NULL;
+ bsi->bsi_e->e_private = NULL;
+
+ if ( eid->eid_oc == NULL ) {
+ eid->eid_oc = backsql_id2oc( bsi->bsi_op->o_bd->be_private,
+ eid->eid_oc_id );
+ if ( eid->eid_oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_id2entry(): unable to fetch objectClass with id=" BACKSQL_IDNUMFMT " for entry id=" BACKSQL_IDFMT " dn=\"%s\"\n",
+ eid->eid_oc_id, BACKSQL_IDARG(eid->eid_id),
+ eid->eid_dn.bv_val );
+ return LDAP_OTHER;
+ }
+ }
+ bsi->bsi_oc = eid->eid_oc;
+ bsi->bsi_c_eid = eid;
+
+ ber_dupbv_x( &bsi->bsi_e->e_name, &eid->eid_dn, op->o_tmpmemctx );
+ ber_dupbv_x( &bsi->bsi_e->e_nname, &eid->eid_ndn, op->o_tmpmemctx );
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* FIXME: unused */
+ bsi->bsi_e->e_id = eid->eid_id;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ rc = attr_merge_normalize_one( bsi->bsi_e,
+ slap_schema.si_ad_objectClass,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ bsi->bsi_op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ if ( bsi->bsi_attrs == NULL || ( bsi->bsi_flags & BSQL_SF_ALL_USER ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "retrieving all attributes\n" );
+ ldap_avl_apply( bsi->bsi_oc->bom_attrs, backsql_get_attr_vals,
+ bsi, 0, AVL_INORDER );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "custom attribute list\n" );
+ for ( i = 0; !BER_BVISNULL( &bsi->bsi_attrs[ i ].an_name ); i++ ) {
+ backsql_at_map_rec **vat;
+ AttributeName *an = &bsi->bsi_attrs[ i ];
+ int j;
+
+ /* if one of the attributes listed here is
+ * a subtype of another, it must be ignored,
+ * because subtypes are already dealt with
+ * by backsql_supad2at()
+ */
+ for ( j = 0; !BER_BVISNULL( &bsi->bsi_attrs[ j ].an_name ); j++ ) {
+ /* skip self */
+ if ( j == i ) {
+ continue;
+ }
+
+ /* skip subtypes */
+ if ( is_at_subtype( an->an_desc->ad_type,
+ bsi->bsi_attrs[ j ].an_desc->ad_type ) )
+ {
+ goto next;
+ }
+ }
+
+ rc = backsql_supad2at( bsi->bsi_oc, an->an_desc, &vat );
+ if ( rc != 0 || vat == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "attribute \"%s\" is not defined "
+ "for objectclass \"%s\"\n",
+ an->an_name.bv_val,
+ BACKSQL_OC_NAME( bsi->bsi_oc ) );
+ continue;
+ }
+
+ for ( j = 0; vat[j]; j++ ) {
+ backsql_get_attr_vals( vat[j], bsi );
+ }
+
+ ch_free( vat );
+
+next:;
+ }
+ }
+
+ if ( bsi->bsi_flags & BSQL_SF_RETURN_ENTRYUUID ) {
+ Attribute *a_entryUUID,
+ **ap;
+
+ a_entryUUID = backsql_operational_entryUUID( bi, eid );
+ if ( a_entryUUID != NULL ) {
+ for ( ap = &bsi->bsi_e->e_attrs;
+ *ap;
+ ap = &(*ap)->a_next );
+
+ *ap = a_entryUUID;
+ }
+ }
+
+ if ( ( bsi->bsi_flags & BSQL_SF_ALL_OPER )
+ || an_find( bsi->bsi_attrs, slap_bv_all_operational_attrs )
+ || an_find( bsi->bsi_attrs, &slap_schema.si_ad_structuralObjectClass->ad_cname ) )
+ {
+ ObjectClass *soc = NULL;
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ Attribute *a;
+ const char *text = NULL;
+ char textbuf[ 1024 ];
+ size_t textlen = sizeof( textbuf );
+ struct berval bv[ 2 ],
+ *nvals;
+ int rc = LDAP_SUCCESS;
+
+ a = attr_find( bsi->bsi_e->e_attrs,
+ slap_schema.si_ad_objectClass );
+ if ( a != NULL ) {
+ nvals = a->a_nvals;
+
+ } else {
+ bv[ 0 ] = bsi->bsi_oc->bom_oc->soc_cname;
+ BER_BVZERO( &bv[ 1 ] );
+ nvals = bv;
+ }
+
+ rc = structural_class( nvals, &soc, NULL,
+ &text, textbuf, textlen, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "structural_class() failed %d (%s)\n",
+ bsi->bsi_e->e_name.bv_val,
+ rc, text ? text : "" );
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ if ( !bvmatch( &soc->soc_cname, &bsi->bsi_oc->bom_oc->soc_cname ) ) {
+ if ( !is_object_subclass( bsi->bsi_oc->bom_oc, soc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "computed structuralObjectClass %s "
+ "does not match objectClass %s associated "
+ "to entry\n",
+ bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val,
+ bsi->bsi_oc->bom_oc->soc_cname.bv_val );
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "computed structuralObjectClass %s "
+ "is subclass of objectClass %s associated "
+ "to entry\n",
+ bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val,
+ bsi->bsi_oc->bom_oc->soc_cname.bv_val );
+ }
+
+ } else {
+ soc = bsi->bsi_oc->bom_oc;
+ }
+
+ rc = attr_merge_normalize_one( bsi->bsi_e,
+ slap_schema.si_ad_structuralObjectClass,
+ &soc->soc_cname,
+ bsi->bsi_op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+ }
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_id2entry()\n" );
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-sql/init.c b/servers/slapd/back-sql/init.c
new file mode 100644
index 0000000..1b45f8f
--- /dev/null
+++ b/servers/slapd/back-sql/init.c
@@ -0,0 +1,661 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "slap-config.h"
+#include "proto-sql.h"
+
+int
+sql_back_initialize(
+ BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_ASSERT,
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_NOOP,
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ SLAP_CONTROL_X_TREE_DELETE,
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+#ifndef BACKSQL_ARBITRARY_KEY
+ LDAP_CONTROL_PAGEDRESULTS,
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ NULL
+ };
+ int rc;
+
+ bi->bi_controls = controls;
+
+ bi->bi_flags |=
+#if 0
+ SLAP_BFLAG_INCREMENT |
+#endif
+ SLAP_BFLAG_REFERRALS;
+
+ Debug( LDAP_DEBUG_TRACE,"==>sql_back_initialize()\n" );
+
+ bi->bi_db_init = backsql_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = backsql_db_open;
+ bi->bi_db_close = backsql_db_close;
+ bi->bi_db_destroy = backsql_db_destroy;
+
+ bi->bi_op_abandon = 0;
+ bi->bi_op_compare = backsql_compare;
+ bi->bi_op_bind = backsql_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = backsql_search;
+ bi->bi_op_modify = backsql_modify;
+ bi->bi_op_modrdn = backsql_modrdn;
+ bi->bi_op_add = backsql_add;
+ bi->bi_op_delete = backsql_delete;
+
+ bi->bi_chk_referrals = 0;
+ bi->bi_operational = backsql_operational;
+ bi->bi_entry_get_rw = backsql_entry_get;
+ bi->bi_entry_release_rw = backsql_entry_release;
+
+ bi->bi_connection_init = 0;
+
+ rc = backsql_init_cf( bi );
+ Debug( LDAP_DEBUG_TRACE,"<==sql_back_initialize()\n" );
+ return rc;
+}
+
+int
+backsql_destroy(
+ BackendInfo *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_destroy()\n" );
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_destroy()\n" );
+ return 0;
+}
+
+int
+backsql_db_init(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi;
+ int rc = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_init()\n" );
+
+ bi = (backsql_info *)ch_calloc( 1, sizeof( backsql_info ) );
+ ldap_pvt_thread_mutex_init( &bi->sql_dbconn_mutex );
+ ldap_pvt_thread_mutex_init( &bi->sql_schema_mutex );
+
+ if ( backsql_init_db_env( bi ) != SQL_SUCCESS ) {
+ rc = -1;
+ }
+
+ bd->be_private = bi;
+ bd->be_cf_ocs = bd->bd_info->bi_cf_ocs;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_init()\n" );
+
+ return rc;
+}
+
+int
+backsql_db_destroy(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_destroy()\n" );
+
+ backsql_free_db_env( bi );
+ ldap_pvt_thread_mutex_destroy( &bi->sql_dbconn_mutex );
+ backsql_destroy_schema_map( bi );
+ ldap_pvt_thread_mutex_destroy( &bi->sql_schema_mutex );
+
+ if ( bi->sql_dbname ) {
+ ch_free( bi->sql_dbname );
+ }
+ if ( bi->sql_dbuser ) {
+ ch_free( bi->sql_dbuser );
+ }
+ if ( bi->sql_dbpasswd ) {
+ ch_free( bi->sql_dbpasswd );
+ }
+ if ( bi->sql_dbhost ) {
+ ch_free( bi->sql_dbhost );
+ }
+ if ( bi->sql_upper_func.bv_val ) {
+ ch_free( bi->sql_upper_func.bv_val );
+ ch_free( bi->sql_upper_func_open.bv_val );
+ ch_free( bi->sql_upper_func_close.bv_val );
+ }
+ if ( bi->sql_concat_func ) {
+ ber_bvarray_free( bi->sql_concat_func );
+ }
+ if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) {
+ ch_free( bi->sql_strcast_func.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_children_cond ) ) {
+ ch_free( bi->sql_children_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_dn_match_cond ) ) {
+ ch_free( bi->sql_dn_match_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ ch_free( bi->sql_subtree_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_dn_oc_aliasing ) ) {
+ ch_free( bi->sql_dn_oc_aliasing.bv_val );
+ }
+ if ( bi->sql_oc_query ) {
+ ch_free( bi->sql_oc_query );
+ }
+ if ( bi->sql_at_query ) {
+ ch_free( bi->sql_at_query );
+ }
+ if ( bi->sql_id_query ) {
+ ch_free( bi->sql_id_query );
+ }
+ if ( bi->sql_has_children_query ) {
+ ch_free( bi->sql_has_children_query );
+ }
+ if ( bi->sql_insentry_stmt ) {
+ ch_free( bi->sql_insentry_stmt );
+ }
+ if ( bi->sql_delentry_stmt ) {
+ ch_free( bi->sql_delentry_stmt );
+ }
+ if ( bi->sql_renentry_stmt ) {
+ ch_free( bi->sql_renentry_stmt );
+ }
+ if ( bi->sql_delobjclasses_stmt ) {
+ ch_free( bi->sql_delobjclasses_stmt );
+ }
+ if ( !BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ch_free( bi->sql_aliasing.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_aliasing_quote ) ) {
+ ch_free( bi->sql_aliasing_quote.bv_val );
+ }
+
+ if ( bi->sql_anlist ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &bi->sql_anlist[ i ].an_name ); i++ )
+ {
+ ch_free( bi->sql_anlist[ i ].an_name.bv_val );
+ }
+ ch_free( bi->sql_anlist );
+ }
+
+ if ( bi->sql_baseObject ) {
+ entry_free( bi->sql_baseObject );
+ }
+
+ ch_free( bi );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_destroy()\n" );
+ return 0;
+}
+
+int
+backsql_db_open(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+ struct berbuf bb = BB_NULL;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation* op;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_open(): "
+ "testing RDBMS connection\n" );
+ if ( bi->sql_dbname == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "datasource name not specified "
+ "(use \"dbname\" directive in slapd.conf)\n" );
+ return 1;
+ }
+
+ if ( bi->sql_concat_func == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "concat func not specified (use \"concat_pattern\" "
+ "directive in slapd.conf)\n" );
+
+ if ( backsql_split_pattern( backsql_def_concat_func,
+ &bi->sql_concat_func, 2 ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to parse pattern \"%s\"",
+ backsql_def_concat_func );
+ return 1;
+ }
+ }
+
+ /*
+ * see back-sql.h for default values
+ */
+ if ( BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ber_str2bv( BACKSQL_ALIASING,
+ STRLENOF( BACKSQL_ALIASING ),
+ 1, &bi->sql_aliasing );
+ }
+
+ if ( BER_BVISNULL( &bi->sql_aliasing_quote ) ) {
+ ber_str2bv( BACKSQL_ALIASING_QUOTE,
+ STRLENOF( BACKSQL_ALIASING_QUOTE ),
+ 1, &bi->sql_aliasing_quote );
+ }
+
+ /*
+ * Prepare cast string as required
+ */
+ if ( bi->sql_upper_func.bv_val ) {
+ char buf[1024];
+
+ if ( BACKSQL_UPPER_NEEDS_CAST( bi ) ) {
+ snprintf( buf, sizeof( buf ),
+ "%s(cast (" /* ? as varchar(%d))) */ ,
+ bi->sql_upper_func.bv_val );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open );
+
+ snprintf( buf, sizeof( buf ),
+ /* (cast(? */ " as varchar(%d)))",
+ BACKSQL_MAX_DN_LEN );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_close );
+
+ } else {
+ snprintf( buf, sizeof( buf ), "%s(" /* ?) */ ,
+ bi->sql_upper_func.bv_val );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open );
+
+ ber_str2bv( /* (? */ ")", 0, 1, &bi->sql_upper_func_close );
+ }
+ }
+
+ /* normalize filter values only if necessary */
+ bi->sql_caseIgnoreMatch = mr_find( "caseIgnoreMatch" );
+ assert( bi->sql_caseIgnoreMatch != NULL );
+
+ bi->sql_telephoneNumberMatch = mr_find( "telephoneNumberMatch" );
+ assert( bi->sql_telephoneNumberMatch != NULL );
+
+ if ( bi->sql_dbuser == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "user name not specified "
+ "(use \"dbuser\" directive in slapd.conf)\n" );
+ return 1;
+ }
+
+ if ( BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ /*
+ * Prepare concat function for subtree search condition
+ */
+ struct berval concat;
+ struct berval values[] = {
+ BER_BVC( "'%'" ),
+ BER_BVC( "?" ),
+ BER_BVNULL
+ };
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "subtree search SQL condition not specified "
+ "(use \"subtree_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( backsql_prepare_pattern( bi->sql_concat_func, values,
+ &concat ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to prepare CONCAT pattern for subtree search" );
+ return 1;
+ }
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%',?))
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbbb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ),
+ "(ldap_entries.dn) LIKE ",
+ &bi->sql_upper_func_open,
+ &concat,
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn LIKE CONCAT('%',?)
+ */
+
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ),
+ "ldap_entries.dn LIKE ",
+ &concat );
+ }
+
+ ch_free( concat.bv_val );
+
+ bi->sql_subtree_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"subtree_cond\"\n",
+ bi->sql_subtree_cond.bv_val );
+ }
+
+ if ( bi->sql_children_cond.bv_val == NULL ) {
+ /*
+ * Prepare concat function for children search condition
+ */
+ struct berval concat;
+ struct berval values[] = {
+ BER_BVC( "'%,'" ),
+ BER_BVC( "?" ),
+ BER_BVNULL
+ };
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "children search SQL condition not specified "
+ "(use \"children_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( backsql_prepare_pattern( bi->sql_concat_func, values,
+ &concat ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to prepare CONCAT pattern for children search" );
+ return 1;
+ }
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%,',?))
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbbb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ),
+ "(ldap_entries.dn) LIKE ",
+ &bi->sql_upper_func_open,
+ &concat,
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn LIKE CONCAT('%,',?)
+ */
+
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ),
+ "ldap_entries.dn LIKE ",
+ &concat );
+ }
+
+ ch_free( concat.bv_val );
+
+ bi->sql_children_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"children_cond\"\n",
+ bi->sql_children_cond.bv_val );
+ }
+
+ if ( bi->sql_dn_match_cond.bv_val == NULL ) {
+ /*
+ * Prepare concat function for dn match search condition
+ */
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "DN match search SQL condition not specified "
+ "(use \"dn_match_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn)=?
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbcb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn)=" ),
+ "(ldap_entries.dn)=",
+ &bi->sql_upper_func_open,
+ '?',
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn=?
+ */
+
+ backsql_strfcat_x( &bb, NULL, "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn=?" ),
+ "ldap_entries.dn=?" );
+ }
+
+ bi->sql_dn_match_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"dn_match_cond\"\n",
+ bi->sql_dn_match_cond.bv_val );
+ }
+
+ if ( bi->sql_oc_query == NULL ) {
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ bi->sql_oc_query =
+ ch_strdup( backsql_def_needs_select_oc_query );
+
+ } else {
+ bi->sql_oc_query = ch_strdup( backsql_def_oc_query );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "objectclass mapping SQL statement not specified "
+ "(use \"oc_query\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n", bi->sql_oc_query );
+ }
+
+ if ( bi->sql_at_query == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "attribute mapping SQL statement not specified "
+ "(use \"at_query\" directive in slapd.conf)\n" );
+ Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_at_query );
+ bi->sql_at_query = ch_strdup( backsql_def_at_query );
+ }
+
+ if ( bi->sql_insentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry insertion SQL statement not specified "
+ "(use \"insentry_stmt\" directive in slapd.conf)\n" );
+ Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_insentry_stmt );
+ bi->sql_insentry_stmt = ch_strdup( backsql_def_insentry_stmt );
+ }
+
+ if ( bi->sql_delentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry deletion SQL statement not specified "
+ "(use \"delentry_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_delentry_stmt );
+ bi->sql_delentry_stmt = ch_strdup( backsql_def_delentry_stmt );
+ }
+
+ if ( bi->sql_renentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry deletion SQL statement not specified "
+ "(use \"renentry_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_renentry_stmt );
+ bi->sql_renentry_stmt = ch_strdup( backsql_def_renentry_stmt );
+ }
+
+ if ( bi->sql_delobjclasses_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "objclasses deletion SQL statement not specified "
+ "(use \"delobjclasses_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_delobjclasses_stmt );
+ bi->sql_delobjclasses_stmt = ch_strdup( backsql_def_delobjclasses_stmt );
+ }
+
+ /* This should just be to force schema loading */
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = bd;
+ if ( backsql_get_db_conn( op, &dbh ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "connection failed, exiting\n" );
+ return 1;
+ }
+ if ( backsql_load_schema_map( bi, dbh ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "schema mapping failed, exiting\n" );
+ return 1;
+ }
+ if ( backsql_free_db_conn( op, dbh ) != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "connection free failed\n" );
+ }
+ if ( !BACKSQL_SCHEMA_LOADED( bi ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "test failed, schema map not loaded - exiting\n" );
+ return 1;
+ }
+
+ /*
+ * Prepare ID selection query
+ */
+ if ( bi->sql_id_query == NULL ) {
+ /* no custom id_query provided */
+ if ( bi->sql_upper_func.bv_val == NULL ) {
+ backsql_strcat_x( &bb, NULL, backsql_id_query, "dn=?", NULL );
+
+ } else {
+ if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
+ backsql_strcat_x( &bb, NULL, backsql_id_query,
+ "dn_ru=?", NULL );
+ } else {
+ if ( BACKSQL_USE_REVERSE_DN( bi ) ) {
+ backsql_strfcat_x( &bb, NULL, "sbl",
+ backsql_id_query,
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(dn)=?" ), "(dn)=?" );
+ } else {
+ backsql_strfcat_x( &bb, NULL, "sblbcb",
+ backsql_id_query,
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(dn)=" ), "(dn)=",
+ &bi->sql_upper_func_open,
+ '?',
+ &bi->sql_upper_func_close );
+ }
+ }
+ }
+ bi->sql_id_query = bb.bb_val.bv_val;
+ }
+
+ /*
+ * Prepare children count query
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "sbsb",
+ "SELECT COUNT(distinct subordinates.id) "
+ "FROM ldap_entries,ldap_entries ",
+ &bi->sql_aliasing, "subordinates "
+ "WHERE subordinates.parent=ldap_entries.id AND ",
+ &bi->sql_dn_match_cond );
+ bi->sql_has_children_query = bb.bb_val.bv_val;
+
+ /*
+ * Prepare DN and objectClass aliasing bit of query
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "sbbsbsbbsb",
+ " ", &bi->sql_aliasing, &bi->sql_aliasing_quote,
+ "objectClass", &bi->sql_aliasing_quote,
+ ",ldap_entries.dn ", &bi->sql_aliasing,
+ &bi->sql_aliasing_quote, "dn", &bi->sql_aliasing_quote );
+ bi->sql_dn_oc_aliasing = bb.bb_val;
+
+ /* should never happen! */
+ assert( bd->be_nsuffix != NULL );
+
+ if ( BER_BVISNULL( &bd->be_nsuffix[ 1 ] ) ) {
+ /* enable if only one suffix is defined */
+ bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT;
+ }
+
+ bi->sql_flags |= BSQLF_CHECK_SCHEMA;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_open(): "
+ "test succeeded, schema map loaded\n" );
+ return 0;
+}
+
+int
+backsql_db_close(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_close()\n" );
+
+ backsql_conn_destroy( bi );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_close()\n" );
+
+ return 0;
+}
+
+#if SLAPD_SQL == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( sql )
+
+#endif /* SLAPD_SQL == SLAPD_MOD_DYNAMIC */
+
diff --git a/servers/slapd/back-sql/modify.c b/servers/slapd/back-sql/modify.c
new file mode 100644
index 0000000..83afec3
--- /dev/null
+++ b/servers/slapd/back-sql/modify.c
@@ -0,0 +1,212 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_modify( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ Entry m = { 0 }, *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ /*
+ * FIXME: in case part of the operation cannot be performed
+ * (missing mapping, SQL write fails or so) the entire operation
+ * should be rolled-back
+ */
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modify(): modifying entry \"%s\"\n",
+ op->o_req_ndn.bv_val );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(): "
+ "could not get connection handle - exiting\n" );
+ /*
+ * FIXME: we don't want to send back
+ * excessively detailed messages
+ */
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto done;
+ }
+
+ bsi.bsi_e = &m;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+ e = &m;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modify(): "
+ "could not retrieve modifyDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &m.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &m;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(): "
+ "modifying entry \"%s\" (id=" BACKSQL_IDFMT ")\n",
+ bsi.bsi_base_id.eid_dn.bv_val,
+ BACKSQL_IDARG(bsi.bsi_base_id.eid_id) );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &m, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &m;
+ goto done;
+ }
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+
+ assert( bsi.bsi_base_id.eid_oc != NULL );
+ oc = bsi.bsi_base_id.eid_oc;
+
+ if ( !acl_check_modlist( op, &m, op->orm_modlist ) ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &m;
+ goto done;
+ }
+
+ rs->sr_err = backsql_modify_internal( op, rs, dbh, oc,
+ &bsi.bsi_base_id, op->orm_modlist );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &m;
+ goto do_transact;
+ }
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ backsql_entry_clean( op, &m );
+
+ bsi.bsi_e = &m;
+ rs->sr_err = backsql_id2entry( &bsi, &bsi.bsi_base_id );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &m;
+ goto do_transact;
+ }
+
+ rs->sr_err = entry_schema_check( op, &m, NULL, 0, 0, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ m.e_name.bv_val );
+ e = NULL;
+ goto do_transact;
+ }
+ }
+
+do_transact:;
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &m.e_nname ) ) {
+ backsql_entry_clean( op, &m );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modify()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/modrdn.c b/servers/slapd/back-sql/modrdn.c
new file mode 100644
index 0000000..39114b3
--- /dev/null
+++ b/servers/slapd/back-sql/modrdn.c
@@ -0,0 +1,510 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_modrdn( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ backsql_entryID e_id = BACKSQL_ENTRYID_INIT,
+ n_id = BACKSQL_ENTRYID_INIT;
+ backsql_srch_info bsi = { 0 };
+ backsql_oc_map_rec *oc = NULL;
+ struct berval pdn = BER_BVNULL, pndn = BER_BVNULL,
+ *new_pdn = NULL, *new_npdn = NULL,
+ realnew_dn = BER_BVNULL;
+ Entry r = { 0 },
+ p = { 0 },
+ n = { 0 },
+ *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ struct berval *newSuperior = op->oq_modrdn.rs_newSup;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modrdn() renaming entry \"%s\", "
+ "newrdn=\"%s\", newSuperior=\"%s\"\n",
+ op->o_req_dn.bv_val, op->oq_modrdn.rs_newrdn.bv_val,
+ newSuperior ? newSuperior->bv_val : "(NULL)" );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "could not get connection handle - exiting\n" );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ bsi.bsi_e = &r;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+ e = &r;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve modrdnDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &r;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): entry id=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(e_id.eid_id) );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &r, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &r;
+ goto done;
+ }
+
+ if ( backsql_has_children( op, dbh, &op->o_req_ndn ) == LDAP_COMPARE_TRUE ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "entry \"%s\" has children\n",
+ op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subtree rename not supported";
+ e = &r;
+ goto done;
+ }
+
+ /*
+ * Check for entry access to target
+ */
+ if ( !access_allowed( op, &r, slap_schema.si_ad_entry,
+ NULL, ACL_WRITE, NULL ) ) {
+ Debug( LDAP_DEBUG_TRACE, " no access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+
+ /*
+ * namingContext "" is not supported
+ */
+ if ( BER_BVISEMPTY( &pdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "parent is \"\" - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "not allowed within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Check for children access to parent
+ */
+ bsi.bsi_e = &p;
+ e_id = bsi.bsi_base_id;
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+ rs->sr_err = backsql_init_search( &bsi, &pndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ BACKSQL_ISF_GET_ENTRY );
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): old parent entry id is " BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(bsi.bsi_base_id.eid_id) );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve renameDN ID - no such entry\n" );
+ e = &p;
+ goto done;
+ }
+
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children, NULL,
+ newSuperior ? ACL_WDEL : ACL_WRITE, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " no access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ if ( newSuperior ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ /*
+ * namingContext "" is not supported
+ */
+ if ( BER_BVISEMPTY( newSuperior ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is \"\" - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "not allowed within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ new_pdn = newSuperior;
+ new_npdn = op->oq_modrdn.rs_nnewSup;
+
+ /*
+ * Check for children access to new parent
+ */
+ bsi.bsi_e = &n;
+ rs->sr_err = backsql_init_search( &bsi, new_npdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve renameDN ID - no such entry\n" );
+ e = &n;
+ goto done;
+ }
+
+ n_id = bsi.bsi_base_id;
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): new parent entry id=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(n_id.eid_id) );
+
+ if ( !access_allowed( op, &n, slap_schema.si_ad_children,
+ NULL, ACL_WADD, NULL ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "no access to new parent \"%s\"\n",
+ new_pdn->bv_val );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &n;
+ goto done;
+ }
+
+ } else {
+ n_id = bsi.bsi_base_id;
+ new_pdn = &pdn;
+ new_npdn = &pndn;
+ }
+
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+
+ if ( newSuperior && dn_match( &pndn, new_npdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is equal to old parent - ignored\n" );
+ newSuperior = NULL;
+ }
+
+ if ( newSuperior && dn_match( &op->o_req_ndn, new_npdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is equal to entry being moved "
+ "- aborting\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "newSuperior is equal to old DN";
+ e = &r;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): new entry dn is \"%s\"\n",
+ op->orr_newDN.bv_val );
+
+ realnew_dn = op->orr_newDN;
+ if ( backsql_api_dn2odbc( op, rs, &realnew_dn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ op->o_req_dn.bv_val, realnew_dn.bv_val );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "executing renentry_stmt\n" );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_renentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error preparing renentry_stmt\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realnew_dn );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding DN parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, &n_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding parent ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &e_id.eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding entry ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 4, SQL_PARAM_INPUT, &e_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "could not rename ldap_entries record\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+ assert( e_id.eid_oc != NULL );
+ oc = e_id.eid_oc;
+
+ if ( op->orr_modlist != NULL ) {
+ rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, &e_id, op->orr_modlist );
+ slap_graduate_commit_csn( op );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &r;
+ goto done;
+ }
+ }
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ backsql_entry_clean( op, &r );
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+
+ bsi.bsi_e = &r;
+ rs->sr_err = backsql_init_search( &bsi, &op->orr_nnewDN,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->orr_nnewDN, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+ e = &r;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve modrdnDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &r;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ e_id = bsi.bsi_base_id;
+
+ rs->sr_err = entry_schema_check( op, &r, NULL, 0, 0, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ r.e_name.bv_val );
+ e = NULL;
+ goto done;
+ }
+ }
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &realnew_dn ) && realnew_dn.bv_val != op->orr_newDN.bv_val ) {
+ ch_free( realnew_dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &e_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &n_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &n_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ backsql_entry_clean( op, &r );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ if ( !BER_BVISNULL( &n.e_nname ) ) {
+ backsql_entry_clean( op, &n );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modrdn()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/operational.c b/servers/slapd/back-sql/operational.c
new file mode 100644
index 0000000..3eb1ec2
--- /dev/null
+++ b/servers/slapd/back-sql/operational.c
@@ -0,0 +1,246 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+#include "lutil.h"
+
+/*
+ * sets the supported operational attributes (if required)
+ */
+
+Attribute *
+backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id )
+{
+ int rc;
+ struct berval val, nval;
+ AttributeDescription *desc = slap_schema.si_ad_entryUUID;
+ Attribute *a;
+
+ backsql_entryUUID( bi, id, &val, NULL );
+
+ rc = (*desc->ad_type->sat_equality->smr_normalize)(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ &val, &nval, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ ber_memfree( val.bv_val );
+ return NULL;
+ }
+
+ a = attr_alloc( desc );
+
+ a->a_numvals = 1;
+ a->a_vals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ a->a_vals[ 0 ] = val;
+ BER_BVZERO( &a->a_vals[ 1 ] );
+
+ a->a_nvals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ a->a_nvals[ 0 ] = nval;
+ BER_BVZERO( &a->a_nvals[ 1 ] );
+
+ return a;
+}
+
+Attribute *
+backsql_operational_entryCSN( Operation *op )
+{
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ struct berval entryCSN;
+ Attribute *a;
+
+ a = attr_alloc( slap_schema.si_ad_entryCSN );
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc( 2 * sizeof( struct berval ) );
+ BER_BVZERO( &a->a_vals[ 1 ] );
+
+#ifdef BACKSQL_SYNCPROV
+ if ( op->o_sync && op->o_tag == LDAP_REQ_SEARCH && op->o_private != NULL ) {
+ assert( op->o_private != NULL );
+
+ entryCSN = *((struct berval *)op->o_private);
+
+ } else
+#endif /* BACKSQL_SYNCPROV */
+ {
+ entryCSN.bv_val = csnbuf;
+ entryCSN.bv_len = sizeof( csnbuf );
+ slap_get_csn( op, &entryCSN, 0 );
+ }
+
+ ber_dupbv( &a->a_vals[ 0 ], &entryCSN );
+
+ a->a_nvals = a->a_vals;
+
+ return a;
+}
+
+int
+backsql_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ int rc = 0;
+ Attribute **ap;
+ enum {
+ BACKSQL_OP_HASSUBORDINATES = 0,
+ BACKSQL_OP_ENTRYUUID,
+ BACKSQL_OP_ENTRYCSN,
+
+ BACKSQL_OP_LAST
+ };
+ int get_conn = BACKSQL_OP_LAST,
+ got[ BACKSQL_OP_LAST ] = { 0 };
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_operational(): entry \"%s\"\n",
+ rs->sr_entry->e_nname.bv_val );
+
+ for ( ap = &rs->sr_entry->e_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) {
+ get_conn--;
+ got[ BACKSQL_OP_HASSUBORDINATES ] = 1;
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_entryUUID ) {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYUUID ] = 1;
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_entryCSN ) {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYCSN ] = 1;
+ }
+ }
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( !got[ BACKSQL_OP_HASSUBORDINATES ] &&
+ (*ap)->a_desc == slap_schema.si_ad_hasSubordinates )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_HASSUBORDINATES ] = 1;
+
+ } else if ( !got[ BACKSQL_OP_ENTRYUUID ] &&
+ (*ap)->a_desc == slap_schema.si_ad_entryUUID )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYUUID ] = 1;
+
+ } else if ( !got[ BACKSQL_OP_ENTRYCSN ] &&
+ (*ap)->a_desc == slap_schema.si_ad_entryCSN )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYCSN ] = 1;
+ }
+ }
+
+ if ( !get_conn ) {
+ return 0;
+ }
+
+ rc = backsql_get_db_conn( op, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not get connection handle - exiting\n" );
+ return 1;
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_HASSUBORDINATES ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL )
+ {
+ rc = backsql_has_children( op, dbh, &rs->sr_entry->e_nname );
+
+ switch( rc ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ *ap = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE );
+ assert( *ap != NULL );
+ ap = &(*ap)->a_next;
+ rc = 0;
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "has_children failed( %d)\n", rc );
+ return 1;
+ }
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryUUID, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_ENTRYUUID ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ) == NULL )
+ {
+ backsql_srch_info bsi = { 0 };
+
+ rc = backsql_init_search( &bsi, &rs->sr_entry->e_nname,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, NULL,
+ BACKSQL_ISF_GET_ID );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entry ID - no such entry\n" );
+ return 1;
+ }
+
+ *ap = backsql_operational_entryUUID( bi, &bsi.bsi_base_id );
+
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( *ap == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entryUUID\n" );
+ return 1;
+ }
+
+ ap = &(*ap)->a_next;
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryCSN, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_ENTRYCSN ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ) == NULL )
+ {
+ *ap = backsql_operational_entryCSN( op );
+ if ( *ap == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entryCSN\n" );
+ return 1;
+ }
+
+ ap = &(*ap)->a_next;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_operational(%d)\n", rc );
+
+ return rc;
+}
+
diff --git a/servers/slapd/back-sql/proto-sql.h b/servers/slapd/back-sql/proto-sql.h
new file mode 100644
index 0000000..169be76
--- /dev/null
+++ b/servers/slapd/back-sql/proto-sql.h
@@ -0,0 +1,313 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Mararati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati
+ */
+
+/*
+ * The following changes have been addressed:
+ *
+ * Enhancements:
+ * - re-styled code for better readability
+ * - upgraded backend API to reflect recent changes
+ * - LDAP schema is checked when loading SQL/LDAP mapping
+ * - AttributeDescription/ObjectClass pointers used for more efficient
+ * mapping lookup
+ * - bervals used where string length is required often
+ * - atomized write operations by committing at the end of each operation
+ * and defaulting connection closure to rollback
+ * - added LDAP access control to write operations
+ * - fully implemented modrdn (with rdn attrs change, deleteoldrdn,
+ * access check, parent/children check and more)
+ * - added parent access control, children control to delete operation
+ * - added structuralObjectClass operational attribute check and
+ * value return on search
+ * - added hasSubordinate operational attribute on demand
+ * - search limits are appropriately enforced
+ * - function backsql_strcat() has been made more efficient
+ * - concat function has been made configurable by means of a pattern
+ * - added config switches:
+ * - fail_if_no_mapping write operations fail if there is no mapping
+ * - has_ldapinfo_dn_ru overrides autodetect
+ * - concat_pattern a string containing two '?' is used
+ * (note that "?||?" should be more portable
+ * than builtin function "CONCAT(?,?)")
+ * - strcast_func cast of string constants in "SELECT DISTINCT
+ * statements (needed by PostgreSQL)
+ * - upper_needs_cast cast the argument of upper when required
+ * (basically when building dn substring queries)
+ * - added noop control
+ * - added values return filter control
+ * - hasSubordinate can be used in search filters (with limitations)
+ * - eliminated oc->name; use oc->oc->soc_cname instead
+ *
+ * Todo:
+ * - add security checks for SQL statements that can be injected (?)
+ * - re-test with previously supported RDBMs
+ * - replace dn_ru and so with normalized dn (no need for upper() and so
+ * in dn match)
+ * - implement a backsql_normalize() function to replace the upper()
+ * conversion routines
+ * - note that subtree deletion, subtree renaming and so could be easily
+ * implemented (rollback and consistency checks are available :)
+ * - implement "lastmod" and other operational stuff (ldap_entries table ?)
+ * - check how to allow multiple operations with one statement, to remove
+ * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?)
+ */
+
+#ifndef PROTO_SQL_H
+#define PROTO_SQL_H
+
+#include "back-sql.h"
+
+/*
+ * add.c
+ */
+int backsql_modify_delete_all_values(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *e_id,
+ backsql_at_map_rec *at );
+
+int backsql_modify_internal(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ backsql_entryID *e_id,
+ Modifications *modlist );
+
+/*
+ * api.c
+ */
+int backsql_api_config( backsql_info *bi, const char *name,
+ int argc, char *argv[] );
+int backsql_api_destroy( backsql_info *bi );
+int backsql_api_register( backsql_api *ba );
+int backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn );
+int backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn );
+
+/*
+ * entry-id.c
+ */
+#ifdef BACKSQL_ARBITRARY_KEY
+extern struct berval backsql_baseObject_bv;
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+/* stores in *id the ID in table ldap_entries corresponding to DN, if any */
+extern int
+backsql_dn2id( Operation *op, SlapReply *rs, SQLHDBC dbh,
+ struct berval *ndn, backsql_entryID *id,
+ int matched, int muck );
+
+/* stores in *nchildren the count of children for an entry */
+extern int
+backsql_count_children( Operation *op, SQLHDBC dbh,
+ struct berval *dn, unsigned long *nchildren );
+
+/* returns LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE if the entry corresponding
+ * to DN has/has not children */
+extern int
+backsql_has_children( Operation *op, SQLHDBC dbh, struct berval *dn );
+
+/* free *id and return next in list */
+extern backsql_entryID *
+backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx );
+
+/* turn an ID into an entry */
+extern int
+backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *id );
+
+/* duplicate an entryID */
+extern backsql_entryID *
+backsql_entryID_dup( backsql_entryID *eid, void *ctx );
+
+/*
+ * operational.c
+ */
+
+Attribute *backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id );
+
+Attribute *backsql_operational_entryCSN( Operation *op );
+
+/*
+ * schema-map.c
+ */
+
+int backsql_load_schema_map( backsql_info *si, SQLHDBC dbh );
+
+backsql_oc_map_rec *backsql_oc2oc( backsql_info *si, ObjectClass *oc );
+
+backsql_oc_map_rec *backsql_id2oc( backsql_info *si, unsigned long id );
+
+backsql_oc_map_rec * backsql_name2oc( backsql_info *si,
+ struct berval *oc_name );
+
+backsql_at_map_rec *backsql_ad2at( backsql_oc_map_rec *objclass,
+ AttributeDescription *ad );
+
+int backsql_supad2at( backsql_oc_map_rec *objclass,
+ AttributeDescription *supad, backsql_at_map_rec ***pret );
+
+int backsql_destroy_schema_map( backsql_info *si );
+
+/*
+ * search.c
+ */
+
+int backsql_init_search( backsql_srch_info *bsi,
+ struct berval *nbase, int scope,
+ time_t stoptime, Filter *filter, SQLHDBC dbh,
+ Operation *op, SlapReply *rs, AttributeName *attrs,
+ unsigned flags );
+
+void backsql_entry_clean( Operation *op, Entry *e );
+
+/*
+ * sql-wrap.h
+ */
+
+RETCODE backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char* query, int timeout );
+
+#define backsql_BindParamStr( sth, par_ind, io, str, maxlen ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_CHAR, SQL_VARCHAR, \
+ (SQLULEN)(maxlen), 0, (SQLPOINTER)(str), \
+ (SQLLEN)(maxlen), NULL )
+
+#define backsql_BindParamBerVal( sth, par_ind, io, bv ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_CHAR, SQL_VARCHAR, \
+ (SQLULEN)(bv)->bv_len, 0, \
+ (SQLPOINTER)(bv)->bv_val, \
+ (SQLLEN)(bv)->bv_len, NULL )
+
+#define backsql_BindParamInt( sth, par_ind, io, val ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_ULONG, SQL_INTEGER, \
+ 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL )
+
+#define backsql_BindParamNumID( sth, par_ind, io, val ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), BACKSQL_C_NUMID, SQL_INTEGER, \
+ 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL )
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define backsql_BindParamID( sth, par_ind, io, id ) \
+ backsql_BindParamBerVal( (sth), (par_ind), (io), (id) )
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define backsql_BindParamID( sth, par_ind, io, id ) \
+ backsql_BindParamNumID( (sth), (par_ind), (io), (id) )
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+RETCODE backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx );
+
+RETCODE backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row );
+
+RETCODE backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx );
+
+RETCODE backsql_FreeRow( BACKSQL_ROW_NTS *row );
+
+void backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc );
+
+int backsql_conn_destroy( backsql_info *bi );
+
+int backsql_init_db_env( backsql_info *si );
+
+int backsql_free_db_env( backsql_info *si );
+
+int backsql_get_db_conn( Operation *op, SQLHDBC *dbh );
+
+int backsql_free_db_conn( Operation *op, SQLHDBC dbh );
+
+/*
+ * util.c
+ */
+
+extern const char
+ backsql_def_oc_query[],
+ backsql_def_needs_select_oc_query[],
+ backsql_def_at_query[],
+ backsql_def_delentry_stmt[],
+ backsql_def_renentry_stmt[],
+ backsql_def_insentry_stmt[],
+ backsql_def_delobjclasses_stmt[],
+ backsql_def_subtree_cond[],
+ backsql_def_upper_subtree_cond[],
+ backsql_id_query[],
+ backsql_def_concat_func[],
+ backsql_check_dn_ru_query[];
+
+struct berbuf * backsql_strcat_x( struct berbuf *dest, void *memctx, ... );
+struct berbuf * backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... );
+
+int backsql_entry_addattr( Entry *e, AttributeDescription *ad,
+ struct berval *at_val, void *memctx );
+
+int backsql_merge_from_clause( backsql_info *bi, struct berbuf *dest_from,
+ struct berval *src_from );
+
+int backsql_split_pattern( const char *pattern, BerVarray *split_pattern,
+ int expected );
+
+int backsql_prepare_pattern( BerVarray split_pattern, BerVarray values,
+ struct berval *res );
+
+int backsql_entryUUID( backsql_info *bi, backsql_entryID *id,
+ struct berval *entryUUID, void *memctx );
+int backsql_entryUUID_decode( struct berval *entryUUID, unsigned long *oc_id,
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval *keyval
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long *keyval
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ );
+
+/*
+ * former external.h
+ */
+
+extern BI_init sql_back_initialize;
+
+extern BI_destroy backsql_destroy;
+
+extern BI_db_init backsql_db_init;
+extern BI_db_open backsql_db_open;
+extern BI_db_close backsql_db_close;
+extern BI_db_destroy backsql_db_destroy;
+extern BI_db_config backsql_db_config;
+
+extern BI_op_bind backsql_bind;
+extern BI_op_search backsql_search;
+extern BI_op_compare backsql_compare;
+extern BI_op_modify backsql_modify;
+extern BI_op_modrdn backsql_modrdn;
+extern BI_op_add backsql_add;
+extern BI_op_delete backsql_delete;
+
+extern BI_operational backsql_operational;
+extern BI_entry_get_rw backsql_entry_get;
+extern BI_entry_release_rw backsql_entry_release;
+
+extern BI_connection_destroy backsql_connection_destroy;
+
+int backsql_init_cf( BackendInfo * bi );
+
+#endif /* PROTO_SQL_H */
diff --git a/servers/slapd/back-sql/rdbms_depend/README b/servers/slapd/back-sql/rdbms_depend/README
new file mode 100644
index 0000000..2b281f6
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/README
@@ -0,0 +1,189 @@
+Author: Pierangelo Masarati <ando@OpenLDAP.org>
+
+Back-sql can be tested with sql-test000-read; it requires a bit of work
+to get everything up and running appropriately.
+
+This document briefly describes the steps that are required to prepare
+a quick'n'dirty installation of back-sql and of the related RDBMS
+and ODBC; Examples are provided, but by no means they pretent
+to represent an exhaustive source of info about how to setup the ODBC;
+refer to the docs for any problem or detail.
+
+Currently, the system has been tested with IBM db2, PostgreSQL and MySQL;
+basic support and test data for other RDBMSes is in place, but as of
+today (November 2004) it's totally untested. If you succeed in running
+any of the other RDBMSes, please provide feedback about any required
+change either in the code or in the test scripts by means of OpenLDAP's
+Issue Tracking System (http://www.openldap.org/its/).
+
+1) slapd must be compiled with back-sql support, i.e. configure
+with --enable-sql switch. This requires an implementation of the ODBC
+to be installed.
+
+2) The ODBC must be set up appropriately, by editing the odbc.ini file
+in /etc/ (or wherever your installation puts it) and, if appropriate,
+the odbcinst.ini file. Note: you can also use custom odbc.ini and
+odbcinst.ini files, provided you export in ODBCINI the full path to the
+odbc.ini file, and in ODBCSYSINI the directory where the odbcinst.ini
+file resides.
+Relevant info for our test setup is highlighted with '<===' on the right.
+
+2.1) PostgreSQL
+
+2.1.1) Add to the odbc.ini file a block of the form
+
+[example] <===
+Description = Example for OpenLDAP's back-sql
+Driver = PostgreSQL
+Trace = No
+Database = example <===
+Servername = localhost
+UserName = manager <===
+Password = secret <===
+Port = 5432
+;Protocol = 6.4
+ReadOnly = No
+RowVersioning = No
+ShowSystemTables = No
+ShowOidColumn = No
+FakeOidIndex = No
+ConnSettings =
+
+2.1.2) Add to the odbcinst.ini file a block of the form
+
+[PostgreSQL]
+Description = ODBC for PostgreSQL
+Driver = /usr/lib/libodbcpsql.so
+Setup = /usr/lib/libodbcpsqlS.so
+FileUsage = 1
+
+2.2) MySQL
+
+2.2.1) Add to the odbc.ini file a block of the form
+
+[example] <===
+Description = Example for OpenLDAP's back-sql
+Driver = MySQL
+Trace = No
+Database = example <===
+Servername = localhost
+UserName = manager <===
+Password = secret <===
+ReadOnly = No
+RowVersioning = No
+ShowSystemTables = No
+ShowOidColumn = No
+FakeOidIndex = No
+ConnSettings =
+SOCKET = /var/lib/mysql/mysql.sock
+
+2.2.2) Add to the odbcinst.ini file a block of the form
+
+[MySQL]
+Description = ODBC for MySQL
+Driver = /usr/lib/libmyodbc.so
+FileUsage = 1
+
+2.3) IBM db2
+[n.a.]
+
+3) The RDBMS must be setup; examples are provided for my installations
+of PostgreSQL and MySQL, but details may change; other RDBMSes should
+be configured in a similar manner, you need to find out the details by
+reading their documentation.
+
+3.1) PostgreSQL
+
+3.1.1) Start the server
+on RedHat:
+[root@localhost]# service postgresql start
+on other systems: read the docs...
+
+3.1.2) Create the database:
+[root@localhost]# su - postgres
+[postgres@localhost]$ createdb example
+
+3.1.3) Create the user:
+[root@localhost]# su - postgres
+[postgres@localhost]$ psql example
+example=> create user manager with password 'secret';
+example=> <control-D>
+
+3.1.4) Populate the database:
+[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/pgsql/
+[root@localhost]# psql -U manager -W example
+example=> <control-D>
+[root@localhost]# psql -U manager example < backsql_create.sql
+[root@localhost]# psql -U manager example < testdb_create.sql
+[root@localhost]# psql -U manager example < testdb_data.sql
+[root@localhost]# psql -U manager example < testdb_metadata.sql
+
+3.1.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=pgsql ./run sql-test000
+
+3.2) MySQL
+
+3.2.1) Start the server
+on RedHat:
+[root@localhost]# service mysqld start
+on other systems: read the docs...
+
+3.2.2) Create the database:
+[root@localhost]# mysqladmin -u root -p create example
+(hit <return> for the empty password).
+
+3.2.3) Create the user:
+[root@localhost]# mysql -u root -p example
+(hit <return> for the empty password)
+mysql> grant all privileges on *.* \
+ to 'manager'@'localhost' identified by 'secret' with grant option;
+mysql> exit;
+
+3.2.4) Populate the database:
+[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/mysql/
+[root@localhost]# mysql -u manager -p example < backsql_create.sql
+[root@localhost]# mysql -u manager -p example < testdb_create.sql
+[root@localhost]# mysql -u manager -p example < testdb_data.sql
+[root@localhost]# mysql -u manager -p example < testdb_metadata.sql
+
+3.2.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=mysql ./run sql-test000
+
+3.3) IBM db2
+[n.a.]
+
+3.3.1) Start the server:
+
+3.3.2) Create the database:
+
+3.3.3) Create the user:
+
+3.3.4) Populate the database:
+connect to the database as user manager, and execute the test files
+in auto-commit mode (-c)
+[root@localhost]# su - manager
+[manager@localhost]$ db2 "connect to example user manager using secret"
+[manager@localhost]$ db2 -ctvf backsql_create.sql
+[manager@localhost]$ db2 -ctvf testdb_create.sql
+[manager@localhost]$ db2 -ctvf testdb_data.sql
+[manager@localhost]$ db2 -ctvf testdb_metadata.sql
+[manager@localhost]$ db2 "connect reset"
+
+3.3.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=ibmdb2 ./run sql-test000
+
+4) Cleanup:
+The test is basically readonly; this can be performed by all RDBMSes
+(listed above).
+
+There is another test, sql-test900-write, which is currently enabled
+only for PostgreSQL and IBM db2. Note that after a successful run
+of the write test, the database is no longer in the correct state
+to restart either of the tests, and step 3.X.4 needs to be re-run first.
+
+More tests are to come; PostgreSQL is known to allow a full reload
+of the test database starting from an empty database.
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql
new file mode 100644
index 0000000..cb2856b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql
@@ -0,0 +1,59 @@
+drop table ldap_oc_mappings;
+create table ldap_oc_mappings
+ (
+ id integer not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ create_keyval varchar(255),
+ delete_proc varchar(255),
+ expect_return integer not null
+);
+
+drop table ldap_attr_mappings;
+create table ldap_attr_mappings
+ (
+ id integer not null primary key,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order integer not null,
+ expect_return integer not null
+);
+
+drop table ldap_entries;
+create table ldap_entries
+ (
+ id integer not null primary key,
+ dn varchar(255) not null,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ parent int NOT NULL ,
+ keyval int NOT NULL
+);
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+drop table ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql
new file mode 100644
index 0000000..49e7e3a
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE ldap_referrals;
+DROP TABLE ldap_entry_objclasses;
+DROP TABLE ldap_attr_mappings;
+DROP TABLE ldap_entries;
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf
new file mode 100644
index 0000000..f6c1613
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf
@@ -0,0 +1,36 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_db2
+dbuser db2inst1
+dbpasswd ibmdb2
+subtree_cond "upper(ldap_entries.dn) LIKE CONCAT('%',?)"
+insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)"
+upper_func "upper"
+upper_needs_cast "yes"
+create_needs_select "yes"
+has_ldapinfo_dn_ru "no"
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql
new file mode 100644
index 0000000..b6e850c
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql
@@ -0,0 +1,75 @@
+drop table persons;
+CREATE TABLE persons (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ password varchar(64)
+);
+
+drop table institutes;
+CREATE TABLE institutes (
+ id int NOT NULL,
+ name varchar(255)
+);
+
+drop table documents;
+CREATE TABLE documents (
+ id int NOT NULL,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+);
+
+drop table authors_docs;
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL
+);
+
+drop table phones;
+CREATE TABLE phones (
+ id int NOT NULL ,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+);
+
+drop table referrals;
+CREATE TABLE referrals (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ url varchar(255) NOT NULL
+);
+
+
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql
new file mode 100644
index 0000000..7bef374
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql
@@ -0,0 +1,18 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql
new file mode 100644
index 0000000..0b0d1c2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql
@@ -0,0 +1,123 @@
+--mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- create_keyval a query that returns the id of the last inserted entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','INSERT INTO persons (id,name,surname) VALUES ((SELECT max(id)+1 FROM persons),'''','''')',
+ 'SELECT max(id) FROM persons','DELETE FROM persons WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (2,'document','documents','id','INSERT INTO documents (id,title,abstract) VALUES ((SELECT max(id)+1 FROM documents),'''','''')',
+ 'SELECT max(id) FROM documents','DELETE FROM documents WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (3,'organization','institutes','id','INSERT INTO institutes (id,name) VALUES ((SELECT max(id)+1 FROM institutes),'''')',
+ 'SELECT max(id) FROM institutes','DELETE FROM institutes WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (4,'referral','referrals','id','INSERT INTO referrals (id,name,url) VALUES ((SELECT max(id)+1 FROM referrals),'''','''')',
+ 'SELECT max(id) FROM referrals','DELETE FROM referrals WHERE id=?',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','INSERT INTO phones (id,phone,pers_id) VALUES ((SELECT max(id)+1 FROM phones),?,?)',
+ 'DELETE FROM phones WHERE phone=? AND pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?',
+ 'UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255)))=ucase(dn)),?)',
+ 'DELETE FROM authors_docs WHERE pers_id=(SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255))=ucase(dn)) AND doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''||rtrim(cast(documents.id AS CHAR(16)))','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lcase(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?',NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject');
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql
new file mode 100644
index 0000000..1f1f6d2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql
@@ -0,0 +1,100 @@
+create table ldap_oc_mappings (
+ id int identity (1, 1) not null ,
+ name varchar (64) not null ,
+ keytbl varchar (64) not null ,
+ keycol varchar (64) not null ,
+ create_proc varchar (255) NULL ,
+ delete_proc varchar (255) NULL,
+ expect_return int not null
+)
+GO
+
+alter table ldap_oc_mappings add
+ constraint pk_ldap_oc_mappings primary key
+ (
+ id
+ )
+GO
+
+
+alter table ldap_oc_mappings add
+ constraint unq1_ldap_oc_mappings unique
+ (
+ name
+ )
+GO
+
+
+create table ldap_attr_mappings (
+ id int identity (1, 1) not null ,
+ oc_map_id int not null references ldap_oc_mappings(id),
+ name varchar (255) not null ,
+ sel_expr varchar (255) not null ,
+ sel_expr_u varchar(255),
+ from_tbls varchar (255) not null ,
+ join_where varchar (255) NULL ,
+ add_proc varchar (255) NULL ,
+ delete_proc varchar (255) NULL ,
+ param_order int not null,
+ expect_return int not null
+)
+GO
+
+alter table ldap_attr_mappings add
+ constraint pk_ldap_attr_mappings primary key
+ (
+ id
+ )
+GO
+
+
+create table ldap_entries (
+ id int identity (1, 1) not null ,
+ dn varchar (255) not null ,
+ oc_map_id int not null references ldap_oc_mappings(id),
+ parent int not null ,
+ keyval int not null
+)
+GO
+
+
+alter table ldap_entries add
+ constraint pk_ldap_entries primary key
+ (
+ id
+ )
+GO
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ )
+GO
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ )
+GO
+
+
+create table ldap_referrals
+ (
+ entry_id int not null references ldap_entries(id),
+ url text not null
+)
+GO
+
+create index entry_idx on ldap_referrals(entry_id);
+
+create table ldap_entry_objclasses
+ (
+ entry_id int not null references ldap_entries(id),
+ oc_name varchar(64)
+ )
+GO
+
+create index entry_idx on ldap_entry_objclasses(entry_id);
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql
new file mode 100644
index 0000000..0e888b3
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql
@@ -0,0 +1,14 @@
+drop table ldap_attr_mappings
+GO
+
+drop table ldap_referrals
+GO
+
+drop table ldap_entry_objclasses
+GO
+
+drop table ldap_entries
+GO
+
+drop table ldap_oc_mappings
+GO
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf
new file mode 100644
index 0000000..c3032f2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf
@@ -0,0 +1,30 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include ./schema/core.schema
+include ./schema/cosine.schema
+include ./schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile ./slapd.pid
+argsfile ./slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_mssql
+dbuser ldap
+dbpasswd ldap
+subtree_cond "ldap_entries.dn LIKE '%'+?"
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql
new file mode 100644
index 0000000..2034afd
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql
@@ -0,0 +1,74 @@
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL ,
+ doc_id int NOT NULL
+)
+GO
+
+CREATE TABLE documents (
+ id int IDENTITY (1, 1) NOT NULL ,
+ abstract varchar (255) NULL ,
+ title varchar (255) NULL ,
+ body binary (255) NULL
+)
+GO
+
+CREATE TABLE institutes (
+ id int IDENTITY (1, 1) NOT NULL ,
+ name varchar (255) NOT NULL
+)
+GO
+
+
+CREATE TABLE persons (
+ id int IDENTITY (1, 1) NOT NULL ,
+ name varchar (255) NULL ,
+ surname varchar (255) NULL ,
+ password varchar (64) NULL
+)
+GO
+
+CREATE TABLE phones (
+ id int IDENTITY (1, 1) NOT NULL ,
+ phone varchar (255) NOT NULL ,
+ pers_id int NOT NULL
+)
+GO
+
+ALTER TABLE authors_docs WITH NOCHECK ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+GO
+
+ALTER TABLE documents WITH NOCHECK ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ )
+GO
+
+ALTER TABLE institutes WITH NOCHECK ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ )
+GO
+
+
+ALTER TABLE persons WITH NOCHECK ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ )
+GO
+
+ALTER TABLE phones WITH NOCHECK ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ )
+GO
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql
new file mode 100644
index 0000000..21a51ef
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql
@@ -0,0 +1,24 @@
+set IDENTITY_INSERT institutes ON
+insert into institutes (id,name) values (1,'Example')
+set IDENTITY_INSERT institutes OFF
+
+set IDENTITY_INSERT persons ON
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit')
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy')
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein')
+set IDENTITY_INSERT persons OFF
+
+set IDENTITY_INSERT phones ON
+insert into phones (id,phone,pers_id) values (1,'332-2334',1)
+insert into phones (id,phone,pers_id) values (2,'222-3234',1)
+insert into phones (id,phone,pers_id) values (3,'545-4563',2)
+set IDENTITY_INSERT phones OFF
+
+set IDENTITY_INSERT documents ON
+insert into documents (id,abstract,title) values (1,'abstract1','book1')
+insert into documents (id,abstract,title) values (2,'abstract2','book2')
+set IDENTITY_INSERT documents OFF
+
+insert into authors_docs (pers_id,doc_id) values (1,1)
+insert into authors_docs (pers_id,doc_id) values (1,2)
+insert into authors_docs (pers_id,doc_id) values (2,1)
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql
new file mode 100644
index 0000000..4842ed8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql
@@ -0,0 +1,39 @@
+drop procedure create_person
+drop procedure set_person_name
+drop procedure delete_phone
+drop procedure add_phone
+drop procedure make_doc_link
+drop procedure del_doc_link
+drop procedure delete_person
+
+drop procedure create_org
+drop procedure set_org_name
+drop procedure delete_org
+
+drop procedure create_document
+drop procedure set_doc_title
+drop procedure set_doc_abstract
+drop procedure make_author_link
+drop procedure del_author_link
+drop procedure delete_document
+
+if exists (select * from sysobjects where id = object_id(N'authors_docs') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table authors_docs
+GO
+
+if exists (select * from sysobjects where id = object_id(N'documents') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table documents
+GO
+
+if exists (select * from sysobjects where id = object_id(N'institutes') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table institutes
+GO
+
+if exists (select * from sysobjects where id = object_id(N'persons') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table persons
+GO
+
+if exists (select * from sysobjects where id = object_id(N'phones') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table phones
+GO
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql
new file mode 100644
index 0000000..e087523
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql
@@ -0,0 +1,198 @@
+-- mappings
+
+
+SET IDENTITY_INSERT ldap_oc_mappings ON
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','{call create_person(?)}','{call delete_person(?)}',0)
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id','{call create_document(?)}','{call delete_document(?)}',0)
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id','{call create_org(?)}','{call delete_org(?)}',0)
+SET IDENTITY_INSERT ldap_oc_mappings OFF
+
+
+SET IDENTITY_INSERT ldap_attr_mappings ON
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name+'' ''+persons.surname','persons',NULL,
+ NULL,NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','{call add_phone(?,?)}',
+ '{call delete_phone(?,?)}',0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,
+ '{call set_person_name(?,?)}',NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,
+ '{call set_person_surname(?,?)}',NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',
+ '{call set_person_password(?,?)}','call del_person_password(?,?)',0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'{call set_doc_abstract(?,?)}',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL, '{call set_doc_title(?,?)}',
+ NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)',
+ 'DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''+text(documents.id)','documents',
+ NULL,NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ '{call set_org_name(?,?)}',NULL,3,0);
+
+SET IDENTITY_INSERT ldap_attr_mappings OFF
+
+-- entries
+
+SET IDENTITY_INSERT ldap_entries ON
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2)
+
+SET IDENTITY_INSERT ldap_entries OFF
+
+-- referrals
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012/');
+
+-- support procedures
+
+SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON
+GO
+
+
+CREATE PROCEDURE create_person @@keyval int OUTPUT AS
+INSERT INTO example.persons (name) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.persons)
+GO
+
+CREATE PROCEDURE delete_person @keyval int AS
+DELETE FROM example.phones WHERE pers_id=@keyval;
+DELETE FROM example.authors_docs WHERE pers_id=@keyval;
+DELETE FROM example.persons WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE create_org @@keyval int OUTPUT AS
+INSERT INTO example.institutes (name) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.institutes)
+GO
+
+CREATE PROCEDURE delete_org @keyval int AS
+DELETE FROM example.institutes WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE create_document @@keyval int OUTPUT AS
+INSERT INTO example.documents (title) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.documents)
+GO
+
+CREATE PROCEDURE delete_document @keyval int AS
+DELETE FROM example.authors_docs WHERE doc_id=@keyval;
+DELETE FROM example.documents WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE add_phone @pers_id int, @phone varchar(255) AS
+INSERT INTO example.phones (pers_id,phone) VALUES (@pers_id,@phone)
+GO
+
+CREATE PROCEDURE delete_phone @keyval int,@phone varchar(64) AS
+DELETE FROM example.phones WHERE pers_id=@keyval AND phone=@phone;
+GO
+
+CREATE PROCEDURE set_person_name @keyval int, @new_name varchar(255) AS
+UPDATE example.persons SET name=@new_name WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_person_surname @keyval int, @new_surname varchar(255) AS
+UPDATE example.persons SET surname=@new_surname WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_org_name @keyval int, @new_name varchar(255) AS
+UPDATE example.institutes SET name=@new_name WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_doc_title @keyval int, @new_title varchar(255) AS
+UPDATE example.documents SET title=@new_title WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_doc_abstract @keyval int, @new_abstract varchar(255) AS
+UPDATE example.documents SET abstract=@new_abstract WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE make_author_link @keyval int, @author_dn varchar(255) AS
+DECLARE @per_id int;
+SET @per_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=1 AND dn=@author_dn);
+IF NOT (@per_id IS NULL)
+ INSERT INTO example.authors_docs (doc_id,pers_id) VALUES (@keyval,@per_id);
+GO
+
+CREATE PROCEDURE make_doc_link @keyval int, @doc_dn varchar(255) AS
+DECLARE @doc_id int;
+SET @doc_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=2 AND dn=@doc_dn);
+IF NOT (@doc_id IS NULL)
+ INSERT INTO example.authors_docs (pers_id,doc_id) VALUES (@keyval,@doc_id);
+GO
+
+CREATE PROCEDURE del_doc_link @keyval int, @doc_dn varchar(255) AS
+DECLARE @doc_id int;
+SET @doc_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=2 AND dn=@doc_dn);
+IF NOT (@doc_id IS NULL)
+DELETE FROM example.authors_docs WHERE pers_id=@keyval AND doc_id=@doc_id;
+GO
+
+CREATE PROCEDURE del_author_link @keyval int, @author_dn varchar(255) AS
+DECLARE @per_id int;
+SET @per_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=1 AND dn=@author_dn);
+IF NOT (@per_id IS NULL)
+ DELETE FROM example.authors_docs WHERE doc_id=@keyval AND pers_id=@per_id;
+GO
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql
new file mode 100644
index 0000000..b2029c4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql
@@ -0,0 +1,58 @@
+drop table if exists ldap_oc_mappings;
+create table ldap_oc_mappings
+ (
+ id integer unsigned not null primary key auto_increment,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return tinyint not null
+);
+
+drop table if exists ldap_attr_mappings;
+create table ldap_attr_mappings
+ (
+ id integer unsigned not null primary key auto_increment,
+ oc_map_id integer unsigned not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order tinyint not null,
+ expect_return tinyint not null
+);
+
+drop table if exists ldap_entries;
+create table ldap_entries
+ (
+ id integer unsigned not null primary key auto_increment,
+ dn varchar(255) not null,
+ oc_map_id integer unsigned not null references ldap_oc_mappings(id),
+ parent int NOT NULL ,
+ keyval int NOT NULL
+);
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+drop table if exists ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer unsigned not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql
new file mode 100644
index 0000000..a81fa8b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql
@@ -0,0 +1,7 @@
+DROP TABLE IF EXISTS ldap_entry_objclasses;
+
+DROP TABLE IF EXISTS ldap_attr_mappings;
+
+DROP TABLE IF EXISTS ldap_entries;
+
+DROP TABLE IF EXISTS ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf
new file mode 100644
index 0000000..8f6e4e1
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf
@@ -0,0 +1,32 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_mysql
+dbuser root
+dbpasswd
+subtree_cond "ldap_entries.dn LIKE CONCAT('%',?)"
+insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)"
+has_ldapinfo_dn_ru no
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql
new file mode 100644
index 0000000..b35261b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql
@@ -0,0 +1,86 @@
+drop table if exists persons;
+CREATE TABLE persons (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ password varchar(64)
+);
+
+drop table if exists institutes;
+CREATE TABLE institutes (
+ id int NOT NULL,
+ name varchar(255)
+);
+
+drop table if exists documents;
+CREATE TABLE documents (
+ id int NOT NULL,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+);
+
+drop table if exists authors_docs;
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL
+);
+
+drop table if exists phones;
+CREATE TABLE phones (
+ id int NOT NULL ,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+);
+
+drop table if exists certs;
+CREATE TABLE certs (
+ id int NOT NULL ,
+ cert LONGBLOB NOT NULL,
+ pers_id int NOT NULL
+);
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE certs ADD
+ CONSTRAINT PK_certs PRIMARY KEY
+ (
+ id
+ );
+
+drop table if exists referrals;
+CREATE TABLE referrals (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ url varchar(255) NOT NULL
+);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql
new file mode 100644
index 0000000..0ccbfb7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql
@@ -0,0 +1,21 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
+
+insert into certs (id,cert,pers_id) values (1,UNHEX('3082036b308202d4a003020102020102300d06092a864886f70d01010405003077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d301e170d3033313031373136333331395a170d3034313031363136333331395a307e310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311830160603550403130f557273756c612048616d7073746572311f301d06092a864886f70d01090116107568616d406578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100eec60a7910b57d2e687158ca55eea738d36f10413dfecf31435e1aeeb9713b8e2da7dd2dde6bc6cec03b4987eaa7b037b9eb50e11c71e58088cc282883122cd8329c6f24f6045e6be9d21b9190c8292998267a5f7905292de936262747ab4b76a88a63872c41629a69d32e894d44c896a8d06fab0a1bc7de343c6c1458478f290203010001a381ff3081fc30090603551d1304023000302c06096086480186f842010d041f161d4f70656e53534c2047656e657261746564204365727469666963617465301d0603551d0e04160414a323de136c19ae0c479450e882dfb10ad147f45e3081a10603551d2304819930819680144b6f211a3624d290f943b053472d7de1c0e69823a17ba4793077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d820100300d06092a864886f70d010104050003818100881470045bdce95660d6e6af59e6a844aec4b9f5eaea88d4eb7a5a47080afa64750f81a3e47d00fd39c69a17a1c66d29d36f06edc537107f8c592239c2d4da55fb3f1d488e7b2387ad2a551cbd1ceb070ae9e020a9467275cb28798abb4cbfff98ddb3f1e7689b067072392511bb08125b5bec2bc207b7b6b275c47248f29acd'),3);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql
new file mode 100644
index 0000000..7c5e9e7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE IF EXISTS persons;
+DROP TABLE IF EXISTS institutes;
+DROP TABLE IF EXISTS documents;
+DROP TABLE IF EXISTS authors_docs;
+DROP TABLE IF EXISTS phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql
new file mode 100644
index 0000000..d7e88e4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql
@@ -0,0 +1,125 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (4,'referral','referrals','id',NULL,NULL,0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn',"concat(persons.name,' ',persons.surname)",'persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id',NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','concat(''document '',documents.id)','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (13,4,'ou','referrals.name','referrals',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (14,4,'ref','referrals.url','referrals',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (15,1,'userCertificate','certs.cert','persons,certs',
+ 'certs.pers_id=persons.id',NULL,NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'pkiUser');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (7,'extensibleObject');
+
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql
new file mode 100644
index 0000000..2e4e6ec
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql
@@ -0,0 +1,90 @@
+create table ldap_oc_mappings (
+ id number not null ,
+ name varchar2(64) not null ,
+ keytbl varchar2(64) not null ,
+ keycol varchar2(64) not null ,
+ create_proc varchar2(255),
+ delete_proc varchar2(255),
+ expect_return number not null
+);
+
+alter table ldap_oc_mappings add
+ constraint PK_ldap_oc_mappings primary key
+ (
+ id
+ );
+
+alter table ldap_oc_mappings add
+ constraint unq_ldap_oc_mappings unique
+ (
+ name
+ );
+
+create table ldap_attr_mappings (
+ id number not null,
+ oc_map_id number not null references ldap_oc_mappings(id),
+ name varchar2(255) not null,
+ sel_expr varchar2(255) not null,
+ sel_expr_u varchar2(255),
+ from_tbls varchar2(255) not null,
+ join_where varchar2(255),
+ add_proc varchar2(255),
+ delete_proc varchar2(255),
+ param_order number not null,
+ expect_return number not null
+);
+
+alter table ldap_attr_mappings add
+ constraint pk_ldap_attr_mappings primary key
+ (
+ id
+ );
+
+
+create table ldap_entries (
+ id number not null ,
+ dn varchar2(255) not null ,
+ dn_ru varchar2(255),
+ oc_map_id number not null references ldap_oc_mappings(id),
+ parent number not null ,
+ keyval number not null
+);
+
+alter table ldap_entries add
+ constraint PK_ldap_entries primary key
+ (
+ id
+ );
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+create sequence ldap_objclass_ids start with 1 increment by 1;
+
+create sequence ldap_attr_ids start with 1 increment by 1;
+
+create sequence ldap_entry_ids start with 1 increment by 1;
+
+create table ldap_referrals
+ (
+ entry_id number not null references ldap_entries(id),
+ url varchar(1023) not null
+);
+
+create table ldap_entry_objclasses
+ (
+ entry_id number not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
+quit
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql
new file mode 100644
index 0000000..19bb8b6
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql
@@ -0,0 +1,8 @@
+drop table ldap_attr_mappings;
+drop table ldap_entry_objclasses;
+drop table ldap_referrals;
+drop sequence ldap_entry_ids;
+drop sequence ldap_attr_ids;
+drop sequence ldap_objclass_ids;
+drop table ldap_entries;
+drop table ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf
new file mode 100644
index 0000000..cc195d9
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf
@@ -0,0 +1,32 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_ora8
+dbuser ldap
+dbpasswd ldap
+subtree_cond "UPPER(ldap_entries.dn) LIKE CONCAT('%',UPPER(?))"
+insentry_stmt "INSERT INTO ldap_entries (id,dn,oc_map_id,parent,keyval) VALUES (ldap_entry_ids.nextval,?,?,?,?)"
+upper_func UPPER
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql
new file mode 100644
index 0000000..710a5fa
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql
@@ -0,0 +1,68 @@
+CREATE TABLE persons (
+ id NUMBER NOT NULL,
+ name varchar2(255) NOT NULL,
+ surname varchar2(255) NOT NULL,
+ password varchar2(64) NOT NULL
+);
+
+CREATE TABLE institutes (
+ id NUMBER NOT NULL,
+ name varchar2(255)
+);
+
+CREATE TABLE documents (
+ id NUMBER NOT NULL,
+ title varchar2(255) NOT NULL,
+ abstract varchar2(255)
+);
+
+CREATE TABLE authors_docs (
+ pers_id NUMBER NOT NULL,
+ doc_id NUMBER NOT NULL
+);
+
+CREATE TABLE phones (
+ id NUMBER NOT NULL ,
+ phone varchar2(255) NOT NULL ,
+ pers_id NUMBER NOT NULL
+);
+
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
+CREATE SEQUENCE person_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE document_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE institute_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE phone_ids START WITH 1 INCREMENT BY 1;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql
new file mode 100644
index 0000000..4fc1977
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql
@@ -0,0 +1,27 @@
+insert into institutes (id,name) values (institute_ids.nextval,'example');
+
+insert into persons (id,name,surname,password) values (person_ids.nextval,'Mitya','Kovalev','mit');
+
+insert into persons (id,name,surname) values (person_ids.nextval,'Torvlobnor','Puzdoy');
+
+insert into persons (id,name,surname) values (person_ids.nextval,'Akakiy','Zinberstein');
+
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'332-2334',1);
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'222-3234',1);
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'545-4563',2);
+
+
+insert into documents (id,abstract,title) values (document_ids.nextval,'abstract1','book1');
+
+insert into documents (id,abstract,title) values (document_ids.nextval,'abstract2','book2');
+
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+
+insert into authors_docs (pers_id,doc_id) values (1,2);
+
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql
new file mode 100644
index 0000000..0cf4463
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql
@@ -0,0 +1,25 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
+DROP SEQUENCE person_ids;
+DROP SEQUENCE institute_ids;
+DROP SEQUENCE document_ids;
+DROP SEQUENCE phone_ids;
+DROP PROCEDURE create_person;
+DROP PROCEDURE delete_person;
+DROP PROCEDURE add_phone;
+DROP PROCEDURE delete_phone;
+DROP PROCEDURE set_person_name;
+DROP PROCEDURE set_org_name;
+DROP PROCEDURE set_doc_title;
+DROP PROCEDURE set_doc_abstract;
+DROP PROCEDURE create_document;
+DROP PROCEDURE create_org;
+DROP PROCEDURE delete_document;
+DROP PROCEDURE delete_org;
+DROP PROCEDURE make_doc_link;
+DROP PROCEDURE del_doc_link;
+DROP PROCEDURE make_author_link;
+DROP PROCEDURE del_author_link;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql
new file mode 100644
index 0000000..354d7bd
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql
@@ -0,0 +1,252 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','call create_person(?)','call delete_person(?)',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id','call create_document(?)','call delete_document(?)',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id','call create_org(?)','call delete_org(?)',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','call add_phone(?,?)',
+ 'call delete_phone(?,?)',0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,'call set_person_name(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,'call set_person_surname(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons',
+ 'persons.password IS NOT NULL','call set_person_password(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'call set_doc_abstract(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,'call set_doc_title(?,?)',NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ '?=call make_author_link(?,?)','?=call del_author_link(?,?)',0,3);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,'call set_org_name(?,?)',NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries dcObject,ldap_entry_objclasses auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,0,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+-- referrals mapping: entries that should be treated as referrals are stored here
+-- entry_id the "ldap_entries.id" of the entry that should be treated as a referral
+-- url the URI of the referral
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012/');
+
+
+-- procedures
+-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion
+CREATE OR REPLACE PROCEDURE create_person(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO persons (id,name) VALUES (person_ids.nextval,' ');
+SELECT person_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_person(keyval IN NUMBER) AS
+BEGIN
+DELETE FROM phones WHERE pers_id=keyval;
+DELETE FROM authors_docs WHERE pers_id=keyval;
+DELETE FROM persons WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE create_org(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO institutes (id,name) VALUES (institute_ids.nextval,' ');
+SELECT institute_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_org(keyval IN NUMBER) AS
+BEGIN
+DELETE FROM institutes WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE create_document(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO documents (id,title) VALUES (document_ids.nextval,' ');
+SELECT document_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_document (keyval IN NUMBER) AS
+BEGIN
+DELETE FROM authors_docs WHERE doc_id=keyval;
+DELETE FROM documents WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE add_phone(pers_id IN NUMBER, phone IN varchar2) AS
+BEGIN
+INSERT INTO phones (id,pers_id,phone) VALUES (phone_ids.nextval,pers_id,phone);
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_phone(keyval IN NUMBER, phone IN varchar2) AS
+BEGIN
+DELETE FROM phones WHERE pers_id=keyval AND phone=phone;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_person_name(keyval IN NUMBER, new_name IN varchar2) AS
+BEGIN
+UPDATE persons SET name=new_name WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_org_name(keyval IN NUMBER, new_name IN varchar2) AS
+BEGIN
+UPDATE institutes SET name=new_name WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_doc_title (keyval IN NUMBER, new_title IN varchar2) AS
+BEGIN
+UPDATE documents SET title=new_title WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_doc_abstract (keyval IN NUMBER, new_abstract IN varchar2) AS
+BEGIN
+UPDATE documents SET abstract=new_abstract WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE FUNCTION make_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS
+per_id NUMBER;
+BEGIN
+SELECT keyval INTO per_id FROM ldap_entries
+ WHERE oc_map_id=1 AND dn=author_dn;
+IF NOT (per_id IS NULL) THEN
+ INSERT INTO authors_docs (doc_id,pers_id) VALUES (keyval,per_id);
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION make_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS
+docid NUMBER;
+BEGIN
+SELECT keyval INTO docid FROM ldap_entries
+ WHERE oc_map_id=2 AND dn=doc_dn;
+IF NOT (docid IS NULL) THEN
+ INSERT INTO authors_docs (pers_id,doc_id) VALUES (keyval,docid);
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION del_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS
+docid NUMBER;
+BEGIN
+SELECT keyval INTO docid FROM ldap_entries
+ WHERE oc_map_id=2 AND dn=doc_dn;
+IF NOT (docid IS NULL) THEN
+ DELETE FROM authors_docs WHERE pers_id=keyval AND doc_id=docid;
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION del_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS
+per_id NUMBER;
+BEGIN
+SELECT keyval INTO per_id FROM ldap_entries
+ WHERE oc_map_id=1 AND dn=author_dn;
+
+IF NOT (per_id IS NULL) THEN
+ DELETE FROM authors_docs WHERE doc_id=keyval AND pers_id=per_id;
+ RETURN 1;
+END IF;
+ RETURN 0;
+END;
+/
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql
new file mode 100644
index 0000000..a4baa70
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql
@@ -0,0 +1,50 @@
+drop table ldap_oc_mappings;
+drop sequence ldap_oc_mappings_id_seq;
+create table ldap_oc_mappings
+ (
+ id serial not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return int not null
+);
+
+drop table ldap_attr_mappings;
+drop sequence ldap_attr_mappings_id_seq;
+create table ldap_attr_mappings
+ (
+ id serial not null primary key,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order int not null,
+ expect_return int not null
+);
+
+drop table ldap_entries;
+drop sequence ldap_entries_id_seq;
+create table ldap_entries
+ (
+ id serial not null primary key,
+ dn varchar(255) not null,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ parent int NOT NULL,
+ keyval int NOT NULL,
+ UNIQUE ( oc_map_id, keyval ),
+ UNIQUE ( dn )
+);
+
+drop table ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql
new file mode 100644
index 0000000..eff0a9e
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql
@@ -0,0 +1,4 @@
+DROP TABLE ldap_entry_objclasses;
+DROP TABLE ldap_attr_mappings;
+DROP TABLE ldap_entries;
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf
new file mode 100644
index 0000000..70a8dee
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf
@@ -0,0 +1,35 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname PostgreSQL
+dbuser postgres
+dbpasswd postgres
+insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)"
+upper_func "upper"
+strcast_func "text"
+concat_pattern "?||?"
+has_ldapinfo_dn_ru no
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql
new file mode 100644
index 0000000..e1c57e7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql
@@ -0,0 +1,55 @@
+drop table persons;
+drop sequence persons_id_seq;
+create table persons (
+ id serial not null primary key,
+ name varchar(255) not null,
+ surname varchar(255) not null,
+ password varchar(64)
+);
+
+drop table institutes;
+drop sequence institutes_id_seq;
+create table institutes (
+ id serial not null primary key,
+ name varchar(255)
+);
+
+drop table documents;
+drop sequence documents_id_seq;
+create table documents (
+ id serial not null primary key,
+ title varchar(255) not null,
+ abstract varchar(255)
+);
+
+drop table authors_docs;
+create table authors_docs (
+ pers_id int not null,
+ doc_id int not null,
+ primary key ( pers_id, doc_id )
+);
+
+drop table phones;
+drop sequence phones_id_seq;
+create table phones (
+ id serial not null primary key,
+ phone varchar(255) not null ,
+ pers_id int not null
+);
+
+drop table certs;
+drop sequence certs_id_seq;
+CREATE TABLE certs (
+ id int not null primary key,
+ cert bytea not null,
+ pers_id int not null
+);
+
+drop table referrals;
+drop sequence referrals_id_seq;
+create table referrals (
+ id serial not null primary key,
+ name varchar(255) not null,
+ url varchar(255) not null
+);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql
new file mode 100644
index 0000000..0e661d4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql
@@ -0,0 +1,21 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
+
+insert into certs (id,cert,pers_id) values (1,decode('MIIDazCCAtSgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjETMBEGA1UEAxMKRXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDMxMDE3MTYzMzE5WhcNMDQxMDE2MTYzMzE5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjEYMBYGA1UEAxMPVXJzdWxhIEhhbXBzdGVyMR8wHQYJKoZIhvcNAQkBFhB1aGFtQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDuxgp5ELV9LmhxWMpV7qc4028QQT3+zzFDXhruuXE7ji2n3S3ea8bOwDtJh+qnsDe561DhHHHlgIjMKCiDEizYMpxvJPYEXmvp0huRkMgpKZgmel95BSkt6TYmJ0erS3aoimOHLEFimmnTLolNRMiWqNBvqwobx940PGwUWEePKQIDAQABo4H/MIH8MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSjI94TbBmuDEeUUOiC37EK0Uf0XjCBoQYDVR0jBIGZMIGWgBRLbyEaNiTSkPlDsFNHLX3hwOaYI6F7pHkwdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHzAdBgNVBAoTFk9wZW5MREFQIEV4YW1wbGUsIEx0ZC4xEzARBgNVBAMTCkV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAIgUcARb3OlWYNbmr1nmqESuxLn16uqI1Ot6WkcICvpkdQ+Bo+R9AP05xpoXocZtKdNvBu3FNxB/jFkiOcLU2lX7Px1Ijnsjh60qVRy9HOsHCungIKlGcnXLKHmKu0y//5jds/HnaJsGcHI5JRG7CBJbW+wrwge3trJ1xHJI8prN','base64'),3);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql
new file mode 100644
index 0000000..c061ff8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql
@@ -0,0 +1,13 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
+DROP TABLE referrals;
+DROP FUNCTION create_person ();
+DROP FUNCTION update_person_cn (varchar, int);
+DROP FUNCTION add_phone (varchar, int);
+DROP FUNCTION create_doc ();
+DROP FUNCTION create_o ();
+DROP FUNCTION create_referral ();
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql
new file mode 100644
index 0000000..d645cf2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql
@@ -0,0 +1,146 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (1,'inetOrgPerson','persons','id','SELECT create_person()','DELETE FROM persons WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (2,'document','documents','id','SELECT create_doc()','DELETE FROM documents WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (3,'organization','institutes','id','SELECT create_o()','DELETE FROM institutes WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (4,'referral','referrals','id','SELECT create_referral()','DELETE FROM referrals WHERE id=?',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (1,1,'cn','text(persons.name||'' ''||persons.surname)','persons',NULL,'SELECT update_person_cn(?,?)','SELECT 1 FROM persons WHERE persons.name=? AND persons.id=? AND 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (2,1,'telephoneNumber','phones.phone','persons,phones','phones.pers_id=persons.id','SELECT add_phone(?,?)','DELETE FROM phones WHERE phone=? AND pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (3,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?','UPDATE persons SET name='''' WHERE (name=? OR name='''') AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (4,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?','UPDATE persons SET surname='''' WHERE (surname=? OR surname='''') AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?','UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons','seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',NULL,'DELETE from authors_docs WHERE authors_docs.doc_id=(SELECT documents.id FROM documents,ldap_entries AS seeAlso WHERE seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND seeAlso.dn=?) AND authors_docs.pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?','UPDATE documents SET abstract='''' WHERE abstract=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?','UPDATE documents SET title='''' WHERE title=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons','documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id','INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)','DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,'SELECT 1 FROM documents WHERE title=? AND id=? AND 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?','UPDATE institutes SET name='''' WHERE name=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass','institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',NULL,'SELECT 1 FROM institutes WHERE lower(name)=? AND id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?','SELECT 1 FROM referrals WHERE name=? AND id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?','SELECT 1 FROM referrals WHERE url=? and id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (15,1,'userCertificate','certs.cert','persons,certs','certs.pers_id=persons.id',NULL,NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (4,'pkiUser');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject');
+
+-- procedures
+-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion
+create function create_person () returns int
+as '
+ select setval (''persons_id_seq'', (select case when max(id) is null then 1 else max(id) end from persons));
+ insert into persons (id,name,surname)
+ values ((select case when max(id) is null then 1 else nextval(''persons_id_seq'') end from persons),'''','''');
+ select max(id) from persons
+' language 'sql';
+
+create function update_person_cn (varchar, int) returns int
+as '
+ update persons set name = (
+ select case
+ when position('' '' in $1) = 0 then $1
+ else substr($1, 1, position('' '' in $1) - 1)
+ end
+ ),surname = (
+ select case
+ when position('' '' in $1) = 0 then ''''
+ else substr($1, position('' '' in $1) + 1)
+ end
+ ) where id = $2;
+ select $2 as return
+' language 'sql';
+
+create function add_phone (varchar, int) returns int
+as '
+ select setval (''phones_id_seq'', (select case when max(id) is null then 1 else max(id) end from phones));
+ insert into phones (id,phone,pers_id)
+ values (nextval(''phones_id_seq''),$1,$2);
+ select max(id) from phones
+' language 'sql';
+
+create function create_doc () returns int
+as '
+ select setval (''documents_id_seq'', (select case when max(id) is null then 1 else max(id) end from documents));
+ insert into documents (id,title,abstract)
+ values ((select case when max(id) is null then 1 else nextval(''documents_id_seq'') end from documents),'''','''');
+ select max(id) from documents
+' language 'sql';
+
+create function create_o () returns int
+as '
+ select setval (''institutes_id_seq'', (select case when max(id) is null then 1 else max(id) end from institutes));
+ insert into institutes (id,name)
+ values ((select case when max(id) is null then 1 else nextval(''institutes_id_seq'') end from institutes),'''');
+ select max(id) from institutes
+' language 'sql';
+
+create function create_referral () returns int
+as '
+ select setval (''referrals_id_seq'', (select case when max(id) is null then 1 else max(id) end from referrals));
+ insert into referrals (id,name,url)
+ values ((select case when max(id) is null then 1 else nextval(''referrals_id_seq'') end from referrals),'''','''');
+ select max(id) from referrals
+' language 'sql';
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql
new file mode 100644
index 0000000..055e9df
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql
@@ -0,0 +1,66 @@
+
+create table ldap_oc_mappings
+ (
+ id integer not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return tinyint not null
+);
+
+create table ldap_attr_mappings
+ (
+ id integer not null primary key,
+ oc_map_id integer not null,
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order tinyint not null,
+ expect_return tinyint not null,
+ foreign key (oc_map_id) references ldap_oc_mappings(id)
+);
+
+create table ldap_entries
+ (
+ id integer not null primary key,
+ dn varchar(255) not null,
+ dn_ru varchar(255),
+ oc_map_id integer not null,
+ parent int NOT NULL ,
+ keyval int NOT NULL,
+ foreign key (oc_map_id) references ldap_oc_mappings(id)
+);
+
+create index ldap_entriesx1 on ldap_entries(dn_ru);
+
+create unique index unq1_ldap_entries on ldap_entries
+ (
+ oc_map_id,
+ keyval
+ );
+
+create unique index unq2_ldap_entries on ldap_entries
+ (
+ dn
+ );
+
+create table ldap_referrals
+ (
+ entry_id integer not null,
+ url varchar(4096) not null,
+ foreign key (entry_id) references ldap_entries(id)
+);
+
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null,
+ oc_name varchar(64),
+ foreign key (entry_id) references ldap_entries(id)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql
new file mode 100644
index 0000000..7aa0b83
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql
@@ -0,0 +1,9 @@
+DROP TABLE ldap_referrals;
+
+DROP TABLE ldap_entry_objclasses;
+
+DROP TABLE ldap_attr_mappings;
+
+DROP TABLE ldap_entries;
+
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh
new file mode 100755
index 0000000..947db21
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh
@@ -0,0 +1,4 @@
+ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_data.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_metadata.sql
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile
new file mode 100644
index 0000000..1b0b1ee
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile
@@ -0,0 +1,48 @@
+## Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved.
+## COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+
+#
+# Build TimesTen ODBC Sample Programs for Solaris 2.5.1.
+# (c) Copyright 1996-1998, TimesTen Performance Software.
+# All rights reserved.
+## Note: This file was contributed by Sam Drake of TimesTen Performance
+## Software for use and redistribution as an integral part of
+## OpenLDAP Software. -Kdz
+
+CPLUSPLUS = CC
+TTCLASSES = ../../../../../../../../../cs/classes
+ODBC = /opt/TimesTen4.1/32
+CFLAGS = -g -I$(ODBC)/include -I. -I$(TTCLASSES) -DUNIX
+LDFLAGS = -g
+DIRLIBS = $(TTCLASSES)/ttclasses.a -L $(ODBC)/lib -R $(ODBC)/lib -ltten -lpthread -lm -lrt
+XLALIB = -L $(ODBC)/lib -lxla
+
+DIRPROGS= dnreverse
+
+DNREVERSE= dnreverse.o
+
+#
+# Top-level targets
+#
+
+all: $(DIRPROGS)
+
+direct: $(DIRPROGS)
+
+clean:
+ rm -rf $(DIRPROGS) *.o
+
+
+#
+# Direct-linked programs
+#
+
+dnreverse: $(DNREVERSE)
+ $(CPLUSPLUS) -o dnreverse $(LDFLAGS) $(DNREVERSE) $(DIRLIBS) $(XLALIB)
+
+#
+# .o files
+#
+
+dnreverse.o: dnreverse.cpp
+ $(CPLUSPLUS) $(CFLAGS) -c dnreverse.cpp
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp
new file mode 100644
index 0000000..7407b4e
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp
@@ -0,0 +1,387 @@
+// Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved.
+// COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+
+// (c) Copyright 1999-2001 TimesTen Performance Software. All rights reserved.
+
+//// Note: This file was contributed by Sam Drake of TimesTen Performance
+//// Software for use and redistribution as an integral part of
+//// OpenLDAP Software. -Kdz
+
+#include <stdlib.h>
+
+#include <TTConnectionPool.h>
+#include <TTConnection.h>
+#include <TTCmd.h>
+#include <TTXla.h>
+
+#include <signal.h>
+
+TTConnectionPool pool;
+TTXlaConnection conn;
+TTConnection conn2;
+TTCmd assignDn_ru;
+TTCmd getNullDNs;
+
+//----------------------------------------------------------------------
+// This class contains all the logic to be implemented whenever
+// the SCOTT.MYDATA table is changed. This is the table that is
+// created by "sample.cpp", one of the other TTClasses demos.
+// That application should be executed before this one in order to
+// create and populate the table.
+//----------------------------------------------------------------------
+
+class LDAPEntriesHandler: public TTXlaTableHandler {
+private:
+ // Definition of the columns in the table
+ int Id;
+ int Dn;
+ int Oc_map_id;
+ int Parent;
+ int Keyval;
+ int Dn_ru;
+
+protected:
+
+public:
+ LDAPEntriesHandler(TTXlaConnection& conn, const char* ownerP, const char* nameP);
+ ~LDAPEntriesHandler();
+
+ virtual void HandleDelete(ttXlaUpdateDesc_t*);
+ virtual void HandleInsert(ttXlaUpdateDesc_t*);
+ virtual void HandleUpdate(ttXlaUpdateDesc_t*);
+
+ static void ReverseAndUpper(char* dnP, int id, bool commit=true);
+
+};
+
+LDAPEntriesHandler::LDAPEntriesHandler(TTXlaConnection& conn,
+ const char* ownerP, const char* nameP) :
+ TTXlaTableHandler(conn, ownerP, nameP)
+{
+ Id = Dn = Oc_map_id = Parent = Keyval = Dn_ru = -1;
+
+ // We are looking for several particular named columns. We need to get
+ // the ordinal position of the columns by name for later use.
+
+ Id = tbl.getColNumber("ID");
+ if (Id < 0) {
+ cerr << "target table has no 'ID' column" << endl;
+ exit(1);
+ }
+ Dn = tbl.getColNumber("DN");
+ if (Dn < 0) {
+ cerr << "target table has no 'DN' column" << endl;
+ exit(1);
+ }
+ Oc_map_id = tbl.getColNumber("OC_MAP_ID");
+ if (Oc_map_id < 0) {
+ cerr << "target table has no 'OC_MAP_ID' column" << endl;
+ exit(1);
+ }
+ Parent = tbl.getColNumber("PARENT");
+ if (Parent < 0) {
+ cerr << "target table has no 'PARENT' column" << endl;
+ exit(1);
+ }
+ Keyval = tbl.getColNumber("KEYVAL");
+ if (Keyval < 0) {
+ cerr << "target table has no 'KEYVAL' column" << endl;
+ exit(1);
+ }
+ Dn_ru = tbl.getColNumber("DN_RU");
+ if (Dn_ru < 0) {
+ cerr << "target table has no 'DN_RU' column" << endl;
+ exit(1);
+ }
+
+}
+
+LDAPEntriesHandler::~LDAPEntriesHandler()
+{
+
+}
+
+void LDAPEntriesHandler::ReverseAndUpper(char* dnP, int id, bool commit)
+{
+ TTStatus stat;
+ char dn_rn[512];
+ int i;
+ int j;
+
+ // Reverse and upper case the given DN
+
+ for ((j=0, i = strlen(dnP)-1); i > -1; (j++, i--)) {
+ dn_rn[j] = toupper(*(dnP+i));
+ }
+ dn_rn[j] = '\0';
+
+
+ // Update the database
+
+ try {
+ assignDn_ru.setParam(1, (char*) &dn_rn[0]);
+ assignDn_ru.setParam(2, id);
+ assignDn_ru.Execute(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error updating id " << id << " ('" << dnP << "' to '"
+ << dn_rn << "'): " << stat;
+ exit(1);
+ }
+
+ // Commit the transaction
+
+ if (commit) {
+ try {
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error committing update: " << stat;
+ exit(1);
+ }
+ }
+
+}
+
+
+
+void LDAPEntriesHandler::HandleInsert(ttXlaUpdateDesc_t* p)
+{
+ char* dnP;
+ int id;
+
+ row.Get(Dn, &dnP);
+ cerr << "DN '" << dnP << "': Inserted ";
+ row.Get(Id, &id);
+
+ ReverseAndUpper(dnP, id);
+
+}
+
+void LDAPEntriesHandler::HandleUpdate(ttXlaUpdateDesc_t* p)
+{
+ char* newDnP;
+ char* oldDnP;
+ char oDn[512];
+ int id;
+
+ // row is 'old'; row2 is 'new'
+ row.Get(Dn, &oldDnP);
+ strcpy(oDn, oldDnP);
+ row.Get(Id, &id);
+ row2.Get(Dn, &newDnP);
+
+ cerr << "old DN '" << oDn << "' / new DN '" << newDnP << "' : Updated ";
+
+ if (strcmp(oDn, newDnP) != 0) {
+ // The DN field changed, update it
+ cerr << "(new DN: '" << newDnP << "')";
+ ReverseAndUpper(newDnP, id);
+ }
+ else {
+ // The DN field did NOT change, leave it alone
+ }
+
+ cerr << endl;
+
+}
+
+void LDAPEntriesHandler::HandleDelete(ttXlaUpdateDesc_t* p)
+{
+ char* dnP;
+
+ row.Get(Dn, &dnP);
+ cerr << "DN '" << dnP << "': Deleted ";
+}
+
+
+
+
+//----------------------------------------------------------------------
+
+int pleaseStop = 0;
+
+extern "C" {
+ void
+ onintr(int sig)
+ {
+ pleaseStop = 1;
+ cerr << "Stopping...\n";
+ }
+};
+
+//----------------------------------------------------------------------
+
+int
+main(int argc, char* argv[])
+{
+
+ char* ownerP;
+
+ TTXlaTableList list(&conn); // List of tables to monitor
+
+ // Handlers, one for each table we want to monitor
+
+ LDAPEntriesHandler* sampP = NULL;
+
+ // Misc stuff
+
+ TTStatus stat;
+
+ ttXlaUpdateDesc_t ** arry;
+
+ int records;
+
+ SQLUBIGINT oldsize;
+ int j;
+
+ if (argc < 2) {
+ cerr << "syntax: " << argv[0] << " <username>" << endl;
+ exit(3);
+ }
+
+ ownerP = argv[1];
+
+ signal(SIGINT, onintr); /* signal for CTRL-C */
+#ifdef _WIN32
+ signal(SIGBREAK, onintr); /* signal for CTRL-BREAK */
+#endif
+
+ // Before we do anything related to XLA, first we connect
+ // to the database. This is the connection we will use
+ // to perform non-XLA operations on the tables.
+
+ try {
+ cerr << "Connecting..." << endl;
+
+ conn2.Connect("DSN=ldap_tt", stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error connecting to TimesTen: " << stat;
+ exit(1);
+ }
+
+ try {
+ assignDn_ru.Prepare(&conn2,
+ "update ldap_entries set dn_ru=? where id=?",
+ "", stat);
+ getNullDNs.Prepare(&conn2,
+ "select dn, id from ldap_entries "
+ "where dn_ru is null "
+ "for update",
+ "", stat);
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error preparing update: " << stat;
+ exit(1);
+ }
+
+ // If there are any entries with a NULL reversed/upper cased DN,
+ // fix them now.
+
+ try {
+ cerr << "Fixing NULL reversed DNs" << endl;
+ getNullDNs.Execute(stat);
+ for (int k = 0;; k++) {
+ getNullDNs.FetchNext(stat);
+ if (stat.rc == SQL_NO_DATA_FOUND) break;
+ char* dnP;
+ int id;
+ getNullDNs.getColumn(1, &dnP);
+ getNullDNs.getColumn(2, &id);
+ // cerr << "Id " << id << ", Dn '" << dnP << "'" << endl;
+ LDAPEntriesHandler::ReverseAndUpper(dnP, id, false);
+ if (k % 1000 == 0)
+ cerr << ".";
+ }
+ getNullDNs.Close(stat);
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error updating NULL rows: " << stat;
+ exit(1);
+ }
+
+
+ // Go ahead and start up the change monitoring application
+
+ cerr << "Starting change monitoring..." << endl;
+ try {
+ conn.Connect("DSN=ldap_tt", stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error connecting to TimesTen: " << stat;
+ exit(1);
+ }
+
+ /* set and configure size of buffer */
+ conn.setXlaBufferSize((SQLUBIGINT) 1000000, &oldsize, stat);
+ if (stat.rc) {
+ cerr << "Error setting buffer size " << stat << endl;
+ exit(1);
+ }
+
+ // Make a handler to process changes to the MYDATA table and
+ // add the handler to the list of all handlers
+
+ sampP = new LDAPEntriesHandler(conn, ownerP, "ldap_entries");
+ if (!sampP) {
+ cerr << "Could not create LDAPEntriesHandler" << endl;
+ exit(3);
+ }
+ list.add(sampP);
+
+ // Enable transaction logging for the table we're interested in
+
+ sampP->EnableTracking(stat);
+
+ // Get updates. Dispatch them to the appropriate handler.
+ // This loop will handle updates to all the tables.
+
+ while (pleaseStop == 0) {
+ conn.fetchUpdates(&arry, 1000, &records, stat);
+ if (stat.rc) {
+ cerr << "Error fetching updates" << stat << endl;
+ exit(1);
+ }
+
+ // Interpret the updates
+
+ for(j=0;j < records;j++){
+ ttXlaUpdateDesc_t *p;
+
+ p = arry[j];
+
+ list.HandleChange(p, stat);
+
+ } // end for each record fetched
+
+ if (records) {
+ cerr << "Processed " << records << " records\n";
+ }
+
+ if (records == 0) {
+#ifdef _WIN32
+ Sleep(250);
+#else
+ struct timeval t;
+ t.tv_sec = 0;
+ t.tv_usec = 250000; // .25 seconds
+ select(0, NULL, NULL, NULL, &t);
+#endif
+ }
+ } // end while pleasestop == 0
+
+
+ // When we get to here, the program is exiting.
+
+ list.del(sampP); // Take the table out of the list
+ delete sampP;
+
+ conn.setXlaBufferSize(oldsize, NULL, stat);
+
+ return 0;
+
+}
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf
new file mode 100644
index 0000000..f93de8b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf
@@ -0,0 +1,31 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_tt
+dbuser root
+dbpasswd
+subtree_cond "ldap_entries.dn LIKE ?"
+insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)"
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql
new file mode 100644
index 0000000..768aec8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql
@@ -0,0 +1,36 @@
+CREATE TABLE persons (
+ id int NOT NULL primary key,
+ name varchar(255) NOT NULL
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE institutes (
+ id int NOT NULL primary key,
+ name varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE documents (
+ id int NOT NULL primary key,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL,
+ PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+) unique hash on (pers_id, doc_id) pages=100;
+
+CREATE TABLE phones (
+ id int NOT NULL primary key,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+)
+unique hash on (id) pages=100;
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql
new file mode 100644
index 0000000..f141f41
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql
@@ -0,0 +1,16 @@
+insert into institutes (id,name) values (1,'sql');
+
+insert into persons (id,name) values (1,'Mitya Kovalev');
+insert into persons (id,name) values (2,'Torvlobnor Puzdoy');
+insert into persons (id,name) values (3,'Akakiy Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql
new file mode 100644
index 0000000..f9e3419
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql
@@ -0,0 +1,108 @@
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol, create_proc,
+delete_proc,expect_return)
+values
+(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();',
+NULL,0);
+
+insert into ldap_oc_mappings
+(id, name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(2, 'document','documents','id', NULL, NULL, 0);
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(3,'organization','institutes','id', NULL, NULL, 0);
+
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls,join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(1, 1, 'cn', 'persons.name', 'persons',NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id,name, sel_expr, from_tbls,
+join_where, add_proc,delete_proc,param_order,expect_return)
+values
+(2, 1, 'telephoneNumber','phones.phone','persons,phones',
+'phones.pers_id=persons.id', NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(3, 1, 'sn', 'persons.name','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(4, 2, 'description', 'documents.abstract','documents', NULL,
+NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(5, 2, 'documentTitle','documents.title','documents',NULL,
+NULL, NULL, 3, 0);
+
+-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs',
+-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id',
+-- NULL,NULL,3,0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+-- entries
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(1, 'o=sql,c=RU', 3, 0, 1);
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,o=sql,c=RU',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,o=sql,c=RU',2,1,2);
+
+
+-- referrals
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012');
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh
new file mode 100755
index 0000000..c4c5df2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh
@@ -0,0 +1,4 @@
+ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_data.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_metadata.sql
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql
new file mode 100644
index 0000000..f5955d2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql
@@ -0,0 +1,42 @@
+CREATE TABLE persons (
+ id int NOT NULL primary key,
+ name varchar(255) NOT NULL,
+ name_u varchar(255),
+ title varchar(255),
+ title_U varchar(255),
+ organization varchar(255)
+)
+unique hash on (id) pages=100;
+create index personsx1 on persons(title_U);
+create index personsx2 on persons(name_u);
+
+CREATE TABLE institutes (
+ id int NOT NULL primary key,
+ name varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE documents (
+ id int NOT NULL primary key,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL,
+ PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+) unique hash on (pers_id, doc_id) pages=100;
+
+CREATE TABLE phones (
+ id int NOT NULL primary key,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+)
+unique hash on (id) pages=100;
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql
new file mode 100644
index 0000000..ca75339
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql
@@ -0,0 +1,20 @@
+insert into institutes (id,name) values (1,'sql');
+
+insert into persons (id,name, title, title_U, organization) values
+(1,'Mitya Kovalev', 'Engineer', 'ENGINEER', 'Development');
+insert into persons (id,name, title, title_U, organization) values
+(2,'Torvlobnor Puzdoy', 'Engineer', 'ENGINEER', 'Sales');
+insert into persons (id,name, title, title_U, organization) values
+(3,'Akakiy Zinberstein', 'Engineer', 'ENGINEER', 'Marketing');
+update persons set name_u = upper(name);
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql
new file mode 100644
index 0000000..69bda8a
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql
@@ -0,0 +1,122 @@
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol, create_proc,
+delete_proc,expect_return)
+values
+(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();',
+NULL,0);
+
+insert into ldap_oc_mappings
+(id, name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(2, 'document','documents','id', NULL, NULL, 0);
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(3,'organization','institutes','id', NULL, NULL, 0);
+
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,
+join_where,add_proc, delete_proc,param_order,expect_return)
+values
+(1, 1, 'cn', 'persons.name', 'persons.name_u','persons',
+NULL, NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,join_where,
+add_proc, delete_proc,param_order,expect_return)
+values
+(10, 1, 'title', 'persons.title', 'persons.title_u', 'persons',NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id,name, sel_expr, from_tbls,
+join_where, add_proc,delete_proc,param_order,expect_return)
+values
+(2, 1, 'telephoneNumber','phones.phone','persons,phones',
+'phones.pers_id=persons.id', NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(3, 1, 'sn', 'persons.name','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(30, 1, 'ou', 'persons.organization','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(4, 2, 'description', 'documents.abstract','documents', NULL,
+NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(5, 2, 'documentTitle','documents.title','documents',NULL,
+NULL, NULL, 3, 0);
+
+-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs',
+-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id',
+-- NULL,NULL,3,0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+-- entries
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(1, 'o=sql,c=RU', 3, 0, 1);
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,o=sql,c=RU',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,o=sql,c=RU',2,1,2);
+
+
+-- referrals
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'http://localhost');
diff --git a/servers/slapd/back-sql/schema-map.c b/servers/slapd/back-sql/schema-map.c
new file mode 100644
index 0000000..f6294bb
--- /dev/null
+++ b/servers/slapd/back-sql/schema-map.c
@@ -0,0 +1,1012 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+#define BACKSQL_DUPLICATE (-1)
+
+/* NOTE: by default, cannot just compare pointers because
+ * objectClass/attributeType order would be machine-dependent
+ * (and tests would fail!); however, if you don't want to run
+ * tests, or see attributeTypes written in the same order
+ * they are defined, define */
+/* #undef BACKSQL_USE_PTR_CMP */
+
+/*
+ * Uses the pointer to the ObjectClass structure
+ */
+static int
+backsql_cmp_oc( const void *v_m1, const void *v_m2 )
+{
+ const backsql_oc_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bom_oc, m2->bom_oc );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bom_oc->soc_cname, &m2->bom_oc->soc_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+}
+
+static int
+backsql_cmp_oc_id( const void *v_m1, const void *v_m2 )
+{
+ const backsql_oc_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ return ( m1->bom_id < m2->bom_id ? -1 : ( m1->bom_id > m2->bom_id ? 1 : 0 ) );
+}
+
+/*
+ * Uses the pointer to the AttributeDescription structure
+ */
+static int
+backsql_cmp_attr( const void *v_m1, const void *v_m2 )
+{
+ const backsql_at_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) {
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bam_ad->ad_type, m2->bam_ad->ad_type );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+ }
+
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bam_ad, m2->bam_ad );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+}
+
+int
+backsql_dup_attr( void *v_m1, void *v_m2 )
+{
+ backsql_at_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) {
+#ifdef BACKSQL_USE_PTR_CMP
+ assert( m1->bam_ad->ad_type == m2->bam_ad->ad_type );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ assert( ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ) == 0 );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+
+ } else {
+#ifdef BACKSQL_USE_PTR_CMP
+ assert( m1->bam_ad == m2->bam_ad );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ assert( ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ) == 0 );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+ }
+
+ /* duplicate definitions of attributeTypes are appended;
+ * this allows to define multiple rules for the same
+ * attributeType. Use with care! */
+ for ( ; m1->bam_next ; m1 = m1->bam_next );
+
+ m1->bam_next = m2;
+ m2->bam_next = NULL;
+
+ return BACKSQL_DUPLICATE;
+}
+
+static int
+backsql_make_attr_query(
+ backsql_info *bi,
+ backsql_oc_map_rec *oc_map,
+ backsql_at_map_rec *at_map )
+{
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb, NULL, "lblbbbblblbcbl",
+ (ber_len_t)STRLENOF( "SELECT " ), "SELECT ",
+ &at_map->bam_sel_expr,
+ (ber_len_t)STRLENOF( " " ), " ",
+ &bi->sql_aliasing,
+ &bi->sql_aliasing_quote,
+ &at_map->bam_ad->ad_cname,
+ &bi->sql_aliasing_quote,
+ (ber_len_t)STRLENOF( " FROM " ), " FROM ",
+ &at_map->bam_from_tbls,
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( "=?" ), "=?" );
+
+ if ( !BER_BVISNULL( &at_map->bam_join_where ) ) {
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at_map->bam_join_where );
+ }
+
+ backsql_strfcat_x( &bb, NULL, "lbbb",
+ (ber_len_t)STRLENOF( " ORDER BY " ), " ORDER BY ",
+ &bi->sql_aliasing_quote,
+ &at_map->bam_ad->ad_cname,
+ &bi->sql_aliasing_quote );
+
+ at_map->bam_query = bb.bb_val.bv_val;
+
+#ifdef BACKSQL_COUNTQUERY
+ /* Query to count how many rows will be returned.
+
+ SELECT COUNT(*) FROM <from_tbls> WHERE <keytbl>.<keycol>=?
+ [ AND <join_where> ]
+
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lblbcbl",
+ (ber_len_t)STRLENOF( "SELECT COUNT(*) FROM " ),
+ "SELECT COUNT(*) FROM ",
+ &at_map->bam_from_tbls,
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( "=?" ), "=?" );
+
+ if ( !BER_BVISNULL( &at_map->bam_join_where ) ) {
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at_map->bam_join_where );
+ }
+
+ at_map->bam_countquery = bb.bb_val.bv_val;
+#endif /* BACKSQL_COUNTQUERY */
+
+ return 0;
+}
+
+static int
+backsql_add_sysmaps( backsql_info *bi, backsql_oc_map_rec *oc_map )
+{
+ backsql_at_map_rec *at_map;
+ char s[LDAP_PVT_INTTYPE_CHARS(long)];
+ struct berval sbv;
+ struct berbuf bb;
+
+ sbv.bv_val = s;
+ sbv.bv_len = snprintf( s, sizeof( s ), BACKSQL_IDNUMFMT, oc_map->bom_id );
+
+ /* extra objectClasses */
+ at_map = (backsql_at_map_rec *)ch_calloc(1,
+ sizeof( backsql_at_map_rec ) );
+ at_map->bam_ad = slap_schema.si_ad_objectClass;
+ at_map->bam_true_ad = slap_schema.si_ad_objectClass;
+ ber_str2bv( "ldap_entry_objclasses.oc_name", 0, 1,
+ &at_map->bam_sel_expr );
+ ber_str2bv( "ldap_entry_objclasses,ldap_entries", 0, 1,
+ &at_map->bam_from_tbls );
+
+ bb.bb_len = at_map->bam_from_tbls.bv_len + 1;
+ bb.bb_val = at_map->bam_from_tbls;
+ backsql_merge_from_clause( bi, &bb, &oc_map->bom_keytbl );
+ at_map->bam_from_tbls = bb.bb_val;
+
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lbcblb",
+ (ber_len_t)STRLENOF( "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=" ),
+ "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( " and ldap_entries.oc_map_id=" ),
+ " and ldap_entries.oc_map_id=",
+ &sbv );
+ at_map->bam_join_where = bb.bb_val;
+
+ at_map->bam_oc = oc_map->bom_oc;
+
+ at_map->bam_add_proc = NULL;
+ {
+ char tmp[STRLENOF("INSERT INTO ldap_entry_objclasses "
+ "(entry_id,oc_name) VALUES "
+ "((SELECT id FROM ldap_entries "
+ "WHERE oc_map_id= "
+ "AND keyval=?),?)") + LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ snprintf( tmp, sizeof(tmp),
+ "INSERT INTO ldap_entry_objclasses "
+ "(entry_id,oc_name) VALUES "
+ "((SELECT id FROM ldap_entries "
+ "WHERE oc_map_id=" BACKSQL_IDNUMFMT " "
+ "AND keyval=?),?)", oc_map->bom_id );
+ at_map->bam_add_proc = ch_strdup( tmp );
+ }
+
+ at_map->bam_delete_proc = NULL;
+ {
+ char tmp[STRLENOF("DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=(SELECT id FROM ldap_entries "
+ "WHERE oc_map_id= "
+ "AND keyval=?) AND oc_name=?") + LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ snprintf( tmp, sizeof(tmp),
+ "DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=(SELECT id FROM ldap_entries "
+ "WHERE oc_map_id=" BACKSQL_IDNUMFMT " "
+ "AND keyval=?) AND oc_name=?",
+ oc_map->bom_id );
+ at_map->bam_delete_proc = ch_strdup( tmp );
+ }
+
+ at_map->bam_param_order = 0;
+ at_map->bam_expect_return = 0;
+ at_map->bam_next = NULL;
+
+ backsql_make_attr_query( bi, oc_map, at_map );
+ if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_add_sysmaps(): "
+ "duplicate attribute \"%s\" in objectClass \"%s\" map\n",
+ at_map->bam_ad->ad_cname.bv_val,
+ oc_map->bom_oc->soc_cname.bv_val );
+ }
+
+ /* FIXME: we need to correct the objectClass join_where
+ * after the attribute query is built */
+ ch_free( at_map->bam_join_where.bv_val );
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lbcblb",
+ (ber_len_t)STRLENOF( /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=" ),
+ /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( " AND ldap_entries.oc_map_id=" ),
+ " AND ldap_entries.oc_map_id=",
+ &sbv );
+ at_map->bam_join_where = bb.bb_val;
+
+ return 1;
+}
+
+struct backsql_attr_schema_info {
+ backsql_info *bas_bi;
+ SQLHDBC bas_dbh;
+ SQLHSTMT bas_sth;
+ backsql_key_t *bas_oc_id;
+ int bas_rc;
+};
+
+static int
+backsql_oc_get_attr_mapping( void *v_oc, void *v_bas )
+{
+ RETCODE rc;
+ BACKSQL_ROW_NTS at_row;
+ backsql_oc_map_rec *oc_map = (backsql_oc_map_rec *)v_oc;
+ backsql_at_map_rec *at_map;
+ struct backsql_attr_schema_info *bas = (struct backsql_attr_schema_info *)v_bas;
+
+ /* bas->bas_oc_id has been bound to bas->bas_sth */
+ *bas->bas_oc_id = oc_map->bom_id;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "executing at_query\n"
+ " \"%s\"\n"
+ " for objectClass \"%s\"\n"
+ " with param oc_id=" BACKSQL_IDNUMFMT "\n",
+ bas->bas_bi->sql_at_query,
+ BACKSQL_OC_NAME( oc_map ),
+ *bas->bas_oc_id );
+
+ rc = SQLExecute( bas->bas_sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "error executing at_query\n"
+ " \"%s\"\n"
+ " for objectClass \"%s\"\n"
+ " with param oc_id=" BACKSQL_IDNUMFMT "\n",
+ bas->bas_bi->sql_at_query,
+ BACKSQL_OC_NAME( oc_map ),
+ *bas->bas_oc_id );
+ backsql_PrintErrors( bas->bas_bi->sql_db_env,
+ bas->bas_dbh, bas->bas_sth, rc );
+ bas->bas_rc = LDAP_OTHER;
+ return BACKSQL_AVL_STOP;
+ }
+
+ backsql_BindRowAsStrings( bas->bas_sth, &at_row );
+ for ( ; rc = SQLFetch( bas->bas_sth ), BACKSQL_SUCCESS( rc ); ) {
+ const char *text = NULL;
+ struct berval bv;
+ struct berbuf bb = BB_NULL;
+ AttributeDescription *ad = NULL;
+
+ {
+ struct {
+ int idx;
+ char *name;
+ } required[] = {
+ { 0, "name" },
+ { 1, "sel_expr" },
+ { 2, "from" },
+ { -1, NULL },
+ };
+ int i;
+
+ for ( i = 0; required[ i ].name != NULL; i++ ) {
+ if ( at_row.value_len[ i ] <= 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backsql_oc_get_attr_mapping(): "
+ "required column #%d \"%s\" is empty\n",
+ required[ i ].idx, required[ i ].name );
+ bas->bas_rc = LDAP_OTHER;
+ return BACKSQL_AVL_STOP;
+ }
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "attributeType: " "name=\"%s\" " "sel_expr=\"%s\" " "from=\"%s\" " "join_where=\"%s\" " "add_proc=\"%s\" " "delete_proc=\"%s\" " "sel_expr_u=\"%s\"\n",
+ at_row.cols[0], at_row.cols[1], at_row.cols[2],
+ at_row.cols[3] ? at_row.cols[3] : "",
+ at_row.cols[4] ? at_row.cols[4] : "",
+ at_row.cols[5] ? at_row.cols[5] : "",
+ at_row.cols[8] ? at_row.cols[8] : "" );
+
+ rc = slap_str2ad( at_row.cols[ 0 ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "attribute \"%s\" for objectClass \"%s\" "
+ "is not defined in schema: %s\n",
+ at_row.cols[ 0 ],
+ BACKSQL_OC_NAME( oc_map ), text );
+ bas->bas_rc = LDAP_CONSTRAINT_VIOLATION;
+ return BACKSQL_AVL_STOP;
+ }
+ at_map = (backsql_at_map_rec *)ch_calloc( 1,
+ sizeof( backsql_at_map_rec ) );
+ at_map->bam_ad = ad;
+ at_map->bam_true_ad = ad;
+ if ( slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && !slap_ad_is_binary( ad ) )
+ {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+ const char *text = NULL;
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%s;binary",
+ ad->ad_cname.bv_val );
+ at_map->bam_true_ad = NULL;
+ bas->bas_rc = slap_bv2ad( &bv, &at_map->bam_true_ad, &text );
+ if ( bas->bas_rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "unable to fetch attribute \"%s\": %s (%d)\n",
+ buf, text, rc );
+ ch_free( at_map );
+ return BACKSQL_AVL_STOP;
+ }
+ }
+
+ ber_str2bv( at_row.cols[ 1 ], 0, 1, &at_map->bam_sel_expr );
+ if ( at_row.value_len[ 8 ] <= 0 ) {
+ BER_BVZERO( &at_map->bam_sel_expr_u );
+
+ } else {
+ ber_str2bv( at_row.cols[ 8 ], 0, 1,
+ &at_map->bam_sel_expr_u );
+ }
+
+ ber_str2bv( at_row.cols[ 2 ], 0, 0, &bv );
+ backsql_merge_from_clause( bas->bas_bi, &bb, &bv );
+ at_map->bam_from_tbls = bb.bb_val;
+ if ( at_row.value_len[ 3 ] <= 0 ) {
+ BER_BVZERO( &at_map->bam_join_where );
+
+ } else {
+ ber_str2bv( at_row.cols[ 3 ], 0, 1,
+ &at_map->bam_join_where );
+ }
+ at_map->bam_add_proc = NULL;
+ if ( at_row.value_len[ 4 ] > 0 ) {
+ at_map->bam_add_proc = ch_strdup( at_row.cols[ 4 ] );
+ }
+ at_map->bam_delete_proc = NULL;
+ if ( at_row.value_len[ 5 ] > 0 ) {
+ at_map->bam_delete_proc = ch_strdup( at_row.cols[ 5 ] );
+ }
+ if ( lutil_atoix( &at_map->bam_param_order, at_row.cols[ 6 ], 0 ) != 0 ) {
+ /* error */
+ }
+ if ( lutil_atoix( &at_map->bam_expect_return, at_row.cols[ 7 ], 0 ) != 0 ) {
+ /* error */
+ }
+ backsql_make_attr_query( bas->bas_bi, oc_map, at_map );
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "preconstructed query \"%s\"\n",
+ at_map->bam_query );
+ at_map->bam_next = NULL;
+ if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "duplicate attribute \"%s\" "
+ "in objectClass \"%s\" map\n",
+ at_map->bam_ad->ad_cname.bv_val,
+ oc_map->bom_oc->soc_cname.bv_val );
+ ch_free( at_map );
+ }
+
+ if ( !BER_BVISNULL( &bas->bas_bi->sql_upper_func ) &&
+ BER_BVISNULL( &at_map->bam_sel_expr_u ) )
+ {
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb, NULL, "bcbc",
+ &bas->bas_bi->sql_upper_func,
+ '(' /* ) */ ,
+ &at_map->bam_sel_expr,
+ /* ( */ ')' );
+ at_map->bam_sel_expr_u = bb.bb_val;
+ }
+ }
+ backsql_FreeRow( &at_row );
+ SQLFreeStmt( bas->bas_sth, SQL_CLOSE );
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(\"%s\"): "
+ "autoadding 'objectClass' and 'ref' mappings\n",
+ BACKSQL_OC_NAME( oc_map ) );
+
+ (void)backsql_add_sysmaps( bas->bas_bi, oc_map );
+
+ return BACKSQL_AVL_CONTINUE;
+}
+
+
+int
+backsql_load_schema_map( backsql_info *bi, SQLHDBC dbh )
+{
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ BACKSQL_ROW_NTS oc_row;
+ backsql_key_t oc_id;
+ backsql_oc_map_rec *oc_map;
+ struct backsql_attr_schema_info bas;
+
+ int delete_proc_idx = 5;
+ int create_hint_idx = delete_proc_idx + 2;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_load_schema_map()\n" );
+
+ /*
+ * TimesTen : See if the ldap_entries.dn_ru field exists in the schema
+ */
+ if ( !BACKSQL_DONTCHECK_LDAPINFO_DN_RU( bi ) ) {
+ rc = backsql_Prepare( dbh, &sth,
+ backsql_check_dn_ru_query, 0 );
+ if ( rc == SQL_SUCCESS ) {
+ /* Yes, the field exists */
+ bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU;
+ Debug( LDAP_DEBUG_TRACE, "ldapinfo.dn_ru field exists "
+ "in the schema\n" );
+ } else {
+ /* No such field exists */
+ bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU;
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): oc_query \"%s\"\n",
+ bi->sql_oc_query );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_oc_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error preparing oc_query: \"%s\"\n",
+ bi->sql_oc_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error executing oc_query: \n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings( sth, &oc_row );
+ rc = SQLFetch( sth );
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ delete_proc_idx++;
+ create_hint_idx++;
+ }
+
+ for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) {
+ {
+ struct {
+ int idx;
+ char *name;
+ } required[] = {
+ { 0, "id" },
+ { 1, "name" },
+ { 2, "keytbl" },
+ { 3, "keycol" },
+ { -1, "expect_return" },
+ { -1, NULL },
+ };
+ int i;
+
+ required[4].idx = delete_proc_idx + 1;
+
+ for ( i = 0; required[ i ].name != NULL; i++ ) {
+ if ( oc_row.value_len[ required[ i ].idx ] <= 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backsql_load_schema_map(): "
+ "required column #%d \"%s\" is empty\n",
+ required[ i ].idx, required[ i ].name );
+ return LDAP_OTHER;
+ }
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "objectClass: " "id=\"%s\" " "name=\"%s\" " "keytbl=\"%s\" " "keycol=\"%s\" " "create_proc=\"%s\" " "create_keyval=\"%s\" " "delete_proc=\"%s\" " "expect_return=\"%s\"" "create_hint=\"%s\" \n",
+ oc_row.cols[0], oc_row.cols[1], oc_row.cols[2],
+ oc_row.cols[3],
+ oc_row.cols[4] ? oc_row.cols[4] : "",
+ (BACKSQL_CREATE_NEEDS_SELECT(bi) && oc_row.cols[5]) ? oc_row.cols[5] : "",
+ oc_row.cols[delete_proc_idx] ? oc_row.cols[delete_proc_idx] : "",
+ oc_row.cols[delete_proc_idx + 1],
+ ((oc_row.ncols > create_hint_idx) && oc_row.cols[create_hint_idx]) ? oc_row.cols[create_hint_idx] : "" );
+
+ oc_map = (backsql_oc_map_rec *)ch_calloc( 1,
+ sizeof( backsql_oc_map_rec ) );
+
+ if ( BACKSQL_STR2ID( &oc_map->bom_id, oc_row.cols[ 0 ], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "unable to parse id=\"%s\"\n",
+ oc_row.cols[ 0 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+
+ oc_map->bom_oc = oc_find( oc_row.cols[ 1 ] );
+ if ( oc_map->bom_oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "objectClass \"%s\" is not defined in schema\n",
+ oc_row.cols[ 1 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER; /* undefined objectClass ? */
+ }
+
+ ber_str2bv( oc_row.cols[ 2 ], 0, 1, &oc_map->bom_keytbl );
+ ber_str2bv( oc_row.cols[ 3 ], 0, 1, &oc_map->bom_keycol );
+ oc_map->bom_create_proc = ( oc_row.value_len[ 4 ] <= 0 ) ? NULL
+ : ch_strdup( oc_row.cols[ 4 ] );
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ oc_map->bom_create_keyval = ( oc_row.value_len[ 5 ] <= 0 )
+ ? NULL : ch_strdup( oc_row.cols[ 5 ] );
+ }
+ oc_map->bom_delete_proc = ( oc_row.value_len[ delete_proc_idx ] <= 0 ) ? NULL
+ : ch_strdup( oc_row.cols[ delete_proc_idx ] );
+ if ( lutil_atoix( &oc_map->bom_expect_return, oc_row.cols[ delete_proc_idx + 1 ], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "unable to parse expect_return=\"%s\" for objectClass \"%s\"\n",
+ oc_row.cols[ delete_proc_idx + 1 ], oc_row.cols[ 1 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+
+ if ( ( oc_row.ncols > create_hint_idx ) &&
+ ( oc_row.value_len[ create_hint_idx ] > 0 ) )
+ {
+ const char *text;
+
+ oc_map->bom_create_hint = NULL;
+ rc = slap_str2ad( oc_row.cols[ create_hint_idx ],
+ &oc_map->bom_create_hint, &text );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "load_schema_map(): "
+ "error matching "
+ "AttributeDescription %s "
+ "in create_hint: %s (%d)\n",
+ oc_row.cols[ create_hint_idx ],
+ text, rc );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+ }
+
+ /*
+ * FIXME: first attempt to check for offending
+ * instructions in {create|delete}_proc
+ */
+
+ oc_map->bom_attrs = NULL;
+ if ( ldap_avl_insert( &bi->sql_oc_by_oc, oc_map, backsql_cmp_oc, ldap_avl_dup_error ) == -1 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "duplicate objectClass \"%s\" in objectClass map\n",
+ oc_map->bom_oc->soc_cname.bv_val );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+ if ( ldap_avl_insert( &bi->sql_oc_by_id, oc_map, backsql_cmp_oc_id, ldap_avl_dup_error ) == -1 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "duplicate objectClass \"%s\" in objectClass by ID map\n",
+ oc_map->bom_oc->soc_cname.bv_val );
+ return LDAP_OTHER;
+ }
+ oc_id = oc_map->bom_id;
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "objectClass \"%s\":\n keytbl=\"%s\" keycol=\"%s\"\n",
+ BACKSQL_OC_NAME( oc_map ),
+ oc_map->bom_keytbl.bv_val, oc_map->bom_keycol.bv_val );
+ if ( oc_map->bom_create_proc ) {
+ Debug( LDAP_DEBUG_TRACE, " create_proc=\"%s\"\n",
+ oc_map->bom_create_proc );
+ }
+ if ( oc_map->bom_create_keyval ) {
+ Debug( LDAP_DEBUG_TRACE, " create_keyval=\"%s\"\n",
+ oc_map->bom_create_keyval );
+ }
+ if ( oc_map->bom_create_hint ) {
+ Debug( LDAP_DEBUG_TRACE, " create_hint=\"%s\"\n",
+ oc_map->bom_create_hint->ad_cname.bv_val );
+ }
+ if ( oc_map->bom_delete_proc ) {
+ Debug( LDAP_DEBUG_TRACE, " delete_proc=\"%s\"\n",
+ oc_map->bom_delete_proc );
+ }
+ Debug( LDAP_DEBUG_TRACE, " expect_return: "
+ "add=%d, del=%d; attributes:\n",
+ BACKSQL_IS_ADD( oc_map->bom_expect_return ),
+ BACKSQL_IS_DEL( oc_map->bom_expect_return ) );
+ }
+
+ backsql_FreeRow( &oc_row );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* prepare for attribute fetching */
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): at_query \"%s\"\n",
+ bi->sql_at_query );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_at_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error preparing at_query: \"%s\"\n",
+ bi->sql_at_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, &oc_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error binding param \"oc_id\" for at_query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ bas.bas_bi = bi;
+ bas.bas_dbh = dbh;
+ bas.bas_sth = sth;
+ bas.bas_oc_id = &oc_id;
+ bas.bas_rc = LDAP_SUCCESS;
+
+ (void)ldap_avl_apply( bi->sql_oc_by_oc, backsql_oc_get_attr_mapping,
+ &bas, BACKSQL_AVL_STOP, AVL_INORDER );
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ bi->sql_flags |= BSQLF_SCHEMA_LOADED;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_load_schema_map()\n" );
+
+ return bas.bas_rc;
+}
+
+backsql_oc_map_rec *
+backsql_oc2oc( backsql_info *bi, ObjectClass *oc )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_oc2oc(): "
+ "searching for objectclass with name=\"%s\"\n",
+ oc->soc_cname.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_oc = oc;
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc );
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): "
+ "found name=\"%s\", id=%d\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_oc_map_rec *
+backsql_name2oc( backsql_info *bi, struct berval *oc_name )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>oc_with_name(): "
+ "searching for objectclass with name=\"%s\"\n",
+ oc_name->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_oc = oc_bvfind( oc_name );
+ if ( tmp.bom_oc == NULL ) {
+ return NULL;
+ }
+
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc );
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): "
+ "found name=\"%s\", id=%d\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_oc_map_rec *
+backsql_id2oc( backsql_info *bi, unsigned long id )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>oc_with_id(): "
+ "searching for objectclass with id=%lu\n", id );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_id = id;
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_id, &tmp,
+ backsql_cmp_oc_id );
+
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): "
+ "found name=\"%s\", id=%lu\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): "
+ "id=%lu not found\n", res->bom_id );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_at_map_rec *
+backsql_ad2at( backsql_oc_map_rec* objclass, AttributeDescription *ad )
+{
+ backsql_at_map_rec tmp = { 0 }, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_ad2at(): "
+ "searching for attribute \"%s\" for objectclass \"%s\"\n",
+ ad->ad_cname.bv_val, BACKSQL_OC_NAME( objclass ) );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bam_ad = ad;
+ res = (backsql_at_map_rec *)ldap_avl_find( objclass->bom_attrs, &tmp,
+ backsql_cmp_attr );
+
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): "
+ "found name=\"%s\", sel_expr=\"%s\"\n",
+ res->bam_ad->ad_cname.bv_val,
+ res->bam_sel_expr.bv_val );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+/* attributeType inheritance */
+struct supad2at_t {
+ backsql_at_map_rec **ret;
+ AttributeDescription *ad;
+ unsigned n;
+};
+
+#define SUPAD2AT_STOP (-1)
+
+static int
+supad2at_f( void *v_at, void *v_arg )
+{
+ backsql_at_map_rec *at = (backsql_at_map_rec *)v_at;
+ struct supad2at_t *va = (struct supad2at_t *)v_arg;
+
+ if ( is_at_subtype( at->bam_ad->ad_type, va->ad->ad_type ) ) {
+ backsql_at_map_rec **ret = NULL;
+ unsigned i;
+
+ /* if already listed, holler! (should never happen) */
+ if ( va->ret ) {
+ for ( i = 0; i < va->n; i++ ) {
+ if ( va->ret[ i ]->bam_ad == at->bam_ad ) {
+ break;
+ }
+ }
+
+ if ( i < va->n ) {
+ return 0;
+ }
+ }
+
+ ret = ch_realloc( va->ret,
+ sizeof( backsql_at_map_rec * ) * ( va->n + 2 ) );
+ if ( ret == NULL ) {
+ ch_free( va->ret );
+ va->ret = NULL;
+ va->n = 0;
+ return SUPAD2AT_STOP;
+ }
+
+ ret[ va->n ] = at;
+ va->n++;
+ ret[ va->n ] = NULL;
+ va->ret = ret;
+ }
+
+ return 0;
+}
+
+/*
+ * stores in *pret a NULL terminated array of pointers
+ * to backsql_at_map_rec whose attributeType is supad->ad_type
+ * or derived from it
+ */
+int
+backsql_supad2at( backsql_oc_map_rec *objclass, AttributeDescription *supad,
+ backsql_at_map_rec ***pret )
+{
+ struct supad2at_t va = { 0 };
+ int rc;
+
+ assert( objclass != NULL );
+ assert( supad != NULL );
+ assert( pret != NULL );
+
+ *pret = NULL;
+
+ va.ad = supad;
+
+ rc = ldap_avl_apply( objclass->bom_attrs, supad2at_f, &va,
+ SUPAD2AT_STOP, AVL_INORDER );
+ if ( rc == SUPAD2AT_STOP ) {
+ return -1;
+ }
+
+ *pret = va.ret;
+
+ return 0;
+}
+
+static void
+backsql_free_attr( void *v_at )
+{
+ backsql_at_map_rec *at = v_at;
+
+ Debug( LDAP_DEBUG_TRACE, "==>free_attr(): \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+ ch_free( at->bam_sel_expr.bv_val );
+ if ( !BER_BVISNULL( &at->bam_from_tbls ) ) {
+ ch_free( at->bam_from_tbls.bv_val );
+ }
+ if ( !BER_BVISNULL( &at->bam_join_where ) ) {
+ ch_free( at->bam_join_where.bv_val );
+ }
+ if ( at->bam_add_proc != NULL ) {
+ ch_free( at->bam_add_proc );
+ }
+ if ( at->bam_delete_proc != NULL ) {
+ ch_free( at->bam_delete_proc );
+ }
+ if ( at->bam_query != NULL ) {
+ ch_free( at->bam_query );
+ }
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( at->bam_countquery != NULL ) {
+ ch_free( at->bam_countquery );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ /* TimesTen */
+ if ( !BER_BVISNULL( &at->bam_sel_expr_u ) ) {
+ ch_free( at->bam_sel_expr_u.bv_val );
+ }
+
+ if ( at->bam_next ) {
+ backsql_free_attr( at->bam_next );
+ }
+
+ ch_free( at );
+
+ Debug( LDAP_DEBUG_TRACE, "<==free_attr()\n" );
+}
+
+static void
+backsql_free_oc( void *v_oc )
+{
+ backsql_oc_map_rec *oc = v_oc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>free_oc(): \"%s\"\n",
+ BACKSQL_OC_NAME( oc ) );
+ ldap_avl_free( oc->bom_attrs, backsql_free_attr );
+ ch_free( oc->bom_keytbl.bv_val );
+ ch_free( oc->bom_keycol.bv_val );
+ if ( oc->bom_create_proc != NULL ) {
+ ch_free( oc->bom_create_proc );
+ }
+ if ( oc->bom_create_keyval != NULL ) {
+ ch_free( oc->bom_create_keyval );
+ }
+ if ( oc->bom_delete_proc != NULL ) {
+ ch_free( oc->bom_delete_proc );
+ }
+ ch_free( oc );
+
+ Debug( LDAP_DEBUG_TRACE, "<==free_oc()\n" );
+}
+
+int
+backsql_destroy_schema_map( backsql_info *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>destroy_schema_map()\n" );
+ ldap_avl_free( bi->sql_oc_by_oc, 0 );
+ ldap_avl_free( bi->sql_oc_by_id, backsql_free_oc );
+ Debug( LDAP_DEBUG_TRACE, "<==destroy_schema_map()\n" );
+ return 0;
+}
+
diff --git a/servers/slapd/back-sql/search.c b/servers/slapd/back-sql/search.c
new file mode 100644
index 0000000..d4177f6
--- /dev/null
+++ b/servers/slapd/back-sql/search.c
@@ -0,0 +1,2874 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+#include "ac/ctype.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+static int backsql_process_filter( backsql_srch_info *bsi, Filter *f );
+static int backsql_process_filter_eq( backsql_srch_info *bsi,
+ backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value );
+static int backsql_process_filter_like( backsql_srch_info *bsi,
+ backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value );
+static int backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f,
+ backsql_at_map_rec *at );
+
+/* For LDAP_CONTROL_PAGEDRESULTS, a 32 bit cookie is available to keep track of
+ the state of paged results. The ldap_entries.id and oc_map_id values of the
+ last entry returned are used as the cookie, so 6 bits are used for the OC id
+ and the other 26 for ldap_entries ID number. If your max(oc_map_id) is more
+ than 63, you will need to steal more bits from ldap_entries ID number and
+ put them into the OC ID part of the cookie. */
+
+/* NOTE: not supported when BACKSQL_ARBITRARY_KEY is defined */
+#ifndef BACKSQL_ARBITRARY_KEY
+#define SQL_TO_PAGECOOKIE(id, oc) (((id) << 6 ) | ((oc) & 0x3F))
+#define PAGECOOKIE_TO_SQL_ID(pc) ((pc) >> 6)
+#define PAGECOOKIE_TO_SQL_OC(pc) ((pc) & 0x3F)
+
+static int parse_paged_cookie( Operation *op, SlapReply *rs );
+
+static void send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+/* Look for chars that need to be escaped, return count of them.
+ * If out is non-NULL, copy escape'd val to it.
+ */
+static int
+backsql_val_escape( Operation *op, struct berval *in, struct berval *out )
+{
+ char *ptr, *end;
+ int q = 0;
+
+ ptr = in->bv_val;
+ end = ptr + in->bv_len;
+ while (ptr < end) {
+ if ( *ptr == '\'' )
+ q++;
+ ptr++;
+ }
+ if ( q && out ) {
+ char *dst;
+ out->bv_len = in->bv_len + q;
+ out->bv_val = op->o_tmpalloc( out->bv_len + 1, op->o_tmpmemctx );
+ ptr = in->bv_val;
+ dst = out->bv_val;
+ while (ptr < end ) {
+ if ( *ptr == '\'' )
+ *dst++ = '\'';
+ *dst++ = *ptr++;
+ }
+ *dst = '\0';
+ }
+ return q;
+}
+
+static int
+backsql_attrlist_add( backsql_srch_info *bsi, AttributeDescription *ad )
+{
+ int n_attrs = 0;
+ AttributeName *an = NULL;
+
+ if ( bsi->bsi_attrs == NULL ) {
+ return 1;
+ }
+
+ /*
+ * clear the list (retrieve all attrs)
+ */
+ if ( ad == NULL ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ bsi->bsi_flags |= BSQL_SF_ALL_ATTRS;
+ return 1;
+ }
+
+ /* strip ';binary' */
+ if ( slap_ad_is_binary( ad ) ) {
+ ad = ad->ad_type->sat_ad;
+ }
+
+ for ( ; !BER_BVISNULL( &bsi->bsi_attrs[ n_attrs ].an_name ); n_attrs++ ) {
+ an = &bsi->bsi_attrs[ n_attrs ];
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): "
+ "attribute \"%s\" is in list\n",
+ an->an_name.bv_val );
+ /*
+ * We can live with strcmp because the attribute
+ * list has been normalized before calling be_search
+ */
+ if ( !BACKSQL_NCMP( &an->an_name, &ad->ad_cname ) ) {
+ return 1;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): "
+ "adding \"%s\" to list\n", ad->ad_cname.bv_val );
+
+ an = (AttributeName *)bsi->bsi_op->o_tmprealloc( bsi->bsi_attrs,
+ sizeof( AttributeName ) * ( n_attrs + 2 ),
+ bsi->bsi_op->o_tmpmemctx );
+ if ( an == NULL ) {
+ return -1;
+ }
+
+ an[ n_attrs ].an_name = ad->ad_cname;
+ an[ n_attrs ].an_desc = ad;
+ BER_BVZERO( &an[ n_attrs + 1 ].an_name );
+
+ bsi->bsi_attrs = an;
+
+ return 1;
+}
+
+/*
+ * Initializes the search structure.
+ *
+ * If get_base_id != 0, the field bsi_base_id is filled
+ * with the entryID of bsi_base_ndn; it must be freed
+ * by backsql_free_entryID() when no longer required.
+ *
+ * NOTE: base must be normalized
+ */
+int
+backsql_init_search(
+ backsql_srch_info *bsi,
+ struct berval *nbase,
+ int scope,
+ time_t stoptime,
+ Filter *filter,
+ SQLHDBC dbh,
+ Operation *op,
+ SlapReply *rs,
+ AttributeName *attrs,
+ unsigned flags )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+
+ bsi->bsi_base_ndn = nbase;
+ bsi->bsi_use_subtree_shortcut = 0;
+ BER_BVZERO( &bsi->bsi_base_id.eid_dn );
+ BER_BVZERO( &bsi->bsi_base_id.eid_ndn );
+ bsi->bsi_scope = scope;
+ bsi->bsi_filter = filter;
+ bsi->bsi_dbh = dbh;
+ bsi->bsi_op = op;
+ bsi->bsi_rs = rs;
+ bsi->bsi_flags = BSQL_SF_NONE;
+
+ bsi->bsi_attrs = NULL;
+
+ if ( BACKSQL_FETCH_ALL_ATTRS( bi ) ) {
+ /*
+ * if requested, simply try to fetch all attributes
+ */
+ bsi->bsi_flags |= BSQL_SF_ALL_ATTRS;
+
+ } else {
+ if ( BACKSQL_FETCH_ALL_USERATTRS( bi ) ) {
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ } else if ( BACKSQL_FETCH_ALL_OPATTRS( bi ) ) {
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+ }
+
+ if ( attrs == NULL ) {
+ /* NULL means all user attributes */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ } else {
+ AttributeName *p;
+ int got_oc = 0;
+
+ bsi->bsi_attrs = (AttributeName *)bsi->bsi_op->o_tmpalloc(
+ sizeof( AttributeName ),
+ bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_attrs[ 0 ].an_name );
+
+ for ( p = attrs; !BER_BVISNULL( &p->an_name ); p++ ) {
+ if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) {
+ /* handle "*" */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) {
+ /* handle "+" */
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_no_attrs ) == 0 ) {
+ /* ignore "1.1" */
+ continue;
+
+ } else if ( p->an_desc == slap_schema.si_ad_objectClass ) {
+ got_oc = 1;
+ }
+
+ backsql_attrlist_add( bsi, p->an_desc );
+ }
+
+ if ( got_oc == 0 && !( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) {
+ /* add objectClass if not present,
+ * because it is required to understand
+ * if an entry is a referral, an alias
+ * or so... */
+ backsql_attrlist_add( bsi, slap_schema.si_ad_objectClass );
+ }
+ }
+
+ if ( !BSQL_ISF_ALL_ATTRS( bsi ) && bi->sql_anlist ) {
+ AttributeName *p;
+
+ /* use hints if available */
+ for ( p = bi->sql_anlist; !BER_BVISNULL( &p->an_name ); p++ ) {
+ if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) {
+ /* handle "*" */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) {
+ /* handle "+" */
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+ }
+
+ backsql_attrlist_add( bsi, p->an_desc );
+ }
+
+ }
+ }
+
+ bsi->bsi_id_list = NULL;
+ bsi->bsi_id_listtail = &bsi->bsi_id_list;
+ bsi->bsi_n_candidates = 0;
+ bsi->bsi_stoptime = stoptime;
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+ bsi->bsi_filter_oc = NULL;
+
+ if ( BACKSQL_IS_GET_ID( flags ) ) {
+ int matched = BACKSQL_IS_MATCHED( flags );
+ int getentry = BACKSQL_IS_GET_ENTRY( flags );
+ int gotit = 0;
+
+ assert( op->o_bd->be_private != NULL );
+
+ rc = backsql_dn2id( op, rs, dbh, nbase, &bsi->bsi_base_id,
+ matched, 1 );
+
+ /* the entry is collected either if requested for by getentry
+ * or if get noSuchObject and requested to climb the tree,
+ * so that a matchedDN or a referral can be returned */
+ if ( ( rc == LDAP_NO_SUCH_OBJECT && matched ) || getentry ) {
+ if ( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ) {
+ assert( bsi->bsi_e != NULL );
+
+ if ( dn_match( nbase, &bsi->bsi_base_id.eid_ndn ) )
+ {
+ gotit = 1;
+ }
+
+ /*
+ * let's see if it is a referral and, in case, get it
+ */
+ backsql_attrlist_add( bsi, slap_schema.si_ad_ref );
+ rc = backsql_id2entry( bsi, &bsi->bsi_base_id );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( is_entry_referral( bsi->bsi_e ) )
+ {
+ BerVarray erefs = get_entry_referrals( op, bsi->bsi_e );
+ if ( erefs ) {
+ rc = rs->sr_err = LDAP_REFERRAL;
+ rs->sr_ref = referral_rewrite( erefs,
+ &bsi->bsi_e->e_nname,
+ &op->o_req_dn,
+ scope );
+ ber_bvarray_free( erefs );
+
+ } else {
+ rc = rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "bad referral object";
+ }
+
+ } else if ( !gotit ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+
+ } else {
+ rs->sr_err = rc;
+ }
+ }
+
+ if ( gotit && BACKSQL_IS_GET_OC( flags ) ) {
+ bsi->bsi_base_id.eid_oc = backsql_id2oc( bi,
+ bsi->bsi_base_id.eid_oc_id );
+ if ( bsi->bsi_base_id.eid_oc == NULL ) {
+ /* error? */
+ backsql_free_entryID( &bsi->bsi_base_id, 1,
+ op->o_tmpmemctx );
+ rc = rs->sr_err = LDAP_OTHER;
+ }
+ }
+ }
+
+ bsi->bsi_status = rc;
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ case LDAP_REFERRAL:
+ break;
+
+ default:
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ break;
+ }
+
+ return rc;
+}
+
+static int
+backsql_process_filter_list( backsql_srch_info *bsi, Filter *f, int op )
+{
+ int res;
+
+ if ( !f ) {
+ return 0;
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ );
+
+ while ( 1 ) {
+ res = backsql_process_filter( bsi, f );
+ if ( res < 0 ) {
+ /*
+ * TimesTen : If the query has no answers,
+ * don't bother to run the query.
+ */
+ return -1;
+ }
+
+ f = f->f_next;
+ if ( f == NULL ) {
+ break;
+ }
+
+ switch ( op ) {
+ case LDAP_FILTER_AND:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "l",
+ (ber_len_t)STRLENOF( " AND " ),
+ " AND " );
+ break;
+
+ case LDAP_FILTER_OR:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "l",
+ (ber_len_t)STRLENOF( " OR " ),
+ " OR " );
+ break;
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", /* ( */ ')' );
+
+ return 1;
+}
+
+static int
+backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
+ backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int i;
+ int casefold = 0;
+ int escaped = 0;
+ struct berval escval, *fvalue;
+
+ if ( !f ) {
+ return 0;
+ }
+
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( f->f_sub_desc->ad_type->sat_substr &&
+ SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ if ( f->f_sub_desc->ad_type->sat_substr &&
+ SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr,
+ bi->sql_telephoneNumberMatch ) )
+ {
+
+ struct berval bv;
+ ber_len_t i, s, a;
+
+ /*
+ * to check for matching telephone numbers
+ * with intermixed chars, e.g. val='1234'
+ * use
+ *
+ * val LIKE '%1%2%3%4%'
+ */
+
+ BER_BVZERO( &bv );
+ if ( f->f_sub_initial.bv_val ) {
+ bv.bv_len += f->f_sub_initial.bv_len + backsql_val_escape( NULL, &f->f_sub_initial, NULL );
+ }
+ if ( f->f_sub_any != NULL ) {
+ for ( a = 0; f->f_sub_any[ a ].bv_val != NULL; a++ ) {
+ bv.bv_len += f->f_sub_any[ a ].bv_len + backsql_val_escape( NULL, &f->f_sub_any[ a ], NULL );
+ }
+ }
+ if ( f->f_sub_final.bv_val ) {
+ bv.bv_len += f->f_sub_final.bv_len + backsql_val_escape( NULL, &f->f_sub_final, NULL );
+ }
+ bv.bv_len = 2 * bv.bv_len - 1;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ s = 0;
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ fvalue = &f->f_sub_initial;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( a = 0; !BER_BVISNULL( &f->f_sub_any[ a ] ); a++ ) {
+ fvalue = &f->f_sub_any[ a ];
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ fvalue = &f->f_sub_final;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+
+ bv.bv_val[ s - 1 ] = '\0';
+
+ (void)backsql_process_filter_like( bsi, at, casefold, &bv );
+ ch_free( bv.bv_val );
+
+ return 1;
+ }
+
+ /*
+ * When dealing with case-sensitive strings
+ * we may omit normalization; however, normalized
+ * SQL filters are more liberal.
+ */
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ );
+
+ /* TimesTen */
+ Debug( LDAP_DEBUG_TRACE, "backsql_process_sub_filter(%s):\n",
+ at->bam_ad->ad_cname.bv_val );
+ Debug(LDAP_DEBUG_TRACE, " expr: '%s%s%s'\n", at->bam_sel_expr.bv_val,
+ at->bam_sel_expr_u.bv_val ? "' '" : "",
+ at->bam_sel_expr_u.bv_val ? at->bam_sel_expr_u.bv_val : "" );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ /*
+ * If a pre-upper-cased version of the column
+ * or a precompiled upper function exists, use it
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( " LIKE '" ),
+ " LIKE '" );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( " LIKE '" ), " LIKE '" );
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_initial=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ f->f_sub_initial.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_initial;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ fvalue );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", '%' );
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[ i ] ); i++ ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_any[%d]=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ i, f->f_sub_any[ i ].bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_any[ i ];
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bc",
+ fvalue,
+ '%' );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ /*
+ * Note: toupper('%') = '%'
+ */
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_final=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ f->f_sub_final.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_final;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ fvalue );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( /* (' */ "')" ), /* (' */ "')" );
+
+ return 1;
+}
+
+static int
+backsql_merge_from_tbls( backsql_srch_info *bsi, struct berval *from_tbls )
+{
+ if ( BER_BVISNULL( from_tbls ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( !BER_BVISNULL( &bsi->bsi_from.bb_val ) ) {
+ char *start, *end;
+ struct berval tmp;
+
+ ber_dupbv_x( &tmp, from_tbls, bsi->bsi_op->o_tmpmemctx );
+
+ for ( start = tmp.bv_val, end = strchr( start, ',' ); start; ) {
+ if ( end ) {
+ end[0] = '\0';
+ }
+
+ if ( strstr( bsi->bsi_from.bb_val.bv_val, start) == NULL )
+ {
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "cs", ',', start );
+ }
+
+ if ( end ) {
+ /* in case there are spaces after the comma... */
+ for ( start = &end[1]; isspace( start[0] ); start++ );
+ if ( start[0] ) {
+ end = strchr( start, ',' );
+ } else {
+ start = NULL;
+ }
+ } else {
+ start = NULL;
+ }
+ }
+
+ bsi->bsi_op->o_tmpfree( tmp.bv_val, bsi->bsi_op->o_tmpmemctx );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "b", from_tbls );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+backsql_process_filter( backsql_srch_info *bsi, Filter *f )
+{
+ backsql_at_map_rec **vat = NULL;
+ AttributeDescription *ad = NULL;
+ unsigned i;
+ int done = 0;
+ int rc = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter()\n" );
+ if ( f->f_choice == SLAPD_FILTER_COMPUTED ) {
+ struct berval flt;
+ char *msg = NULL;
+
+ switch ( f->f_result ) {
+ case LDAP_COMPARE_TRUE:
+ BER_BVSTR( &flt, "10=10" );
+ msg = "TRUE";
+ break;
+
+ case LDAP_COMPARE_FALSE:
+ BER_BVSTR( &flt, "11=0" );
+ msg = "FALSE";
+ break;
+
+ case SLAPD_COMPARE_UNDEFINED:
+ BER_BVSTR( &flt, "12=0" );
+ msg = "UNDEFINED";
+ break;
+
+ default:
+ rc = -1;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_process_filter(): "
+ "filter computed (%s)\n", msg );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "b", &flt );
+ rc = 1;
+ goto done;
+ }
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=0" ), "1=0" );
+ done = 1;
+ rc = 1;
+ goto done;
+ }
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_OR:
+ rc = backsql_process_filter_list( bsi, f->f_or,
+ LDAP_FILTER_OR );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_AND:
+ rc = backsql_process_filter_list( bsi, f->f_and,
+ LDAP_FILTER_AND );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_NOT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "NOT (" /* ) */ ),
+ "NOT (" /* ) */ );
+ rc = backsql_process_filter( bsi, f->f_not );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", /* ( */ ')' );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ break;
+
+ case LDAP_FILTER_EXT:
+ ad = f->f_mra->ma_desc;
+ if ( f->f_mr_dnattrs ) {
+ /*
+ * if dn attrs filtering is requested, better return
+ * success and let test_filter() deal with candidate
+ * selection; otherwise we'd need to set conditions
+ * on the contents of the DN, e.g. "SELECT ... FROM
+ * ldap_entries AS attributeName WHERE attributeName.dn
+ * like '%attributeName=value%'"
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=1" ), "1=1" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+ break;
+
+ default:
+ ad = f->f_av_desc;
+ break;
+ }
+
+ if ( rc == -1 ) {
+ goto done;
+ }
+
+ if ( done ) {
+ rc = 1;
+ goto done;
+ }
+
+ /*
+ * Turn structuralObjectClass into objectClass
+ */
+ if ( ad == slap_schema.si_ad_objectClass
+ || ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ /*
+ * If the filter is LDAP_FILTER_PRESENT, then it's done;
+ * otherwise, let's see if we are lucky: filtering
+ * for "structural" objectclass or ancestor...
+ */
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ {
+ ObjectClass *oc = oc_bvfind( &f->f_av_value );
+
+ if ( oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_process_filter(): "
+ "unknown objectClass \"%s\" "
+ "in filter\n",
+ f->f_av_value.bv_val );
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ /*
+ * "structural" objectClass inheritance:
+ * - a search for "person" will also return
+ * "inetOrgPerson"
+ * - a search for "top" will return everything
+ */
+ if ( is_object_subclass( oc, bsi->bsi_oc->bom_oc ) ) {
+ static struct berval ldap_entry_objclasses = BER_BVC( "ldap_entry_objclasses" );
+
+ backsql_merge_from_tbls( bsi, &ldap_entry_objclasses );
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */ ),
+ "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ (ber_len_t)STRLENOF( /* ((' */ "'))" ),
+ /* ((' */ "'))" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+
+ break;
+ }
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "3=3" ), "3=3" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+
+ /* FIXME: LDAP_FILTER_EXT? */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_process_filter(): "
+ "illegal/unhandled filter "
+ "on objectClass attribute" );
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ } else if ( ad == slap_schema.si_ad_entryUUID ) {
+ unsigned long oc_id;
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval keyval;
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long keyval;
+ char keyvalbuf[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ backsql_entryUUID_decode( &f->f_av_value, &oc_id, &keyval );
+
+ if ( oc_id != bsi->bsi_oc->bom_id ) {
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = -1;
+ goto done;
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bcblbc",
+ &bsi->bsi_oc->bom_keytbl, '.',
+ &bsi->bsi_oc->bom_keycol,
+ STRLENOF( " LIKE '" ), " LIKE '",
+ &keyval, '\'' );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ snprintf( keyvalbuf, sizeof( keyvalbuf ), "%lu", keyval );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bcbcs",
+ &bsi->bsi_oc->bom_keytbl, '.',
+ &bsi->bsi_oc->bom_keycol, '=', keyvalbuf );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "4=4" ), "4=4" );
+ break;
+
+ default:
+ rc = -1;
+ goto done;
+ }
+
+ bsi->bsi_flags |= BSQL_SF_FILTER_ENTRYUUID;
+ rc = 1;
+ goto done;
+
+#ifdef BACKSQL_SYNCPROV
+ } else if ( ad == slap_schema.si_ad_entryCSN ) {
+ /*
+ * support for syncrepl as provider...
+ */
+#if 0
+ if ( !bsi->bsi_op->o_sync ) {
+ /* unsupported at present... */
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+#endif
+
+ bsi->bsi_flags |= ( BSQL_SF_FILTER_ENTRYCSN | BSQL_SF_RETURN_ENTRYUUID);
+
+ /* if doing a syncrepl, try to return as much as possible,
+ * and always match the filter */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "5=5" ), "5=5" );
+
+ /* save for later use in operational attributes */
+ /* FIXME: saves only the first occurrence, because
+ * the filter during updates is written as
+ * "(&(entryCSN<={contextCSN})(entryCSN>={oldContextCSN})({filter}))"
+ * so we want our fake entryCSN to match the greatest
+ * value
+ */
+ if ( bsi->bsi_op->o_private == NULL ) {
+ bsi->bsi_op->o_private = &f->f_av_value;
+ }
+ bsi->bsi_status = LDAP_SUCCESS;
+
+ rc = 1;
+ goto done;
+#endif /* BACKSQL_SYNCPROV */
+
+ } else if ( ad == slap_schema.si_ad_hasSubordinates || ad == NULL ) {
+ /*
+ * FIXME: this is not robust; e.g. a filter
+ * '(!(hasSubordinates=TRUE))' fails because
+ * in SQL it would read 'NOT (1=1)' instead
+ * of no condition.
+ * Note however that hasSubordinates is boolean,
+ * so a more appropriate filter would be
+ * '(hasSubordinates=FALSE)'
+ *
+ * A more robust search for hasSubordinates
+ * would * require joining the ldap_entries table
+ * selecting if there are descendants of the
+ * candidate.
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "6=6" ), "6=6" );
+ if ( ad == slap_schema.si_ad_hasSubordinates ) {
+ /*
+ * instruct candidate selection algorithm
+ * and attribute list to try to detect
+ * if an entry has subordinates
+ */
+ bsi->bsi_flags |= BSQL_SF_FILTER_HASSUBORDINATE;
+
+ } else {
+ /*
+ * clear attributes to fetch, to require ALL
+ * and try extended match on all attributes
+ */
+ backsql_attrlist_add( bsi, NULL );
+ }
+ rc = 1;
+ goto done;
+ }
+
+ /*
+ * attribute inheritance:
+ */
+ if ( backsql_supad2at( bsi->bsi_oc, ad, &vat ) ) {
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ if ( vat == NULL ) {
+ /* search anyway; other parts of the filter
+ * may succeed */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "7=7" ), "7=7" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+
+ /* if required, open extra level of parens */
+ done = 0;
+ if ( vat[0]->bam_next || vat[1] ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", '(' );
+ done = 1;
+ }
+
+ i = 0;
+next:;
+ /* apply attr */
+ if ( backsql_process_filter_attr( bsi, f, vat[i] ) == -1 ) {
+ return -1;
+ }
+
+ /* if more definitions of the same attr, apply */
+ if ( vat[i]->bam_next ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ STRLENOF( " OR " ), " OR " );
+ vat[i] = vat[i]->bam_next;
+ goto next;
+ }
+
+ /* if more descendants of the same attr, apply */
+ i++;
+ if ( vat[i] ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ STRLENOF( " OR " ), " OR " );
+ goto next;
+ }
+
+ /* if needed, close extra level of parens */
+ if ( done ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", ')' );
+ }
+
+ rc = 1;
+
+done:;
+ if ( vat ) {
+ ch_free( vat );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_process_filter() %s\n",
+ rc == 1 ? "succeeded" : "failed" );
+
+ return rc;
+}
+
+static int
+backsql_process_filter_eq( backsql_srch_info *bsi, backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value )
+{
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbl",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( "='" ),
+ "='" );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cblbl",
+ '(', /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( "='" ), "='",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ }
+
+ return 1;
+}
+
+static int
+backsql_process_filter_like( backsql_srch_info *bsi, backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value )
+{
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbl",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( " LIKE '%" ),
+ " LIKE '%" );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "%')" ),
+ /* (' */ "%')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cblbl",
+ '(', /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( " LIKE '%" ),
+ " LIKE '%",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "%')" ),
+ /* (' */ "%')" );
+ }
+
+ return 1;
+}
+
+static int
+backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int casefold = 0;
+ struct berval *filter_value = NULL;
+ MatchingRule *matching_rule = NULL;
+ struct berval ordering = BER_BVC("<=");
+ struct berval escval;
+ int escaped = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter_attr(%s)\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ /*
+ * need to add this attribute to list of attrs to load,
+ * so that we can do test_filter() later
+ */
+ backsql_attrlist_add( bsi, at->bam_ad );
+
+ backsql_merge_from_tbls( bsi, &at->bam_from_tbls );
+
+ if ( !BER_BVISNULL( &at->bam_join_where )
+ && strstr( bsi->bsi_join_where.bb_val.bv_val,
+ at->bam_join_where.bv_val ) == NULL )
+ {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at->bam_join_where );
+ }
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=0" ), "1=0" );
+ return 1;
+ }
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ filter_value = &f->f_av_value;
+ matching_rule = at->bam_ad->ad_type->sat_equality;
+
+ goto equality_match;
+
+ /* fail over into next case */
+
+ case LDAP_FILTER_EXT:
+ filter_value = &f->f_mra->ma_value;
+ matching_rule = f->f_mr_rule;
+
+equality_match:;
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( SLAP_MR_ASSOCIATED( matching_rule,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+
+ /* FIXME: directoryString filtering should use a similar
+ * approach to deal with non-prettified values like
+ * " A non prettified value ", by using a LIKE
+ * filter with all whitespaces collapsed to a single '%' */
+ if ( SLAP_MR_ASSOCIATED( matching_rule,
+ bi->sql_telephoneNumberMatch ) )
+ {
+ struct berval bv;
+ ber_len_t i;
+
+ /*
+ * to check for matching telephone numbers
+ * with intermized chars, e.g. val='1234'
+ * use
+ *
+ * val LIKE '%1%2%3%4%'
+ */
+
+ bv.bv_len = 2 * filter_value->bv_len - 1;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ bv.bv_val[ 0 ] = filter_value->bv_val[ 0 ];
+ for ( i = 1; i < filter_value->bv_len; i++ ) {
+ bv.bv_val[ 2 * i - 1 ] = '%';
+ bv.bv_val[ 2 * i ] = filter_value->bv_val[ i ];
+ }
+ bv.bv_val[ 2 * i - 1 ] = '\0';
+
+ (void)backsql_process_filter_like( bsi, at, casefold, &bv );
+ ch_free( bv.bv_val );
+
+ break;
+ }
+
+ /* NOTE: this is required by objectClass inheritance
+ * and auxiliary objectClass use in filters for slightly
+ * more efficient candidate selection. */
+ /* FIXME: a bit too many specializations to deal with
+ * very specific cases... */
+ if ( at->bam_ad == slap_schema.si_ad_objectClass
+ || at->bam_ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */ ),
+ "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */,
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ break;
+ }
+
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ (void)backsql_process_filter_eq( bsi, at, casefold, filter_value );
+ break;
+
+ case LDAP_FILTER_GE:
+ ordering.bv_val = ">=";
+
+ /* fall thru to next case */
+
+ case LDAP_FILTER_LE:
+ filter_value = &f->f_av_value;
+
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( at->bam_ad->ad_type->sat_ordering &&
+ SLAP_MR_ASSOCIATED( at->bam_ad->ad_type->sat_ordering,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+
+ /*
+ * FIXME: should we uppercase the operands?
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbbc",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ &ordering,
+ '\'' );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbbcbl",
+ '(' /* ) */ ,
+ &at->bam_sel_expr,
+ &ordering,
+ '\'',
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* ( */ "')" );
+ }
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "NOT (" /* ) */),
+ "NOT (", /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( /* ( */ " IS NULL)" ),
+ /* ( */ " IS NULL)" );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ backsql_process_sub_filter( bsi, f, at );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ /* we do our best */
+
+ filter_value = &f->f_av_value;
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ (void)backsql_process_filter_like( bsi, at, 1, filter_value );
+ break;
+
+ default:
+ /* unhandled filter type; should not happen */
+ assert( 0 );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "8=8" ), "8=8" );
+ break;
+
+ }
+
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_process_filter_attr(%s)\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ return 1;
+}
+
+static int
+backsql_srch_query( backsql_srch_info *bsi, struct berval *query )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int rc;
+
+ assert( query != NULL );
+ BER_BVZERO( query );
+
+ bsi->bsi_use_subtree_shortcut = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_srch_query()\n" );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbcbc",
+ (ber_len_t)STRLENOF( "SELECT DISTINCT ldap_entries.id," ),
+ "SELECT DISTINCT ldap_entries.id,",
+ &bsi->bsi_oc->bom_keytbl,
+ '.',
+ &bsi->bsi_oc->bom_keycol,
+ ',' );
+
+ if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) {
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "blbl",
+ &bi->sql_strcast_func,
+ (ber_len_t)STRLENOF( "('" /* ') */ ),
+ "('" /* ') */ ,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbc",
+ '\'',
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ '\'' );
+ }
+
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ &bi->sql_dn_oc_aliasing );
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "lb",
+ (ber_len_t)STRLENOF( " FROM ldap_entries," ),
+ " FROM ldap_entries,",
+ &bsi->bsi_oc->bom_keytbl );
+
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbcbl",
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &bsi->bsi_oc->bom_keytbl,
+ '.',
+ &bsi->bsi_oc->bom_keycol,
+ (ber_len_t)STRLENOF( "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ),
+ "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " );
+
+ switch ( bsi->bsi_scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn)=?" ),
+ "(ldap_entries.dn)=?" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn=?" ),
+ "ldap_entries.dn=?" );
+ }
+ break;
+
+ case BACKSQL_SCOPE_BASE_LIKE:
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ),
+ "(ldap_entries.dn) LIKE ?" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ),
+ "ldap_entries.dn LIKE ?" );
+ }
+ break;
+
+ case LDAP_SCOPE_ONELEVEL:
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.parent=?" ),
+ "ldap_entries.parent=?" );
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_SUBTREE:
+ if ( BACKSQL_USE_SUBTREE_SHORTCUT( bi ) ) {
+ int i;
+ BackendDB *bd = bsi->bsi_op->o_bd;
+
+ assert( bd->be_nsuffix != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &bd->be_nsuffix[ i ] ); i++ )
+ {
+ if ( dn_match( &bd->be_nsuffix[ i ],
+ bsi->bsi_base_ndn ) )
+ {
+ /* pass this to the candidate selection
+ * routine so that the DN is not bound
+ * to the select statement */
+ bsi->bsi_use_subtree_shortcut = 1;
+ break;
+ }
+ }
+ }
+
+ if ( bsi->bsi_use_subtree_shortcut ) {
+ /* Skip the base DN filter, as every entry will match it */
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "9=9"), "9=9");
+
+ } else if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ /* This should always be true... */
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ &bi->sql_subtree_cond );
+
+ } else if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ),
+ "(ldap_entries.dn) LIKE ?" );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ),
+ "ldap_entries.dn LIKE ?" );
+ }
+
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* If paged results are in effect, ignore low ldap_entries.id numbers */
+ if ( get_pagedresults(bsi->bsi_op) > SLAP_CONTROL_IGNORED ) {
+ unsigned long lowid = 0;
+
+ /* Pick up the previous ldap_entries.id if the previous page ended in this objectClass */
+ if ( bsi->bsi_oc->bom_id == PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ) )
+ {
+ lowid = PAGECOOKIE_TO_SQL_ID( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie );
+ }
+
+ if ( lowid ) {
+ char lowidstring[48];
+ int lowidlen;
+
+ lowidlen = snprintf( lowidstring, sizeof( lowidstring ),
+ " AND ldap_entries.id>%lu", lowid );
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)lowidlen,
+ lowidstring );
+ }
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ rc = backsql_process_filter( bsi, bsi->bsi_filter );
+ if ( rc > 0 ) {
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb,
+ bsi->bsi_op->o_tmpmemctx,
+ "bbblb",
+ &bsi->bsi_sel.bb_val,
+ &bsi->bsi_from.bb_val,
+ &bsi->bsi_join_where.bb_val,
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &bsi->bsi_flt_where.bb_val );
+
+ *query = bb.bb_val;
+
+ } else if ( rc < 0 ) {
+ /*
+ * Indicates that there's no possible way the filter matches
+ * anything. No need to issue the query
+ */
+ free( query->bv_val );
+ BER_BVZERO( query );
+ }
+
+ bsi->bsi_op->o_tmpfree( bsi->bsi_sel.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_from.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_join_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_flt_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_srch_query() returns %s\n",
+ query->bv_val ? query->bv_val : "NULL" );
+
+ return ( rc <= 0 ? 1 : 0 );
+}
+
+static int
+backsql_oc_get_candidates( void *v_oc, void *v_bsi )
+{
+ backsql_oc_map_rec *oc = v_oc;
+ backsql_srch_info *bsi = v_bsi;
+ Operation *op = bsi->bsi_op;
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ struct berval query;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ int res;
+ BACKSQL_ROW_NTS row;
+ int i;
+ int j;
+ int n_candidates = bsi->bsi_n_candidates;
+
+ /*
+ * + 1 because we need room for '%';
+ * + 1 because we need room for ',' for LDAP_SCOPE_SUBORDINATE;
+ * this makes a subtree
+ * search for a DN BACKSQL_MAX_DN_LEN long legal
+ * if it returns that DN only
+ */
+ char tmp_base_ndn[ BACKSQL_MAX_DN_LEN + 1 + 1 ];
+
+ bsi->bsi_status = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_oc_get_candidates(): oc=\"%s\"\n",
+ BACKSQL_OC_NAME( oc ) );
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ bsi->bsi_status = SLAPD_ABANDON;
+ return BACKSQL_AVL_STOP;
+ }
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* If paged results have already completed this objectClass, skip it */
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ if ( oc->bom_id < PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)op->o_pagedresults_state)->ps_cookie ) )
+ {
+ return BACKSQL_AVL_CONTINUE;
+ }
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ if ( bsi->bsi_n_candidates == -1 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "unchecked limit has been overcome\n" );
+ /* should never get here */
+ assert( 0 );
+ bsi->bsi_status = LDAP_ADMINLIMIT_EXCEEDED;
+ return BACKSQL_AVL_STOP;
+ }
+
+ bsi->bsi_oc = oc;
+ res = backsql_srch_query( bsi, &query );
+ if ( res ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error while constructing query for objectclass \"%s\"\n",
+ oc->bom_oc->soc_cname.bv_val );
+ /*
+ * FIXME: need to separate errors from legally
+ * impossible filters
+ */
+ switch ( bsi->bsi_status ) {
+ case LDAP_SUCCESS:
+ case LDAP_UNDEFINED_TYPE:
+ case LDAP_NO_SUCH_OBJECT:
+ /* we are conservative... */
+ default:
+ bsi->bsi_status = LDAP_SUCCESS;
+ /* try next */
+ return BACKSQL_AVL_CONTINUE;
+
+ case LDAP_ADMINLIMIT_EXCEEDED:
+ case LDAP_OTHER:
+ /* don't try any more */
+ return BACKSQL_AVL_STOP;
+ }
+ }
+
+ if ( BER_BVISNULL( &query ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "could not construct query for objectclass \"%s\"\n",
+ oc->bom_oc->soc_cname.bv_val );
+ bsi->bsi_status = LDAP_SUCCESS;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "Constructed query: %s\n",
+ query.bv_val );
+
+ rc = backsql_Prepare( bsi->bsi_dbh, &sth, query.bv_val, 0 );
+ bsi->bsi_op->o_tmpfree( query.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &query );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error preparing query\n" );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "id: '" BACKSQL_IDNUMFMT "'\n",
+ bsi->bsi_oc->bom_id );
+
+ rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT,
+ &bsi->bsi_oc->bom_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error binding objectclass id parameter\n" );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ switch ( bsi->bsi_scope ) {
+ case LDAP_SCOPE_BASE:
+ case BACKSQL_SCOPE_BASE_LIKE:
+ /*
+ * We do not accept DNs longer than BACKSQL_MAX_DN_LEN;
+ * however this should be handled earlier
+ */
+ if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ AC_MEMCPY( tmp_base_ndn, bsi->bsi_base_ndn->bv_val,
+ bsi->bsi_base_ndn->bv_len + 1 );
+
+ /* uppercase DN only if the stored DN can be uppercased
+ * for comparison */
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ ldap_pvt_str2upper( tmp_base_ndn );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "(base)dn: \"%s\"\n",
+ tmp_base_ndn );
+
+ rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT,
+ tmp_base_ndn, BACKSQL_MAX_DN_LEN );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error binding base_ndn parameter\n" );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh,
+ sth, rc );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_SUBTREE:
+ {
+ /* if short-cutting the search base,
+ * don't bind any parameter */
+ if ( bsi->bsi_use_subtree_shortcut ) {
+ break;
+ }
+
+ /*
+ * We do not accept DNs longer than BACKSQL_MAX_DN_LEN;
+ * however this should be handled earlier
+ */
+ if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ /*
+ * Sets the parameters for the SQL built earlier
+ * NOTE that all the databases could actually use
+ * the TimesTen version, which would be cleaner
+ * and would also eliminate the need for the
+ * subtree_cond line in the configuration file.
+ * For now, I'm leaving it the way it is,
+ * so non-TimesTen databases use the original code.
+ * But at some point this should get cleaned up.
+ *
+ * If "dn" is being used, do a suffix search.
+ * If "dn_ru" is being used, do a prefix search.
+ */
+ if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
+ tmp_base_ndn[ 0 ] = '\0';
+
+ for ( i = 0, j = bsi->bsi_base_ndn->bv_len - 1;
+ j >= 0; i++, j--) {
+ tmp_base_ndn[ i ] = bsi->bsi_base_ndn->bv_val[ j ];
+ }
+
+ if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
+ tmp_base_ndn[ i++ ] = ',';
+ }
+
+ tmp_base_ndn[ i ] = '%';
+ tmp_base_ndn[ i + 1 ] = '\0';
+
+ } else {
+ i = 0;
+
+ tmp_base_ndn[ i++ ] = '%';
+
+ if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
+ tmp_base_ndn[ i++ ] = ',';
+ }
+
+ AC_MEMCPY( &tmp_base_ndn[ i ], bsi->bsi_base_ndn->bv_val,
+ bsi->bsi_base_ndn->bv_len + 1 );
+ }
+
+ /* uppercase DN only if the stored DN can be uppercased
+ * for comparison */
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ ldap_pvt_str2upper( tmp_base_ndn );
+ }
+
+ if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
+ Debug( LDAP_DEBUG_TRACE, "(children)dn: \"%s\"\n",
+ tmp_base_ndn );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "(sub)dn: \"%s\"\n",
+ tmp_base_ndn );
+ }
+
+ rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT,
+ tmp_base_ndn, BACKSQL_MAX_DN_LEN );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error binding base_ndn parameter (2)\n" );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh,
+ sth, rc );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+ break;
+ }
+
+ case LDAP_SCOPE_ONELEVEL:
+ assert( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) );
+
+ Debug( LDAP_DEBUG_TRACE, "(one)id=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(bsi->bsi_base_id.eid_id) );
+ rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT,
+ &bsi->bsi_base_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error binding base id parameter\n" );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+ break;
+ }
+
+ rc = SQLExecute( sth );
+ if ( !BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "error executing query\n" );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ bsi->bsi_status = LDAP_OTHER;
+ return BACKSQL_AVL_CONTINUE;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx );
+ rc = SQLFetch( sth );
+ for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) {
+ struct berval dn, pdn, ndn;
+ backsql_entryID *c_id = NULL;
+ int ret;
+
+ ber_str2bv( row.cols[ 3 ], 0, 0, &dn );
+
+ if ( backsql_api_odbc2dn( bsi->bsi_op, bsi->bsi_rs, &dn ) ) {
+ continue;
+ }
+
+ ret = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx );
+ if ( dn.bv_val != row.cols[ 3 ] ) {
+ free( dn.bv_val );
+ }
+
+ if ( ret != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ if ( bi->sql_baseObject && dn_match( &ndn, &bi->sql_baseObject->e_nname ) ) {
+ goto cleanup;
+ }
+
+ c_id = (backsql_entryID *)op->o_tmpcalloc( 1,
+ sizeof( backsql_entryID ), op->o_tmpmemctx );
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_str2bv_x( row.cols[ 0 ], 0, 1, &c_id->eid_id,
+ op->o_tmpmemctx );
+ ber_str2bv_x( row.cols[ 1 ], 0, 1, &c_id->eid_keyval,
+ op->o_tmpmemctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ if ( BACKSQL_STR2ID( &c_id->eid_id, row.cols[ 0 ], 0 ) != 0 ) {
+ goto cleanup;
+ }
+ if ( BACKSQL_STR2ID( &c_id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) {
+ goto cleanup;
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ c_id->eid_oc = bsi->bsi_oc;
+ c_id->eid_oc_id = bsi->bsi_oc->bom_id;
+
+ c_id->eid_dn = pdn;
+ c_id->eid_ndn = ndn;
+
+ /* append at end of list ... */
+ c_id->eid_next = NULL;
+ *bsi->bsi_id_listtail = c_id;
+ bsi->bsi_id_listtail = &c_id->eid_next;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
+ "added entry id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDFMT " dn=\"%s\"\n",
+ BACKSQL_IDARG(c_id->eid_id),
+ BACKSQL_IDARG(c_id->eid_keyval),
+ row.cols[ 3 ] );
+
+ /* count candidates, for unchecked limit */
+ bsi->bsi_n_candidates--;
+ if ( bsi->bsi_n_candidates == -1 ) {
+ break;
+ }
+ continue;
+
+cleanup:;
+ if ( !BER_BVISNULL( &pdn ) ) {
+ op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &ndn ) ) {
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+ }
+ if ( c_id != NULL ) {
+ ch_free( c_id );
+ }
+ }
+ backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_oc_get_candidates(): %d\n",
+ n_candidates - bsi->bsi_n_candidates );
+
+ return ( bsi->bsi_n_candidates == -1 ? BACKSQL_AVL_STOP : BACKSQL_AVL_CONTINUE );
+}
+
+int
+backsql_search( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ int sres;
+ Entry user_entry = { 0 },
+ base_entry = { 0 };
+ int manageDSAit = get_manageDSAit( op );
+ time_t stoptime = 0;
+ backsql_srch_info bsi = { 0 };
+ backsql_entryID *eid = NULL;
+ struct berval nbase = BER_BVNULL;
+#ifndef BACKSQL_ARBITRARY_KEY
+ ID lastid = 0;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_search(): "
+ "base=\"%s\", filter=\"%s\", scope=%d,",
+ op->o_req_ndn.bv_val,
+ op->ors_filterstr.bv_val,
+ op->ors_scope );
+ Debug( LDAP_DEBUG_TRACE, " deref=%d, attrsonly=%d, "
+ "attributes to load: %s\n",
+ op->ors_deref,
+ op->ors_attrsonly,
+ op->ors_attrs == NULL ? "all" : "custom list" );
+
+ if ( op->o_req_ndn.bv_len > BACKSQL_MAX_DN_LEN ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
+ "search base length (%ld) exceeds max length (%d)\n",
+ op->o_req_ndn.bv_len, BACKSQL_MAX_DN_LEN );
+ /*
+ * FIXME: a LDAP_NO_SUCH_OBJECT could be appropriate
+ * since it is impossible that such a long DN exists
+ * in the backend
+ */
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ return 1;
+ }
+
+ sres = backsql_get_db_conn( op, &dbh );
+ if ( sres != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
+ "could not get connection handle - exiting\n" );
+ rs->sr_err = sres;
+ rs->sr_text = sres == LDAP_OTHER ? "SQL-backend error" : NULL;
+ send_ldap_result( op, rs );
+ return 1;
+ }
+
+ /* compute it anyway; root does not use it */
+ stoptime = op->o_time + op->ors_tlimit;
+
+ /* init search */
+ bsi.bsi_e = &base_entry;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ op->ors_scope,
+ stoptime, op->ors_filter,
+ dbh, op, rs, op->ors_attrs,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_REFERRAL:
+ if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
+ dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ break;
+ }
+
+ /* an entry was created; free it */
+ entry_clean( bsi.bsi_e );
+
+ /* fall thru */
+
+ default:
+ if ( !BER_BVISNULL( &base_entry.e_nname )
+ && !access_allowed( op, &base_entry,
+ slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ rs->sr_matched = NULL;
+ rs->sr_text = NULL;
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ if ( !BER_BVISNULL( &base_entry.e_nname ) ) {
+ entry_clean( &base_entry );
+ }
+
+ goto done;
+ }
+ /* NOTE: __NEW__ "search" access is required
+ * on searchBase object */
+ {
+ slap_mask_t mask;
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &base_entry, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+
+ }
+ if ( ! access_allowed_mask( op, &base_entry,
+ slap_schema.si_ad_entry,
+ NULL, ACL_SEARCH, NULL, &mask ) )
+ {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ }
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ }
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ bsi.bsi_e = NULL;
+
+ bsi.bsi_n_candidates =
+ ( op->ors_limit == NULL /* isroot == TRUE */ ? -2 :
+ ( op->ors_limit->lms_s_unchecked == -1 ? -2 :
+ ( op->ors_limit->lms_s_unchecked ) ) );
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* If paged results are in effect, check the paging cookie */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ rs->sr_err = parse_paged_cookie( op, rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ switch ( bsi.bsi_scope ) {
+ case LDAP_SCOPE_BASE:
+ case BACKSQL_SCOPE_BASE_LIKE:
+ /*
+ * probably already found...
+ */
+ bsi.bsi_id_list = &bsi.bsi_base_id;
+ bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next;
+ break;
+
+ case LDAP_SCOPE_SUBTREE:
+ /*
+ * if baseObject is defined, and if it is the root
+ * of the search, add it to the candidate list
+ */
+ if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &bsi.bsi_base_id.eid_id ) )
+ {
+ bsi.bsi_id_list = &bsi.bsi_base_id;
+ bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next;
+ }
+
+ /* FALLTHRU */
+ default:
+
+ /*
+ * for each objectclass we try to construct query which gets IDs
+ * of entries matching LDAP query filter and scope (or at least
+ * candidates), and get the IDs. Do this in ID order for paging.
+ */
+ ldap_avl_apply( bi->sql_oc_by_id, backsql_oc_get_candidates,
+ &bsi, BACKSQL_AVL_STOP, AVL_INORDER );
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ eid = bsi.bsi_id_list;
+ rs->sr_err = SLAPD_ABANDON;
+ goto send_results;
+ }
+ }
+
+ if ( op->ors_limit != NULL /* isroot == FALSE */
+ && op->ors_limit->lms_s_unchecked != -1
+ && bsi.bsi_n_candidates == -1 )
+ {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /*
+ * now we load candidate entries (only those attributes
+ * mentioned in attrs and filter), test it against full filter
+ * and then send to client; don't free entry_id if baseObject...
+ */
+ for ( eid = bsi.bsi_id_list;
+ eid != NULL;
+ eid = backsql_free_entryID(
+ eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) )
+ {
+ int rc;
+ Attribute *a_hasSubordinate = NULL,
+ *a_entryUUID = NULL,
+ *a_entryCSN = NULL,
+ **ap = NULL;
+ Entry *e = NULL;
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ goto send_results;
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
+ rs->sr_ctrls = NULL;
+ rs->sr_ref = rs->sr_v2ref;
+ goto send_results;
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "backsql_search(): loading data "
+ "for entry id=" BACKSQL_IDFMT " oc_id=" BACKSQL_IDNUMFMT ", keyval=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(eid->eid_id),
+ eid->eid_oc_id,
+ BACKSQL_IDARG(eid->eid_keyval) );
+
+ /* check scope */
+ switch ( op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ case BACKSQL_SCOPE_BASE_LIKE:
+ if ( !dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) {
+ goto next_entry2;
+ }
+ break;
+
+ case LDAP_SCOPE_ONE:
+ {
+ struct berval rdn = eid->eid_ndn;
+
+ rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
+ if ( !dnIsOneLevelRDN( &rdn ) ) {
+ goto next_entry2;
+ }
+ /* fall thru */
+ }
+
+ case LDAP_SCOPE_SUBORDINATE:
+ /* discard the baseObject entry */
+ if ( dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) {
+ goto next_entry2;
+ }
+ /* FALLTHRU */
+ case LDAP_SCOPE_SUBTREE:
+ /* FIXME: this should never fail... */
+ if ( !dnIsSuffix( &eid->eid_ndn, &op->o_req_ndn ) ) {
+ goto next_entry2;
+ }
+ break;
+ }
+
+ if ( BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) {
+ /* don't recollect baseObject... */
+ e = bi->sql_baseObject;
+
+ } else if ( eid == &bsi.bsi_base_id ) {
+ /* don't recollect searchBase object... */
+ e = &base_entry;
+
+ } else {
+ bsi.bsi_e = &user_entry;
+ rc = backsql_id2entry( &bsi, eid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
+ "error %d in backsql_id2entry() "
+ "- skipping\n", rc );
+ continue;
+ }
+ e = &user_entry;
+ }
+
+ if ( !manageDSAit &&
+ op->ors_scope != LDAP_SCOPE_BASE &&
+ op->ors_scope != BACKSQL_SCOPE_BASE_LIKE &&
+ is_entry_referral( e ) )
+ {
+ BerVarray refs;
+
+ refs = get_entry_referrals( op, e );
+ if ( !refs ) {
+ backsql_srch_info bsi2 = { 0 };
+ Entry user_entry2 = { 0 };
+
+ /* retry with the full entry... */
+ bsi2.bsi_e = &user_entry2;
+ rc = backsql_init_search( &bsi2,
+ &e->e_nname,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL,
+ dbh, op, rs, NULL,
+ BACKSQL_ISF_GET_ENTRY );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( is_entry_referral( &user_entry2 ) )
+ {
+ refs = get_entry_referrals( op,
+ &user_entry2 );
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ }
+ backsql_entry_clean( op, &user_entry2 );
+ }
+ if ( bsi2.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi2.bsi_attrs,
+ op->o_tmpmemctx );
+ }
+ }
+
+ if ( refs ) {
+ rs->sr_ref = referral_rewrite( refs,
+ &e->e_name,
+ &op->o_req_dn,
+ op->ors_scope );
+ ber_bvarray_free( refs );
+ }
+
+ if ( rs->sr_ref ) {
+ rs->sr_err = LDAP_REFERRAL;
+
+ } else {
+ rs->sr_text = "bad referral object";
+ }
+
+ rs->sr_entry = e;
+ rs->sr_matched = user_entry.e_name.bv_val;
+ send_search_reference( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_entry = NULL;
+ if ( rs->sr_err == LDAP_REFERRAL ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ goto next_entry;
+ }
+
+ /*
+ * We use this flag since we need to parse the filter
+ * anyway; we should have used the frontend API function
+ * filter_has_subordinates()
+ */
+ if ( bsi.bsi_flags & BSQL_SF_FILTER_HASSUBORDINATE ) {
+ rc = backsql_has_children( op, dbh, &e->e_nname );
+
+ switch ( rc ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ a_hasSubordinate = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE );
+ if ( a_hasSubordinate != NULL ) {
+ for ( ap = &user_entry.e_attrs;
+ *ap;
+ ap = &(*ap)->a_next );
+
+ *ap = a_hasSubordinate;
+ }
+ rc = 0;
+ break;
+
+ default:
+ Debug(LDAP_DEBUG_TRACE,
+ "backsql_search(): "
+ "has_children failed( %d)\n",
+ rc );
+ rc = 1;
+ goto next_entry;
+ }
+ }
+
+ if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYUUID ) {
+ a_entryUUID = backsql_operational_entryUUID( bi, eid );
+ if ( a_entryUUID != NULL ) {
+ if ( ap == NULL ) {
+ ap = &user_entry.e_attrs;
+ }
+
+ for ( ; *ap; ap = &(*ap)->a_next );
+
+ *ap = a_entryUUID;
+ }
+ }
+
+#ifdef BACKSQL_SYNCPROV
+ if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYCSN ) {
+ a_entryCSN = backsql_operational_entryCSN( op );
+ if ( a_entryCSN != NULL ) {
+ if ( ap == NULL ) {
+ ap = &user_entry.e_attrs;
+ }
+
+ for ( ; *ap; ap = &(*ap)->a_next );
+
+ *ap = a_entryCSN;
+ }
+ }
+#endif /* BACKSQL_SYNCPROV */
+
+ if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE )
+ {
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* If paged results are in effect, see if the page limit was exceeded */
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size )
+ {
+ e = NULL;
+ send_paged_response( op, rs, &lastid );
+ goto done;
+ }
+ lastid = SQL_TO_PAGECOOKIE( eid->eid_id, eid->eid_oc_id );
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_entry = e;
+ e->e_private = (void *)eid;
+ rs->sr_flags = ( e == &user_entry ) ? REP_ENTRY_MODIFIABLE : 0;
+ /* FIXME: need the whole entry (ITS#3480) */
+ rs->sr_err = send_search_entry( op, rs );
+ e->e_private = NULL;
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ rs->sr_operational_attrs = NULL;
+
+ switch ( rs->sr_err ) {
+ case LDAP_UNAVAILABLE:
+ /*
+ * FIXME: send_search_entry failed;
+ * better stop
+ */
+ Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
+ "connection lost\n" );
+ goto end_of_search;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_BUSY:
+ goto send_results;
+ }
+ }
+
+next_entry:;
+ if ( e == &user_entry ) {
+ backsql_entry_clean( op, &user_entry );
+ }
+
+next_entry2:;
+ }
+
+end_of_search:;
+ if ( rs->sr_nentries > 0 ) {
+ rs->sr_ref = rs->sr_v2ref;
+ rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS
+ : LDAP_REFERRAL;
+
+ } else {
+ rs->sr_err = bsi.bsi_status;
+ }
+
+send_results:;
+ if ( rs->sr_err != SLAPD_ABANDON ) {
+#ifndef BACKSQL_ARBITRARY_KEY
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ send_paged_response( op, rs, NULL );
+ } else
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ /* cleanup in case of abandon */
+ for ( ; eid != NULL;
+ eid = backsql_free_entryID(
+ eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) )
+ ;
+
+ backsql_entry_clean( op, &base_entry );
+
+ /* in case we got here accidentally */
+ backsql_entry_clean( op, &user_entry );
+
+ if ( rs->sr_v2ref ) {
+ ber_bvarray_free( rs->sr_v2ref );
+ rs->sr_v2ref = NULL;
+ }
+
+#ifdef BACKSQL_SYNCPROV
+ if ( op->o_sync ) {
+ Operation op2 = *op;
+ SlapReply rs2 = { REP_RESULT };
+ Entry *e = entry_alloc();
+ slap_callback cb = { 0 };
+
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = select_backend( &op->o_bd->be_nsuffix[0], 0 );
+ op2.ora_e = e;
+ op2.o_callback = &cb;
+
+ ber_dupbv( &e->e_name, op->o_bd->be_suffix );
+ ber_dupbv( &e->e_nname, op->o_bd->be_nsuffix );
+
+ cb.sc_response = slap_null_cb;
+
+ op2.o_bd->be_add( &op2, &rs2 );
+
+ if ( op2.ora_e == e )
+ entry_free( e );
+ }
+#endif /* BACKSQL_SYNCPROV */
+
+done:;
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &nbase )
+ && nbase.bv_val != op->o_req_ndn.bv_val )
+ {
+ ch_free( nbase.bv_val );
+ }
+
+ /* restore scope ... FIXME: this should be done before ANY
+ * frontend call that uses op */
+ if ( op->ors_scope == BACKSQL_SCOPE_BASE_LIKE ) {
+ op->ors_scope = LDAP_SCOPE_BASE;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_search()\n" );
+
+ return rs->sr_err;
+}
+
+/* return LDAP_SUCCESS IFF we can retrieve the specified entry.
+ */
+int
+backsql_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ backsql_srch_info bsi = { 0 };
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ int rc;
+ SlapReply rs = { 0 };
+ AttributeName anlist[ 2 ];
+
+ *ent = NULL;
+
+ rc = backsql_get_db_conn( op, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( at ) {
+ anlist[ 0 ].an_name = at->ad_cname;
+ anlist[ 0 ].an_desc = at;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+ }
+
+ bsi.bsi_e = entry_alloc();
+ rc = backsql_init_search( &bsi,
+ ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL,
+ dbh, op, &rs, at ? anlist : NULL,
+ BACKSQL_ISF_GET_ENTRY );
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+
+#if 0 /* not supported at present */
+ /* find attribute values */
+ if ( is_entry_alias( bsi.bsi_e ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: entry is an alias\n" );
+ rc = LDAP_ALIAS_PROBLEM;
+ goto return_results;
+ }
+#endif
+
+ if ( is_entry_referral( bsi.bsi_e ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: entry is a referral\n" );
+ rc = LDAP_REFERRAL;
+ goto return_results;
+ }
+
+ if ( oc && !is_entry_objectclass( bsi.bsi_e, oc, 0 ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: "
+ "failed to find objectClass\n" );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+ *ent = bsi.bsi_e;
+ }
+
+return_results:;
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ if ( bsi.bsi_e ) {
+ entry_free( bsi.bsi_e );
+ }
+ }
+
+ return rc;
+}
+
+void
+backsql_entry_clean(
+ Operation *op,
+ Entry *e )
+{
+ void *ctx;
+
+ ctx = ldap_pvt_thread_pool_context();
+
+ if ( ctx == NULL || ctx != op->o_tmpmemctx ) {
+ if ( !BER_BVISNULL( &e->e_name ) ) {
+ op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &e->e_name );
+ }
+
+ if ( !BER_BVISNULL( &e->e_nname ) ) {
+ op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &e->e_nname );
+ }
+ }
+
+ entry_clean( e );
+}
+
+int
+backsql_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ backsql_entry_clean( op, e );
+
+ entry_free( e );
+
+ return 0;
+}
+
+#ifndef BACKSQL_ARBITRARY_KEY
+/* This function is copied verbatim from back-bdb/search.c */
+static int
+parse_paged_cookie( Operation *op, SlapReply *rs )
+{
+ int rc = LDAP_SUCCESS;
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* this function must be invoked only if the pagedResults
+ * control has been detected, parsed and partially checked
+ * by the frontend */
+ assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED );
+
+ /* cookie decoding/checks deferred to backend... */
+ if ( ps->ps_cookieval.bv_len ) {
+ PagedResultsCookie reqcookie;
+ if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie ));
+
+ if ( reqcookie > ps->ps_cookie ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+
+ } else if ( reqcookie < ps->ps_cookie ) {
+ rs->sr_text = "paged results cookie is invalid or old";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ } else {
+ /* Initial request. Initialize state. */
+ ps->ps_cookie = 0;
+ ps->ps_count = 0;
+ }
+
+done:;
+
+ return rc;
+}
+
+/* This function is copied nearly verbatim from back-bdb/search.c */
+static void
+send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid )
+{
+ LDAPControl ctrl, *ctrls[2];
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie respcookie;
+ struct berval cookie;
+
+ Debug(LDAP_DEBUG_ARGS,
+ "send_paged_response: lastid=0x%08lx nentries=%d\n",
+ lastid ? *lastid : 0, rs->sr_nentries );
+
+ BER_BVZERO( &ctrl.ldctl_value );
+ ctrls[0] = &ctrl;
+ ctrls[1] = NULL;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( lastid ) {
+ respcookie = ( PagedResultsCookie )(*lastid);
+ cookie.bv_len = sizeof( respcookie );
+ cookie.bv_val = (char *)&respcookie;
+
+ } else {
+ respcookie = ( PagedResultsCookie )0;
+ BER_BVSTR( &cookie, "" );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = respcookie;
+ op->o_conn->c_pagedresults_state.ps_count =
+ ((PagedResultsState *)op->o_pagedresults_state)->ps_count +
+ rs->sr_nentries;
+
+ /* return size of 0 -- no estimate */
+ ber_printf( ber, "{iO}", 0, &cookie );
+
+ if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) {
+ goto done;
+ }
+
+ ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrls[0]->ldctl_iscritical = 0;
+
+ rs->sr_ctrls = ctrls;
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+ rs->sr_ctrls = NULL;
+
+done:
+ (void) ber_free_buf( ber );
+}
+#endif /* ! BACKSQL_ARBITRARY_KEY */
diff --git a/servers/slapd/back-sql/sql-wrap.c b/servers/slapd/back-sql/sql-wrap.c
new file mode 100644
index 0000000..289b590
--- /dev/null
+++ b/servers/slapd/back-sql/sql-wrap.c
@@ -0,0 +1,529 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+#define MAX_ATTR_LEN 16384
+
+void
+backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc )
+{
+ SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH]; /* msg. buffer */
+ SQLCHAR state[SQL_SQLSTATE_SIZE]; /* statement buf. */
+ SDWORD iSqlCode; /* return code */
+ SWORD len = SQL_MAX_MESSAGE_LENGTH - 1; /* return length */
+
+ Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc );
+
+ for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg,
+ SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ " nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n",
+ (int)iSqlCode, state, msg );
+ }
+}
+
+RETCODE
+backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout )
+{
+ RETCODE rc;
+
+ rc = SQLAllocStmt( dbh, sth );
+ if ( rc != SQL_SUCCESS ) {
+ return rc;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n" );
+#endif /* BACKSQL_TRACE */
+
+#ifdef BACKSQL_MSSQL_WORKAROUND
+ {
+ char drv_name[ 30 ];
+ SWORD len;
+
+ SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n",
+ drv_name );
+#endif /* BACKSQL_TRACE */
+
+ ldap_pvt_str2upper( drv_name );
+ if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) {
+ /*
+ * stupid default result set in MS SQL Server
+ * does not support multiple active statements
+ * on the same connection -- so we are trying
+ * to make it not to use default result set...
+ */
+ Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
+ "enabling MS SQL Server default result "
+ "set workaround\n" );
+ rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY,
+ SQL_CONCUR_ROWVER );
+ if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): "
+ "SQLSetStmtOption(SQL_CONCURRENCY,"
+ "SQL_CONCUR_ROWVER) failed:\n" );
+ backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
+ SQLFreeStmt( *sth, SQL_DROP );
+ return rc;
+ }
+ }
+ }
+#endif /* BACKSQL_MSSQL_WORKAROUND */
+
+ if ( timeout > 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
+ "setting query timeout to %d sec.\n",
+ timeout );
+ rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout );
+ if ( rc != SQL_SUCCESS ) {
+ backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
+ SQLFreeStmt( *sth, SQL_DROP );
+ return rc;
+ }
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n" );
+#endif /* BACKSQL_TRACE */
+
+ return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS );
+}
+
+RETCODE
+backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx )
+{
+ RETCODE rc;
+
+ if ( row == NULL ) {
+ return SQL_ERROR;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n" );
+#endif /* BACKSQL_TRACE */
+
+ rc = SQLNumResultCols( sth, &row->ncols );
+ if ( rc != SQL_SUCCESS ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): "
+ "SQLNumResultCols() failed:\n" );
+#endif /* BACKSQL_TRACE */
+
+ backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc );
+
+ } else {
+ SQLCHAR colname[ 64 ];
+ SQLSMALLINT name_len, col_type, col_scale, col_null;
+ SQLLEN col_prec;
+ int i;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "ncols=%d\n", (int)row->ncols );
+#endif /* BACKSQL_TRACE */
+
+ row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1,
+ sizeof( struct berval ), ctx );
+ if ( row->col_names == NULL ) {
+ goto nomem;
+ }
+
+ row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols,
+ sizeof( UDWORD ), ctx );
+ if ( row->col_prec == NULL ) {
+ goto nomem;
+ }
+
+ row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols,
+ sizeof( SQLSMALLINT ), ctx );
+ if ( row->col_type == NULL ) {
+ goto nomem;
+ }
+
+ row->cols = (char **)ber_memcalloc_x( row->ncols + 1,
+ sizeof( char * ), ctx );
+ if ( row->cols == NULL ) {
+ goto nomem;
+ }
+
+ row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols,
+ sizeof( SQLLEN ), ctx );
+ if ( row->value_len == NULL ) {
+ goto nomem;
+ }
+
+ if ( 0 ) {
+nomem:
+ ber_memfree_x( row->col_names, ctx );
+ row->col_names = NULL;
+ ber_memfree_x( row->col_prec, ctx );
+ row->col_prec = NULL;
+ ber_memfree_x( row->col_type, ctx );
+ row->col_type = NULL;
+ ber_memfree_x( row->cols, ctx );
+ row->cols = NULL;
+ ber_memfree_x( row->value_len, ctx );
+ row->value_len = NULL;
+
+ Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: "
+ "out of memory\n" );
+
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = 0; i < row->ncols; i++ ) {
+ SQLSMALLINT TargetType;
+
+ rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ],
+ (SQLUINTEGER)( sizeof( colname ) - 1 ),
+ &name_len, &col_type,
+ &col_prec, &col_scale, &col_null );
+ /* FIXME: test rc? */
+
+ ber_str2bv_x( (char *)colname, 0, 1,
+ &row->col_names[ i ], ctx );
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_prec[%d]=%d\n",
+ colname, (int)(i + 1), (int)col_prec );
+#endif /* BACKSQL_TRACE */
+ if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR )
+ {
+ col_prec = MAX_ATTR_LEN;
+ }
+
+ row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1,
+ sizeof( char ), ctx );
+ row->col_prec[ i ] = col_prec;
+ row->col_type[ i ] = col_type;
+
+ /*
+ * ITS#3386, ITS#3113 - 20070308
+ * Note: there are many differences between various DPMS and ODBC
+ * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR. YMMV:
+ * This has only been tested on Linux/MySQL/UnixODBC
+ * For BINARY-type Fields (BLOB, etc), read the data as BINARY
+ */
+ if ( BACKSQL_IS_BINARY( col_type ) ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_type[%d]=%d: reading binary data\n",
+ colname, (int)(i + 1), (int)col_type);
+#endif /* BACKSQL_TRACE */
+ TargetType = SQL_C_BINARY;
+
+ } else {
+ /* Otherwise read it as Character data */
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_type[%d]=%d: reading character data\n",
+ colname, (int)(i + 1), (int)col_type);
+#endif /* BACKSQL_TRACE */
+ TargetType = SQL_C_CHAR;
+ }
+
+ rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1),
+ TargetType,
+ (SQLPOINTER)row->cols[ i ],
+ col_prec + 1,
+ &row->value_len[ i ] );
+
+ /* FIXME: test rc? */
+ }
+
+ BER_BVZERO( &row->col_names[ i ] );
+ row->cols[ i ] = NULL;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n" );
+#endif /* BACKSQL_TRACE */
+
+ return rc;
+}
+
+RETCODE
+backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row )
+{
+ return backsql_BindRowAsStrings_x( sth, row, NULL );
+}
+
+RETCODE
+backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx )
+{
+ if ( row->cols == NULL ) {
+ return SQL_ERROR;
+ }
+
+ ber_bvarray_free_x( row->col_names, ctx );
+ ber_memfree_x( row->col_prec, ctx );
+ ber_memfree_x( row->col_type, ctx );
+ ber_memvfree_x( (void **)row->cols, ctx );
+ ber_memfree_x( row->value_len, ctx );
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE
+backsql_FreeRow( BACKSQL_ROW_NTS *row )
+{
+ return backsql_FreeRow_x( row, NULL );
+}
+
+static void
+backsql_close_db_handle( SQLHDBC dbh )
+{
+ if ( dbh == SQL_NULL_HDBC ) {
+ return;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n",
+ (void *)dbh );
+
+ /*
+ * Default transact is SQL_ROLLBACK; commit is required only
+ * by write operations, and it is explicitly performed after
+ * each atomic operation succeeds.
+ */
+
+ /* TimesTen */
+ SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
+ SQLDisconnect( dbh );
+ SQLFreeConnect( dbh );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n",
+ (void *)dbh );
+}
+
+int
+backsql_conn_destroy(
+ backsql_info *bi )
+{
+ return 0;
+}
+
+int
+backsql_init_db_env( backsql_info *bi )
+{
+ RETCODE rc;
+ int ret = SQL_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n" );
+
+ rc = SQLAllocEnv( &bi->sql_db_env );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n" );
+ backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC,
+ SQL_NULL_HENV, rc );
+ ret = SQL_ERROR;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret );
+
+ return ret;
+}
+
+int
+backsql_free_db_env( backsql_info *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n" );
+
+ (void)SQLFreeEnv( bi->sql_db_env );
+ bi->sql_db_env = SQL_NULL_HENV;
+
+ /*
+ * stop, if frontend waits for all threads to shutdown
+ * before calling this -- then what are we going to delete??
+ * everything is already deleted...
+ */
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n" );
+
+ return SQL_SUCCESS;
+}
+
+static int
+backsql_open_db_handle(
+ backsql_info *bi,
+ SQLHDBC *dbhp )
+{
+ /* TimesTen */
+ char DBMSName[ 32 ];
+ int rc;
+
+ assert( dbhp != NULL );
+ *dbhp = SQL_NULL_HDBC;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n" );
+
+ rc = SQLAllocConnect( bi->sql_db_env, dbhp );
+ if ( !BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLAllocConnect() failed:\n" );
+ backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC,
+ SQL_NULL_HENV, rc );
+ return LDAP_UNAVAILABLE;
+ }
+
+ rc = SQLConnect( *dbhp,
+ (SQLCHAR*)bi->sql_dbname, SQL_NTS,
+ (SQLCHAR*)bi->sql_dbuser, SQL_NTS,
+ (SQLCHAR*)bi->sql_dbpasswd, SQL_NTS );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLConnect() to database \"%s\" %s.\n",
+ bi->sql_dbname,
+ rc == SQL_SUCCESS_WITH_INFO ?
+ "succeeded with info" : "failed" );
+ backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
+ if ( rc != SQL_SUCCESS_WITH_INFO ) {
+ SQLFreeConnect( *dbhp );
+ return LDAP_UNAVAILABLE;
+ }
+ }
+
+ /*
+ * TimesTen : Turn off autocommit. We must explicitly
+ * commit any transactions.
+ */
+ SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT,
+ BACKSQL_AUTOCOMMIT_ON( bi ) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF );
+
+ /*
+ * See if this connection is to TimesTen. If it is,
+ * remember that fact for later use.
+ */
+ /* Assume until proven otherwise */
+ bi->sql_flags &= ~BSQLF_USE_REVERSE_DN;
+ DBMSName[ 0 ] = '\0';
+ rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName,
+ sizeof( DBMSName ), NULL );
+ if ( rc == SQL_SUCCESS ) {
+ if ( strcmp( DBMSName, "TimesTen" ) == 0 ||
+ strcmp( DBMSName, "Front-Tier" ) == 0 )
+ {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "TimesTen database!\n" );
+ bi->sql_flags |= BSQLF_USE_REVERSE_DN;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLGetInfo() failed.\n" );
+ backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
+ SQLDisconnect( *dbhp );
+ SQLFreeConnect( *dbhp );
+ return LDAP_UNAVAILABLE;
+ }
+ /* end TimesTen */
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n" );
+
+ return LDAP_SUCCESS;
+}
+
+static void *backsql_db_conn_dummy;
+
+static void
+backsql_db_conn_keyfree(
+ void *key,
+ void *data )
+{
+ (void)backsql_close_db_handle( (SQLHDBC)data );
+}
+
+int
+backsql_free_db_conn( Operation *op, SQLHDBC dbh )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n" );
+
+ (void)backsql_close_db_handle( dbh );
+ ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ &backsql_db_conn_dummy, (void *)SQL_NULL_HDBC,
+ backsql_db_conn_keyfree, NULL, NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n" );
+
+ return LDAP_SUCCESS;
+}
+
+int
+backsql_get_db_conn( Operation *op, SQLHDBC *dbhp )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n" );
+
+ assert( dbhp != NULL );
+ *dbhp = SQL_NULL_HDBC;
+
+ if ( op->o_threadctx ) {
+ void *data = NULL;
+
+ ldap_pvt_thread_pool_getkey( op->o_threadctx,
+ &backsql_db_conn_dummy, &data, NULL );
+ dbh = (SQLHDBC)data;
+
+ } else {
+ dbh = bi->sql_dbh;
+ }
+
+ if ( dbh == SQL_NULL_HDBC ) {
+ rc = backsql_open_db_handle( bi, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( op->o_threadctx ) {
+ void *data = (void *)dbh;
+
+ ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ &backsql_db_conn_dummy, data,
+ backsql_db_conn_keyfree, NULL, NULL );
+
+ } else {
+ bi->sql_dbh = dbh;
+ }
+ }
+
+ *dbhp = dbh;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n" );
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-sql/util.c b/servers/slapd/back-sql/util.c
new file mode 100644
index 0000000..94a00ca
--- /dev/null
+++ b/servers/slapd/back-sql/util.c
@@ -0,0 +1,572 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+#include "ac/ctype.h"
+#include "ac/stdarg.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+#include "lutil.h"
+
+#define BACKSQL_MAX(a,b) ((a)>(b)?(a):(b))
+#define BACKSQL_MIN(a,b) ((a)<(b)?(a):(b))
+
+#define BACKSQL_STR_GROW 256
+
+const char backsql_def_oc_query[] =
+ "SELECT id,name,keytbl,keycol,create_proc,delete_proc,expect_return "
+ "FROM ldap_oc_mappings";
+const char backsql_def_needs_select_oc_query[] =
+ "SELECT id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,"
+ "expect_return FROM ldap_oc_mappings";
+const char backsql_def_at_query[] =
+ "SELECT name,sel_expr,from_tbls,join_where,add_proc,delete_proc,"
+ "param_order,expect_return,sel_expr_u FROM ldap_attr_mappings "
+ "WHERE oc_map_id=?";
+const char backsql_def_delentry_stmt[] = "DELETE FROM ldap_entries WHERE id=?";
+const char backsql_def_renentry_stmt[] =
+ "UPDATE ldap_entries SET dn=?,parent=?,keyval=? WHERE id=?";
+const char backsql_def_insentry_stmt[] =
+ "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) "
+ "VALUES (?,?,?,?)";
+const char backsql_def_delobjclasses_stmt[] = "DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=?";
+const char backsql_def_subtree_cond[] = "ldap_entries.dn LIKE CONCAT('%',?)";
+const char backsql_def_upper_subtree_cond[] = "(ldap_entries.dn) LIKE CONCAT('%',?)";
+const char backsql_id_query[] = "SELECT id,keyval,oc_map_id,dn FROM ldap_entries WHERE ";
+/* better ?||? or cast(?||? as varchar) */
+const char backsql_def_concat_func[] = "CONCAT(?,?)";
+
+/* TimesTen */
+const char backsql_check_dn_ru_query[] = "SELECT dn_ru FROM ldap_entries";
+
+struct berbuf *
+backsql_strcat_x( struct berbuf *dest, void *memctx, ... )
+{
+ va_list strs;
+ ber_len_t cdlen, cslen, grow;
+ char *cstr;
+
+ assert( dest != NULL );
+ assert( dest->bb_val.bv_val == NULL
+ || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_strcat()\n" );
+#endif /* BACKSQL_TRACE */
+
+ va_start( strs, memctx );
+ if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) {
+ dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx );
+ dest->bb_val.bv_len = 0;
+ dest->bb_len = BACKSQL_STR_GROW;
+ }
+ cdlen = dest->bb_val.bv_len;
+ while ( ( cstr = va_arg( strs, char * ) ) != NULL ) {
+ cslen = strlen( cstr );
+ grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen );
+ if ( dest->bb_len - cdlen <= cslen ) {
+ char *tmp_dest;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): "
+ "buflen=%d, cdlen=%d, cslen=%d "
+ "-- reallocating dest\n",
+ dest->bb_len, cdlen + 1, cslen );
+#endif /* BACKSQL_TRACE */
+
+ tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val,
+ dest->bb_len + grow * sizeof( char ), memctx );
+ if ( tmp_dest == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "backsql_strcat(): "
+ "could not reallocate string buffer.\n" );
+ va_end( strs );
+ return NULL;
+ }
+ dest->bb_val.bv_val = tmp_dest;
+ dest->bb_len += grow;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): "
+ "new buflen=%d, dest=%p\n",
+ dest->bb_len, dest );
+#endif /* BACKSQL_TRACE */
+ }
+ AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 );
+ cdlen += cslen;
+ }
+ va_end( strs );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_strcat() (dest=\"%s\")\n",
+ dest->bb_val.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ dest->bb_val.bv_len = cdlen;
+
+ return dest;
+}
+
+struct berbuf *
+backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... )
+{
+ va_list strs;
+ ber_len_t cdlen;
+
+ assert( dest != NULL );
+ assert( fmt != NULL );
+ assert( dest->bb_len == 0 || dest->bb_len > dest->bb_val.bv_len );
+ assert( dest->bb_val.bv_val == NULL
+ || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_strfcat()\n" );
+#endif /* BACKSQL_TRACE */
+
+ va_start( strs, fmt );
+ if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) {
+ dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx );
+ dest->bb_val.bv_len = 0;
+ dest->bb_len = BACKSQL_STR_GROW;
+ }
+
+ cdlen = dest->bb_val.bv_len;
+ for ( ; fmt[0]; fmt++ ) {
+ ber_len_t cslen, grow;
+ char *cstr, cc[ 2 ] = { '\0', '\0' };
+ struct berval *cbv;
+
+ switch ( fmt[ 0 ] ) {
+
+ /* berval */
+ case 'b':
+ cbv = va_arg( strs, struct berval * );
+ cstr = cbv->bv_val;
+ cslen = cbv->bv_len;
+ break;
+
+ /* length + string */
+ case 'l':
+ cslen = va_arg( strs, ber_len_t );
+ cstr = va_arg( strs, char * );
+ break;
+
+ /* string */
+ case 's':
+ cstr = va_arg( strs, char * );
+ cslen = strlen( cstr );
+ break;
+
+ /* char */
+ case 'c':
+ /*
+ * `char' is promoted to `int' when passed through `...'
+ */
+ cc[0] = va_arg( strs, int );
+ cstr = cc;
+ cslen = 1;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen );
+ if ( dest->bb_len - cdlen <= cslen ) {
+ char *tmp_dest;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): "
+ "buflen=%d, cdlen=%d, cslen=%d "
+ "-- reallocating dest\n",
+ dest->bb_len, cdlen + 1, cslen );
+#endif /* BACKSQL_TRACE */
+
+ tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val,
+ ( dest->bb_len ) + grow * sizeof( char ), memctx );
+ if ( tmp_dest == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "backsql_strfcat(): "
+ "could not reallocate string buffer.\n" );
+ va_end( strs );
+ return NULL;
+ }
+ dest->bb_val.bv_val = tmp_dest;
+ dest->bb_len += grow * sizeof( char );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): "
+ "new buflen=%d, dest=%p\n", dest->bb_len, dest );
+#endif /* BACKSQL_TRACE */
+ }
+
+ assert( cstr != NULL );
+
+ AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 );
+ cdlen += cslen;
+ }
+
+ va_end( strs );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_strfcat() (dest=\"%s\")\n",
+ dest->bb_val.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ dest->bb_val.bv_len = cdlen;
+
+ return dest;
+}
+
+int
+backsql_entry_addattr(
+ Entry *e,
+ AttributeDescription *ad,
+ struct berval *val,
+ void *memctx )
+{
+ int rc;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): %s=%s\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val, val->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ rc = attr_merge_normalize_one( e, ad, val, memctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): "
+ "failed to merge value \"%s\" for attribute \"%s\"\n",
+ e->e_name.bv_val, val->bv_val, ad->ad_cname.bv_val );
+ return rc;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_entry_addattr(\"%s\")\n",
+ e->e_name.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ return LDAP_SUCCESS;
+}
+
+static char *
+backsql_get_table_spec( backsql_info *bi, char **p )
+{
+ char *s, *q;
+ struct berbuf res = BB_NULL;
+
+ assert( p != NULL );
+ assert( *p != NULL );
+
+ s = *p;
+ while ( **p && **p != ',' ) {
+ (*p)++;
+ }
+
+ if ( **p ) {
+ *(*p)++ = '\0';
+ }
+
+#define BACKSQL_NEXT_WORD { \
+ while ( *s && isspace( (unsigned char)*s ) ) s++; \
+ if ( !*s ) return res.bb_val.bv_val; \
+ q = s; \
+ while ( *q && !isspace( (unsigned char)*q ) ) q++; \
+ if ( *q ) *q++='\0'; \
+ }
+
+ BACKSQL_NEXT_WORD;
+ /* table name */
+ backsql_strcat_x( &res, NULL, s, NULL );
+ s = q;
+
+ BACKSQL_NEXT_WORD;
+ if ( strcasecmp( s, "AS" ) == 0 ) {
+ s = q;
+ BACKSQL_NEXT_WORD;
+ }
+
+ /* oracle doesn't understand "AS" :( and other RDBMSes don't need it */
+ backsql_strfcat_x( &res, NULL, "lbbsb",
+ STRLENOF( " " ), " ",
+ &bi->sql_aliasing,
+ &bi->sql_aliasing_quote,
+ s,
+ &bi->sql_aliasing_quote );
+
+ return res.bb_val.bv_val;
+}
+
+int
+backsql_merge_from_clause(
+ backsql_info *bi,
+ struct berbuf *dest_from,
+ struct berval *src_from )
+{
+ char *s, *p, *srcc, *pos, e;
+ struct berbuf res = BB_NULL;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_merge_from_clause(): "
+ "dest_from=\"%s\",src_from=\"%s\"\n",
+ dest_from ? dest_from->bb_val.bv_val : "<NULL>",
+ src_from->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ srcc = ch_strdup( src_from->bv_val );
+ p = srcc;
+
+ if ( dest_from != NULL ) {
+ res = *dest_from;
+ }
+
+ while ( *p ) {
+ s = backsql_get_table_spec( bi, &p );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_merge_from_clause(): "
+ "p=\"%s\" s=\"%s\"\n", p, s );
+#endif /* BACKSQL_TRACE */
+
+ if ( BER_BVISNULL( &res.bb_val ) ) {
+ backsql_strcat_x( &res, NULL, s, NULL );
+
+ } else {
+ pos = strstr( res.bb_val.bv_val, s );
+ if ( pos == NULL || ( ( e = pos[ strlen( s ) ] ) != '\0' && e != ',' ) ) {
+ backsql_strfcat_x( &res, NULL, "cs", ',', s );
+ }
+ }
+
+ if ( s ) {
+ ch_free( s );
+ }
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_merge_from_clause()\n" );
+#endif /* BACKSQL_TRACE */
+
+ free( srcc );
+ *dest_from = res;
+
+ return 1;
+}
+
+/*
+ * splits a pattern in components separated by '?'
+ * (double ?? are turned into single ? and left in the string)
+ * expected contains the number of expected occurrences of '?'
+ * (a negative value means parse as many as possible)
+ */
+
+int
+backsql_split_pattern(
+ const char *_pattern,
+ BerVarray *split_pattern,
+ int expected )
+{
+ char *pattern, *start, *end;
+ struct berval bv;
+ int rc = 0;
+
+#define SPLIT_CHAR '?'
+
+ assert( _pattern != NULL );
+ assert( split_pattern != NULL );
+
+ pattern = ch_strdup( _pattern );
+
+ start = pattern;
+ end = strchr( start, SPLIT_CHAR );
+ for ( ; start; expected-- ) {
+ char *real_end = end;
+ ber_len_t real_len;
+
+ if ( real_end == NULL ) {
+ real_end = start + strlen( start );
+
+ } else if ( real_end[ 1 ] == SPLIT_CHAR ) {
+ expected++;
+ AC_MEMCPY( real_end, real_end + 1, strlen( real_end ) );
+ end = strchr( real_end + 1, SPLIT_CHAR );
+ continue;
+ }
+
+ real_len = real_end - start;
+ if ( real_len == 0 ) {
+ ber_str2bv( "", 0, 1, &bv );
+ } else {
+ ber_str2bv( start, real_len, 1, &bv );
+ }
+
+ ber_bvarray_add( split_pattern, &bv );
+
+ if ( expected == 0 ) {
+ if ( end != NULL ) {
+ rc = -1;
+ goto done;
+ }
+ break;
+ }
+
+ if ( end != NULL ) {
+ start = end + 1;
+ end = strchr( start, SPLIT_CHAR );
+ }
+ }
+
+done:;
+
+ ch_free( pattern );
+
+ return rc;
+}
+
+int
+backsql_prepare_pattern(
+ BerVarray split_pattern,
+ BerVarray values,
+ struct berval *res )
+{
+ int i;
+ struct berbuf bb = BB_NULL;
+
+ assert( res != NULL );
+
+ for ( i = 0; values[i].bv_val; i++ ) {
+ if ( split_pattern[i].bv_val == NULL ) {
+ ch_free( bb.bb_val.bv_val );
+ return -1;
+ }
+ backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] );
+ backsql_strfcat_x( &bb, NULL, "b", &values[ i ] );
+ }
+
+ if ( split_pattern[ i ].bv_val == NULL ) {
+ ch_free( bb.bb_val.bv_val );
+ return -1;
+ }
+
+ backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] );
+
+ *res = bb.bb_val;
+
+ return 0;
+}
+
+int
+backsql_entryUUID(
+ backsql_info *bi,
+ backsql_entryID *id,
+ struct berval *entryUUID,
+ void *memctx )
+{
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+ struct berval uuid;
+#ifdef BACKSQL_ARBITRARY_KEY
+ int i;
+ ber_len_t l, lmax;
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+ /* entryUUID is generated as "%08x-%04x-%04x-0000-eaddrXXX"
+ * with eid_oc_id as %08x and hi and lo eid_id as %04x-%04x */
+ assert( bi != NULL );
+ assert( id != NULL );
+ assert( entryUUID != NULL );
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ snprintf( uuidbuf, sizeof( uuidbuf ),
+ "%08x-0000-0000-0000-000000000000",
+ ( id->eid_oc_id & 0xFFFFFFFF ) );
+ lmax = id->eid_keyval.bv_len < 12 ? id->eid_keyval.bv_len : 12;
+ for ( l = 0, i = 9; l < lmax; l++, i += 2 ) {
+ switch ( i ) {
+ case STRLENOF( "00000000-0000" ):
+ case STRLENOF( "00000000-0000-0000" ):
+ case STRLENOF( "00000000-0000-0000-0000" ):
+ uuidbuf[ i++ ] = '-';
+ /* FALLTHRU */
+
+ default:
+ snprintf( &uuidbuf[ i ], 3, "%2x", id->eid_keyval.bv_val[ l ] );
+ break;
+ }
+ }
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ /* note: works only with 32 bit architectures... */
+ snprintf( uuidbuf, sizeof( uuidbuf ),
+ "%08x-%04x-%04x-0000-000000000000",
+ ( (unsigned)id->eid_oc_id & 0xFFFFFFFF ),
+ ( ( (unsigned)id->eid_keyval & 0xFFFF0000 ) >> 020 /* 16 */ ),
+ ( (unsigned)id->eid_keyval & 0xFFFF ) );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ uuid.bv_val = uuidbuf;
+ uuid.bv_len = strlen( uuidbuf );
+
+ ber_dupbv_x( entryUUID, &uuid, memctx );
+
+ return 0;
+}
+
+int
+backsql_entryUUID_decode(
+ struct berval *entryUUID,
+ unsigned long *oc_id,
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval *keyval
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long *keyval
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ )
+{
+#if 0
+ fprintf( stderr, "==> backsql_entryUUID_decode()\n" );
+#endif
+
+ *oc_id = ( entryUUID->bv_val[0] << 030 /* 24 */ )
+ + ( entryUUID->bv_val[1] << 020 /* 16 */ )
+ + ( entryUUID->bv_val[2] << 010 /* 8 */ )
+ + entryUUID->bv_val[3];
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ /* FIXME */
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ *keyval = ( entryUUID->bv_val[4] << 030 /* 24 */ )
+ + ( entryUUID->bv_val[5] << 020 /* 16 */ )
+ + ( entryUUID->bv_val[6] << 010 /* 8 */ )
+ + entryUUID->bv_val[7];
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+#if 0
+ fprintf( stderr, "<== backsql_entryUUID_decode(): oc=%lu id=%lu\n",
+ *oc_id, *keyval );
+#endif
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-wt/Makefile.in b/servers/slapd/back-wt/Makefile.in
new file mode 100644
index 0000000..054025e
--- /dev/null
+++ b/servers/slapd/back-wt/Makefile.in
@@ -0,0 +1,54 @@
+# Makefile.in for back-wt
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c tools.c config.c \
+ add.c bind.c compare.c delete.c modify.c modrdn.c search.c \
+ extended.c operational.c \
+ attr.c index.c key.c filterindex.c \
+ dn2entry.c dn2id.c id2entry.c idl.c \
+ nextid.c ctx.c cache.c
+
+OBJS = init.lo tools.lo config.lo \
+ add.lo bind.lo compare.lo delete.lo modify.lo modrdn.lo search.lo \
+ extended.lo operational.lo \
+ attr.lo index.lo key.lo filterindex.lo \
+ dn2entry.lo dn2id.lo id2entry.lo idl.lo \
+ nextid.lo ctx.lo cache.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-wt"
+BUILD_MOD = @BUILD_WT@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = @WT_CFLAGS@
+MOD_LIBS = @WT_LIBS@
+
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_wt
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-wt/add.c b/servers/slapd/back-wt/add.c
new file mode 100644
index 0000000..04c08a1
--- /dev/null
+++ b/servers/slapd/back-wt/add.c
@@ -0,0 +1,373 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_add( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ struct berval pdn;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ ID eid = NOID;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+ wt_ctx *wc;
+ Entry *e = NULL;
+ Entry *p = NULL;
+ ID pid = NOID;
+ int rc;
+
+ Debug( LDAP_DEBUG_ARGS, "==> wt_add: %s\n", op->ora_e->e_name.bv_val );
+
+ ctrls[num_ctrls] = 0;
+
+ /* check entry's schema */
+ rs->sr_err = entry_schema_check(
+ op, op->ora_e, NULL,
+ get_relax(op), 1, NULL, &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_add: entry failed schema check: %s (%d)\n",
+ rs->sr_text, rs->sr_err );
+ goto return_results;
+ }
+
+ /* add opattrs to shadow as well, only missing attrs will actually
+ * be added; helps compatibility with older OL versions */
+ rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_add: entry failed op attrs add: %s (%d)\n",
+ rs->sr_text, rs->sr_err );
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, op->ora_e, get_assertion( op ))
+ != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ /* Not used
+ * subentry = is_entry_subentry( op->ora_e );
+ */
+
+ /*
+ * Get the parent dn and see if the corresponding entry exists.
+ */
+ if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) {
+ pdn = slap_empty_bv;
+ } else {
+ dnParent( &op->ora_e->e_nname, &pdn );
+ }
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY, "wt_add: wt_ctx_get failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ goto return_results;
+ break;
+ case WT_NOTFOUND:
+ break;
+ default:
+ /* TODO: retry handling */
+ Debug( LDAP_DEBUG_ANY,
+ "wt_add: error at wt_dn2entry() rc=%d\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* get parent entry */
+ rc = wt_dn2pentry(op->o_bd, wc, &op->o_req_ndn, &p);
+ switch( rc ){
+ case 0:
+ case WT_NOTFOUND:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_add: error at wt_dn2pentry() rc=%d\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( !p )
+ p = (Entry *)&slap_entry_root;
+
+ if ( !bvmatch( &pdn, &p->e_nname ) ) {
+ rs->sr_matched = ber_strdup_x( p->e_name.bv_val,
+ op->o_tmpmemctx );
+ if ( p != (Entry *)&slap_entry_root ) {
+ rs->sr_ref = is_entry_referral( p )
+ ? get_entry_referrals( op, p )
+ : NULL;
+ wt_entry_return( p );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ p = NULL;
+ Debug( LDAP_DEBUG_TRACE, "wt_add: parent does not exist\n" );
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WADD, NULL );
+ if ( ! rs->sr_err ) {
+ /*
+ if ( p != (Entry *)&slap_entry_root )
+ wt_entry_return( op, p );
+ */
+ p = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "wt_add: no write access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;;
+ }
+
+ if ( p != (Entry *)&slap_entry_root ) {
+ if ( is_entry_subentry( p ) ) {
+ wt_entry_return( p );
+ p = NULL;
+ /* parent is a subentry, don't allow add */
+ Debug( LDAP_DEBUG_TRACE, "wt_add: parent is subentry\n" );
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "parent is a subentry";
+ goto return_results;;
+ }
+
+ if ( is_entry_alias( p ) ) {
+ wt_entry_return( p );
+ p = NULL;
+ /* parent is an alias, don't allow add */
+ Debug( LDAP_DEBUG_TRACE, "wt_add: parent is alias\n" );
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ rs->sr_text = "parent is an alias";
+ goto return_results;;
+ }
+
+ if ( is_entry_referral( p ) ) {
+ BerVarray ref = get_entry_referrals( op, p );
+ /* parent is a referral, don't allow add */
+ rs->sr_matched = ber_strdup_x( p->e_name.bv_val,
+ op->o_tmpmemctx );
+ rs->sr_ref = referral_rewrite( ref, &p->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ wt_entry_return( p );
+ p = NULL;
+ Debug( LDAP_DEBUG_TRACE, "wt_add: parent is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+ }
+
+#if 0
+ if ( subentry ) {
+ /* FIXME: */
+ /* parent must be an administrative point of the required kind */
+ }
+#endif
+
+ /* free parent */
+ if ( p != (Entry *)&slap_entry_root ) {
+ pid = p->e_id;
+ if ( p->e_nname.bv_len ) {
+ struct berval ppdn;
+
+ /* ITS#5326: use parent's DN if differs from provided one */
+ dnParent( &op->ora_e->e_name, &ppdn );
+ if ( !dn_match( &p->e_name, &ppdn ) ) {
+ struct berval rdn;
+ struct berval newdn;
+
+ dnRdn( &op->ora_e->e_name, &rdn );
+
+ build_new_dn( &newdn, &p->e_name, &rdn, NULL );
+ if ( op->ora_e->e_name.bv_val != op->o_req_dn.bv_val )
+ ber_memfree( op->ora_e->e_name.bv_val );
+ op->ora_e->e_name = newdn;
+
+ /* FIXME: should check whether
+ * dnNormalize(newdn) == e->e_nname ... */
+ }
+ }
+
+ wt_entry_return( p );
+ }
+ p = NULL;
+
+ rs->sr_err = access_allowed( op, op->ora_e,
+ entry, NULL, ACL_WADD, NULL );
+
+ if ( ! rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE, "wt_add: no write access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to entry";
+ goto return_results;
+ }
+
+ /*
+ * Check ACL for attribute write access
+ */
+ if (!acl_check_modlist(op, op->ora_e, op->ora_modlist)) {
+ Debug( LDAP_DEBUG_TRACE, "wt_add: no write access to attribute\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to attribute";
+ goto return_results;
+ }
+
+ rc = wc->session->begin_transaction(wc->session, "isolation=read-uncommitted");
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "wt_add: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "begin_transaction failed";
+ goto return_results;
+ }
+ Debug( LDAP_DEBUG_TRACE, "wt_add: session id: %p\n", wc->session );
+
+ wt_next_id( op->o_bd, &eid );
+ op->ora_e->e_id = eid;
+
+ rc = wt_dn2id_add( op, wc, pid, op->ora_e );
+ if( rc ){
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_add: dn2id_add failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ switch( rc ) {
+ case WT_DUPLICATE_KEY:
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ break;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ }
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ rc = wt_id2entry_add( op, wc, op->ora_e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_add: id2entry_add failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ if ( rc == LDAP_ADMINLIMIT_EXCEEDED ) {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ rs->sr_text = "entry is too big";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry store failed";
+ }
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ /* add indices */
+ rc = wt_index_entry_add( op, wc, op->ora_e );
+ if ( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_add: index add failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "index add failed";
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_add: commit_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit_transaction failed";
+ goto return_results;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* post-read */
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, op->ora_e,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, "<=- wt_add: post-read failed!\n" );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "wt_add: added%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ op->ora_e->e_id, op->ora_e->e_dn );
+
+return_results:
+ send_ldap_result( op, rs );
+
+ slap_graduate_commit_csn( op );
+
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/attr.c b/servers/slapd/back-wt/attr.c
new file mode 100644
index 0000000..70f278c
--- /dev/null
+++ b/servers/slapd/back-wt/attr.c
@@ -0,0 +1,425 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "back-wt.h"
+#include "slap-config.h"
+#include "lutil.h"
+
+/* Find the ad, return -1 if not found,
+ * set point for insertion if ins is non-NULL
+ */
+int
+wt_attr_slot( struct wt_info *wi, AttributeDescription *ad, int *ins )
+{
+ unsigned base = 0, cursor = 0;
+ unsigned n = wi->wi_nattrs;
+ int val = 0;
+
+ while ( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot;
+
+ val = SLAP_PTRCMP( ad, wi->wi_attrs[cursor]->ai_desc );
+ if ( val < 0 ) {
+ n = pivot;
+ } else if ( val > 0 ) {
+ base = cursor + 1;
+ n -= pivot + 1;
+ } else {
+ return cursor;
+ }
+ }
+ if ( ins ) {
+ if ( val > 0 )
+ ++cursor;
+ *ins = cursor;
+ }
+ return -1;
+}
+
+static int
+ainfo_insert( struct wt_info *wi, AttrInfo *a )
+{
+ int x = INT_MAX;
+ int i = wt_attr_slot( wi, a->ai_desc, &x );
+
+ /* Is it a dup? */
+ if ( i >= 0 )
+ return -1;
+
+ wi->wi_attrs = ch_realloc( wi->wi_attrs, ( wi->wi_nattrs+1 ) *
+ sizeof( AttrInfo * ));
+ if ( x < wi->wi_nattrs )
+ AC_MEMCPY( &wi->wi_attrs[x+1], &wi->wi_attrs[x],
+ ( wi->wi_nattrs - x ) * sizeof( AttrInfo *));
+ wi->wi_attrs[x] = a;
+ wi->wi_nattrs++;
+ return 0;
+}
+
+AttrInfo *
+wt_attr_mask(
+ struct wt_info *wi,
+ AttributeDescription *desc )
+{
+ int i = wt_attr_slot( wi, desc, NULL );
+ return i < 0 ? NULL : wi->wi_attrs[i];
+}
+
+int
+wt_attr_index_config(
+ struct wt_info *wi,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ struct config_reply_s *c_reply)
+{
+ int rc = 0;
+ int i;
+ slap_mask_t mask;
+ char **attrs;
+ char **indexes = NULL;
+
+ attrs = ldap_str2charray( argv[0], "," );
+
+ if( attrs == NULL ) {
+ fprintf( stderr, "%s: line %d: "
+ "no attributes specified: %s\n",
+ fname, lineno, argv[0] );
+ return LDAP_PARAM_ERROR;
+ }
+
+ if ( argc > 1 ) {
+ indexes = ldap_str2charray( argv[1], "," );
+
+ if( indexes == NULL ) {
+ fprintf( stderr, "%s: line %d: "
+ "no indexes specified: %s\n",
+ fname, lineno, argv[1] );
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+ }
+
+ if( indexes == NULL ) {
+ mask = wi->wi_defaultmask;
+
+ } else {
+ mask = 0;
+
+ for ( i = 0; indexes[i] != NULL; i++ ) {
+ slap_mask_t index;
+
+ rc = slap_str2index( indexes[i], &index );
+
+ if( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index type \"%s\" undefined", indexes[i] );
+
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+
+ mask |= index;
+ }
+ }
+
+ if( !mask ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "no indexes selected" );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ AttrInfo *a;
+ AttributeDescription *ad;
+ const char *text;
+#ifdef LDAP_COMP_MATCH
+ ComponentReference* cr = NULL;
+ AttrInfo *a_cr = NULL;
+#endif
+
+ if( strcasecmp( attrs[i], "default" ) == 0 ) {
+ wi->wi_defaultmask |= mask;
+ continue;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( is_component_reference( attrs[i] ) ) {
+ rc = extract_component_reference( attrs[i], &cr );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index component reference\"%s\" undefined",
+ attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ goto done;
+ }
+ cr->cr_indexmask = mask;
+ /*
+ * After extracting a component reference
+ * only the name of a attribute will be remaining
+ */
+ } else {
+ cr = NULL;
+ }
+#endif
+ ad = NULL;
+ rc = slap_str2ad( attrs[i], &ad, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ if ( c_reply )
+ {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index attribute \"%s\" undefined",
+ attrs[i] );
+
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+fail:
+#ifdef LDAP_COMP_MATCH
+ ch_free( cr );
+#endif
+ goto done;
+ }
+
+ if( ad == slap_schema.si_ad_entryDN || slap_ad_is_binary( ad ) ) {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) && !(
+ ad->ad_type->sat_approx
+ && ad->ad_type->sat_approx->smr_indexer
+ && ad->ad_type->sat_approx->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "approx index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) && !(
+ ad->ad_type->sat_equality
+ && ad->ad_type->sat_equality->smr_indexer
+ && ad->ad_type->sat_equality->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "equality index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) && !(
+ ad->ad_type->sat_substr
+ && ad->ad_type->sat_substr->smr_indexer
+ && ad->ad_type->sat_substr->smr_filter ) )
+ {
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "substr index of attribute \"%s\" disallowed", attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ Debug( LDAP_DEBUG_CONFIG, "index %s 0x%04lx\n",
+ ad->ad_cname.bv_val, mask );
+
+ a = (AttrInfo *) ch_malloc( sizeof(AttrInfo) );
+
+#ifdef LDAP_COMP_MATCH
+ a->ai_cr = NULL;
+#endif
+ a->ai_desc = ad;
+
+ if ( wi->wi_flags & WT_IS_OPEN ) {
+ a->ai_indexmask = 0;
+ a->ai_newmask = mask;
+ } else {
+ a->ai_indexmask = mask;
+ a->ai_newmask = 0;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( cr ) {
+ a_cr = wt_attr_mask( wi, ad );
+ if ( a_cr ) {
+ /*
+ * AttrInfo is already in AVL
+ * just add the extracted component reference
+ * in the AttrInfo
+ */
+ ch_free( a );
+ rc = insert_component_reference( cr, &a_cr->ai_cr );
+ if ( rc != LDAP_SUCCESS) {
+ fprintf( stderr, " error during inserting component reference in %s ", attrs[i]);
+ rc = LDAP_PARAM_ERROR;
+ goto fail;
+ }
+ continue;
+ } else {
+ rc = insert_component_reference( cr, &a->ai_cr );
+ if ( rc != LDAP_SUCCESS) {
+ fprintf( stderr, " error during inserting component reference in %s ", attrs[i]);
+ rc = LDAP_PARAM_ERROR;
+ ch_free( a );
+ goto fail;
+ }
+ }
+ }
+#endif
+ rc = ainfo_insert( wi, a );
+ if( rc ) {
+ if ( wi->wi_flags & WT_IS_OPEN ) {
+ AttrInfo *b = wt_attr_mask( wi, ad );
+ /* If there is already an index defined for this attribute
+ * it must be replaced. Otherwise we end up with multiple
+ * olcIndex values for the same attribute */
+ if ( b->ai_indexmask & WT_INDEX_DELETING ) {
+ /* If we were editing this attr, reset it */
+ b->ai_indexmask &= ~WT_INDEX_DELETING;
+ /* If this is leftover from a previous add, commit it */
+ if ( b->ai_newmask )
+ b->ai_indexmask = b->ai_newmask;
+ b->ai_newmask = a->ai_newmask;
+ ch_free( a );
+ rc = 0;
+ continue;
+ }
+ }
+ if (c_reply) {
+ snprintf(c_reply->msg, sizeof(c_reply->msg),
+ "duplicate index definition for attr \"%s\"",
+ attrs[i] );
+ fprintf( stderr, "%s: line %d: %s\n",
+ fname, lineno, c_reply->msg );
+ }
+
+ rc = LDAP_PARAM_ERROR;
+ goto done;
+ }
+ }
+
+done:
+ ldap_charray_free( attrs );
+ if ( indexes != NULL ) ldap_charray_free( indexes );
+
+ return rc;
+}
+
+static int
+wt_attr_index_unparser( void *v1, void *v2 )
+{
+ AttrInfo *ai = v1;
+ BerVarray *bva = v2;
+ struct berval bv;
+ char *ptr;
+
+ slap_index2bvlen( ai->ai_indexmask, &bv );
+ if ( bv.bv_len ) {
+ bv.bv_len += ai->ai_desc->ad_cname.bv_len + 1;
+ ptr = ch_malloc( bv.bv_len+1 );
+ bv.bv_val = lutil_strcopy(ptr,
+ (const char*)ai->ai_desc->ad_cname.bv_val );
+ *bv.bv_val++ = ' ';
+ slap_index2bv( ai->ai_indexmask, &bv );
+ bv.bv_val = ptr;
+ ber_bvarray_add( bva, &bv );
+ }
+ return 0;
+}
+
+static AttributeDescription addef = { NULL, NULL, BER_BVC("default") };
+static AttrInfo aidef = { &addef };
+
+void
+wt_attr_index_unparse( struct wt_info *wi, BerVarray *bva )
+{
+ int i;
+
+ if ( wi->wi_defaultmask ) {
+ aidef.ai_indexmask = wi->wi_defaultmask;
+ wt_attr_index_unparser( &aidef, bva );
+ }
+ for ( i=0; i<wi->wi_nattrs; i++ )
+ wt_attr_index_unparser( wi->wi_attrs[i], bva );
+}
+
+void
+wt_attr_info_free( AttrInfo *ai )
+{
+#ifdef LDAP_COMP_MATCH
+ free( ai->ai_cr );
+#endif
+ free( ai );
+}
+
+void
+wt_attr_index_destroy( struct wt_info *wi )
+{
+ int i;
+
+ for ( i=0; i<wi->wi_nattrs; i++ )
+ wt_attr_info_free( wi->wi_attrs[i] );
+
+ free( wi->wi_attrs );
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/back-wt.h b/servers/slapd/back-wt/back-wt.h
new file mode 100644
index 0000000..386dd85
--- /dev/null
+++ b/servers/slapd/back-wt/back-wt.h
@@ -0,0 +1,120 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#ifndef _BACK_WT_H_
+#define _BACK_WT_H_
+
+#include <portable.h>
+
+#include <ac/errno.h>
+#include <sys/stat.h>
+
+#include "slap.h"
+#include "wiredtiger.h"
+
+/* The default search IDL stack cache depth */
+#define DEFAULT_SEARCH_STACK_DEPTH 16
+
+#define WT_CONFIG_MAX 2048
+
+struct wt_info {
+ WT_CONNECTION *wi_conn;
+ WT_CONNECTION *wi_cache;
+ char *wi_home;
+ char *wi_config;
+ ID wi_lastid;
+
+ slap_mask_t wi_defaultmask;
+ int wi_nattrs;
+ struct wt_attrinfo **wi_attrs;
+ void *wi_search_stack;
+ int wi_search_stack_depth;
+
+ struct re_s *wi_index_task;
+
+ int wi_flags;
+#define WT_IS_OPEN 0x01
+#define WT_OPEN_INDEX 0x02
+#define WT_DEL_INDEX 0x08
+#define WT_RE_OPEN 0x10
+#define WT_NEED_UPGRADE 0x20
+#define WT_USE_IDLCACHE 0x40
+};
+
+#define WT_TABLE_ID2ENTRY "table:id2entry"
+#define WT_TABLE_DN2ID "table:dn2id"
+
+#define WT_INDEX_DN "index:id2entry:dn"
+#define WT_INDEX_NDN "index:dn2id:ndn"
+#define WT_INDEX_PID "index:dn2id:pid"
+/* Currently, revdn is primary key, the revdn index is obsolete. */
+#define WT_INDEX_REVDN "index:dn2id:revdn"
+
+/* table for cache */
+#define WT_TABLE_IDLCACHE "table:idlcache"
+
+#define ITEMzero(item) (memset((item), 0, sizeof(WT_ITEM)))
+#define ITEM2bv(item,bv) ((bv)->bv_val = (item)->data, \
+ (bv)->bv_len = (item)->size)
+#define bv2ITEM(bv,item) ((item)->data = (bv)->bv_val, \
+ (item)->size = (bv)->bv_len )
+
+#define WT_INDEX_CACHE_SIZE 1024
+
+typedef struct {
+ WT_SESSION *session;
+ int is_begin_transaction;
+ WT_CURSOR *dn2id;
+ WT_CURSOR *dn2id_w;
+ WT_CURSOR *dn2id_ndn;
+ WT_CURSOR *dn2entry;
+ WT_CURSOR *id2entry;
+ WT_CURSOR *id2entry_add;
+ WT_CURSOR *id2entry_update;
+ WT_SESSION *idlcache_session;
+ WT_CURSOR *index_pid;
+} wt_ctx;
+
+/* for the cache of attribute information (which are indexed, etc.) */
+typedef struct wt_attrinfo {
+ AttributeDescription *ai_desc; /* attribute description cn;lang-en */
+ slap_mask_t ai_indexmask; /* how the attr is indexed */
+ slap_mask_t ai_newmask; /* new settings to replace old mask */
+ #ifdef LDAP_COMP_MATCH
+ ComponentReference* ai_cr; /*component indexing*/
+ #endif
+} AttrInfo;
+
+/* These flags must not clash with SLAP_INDEX flags or ops in slap.h! */
+#define WT_INDEX_DELETING 0x8000U /* index is being modified */
+#define WT_INDEX_UPDATE_OP 0x03 /* performing an index update */
+
+#include "proto-wt.h"
+
+#endif /* _BACK_WT_H_ */
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/bind.c b/servers/slapd/back-wt/bind.c
new file mode 100644
index 0000000..43abe87
--- /dev/null
+++ b/servers/slapd/back-wt/bind.c
@@ -0,0 +1,150 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_bind( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ wt_ctx *wc;
+ int rc;
+ Entry *e = NULL;
+ Attribute *a;
+ AttributeDescription *password = slap_schema.si_ad_userPassword;
+
+ Debug( LDAP_DEBUG_ARGS, "==> wt_bind: dn: %s\n",
+ op->o_req_dn.bv_val );
+
+ /* allow noauth binds */
+ switch ( be_rootdn_bind( op, NULL ) ) {
+ case LDAP_SUCCESS:
+ /* frontend will send result */
+ return rs->sr_err = LDAP_SUCCESS;
+
+ default:
+ /* give the database a chance */
+ /* NOTE: this behavior departs from that of other backends,
+ * since the others, in case of password checking failure
+ * do not give the database a chance. If an entry with
+ * rootdn's name does not exist in the database the result
+ * will be the same. See ITS#4962 for discussion. */
+ break;
+ }
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_bind: wt_ctx_get failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ /* get entry */
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ ber_dupbv( &op->oq_bind.rb_edn, &e->e_name );
+
+ /* check for deleted */
+ if ( is_entry_subentry( e ) ) {
+ /* entry is an subentry, don't allow bind */
+ Debug( LDAP_DEBUG_TRACE, "entry is subentry\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( is_entry_alias( e ) ) {
+ /* entry is an alias, don't allow bind */
+ Debug( LDAP_DEBUG_TRACE, "entry is alias\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( is_entry_referral( e ) ) {
+ Debug( LDAP_DEBUG_TRACE, "entry is referral\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ switch ( op->oq_bind.rb_method ) {
+ case LDAP_AUTH_SIMPLE:
+ a = attr_find( e->e_attrs, password );
+ if ( a == NULL ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ if ( slap_passwd_check( op, e, a, &op->oq_bind.rb_cred,
+ &rs->sr_text ) != 0 )
+ {
+ /* failure; stop front end from sending result */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+ rs->sr_err = 0;
+ break;
+
+ default:
+ rs->sr_err = LDAP_STRONG_AUTH_NOT_SUPPORTED;
+ rs->sr_text = "authentication method not supported";
+ }
+
+done:
+ /* free entry */
+ if (e) {
+ wt_entry_return(e);
+ }
+ if (rs->sr_err) {
+ send_ldap_result( op, rs );
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/cache.c b/servers/slapd/back-wt/cache.c
new file mode 100644
index 0000000..ee393a0
--- /dev/null
+++ b/servers/slapd/back-wt/cache.c
@@ -0,0 +1,231 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+#include "idl.h"
+
+int wt_idlcache_get(wt_ctx *wc, struct berval *ndn, int scope, ID *ids)
+{
+ int rc = 0;
+ WT_ITEM item;
+ WT_SESSION *session = wc->idlcache_session;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_idlcache_get(\"%s\", %d)\n",
+ ndn->bv_val, scope );
+
+ rc = session->open_cursor(session, WT_TABLE_IDLCACHE, NULL,
+ NULL, &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_get: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ cursor->set_key(cursor, ndn->bv_val, (int8_t)scope);
+ rc = cursor->search(cursor);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ Debug(LDAP_DEBUG_TRACE, "<= wt_idlcache_get: miss\n" );
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY, "<= wt_idlcache_get: search failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rc = 0;
+ goto done;
+ }
+ rc = cursor->get_value(cursor, &item);
+ if (rc) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_get: get_value failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ if (item.size == 0) {
+ Debug(LDAP_DEBUG_TRACE, "<= wt_idlcache_get: updating\n");
+ rc = WT_NOTFOUND;
+ goto done;
+ }
+ memcpy(ids, item.data, item.size);
+
+ Debug(LDAP_DEBUG_TRACE,
+ "<= wt_idlcache_get: hit id=%ld first=%ld last=%ld\n",
+ (long)ids[0],
+ (long)WT_IDL_FIRST(ids),
+ (long)WT_IDL_LAST(ids));
+done:
+ if(cursor) {
+ cursor->close(cursor);
+ }
+ return rc;
+}
+
+int wt_idlcache_set(wt_ctx *wc, struct berval *ndn, int scope, ID *ids)
+{
+ int rc = 0;
+ WT_ITEM item;
+ WT_SESSION *session = wc->idlcache_session;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_idlcache_set(\"%s\", %d)\n",
+ ndn->bv_val, scope );
+
+ item.size = WT_IDL_SIZEOF(ids);
+ item.data = ids;
+
+ rc = session->open_cursor(session, WT_TABLE_IDLCACHE, NULL,
+ "overwrite=false", &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_set: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ cursor->set_key(cursor, ndn->bv_val, (int8_t)scope);
+ cursor->set_value(cursor, &item);
+ rc = cursor->update(cursor);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ // updating cache by another thread
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_set: update failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "<= wt_idlcache_set: set idl size=%ld\n",
+ (long)ids[0]);
+done:
+ if(cursor) {
+ cursor->close(cursor);
+ }
+ return rc;
+}
+
+int wt_idlcache_begin(wt_ctx *wc, struct berval *ndn, int scope)
+{
+ int rc = 0;
+ WT_ITEM item;
+ WT_SESSION *session = wc->idlcache_session;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_idlcache_begin(\"%s\", %d)\n",
+ ndn->bv_val, scope );
+
+ item.size = 0;
+ item.data = "";
+
+ rc = session->open_cursor(session, WT_TABLE_IDLCACHE, NULL,
+ "overwrite=true", &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_begin: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ cursor->set_key(cursor, ndn->bv_val, (int8_t)scope);
+ cursor->set_value(cursor, &item);
+ rc = cursor->update(cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_begin: update failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "<= wt_idlcache_begin: set updating\n" );
+
+done:
+ if(cursor) {
+ cursor->close(cursor);
+ }
+ return rc;
+}
+
+int wt_idlcache_clear(Operation *op, wt_ctx *wc, struct berval *ndn)
+{
+ BackendDB *be = op->o_bd;
+ int rc = 0;
+ struct berval pdn = *ndn;
+ WT_SESSION *session = wc->idlcache_session;
+ WT_CURSOR *cursor = NULL;
+ int level = 0;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_idlcache_clear(\"%s\")\n",
+ ndn->bv_val );
+
+ if (be_issuffix( be, ndn )) {
+ return 0;
+ }
+
+ rc = session->open_cursor(session, WT_TABLE_IDLCACHE, NULL,
+ NULL, &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_idlcache_clear: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+
+ do {
+ dnParent( &pdn, &pdn );
+ if (level == 0) {
+ /* clear only parent level cache */
+ cursor->set_key(cursor, pdn.bv_val, (int8_t)LDAP_SCOPE_ONE);
+ cursor->remove(cursor);
+ }
+ cursor->set_key(cursor, pdn.bv_val, (int8_t)LDAP_SCOPE_SUB);
+ cursor->remove(cursor);
+ cursor->set_key(cursor, pdn.bv_val, (int8_t)LDAP_SCOPE_CHILDREN);
+ cursor->remove(cursor);
+ level++;
+ }while(!be_issuffix( be, &pdn ));
+
+ if(cursor) {
+ cursor->close(cursor);
+ }
+ return 0;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/compare.c b/servers/slapd/back-wt/compare.c
new file mode 100644
index 0000000..038b273
--- /dev/null
+++ b/servers/slapd/back-wt/compare.c
@@ -0,0 +1,154 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_compare( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ Entry *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ int rc;
+ wt_ctx *wc = NULL;
+
+ Debug( LDAP_DEBUG_ARGS, "==> wt_compare: %s\n",
+ op->o_req_dn.bv_val );
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY, "wt_compare: wt_ctx_get failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ case WT_NOTFOUND:
+ break;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( rc == WT_NOTFOUND ||
+ (!manageDSAit && e && is_entry_glue( e ) )) {
+
+ if ( !e ) {
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_compare: wt_dn2aentry failed (%d)\n",
+ rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ }
+
+ /* return referral only if "disclose" is granted on the object */
+ if ( ! access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref,
+ &e->e_name,
+ &op->o_req_dn,
+ LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ rs->sr_err = LDAP_REFERRAL;
+ }
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if (!manageDSAit && is_entry_referral( e ) ) {
+ /* return referral only if "disclose" is granted on the object */
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ /* entry is a referral, don't allow compare */
+ rs->sr_ref = get_entry_referrals( op, e );
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = e->e_name.bv_val;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "entry is referral\n" );
+
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ rs->sr_err = slap_compare_entry( op, e, op->orc_ava );
+
+return_results:
+ send_ldap_result( op, rs );
+
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_FALSE:
+ case LDAP_COMPARE_TRUE:
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+
+done:
+ if ( e != NULL ) {
+ wt_entry_return( e );
+ }
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/config.c b/servers/slapd/back-wt/config.c
new file mode 100644
index 0000000..804e25b
--- /dev/null
+++ b/servers/slapd/back-wt/config.c
@@ -0,0 +1,209 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+#include "lutil.h"
+#include "ldap_rq.h"
+
+static ConfigDriver wt_cf_gen;
+
+enum {
+ WT_DIRECTORY = 1,
+ WT_CONFIG,
+ WT_INDEX,
+ WT_MODE,
+ WT_IDLCACHE,
+};
+
+static ConfigTable wtcfg[] = {
+ { "directory", "dir", 2, 2, 0, ARG_STRING|ARG_MAGIC|WT_DIRECTORY,
+ wt_cf_gen, "( OLcfgDbAt:0.1 NAME 'olcDbDirectory' "
+ "DESC 'Directory for database content' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "index", "attr> <[pres,eq,approx,sub]", 2, 3, 0, ARG_MAGIC|WT_INDEX,
+ wt_cf_gen, "( OLcfgDbAt:0.2 NAME 'olcDbIndex' "
+ "DESC 'Attribute index parameters' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "mode", "mode", 2, 2, 0, ARG_MAGIC|WT_MODE,
+ wt_cf_gen, "( OLcfgDbAt:0.3 NAME 'olcDbMode' "
+ "DESC 'Unix permissions of database files' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "wtconfig", "config", 2, 2, 0, ARG_STRING|ARG_MAGIC|WT_CONFIG,
+ wt_cf_gen, "( OLcfgDbAt:13.1 NAME 'olcWtConfig' "
+ "DESC 'Configuration for WiredTiger' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "idlcache", NULL, 1, 2, 0, ARG_ON_OFF|ARG_MAGIC|WT_IDLCACHE,
+ wt_cf_gen, "( OLcfgDbAt:13.2 NAME 'olcIDLcache' "
+ "DESC 'enable IDL cache' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs wtocs[] = {
+ { "( OLcfgDbOc:13.1 "
+ "NAME 'olcWtConfig' "
+ "DESC 'Wt backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST olcDbDirectory "
+ "MAY ( olcWtConfig $ olcDbIndex $ olcDbMode $ olcIDLcache) )",
+ Cft_Database, wtcfg },
+ { NULL, 0, NULL }
+};
+
+/* reindex entries on the fly */
+static void *
+wt_online_index( void *ctx, void *arg )
+{
+ // Not implement yet
+ return NULL;
+}
+
+/* Cleanup loose ends after Modify completes */
+static int
+wt_cf_cleanup( ConfigArgs *c )
+{
+ // Not implement yet
+ return 0;
+}
+
+static int
+wt_cf_gen( ConfigArgs *c )
+{
+ struct wt_info *wi = (struct wt_info *) c->be->be_private;
+ int rc;
+
+ if( c->op == SLAP_CONFIG_EMIT ) {
+ rc = 0;
+ switch( c->type ) {
+ case WT_DIRECTORY:
+ if ( wi->wi_home ) {
+ c->value_string = ch_strdup( wi->wi_home );
+ } else {
+ rc = 1;
+ }
+ break;
+ case WT_INDEX:
+ wt_attr_index_unparse( wi, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+ case WT_IDLCACHE:
+ if ( wi->wi_flags & WT_USE_IDLCACHE) {
+ c->value_int = 1;
+ }
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ rc = 0;
+ return rc;
+ }
+
+ switch( c->type ) {
+ case WT_DIRECTORY:
+ ch_free( wi->wi_home );
+ wi->wi_home = c->value_string;
+ break;
+ case WT_CONFIG:
+ if(strlen(wi->wi_config) + 1 + strlen(c->value_string) > WT_CONFIG_MAX){
+ fprintf( stderr, "%s: "
+ "\"wtconfig\" are too long. Increase WT_CONFIG_MAX or you may realloc the buffer.\n",
+ c->log );
+ return 1;
+ }
+ /* size of wi->wi_config is WT_CONFIG_MAX + 1, it's guaranteed with NUL-terminate. */
+ strcat(wi->wi_config, ",");
+ strcat(wi->wi_config, c->value_string);
+ break;
+
+ case WT_INDEX:
+ rc = wt_attr_index_config( wi, c->fname, c->lineno,
+ c->argc - 1, &c->argv[1], &c->reply);
+
+ if( rc != LDAP_SUCCESS ) return 1;
+ wi->wi_flags |= WT_OPEN_INDEX;
+
+ if ( wi->wi_flags & WT_IS_OPEN ) {
+ config_push_cleanup( c, wt_cf_cleanup );
+
+ if ( !wi->wi_index_task ) {
+ /* Start the task as soon as we finish here. Set a long
+ * interval (10 hours) so that it only gets scheduled once.
+ */
+ if ( c->be->be_suffix == NULL || BER_BVISNULL( &c->be->be_suffix[0] ) ) {
+ fprintf( stderr, "%s: "
+ "\"index\" must occur after \"suffix\".\n",
+ c->log );
+ return 1;
+ }
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ wi->wi_index_task = ldap_pvt_runqueue_insert(&slapd_rq, 36000,
+ wt_online_index, c->be,
+ LDAP_XSTRING(wt_online_index),
+ c->be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ }
+ break;
+
+ case WT_MODE:
+ fprintf( stderr, "%s: "
+ "back-wt does not support \"mode\" option. use umask instead.\n",
+ c->log );
+ return 1;
+
+ case WT_IDLCACHE:
+ if ( c->value_int ) {
+ wi->wi_flags |= WT_USE_IDLCACHE;
+ } else {
+ wi->wi_flags &= ~WT_USE_IDLCACHE;
+ }
+ break;
+ }
+ return LDAP_SUCCESS;
+}
+
+int wt_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+ bi->bi_cf_ocs = wtocs;
+
+ rc = config_register_schema( wtcfg, wtocs );
+ if ( rc ) return rc;
+ return 0;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/ctx.c b/servers/slapd/back-wt/ctx.c
new file mode 100644
index 0000000..de6578e
--- /dev/null
+++ b/servers/slapd/back-wt/ctx.c
@@ -0,0 +1,117 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "back-wt.h"
+#include "slap-config.h"
+
+wt_ctx *
+wt_ctx_init(struct wt_info *wi)
+{
+ int rc;
+ wt_ctx *wc;
+
+ wc = ch_malloc( sizeof( wt_ctx ) );
+ if( !wc ) {
+ Debug( LDAP_DEBUG_ANY, "wt_ctx_init: cannot allocate memory\n" );
+ return NULL;
+ }
+
+ memset(wc, 0, sizeof(wt_ctx));
+
+ rc = wi->wi_conn->open_session(wi->wi_conn, NULL, NULL, &wc->session);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY, "wt_ctx_init: open_session error %s(%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NULL;
+ }
+
+ /* readonly mode */
+ if (!wi->wi_cache) {
+ return wc;
+ }
+
+ rc = wi->wi_cache->open_session(wi->wi_cache, NULL, NULL, &wc->idlcache_session);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_ctx_init: cannot open idlcache session %s(%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NULL;
+ }
+
+ return wc;
+}
+
+void
+wt_ctx_free( void *key, void *data )
+{
+ wt_ctx *wc = data;
+
+ if(wc->session){
+ /*
+ * The session will close automatically when db closing.
+ * We can close session here, but it's require to check db
+ * status, otherwise it will cause SEGV.
+ */
+ /*
+ if(IS_DB_OPEN) {
+ wc->session->close(wc->session, NULL);
+ }
+ */
+ wc->session = NULL;
+ }
+
+ ch_free(wc);
+}
+
+wt_ctx *
+wt_ctx_get(Operation *op, struct wt_info *wi){
+ int rc;
+ void *data;
+ wt_ctx *wc = NULL;
+
+ rc = ldap_pvt_thread_pool_getkey(op->o_threadctx,
+ wi, &data, NULL );
+ if( rc ){
+ wc = wt_ctx_init(wi);
+ if( !wc ) {
+ Debug( LDAP_DEBUG_ANY, "wt_ctx: wt_ctx_init failed\n" );
+ return NULL;
+ }
+ rc = ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ wi, wc, wt_ctx_free,
+ NULL, NULL );
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY, "wt_ctx: setkey error(%d)\n",
+ rc );
+ return NULL;
+ }
+ return wc;
+ }
+ return (wt_ctx *)data;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/delete.c b/servers/slapd/back-wt/delete.c
new file mode 100644
index 0000000..9673662
--- /dev/null
+++ b/servers/slapd/back-wt/delete.c
@@ -0,0 +1,419 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_delete( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ struct berval pdn = {0, NULL};
+ Entry *e = NULL;
+ Entry *p = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ wt_ctx *wc;
+ int rc;
+
+ int parent_is_glue = 0;
+ int parent_is_leaf = 0;
+
+ Debug( LDAP_DEBUG_ARGS, "==> wt_delete: %s\n",
+ op->o_req_dn.bv_val );
+
+ if( op->o_txnSpec && txn_preop( op, rs ))
+ return rs->sr_err;
+
+ ctrls[num_ctrls] = 0;
+ rs->sr_text = NULL;
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_TRACE, "wt_delete: wt_ctx_get failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+/* allocate CSN */
+ if ( BER_BVISNULL( &op->o_csn ) ) {
+ struct berval csn;
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof(csnbuf);
+ slap_get_csn( op, &csn, 1 );
+ }
+
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ dnParent( &op->o_req_ndn, &pdn );
+ }
+
+ /* get parent */
+ rc = wt_dn2entry(op->o_bd, wc, &pdn, &p);
+ switch( rc ) {
+ case 0:
+ case WT_NOTFOUND:
+ break;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ Debug( LDAP_DEBUG_ANY,
+ "wt_delete: error at wt_dn2entry() rc=%d\n", rc );
+ goto return_results;
+ }
+
+ if ( rc == WT_NOTFOUND && pdn.bv_len != 0 ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "<== wt_delete: parent not found %s\n", op->o_req_dn.bv_val );
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &e);
+ Debug( LDAP_DEBUG_ARGS, "<== wt_delete: rc=%d\n", rc );
+
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_delete: wt_dn2aentry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( e && !BER_BVISEMPTY( &e->e_name )) {
+ rs->sr_matched = ch_strdup( e->e_name.bv_val );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ } else {
+ rs->sr_ref = referral_rewrite( default_referral, NULL,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ }
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ /* get entry */
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ break;
+ default:
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ Debug( LDAP_DEBUG_ANY,
+ "wt_delete: error at wt_dn2entry() rc=%d\n", rc );
+ goto return_results;
+ }
+
+ /* FIXME : dn2entry() should return non-glue entry */
+ if (rc == WT_NOTFOUND ||
+ ( !manageDSAit && e && is_entry_glue( e ) )) {
+ if ( !e ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "<== wt_delete: no such object %s\n",
+ op->o_req_dn.bv_val);
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_delete: wt_dn2aentry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ }
+
+ rs->sr_matched = ch_strdup( e->e_dn );
+ if ( is_entry_referral( e )) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ if ( pdn.bv_len != 0 ) {
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WDEL, NULL );
+
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: no write access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;
+ }
+
+ } else {
+ /* no parent, must be root to delete */
+ if( ! be_isroot( op ) ) {
+ if ( be_issuffix( op->o_bd, (struct berval *)&slap_empty_bv )
+ || be_shadow_update( op ) ) {
+ p = (Entry *)&slap_entry_root;
+
+ /* check parent for "children" acl */
+ rs->sr_err = access_allowed( op, p,
+ children, NULL, ACL_WDEL, NULL );
+
+ p = NULL;
+
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: no access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to parent";
+ goto return_results;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: no parent and not root\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+ }
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ rs->sr_err = access_allowed( op, e,
+ entry, NULL, ACL_WDEL, NULL );
+ if ( !rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: no write access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to entry";
+ goto return_results;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral, don't allow delete */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE, "wt_delete: entry is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = ch_strdup( e->e_name.bv_val );
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ goto return_results;
+ }
+
+ /* pre-read */
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: pre-read failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* Can't do it if we have kids */
+ rc = wt_dn2id_has_children( op, wc, e->e_id );
+ if( rc != WT_NOTFOUND ) {
+ switch( rc ) {
+ case 0:
+ Debug(LDAP_DEBUG_ARGS,
+ "<== wt_delete: non-leaf %s\n", op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subordinate objects must be deleted first";
+ break;
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<== wt_delete: has_children failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ }
+ goto return_results;
+ }
+
+ /* begin transaction */
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_delete: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "begin_transaction failed";
+ goto return_results;
+ }
+
+ /* delete from dn2id */
+ rc = wt_dn2id_delete( op, wc, &op->o_req_ndn);
+ if ( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_delete: dn2id failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "dn2id delete failed";
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ /* delete indices for old attributes */
+ rc = wt_index_entry_del( op, wc, e );
+ if ( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_delete: index delete failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "index delete failed";
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ /* fixup delete CSN */
+ if ( !SLAP_SHADOW( op->o_bd )) {
+ struct berval vals[2];
+
+ assert( !BER_BVISNULL( &op->o_csn ) );
+ vals[0] = op->o_csn;
+ BER_BVZERO( &vals[1] );
+ rs->sr_err = wt_index_values( op, wc, slap_schema.si_ad_entryCSN,
+ vals, 0, SLAP_INDEX_ADD_OP );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = "entryCSN index update failed";
+ rs->sr_err = LDAP_OTHER;
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+ }
+
+ /* delete from id2entry */
+ rc = wt_id2entry_delete( op, wc, e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: id2entry failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry delete failed";
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto return_results;
+ }
+
+ if ( pdn.bv_len != 0 ) {
+ // TODO: glue entry
+ }
+
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_delete: commit_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit_transaction failed";
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_delete: deleted%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "", e->e_id, op->o_req_dn.bv_val );
+
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ if( num_ctrls ) {
+ rs->sr_ctrls = ctrls;
+ }
+
+return_results:
+ if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) {
+ op->o_delete_glue_parent = 1;
+ }
+
+ if ( p != NULL ) {
+ wt_entry_return( p );
+ }
+
+ /* free entry */
+ if( e != NULL ) {
+ wt_entry_return( e );
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+
+ /* TODO: checkpoint */
+
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/dn2entry.c b/servers/slapd/back-wt/dn2entry.c
new file mode 100644
index 0000000..84cb13d
--- /dev/null
+++ b/servers/slapd/back-wt/dn2entry.c
@@ -0,0 +1,176 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+/*
+ * dn2entry - look up dn in the db and return the corresponding entry.
+ * No longer return closest ancestor, see wt_dn2pentry().
+ */
+int wt_dn2entry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep ){
+ uint64_t id;
+ WT_ITEM item;
+ EntryHeader eh;
+ int rc;
+ int eoff;
+ Entry *e = NULL;
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->dn2entry;
+
+ if( ndn->bv_len == 0 ){
+ /* empty dn */
+ e = entry_alloc();
+ ber_dupbv(&e->e_nname, ndn);
+ *ep = e;
+ return LDAP_SUCCESS;
+ }
+
+ if(!cursor){
+ rc = session->open_cursor(session,
+ WT_INDEX_DN"(id, entry)",
+ NULL, NULL, &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2entry: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->dn2entry = cursor;
+ }
+
+ cursor->set_key(cursor, ndn->bv_val);
+ rc = cursor->search(cursor);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2entry: search failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ cursor->get_value(cursor, &id, &item);
+ rc = wt_entry_header( &item, &eh );
+
+ eoff = eh.data - (char *)item.data;
+ eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + item.size;
+ eh.bv.bv_val = ch_malloc( eh.bv.bv_len );
+ memset(eh.bv.bv_val, 0xff, eh.bv.bv_len);
+ eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval );
+ memcpy(eh.data, item.data, item.size);
+ eh.data += eoff;
+ rc = entry_decode( &eh, &e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2entry: entry decode error: %d\n", rc );
+ goto done;
+ }
+
+ e->e_id = id;
+ *ep = e;
+
+done:
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->dn2entry = NULL;
+ }
+#endif
+ return rc;
+}
+
+/* dn2pentry - return parent entry */
+int wt_dn2pentry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep ){
+ Entry *e = NULL;
+ struct berval pdn;
+ int rc;
+
+ if (be_issuffix( be, ndn )) {
+ *ep = NULL;
+ return WT_NOTFOUND;
+ }
+
+ dnParent( ndn, &pdn );
+ rc = wt_dn2entry(be, wc, &pdn, &e);
+ *ep = e;
+ return rc;
+}
+
+/* dn2aentry - return ancestor entry */
+int wt_dn2aentry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep ) {
+ Entry *e = NULL;
+ struct berval pdn;
+ int rc;
+
+ if (be_issuffix( be, ndn )) {
+ *ep = NULL;
+ return 0;
+ }
+
+ dnParent( ndn, &pdn );
+ rc = wt_dn2entry(be, wc, &pdn, &e);
+ switch( rc ) {
+ case 0:
+ *ep = e;
+ break;
+ case WT_NOTFOUND:
+ rc = wt_dn2aentry(be, wc, &pdn, &e);
+ if (rc != 0 && rc != WT_NOTFOUND) {
+ return rc;
+ }
+ *ep = e;
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2aentry: failed %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ }
+ return rc;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/dn2id.c b/servers/slapd/back-wt/dn2id.c
new file mode 100644
index 0000000..d8765ce
--- /dev/null
+++ b/servers/slapd/back-wt/dn2id.c
@@ -0,0 +1,453 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "back-wt.h"
+#include "slap-config.h"
+#include "idl.h"
+
+static char *
+mkrevdn(struct berval src){
+ char *dst, *p;
+ char *rdn;
+ size_t rdn_len;
+
+ p = dst = ch_malloc(src.bv_len + 2);
+ while(src.bv_len){
+ rdn = ber_bvrchr( &src, ',' );
+ if (rdn) {
+ rdn_len = src.bv_len;
+ src.bv_len = rdn - src.bv_val;
+ rdn_len -= src.bv_len + 1;
+ rdn++;
+ }else{
+ /* first rdn */
+ rdn_len = src.bv_len;
+ rdn = src.bv_val;
+ src.bv_len = 0;
+ }
+ memcpy( p, rdn, rdn_len );
+ p += rdn_len;
+ *p++ = ',';
+ }
+ *p = '\0';
+ return dst;
+}
+
+int
+wt_dn2id_add(
+ Operation *op,
+ wt_ctx *wc,
+ ID pid,
+ Entry *e)
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int rc;
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->dn2id_w;
+ char *revdn = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id_add 0x%lx: \"%s\"\n",
+ e->e_id, e->e_ndn );
+ assert( e->e_id != NOID );
+
+ /* make reverse dn */
+ revdn = mkrevdn(e->e_nname);
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_TABLE_DN2ID, NULL,
+ "overwrite=false", &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_add: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->dn2id_w = cursor;
+ }
+ cursor->set_key(cursor, revdn);
+ cursor->set_value(cursor, e->e_ndn, e->e_id, pid);
+ rc = cursor->insert(cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_add: insert failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ if (wi->wi_flags & WT_USE_IDLCACHE) {
+ wt_idlcache_clear(op, wc, &e->e_nname);
+ }
+
+done:
+ if(revdn){
+ ch_free(revdn);
+ }
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->dn2id_w = NULL;
+ }
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id_add 0x%lx: %d\n", e->e_id, rc );
+ return rc;
+}
+
+int
+wt_dn2id_delete(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn)
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int rc = 0;
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->dn2id_w;
+ char *revdn = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id_delete %s\n", ndn->bv_val );
+
+ /* make reverse dn */
+ revdn = mkrevdn(*ndn);
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_TABLE_DN2ID, NULL,
+ "overwrite=false", &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_delete: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->dn2id_w = cursor;
+ }
+
+ cursor->set_key(cursor, revdn);
+ rc = cursor->remove(cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_delete: remove failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ if (wi->wi_flags & WT_USE_IDLCACHE) {
+ wt_idlcache_clear(op, wc, ndn);
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_dn2id_delete %s: %d\n", ndn->bv_val, rc );
+done:
+ if(revdn){
+ ch_free(revdn);
+ }
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->dn2id_w = NULL;
+ }
+#endif
+ return rc;
+}
+
+int
+wt_dn2id(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn,
+ ID *id)
+{
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->dn2id_ndn;
+ int rc = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id(\"%s\")\n", ndn->bv_val );
+
+ if ( ndn->bv_len == 0 ) {
+ *id = 0;
+ goto done;
+ }
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_INDEX_NDN
+ "(id)",
+ NULL, NULL, &cursor);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id: cursor open failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->dn2id_ndn = cursor;
+ }
+
+ cursor->set_key(cursor, ndn->bv_val);
+ rc = cursor->search(cursor);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id: search failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ rc = cursor->get_value(cursor, id);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id: get_value failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+done:
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->dn2id_ndn = NULL;
+ }
+#endif
+
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id: get failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id: got id=0x%lx\n",
+ *id );
+ }
+
+ return rc;
+}
+
+int
+wt_dn2id_has_children(
+ Operation *op,
+ wt_ctx *wc,
+ ID id )
+{
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->index_pid;
+ int rc;
+ uint64_t key = id;
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_INDEX_PID,
+ NULL, NULL, &cursor);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_has_children: cursor open failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->index_pid = cursor;
+ }
+
+ cursor->set_key(cursor, key);
+ rc = cursor->search(cursor);
+
+done:
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->index_pid = NULL;
+ }
+#endif
+
+ return rc;
+}
+
+int
+wt_dn2idl_db(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry *e,
+ ID *ids,
+ ID *stack)
+{
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->dn2id;
+ int rc;
+ char *revdn = NULL;
+ size_t revdn_len;
+ char *key;
+ ID id, pid;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_dn2idl(\"%s\")\n",
+ ndn->bv_val );
+
+ revdn = mkrevdn(*ndn);
+ revdn_len = strlen(revdn);
+
+ if ( !cursor ) {
+ rc = session->open_cursor(session, WT_TABLE_DN2ID"(id, pid)",
+ NULL, NULL, &cursor);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2idl: cursor open failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->dn2id = cursor;
+ }
+ cursor->set_key(cursor, revdn);
+ rc = cursor->search(cursor);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2idl: search failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ if( op->ors_scope == LDAP_SCOPE_CHILDREN ) {
+ cursor->next(cursor);
+ }
+
+ do {
+ rc = cursor->get_key(cursor, &key);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2idl: get_key failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ rc = cursor->get_value(cursor, &id, &pid);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id: get_value failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ if( strncmp(revdn, key, revdn_len) ){
+ break;
+ }
+
+ if( op->ors_scope == LDAP_SCOPE_ONELEVEL && e->e_id != pid ){
+ goto next;
+ }
+ wt_idl_append_one(ids, id);
+ next:
+ rc = cursor->next(cursor);
+ }while(rc == 0);
+
+ if (rc == WT_NOTFOUND ) {
+ rc = LDAP_SUCCESS;
+ }
+
+ wt_idl_sort(ids, stack);
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_dn2idl_db: size=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids) );
+
+done:
+ if(revdn){
+ ch_free(revdn);
+ }
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->dn2id = NULL;
+ }
+#endif
+ return rc;
+}
+
+int
+wt_dn2idl(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry *e,
+ ID *ids,
+ ID *stack)
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_dn2idl(\"%s\")\n", ndn->bv_val );
+
+ if(op->ors_scope != LDAP_SCOPE_ONELEVEL &&
+ be_issuffix( op->o_bd, &e->e_nname )){
+ WT_IDL_ALL(wi, ids);
+ return 0;
+ }
+
+ if (wi->wi_flags & WT_USE_IDLCACHE) {
+ rc = wt_idlcache_get(wc, ndn, op->ors_scope, ids);
+ if (rc == 0) {
+ /* cache hit */
+ return rc;
+ }
+ /* cache miss */
+ }
+
+ if ( wi->wi_flags & WT_USE_IDLCACHE ) {
+ wt_idlcache_begin(wc, ndn, op->ors_scope);
+ }
+ rc = wt_dn2idl_db(op, wc, ndn, e, ids, stack);
+ if ( rc == 0 && wi->wi_flags & WT_USE_IDLCACHE ) {
+ wt_idlcache_set(wc, ndn, op->ors_scope, ids);
+ }
+
+ return rc;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/extended.c b/servers/slapd/back-wt/extended.c
new file mode 100644
index 0000000..595672f
--- /dev/null
+++ b/servers/slapd/back-wt/extended.c
@@ -0,0 +1,58 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "lber_pvt.h"
+
+static struct exop {
+ struct berval *oid;
+ BI_op_extended *extended;
+} exop_table[] = {
+ { NULL, NULL }
+};
+
+int
+wt_extended( Operation *op, SlapReply *rs )
+{
+ int i;
+
+ for( i=0; exop_table[i].extended != NULL; i++ ) {
+ if( ber_bvcmp( exop_table[i].oid, &op->oq_extended.rs_reqoid ) == 0 ) {
+ return (exop_table[i].extended)( op, rs );
+ }
+ }
+
+ rs->sr_text = "not supported within naming context";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/filterindex.c b/servers/slapd/back-wt/filterindex.c
new file mode 100644
index 0000000..f321128
--- /dev/null
+++ b/servers/slapd/back-wt/filterindex.c
@@ -0,0 +1,718 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "idl.h"
+
+static int
+presence_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeDescription *desc,
+ ID *ids )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ int rc;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_presence_candidates (%s)\n",
+ desc->ad_cname.bv_val );
+
+ WT_IDL_ALL( wi, ids );
+
+ if( desc == slap_schema.si_ad_objectClass ) {
+ return 0;
+ }
+
+ rc = wt_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT,
+ &mask, &prefix );
+
+ if( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ /* not indexed */
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_presence_candidates: (%s) not indexed\n",
+ desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_presence_candidates: (%s) index_param "
+ "returned=%d\n",
+ desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( prefix.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_presence_candidates: (%s) no prefix\n",
+ desc->ad_cname.bv_val );
+ return -1;
+ }
+
+ /* open index cursor */
+ cursor = wt_index_open(wc, &desc->ad_type->sat_cname, 0);
+ if( !cursor ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_presence_candidates: open index cursor failed: %s\n",
+ desc->ad_type->sat_cname.bv_val );
+ return 0;
+ }
+
+ rc = wt_key_read( op->o_bd, cursor, &prefix, ids, NULL, 0 );
+
+ cursor->close(cursor);
+ Debug(LDAP_DEBUG_TRACE,
+ "<= wt_presence_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids) );
+
+ return 0;
+}
+
+static int
+equality_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp)
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ int i;
+ int rc;
+ MatchingRule *mr;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_equality_candidates (%s=%s)\n",
+ ava->aa_desc->ad_cname.bv_val, ava->aa_value.bv_val );
+
+ if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
+ ID id = NOID;
+ rc = wt_dn2id(op, wc, &ava->aa_value, &id);
+ if( rc == 0 ){
+ wt_idl_append_one(ids, id);
+ }else if ( rc == WT_NOTFOUND ) {
+ WT_IDL_ZERO( ids );
+ rc = 0;
+ }
+ return rc;
+ }
+
+ WT_IDL_ALL( wi, ids );
+
+ rc = wt_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY,
+ &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_equality_candidates: (%s) not indexed\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_equality_candidates: (%s) index_param failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = ava->aa_desc->ad_type->sat_equality;
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_EQUALITY,
+ mask,
+ ava->aa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ &ava->aa_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_equality_candidates: (%s, %s) "
+ "MR filter failed (%d)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_equality_candidates: (%s) no keys\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ /* open index cursor */
+ cursor = wt_index_open(wc, &ava->aa_desc->ad_type->sat_cname, 0);
+ if( !cursor ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_equality_candidates: open index cursor failed: %s\n",
+ ava->aa_desc->ad_type->sat_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
+ if( rc == WT_NOTFOUND ) {
+ WT_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_equality_candidates: (%s) key read failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+ if ( i == 0 ) {
+ WT_IDL_CPY( ids, tmp );
+ } else {
+ wt_idl_intersection( ids, tmp );
+ }
+
+ if( WT_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ cursor->close(cursor);
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_equality_candidates: id=%ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids) );
+
+ return rc;
+}
+
+static int
+approx_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeAssertion *ava,
+ ID *ids,
+ ID *tmp )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_approx_candidates (%s)\n",
+ ava->aa_desc->ad_cname.bv_val );
+
+ WT_IDL_ALL( wi, ids );
+
+ rc = wt_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX,
+ &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_approx_candidates: (%s) not indexed\n",
+ ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_approx_candidates: (%s) index_param failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = ava->aa_desc->ad_type->sat_approx;
+ if( !mr ) {
+ /* no approx matching rule, try equality matching rule */
+ mr = ava->aa_desc->ad_type->sat_equality;
+ }
+
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_APPROX,
+ mask,
+ ava->aa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ &ava->aa_value,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_approx_candidates: (%s, %s) MR filter failed (%d)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_approx_candidates: (%s) no keys (%s)\n",
+ prefix.bv_val, ava->aa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ /* open index cursor */
+ cursor = wt_index_open(wc, &ava->aa_desc->ad_type->sat_cname, 0);
+ if( !cursor ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_approx_candidates: open index cursor failed: %s\n",
+ ava->aa_desc->ad_type->sat_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
+ if( rc == WT_NOTFOUND ) {
+ WT_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_approx_candidates: (%s) key read failed (%d)\n",
+ ava->aa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( WT_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_approx_candidates: (%s) NULL\n",
+ ava->aa_desc->ad_cname.bv_val );
+ WT_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ WT_IDL_CPY( ids, tmp );
+ } else {
+ wt_idl_intersection( ids, tmp );
+ }
+
+ if( WT_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ cursor->close(cursor);
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_approx_candidates %ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids) );
+
+ return rc;
+}
+
+static int
+substring_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ SubstringsAssertion *sub,
+ ID *ids,
+ ID *tmp )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int i;
+ int rc;
+ slap_mask_t mask;
+ struct berval prefix = {0, NULL};
+ struct berval *keys = NULL;
+ MatchingRule *mr;
+ WT_CURSOR *cursor = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "=> wt_substring_candidates (%s)\n",
+ sub->sa_desc->ad_cname.bv_val );
+
+ WT_IDL_ALL( wi, ids );
+
+ rc = wt_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS,
+ &mask, &prefix );
+
+ if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_substring_candidates: (%s) not indexed\n",
+ sub->sa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_substring_candidates: (%s) index_param failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ mr = sub->sa_desc->ad_type->sat_substr;
+
+ if( !mr ) {
+ return 0;
+ }
+
+ if( !mr->smr_filter ) {
+ return 0;
+ }
+
+ rc = (mr->smr_filter)(
+ LDAP_FILTER_SUBSTRINGS,
+ mask,
+ sub->sa_desc->ad_type->sat_syntax,
+ mr,
+ &prefix,
+ sub,
+ &keys, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_substring_candidates: (%s) MR filter failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ return 0;
+ }
+
+ if( keys == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_substring_candidates: (0x%04lx) no keys (%s)\n",
+ mask, sub->sa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ /* open index cursor */
+ cursor = wt_index_open(wc, &sub->sa_desc->ad_cname, 0);
+ if( !cursor ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= wt_substring_candidates: open index cursor failed: %s\n",
+ sub->sa_desc->ad_cname.bv_val );
+ return 0;
+ }
+
+ for ( i= 0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
+
+ if( rc == WT_NOTFOUND ) {
+ WT_IDL_ZERO( ids );
+ rc = 0;
+ break;
+ } else if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_substring_candidates: (%s) key read failed (%d)\n",
+ sub->sa_desc->ad_cname.bv_val, rc );
+ break;
+ }
+
+ if( WT_IDL_IS_ZERO( tmp ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_substring_candidates: (%s) NULL\n",
+ sub->sa_desc->ad_cname.bv_val );
+ WT_IDL_ZERO( ids );
+ break;
+ }
+
+ if ( i == 0 ) {
+ WT_IDL_CPY( ids, tmp );
+ } else {
+ wt_idl_intersection( ids, tmp );
+ }
+
+ if( WT_IDL_IS_ZERO( ids ) )
+ break;
+ }
+
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+
+ cursor->close(cursor);
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= wt_substring_candidates: %ld, first=%ld, last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids));
+ return rc;
+}
+
+#ifdef LDAP_COMP_MATCH
+static int
+comp_candidates (
+ Operation *op,
+ wt_ctx *wc,
+ MatchingRuleAssertion *mra,
+ ComponentFilter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack)
+{
+ int rc = 0;
+
+ if ( !f ) return LDAP_PROTOCOL_ERROR;
+
+ Debug( LDAP_DEBUG_FILTER, "comp_candidates\n" );
+ /* TODO: */
+ Debug( LDAP_DEBUG_FILTER, "=> not implement yet\n" );
+ return( rc );
+}
+
+#endif
+
+static int
+ext_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ MatchingRuleAssertion *mra,
+ ID *ids,
+ ID *tmp,
+ ID *stack )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+
+#ifdef LDAP_COMP_MATCH
+ /*
+ * Currently Only Component Indexing for componentFilterMatch is supported
+ * Indexing for an extensible filter is not supported yet
+ */
+ if ( mra->ma_cf ) {
+ return comp_candidates ( op, wc, mra, mra->ma_cf, ids, tmp, stack);
+ }
+#endif
+ if ( mra->ma_desc == slap_schema.si_ad_entryDN ) {
+ /* TODO: */
+ Debug( LDAP_DEBUG_FILTER, "=> not implement yet.\n" );
+ }
+ WT_IDL_ALL( wi, ids );
+ return 0;
+}
+
+static int
+list_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ Filter *flist,
+ int ftype,
+ ID *ids,
+ ID *tmp,
+ ID *save )
+{
+ int rc = 0;
+ Filter *f;
+
+ Debug( LDAP_DEBUG_FILTER, "=> wt_list_candidates 0x%x\n", ftype );
+ for ( f = flist; f != NULL; f = f->f_next ) {
+ /* ignore precomputed scopes */
+ if ( f->f_choice == SLAPD_FILTER_COMPUTED &&
+ f->f_result == LDAP_SUCCESS ) {
+ continue;
+ }
+ WT_IDL_ZERO( save );
+ rc = wt_filter_candidates( op, wc, f, save, tmp,
+ save+WT_IDL_UM_SIZE );
+
+ if ( rc != 0 ) {
+ /* TODO: error handling */
+ /*
+ if ( rc == DB_LOCK_DEADLOCK )
+ return rc;
+ */
+ if ( ftype == LDAP_FILTER_AND ) {
+ rc = 0;
+ continue;
+ }
+ break;
+ }
+
+
+ if ( ftype == LDAP_FILTER_AND ) {
+ if ( f == flist ) {
+ WT_IDL_CPY( ids, save );
+ } else {
+ wt_idl_intersection( ids, save );
+ }
+ if( WT_IDL_IS_ZERO( ids ) )
+ break;
+ } else {
+ if ( f == flist ) {
+ WT_IDL_CPY( ids, save );
+ } else {
+ wt_idl_union( ids, save );
+ }
+ }
+ }
+
+ if( rc == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_list_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids) );
+
+ } else {
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_list_candidates: undefined rc=%d\n",
+ rc );
+ }
+
+ return 0;
+}
+
+int
+wt_filter_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ Filter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack )
+{
+ struct wt_info *wi = (struct wt_info *)op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+ Debug( LDAP_DEBUG_FILTER, "=> wt_filter_candidates\n" );
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ WT_IDL_ZERO( ids );
+ goto done;
+ }
+
+ switch ( f->f_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ switch( f->f_result ) {
+ case SLAPD_COMPARE_UNDEFINED:
+ /* This technically is not the same as FALSE, but it
+ * certainly will produce no matches.
+ */
+ /* FALL THRU */
+ case LDAP_COMPARE_FALSE:
+ WT_IDL_ZERO( ids );
+ break;
+ case LDAP_COMPARE_TRUE: {
+
+ WT_IDL_ALL( wi, ids );
+ } break;
+ case LDAP_SUCCESS:
+ /* this is a pre-computed scope, leave it alone */
+ break;
+ }
+ break;
+ case LDAP_FILTER_PRESENT:
+ Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n" );
+ rc = presence_candidates( op, wc, f->f_desc, ids );
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n" );
+ rc = equality_candidates( op, wc, f->f_ava, ids, tmp );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n" );
+ rc = approx_candidates( op, wc, f->f_ava, ids, tmp );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n" );
+ rc = substring_candidates( op, wc, f->f_sub, ids, tmp );
+ break;
+
+ case LDAP_FILTER_GE:
+ /* if no GE index, use pres */
+ /* TODO: not implement yet */
+ rc = presence_candidates( op, wc, f->f_ava->aa_desc, ids );
+ break;
+
+ case LDAP_FILTER_LE:
+ /* if no LE index, use pres */
+ /* TODO: not implement yet */
+ Debug( LDAP_DEBUG_FILTER, "\tLE\n" );
+ rc = presence_candidates( op, wc, f->f_ava->aa_desc, ids );
+ break;
+
+ case LDAP_FILTER_NOT:
+ /* no indexing to support NOT filters */
+ Debug( LDAP_DEBUG_FILTER, "\tNOT\n" );
+ WT_IDL_ALL( wi, ids );
+ break;
+
+ case LDAP_FILTER_AND:
+ Debug( LDAP_DEBUG_FILTER, "\tAND\n" );
+ rc = list_candidates( op, wc,
+ f->f_and, LDAP_FILTER_AND, ids, tmp, stack );
+ break;
+
+ case LDAP_FILTER_OR:
+ Debug( LDAP_DEBUG_FILTER, "\tOR\n" );
+ rc = list_candidates( op, wc,
+ f->f_or, LDAP_FILTER_OR, ids, tmp, stack );
+ break;
+
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, "\tEXT\n" );
+ rc = ext_candidates( op, wc, f->f_mra, ids, tmp, stack);
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n",
+ (unsigned long) f->f_choice );
+ /* Must not return NULL, otherwise extended filters break */
+ WT_IDL_ALL( wi, ids );
+ }
+
+done:
+ Debug( LDAP_DEBUG_FILTER,
+ "<= wt_filter_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST( ids ),
+ (long) WT_IDL_LAST( ids ) );
+ return rc;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/id2entry.c b/servers/slapd/back-wt/id2entry.c
new file mode 100644
index 0000000..95b197c
--- /dev/null
+++ b/servers/slapd/back-wt/id2entry.c
@@ -0,0 +1,352 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "back-wt.h"
+#include "slap-config.h"
+
+static int wt_id2entry_put(
+ Operation *op,
+ wt_ctx *wc,
+ Entry *e,
+ WT_CURSOR *cursor)
+{
+ struct berval bv;
+ WT_ITEM item;
+ int rc;
+
+ rc = entry_encode( e, &bv );
+ if(rc != LDAP_SUCCESS){
+ return -1;
+ }
+ item.size = bv.bv_len;
+ item.data = bv.bv_val;
+
+ cursor->set_key(cursor, e->e_id);
+ cursor->set_value(cursor, e->e_ndn, &item);
+ rc = cursor->insert(cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry_put: insert failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+done:
+ ch_free( bv.bv_val );
+
+ return rc;
+}
+
+int wt_id2entry_add(
+ Operation *op,
+ wt_ctx *wc,
+ Entry *e )
+{
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->id2entry_add;
+ int rc;
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_TABLE_ID2ENTRY, NULL,
+ "overwrite=false", &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry_put: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ wc->id2entry_add = cursor;
+ }
+
+ rc = wt_id2entry_put(op, wc, e, cursor);
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->id2entry_add = NULL;
+ }
+#endif
+
+ return rc;
+}
+
+int wt_id2entry_update(
+ Operation *op,
+ wt_ctx *wc,
+ Entry *e )
+{
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->id2entry_update;
+ int rc;
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_TABLE_ID2ENTRY, NULL,
+ "overwrite=true", &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry_put: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ wc->id2entry_update = cursor;
+ }
+ rc = wt_id2entry_put(op, wc, e, cursor);
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->id2entry_update = NULL;
+ }
+#endif
+ return rc;
+}
+
+int wt_id2entry_delete(
+ Operation *op,
+ wt_ctx *wc,
+ Entry *e )
+{
+ int rc;
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = NULL;
+
+ rc = session->open_cursor(session, WT_TABLE_ID2ENTRY, NULL,
+ NULL, &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry_delete: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ cursor->set_key(cursor, e->e_id);
+ rc = cursor->remove(cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry_delete: remove failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+done:
+ if(cursor){
+ cursor->close(cursor);
+ }
+ return rc;
+}
+
+int wt_id2entry( BackendDB *be,
+ wt_ctx *wc,
+ ID id,
+ Entry **ep ){
+ int rc;
+ WT_SESSION *session = wc->session;
+ WT_CURSOR *cursor = wc->id2entry;
+ WT_ITEM item;
+ EntryHeader eh;
+ int eoff;
+ Entry *e = NULL;
+
+ if(!cursor){
+ rc = session->open_cursor(session, WT_TABLE_ID2ENTRY"(entry)", NULL,
+ NULL, &cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ wc->id2entry = cursor;
+ }
+
+ cursor->set_key(cursor, id);
+ rc = cursor->search(cursor);
+ if ( rc ) {
+ goto done;
+ }
+
+ cursor->get_value(cursor, &item);
+ rc = wt_entry_header( &item, &eh );
+ eoff = eh.data - (char *)item.data;
+ eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + item.size;
+ eh.bv.bv_val = ch_malloc( eh.bv.bv_len );
+ memset(eh.bv.bv_val, 0xff, eh.bv.bv_len);
+ eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval );
+ memcpy(eh.data, item.data, item.size);
+ eh.data += eoff;
+ rc = entry_decode( &eh, &e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_id2entry: entry decode error: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ e->e_id = id;
+ *ep = e;
+
+done:
+
+#ifdef WT_CURSOR_CACHE
+ if(cursor){
+ cursor->reset(cursor);
+ }
+#else
+ if(cursor){
+ cursor->close(cursor);
+ wc->id2entry = NULL;
+ }
+#endif
+ return rc;
+}
+
+int wt_entry_return(
+ Entry *e
+ )
+{
+ if ( !e ) {
+ return 0;
+ }
+
+ /* Our entries are allocated in two blocks; the data comes from
+ * the db itself and the Entry structure and associated pointers
+ * are allocated in entry_decode. The db data pointer is saved
+ * in e_bv.
+ */
+ if ( e->e_bv.bv_val ) {
+#if 0
+ /* See if the DNs were changed by modrdn */
+ if( e->e_nname.bv_val < e->e_bv.bv_val || e->e_nname.bv_val >
+ e->e_bv.bv_val + e->e_bv.bv_len ) {
+ ch_free(e->e_name.bv_val);
+ ch_free(e->e_nname.bv_val);
+ }
+#endif
+ e->e_name.bv_val = NULL;
+ e->e_nname.bv_val = NULL;
+ /* In tool mode the e_bv buffer is realloc'd, leave it alone */
+ if( !(slapMode & SLAP_TOOL_MODE) ) {
+ free( e->e_bv.bv_val );
+ }
+ BER_BVZERO( &e->e_bv );
+ }
+
+ entry_free( e );
+}
+
+int wt_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ return wt_entry_return( e );
+}
+
+/*
+ * return LDAP_SUCCESS IFF we can retrieve the specified entry.
+ */
+int wt_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ wt_ctx *wc;
+ Entry *e = NULL;
+ int rc;
+ const char *at_name = at ? at->ad_cname.bv_val : "(null)";
+
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_entry_get: ndn: \"%s\"\n", ndn->bv_val );
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_entry_get: oc: \"%s\", at: \"%s\"\n",
+ oc ? oc->soc_cname.bv_val : "(null)", at_name );
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_entry_get: wt_ctx_get failed\n" );
+ return LDAP_OTHER;
+ }
+ rc = wt_dn2entry(op->o_bd, wc, ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ Debug( LDAP_DEBUG_ACL,
+ "wt_entry_get: cannot find entry: \"%s\"\n",
+ ndn->bv_val );
+ return LDAP_NO_SUCH_OBJECT;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_entry_get: wt_dn2entry failed %s rc=%d\n",
+ wiredtiger_strerror(rc), rc );
+ rc = LDAP_OTHER;
+ }
+
+ Debug( LDAP_DEBUG_ACL,
+ "wt_entry_get: found entry: \"%s\"\n", ndn->bv_val );
+
+ if ( oc && !is_entry_objectclass( e, oc, 0 )) {
+ Debug( LDAP_DEBUG_ACL,
+ "wt_entry_get: failed to find objectClass %s\n",
+ oc->soc_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+ /* NOTE: attr_find() or attrs_find()? */
+ if ( at && attr_find( e->e_attrs, at ) == NULL ) {
+ Debug( LDAP_DEBUG_ACL,
+ "wt_entry_get: failed to find attribute %s\n",
+ at->ad_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+return_results:
+ if( rc != LDAP_SUCCESS ) {
+ wt_entry_return( e );
+ }else{
+ *ent = e;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "wt_entry_get: rc=%d\n", rc );
+
+ return rc;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/idl.c b/servers/slapd/back-wt/idl.c
new file mode 100644
index 0000000..0f3167d
--- /dev/null
+++ b/servers/slapd/back-wt/idl.c
@@ -0,0 +1,789 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "idl.h"
+
+#define IDL_MAX(x,y) ( (x) > (y) ? (x) : (y) )
+#define IDL_MIN(x,y) ( (x) < (y) ? (x) : (y) )
+#define IDL_CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
+
+void wt_idl_check( ID *ids )
+{
+ if( WT_IDL_IS_RANGE( ids ) ) {
+ assert( WT_IDL_RANGE_FIRST(ids) <= WT_IDL_RANGE_LAST(ids) );
+ } else {
+ ID i;
+ for( i=1; i < ids[0]; i++ ) {
+ assert( ids[i+1] > ids[i] );
+ }
+ }
+}
+
+void wt_idl_dump( ID *ids )
+{
+ if( WT_IDL_IS_RANGE( ids ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "IDL: range ( %ld - %ld )\n",
+ (long) WT_IDL_RANGE_FIRST( ids ),
+ (long) WT_IDL_RANGE_LAST( ids ) );
+
+ } else {
+ ID i;
+ Debug( LDAP_DEBUG_ANY, "IDL: size %ld", (long) ids[0] );
+
+ for( i=1; i<=ids[0]; i++ ) {
+ if( i % 16 == 1 ) {
+ Debug( LDAP_DEBUG_ANY, "\n" );
+ }
+ Debug( LDAP_DEBUG_ANY, " %02lx", (long) ids[i] );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "\n" );
+ }
+
+ wt_idl_check( ids );
+}
+
+unsigned wt_idl_search( ID *ids, ID id )
+{
+#define IDL_BINARY_SEARCH 1
+#ifdef IDL_BINARY_SEARCH
+ /*
+ * binary search of id in ids
+ * if found, returns position of id
+ * if not found, returns first position greater than id
+ */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = ids[0];
+
+#if IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = IDL_CMP( id, ids[cursor] );
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ return cursor;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ return cursor;
+
+#else
+ /* (reverse) linear search */
+ int i;
+
+#if IDL_DEBUG > 0
+ idl_check( ids );
+#endif
+
+ for( i=ids[0]; i; i-- ) {
+ if( id > ids[i] ) {
+ break;
+ }
+ }
+
+ return i+1;
+#endif
+}
+
+int wt_idl_insert( ID *ids, ID id )
+{
+ unsigned x;
+
+#if IDL_DEBUG > 1
+ Debug( LDAP_DEBUG_ANY, "insert: %04lx at %d\n", (long) id, x );
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ wt_idl_check( ids );
+#endif
+
+ if (WT_IDL_IS_RANGE( ids )) {
+ /* if already in range, treat as a dup */
+ if (id >= WT_IDL_RANGE_FIRST(ids) && id <= WT_IDL_RANGE_LAST(ids))
+ return -1;
+ if (id < WT_IDL_RANGE_FIRST(ids))
+ ids[1] = id;
+ else if (id > WT_IDL_RANGE_LAST(ids))
+ ids[2] = id;
+ return 0;
+ }
+
+ x = wt_idl_search( ids, id );
+ assert( x > 0 );
+
+ if( x < 1 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if ( x <= ids[0] && ids[x] == id ) {
+ /* duplicate */
+ return -1;
+ }
+
+ if ( ++ids[0] >= WT_IDL_DB_MAX ) {
+ if( id < ids[1] ) {
+ ids[1] = id;
+ ids[2] = ids[ids[0]-1];
+ } else if ( ids[ids[0]-1] < id ) {
+ ids[2] = id;
+ } else {
+ ids[2] = ids[ids[0]-1];
+ }
+ ids[0] = NOID;
+
+ } else {
+ /* insert id */
+ AC_MEMCPY( &ids[x+1], &ids[x], (ids[0]-x) * sizeof(ID) );
+ ids[x] = id;
+ }
+
+#if IDL_DEBUG > 1
+ wt_idl_dump( ids );
+#elif IDL_DEBUG > 0
+ wt_idl_check( ids );
+#endif
+
+ return 0;
+}
+
+static int wt_idl_delete( ID *ids, ID id )
+{
+ unsigned x;
+
+#if IDL_DEBUG > 1
+ Debug( LDAP_DEBUG_ANY, "delete: %04lx at %d\n", (long) id, x );
+ idl_dump( ids );
+#elif IDL_DEBUG > 0
+ wt_idl_check( ids );
+#endif
+
+ if (WT_IDL_IS_RANGE( ids )) {
+ /* If deleting a range boundary, adjust */
+ if ( ids[1] == id )
+ ids[1]++;
+ else if ( ids[2] == id )
+ ids[2]--;
+ /* deleting from inside a range is a no-op */
+
+ /* If the range has collapsed, re-adjust */
+ if ( ids[1] > ids[2] )
+ ids[0] = 0;
+ else if ( ids[1] == ids[2] )
+ ids[1] = 1;
+ return 0;
+ }
+
+ x = wt_idl_search( ids, id );
+ assert( x > 0 );
+
+ if( x <= 0 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if( x > ids[0] || ids[x] != id ) {
+ /* not found */
+ return -1;
+
+ } else if ( --ids[0] == 0 ) {
+ if( x != 1 ) {
+ return -3;
+ }
+
+ } else {
+ AC_MEMCPY( &ids[x], &ids[x+1], (1+ids[0]-x) * sizeof(ID) );
+ }
+
+#if IDL_DEBUG > 1
+ wt_idl_dump( ids );
+#elif IDL_DEBUG > 0
+ wt_idl_check( ids );
+#endif
+
+ return 0;
+}
+
+static char *
+wt_show_key(
+ char *buf,
+ void *val,
+ size_t len )
+{
+ if ( len == 4 /* LUTIL_HASH_BYTES */ ) {
+ unsigned char *c = val;
+ sprintf( buf, "[%02x%02x%02x%02x]", c[0], c[1], c[2], c[3] );
+ return buf;
+ } else {
+ return val;
+ }
+}
+
+/*
+ * idl_intersection - return a = a intersection b
+ */
+int
+wt_idl_intersection(
+ ID *a,
+ ID *b )
+{
+ ID ida, idb;
+ ID idmax, idmin;
+ ID cursora = 0, cursorb = 0, cursorc;
+ int swap = 0;
+
+ if ( WT_IDL_IS_ZERO( a ) || WT_IDL_IS_ZERO( b ) ) {
+ a[0] = 0;
+ return 0;
+ }
+
+ idmin = IDL_MAX( WT_IDL_FIRST(a), WT_IDL_FIRST(b) );
+ idmax = IDL_MIN( WT_IDL_LAST(a), WT_IDL_LAST(b) );
+ if ( idmin > idmax ) {
+ a[0] = 0;
+ return 0;
+ } else if ( idmin == idmax ) {
+ a[0] = 1;
+ a[1] = idmin;
+ return 0;
+ }
+
+ if ( WT_IDL_IS_RANGE( a ) ) {
+ if ( WT_IDL_IS_RANGE(b) ) {
+ /* If both are ranges, just shrink the boundaries */
+ a[1] = idmin;
+ a[2] = idmax;
+ return 0;
+ } else {
+ /* Else swap so that b is the range, a is a list */
+ ID *tmp = a;
+ a = b;
+ b = tmp;
+ swap = 1;
+ }
+ }
+
+ /* If a range completely covers the list, the result is
+ * just the list. If idmin to idmax is contiguous, just
+ * turn it into a range.
+ */
+ if ( WT_IDL_IS_RANGE( b )
+ && WT_IDL_RANGE_FIRST( b ) <= WT_IDL_FIRST( a )
+ && WT_IDL_RANGE_LAST( b ) >= WT_IDL_LLAST( a ) ) {
+ if (idmax - idmin + 1 == a[0])
+ {
+ a[0] = NOID;
+ a[1] = idmin;
+ a[2] = idmax;
+ }
+ goto done;
+ }
+
+ /* Fine, do the intersection one element at a time.
+ * First advance to idmin in both IDLs.
+ */
+ cursora = cursorb = idmin;
+ ida = wt_idl_first( a, &cursora );
+ idb = wt_idl_first( b, &cursorb );
+ cursorc = 0;
+
+ while( ida <= idmax || idb <= idmax ) {
+ if( ida == idb ) {
+ a[++cursorc] = ida;
+ ida = wt_idl_next( a, &cursora );
+ idb = wt_idl_next( b, &cursorb );
+ } else if ( ida < idb ) {
+ ida = wt_idl_next( a, &cursora );
+ } else {
+ idb = wt_idl_next( b, &cursorb );
+ }
+ }
+ a[0] = cursorc;
+done:
+ if (swap)
+ WT_IDL_CPY( b, a );
+
+ return 0;
+}
+
+
+/*
+ * idl_union - return a = a union b
+ */
+int
+wt_idl_union(
+ ID *a,
+ ID *b )
+{
+ ID ida, idb;
+ ID cursora = 0, cursorb = 0, cursorc;
+
+ if ( WT_IDL_IS_ZERO( b ) ) {
+ return 0;
+ }
+
+ if ( WT_IDL_IS_ZERO( a ) ) {
+ WT_IDL_CPY( a, b );
+ return 0;
+ }
+
+ if ( WT_IDL_IS_RANGE( a ) || WT_IDL_IS_RANGE(b) ) {
+over: ida = IDL_MIN( WT_IDL_FIRST(a), WT_IDL_FIRST(b) );
+ idb = IDL_MAX( WT_IDL_LAST(a), WT_IDL_LAST(b) );
+ a[0] = NOID;
+ a[1] = ida;
+ a[2] = idb;
+ return 0;
+ }
+
+ ida = wt_idl_first( a, &cursora );
+ idb = wt_idl_first( b, &cursorb );
+
+ cursorc = b[0];
+
+ /* The distinct elements of a are cat'd to b */
+ while( ida != NOID || idb != NOID ) {
+ if ( ida < idb ) {
+ if( ++cursorc > WT_IDL_UM_MAX ) {
+ goto over;
+ }
+ b[cursorc] = ida;
+ ida = wt_idl_next( a, &cursora );
+
+ } else {
+ if ( ida == idb )
+ ida = wt_idl_next( a, &cursora );
+ idb = wt_idl_next( b, &cursorb );
+ }
+ }
+
+ /* b is copied back to a in sorted order */
+ a[0] = cursorc;
+ cursora = 1;
+ cursorb = 1;
+ cursorc = b[0]+1;
+ while (cursorb <= b[0] || cursorc <= a[0]) {
+ if (cursorc > a[0])
+ idb = NOID;
+ else
+ idb = b[cursorc];
+ if (cursorb <= b[0] && b[cursorb] < idb)
+ a[cursora++] = b[cursorb++];
+ else {
+ a[cursora++] = idb;
+ cursorc++;
+ }
+ }
+
+ return 0;
+}
+
+
+#if 0
+/*
+ * wt_idl_notin - return a intersection ~b (or a minus b)
+ */
+int
+wt_idl_notin(
+ ID *a,
+ ID *b,
+ ID *ids )
+{
+ ID ida, idb;
+ ID cursora = 0, cursorb = 0;
+
+ if( WT_IDL_IS_ZERO( a ) ||
+ WT_IDL_IS_ZERO( b ) ||
+ WT_IDL_IS_RANGE( b ) )
+ {
+ WT_IDL_CPY( ids, a );
+ return 0;
+ }
+
+ if( WT_IDL_IS_RANGE( a ) ) {
+ WT_IDL_CPY( ids, a );
+ return 0;
+ }
+
+ ida = wt_idl_first( a, &cursora ),
+ idb = wt_idl_first( b, &cursorb );
+
+ ids[0] = 0;
+
+ while( ida != NOID ) {
+ if ( idb == NOID ) {
+ /* we could shortcut this */
+ ids[++ids[0]] = ida;
+ ida = wt_idl_next( a, &cursora );
+
+ } else if ( ida < idb ) {
+ ids[++ids[0]] = ida;
+ ida = wt_idl_next( a, &cursora );
+
+ } else if ( ida > idb ) {
+ idb = wt_idl_next( b, &cursorb );
+
+ } else {
+ ida = wt_idl_next( a, &cursora );
+ idb = wt_idl_next( b, &cursorb );
+ }
+ }
+
+ return 0;
+}
+#endif
+
+ID wt_idl_first( ID *ids, ID *cursor )
+{
+ ID pos;
+
+ if ( ids[0] == 0 ) {
+ *cursor = NOID;
+ return NOID;
+ }
+
+ if ( WT_IDL_IS_RANGE( ids ) ) {
+ if( *cursor < ids[1] ) {
+ *cursor = ids[1];
+ }
+ return *cursor;
+ }
+
+ if ( *cursor == 0 )
+ pos = 1;
+ else
+ pos = wt_idl_search( ids, *cursor );
+
+ if( pos > ids[0] ) {
+ return NOID;
+ }
+
+ *cursor = pos;
+ return ids[pos];
+}
+
+ID wt_idl_next( ID *ids, ID *cursor )
+{
+ if ( WT_IDL_IS_RANGE( ids ) ) {
+ if( ids[2] < ++(*cursor) ) {
+ return NOID;
+ }
+ return *cursor;
+ }
+
+ if ( ++(*cursor) <= ids[0] ) {
+ return ids[*cursor];
+ }
+
+ return NOID;
+}
+
+/* Add one ID to an unsorted list. We ensure that the first element is the
+ * minimum and the last element is the maximum, for fast range compaction.
+ * this means IDLs up to length 3 are always sorted...
+ */
+int wt_idl_append_one( ID *ids, ID id )
+{
+ if (WT_IDL_IS_RANGE( ids )) {
+ /* if already in range, treat as a dup */
+ if (id >= WT_IDL_RANGE_FIRST(ids) && id <= WT_IDL_RANGE_LAST(ids))
+ return -1;
+ if (id < WT_IDL_RANGE_FIRST(ids))
+ ids[1] = id;
+ else if (id > WT_IDL_RANGE_LAST(ids))
+ ids[2] = id;
+ return 0;
+ }
+ if ( ids[0] ) {
+ ID tmp;
+
+ if (id < ids[1]) {
+ tmp = ids[1];
+ ids[1] = id;
+ id = tmp;
+ }
+ if ( ids[0] > 1 && id < ids[ids[0]] ) {
+ tmp = ids[ids[0]];
+ ids[ids[0]] = id;
+ id = tmp;
+ }
+ }
+ ids[0]++;
+ if ( ids[0] >= WT_IDL_UM_MAX ) {
+ ids[0] = NOID;
+ ids[2] = id;
+ } else {
+ ids[ids[0]] = id;
+ }
+ return 0;
+}
+
+/* Append sorted list b to sorted list a. The result is unsorted but
+ * a[1] is the min of the result and a[a[0]] is the max.
+ */
+int wt_idl_append( ID *a, ID *b )
+{
+ ID ida, idb, tmp, swap = 0;
+
+ if ( WT_IDL_IS_ZERO( b ) ) {
+ return 0;
+ }
+
+ if ( WT_IDL_IS_ZERO( a ) ) {
+ WT_IDL_CPY( a, b );
+ return 0;
+ }
+
+ ida = WT_IDL_LAST( a );
+ idb = WT_IDL_LAST( b );
+ if ( WT_IDL_IS_RANGE( a ) || WT_IDL_IS_RANGE(b) ||
+ a[0] + b[0] >= WT_IDL_UM_MAX ) {
+ a[2] = IDL_MAX( ida, idb );
+ a[1] = IDL_MIN( a[1], b[1] );
+ a[0] = NOID;
+ return 0;
+ }
+
+ if ( b[0] > 1 && ida > idb ) {
+ swap = idb;
+ a[a[0]] = idb;
+ b[b[0]] = ida;
+ }
+
+ if ( b[1] < a[1] ) {
+ tmp = a[1];
+ a[1] = b[1];
+ } else {
+ tmp = b[1];
+ }
+ a[0]++;
+ a[a[0]] = tmp;
+
+ if ( b[0] > 1 ) {
+ int i = b[0] - 1;
+ AC_MEMCPY(a+a[0]+1, b+2, i * sizeof(ID));
+ a[0] += i;
+ }
+ if ( swap ) {
+ b[b[0]] = swap;
+ }
+ return 0;
+}
+
+#if 1
+
+/* Quicksort + Insertion sort for small arrays */
+
+#define SMALL 8
+#define SWAP(a,b) itmp=(a);(a)=(b);(b)=itmp
+
+void
+wt_idl_sort( ID *ids, ID *tmp )
+{
+ int *istack = (int *)tmp; /* Private stack, not used by caller */
+ int i,j,k,l,ir,jstack;
+ ID a, itmp;
+
+ if ( WT_IDL_IS_RANGE( ids ))
+ return;
+
+ ir = ids[0];
+ l = 1;
+ jstack = 0;
+ for(;;) {
+ if (ir - l < SMALL) { /* Insertion sort */
+ for (j=l+1;j<=ir;j++) {
+ a = ids[j];
+ for (i=j-1;i>=1;i--) {
+ if (ids[i] <= a) break;
+ ids[i+1] = ids[i];
+ }
+ ids[i+1] = a;
+ }
+ if (jstack == 0) break;
+ ir = istack[jstack--];
+ l = istack[jstack--];
+ } else {
+ k = (l + ir) >> 1; /* Choose median of left, center, right */
+ SWAP(ids[k], ids[l+1]);
+ if (ids[l] > ids[ir]) {
+ SWAP(ids[l], ids[ir]);
+ }
+ if (ids[l+1] > ids[ir]) {
+ SWAP(ids[l+1], ids[ir]);
+ }
+ if (ids[l] > ids[l+1]) {
+ SWAP(ids[l], ids[l+1]);
+ }
+ i = l+1;
+ j = ir;
+ a = ids[l+1];
+ for(;;) {
+ do i++; while(ids[i] < a);
+ do j--; while(ids[j] > a);
+ if (j < i) break;
+ SWAP(ids[i],ids[j]);
+ }
+ ids[l+1] = ids[j];
+ ids[j] = a;
+ jstack += 2;
+ if (ir-i+1 >= j-l) {
+ istack[jstack] = ir;
+ istack[jstack-1] = i;
+ ir = j-1;
+ } else {
+ istack[jstack] = j-1;
+ istack[jstack-1] = l;
+ l = i;
+ }
+ }
+ }
+}
+
+#else
+
+/* 8 bit Radix sort + insertion sort
+ *
+ * based on code from http://www.cubic.org/docs/radix.htm
+ * with improvements by ebackes@symas.com and hyc@symas.com
+ *
+ * This code is O(n) but has a relatively high constant factor. For lists
+ * up to ~50 Quicksort is slightly faster; up to ~100 they are even.
+ * Much faster than quicksort for lists longer than ~100. Insertion
+ * sort is actually superior for lists <50.
+ */
+
+#define BUCKETS (1<<8)
+#define SMALL 50
+
+void
+wt_idl_sort( ID *ids, ID *tmp )
+{
+ int count, soft_limit, phase = 0, size = ids[0];
+ ID *idls[2];
+ unsigned char *maxv = (unsigned char *)&ids[size];
+
+ if ( WT_IDL_IS_RANGE( ids ))
+ return;
+
+ /* Use insertion sort for small lists */
+ if ( size <= SMALL ) {
+ int i,j;
+ ID a;
+
+ for (j=1;j<=size;j++) {
+ a = ids[j];
+ for (i=j-1;i>=1;i--) {
+ if (ids[i] <= a) break;
+ ids[i+1] = ids[i];
+ }
+ ids[i+1] = a;
+ }
+ return;
+ }
+
+ tmp[0] = size;
+ idls[0] = ids;
+ idls[1] = tmp;
+
+#if BYTE_ORDER == BIG_ENDIAN
+ for (soft_limit = 0; !maxv[soft_limit]; soft_limit++);
+#else
+ for (soft_limit = sizeof(ID)-1; !maxv[soft_limit]; soft_limit--);
+#endif
+
+ for (
+#if BYTE_ORDER == BIG_ENDIAN
+ count = sizeof(ID)-1; count >= soft_limit; --count
+#else
+ count = 0; count <= soft_limit; ++count
+#endif
+ ) {
+ unsigned int num[BUCKETS], * np, n, sum;
+ int i;
+ ID *sp, *source, *dest;
+ unsigned char *bp, *source_start;
+
+ source = idls[phase]+1;
+ dest = idls[phase^1]+1;
+ source_start = ((unsigned char *) source) + count;
+
+ np = num;
+ for ( i = BUCKETS; i > 0; --i ) *np++ = 0;
+
+ /* count occurrences of every byte value */
+ bp = source_start;
+ for ( i = size; i > 0; --i, bp += sizeof(ID) )
+ num[*bp]++;
+
+ /* transform count into index by summing elements and storing
+ * into same array
+ */
+ sum = 0;
+ np = num;
+ for ( i = BUCKETS; i > 0; --i ) {
+ n = *np;
+ *np++ = sum;
+ sum += n;
+ }
+
+ /* fill dest with the right values in the right place */
+ bp = source_start;
+ sp = source;
+ for ( i = size; i > 0; --i, bp += sizeof(ID) ) {
+ np = num + *bp;
+ dest[*np] = *sp++;
+ ++(*np);
+ }
+ phase ^= 1;
+ }
+
+ /* copy back from temp if needed */
+ if ( phase ) {
+ ids++; tmp++;
+ for ( count = 0; count < size; ++count )
+ *ids++ = *tmp++;
+ }
+}
+#endif /* Quick vs Radix */
+
diff --git a/servers/slapd/back-wt/idl.h b/servers/slapd/back-wt/idl.h
new file mode 100644
index 0000000..d323e54
--- /dev/null
+++ b/servers/slapd/back-wt/idl.h
@@ -0,0 +1,80 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#ifndef _WT_IDL_H_
+#define _WT_IDL_H_
+
+/* IDL sizes - likely should be even bigger
+ * limiting factors: sizeof(ID), thread stack size
+ */
+#define WT_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
+#define WT_IDL_DB_SIZE (1<<WT_IDL_LOGN)
+#define WT_IDL_UM_SIZE (1<<(WT_IDL_LOGN+1))
+#define WT_IDL_UM_SIZEOF (WT_IDL_UM_SIZE * sizeof(ID))
+
+#define WT_IDL_DB_MAX (WT_IDL_DB_SIZE-1)
+
+#define WT_IDL_UM_MAX (WT_IDL_UM_SIZE-1)
+
+#define WT_IDL_IS_RANGE(ids) ((ids)[0] == NOID)
+#define WT_IDL_RANGE_SIZE (3)
+#define WT_IDL_RANGE_SIZEOF (WT_IDL_RANGE_SIZE * sizeof(ID))
+#define WT_IDL_SIZEOF(ids) ((WT_IDL_IS_RANGE(ids) \
+ ? WT_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(ID))
+
+#define WT_IDL_RANGE_FIRST(ids) ((ids)[1])
+#define WT_IDL_RANGE_LAST(ids) ((ids)[2])
+
+#define WT_IDL_RANGE( ids, f, l ) \
+ do { \
+ (ids)[0] = NOID; \
+ (ids)[1] = (f); \
+ (ids)[2] = (l); \
+ } while(0)
+
+#define WT_IDL_ZERO(ids) \
+ do { \
+ (ids)[0] = 0; \
+ (ids)[1] = 0; \
+ (ids)[2] = 0; \
+ } while(0)
+
+#define WT_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
+#define WT_IDL_IS_ALL( range, ids ) ( (ids)[0] == NOID \
+ && (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] )
+
+#define WT_IDL_CPY( dst, src ) (AC_MEMCPY( dst, src, WT_IDL_SIZEOF( src ) ))
+
+#define WT_IDL_ID( wi, ids, id ) WT_IDL_RANGE( ids, id, ((wi)->wi_lastid) )
+#define WT_IDL_ALL( wi, ids ) WT_IDL_RANGE( ids, 1, ((wi)->wi_lastid) )
+
+#define WT_IDL_FIRST( ids ) ( (ids)[1] )
+#define WT_IDL_LLAST( ids ) ( (ids)[(ids)[0]] )
+#define WT_IDL_LAST( ids ) ( WT_IDL_IS_RANGE(ids) \
+ ? (ids)[2] : (ids)[(ids)[0]] )
+
+#define WT_IDL_N( ids ) ( WT_IDL_IS_RANGE(ids) \
+ ? ((ids)[2]-(ids)[1])+1 : (ids)[0] )
+
+LDAP_BEGIN_DECL
+LDAP_END_DECL
+
+#endif
diff --git a/servers/slapd/back-wt/index.c b/servers/slapd/back-wt/index.c
new file mode 100644
index 0000000..3301977
--- /dev/null
+++ b/servers/slapd/back-wt/index.c
@@ -0,0 +1,423 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+static char presence_keyval[] = {0,0};
+static struct berval presence_key = BER_BVC(presence_keyval);
+
+AttrInfo *wt_index_mask(
+ Backend *be,
+ AttributeDescription *desc,
+ struct berval *atname )
+{
+ AttributeType *at;
+ AttrInfo *ai = wt_attr_mask( be->be_private, desc );
+
+ if( ai ) {
+ *atname = desc->ad_cname;
+ return ai;
+ }
+
+ /* If there is a tagging option, did we ever index the base
+ * type? If so, check for mask, otherwise it's not there.
+ */
+ if( slap_ad_is_tagged( desc ) && desc != desc->ad_type->sat_ad ) {
+ /* has tagging option */
+ ai = wt_attr_mask( be->be_private, desc->ad_type->sat_ad );
+
+ if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOTAGS ) ) {
+ *atname = desc->ad_type->sat_cname;
+ return ai;
+ }
+ }
+
+ /* see if supertype defined mask for its subtypes */
+ for( at = desc->ad_type; at != NULL ; at = at->sat_sup ) {
+ /* If no AD, we've never indexed this type */
+ if ( !at->sat_ad ) continue;
+
+ ai = wt_attr_mask( be->be_private, at->sat_ad );
+
+ if ( ai && !( ai->ai_indexmask & SLAP_INDEX_NOSUBTYPES ) ) {
+ *atname = at->sat_cname;
+ return ai;
+ }
+ }
+
+ return 0;
+}
+
+/* This function is only called when evaluating search filters.
+ */
+int wt_index_param(
+ Backend *be,
+ AttributeDescription *desc,
+ int ftype,
+ slap_mask_t *maskp,
+ struct berval *prefixp )
+{
+ AttrInfo *ai;
+ slap_mask_t mask, type = 0;
+
+ ai = wt_index_mask( be, desc, prefixp );
+
+ if ( !ai ) {
+ /* TODO: add monitor */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ mask = ai->ai_indexmask;
+
+ switch( ftype ) {
+ case LDAP_FILTER_PRESENT:
+ type = SLAP_INDEX_PRESENT;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) {
+ *prefixp = presence_key;
+ *maskp = mask;
+ return LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_FILTER_APPROX:
+ type = SLAP_INDEX_APPROX;
+ if ( desc->ad_type->sat_approx ) {
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) {
+ *maskp = mask;
+ return LDAP_SUCCESS;
+ }
+ break;
+ }
+
+ /* Use EQUALITY rule and index for approximate match */
+ /* fall thru */
+
+ case LDAP_FILTER_EQUALITY:
+ type = SLAP_INDEX_EQUALITY;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) {
+ *maskp = mask;
+ return LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ type = SLAP_INDEX_SUBSTR;
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) {
+ *maskp = mask;
+ return LDAP_SUCCESS;
+ }
+ break;
+
+ default:
+ return LDAP_OTHER;
+ }
+
+ /* TODO: add monitor index */
+ return LDAP_INAPPROPRIATE_MATCHING;
+}
+
+static int indexer(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeDescription *ad,
+ struct berval *atname,
+ BerVarray vals,
+ ID id,
+ int opid,
+ slap_mask_t mask )
+{
+ int rc = LDAP_SUCCESS, i;
+ struct berval *keys;
+ WT_CURSOR *cursor = NULL;
+ assert( mask != 0 );
+
+ cursor = wt_index_open(wc, atname, 1);
+ if( !cursor ) {
+ Debug( LDAP_DEBUG_ANY,
+ "indexer: open index cursor failed: %s\n",
+ atname->bv_val );
+ goto done;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_PRESENT ) ) {
+ rc = wt_key_change( op->o_bd, cursor, &presence_key, id, opid );
+ if( rc ) {
+ goto done;
+ }
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_EQUALITY ) ) {
+ rc = ad->ad_type->sat_equality->smr_indexer(
+ LDAP_FILTER_EQUALITY,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_equality,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ for( i=0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_change( op->o_bd, cursor, &keys[i], id, opid );
+ if( rc ) {
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ goto done;
+ }
+ }
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ }
+ rc = LDAP_SUCCESS;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_APPROX ) ) {
+ rc = ad->ad_type->sat_approx->smr_indexer(
+ LDAP_FILTER_APPROX,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_approx,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ for( i=0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_change( op->o_bd, cursor, &keys[i], id, opid );
+ if( rc ) {
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ goto done;
+ }
+ }
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ }
+
+ rc = LDAP_SUCCESS;
+ }
+
+ if( IS_SLAP_INDEX( mask, SLAP_INDEX_SUBSTR ) ) {
+ rc = ad->ad_type->sat_substr->smr_indexer(
+ LDAP_FILTER_SUBSTRINGS,
+ mask,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_substr,
+ atname, vals, &keys, op->o_tmpmemctx );
+
+ if( rc == LDAP_SUCCESS && keys != NULL ) {
+ for( i=0; keys[i].bv_val != NULL; i++ ) {
+ rc = wt_key_change( op->o_bd, cursor, &keys[i], id, opid );
+ if( rc ) {
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ goto done;
+ }
+ }
+ ber_bvarray_free_x( keys, op->o_tmpmemctx );
+ }
+
+ rc = LDAP_SUCCESS;
+ }
+
+done:
+ cursor->close(cursor);
+ return rc;
+}
+
+static int index_at_values(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeDescription *ad,
+ AttributeType *type,
+ struct berval *tags,
+ BerVarray vals,
+ ID id,
+ int opid )
+{
+ int rc = LDAP_SUCCESS;
+ slap_mask_t mask = 0;
+ int ixop = opid;
+ AttrInfo *ai = NULL;
+
+ if ( opid == WT_INDEX_UPDATE_OP )
+ ixop = SLAP_INDEX_ADD_OP;
+
+ if( type->sat_sup ) {
+ /* recurse */
+ rc = index_at_values( op, wc, NULL,
+ type->sat_sup, tags,
+ vals, id, opid );
+
+ if( rc ) return rc;
+ }
+
+ /* If this type has no AD, we've never used it before */
+ if( type->sat_ad ) {
+ ai = wt_attr_mask( op->o_bd->be_private, type->sat_ad );
+ if ( ai ) {
+ #ifdef LDAP_COMP_MATCH
+ /* component indexing */
+ if ( ai->ai_cr ) {
+ ComponentReference *cr;
+ for( cr = ai->ai_cr ; cr ; cr = cr->cr_next ) {
+ rc = indexer( op, wc, cr->cr_ad, &type->sat_cname,
+ cr->cr_nvals, id, ixop,
+ cr->cr_indexmask );
+ }
+ }
+ #endif
+ ad = type->sat_ad;
+ /* If we're updating the index, just set the new bits that aren't
+ * already in the old mask.
+ */
+ if ( opid == WT_INDEX_UPDATE_OP )
+ mask = ai->ai_newmask & ~ai->ai_indexmask;
+ else
+ /* For regular updates, if there is a newmask use it. Otherwise
+ * just use the old mask.
+ */
+ mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
+ if( mask ) {
+ rc = indexer( op, wc, ad, &type->sat_cname,
+ vals, id, ixop, mask );
+ if( rc ) return rc;
+ }
+ }
+ }
+
+ if( tags->bv_len ) {
+ AttributeDescription *desc;
+
+ desc = ad_find_tags( type, tags );
+ if( desc ) {
+ ai = wt_attr_mask( op->o_bd->be_private, desc );
+
+ if( ai ) {
+ if ( opid == WT_INDEX_UPDATE_OP )
+ mask = ai->ai_newmask & ~ai->ai_indexmask;
+ else
+ mask = ai->ai_newmask ? ai->ai_newmask : ai->ai_indexmask;
+ if ( mask ) {
+ rc = indexer( op, wc, desc, &desc->ad_cname,
+ vals, id, ixop, mask );
+
+ if( rc ) {
+ return rc;
+ }
+ }
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int wt_index_values(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeDescription *desc,
+ BerVarray vals,
+ ID id,
+ int opid )
+{
+ int rc;
+
+ /* Never index ID 0 */
+ if ( id == 0 )
+ return 0;
+
+ rc = index_at_values( op, wc, desc,
+ desc->ad_type, &desc->ad_tags,
+ vals, id, opid );
+
+ return rc;
+}
+
+int
+wt_index_entry( Operation *op, wt_ctx *wc, int opid, Entry *e )
+{
+ int rc;
+ Attribute *ap = e->e_attrs;
+
+ if ( e->e_id == 0 )
+ return 0;
+
+ Debug( LDAP_DEBUG_TRACE, "=> index_entry_%s( %ld, \"%s\" )\n",
+ opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
+ (long) e->e_id, e->e_dn ? e->e_dn : "" );
+
+ for ( ; ap != NULL; ap = ap->a_next ) {
+ rc = wt_index_values( op, wc, ap->a_desc,
+ ap->a_nvals, e->e_id, opid );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= index_entry_%s( %ld, \"%s\" ) failure\n",
+ opid == SLAP_INDEX_ADD_OP ? "add" : "del",
+ (long) e->e_id, e->e_dn );
+ return rc;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= index_entry_%s( %ld, \"%s\" ) success\n",
+ opid == SLAP_INDEX_DELETE_OP ? "del" : "add",
+ (long) e->e_id, e->e_dn ? e->e_dn : "" );
+ return 0;
+}
+
+WT_CURSOR *
+wt_index_open(wt_ctx *wc, struct berval *name, int create)
+{
+ WT_CURSOR *cursor = NULL;
+ WT_SESSION *session = wc->session;
+ char uri[1024];
+ int rc;
+
+ snprintf(uri, sizeof(uri), "table:%s", name->bv_val);
+
+ rc = session->open_cursor(session, uri, NULL, "overwrite=false", &cursor);
+ if (rc == ENOENT && create) {
+ rc = session->create(session, uri,
+ "key_format=uQ,"
+ "value_format=x,"
+ "columns=(key, id, none)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_index_open: table \"%s\": "
+ "cannot create index table: %s (%d)\n",
+ uri, wiredtiger_strerror(rc), rc);
+ return NULL;
+ }
+ rc = session->open_cursor(session, uri, NULL,
+ "overwrite=false", &cursor);
+ }
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_index_open: table \"%s\": "
+ ": open cursor failed: %s (%d)\n",
+ uri, wiredtiger_strerror(rc), rc);
+ return NULL;
+ }
+ return cursor;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/init.c b/servers/slapd/back-wt/init.c
new file mode 100644
index 0000000..cc0c53a
--- /dev/null
+++ b/servers/slapd/back-wt/init.c
@@ -0,0 +1,385 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+static int
+wt_db_init( BackendDB *be, ConfigReply *cr )
+{
+ struct wt_info *wi;
+
+ Debug( LDAP_DEBUG_TRACE, "wt_db_init: Initializing wt backend\n" );
+
+ /* allocate backend-database-specific stuff */
+ wi = ch_calloc( 1, sizeof(struct wt_info) );
+ wi->wi_home = ch_strdup( SLAPD_DEFAULT_DB_DIR );
+ wi->wi_config = ch_calloc( 1, WT_CONFIG_MAX + 1);
+ if ( slapMode & SLAP_TOOL_READONLY ) {
+ strcpy(wi->wi_config, "readonly");
+ } else {
+ strcpy(wi->wi_config, "create");
+ }
+ wi->wi_lastid = 0;
+ wi->wi_search_stack_depth = DEFAULT_SEARCH_STACK_DEPTH;
+ wi->wi_search_stack = NULL;
+ wi->wi_flags = WT_USE_IDLCACHE;
+
+ be->be_private = wi;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+wt_db_open( BackendDB *be, ConfigReply *cr )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ int rc;
+ struct stat st;
+ WT_SESSION *session = NULL;
+ WT_SESSION *cache_session = NULL;
+
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "wt_db_open: need suffix.\n" );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_db_open: \"%s\", home=%s, config=%s\n",
+ be->be_suffix[0].bv_val, wi->wi_home, wi->wi_config );
+
+ /* Check existence of home. Any error means trouble */
+ rc = stat( wi->wi_home, &st );
+ if( rc ) {
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot access database directory \"%s\" (%d).\n",
+ be->be_suffix[0].bv_val, wi->wi_home, saved_errno );
+ return -1;
+ }
+
+ /* back-wt is always clean */
+ be->be_flags |= SLAP_DBFLAG_CLEAN;
+
+ /* Open and create database */
+ rc = wiredtiger_open(wi->wi_home, NULL,
+ wi->wi_config, &wi->wi_conn);
+ if( rc ) {
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot open database \"%s\" (%d).\n",
+ be->be_suffix[0].bv_val, wi->wi_home, saved_errno );
+ return -1;
+ }
+
+ rc = wi->wi_conn->open_session(wi->wi_conn, NULL, NULL, &session);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot open session: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ if ( slapMode & SLAP_TOOL_READONLY ) {
+ goto readonly;
+ }
+
+ /* checking for obsolete table */
+ rc = session->verify(session, WT_INDEX_REVDN, NULL);
+ if ( !rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "incompatible wiredtiger table, please restore from LDIF.\n",
+ be->be_suffix[0].bv_val );
+ return -1;
+ }
+
+ /* create tables and indexes */
+ rc = session->create(session,
+ WT_TABLE_ID2ENTRY,
+ "key_format=Q,"
+ "value_format=Su,"
+ "columns=(id,dn,entry)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create entry table: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ rc = session->create(session,
+ WT_TABLE_DN2ID,
+ "key_format=S,"
+ "value_format=SQQ,"
+ "columns=(revdn,ndn,id,pid)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create entry table: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ /* not using dn2id index for id2entry table */
+ rc = session->create(session, WT_INDEX_DN, "columns=(dn)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create dn index: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ rc = session->create(session, WT_INDEX_PID, "columns=(pid)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create pid index: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ rc = session->create(session, WT_INDEX_NDN, "columns=(ndn)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create ndn index: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ /* open in-memory database for idlcache */
+ rc = wiredtiger_open(be->be_suffix[0].bv_val, NULL,
+ "in_memory=true", &wi->wi_cache);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot open database for cache (%s).\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ rc = wi->wi_cache->open_session(wi->wi_cache, NULL, NULL, &cache_session);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot open session for cache: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+ rc = cache_session->create(cache_session,
+ WT_TABLE_IDLCACHE,
+ "key_format=Sb,"
+ "value_format=u,"
+ "columns=(ndn,scope,idl)");
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_open: database \"%s\": "
+ "cannot create idlcache table: \"%s\"\n",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc) );
+ return -1;
+ }
+
+readonly:
+ rc = wt_last_id( be, session, &wi->wi_lastid);
+ if (rc) {
+ snprintf( cr->msg, sizeof(cr->msg), "database \"%s\": "
+ "last_id() failed: %s(%d).",
+ be->be_suffix[0].bv_val, wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY, "wt_db_open: %s\n", cr->msg );
+ return rc;
+ }
+
+ if (session) {
+ session->close(session, NULL);
+ }
+ if (cache_session) {
+ cache_session->close(cache_session, NULL);
+ }
+
+ wi->wi_flags |= WT_IS_OPEN;
+ return LDAP_SUCCESS;
+}
+
+static int
+wt_db_close( BackendDB *be, ConfigReply *cr )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ int rc;
+
+ if ( wi->wi_cache ) {
+ rc = wi->wi_cache->close(wi->wi_cache, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_close: cannot close cache database (%d).\n", rc );
+ return -1;
+ }
+ }
+
+ if ( wi->wi_conn ) {
+ rc = wi->wi_conn->close(wi->wi_conn, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_db_close: cannot close database (%d).\n", rc );
+ return -1;
+ }
+ wi->wi_flags &= ~WT_IS_OPEN;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+wt_db_destroy( Backend *be, ConfigReply *cr )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+
+ if( wi->wi_home ) {
+ ch_free( wi->wi_home );
+ wi->wi_home = NULL;
+ }
+
+ if( wi->wi_config ) {
+ ch_free( wi->wi_config );
+ wi->wi_config = NULL;
+ }
+
+ wt_attr_index_destroy( wi );
+ ch_free( wi );
+ be->be_private = NULL;
+
+ return LDAP_SUCCESS;
+}
+
+int
+wt_back_initialize( BackendInfo *bi )
+{
+ static const char *controls[] = {
+ LDAP_CONTROL_ASSERT,
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_NOOP,
+ LDAP_CONTROL_PAGEDRESULTS,
+ LDAP_CONTROL_PRE_READ,
+ LDAP_CONTROL_POST_READ,
+ LDAP_CONTROL_SUBENTRIES,
+ LDAP_CONTROL_X_PERMISSIVE_MODIFY,
+#ifdef LDAP_X_TXN
+ LDAP_CONTROL_X_TXN_SPEC,
+#endif
+ NULL
+ };
+
+ /* initialize the database system */
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_back_initialize: initialize WiredTiger backend\n" );
+
+ bi->bi_flags |=
+ SLAP_BFLAG_INCREMENT |
+ SLAP_BFLAG_SUBENTRIES |
+ SLAP_BFLAG_ALIASES |
+ SLAP_BFLAG_REFERRALS;
+
+ bi->bi_controls = (char **)controls;
+
+ /* version check */
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_back_initialize: %s\n",
+ wiredtiger_version(NULL, NULL, NULL) );
+
+ bi->bi_open = 0;
+ bi->bi_close = 0;
+ bi->bi_config = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = wt_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = wt_db_open;
+ bi->bi_db_close = wt_db_close;
+ bi->bi_db_destroy = wt_db_destroy;
+
+ bi->bi_op_add = wt_add;
+ bi->bi_op_bind = wt_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = wt_search;
+ bi->bi_op_compare = wt_compare;
+ bi->bi_op_modify = wt_modify;
+ bi->bi_op_modrdn = wt_modrdn;
+ bi->bi_op_delete = wt_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = wt_extended;
+#ifdef LDAP_X_TXN
+ bi->bi_op_txn = 0;
+#endif
+
+ bi->bi_chk_referrals = 0;
+ bi->bi_operational = wt_operational;
+
+ bi->bi_has_subordinates = wt_hasSubordinates;
+ bi->bi_entry_release_rw = wt_entry_release;
+ bi->bi_entry_get_rw = wt_entry_get;
+
+ bi->bi_tool_entry_open = wt_tool_entry_open;
+ bi->bi_tool_entry_close = wt_tool_entry_close;
+ bi->bi_tool_entry_first = backend_tool_entry_first;
+ bi->bi_tool_entry_first_x = wt_tool_entry_first_x;
+ bi->bi_tool_entry_next = wt_tool_entry_next;
+ bi->bi_tool_entry_get = wt_tool_entry_get;
+ bi->bi_tool_entry_put = wt_tool_entry_put;
+ bi->bi_tool_entry_reindex = wt_tool_entry_reindex;
+ bi->bi_tool_sync = 0;
+ bi->bi_tool_dn2id_get = wt_tool_dn2id_get;
+ bi->bi_tool_entry_modify = wt_tool_entry_modify;
+
+#if LDAP_VENDOR_VERSION_MINOR == X || LDAP_VENDOR_VERSION_MINOR >= 5
+ bi->bi_tool_entry_delete = wt_tool_entry_delete;
+#endif
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ return wt_back_init_cf( bi );
+}
+
+#if SLAPD_WT == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( wt )
+
+#endif /* SLAPD_WT == SLAPD_MOD_DYNAMIC */
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/key.c b/servers/slapd/back-wt/key.c
new file mode 100644
index 0000000..ea86829
--- /dev/null
+++ b/servers/slapd/back-wt/key.c
@@ -0,0 +1,162 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+#include "idl.h"
+
+/* read a key */
+int
+wt_key_read(
+ Backend *be,
+ WT_CURSOR *cursor,
+ struct berval *bkey,
+ ID *ids,
+ WT_CURSOR **saved_cursor,
+ int get_flag
+ )
+{
+ int rc;
+ WT_ITEM key;
+ int exact;
+ WT_ITEM key2;
+ ID id;
+ int comp;
+ long scanned = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "=> key_read\n" );
+
+ WT_IDL_ZERO(ids);
+ bv2ITEM(bkey, &key);
+ cursor->set_key(cursor, &key, 0);
+ rc = cursor->search_near(cursor, &exact);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rc = LDAP_SUCCESS;
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_key_read: search_near failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc);
+ goto done;
+ }
+ do {
+ scanned++;
+ rc = cursor->get_key(cursor, &key2, &id);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_key_read: get_key failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ break;
+ }
+ comp = 0;
+ if (key.size != key2.size ||
+ (comp = memcmp(key2.data, key.data, key.size))) {
+ if(comp > 0){
+ break;
+ }
+ if(exact < 0){
+ rc = cursor->next(cursor);
+ if (rc) {
+ break;
+ }else{
+ continue;
+ }
+ }
+ break;
+ }
+ exact = 0;
+ wt_idl_append_one(ids, id);
+ rc = cursor->next(cursor);
+ } while(rc == 0);
+
+ if ( rc == WT_NOTFOUND && exact == 0 ) {
+ rc = LDAP_SUCCESS;
+ }
+
+done:
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "<= wt_key_read: failed (%d) %ld scanned\n",
+ rc, scanned );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<= wt_key_read %ld candidates %ld scanned\n",
+ (long) WT_IDL_N(ids), scanned );
+ }
+
+ return rc;
+}
+
+/* Add or remove stuff from index files */
+int
+wt_key_change(
+ Backend *be,
+ WT_CURSOR *cursor,
+ struct berval *k,
+ ID id,
+ int op
+)
+{
+ int rc;
+ WT_ITEM item;
+
+ Debug( LDAP_DEBUG_TRACE, "=> key_change(%s,%lx)\n",
+ op == SLAP_INDEX_ADD_OP ? "ADD":"DELETE", (long) id );
+
+ bv2ITEM(k, &item);
+ cursor->set_key(cursor, &item, id);
+ cursor->set_value(cursor, NULL);
+
+ if (op == SLAP_INDEX_ADD_OP) {
+ /* Add values */
+ rc = cursor->insert(cursor);
+ if ( rc == WT_DUPLICATE_KEY ) rc = 0;
+ } else {
+ /* Delete values */
+ rc = cursor->remove(cursor);
+ if ( rc == WT_NOTFOUND ) rc = 0;
+ }
+ if( rc ) {
+ if ( rc != WT_ROLLBACK ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_key_change: error: %s (%d)\n",
+ wiredtiger_strerror(rc), rc);
+ }
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= key_change %d\n", rc );
+
+ return rc;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/modify.c b/servers/slapd/back-wt/modify.c
new file mode 100644
index 0000000..d78c438
--- /dev/null
+++ b/servers/slapd/back-wt/modify.c
@@ -0,0 +1,714 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+B * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+static struct berval scbva[] = {
+ BER_BVC("glue"),
+ BER_BVNULL
+};
+
+static void
+wt_modify_idxflags(
+ Operation *op,
+ AttributeDescription *desc,
+ int got_delete,
+ Attribute *newattrs,
+ Attribute *oldattrs )
+{
+ struct berval ix_at;
+ AttrInfo *ai;
+
+ /* check if modified attribute was indexed
+ * but not in case of NOOP... */
+ ai = wt_index_mask( op->o_bd, desc, &ix_at );
+ if ( ai ) {
+ if ( got_delete ) {
+ Attribute *ap;
+ struct berval ix2;
+
+ ap = attr_find( oldattrs, desc );
+ if ( ap ) ap->a_flags |= SLAP_ATTR_IXDEL;
+
+ /* Find all other attrs that index to same slot */
+ for ( ap = newattrs; ap; ap = ap->a_next ) {
+ ai = wt_index_mask( op->o_bd, ap->a_desc, &ix2 );
+ if ( ai && ix2.bv_val == ix_at.bv_val )
+ ap->a_flags |= SLAP_ATTR_IXADD;
+ }
+
+ } else {
+ Attribute *ap;
+
+ ap = attr_find( newattrs, desc );
+ if ( ap ) ap->a_flags |= SLAP_ATTR_IXADD;
+ }
+ }
+}
+
+int wt_modify_internal(
+ Operation *op,
+ wt_ctx *wc,
+ Modifications *modlist,
+ Entry *e,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ int rc, err;
+ Modification *mod;
+ Modifications *ml;
+ Attribute *save_attrs;
+ Attribute *ap;
+ int glue_attr_delete = 0;
+ int got_delete;
+
+ Debug( LDAP_DEBUG_TRACE, "wt_modify_internal: 0x%08lx: %s\n",
+ e->e_id, e->e_dn );
+
+ if ( !acl_check_modlist( op, e, modlist )) {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ /* save_attrs will be disposed of by caller */
+ save_attrs = e->e_attrs;
+ e->e_attrs = attrs_dup( e->e_attrs );
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ int match;
+ mod = &ml->sml_mod;
+ switch( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ if ( mod->sm_desc == slap_schema.si_ad_structuralObjectClass ) {
+ value_match( &match, slap_schema.si_ad_structuralObjectClass,
+ slap_schema.si_ad_structuralObjectClass->
+ ad_type->sat_equality,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &mod->sm_values[0], &scbva[0], text );
+ if ( !match ) glue_attr_delete = 1;
+ }
+ }
+ if ( glue_attr_delete )
+ break;
+ }
+
+ if ( glue_attr_delete ) {
+ Attribute **app = &e->e_attrs;
+ while ( *app != NULL ) {
+ if ( !is_at_operational( (*app)->a_desc->ad_type )) {
+ Attribute *save = *app;
+ *app = (*app)->a_next;
+ attr_free( save );
+ continue;
+ }
+ app = &(*app)->a_next;
+ }
+ }
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ mod = &ml->sml_mod;
+ got_delete = 0;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ Debug(LDAP_DEBUG_ARGS,
+ "wt_modify_internal: add %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_add_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "wt_modify_internal: %d %s\n",
+ err, *text );
+ }
+ break;
+
+ case LDAP_MOD_DELETE:
+ if ( glue_attr_delete ) {
+ err = LDAP_SUCCESS;
+ break;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: delete %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_delete_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS,
+ "wt_modify_internal: %d %s\n", err, *text );
+ } else {
+ got_delete = 1;
+ }
+ break;
+
+ case LDAP_MOD_REPLACE:
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: replace %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_replace_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS,
+ "wt_modify_internal: %d %s\n", err, *text );
+ } else {
+ got_delete = 1;
+ }
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: increment %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ err = modify_increment_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+ if( err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: %d %s\n", err, *text );
+ } else {
+ got_delete = 1;
+ }
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: softadd %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_add_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_ADD;
+
+ err = modify_add_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+
+ mod->sm_op = SLAP_MOD_SOFTADD;
+
+ if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ err = LDAP_SUCCESS;
+ }
+
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "wt_modify_internal: %d %s\n",
+ err, *text );
+ }
+ break;
+
+ case SLAP_MOD_SOFTDEL:
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_modify_internal: softdel %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_delete_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_DELETE;
+
+ err = modify_delete_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+
+ mod->sm_op = SLAP_MOD_SOFTDEL;
+
+ if ( err == LDAP_SUCCESS ) {
+ got_delete = 1;
+ } else if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+ err = LDAP_SUCCESS;
+ }
+
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "wt_modify_internal: %d %s\n",
+ err, *text );
+ }
+ break;
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( attr_find( e->e_attrs, mod->sm_desc ) != NULL ) {
+ /* skip */
+ err = LDAP_SUCCESS;
+ break;
+ }
+
+ Debug(LDAP_DEBUG_ARGS,
+ "wt_modify_internal: add_if_not_present %s\n",
+ mod->sm_desc->ad_cname.bv_val );
+ /* Avoid problems in index_add_mods()
+ * We need to add index if necessary.
+ */
+ mod->sm_op = LDAP_MOD_ADD;
+
+ err = modify_add_values( e, mod, get_permissiveModify(op),
+ text, textbuf, textlen );
+
+ mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+
+ if( err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ARGS, "wt_modify_internal: %d %s\n",
+ err, *text );
+ }
+ break;
+
+ default:
+ Debug(LDAP_DEBUG_ANY, "wt_modify_internal: invalid op %d\n",
+ mod->sm_op );
+ *text = "Invalid modify operation";
+ err = LDAP_OTHER;
+ Debug(LDAP_DEBUG_ARGS, "wt_modify_internal: %d %s\n",
+ err, *text );
+ }
+
+ if ( err != LDAP_SUCCESS ) {
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ /* unlock entry, delete from cache */
+ return err;
+ }
+
+ /* If objectClass was modified, reset the flags */
+ if ( mod->sm_desc == slap_schema.si_ad_objectClass ) {
+ e->e_ocflags = 0;
+ }
+
+ if ( glue_attr_delete ) e->e_ocflags = 0;
+
+
+ /* check if modified attribute was indexed
+ * but not in case of NOOP... */
+ if ( !op->o_noop ) {
+ wt_modify_idxflags( op, mod->sm_desc, got_delete, e->e_attrs, save_attrs );
+ }
+
+ }
+
+ /* check that the entry still obeys the schema */
+ ap = NULL;
+ rc = entry_schema_check( op, e, save_attrs, get_relax(op), 0, &ap,
+ text, textbuf, textlen );
+ if ( rc != LDAP_SUCCESS || op->o_noop ) {
+ attrs_free( e->e_attrs );
+ /* clear the indexing flags */
+ for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
+ ap->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL);
+ }
+ e->e_attrs = save_attrs;
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "entry failed schema check: %s\n", *text );
+ }
+
+ /* if NOOP then silently revert to saved attrs */
+ return rc;
+ }
+
+ /* structuralObjectClass modified! */
+ if ( ap ) {
+ assert( ap->a_desc == slap_schema.si_ad_structuralObjectClass );
+ if ( !op->o_noop ) {
+ wt_modify_idxflags( op, slap_schema.si_ad_structuralObjectClass,
+ 1, e->e_attrs, save_attrs );
+ }
+ }
+
+ /* update the indices of the modified attributes */
+
+ /* start with deleting the old index entries */
+ for ( ap = save_attrs; ap != NULL; ap = ap->a_next ) {
+ if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
+ struct berval *vals;
+ Attribute *a2;
+ ap->a_flags &= ~SLAP_ATTR_IXDEL;
+ a2 = attr_find( e->e_attrs, ap->a_desc );
+ if ( a2 ) {
+ /* need to detect which values were deleted */
+ int i, j;
+ /* let add know there were deletes */
+ if ( a2->a_flags & SLAP_ATTR_IXADD )
+ a2->a_flags |= SLAP_ATTR_IXDEL;
+ vals = op->o_tmpalloc( (ap->a_numvals + 1) *
+ sizeof(struct berval), op->o_tmpmemctx );
+ j = 0;
+ for ( i=0; i < ap->a_numvals; i++ ) {
+ rc = attr_valfind( a2, SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &ap->a_nvals[i], NULL, op->o_tmpmemctx );
+ /* Save deleted values */
+ if ( rc == LDAP_NO_SUCH_ATTRIBUTE )
+ vals[j++] = ap->a_nvals[i];
+ }
+ BER_BVZERO(vals+j);
+ } else {
+ /* attribute was completely deleted */
+ vals = ap->a_nvals;
+ }
+ rc = 0;
+ if ( !BER_BVISNULL( vals )) {
+ rc = wt_index_values( op, wc, ap->a_desc,
+ vals, e->e_id, SLAP_INDEX_DELETE_OP );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: attribute \"%s\" index delete failure\n",
+ op->o_log_prefix, ap->a_desc->ad_cname.bv_val );
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ }
+ }
+ if ( vals != ap->a_nvals )
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ if ( rc ) return rc;
+ }
+ }
+
+ /* add the new index entries */
+ for ( ap = e->e_attrs; ap != NULL; ap = ap->a_next ) {
+ if (ap->a_flags & SLAP_ATTR_IXADD) {
+ ap->a_flags &= ~SLAP_ATTR_IXADD;
+ if ( ap->a_flags & SLAP_ATTR_IXDEL ) {
+ /* if any values were deleted, we must readd index
+ * for all remaining values.
+ */
+ ap->a_flags &= ~SLAP_ATTR_IXDEL;
+ rc = wt_index_values( op, wc, ap->a_desc, ap->a_nvals,
+ e->e_id, SLAP_INDEX_ADD_OP );
+ } else {
+ int found = 0;
+ /* if this was only an add, we only need to index
+ * the added values.
+ */
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ struct berval *vals;
+ if ( ml->sml_desc != ap->a_desc || !ml->sml_numvals )
+ continue;
+ found = 1;
+ switch( ml->sml_op ) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ case LDAP_MOD_INCREMENT:
+ case SLAP_MOD_SOFTADD:
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( ml->sml_op == LDAP_MOD_INCREMENT )
+ vals = ap->a_nvals;
+ else if ( ml->sml_nvalues )
+ vals = ml->sml_nvalues;
+ else
+ vals = ml->sml_values;
+ rc = wt_index_values( op, wc, ap->a_desc,
+ vals, e->e_id, SLAP_INDEX_ADD_OP );
+ break;
+ }
+ if ( rc )
+ break;
+ }
+ /* This attr was affected by a modify of a subtype, so
+ * there was no direct match in the modlist. Just readd
+ * all of its values.
+ */
+ if ( !found ) {
+ rc = wt_index_values( op, wc, ap->a_desc, ap->a_nvals,
+ e->e_id, SLAP_INDEX_ADD_OP );
+ }
+ }
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: attribute \"%s\" index add failure\n",
+ op->o_log_prefix, ap->a_desc->ad_cname.bv_val );
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+int
+wt_modify( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ wt_ctx *wc = NULL;
+ Entry *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ Entry dummy = {0};
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ int rc;
+
+ Debug( LDAP_DEBUG_ARGS, "wt_modify: %s\n", op->o_req_dn.bv_val );
+
+#ifdef LDAP_X_TXN
+ if( op->o_txnSpec && txn_preop( op, rs ))
+ return rs->sr_err;
+#endif
+
+ ctrls[num_ctrls] = NULL;
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY, "wt_modify: wt_ctx_get failed\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ /* Don't touch the opattrs, if this is a contextCSN update
+ * initiated from updatedn */
+ if ( !be_isupdate(op) || !op->orm_modlist || op->orm_modlist->sml_next ||
+ op->orm_modlist->sml_desc != slap_schema.si_ad_contextCSN ) {
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+ }
+
+retry:
+ /* begin transaction */
+ wc->is_begin_transaction = 0;
+ rc = wc->session->begin_transaction(wc->session, "isolation=snapshot");
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modify: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "begin_transaction failed";
+ goto return_results;
+ }
+ wc->is_begin_transaction = 1;
+ Debug( LDAP_DEBUG_TRACE, "wt_modify: session id: %p\n", wc->session );
+
+ /* get entry */
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modify: wt_dn2entry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( rc == WT_NOTFOUND ||
+ ( !manageDSAit && e && is_entry_glue( e ))) {
+ if ( !e ) {
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_modify: wt_dna2entry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ }
+
+ rs->sr_matched = ch_strdup( e->e_dn );
+
+ if ( is_entry_referral( e ) ) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ /* entry is a referral, don't allow modify */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE, "wt_modify: entry is referral\n" );
+
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = e->e_name.bv_val;
+ rs->sr_flags = REP_REF_MUSTBEFREED;
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<=- wt_modify: pre-read failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* Modify the entry */
+ dummy = *e;
+ rs->sr_err = wt_modify_internal( op, wc, op->orm_modlist,
+ &dummy, &rs->sr_text, textbuf, textlen );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+ case WT_ROLLBACK:
+ Debug (LDAP_DEBUG_TRACE, "wt_modify: rollback wt_modify_internal failed.\n" );
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto retry;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_modify: modify failed (%d)\n", rs->sr_err );
+ /* Only free attrs if they were dup'd. */
+ if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+ goto return_results;
+ }
+
+ /* change the entry itself */
+ rs->sr_err = wt_id2entry_update( op, wc, &dummy );
+ switch ( rs->sr_err ) {
+ case 0:
+ break;
+ case WT_ROLLBACK:
+ Debug (LDAP_DEBUG_TRACE, "wt_modify: rollback wt_id2entry_update failed.\n");
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto retry;
+ case LDAP_ADMINLIMIT_EXCEEDED:
+ Debug( LDAP_DEBUG_ANY, "wt_modify: id2entry update failed (%d)\n",
+ rs->sr_err);
+ rs->sr_text = "entry too big";
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_modify: id2entry update failed (%d)\n",
+ rs->sr_err);
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry update failed";
+ goto return_results;
+ }
+
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, &dummy,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_modify: post-read failed!\n");
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ if( op->o_noop ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ goto return_results;
+ }
+
+ /* Only free attrs if they were dup'd. */
+ if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ wc->is_begin_transaction = 0;
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_modify: commit failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit failed";
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modify: updated%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ dummy.e_id, op->o_req_dn.bv_val );
+
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ if( dummy.e_attrs ) {
+ attrs_free( dummy.e_attrs );
+ }
+ send_ldap_result( op, rs );
+
+done:
+ slap_graduate_commit_csn( op );
+
+ if( wc && wc->is_begin_transaction ){
+ Debug( LDAP_DEBUG_TRACE, "wt_modify: rollback transaction\n" );
+ wc->session->rollback_transaction(wc->session, NULL);
+ wc->is_begin_transaction = 0;
+ }
+
+ if( e != NULL ) {
+ wt_entry_return( e );
+ }
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/modrdn.c b/servers/slapd/back-wt/modrdn.c
new file mode 100644
index 0000000..faef948
--- /dev/null
+++ b/servers/slapd/back-wt/modrdn.c
@@ -0,0 +1,552 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_modrdn( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ AttributeDescription *children = slap_schema.si_ad_children;
+ AttributeDescription *entry = slap_schema.si_ad_entry;
+ wt_ctx *wc = NULL;
+ Entry *e = NULL;
+ Entry *p = NULL;
+ Entry *ne = NULL;
+ Entry dummy = {0};
+
+ struct berval p_dn, p_ndn;
+ struct berval new_dn = {0, NULL}, new_ndn = {0, NULL};
+
+ Entry *np = NULL; /* newSuperior Entry */
+ struct berval *np_dn = NULL; /* newSuperior dn */
+ struct berval *np_ndn = NULL; /* newSuperior ndn */
+ struct berval *new_parent_dn = NULL; /* np_dn, p_dn, or NULL */
+
+ int manageDSAit = get_manageDSAit( op );
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ int rc;
+
+ int parent_is_glue = 0;
+ int parent_is_leaf = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==> wt_modrdn(%s -> newrdn=%s - newsup=%s)\n",
+ op->o_req_dn.bv_val,
+ op->oq_modrdn.rs_newrdn.bv_val,
+ op->oq_modrdn.rs_newSup?op->oq_modrdn.rs_newSup->bv_val:"NULL" );
+
+ ctrls[num_ctrls] = NULL;
+
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY, "wt_modrdn: wt_ctx_get failed\n");
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ /* get parent entry */
+ if ( be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ rs->sr_text = "cannot rename suffix entry";
+ goto return_results;
+ } else {
+ dnParent( &op->o_req_ndn, &p_ndn );
+ }
+
+ rc = wt_dn2entry(op->o_bd, wc, &p_ndn, &p);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ Debug( LDAP_DEBUG_ARGS,
+ "<== wt_modrdn: parent does not exist %s\n", p_ndn.bv_val);
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modrdn: wt_dn2entry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ /* check parent for "children" acl */
+ rc = access_allowed( op, p, children, NULL,
+ op->oq_modrdn.rs_newSup == NULL ?
+ ACL_WRITE : ACL_WDEL, NULL );
+
+ if ( !rc ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: no access to parent\n");
+ rs->sr_text = "no write access to old parent's children";
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: wr to children of entry %s OK\n", p_ndn.bv_val );
+
+ if ( p_ndn.bv_val == slap_empty_bv.bv_val ) {
+ p_dn = slap_empty_bv;
+ } else {
+ dnParent( &op->o_req_dn, &p_dn );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: parent dn=%s\n", p_dn.bv_val );
+
+ /* get entry */
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modrdn: wt_dn2entry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ if ( rc == WT_NOTFOUND ||
+ ( !manageDSAit && e && is_entry_glue( e ) )) {
+
+ if ( !e ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "<== wt_modrdn: no such object %s\n", op->o_req_dn.bv_val);
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_modrdn: wt_dn2aentry failed (%d)\n", rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ }
+
+ rs->sr_matched = ch_strdup( e->e_dn );
+
+ if ( is_entry_referral( e ) ) {
+ BerVarray ref = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( ref, &e->e_name,
+ &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ ber_bvarray_free( ref );
+ } else {
+ rs->sr_ref = NULL;
+ }
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ /* check write on old entry */
+ rc = access_allowed( op, e, entry, NULL, ACL_WRITE, NULL );
+ if ( !rc ) {
+ Debug( LDAP_DEBUG_TRACE, "wt_modrdn: no access to entry\n");
+ rs->sr_text = "no write access to old entry";
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ /* Can't do it if we have kids */
+ rc = wt_dn2id_has_children( op, wc, e->e_id );
+ if( rc != WT_NOTFOUND ) {
+ switch( rc ) {
+ case 0:
+ Debug(LDAP_DEBUG_ARGS, "<== wt_modrdn: non-leaf %s\n", op->o_req_dn.bv_val);
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subtree rename not supported";
+ break;
+ default:
+ Debug(LDAP_DEBUG_ARGS,
+ "<== wt_modrdn: has_children failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ }
+ goto return_results;
+ }
+
+ if (!manageDSAit && is_entry_referral( e ) ) {
+ /* parent is a referral, don't allow add */
+ rs->sr_ref = get_entry_referrals( op, e );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: entry %s is referral\n", e->e_dn );
+
+ rs->sr_err = LDAP_REFERRAL,
+ rs->sr_matched = e->e_name.bv_val;
+ send_ldap_result( op, rs );
+
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ new_parent_dn = &p_dn; /* New Parent unless newSuperior given */
+ if ( op->oq_modrdn.rs_newSup != NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: new parent \"%s\" requested...\n",
+ op->oq_modrdn.rs_newSup->bv_val );
+
+ /* newSuperior == oldParent? */
+ if( dn_match( &p_ndn, op->oq_modrdn.rs_nnewSup ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: new parent \"%s\" same as the old parent \"%s\"\n",
+ op->oq_modrdn.rs_newSup->bv_val, p_dn.bv_val );
+ op->oq_modrdn.rs_newSup = NULL; /* ignore newSuperior */
+ }
+ }
+
+ if ( op->oq_modrdn.rs_newSup != NULL ) {
+ if ( op->oq_modrdn.rs_newSup->bv_len ) {
+ np_dn = op->oq_modrdn.rs_newSup;
+ np_ndn = op->oq_modrdn.rs_nnewSup;
+
+ /* newSuperior == oldParent? - checked above */
+ /* newSuperior == entry being moved?, if so ==> ERROR */
+ if ( dnIsSuffix( np_ndn, &e->e_nname )) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "new superior not found";
+ goto return_results;
+ }
+ /* Get Entry with dn=newSuperior. Does newSuperior exist? */
+ rc = wt_dn2entry(op->o_bd, wc, np_ndn, &np);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modrdn: new superior not found: %s\n",
+ np_ndn->bv_val );
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "new superior not found";
+ goto return_results;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modrdn: wt_dn2entry failed %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: wr to new parent OK np=%p, id=%ld\n",
+ (void *) np, (long) np->e_id );
+ rs->sr_err = access_allowed( op, np, children,
+ NULL, ACL_WADD, NULL );
+ if( ! rs->sr_err ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: no wr to newSup children\n" );
+ rs->sr_text = "no write access to new superior's children";
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+ if ( is_entry_alias( np ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: entry is alias\n" );
+ rs->sr_text = "new superior is an alias";
+ rs->sr_err = LDAP_ALIAS_PROBLEM;
+ goto return_results;
+ }
+ if ( is_entry_referral( np ) ) {
+ /* parent is a referral, don't allow add */
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: entry is referral\n" );
+ rs->sr_text = "new superior is a referral";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+ } else {
+ /* no parent, modrdn entry directly under root */
+ /* TODO: */
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: no parent, not implement yet\n" );
+ rs->sr_text = "not implement yet";
+ rs->sr_err = LDAP_OTHER;
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: wr to new parent's children OK\n" );
+ new_parent_dn = np_dn;
+ }
+
+ /* Build target dn and make sure target entry doesn't exist already. */
+ if (!new_dn.bv_val) {
+ build_new_dn( &new_dn, new_parent_dn, &op->oq_modrdn.rs_newrdn, NULL );
+ }
+
+ if (!new_ndn.bv_val) {
+ struct berval bv = {0, NULL};
+ dnNormalize( 0, NULL, NULL, &new_dn, &bv, op->o_tmpmemctx );
+ ber_dupbv( &new_ndn, &bv );
+ /* FIXME: why not call dnNormalize() w/o ctx? */
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: new ndn=%s\n", new_ndn.bv_val );
+
+ /* check new entry */
+ rc = wt_dn2entry(op->o_bd, wc, &new_ndn, &ne);
+ switch( rc ) {
+ case 0:
+ /* Allow rename to same DN */
+ if(e->e_id == ne->e_id){
+ break;
+ }
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ goto return_results;
+ break;
+ case WT_NOTFOUND:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_modrdn: wt_dn2entry failed %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+ goto return_results;
+ }
+
+ assert( op->orr_modlist != NULL );
+
+ if( op->o_preread ) {
+ if( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, e,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: pre-read failed!\n" );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ /* begin transaction */
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "begin_transaction failed";
+ goto return_results;
+ }
+ wc->is_begin_transaction = 1;
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: session id: %p\n", wc->session );
+
+ /* delete old DN */
+ rc = wt_dn2id_delete( op, wc, &e->e_nname);
+ if ( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: delete failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "dn2id delete failed";
+ goto return_results;
+ }
+
+ /* copy the entry, then override some fields */
+ dummy = *e;
+ dummy.e_name = new_dn;
+ dummy.e_nname = new_ndn;
+ dummy.e_attrs = NULL;
+
+ /* add new DN */
+ rc = wt_dn2id_add( op, wc, np?np->e_id:p->e_id, &dummy );
+ if ( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: add failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "DN add failed";
+ goto return_results;
+ }
+ dummy.e_attrs = e->e_attrs;
+
+ rc = wt_modify_internal( op, wc, op->orm_modlist,
+ &dummy, &rs->sr_text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: modify failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ if ( dummy.e_attrs == e->e_attrs ) dummy.e_attrs = NULL;
+ goto return_results;
+ }
+
+ /* update entry */
+ rc = wt_id2entry_update( op, wc, &dummy );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_modrdn: id2entry update failed(%d)\n", rc );
+ if ( rc == LDAP_ADMINLIMIT_EXCEEDED ) {
+ rs->sr_text = "entry too big";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "entry update failed";
+ }
+ goto return_results;
+ }
+
+ if ( p_ndn.bv_len != 0 ) {
+ parent_is_glue = is_entry_glue(p);
+ /* TODO: glue entry handling */
+ }
+
+ if( op->o_postread ) {
+ if( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if( slap_read_controls( op, rs, &dummy,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: post-read failed!\n" );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto return_results;
+ }
+ }
+ }
+
+ if( op->o_noop ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ goto return_results;
+ }
+
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ wc->is_begin_transaction = 0;
+ if( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_modrdn: commit failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "commit failed";
+ goto return_results;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "wt_modrdn: rdn modified%s id=%08lx dn=\"%s\"\n",
+ op->o_noop ? " (no-op)" : "",
+ dummy.e_id, op->o_req_dn.bv_val );
+
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ if( num_ctrls ) rs->sr_ctrls = ctrls;
+
+return_results:
+ if ( dummy.e_attrs ) {
+ attrs_free( dummy.e_attrs );
+ }
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_err == LDAP_SUCCESS && parent_is_glue && parent_is_leaf ) {
+ op->o_delete_glue_parent = 1;
+ }
+
+done:
+ if( wc && wc->is_begin_transaction ){
+ Debug( LDAP_DEBUG_TRACE, "wt_modrdn: rollback transaction\n" );
+ wc->session->rollback_transaction(wc->session, NULL);
+ wc->is_begin_transaction = 0;
+ }
+
+ slap_graduate_commit_csn( op );
+
+ if( new_dn.bv_val != NULL ) free( new_dn.bv_val );
+ if( new_ndn.bv_val != NULL ) free( new_ndn.bv_val );
+
+ /* free entry */
+ if( e != NULL ) {
+ wt_entry_return( e );
+ }
+ /* free parent entry */
+ if( p != NULL ) {
+ wt_entry_return( p );
+ }
+ /* free new entry */
+ if( ne != NULL ) {
+ wt_entry_return( ne );
+ }
+ /* free new parent entry */
+ if( np != NULL ) {
+ wt_entry_return( np );
+ }
+
+ if( preread_ctrl != NULL && (*preread_ctrl) != NULL ) {
+ slap_sl_free( (*preread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *preread_ctrl, op->o_tmpmemctx );
+ }
+ if( postread_ctrl != NULL && (*postread_ctrl) != NULL ) {
+ slap_sl_free( (*postread_ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ slap_sl_free( *postread_ctrl, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/nextid.c b/servers/slapd/back-wt/nextid.c
new file mode 100644
index 0000000..99620e9
--- /dev/null
+++ b/servers/slapd/back-wt/nextid.c
@@ -0,0 +1,88 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+int wt_next_id(BackendDB *be, ID *out){
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ *out = __sync_add_and_fetch(&wi->wi_lastid, 1);
+ return 0;
+}
+
+int wt_last_id( BackendDB *be, WT_SESSION *session, ID *out )
+{
+ WT_CURSOR *cursor;
+ int rc;
+ uint64_t id;
+
+ rc = session->open_cursor(session, WT_TABLE_ID2ENTRY, NULL, NULL, &cursor);
+ if(rc){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_last_id: open_cursor failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+
+ rc = cursor->prev(cursor);
+ switch(rc) {
+ case 0:
+ rc = cursor->get_key(cursor, &id);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_last_id: get_key failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+ *out = id;
+ break;
+ case WT_NOTFOUND:
+ /* no entry */
+ *out = 0;
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_last_id: prev failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ }
+
+ rc = cursor->close(cursor);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_last_id: close failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return rc;
+ }
+
+ return 0;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/operational.c b/servers/slapd/back-wt/operational.c
new file mode 100644
index 0000000..df9d0c6
--- /dev/null
+++ b/servers/slapd/back-wt/operational.c
@@ -0,0 +1,110 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "slap-config.h"
+
+int
+wt_hasSubordinates(
+ Operation *op,
+ Entry *e,
+ int *hasSubordinates )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ wt_ctx *wc = NULL;
+ int rc;
+
+ assert( e != NULL );
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_hasSubordinates: wt_ctx_get failed\n" );
+ return LDAP_OTHER;
+ }
+
+ rc = wt_dn2id_has_children(op, wc, e->e_id);
+ switch(rc){
+ case 0:
+ *hasSubordinates = LDAP_COMPARE_TRUE;
+ break;
+ case WT_NOTFOUND:
+ *hasSubordinates = LDAP_COMPARE_FALSE;
+ rc = LDAP_SUCCESS;
+ break;
+ default:
+ Debug(LDAP_DEBUG_ANY,
+ "<=- wt_hasSubordinates: has_children failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ rc = LDAP_OTHER;
+ }
+ return rc;
+}
+
+/*
+ * sets the supported operational attributes (if required)
+ */
+int
+wt_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ Attribute **ap;
+
+ assert( rs->sr_entry != NULL );
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) {
+ break;
+ }
+ }
+
+ if ( *ap == NULL &&
+ attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL &&
+ ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) )
+ {
+ int hasSubordinates, rc;
+
+ rc = wt_hasSubordinates( op, rs->sr_entry, &hasSubordinates );
+ if ( rc == LDAP_SUCCESS ) {
+ *ap = slap_operational_hasSubordinate( hasSubordinates == LDAP_COMPARE_TRUE );
+ assert( *ap != NULL );
+
+ ap = &(*ap)->a_next;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/proto-wt.h b/servers/slapd/back-wt/proto-wt.h
new file mode 100644
index 0000000..7fa2666
--- /dev/null
+++ b/servers/slapd/back-wt/proto-wt.h
@@ -0,0 +1,268 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#ifndef _PROTO_WT_H_
+#define _PROTO_WT_H_
+
+LDAP_BEGIN_DECL
+
+#define WT_UCTYPE "WT"
+
+/*
+ * attr.c
+ */
+
+AttrInfo *wt_attr_mask( struct wt_info *wi, AttributeDescription *desc );
+void wt_attr_flush( struct wt_info *wi );
+void wt_attr_index_unparse( struct wt_info *wi, BerVarray *bva );
+int wt_attr_index_config(
+ struct wt_info *wi,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ struct config_reply_s *c_reply);
+void wt_attr_index_destroy( struct wt_info *wi );
+
+/*
+ * id2entry.c
+ */
+int wt_id2entry(BackendDB *be, wt_ctx *wc, ID id, Entry **ep );
+int wt_id2entry_add(Operation *op, wt_ctx *wc, Entry *e );
+int wt_id2entry_update(Operation *op, wt_ctx *wc, Entry *e );
+int wt_id2entry_delete(Operation *op, wt_ctx *wc, Entry *e );
+
+BI_entry_release_rw wt_entry_release;
+BI_entry_get_rw wt_entry_get;
+
+int wt_entry_return(Entry *e);
+int wt_entry_release(Operation *op, Entry *e, int rw);
+
+/*
+ * idl.c
+ */
+
+unsigned wt_idl_search( ID *ids, ID id );
+
+ID wt_idl_first( ID *ids, ID *cursor );
+ID wt_idl_next( ID *ids, ID *cursor );
+int wt_idl_append_one( ID *ids, ID id );
+void wt_idl_sort( ID *ids, ID *tmp );
+int wt_idl_intersection( ID *a, ID *b );
+int wt_filter_candidates(
+ Operation *op,
+ wt_ctx *wc,
+ Filter *f,
+ ID *ids,
+ ID *tmp,
+ ID *stack );
+int
+wt_idl_union(
+ ID *a,
+ ID *b );
+
+/*
+ * index.c
+ */
+
+extern AttrInfo *
+wt_index_mask LDAP_P((
+ Backend *be,
+ AttributeDescription *desc,
+ struct berval *atname ));
+
+int wt_index_entry LDAP_P(( Operation *op, wt_ctx *wc, int r, Entry *e ));
+int wt_index_values(
+ Operation *op,
+ wt_ctx *wc,
+ AttributeDescription *desc,
+ BerVarray vals,
+ ID id,
+ int opid );
+int wt_index_param(
+ Backend *be,
+ AttributeDescription *desc,
+ int ftype,
+ slap_mask_t *maskp,
+ struct berval *prefixp );
+
+WT_CURSOR *wt_index_open(wt_ctx *wc, struct berval *name, int create);
+
+#define wt_index_entry_add(op,t,e) \
+ wt_index_entry((op),(t),SLAP_INDEX_ADD_OP,(e))
+#define wt_index_entry_del(op,t,e) \
+ wt_index_entry((op),(t),SLAP_INDEX_DELETE_OP,(e))
+
+/*
+ * key.c
+ */
+int
+wt_key_read( Backend *be,
+ WT_CURSOR *cursor,
+ struct berval *k,
+ ID *ids,
+ WT_CURSOR **saved_cursor,
+ int get_flag);
+
+int
+wt_key_change( Backend *be,
+ WT_CURSOR *cursor,
+ struct berval *k,
+ ID id,
+ int op);
+
+/*
+ * nextid.c
+ */
+int wt_next_id(BackendDB *be, ID *out);
+int wt_last_id( BackendDB *be, WT_SESSION *session, ID *out );
+
+/*
+ * modify.c
+ */
+int wt_modify_internal(
+ Operation *op,
+ wt_ctx *wc,
+ Modifications *modlist,
+ Entry *e,
+ const char **text,
+ char *textbuf,
+ size_t textlen );
+
+/*
+ * config.c
+ */
+int wt_back_init_cf( BackendInfo *bi );
+
+/*
+ * dn2id.c
+ */
+
+int
+wt_dn2id(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn,
+ ID *id);
+
+int
+wt_dn2id_add(
+ Operation *op,
+ wt_ctx *wc,
+ ID pid,
+ Entry *e);
+
+int
+wt_dn2idl(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry *e,
+ ID *ids,
+ ID *stack);
+
+int
+wt_dn2id_delete(
+ Operation *op,
+ wt_ctx *wc,
+ struct berval *ndn);
+
+int
+wt_dn2id_has_children(
+ Operation *op,
+ wt_ctx *wc,
+ ID id );
+
+/*
+ * dn2entry.c
+ */
+int wt_dn2entry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep );
+
+int wt_dn2pentry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep );
+int wt_dn2aentry( BackendDB *be,
+ wt_ctx *wc,
+ struct berval *ndn,
+ Entry **ep );
+
+/*
+ * former ctx.c
+ */
+wt_ctx *wt_ctx_init(struct wt_info *wi);
+void wt_ctx_free(void *key, void *data);
+wt_ctx *wt_ctx_get(Operation *op, struct wt_info *wi);
+
+/*
+ * former cache.c
+ */
+int wt_idlcache_get(wt_ctx *wc, struct berval *ndn, int scope, ID *ids);
+int wt_idlcache_set(wt_ctx *wc, struct berval *ndn, int scope, ID *ids);
+int wt_idlcache_begin(wt_ctx *wc, struct berval *ndn, int scope);
+int wt_idlcache_clear(Operation *op, wt_ctx *wc, struct berval *ndn);
+
+/*
+ * former external.h
+ */
+
+extern BI_init wt_back_initialize;
+extern BI_db_config wt_db_config;
+extern BI_op_add wt_add;
+extern BI_op_bind wt_bind;
+extern BI_op_compare wt_compare;
+extern BI_op_delete wt_delete;
+extern BI_op_modify wt_modify;
+extern BI_op_modrdn wt_modrdn;
+extern BI_op_search wt_search;
+extern BI_op_extended wt_extended;
+
+extern BI_operational wt_operational;
+extern BI_has_subordinates wt_hasSubordinates;
+
+/* tools.c */
+int wt_entry_header(WT_ITEM *item, EntryHeader *eh);
+extern BI_tool_entry_open wt_tool_entry_open;
+extern BI_tool_entry_close wt_tool_entry_close;
+extern BI_tool_entry_first_x wt_tool_entry_first_x;
+extern BI_tool_entry_next wt_tool_entry_next;
+extern BI_tool_entry_get wt_tool_entry_get;
+extern BI_tool_entry_put wt_tool_entry_put;
+extern BI_tool_entry_reindex wt_tool_entry_reindex;
+extern BI_tool_dn2id_get wt_tool_dn2id_get;
+extern BI_tool_entry_modify wt_tool_entry_modify;
+extern BI_tool_entry_delete wt_tool_entry_delete;
+
+LDAP_END_DECL
+
+#endif /* _PROTO_WT_H */
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
diff --git a/servers/slapd/back-wt/search.c b/servers/slapd/back-wt/search.c
new file mode 100644
index 0000000..8a313b3
--- /dev/null
+++ b/servers/slapd/back-wt/search.c
@@ -0,0 +1,759 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "back-wt.h"
+#include "idl.h"
+
+static int search_aliases(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ WT_SESSION *session,
+ ID *ids,
+ ID *scopes,
+ ID *stack )
+{
+ /* TODO: search_aliases does not implement yet. */
+ WT_IDL_ZERO( ids );
+ return 0;
+}
+
+static int base_candidate(
+ BackendDB *be,
+ Entry *e,
+ ID *ids )
+{
+ Debug(LDAP_DEBUG_ARGS,
+ "base_candidate: base: \"%s\" (0x%08lx)\n",
+ e->e_nname.bv_val, (long) e->e_id );
+
+ ids[0] = 1;
+ ids[1] = e->e_id;
+ return 0;
+}
+
+/* Look for "objectClass Present" in this filter.
+ * Also count depth of filter tree while we're at it.
+ */
+static int oc_filter(
+ Filter *f,
+ int cur,
+ int *max )
+{
+ int rc = 0;
+
+ assert( f != NULL );
+
+ if( cur > *max ) *max = cur;
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_PRESENT:
+ if (f->f_desc == slap_schema.si_ad_objectClass) {
+ rc = 1;
+ }
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ cur++;
+ for ( f=f->f_and; f; f=f->f_next ) {
+ (void) oc_filter(f, cur, max);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return rc;
+}
+
+static void search_stack_free( void *key, void *data )
+{
+ ber_memfree_x(data, NULL);
+}
+
+static void *search_stack( Operation *op )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ void *ret = NULL;
+
+ if ( op->o_threadctx ) {
+ ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack,
+ &ret, NULL );
+ } else {
+ ret = wi->wi_search_stack;
+ }
+
+ if ( !ret ) {
+ ret = ch_malloc( wi->wi_search_stack_depth * WT_IDL_UM_SIZE
+ * sizeof( ID ) );
+ if ( op->o_threadctx ) {
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack,
+ ret, search_stack_free, NULL, NULL );
+ } else {
+ wi->wi_search_stack = ret;
+ }
+ }
+ return ret;
+}
+
+static int search_candidates(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ wt_ctx *wc,
+ ID *ids,
+ ID *scopes )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ int rc, depth = 1;
+ Filter f, rf, xf, nf;
+ ID *stack;
+ AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT;
+ Filter sf;
+ AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT;
+
+ Debug(LDAP_DEBUG_TRACE,
+ "wt_search_candidates: base=\"%s\" (0x%08lx) scope=%d\n",
+ e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope );
+
+ xf.f_or = op->oq_search.rs_filter;
+ xf.f_choice = LDAP_FILTER_OR;
+ xf.f_next = NULL;
+
+ /* If the user's filter uses objectClass=*,
+ * these clauses are redundant.
+ */
+ if (!oc_filter(op->oq_search.rs_filter, 1, &depth)
+ && !get_subentries_visibility(op)) {
+ if( !get_manageDSAit(op) && !get_domainScope(op) ) {
+ /* match referral objects */
+ struct berval bv_ref = BER_BVC( "referral" );
+ rf.f_choice = LDAP_FILTER_EQUALITY;
+ rf.f_ava = &aa_ref;
+ rf.f_av_desc = slap_schema.si_ad_objectClass;
+ rf.f_av_value = bv_ref;
+ rf.f_next = xf.f_or;
+ xf.f_or = &rf;
+ depth++;
+ }
+ }
+
+ f.f_next = NULL;
+ f.f_choice = LDAP_FILTER_AND;
+ f.f_and = &nf;
+ /* Dummy; we compute scope separately now */
+ nf.f_choice = SLAPD_FILTER_COMPUTED;
+ nf.f_result = LDAP_SUCCESS;
+ nf.f_next = ( xf.f_or == op->oq_search.rs_filter )
+ ? op->oq_search.rs_filter : &xf ;
+ /* Filter depth increased again, adding dummy clause */
+ depth++;
+
+ if( get_subentries_visibility( op ) ) {
+ struct berval bv_subentry = BER_BVC( "subentry" );
+ sf.f_choice = LDAP_FILTER_EQUALITY;
+ sf.f_ava = &aa_subentry;
+ sf.f_av_desc = slap_schema.si_ad_objectClass;
+ sf.f_av_value = bv_subentry;
+ sf.f_next = nf.f_next;
+ nf.f_next = &sf;
+ }
+
+ /* Allocate IDL stack, plus 1 more for former tmp */
+ if ( depth+1 > wi->wi_search_stack_depth ) {
+ stack = ch_malloc( (depth + 1) * WT_IDL_UM_SIZE * sizeof( ID ) );
+ } else {
+ stack = search_stack( op );
+ }
+
+ if( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ rc = search_aliases( op, rs, e, wc->session, ids, scopes, stack );
+ if ( WT_IDL_IS_ZERO( ids ) && rc == LDAP_SUCCESS )
+ rc = wt_dn2idl( op, wc, &e->e_nname, e, ids, stack );
+ } else {
+ rc = wt_dn2idl(op, wc, &e->e_nname, e, ids, stack );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = wt_filter_candidates( op, wc, &f, ids,
+ stack, stack+WT_IDL_UM_SIZE );
+ }
+
+ if ( depth+1 > wi->wi_search_stack_depth ) {
+ ch_free( stack );
+ }
+
+ if( rc ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "wt_search_candidates: failed (rc=%d)\n", rc );
+
+ } else {
+ Debug(LDAP_DEBUG_TRACE,
+ "wt_search_candidates: id=%ld first=%ld last=%ld\n",
+ (long) ids[0],
+ (long) WT_IDL_FIRST(ids),
+ (long) WT_IDL_LAST(ids));
+ }
+ return 0;
+}
+
+static int
+parse_paged_cookie( Operation *op, SlapReply *rs )
+{
+ int rc = LDAP_SUCCESS;
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* this function must be invoked only if the pagedResults
+ * control has been detected, parsed and partially checked
+ * by the frontend */
+ assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED );
+
+ /* cookie decoding/checks deferred to backend... */
+ if ( ps->ps_cookieval.bv_len ) {
+ PagedResultsCookie reqcookie;
+ if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ memcpy( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie ));
+
+ if ( reqcookie > ps->ps_cookie ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+
+ } else if ( reqcookie < ps->ps_cookie ) {
+ rs->sr_text = "paged results cookie is invalid or old";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ } else {
+ /* we're going to use ps_cookie */
+ op->o_conn->c_pagedresults_state.ps_cookie = 0;
+ }
+
+done:;
+
+ return rc;
+}
+
+static void
+send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid,
+ int tentries )
+{
+ LDAPControl *ctrls[2];
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie respcookie;
+ struct berval cookie;
+
+ Debug(LDAP_DEBUG_ARGS,
+ "send_paged_response: lastid=0x%08lx nentries=%d\n",
+ lastid ? *lastid : 0, rs->sr_nentries );
+
+ ctrls[1] = NULL;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( lastid ) {
+ respcookie = ( PagedResultsCookie )(*lastid);
+ cookie.bv_len = sizeof( respcookie );
+ cookie.bv_val = (char *)&respcookie;
+
+ } else {
+ respcookie = ( PagedResultsCookie )0;
+ BER_BVSTR( &cookie, "" );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = respcookie;
+ op->o_conn->c_pagedresults_state.ps_count =
+ ((PagedResultsState *)op->o_pagedresults_state)->ps_count +
+ rs->sr_nentries;
+
+ /* return size of 0 -- no estimate */
+ ber_printf( ber, "{iO}", 0, &cookie );
+
+ ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx );
+ if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) {
+ goto done;
+ }
+
+ ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrls[0]->ldctl_iscritical = 0;
+
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+
+done:
+ (void) ber_free_buf( ber );
+}
+
+int
+wt_search( Operation *op, SlapReply *rs )
+{
+ struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
+ ID id, cursor;
+ ID lastid = NOID;
+ int manageDSAit;
+ wt_ctx *wc;
+ int rc = LDAP_OTHER;
+ Entry *e = NULL;
+ Entry *ae = NULL;
+ Entry *base = NULL;
+ slap_mask_t mask;
+ time_t stoptime;
+
+ ID candidates[WT_IDL_UM_SIZE];
+ ID scopes[WT_IDL_DB_SIZE];
+ int tentries = 0;
+ unsigned nentries = 0;
+
+ Debug( LDAP_DEBUG_ARGS, "==> wt_search: %s\n", op->o_req_dn.bv_val );
+
+ manageDSAit = get_manageDSAit( op );
+
+ wc = wt_ctx_get(op, wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_search: wt_ctx_get failed: %d\n", rc );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return rc;
+ }
+
+ /* get entry */
+ rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &ae);
+ break;
+ default:
+ /* TODO: error handling */
+ Debug( LDAP_DEBUG_ANY,
+ "<== wt_search: error at wt_dn2entry() rc=%d\n", rc );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ goto done;
+ }
+
+ if ( op->ors_deref & LDAP_DEREF_FINDING ) {
+ /* not implement yet */
+ }
+
+ if ( e == NULL ) {
+ if ( ae ) {
+ struct berval matched_dn = BER_BVNULL;
+ /* found ancestor entry */
+ if ( access_allowed( op, ae,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) ) {
+ BerVarray erefs = NULL;
+ ber_dupbv( &matched_dn, &ae->e_name );
+ erefs = is_entry_referral( ae )
+ ? get_entry_referrals( op, ae )
+ : NULL;
+ rs->sr_err = LDAP_REFERRAL;
+ rs->sr_matched = matched_dn.bv_val;
+ if ( erefs ) {
+ rs->sr_ref = referral_rewrite( erefs, &matched_dn,
+ &op->o_req_dn, op->oq_search.rs_scope );
+ ber_bvarray_free( erefs );
+ }
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_search: ancestor is referral\n");
+ rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+ Debug( LDAP_DEBUG_ARGS,
+ "wt_search: no such object %s\n",
+ op->o_req_dn.bv_val);
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* NOTE: __NEW__ "search" access is required
+ * on searchBase object */
+ if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_SEARCH, NULL, &mask ) )
+ {
+ if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( !manageDSAit && is_entry_referral( e ) ) {
+ struct berval matched_dn = BER_BVNULL;
+ BerVarray erefs = NULL;
+ ber_dupbv( &matched_dn, &e->e_name );
+ erefs = get_entry_referrals( op, e );
+ rs->sr_err = LDAP_REFERRAL;
+ if ( erefs ) {
+ rs->sr_ref = referral_rewrite( erefs, &matched_dn,
+ &op->o_req_dn, op->oq_search.rs_scope );
+ ber_bvarray_free( erefs );
+ if ( !rs->sr_ref ) {
+ rs->sr_text = "bad_referral object";
+ }
+ }
+ Debug( LDAP_DEBUG_ARGS, "wt_search: entry is referral\n");
+ rs->sr_matched = matched_dn.bv_val;
+ send_ldap_result( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ ber_memfree( matched_dn.bv_val );
+ rs->sr_matched = NULL;
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* compute it anyway; root does not use it */
+ stoptime = op->o_time + op->ors_tlimit;
+
+ base = e;
+
+ e = NULL;
+
+ /* select candidates */
+ if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) {
+ rs->sr_err = base_candidate( op->o_bd, base, candidates );
+ }else{
+ WT_IDL_ZERO( candidates );
+ WT_IDL_ZERO( scopes );
+ rc = search_candidates( op, rs, base,
+ wc, candidates, scopes );
+ switch(rc){
+ case 0:
+ case WT_NOTFOUND:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "wt_search: error search_candidates\n" );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ goto done;
+ }
+ }
+
+ /* start cursor at beginning of candidates.
+ */
+ cursor = 0;
+
+ if ( candidates[0] == 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "wt_search: no candidates\n" );
+ goto nochange;
+ }
+
+ if ( op->ors_limit &&
+ op->ors_limit->lms_s_unchecked != -1 &&
+ WT_IDL_N(candidates) > (unsigned) op->ors_limit->lms_s_unchecked )
+ {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ goto done;
+ }
+
+ if ( op->ors_limit == NULL /* isroot == TRUE */ ||
+ !op->ors_limit->lms_s_pr_hide )
+ {
+ tentries = WT_IDL_N(candidates);
+ }
+
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ /* TODO: pageresult */
+ PagedResultsState *ps = op->o_pagedresults_state;
+ /* deferred cookie parsing */
+ rs->sr_err = parse_paged_cookie( op, rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ cursor = (ID) ps->ps_cookie;
+ if ( cursor && ps->ps_size == 0 ) {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = "search abandoned by pagedResult size=0";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ id = wt_idl_first( candidates, &cursor );
+ if ( id == NOID ) {
+ Debug( LDAP_DEBUG_TRACE, "wt_search: no paged results candidates\n" );
+ send_paged_response( op, rs, &lastid, 0 );
+
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ nentries = ps->ps_count;
+ if ( id == (ID)ps->ps_cookie )
+ id = wt_idl_next( candidates, &cursor );
+ goto loop_begin;
+ }
+
+ for ( id = wt_idl_first( candidates, &cursor );
+ id != NOID ; id = wt_idl_next( candidates, &cursor ) )
+ {
+ int scopeok;
+
+loop_begin:
+
+ /* check for abandon */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ /* mostly needed by internal searches,
+ * e.g. related to syncrepl, for whom
+ * abandon does not get set... */
+ if ( slapd_shutdown ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ send_ldap_disconnect( op, rs );
+ goto done;
+ }
+
+ /* check time limit */
+ if ( op->ors_tlimit != SLAP_NO_LIMIT
+ && slap_get_time() > stoptime )
+ {
+ rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
+ rs->sr_ref = rs->sr_v2ref;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ goto done;
+ }
+
+ nentries++;
+
+ fetch_entry_retry:
+
+ rc = wt_id2entry(op->o_bd, wc, id, &e);
+ /* TODO: error handling */
+ if ( e == NULL ) {
+ /* TODO: */
+ goto loop_continue;
+ }
+ if ( is_entry_subentry( e ) ) {
+ if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) {
+ if(!get_subentries_visibility( op )) {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ } else if ( get_subentries( op ) &&
+ !get_subentries_visibility( op ))
+ {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ } else if ( get_subentries_visibility( op )) {
+ /* only subentries are visible */
+ goto loop_continue;
+ }
+
+ scopeok = 0;
+ switch( op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ /* This is always true, yes? */
+ if ( id == base->e_id ) scopeok = 1;
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ scopeok = 1;
+ break;
+ case LDAP_SCOPE_CHILDREN:
+ if ( id == base->e_id ) break;
+ /* Fall-thru */
+ case LDAP_SCOPE_SUBTREE:
+ scopeok = dnIsSuffix(&e->e_nname, &base->e_nname);
+ break;
+ }
+
+ /* aliases were already dereferenced in candidate list */
+ if ( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ /* but if the search base is an alias, and we didn't
+ * deref it when finding, return it.
+ */
+ if ( is_entry_alias(e) &&
+ ((op->ors_deref & LDAP_DEREF_FINDING) ||
+ !bvmatch(&e->e_nname, &op->o_req_ndn)))
+ {
+ goto loop_continue;
+ }
+ /* TODO: alias handling */
+ }
+
+ /* Not in scope, ignore it */
+ if ( !scopeok )
+ {
+ Debug( LDAP_DEBUG_TRACE, "wt_search: %ld scope not okay\n",
+ (long) id );
+ goto loop_continue;
+ }
+
+ /*
+ * if it's a referral, add it to the list of referrals. only do
+ * this for non-base searches, and don't check the filter
+ * explicitly here since it's only a candidate anyway.
+ */
+ if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE
+ && is_entry_referral( e ) )
+ {
+ BerVarray erefs = get_entry_referrals( op, e );
+ rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL,
+ op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL
+ ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE );
+ rs->sr_entry = e;
+ send_search_reference( op, rs );
+ rs->sr_entry = NULL;
+ ber_bvarray_free( rs->sr_ref );
+ ber_bvarray_free( erefs );
+ goto loop_continue;
+ }
+
+ if ( !manageDSAit && is_entry_glue( e )) {
+ goto loop_continue;
+ }
+
+ /* if it matches the filter and scope, send it */
+ rs->sr_err = test_filter( op, e, op->oq_search.rs_filter );
+ if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ /* check size limit */
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) {
+ wt_entry_return( e );
+ e = NULL;
+ send_paged_response( op, rs, &lastid, tentries );
+ goto done;
+ }
+ lastid = id;
+ }
+
+ if (e) {
+ /* safe default */
+ rs->sr_attrs = op->oq_search.rs_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_ctrls = NULL;
+ rs->sr_entry = e;
+ RS_ASSERT( e->e_private != NULL );
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_err = send_search_entry( op, rs );
+ rs->sr_attrs = NULL;
+ rs->sr_entry = NULL;
+ e = NULL;
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS: /* entry sent ok */
+ break;
+ default: /* entry not sent */
+ break;
+ case LDAP_BUSY:
+ send_ldap_result( op, rs );
+ goto done;
+ case LDAP_UNAVAILABLE:
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ case LDAP_SIZELIMIT_EXCEEDED:
+ rs->sr_ref = rs->sr_v2ref;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ goto done;
+ }
+ }
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "wt_search: %ld does not match filter\n", (long) id );
+ }
+
+ loop_continue:
+ if( e ) {
+ wt_entry_return( e );
+ e = NULL;
+ }
+ }
+
+nochange:
+ rs->sr_ctrls = NULL;
+ rs->sr_ref = rs->sr_v2ref;
+ rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL;
+ rs->sr_rspoid = NULL;
+ if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
+ send_paged_response( op, rs, NULL, 0 );
+ } else {
+ send_ldap_result( op, rs );
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:
+
+ if( base ) {
+ wt_entry_return( base );
+ }
+
+ if( e ) {
+ wt_entry_return( e );
+ }
+
+ if( ae ) {
+ wt_entry_return( ae );
+ }
+
+ return rs->sr_err;
+}
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/back-wt/tools.c b/servers/slapd/back-wt/tools.c
new file mode 100644
index 0000000..795e81d
--- /dev/null
+++ b/servers/slapd/back-wt/tools.c
@@ -0,0 +1,721 @@
+/* OpenLDAP WiredTiger backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
+ * based on back-bdb for inclusion in OpenLDAP Software.
+ * WiredTiger is a product of MongoDB Inc.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include "back-wt.h"
+#include "slap-config.h"
+
+typedef struct dn_id {
+ ID id;
+ struct berval dn;
+} dn_id;
+
+#define HOLE_SIZE 4096
+static dn_id hbuf[HOLE_SIZE], *holes = hbuf;
+static unsigned nholes;
+
+static struct berval *tool_base;
+static int tool_scope;
+static Filter *tool_filter;
+
+static wt_ctx *wc;
+static WT_CURSOR *reader;
+static WT_ITEM item;
+
+int
+wt_tool_entry_open( BackendDB *be, int mode )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ int rc;
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ wc = wt_ctx_init(wi);
+ if( !wc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_open: wt_ctx_get failed\n" );
+ return -1;
+ }
+
+ rc = wc->session->open_cursor(wc->session, WT_TABLE_ID2ENTRY"(entry)"
+ ,NULL, NULL, &reader);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_open: cursor open failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+wt_tool_entry_close( BackendDB *be )
+{
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ if( reader ) {
+ reader->close(reader);
+ reader = NULL;
+ }
+
+ wt_ctx_free(NULL, wc);
+
+ if( nholes ) {
+ unsigned i;
+ fprintf( stderr, "Error, entries missing!\n");
+ for (i=0; i<nholes; i++) {
+ fprintf(stderr, " entry %ld: %s\n",
+ holes[i].id, holes[i].dn.bv_val);
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+ID
+wt_tool_entry_first_x( BackendDB *be,
+ struct berval *base,
+ int scope,
+ Filter *f )
+{
+ tool_base = base;
+ tool_scope = scope;
+ tool_filter = f;
+
+ return wt_tool_entry_next( be );
+}
+
+ID
+wt_tool_entry_next( BackendDB *be )
+{
+ int rc;
+ ID id;
+
+ rc = reader->next(reader);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ return NOID;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_next: next failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NOID;
+ }
+
+ rc = reader->get_key(reader, &id);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_next: get_key failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ }
+ return id;
+}
+
+static ber_len_t
+entry_getlen(unsigned char **buf)
+{
+ ber_len_t len;
+ int i;
+
+ len = *(*buf)++;
+ if (len <= 0x7f)
+ return len;
+ i = len & 0x7f;
+ len = 0;
+ for (;i > 0; i--) {
+ len <<= 8;
+ len |= *(*buf)++;
+ }
+ return len;
+}
+
+int wt_entry_header(WT_ITEM *item, EntryHeader *eh)
+{
+ unsigned char *ptr = (unsigned char *)item->data;
+
+ /* Some overlays can create empty entries
+ * so don't check for zeros here.
+ */
+ eh->nattrs = entry_getlen(&ptr);
+ eh->nvals = entry_getlen(&ptr);
+ eh->data = (char *)ptr;
+ return LDAP_SUCCESS;
+}
+
+Entry *
+wt_tool_entry_get( BackendDB *be, ID id )
+{
+ Entry *e = NULL;
+ static EntryHeader eh;
+ int rc, eoff;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ reader->set_key(reader, id);
+ rc = reader->search(reader);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_get: search failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ rc = reader->get_value(reader, &item);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_get: get_value failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ rc = wt_entry_header( &item, &eh );
+ assert( rc == 0 );
+ eoff = eh.data - (char *)item.data;
+
+ eh.bv.bv_len = eh.nvals * sizeof( struct berval ) + item.size;
+ eh.bv.bv_val = ch_realloc( eh.bv.bv_val, eh.bv.bv_len );
+ memset(eh.bv.bv_val, 0xff, eh.bv.bv_len);
+ eh.data = eh.bv.bv_val + eh.nvals * sizeof( struct berval );
+ memcpy(eh.data, item.data, item.size);
+ eh.data += eoff;
+
+ rc = entry_decode( &eh, &e );
+ assert( rc == 0 );
+
+ if( rc == LDAP_SUCCESS ) {
+ e->e_id = id;
+ }
+
+done:
+ return e;
+}
+
+static int wt_tool_next_id(
+ Operation *op,
+ Entry *e,
+ struct berval *text,
+ int hole )
+{
+ struct berval dn = e->e_name;
+ struct berval ndn = e->e_nname;
+ struct berval pdn, npdn;
+ int rc;
+ ID id = 0;
+ ID pid = 0;
+
+ if(ndn.bv_len == 0){
+ e->e_id = 0;
+ return 0;
+ }
+
+ rc = wt_dn2id(op, wc, &ndn, &id);
+ if(rc == 0){
+ e->e_id = id;
+ }else if( rc == WT_NOTFOUND ){
+ if ( !be_issuffix( op->o_bd, &ndn ) ) {
+ ID eid = e->e_id;
+ dnParent( &dn, &pdn );
+ dnParent( &ndn, &npdn );
+ e->e_name = pdn;
+ e->e_nname = npdn;
+ rc = wt_tool_next_id( op, e, text, 1 );
+ e->e_name = dn;
+ e->e_nname = ndn;
+ if ( rc ) {
+ return rc;
+ }
+ /* If parent didn't exist, it was created just now
+ * and its ID is now in e->e_id. Make sure the current
+ * entry gets added under the new parent ID.
+ */
+ if ( eid != e->e_id ) {
+ pid = e->e_id;
+ }
+ }else{
+ pid = id;
+ }
+ wt_next_id( op->o_bd, &e->e_id );
+ rc = wt_dn2id_add(op, wc, pid, e);
+ if( rc ){
+ snprintf( text->bv_val, text->bv_len,
+ "wt_dn2id_add failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_next_id: %s\n", text->bv_val );
+ }
+
+ }else if ( !hole ) {
+ unsigned i, j;
+ e->e_id = id;
+
+ for ( i=0; i<nholes; i++) {
+ if ( holes[i].id == e->e_id ) {
+ free(holes[i].dn.bv_val);
+ for (j=i;j<nholes;j++) holes[j] = holes[j+1];
+ holes[j].id = 0;
+ nholes--;
+ break;
+ } else if ( holes[i].id > e->e_id ) {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+static int
+wt_tool_index_add(
+ Operation *op,
+ wt_ctx *wc,
+ Entry *e )
+{
+ return wt_index_entry_add( op, wc, e );
+}
+
+ID
+wt_tool_entry_put( BackendDB *be, Entry *e, struct berval *text )
+{
+ int rc;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ if ( slapMode & SLAP_TOOL_DRYRUN )
+ return 0;
+
+ assert( slapMode & SLAP_TOOL_MODE );
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_tool_entry_put: ( \"%s\" )\n", e->e_dn );
+
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_dn2id_add: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NOID;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = wt_tool_next_id( &op, e, text, 0 );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "wt_tool_next_id failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_put: %s\n", text->bv_val );
+ goto done;
+ }
+
+ rc = wt_id2entry_add( &op, wc, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "id2entry_add failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_put: %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+ rc = wt_tool_index_add( &op, wc, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "index_entry_add failed: %s (%d)",
+ rc == LDAP_OTHER ? "Internal error" :
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_put: %s\n", text->bv_val );
+ goto done;
+ }
+
+done:
+ if ( rc == 0 ){
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_put: %s\n", text->bv_val );
+ e->e_id = NOID;
+ }
+ }else{
+ rc = wc->session->rollback_transaction(wc->session, NULL);
+ snprintf( text->bv_val, text->bv_len,
+ "txn_aborted! %s (%d)",
+ rc == LDAP_OTHER ? "Internal error" :
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_put: %s\n", text->bv_val );
+ e->e_id = NOID;
+ }
+
+ return e->e_id;
+}
+
+int wt_tool_entry_reindex(
+ BackendDB *be,
+ ID id,
+ AttributeDescription **adv )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ int rc;
+ Entry *e;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ Debug( LDAP_DEBUG_ARGS,
+ "=> wt_tool_entry_reindex( %ld )\n", (long) id );
+ assert( tool_base == NULL );
+ assert( tool_filter == NULL );
+
+ /* No indexes configured, nothing to do. Could return an
+ * error here to shortcut things.
+ */
+ if (!wi->wi_attrs) {
+ return 0;
+ }
+
+ /* Check for explicit list of attrs to index */
+ if ( adv ) {
+ int i, j, n;
+
+ if ( wi->wi_attrs[0]->ai_desc != adv[0] ) {
+ /* count */
+ for ( n = 0; adv[n]; n++ ) ;
+
+ /* insertion sort */
+ for ( i = 0; i < n; i++ ) {
+ AttributeDescription *ad = adv[i];
+ for ( j = i-1; j>=0; j--) {
+ if ( SLAP_PTRCMP( adv[j], ad ) <= 0 ) break;
+ adv[j+1] = adv[j];
+ }
+ adv[j+1] = ad;
+ }
+ }
+
+ for ( i = 0; adv[i]; i++ ) {
+ if ( wi->wi_attrs[i]->ai_desc != adv[i] ) {
+ for ( j = i+1; j < wi->wi_nattrs; j++ ) {
+ if ( wi->wi_attrs[j]->ai_desc == adv[i] ) {
+ AttrInfo *ai = wi->wi_attrs[i];
+ wi->wi_attrs[i] = wi->wi_attrs[j];
+ wi->wi_attrs[j] = ai;
+ break;
+ }
+ }
+ if ( j == wi->wi_nattrs ) {
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_reindex: no index configured for %s\n",
+ adv[i]->ad_cname.bv_val );
+ return -1;
+ }
+ }
+ }
+ wi->wi_nattrs = i;
+ }
+
+ e = wt_tool_entry_get( be, id );
+
+ if( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "=> wt_tool_entry_reindex"
+ ": could not locate id=%ld\n",
+ (long) id );
+ return -1;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_reindex: begin_transaction failed %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_tool_entry_reindex( %ld, \"%s\" )\n",
+ (long) id, e->e_dn );
+
+ rc = wt_tool_index_add( &op, wc, e );
+
+done:
+ if ( rc == 0 ){
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_reindex: commit_transaction failed %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ }
+ }else{
+ rc = wc->session->rollback_transaction(wc->session, NULL);
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_reindex: rollback transaction %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ }
+
+ wt_entry_release( &op, e, 0 );
+
+ return rc;
+}
+
+ID wt_tool_dn2id_get(
+ Backend *be,
+ struct berval *dn
+)
+{
+ Operation op = {0};
+ Opheader ohdr = {0};
+ ID id;
+ int rc;
+
+ if ( BER_BVISEMPTY(dn) )
+ return 0;
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = wt_dn2id(&op, wc, dn, &id);
+ switch( rc ){
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ return NOID;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_get: entry get failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NOID;
+ }
+ return id;
+}
+
+ID wt_tool_entry_modify(
+ BackendDB *be,
+ Entry *e,
+ struct berval *text )
+{
+ int rc;
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ assert ( e->e_id != NOID );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_tool_entry_modify( %ld, \"%s\" )\n",
+ (long) e->e_id, e->e_dn );
+
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY, "=> wt_tool_entry_modify"
+ ": begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ return NOID;
+ }
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ rc = wt_id2entry_update( &op, wc, e );
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "id2entry_update failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY, "=> wt_tool_entry_modify: %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+done:
+ if ( rc == 0 ){
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY, "=> wt_tool_entry_modify: %s\n",
+ text->bv_val );
+ e->e_id = NOID;
+ }
+ }else{
+ rc = wc->session->rollback_transaction(wc->session, NULL);
+ snprintf( text->bv_val, text->bv_len,
+ "txn_aborted! %s (%d)",
+ rc == LDAP_OTHER ? "Internal error" :
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY, "=> wt_tool_entry_modify: %s\n",
+ text->bv_val );
+ e->e_id = NOID;
+ }
+
+ return e->e_id;
+}
+
+int wt_tool_entry_delete(
+ BackendDB *be,
+ struct berval *ndn,
+ struct berval *text )
+{
+ struct wt_info *wi = (struct wt_info *) be->be_private;
+ int rc;
+ Operation op = {0};
+ Opheader ohdr = {0};
+ Entry *e = NULL;
+
+ assert( be != NULL );
+ assert( slapMode & SLAP_TOOL_MODE );
+
+ assert( text != NULL );
+ assert( text->bv_val != NULL );
+ assert( text->bv_val[0] == '\0' ); /* overconservative? */
+
+ assert ( ndn != NULL );
+ assert ( ndn->bv_val != NULL );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> wt_tool_entry_delete( %s )\n",
+ ndn->bv_val );
+
+ op.o_hdr = &ohdr;
+ op.o_bd = be;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ /* get entry */
+ rc = wt_dn2entry(op.o_bd, wc, ndn, &e);
+ switch( rc ) {
+ case 0:
+ break;
+ case WT_NOTFOUND:
+ Debug( LDAP_DEBUG_ARGS,
+ "<== wt_tool_entry_delete: no such object %s\n",
+ ndn->bv_val);
+ goto done;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_delete: error at wt_dn2entry() rc=%d\n",
+ rc );
+ goto done;
+ }
+
+ rc = wt_dn2id_has_children( &op, wc, e->e_id );
+ if( rc != WT_NOTFOUND ) {
+ /* subordinate objects must be deleted first */
+ rc = -1;
+ goto done;
+ }
+
+ rc = wc->session->begin_transaction(wc->session, NULL);
+ if( rc ){
+ Debug( LDAP_DEBUG_ANY,
+ "wt_tool_entry_delete: begin_transaction failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ goto done;
+ }
+
+ /* delete from dn2id */
+ rc = wt_dn2id_delete( &op, wc, &e->e_nname);
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_tool_entry_delete: dn2id failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto done;
+ }
+
+ /* delete indices for old attributes */
+ rc = wt_index_entry_del( &op, wc, e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_tool_entry_delete: index delete failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto done;
+ }
+
+ /* delete from id2entry */
+ rc = wt_id2entry_delete( &op, wc, e );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<== wt_tool_entry_delete: id2entry failed: %s (%d)\n",
+ wiredtiger_strerror(rc), rc );
+ wc->session->rollback_transaction(wc->session, NULL);
+ goto done;
+ }
+
+ rc = wc->session->commit_transaction(wc->session, NULL);
+ if( rc != 0 ) {
+ snprintf( text->bv_val, text->bv_len,
+ "txn_commit failed: %s (%d)",
+ wiredtiger_strerror(rc), rc );
+ Debug( LDAP_DEBUG_ANY,
+ "=> wt_tool_entry_delete: %s\n",
+ text->bv_val );
+ goto done;
+ }
+
+done:
+ /* free entry */
+ if( e != NULL ) {
+ wt_entry_return( e );
+ }
+ return rc;
+}
+
+
+/*
+ * Local variables:
+ * indent-tabs-mode: t
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/servers/slapd/backend.c b/servers/slapd/backend.c
new file mode 100644
index 0000000..30a32db
--- /dev/null
+++ b/servers/slapd/backend.c
@@ -0,0 +1,2056 @@
+/* backend.c - routines for dealing with back-end databases */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <sys/stat.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "lber_pvt.h"
+
+/*
+ * If a module is configured as dynamic, its header should not
+ * get included into slapd. While this is a general rule and does
+ * not have much of an effect in UNIX, this rule should be adhered
+ * to for Windows, where dynamic object code should not be implicitly
+ * imported into slapd without appropriate __declspec(dllimport) directives.
+ */
+
+int nBackendInfo = 0;
+slap_bi_head backendInfo = LDAP_STAILQ_HEAD_INITIALIZER(backendInfo);
+
+int nBackendDB = 0;
+slap_be_head backendDB = LDAP_STAILQ_HEAD_INITIALIZER(backendDB);
+
+static int
+backend_init_controls( BackendInfo *bi )
+{
+ if ( bi->bi_controls ) {
+ int i;
+
+ for ( i = 0; bi->bi_controls[ i ]; i++ ) {
+ int cid;
+
+ if ( slap_find_control_id( bi->bi_controls[ i ], &cid )
+ == LDAP_CONTROL_NOT_FOUND )
+ {
+ if ( !( slapMode & SLAP_TOOL_MODE ) ) {
+ assert( 0 );
+ }
+
+ return -1;
+ }
+
+ bi->bi_ctrls[ cid ] = 1;
+ }
+ }
+
+ return 0;
+}
+
+extern int syncrepl_monitor_init(void);
+
+int backend_init(void)
+{
+ int rc = -1;
+ BackendInfo *bi;
+
+ if((nBackendInfo != 0) || !LDAP_STAILQ_EMPTY(&backendInfo)) {
+ /* already initialized */
+ Debug( LDAP_DEBUG_ANY,
+ "backend_init: already initialized\n" );
+ return -1;
+ }
+
+ for( bi=slap_binfo; bi->bi_type != NULL; bi++,nBackendInfo++ ) {
+ assert( bi->bi_init != 0 );
+
+ rc = bi->bi_init( bi );
+
+ if(rc != 0) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_init: initialized for type \"%s\"\n",
+ bi->bi_type );
+ /* destroy those we've already inited */
+ for( nBackendInfo--;
+ nBackendInfo >= 0 ;
+ nBackendInfo-- )
+ {
+ if ( slap_binfo[nBackendInfo].bi_destroy ) {
+ slap_binfo[nBackendInfo].bi_destroy(
+ &slap_binfo[nBackendInfo] );
+ }
+ }
+ return rc;
+ }
+
+ LDAP_STAILQ_INSERT_TAIL(&backendInfo, bi, bi_next);
+ }
+ /* HACK: need schema defined in deterministic order */
+ syncrepl_monitor_init();
+
+ if ( nBackendInfo > 0) {
+ return 0;
+ }
+
+#ifdef SLAPD_MODULES
+ return 0;
+#else
+
+ Debug( LDAP_DEBUG_ANY,
+ "backend_init: failed\n" );
+
+ return rc;
+#endif /* SLAPD_MODULES */
+}
+
+int backend_add(BackendInfo *aBackendInfo)
+{
+ int rc = 0;
+
+ if ( aBackendInfo->bi_init == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "backend_add: "
+ "backend type \"%s\" does not have the (mandatory)init function\n",
+ aBackendInfo->bi_type );
+ return -1;
+ }
+
+ rc = aBackendInfo->bi_init(aBackendInfo);
+ if ( rc != 0) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_add: initialization for type \"%s\" failed\n",
+ aBackendInfo->bi_type );
+ return rc;
+ }
+
+ (void)backend_init_controls( aBackendInfo );
+
+ /* now add the backend type to the Backend Info List */
+ LDAP_STAILQ_INSERT_TAIL( &backendInfo, aBackendInfo, bi_next );
+ nBackendInfo++;
+ return 0;
+}
+
+static int
+backend_set_controls( BackendDB *be )
+{
+ BackendInfo *bi = be->bd_info;
+
+ /* back-relay takes care of itself; so may do other */
+ if ( overlay_is_over( be ) ) {
+ bi = ((slap_overinfo *)be->bd_info->bi_private)->oi_orig;
+ }
+
+ if ( bi->bi_controls ) {
+ if ( be->be_ctrls[ SLAP_MAX_CIDS ] == 0 ) {
+ AC_MEMCPY( be->be_ctrls, bi->bi_ctrls,
+ sizeof( be->be_ctrls ) );
+ be->be_ctrls[ SLAP_MAX_CIDS ] = 1;
+
+ } else {
+ int i;
+
+ for ( i = 0; i < SLAP_MAX_CIDS; i++ ) {
+ if ( bi->bi_ctrls[ i ] ) {
+ be->be_ctrls[ i ] = bi->bi_ctrls[ i ];
+ }
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+/* startup a specific backend database */
+int backend_startup_one(Backend *be, ConfigReply *cr)
+{
+ int rc = 0;
+
+ assert( be != NULL );
+
+ LDAP_TAILQ_INIT( &be->be_pcsn_st.be_pcsn_list );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "backend_startup_one: starting \"%s\"\n",
+ be->be_suffix ? be->be_suffix[0].bv_val : "(unknown)" );
+
+ /* set database controls */
+ (void)backend_set_controls( be );
+
+#if 0
+ if ( !BER_BVISEMPTY( &be->be_rootndn )
+ && select_backend( &be->be_rootndn, 0 ) == be
+ && BER_BVISNULL( &be->be_rootpw ) )
+ {
+ /* warning: if rootdn entry is created,
+ * it can take rootdn privileges;
+ * set empty rootpw to prevent */
+ }
+#endif
+
+ if ( be->bd_info->bi_db_open ) {
+ rc = be->bd_info->bi_db_open( be, cr );
+ if ( rc == 0 ) {
+ (void)backend_set_controls( be );
+ be->be_flags |= SLAP_DBFLAG_OPEN;
+
+ } else {
+ char *type = be->bd_info->bi_type;
+ char *suffix = "(null)";
+
+ if ( overlay_is_over( be ) ) {
+ slap_overinfo *oi = (slap_overinfo *)be->bd_info->bi_private;
+ type = oi->oi_orig->bi_type;
+ }
+
+ if ( be->be_suffix != NULL && !BER_BVISNULL( &be->be_suffix[0] ) ) {
+ suffix = be->be_suffix[0].bv_val;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup_one (type=%s, suffix=\"%s\"): "
+ "bi_db_open failed! (%d)\n",
+ type, suffix, rc );
+ }
+ }
+
+ return rc;
+}
+
+int backend_startup(Backend *be)
+{
+ int i;
+ int rc = 0;
+ BackendInfo *bi;
+ ConfigReply cr={0, ""};
+
+ if( ! ( nBackendDB > 0 ) ) {
+ /* no databases */
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: %d databases to startup.\n",
+ nBackendDB );
+ return 1;
+ }
+
+ if(be != NULL) {
+ /* silent noop if disabled */
+ if ( SLAP_DBDISABLED( be ))
+ return 0;
+ if ( be->bd_info->bi_open ) {
+ rc = be->bd_info->bi_open( be->bd_info );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: bi_open failed!\n" );
+
+ return rc;
+ }
+ }
+
+ return backend_startup_one( be, &cr );
+ }
+
+ /* open frontend, if required */
+ if ( frontendDB->bd_info->bi_db_open ) {
+ rc = frontendDB->bd_info->bi_db_open( frontendDB, &cr );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: bi_db_open(frontend) failed! (%d)\n",
+ rc );
+ return rc;
+ }
+ frontendDB->be_flags |= SLAP_DBFLAG_OPEN;
+ }
+
+ /* open each backend type */
+ i = -1;
+ LDAP_STAILQ_FOREACH(bi, &backendInfo, bi_next) {
+ i++;
+ if( bi->bi_nDB == 0) {
+ /* no database of this type, don't open */
+ continue;
+ }
+
+ if( bi->bi_open ) {
+ rc = bi->bi_open( bi );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: bi_open %d (%s) failed!\n",
+ i, bi->bi_type );
+ return rc;
+ }
+ }
+
+ (void)backend_init_controls( bi );
+ }
+
+ /* open each backend database */
+ i = -1;
+ LDAP_STAILQ_FOREACH(be, &backendDB, be_next) {
+ i++;
+ if ( SLAP_DBDISABLED( be ))
+ continue;
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: warning, database %d (%s) "
+ "has no suffix\n",
+ i, be->bd_info->bi_type );
+ }
+
+ rc = backend_startup_one( be, &cr );
+
+ if ( rc ) return rc;
+ }
+
+ return rc;
+}
+
+int backend_num( Backend *be )
+{
+ int i = 0;
+ BackendDB *b2;
+
+ if( be == NULL ) return -1;
+
+ LDAP_STAILQ_FOREACH( b2, &backendDB, be_next ) {
+ if( be == b2 ) return i;
+ i++;
+ }
+ return -1;
+}
+
+int backend_shutdown( Backend *be )
+{
+ int rc = 0;
+ BackendInfo *bi;
+
+ if( be != NULL ) {
+ /* shutdown a specific backend database */
+
+ if ( be->bd_info->bi_nDB == 0 ) {
+ /* no database of this type, we never opened it */
+ return 0;
+ }
+
+ if ( be->bd_info->bi_db_close ) {
+ rc = be->bd_info->bi_db_close( be, NULL );
+ be->be_flags &= ~SLAP_DBFLAG_OPEN;
+ if ( rc ) return rc;
+ }
+
+ if( be->bd_info->bi_close ) {
+ rc = be->bd_info->bi_close( be->bd_info );
+ if ( rc ) return rc;
+ }
+
+ return 0;
+ }
+
+ /* close each backend database */
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( SLAP_DBDISABLED( be ))
+ continue;
+ if ( be->bd_info->bi_db_close ) {
+ be->bd_info->bi_db_close( be, NULL );
+ be->be_flags &= ~SLAP_DBFLAG_OPEN;
+ }
+
+ if(rc != 0) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_close: bi_db_close %s failed!\n",
+ be->be_type );
+ }
+ }
+
+ /* close each backend type */
+ LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) {
+ if( bi->bi_nDB == 0 ) {
+ /* no database of this type */
+ continue;
+ }
+
+ if( bi->bi_close ) {
+ bi->bi_close( bi );
+ }
+ }
+
+ /* close frontend, if required */
+ if ( frontendDB->bd_info->bi_db_close ) {
+ rc = frontendDB->bd_info->bi_db_close ( frontendDB, NULL );
+ frontendDB->be_flags &= ~SLAP_DBFLAG_OPEN;
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backend_startup: bi_db_close(frontend) failed! (%d)\n",
+ rc );
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This function is supposed to be the exact counterpart
+ * of backend_startup_one(), although this one calls bi_db_destroy()
+ * while backend_startup_one() calls bi_db_open().
+ *
+ * Make sure backend_stopdown_one() destroys resources allocated
+ * by backend_startup_one(); only call backend_destroy_one() when
+ * all stuff in a BackendDB needs to be destroyed
+ */
+void
+backend_stopdown_one( BackendDB *bd )
+{
+ struct slap_csn_entry *csne;
+ csne = LDAP_TAILQ_FIRST( &bd->be_pcsn_st.be_pcsn_list );
+ while ( csne ) {
+ struct slap_csn_entry *tmp_csne = csne;
+
+ LDAP_TAILQ_REMOVE( &bd->be_pcsn_st.be_pcsn_list, csne, ce_csn_link );
+ ch_free( csne->ce_csn.bv_val );
+ csne = LDAP_TAILQ_NEXT( csne, ce_csn_link );
+ ch_free( tmp_csne );
+ }
+
+ if ( bd->bd_info->bi_db_destroy ) {
+ bd->bd_info->bi_db_destroy( bd, NULL );
+ }
+}
+
+void backend_destroy_one( BackendDB *bd, int dynamic )
+{
+ if ( dynamic ) {
+ LDAP_STAILQ_REMOVE(&backendDB, bd, BackendDB, be_next );
+ }
+
+ if ( bd->be_syncinfo ) {
+ syncinfo_free( bd->be_syncinfo, 1 );
+ }
+
+ backend_stopdown_one( bd );
+
+ ber_bvarray_free( bd->be_suffix );
+ ber_bvarray_free( bd->be_nsuffix );
+ if ( !BER_BVISNULL( &bd->be_rootdn ) ) {
+ free( bd->be_rootdn.bv_val );
+ }
+ if ( !BER_BVISNULL( &bd->be_rootndn ) ) {
+ free( bd->be_rootndn.bv_val );
+ }
+ if ( !BER_BVISNULL( &bd->be_rootpw ) ) {
+ free( bd->be_rootpw.bv_val );
+ }
+ acl_destroy( bd->be_acl );
+ limits_destroy( bd->be_limits );
+ if ( bd->be_extra_anlist ) {
+ anlist_free( bd->be_extra_anlist, 1, NULL );
+ }
+ if ( !BER_BVISNULL( &bd->be_update_ndn ) ) {
+ ch_free( bd->be_update_ndn.bv_val );
+ }
+ if ( bd->be_update_refs ) {
+ ber_bvarray_free( bd->be_update_refs );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &bd->be_pcsn_st.be_pcsn_mutex );
+
+ if ( dynamic ) {
+ free( bd );
+ }
+}
+
+int backend_destroy(void)
+{
+ BackendDB *bd;
+ BackendInfo *bi;
+
+ /* destroy each backend database */
+ while (( bd = LDAP_STAILQ_FIRST(&backendDB))) {
+ backend_destroy_one( bd, 1 );
+ }
+
+ /* destroy each backend type */
+ LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next ) {
+ if( bi->bi_destroy ) {
+ bi->bi_destroy( bi );
+ }
+ }
+
+ nBackendInfo = 0;
+ LDAP_STAILQ_INIT(&backendInfo);
+
+ /* destroy frontend database */
+ bd = frontendDB;
+ if ( bd ) {
+ if ( bd->bd_info->bi_db_destroy ) {
+ bd->bd_info->bi_db_destroy( bd, NULL );
+ }
+ ber_bvarray_free( bd->be_suffix );
+ ber_bvarray_free( bd->be_nsuffix );
+ if ( !BER_BVISNULL( &bd->be_rootdn ) ) {
+ free( bd->be_rootdn.bv_val );
+ }
+ if ( !BER_BVISNULL( &bd->be_rootndn ) ) {
+ free( bd->be_rootndn.bv_val );
+ }
+ if ( !BER_BVISNULL( &bd->be_rootpw ) ) {
+ free( bd->be_rootpw.bv_val );
+ }
+ acl_destroy( bd->be_acl );
+ frontendDB = NULL;
+ }
+
+ return 0;
+}
+
+BackendInfo* backend_info(const char *type)
+{
+ BackendInfo *bi;
+
+ /* search for the backend type */
+ LDAP_STAILQ_FOREACH(bi,&backendInfo,bi_next) {
+ if( strcasecmp(bi->bi_type, type) == 0 ) {
+ return bi;
+ }
+ }
+
+ return NULL;
+}
+
+void
+backend_db_insert(
+ BackendDB *be,
+ int idx
+)
+{
+ /* If idx < 0, just add to end of list */
+ if ( idx < 0 ) {
+ LDAP_STAILQ_INSERT_TAIL(&backendDB, be, be_next);
+ } else if ( idx == 0 ) {
+ LDAP_STAILQ_INSERT_HEAD(&backendDB, be, be_next);
+ } else {
+ int i;
+ BackendDB *b2;
+
+ b2 = LDAP_STAILQ_FIRST(&backendDB);
+ idx--;
+ for (i=0; i<idx; i++) {
+ b2 = LDAP_STAILQ_NEXT(b2, be_next);
+ }
+ LDAP_STAILQ_INSERT_AFTER(&backendDB, b2, be, be_next);
+ }
+}
+
+void
+backend_db_move(
+ BackendDB *be,
+ int idx
+)
+{
+ LDAP_STAILQ_REMOVE(&backendDB, be, BackendDB, be_next);
+ backend_db_insert(be, idx);
+}
+
+BackendDB *
+backend_db_init(
+ const char *type,
+ BackendDB *b0,
+ int idx,
+ ConfigReply *cr)
+{
+ BackendInfo *bi = backend_info(type);
+ BackendDB *be = b0;
+ int rc = 0;
+
+ if( bi == NULL ) {
+ fprintf( stderr, "Unrecognized database type (%s)\n", type );
+ return NULL;
+ }
+
+ /* If be is provided, treat it as private. Otherwise allocate
+ * one and add it to the global list.
+ */
+ if ( !be ) {
+ be = ch_calloc( 1, sizeof(Backend) );
+ /* Just append */
+ if ( idx >= nbackends )
+ idx = -1;
+ nbackends++;
+ backend_db_insert( be, idx );
+ }
+
+ be->bd_info = bi;
+ be->bd_self = be;
+
+ be->be_def_limit = frontendDB->be_def_limit;
+ be->be_dfltaccess = frontendDB->be_dfltaccess;
+
+ be->be_restrictops = frontendDB->be_restrictops;
+ be->be_requires = frontendDB->be_requires;
+ be->be_ssf_set = frontendDB->be_ssf_set;
+
+ ldap_pvt_thread_mutex_init( &be->be_pcsn_st.be_pcsn_mutex );
+ be->be_pcsn_p = &be->be_pcsn_st;
+
+ /* assign a default depth limit for alias deref */
+ be->be_max_deref_depth = SLAPD_DEFAULT_MAXDEREFDEPTH;
+
+ if ( bi->bi_db_init ) {
+ rc = bi->bi_db_init( be, cr );
+ }
+
+ if ( rc != 0 ) {
+ fprintf( stderr, "database init failed (%s)\n", type );
+ /* If we created and linked this be, remove it and free it */
+ if ( !b0 ) {
+ LDAP_STAILQ_REMOVE(&backendDB, be, BackendDB, be_next);
+ ldap_pvt_thread_mutex_destroy( &be->be_pcsn_st.be_pcsn_mutex );
+ ch_free( be );
+ be = NULL;
+ nbackends--;
+ }
+ } else {
+ if ( !bi->bi_nDB ) {
+ backend_init_controls( bi );
+ }
+ bi->bi_nDB++;
+ }
+ return( be );
+}
+
+void
+be_db_close( void )
+{
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->bd_info->bi_db_close ) {
+ be->bd_info->bi_db_close( be, NULL );
+ be->be_flags &= ~SLAP_DBFLAG_OPEN;
+ }
+ }
+
+ if ( frontendDB->bd_info->bi_db_close ) {
+ frontendDB->bd_info->bi_db_close( frontendDB, NULL );
+ }
+
+}
+
+Backend *
+select_backend(
+ struct berval * dn,
+ int noSubs )
+{
+ int j;
+ ber_len_t len, dnlen = dn->bv_len;
+ Backend *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->be_nsuffix == NULL || SLAP_DBHIDDEN( be ) || SLAP_DBDISABLED( be )) {
+ continue;
+ }
+
+ for ( j = 0; !BER_BVISNULL( &be->be_nsuffix[j] ); j++ )
+ {
+ if ( ( SLAP_GLUE_SUBORDINATE( be ) ) && noSubs )
+ {
+ continue;
+ }
+
+ len = be->be_nsuffix[j].bv_len;
+
+ if ( len > dnlen ) {
+ /* suffix is longer than DN */
+ continue;
+ }
+
+ /*
+ * input DN is normalized, so the separator check
+ * need not look at escaping
+ */
+ if ( len && len < dnlen &&
+ !DN_SEPARATOR( dn->bv_val[(dnlen-len)-1] ))
+ {
+ continue;
+ }
+
+ if ( strcmp( be->be_nsuffix[j].bv_val,
+ &dn->bv_val[dnlen-len] ) == 0 )
+ {
+ return be;
+ }
+ }
+ }
+
+ return be;
+}
+
+int
+be_issuffix(
+ Backend *be,
+ struct berval *bvsuffix )
+{
+ int i;
+
+ if ( be->be_nsuffix == NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &be->be_nsuffix[i] ); i++ ) {
+ if ( bvmatch( &be->be_nsuffix[i], bvsuffix ) ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+be_issubordinate(
+ Backend *be,
+ struct berval *bvsubordinate )
+{
+ int i;
+
+ if ( be->be_nsuffix == NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &be->be_nsuffix[i] ); i++ ) {
+ if ( dnIsSuffix( bvsubordinate, &be->be_nsuffix[i] ) ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+be_isroot_dn( Backend *be, struct berval *ndn )
+{
+ if ( BER_BVISEMPTY( ndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) {
+ return 0;
+ }
+
+ return dn_match( &be->be_rootndn, ndn );
+}
+
+int
+be_slurp_update( Operation *op )
+{
+ return ( SLAP_SLURP_SHADOW( op->o_bd ) &&
+ be_isupdate_dn( op->o_bd, &op->o_ndn ) );
+}
+
+int
+be_shadow_update( Operation *op )
+{
+ /* This assumes that all internal ops (connid <= -1000) on a syncrepl
+ * database are syncrepl operations.
+ */
+ return ( ( SLAP_SYNC_SHADOW( op->o_bd ) && SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) ||
+ ( SLAP_SHADOW( op->o_bd ) && be_isupdate_dn( op->o_bd, &op->o_ndn ) ) );
+}
+
+int
+be_isupdate_dn( Backend *be, struct berval *ndn )
+{
+ if ( BER_BVISEMPTY( ndn ) || BER_BVISEMPTY( &be->be_update_ndn ) ) {
+ return 0;
+ }
+
+ return dn_match( &be->be_update_ndn, ndn );
+}
+
+struct berval *
+be_root_dn( Backend *be )
+{
+ return &be->be_rootdn;
+}
+
+int
+be_isroot( Operation *op )
+{
+ return be_isroot_dn( op->o_bd, &op->o_ndn );
+}
+
+int
+be_isroot_pw( Operation *op )
+{
+ return be_rootdn_bind( op, NULL ) == LDAP_SUCCESS;
+}
+
+/*
+ * checks if binding as rootdn
+ *
+ * return value:
+ * SLAP_CB_CONTINUE if not the rootdn, or if rootpw is null
+ * LDAP_SUCCESS if rootdn & rootpw
+ * LDAP_INVALID_CREDENTIALS if rootdn & !rootpw
+ *
+ * if rs != NULL
+ * if LDAP_SUCCESS, op->orb_edn is set
+ * if LDAP_INVALID_CREDENTIALS, response is sent to client
+ */
+int
+be_rootdn_bind( Operation *op, SlapReply *rs )
+{
+ int rc;
+#ifdef SLAPD_SPASSWD
+ void *old_authctx = NULL;
+#endif
+
+ assert( op->o_tag == LDAP_REQ_BIND );
+ assert( op->orb_method == LDAP_AUTH_SIMPLE );
+
+ if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( BER_BVISNULL( &op->o_bd->be_rootpw ) ) {
+ /* give the database a chance */
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( BER_BVISEMPTY( &op->o_bd->be_rootpw ) ) {
+ /* rootdn bind explicitly disallowed */
+ rc = LDAP_INVALID_CREDENTIALS;
+ if ( rs ) {
+ goto send_result;
+ }
+
+ return rc;
+ }
+
+#ifdef SLAPD_SPASSWD
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind,
+ op->o_conn->c_sasl_authctx, 0, &old_authctx, NULL );
+#endif
+
+ rc = lutil_passwd( &op->o_bd->be_rootpw, &op->orb_cred, NULL, NULL );
+
+#ifdef SLAPD_SPASSWD
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind,
+ old_authctx, 0, NULL, NULL );
+#endif
+
+ rc = ( rc == 0 ? LDAP_SUCCESS : LDAP_INVALID_CREDENTIALS );
+ if ( rs ) {
+send_result:;
+ rs->sr_err = rc;
+
+ Debug( LDAP_DEBUG_TRACE, "%s: rootdn=\"%s\" bind%s\n",
+ op->o_log_prefix, op->o_bd->be_rootdn.bv_val,
+ rc == LDAP_SUCCESS ? " succeeded" : " failed" );
+
+ if ( rc == LDAP_SUCCESS ) {
+ /* Set to the pretty rootdn */
+ ber_dupbv( &op->orb_edn, &op->o_bd->be_rootdn );
+
+ } else {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return rc;
+}
+
+/* Inlined in proto-slap.h, sans assertions, when !(USE_RS_ASSERT) */
+int
+(slap_bi_op)(
+ BackendInfo *bi,
+ slap_operation_t which,
+ Operation *op,
+ SlapReply *rs )
+{
+ int rc;
+#ifndef slap_bi_op
+ void (*rsCheck)( const SlapReply *rs ) =
+ which < op_aux_operational ? rs_assert_ready : rs_assert_ok;
+#else
+# define rsCheck(rs) ((void) 0)
+#endif
+ BI_op_func *fn;
+
+ assert( bi != NULL );
+ assert( (unsigned) which < (unsigned) op_last );
+
+ fn = (&bi->bi_op_bind)[ which ];
+
+ assert( op != NULL );
+ assert( rs != NULL );
+ assert( fn != 0 );
+ rsCheck( rs );
+
+ rc = fn( op, rs );
+
+#ifndef slap_bi_op
+ if ( rc != SLAP_CB_CONTINUE && rc != SLAP_CB_BYPASS ) {
+ int err = rs->sr_err;
+
+ if ( 0 ) /* TODO */
+ if ( err == LDAP_COMPARE_TRUE || err == LDAP_COMPARE_FALSE ) {
+ assert( which == op_compare );
+ assert( rc == LDAP_SUCCESS );
+ }
+
+ rsCheck = which < op_extended ? rs_assert_done : rs_assert_ok;
+ if ( which == op_aux_chk_referrals ) {
+ if ( rc == LDAP_SUCCESS ) rsCheck = rs_assert_ready;
+ else if ( rc == LDAP_REFERRAL ) rsCheck = rs_assert_done;
+ } else if ( which == op_bind ) {
+ if ( rc == LDAP_SUCCESS ) rsCheck = rs_assert_ok;
+ }
+
+ /* TODO: Just what is the relation between rc and rs->sr_err? */
+ if ( rc != err &&
+ (rc != LDAP_SUCCESS ||
+ (err != LDAP_COMPARE_TRUE && err != LDAP_COMPARE_FALSE)) )
+ {
+ rs->sr_err = rc;
+ rsCheck( rs );
+ rs->sr_err = err;
+ }
+ }
+ rsCheck( rs );
+#endif
+
+ return rc;
+}
+
+int
+be_entry_release_rw(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ if ( op->o_bd->be_release ) {
+ /* free and release entry from backend */
+ return op->o_bd->be_release( op, e, rw );
+ } else {
+ /* free entry */
+ entry_free( e );
+ return 0;
+ }
+}
+
+int
+backend_unbind( Operation *op, SlapReply *rs )
+{
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->be_unbind ) {
+ op->o_bd = be;
+ be->be_unbind( op, rs );
+ }
+ }
+
+ return 0;
+}
+
+int
+backend_connection_init(
+ Connection *conn )
+{
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->be_connection_init ) {
+ be->be_connection_init( be, conn );
+ }
+ }
+
+ return 0;
+}
+
+int
+backend_connection_destroy(
+ Connection *conn )
+{
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->be_connection_destroy ) {
+ be->be_connection_destroy( be, conn);
+ }
+ }
+
+ return 0;
+}
+
+int
+backend_check_controls(
+ Operation *op,
+ SlapReply *rs )
+{
+ LDAPControl **ctrls = op->o_ctrls;
+ rs->sr_err = LDAP_SUCCESS;
+
+ if( ctrls ) {
+ for( ; *ctrls != NULL ; ctrls++ ) {
+ int cid;
+
+ switch ( slap_global_control( op, (*ctrls)->ldctl_oid, &cid ) ) {
+ case LDAP_CONTROL_NOT_FOUND:
+ /* unrecognized control */
+ if ( (*ctrls)->ldctl_iscritical ) {
+ /* should not be reachable */
+ Debug( LDAP_DEBUG_ANY, "backend_check_controls: "
+ "unrecognized critical control: %s\n",
+ (*ctrls)->ldctl_oid );
+ assert( 0 );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "backend_check_controls: "
+ "unrecognized non-critical control: %s\n",
+ (*ctrls)->ldctl_oid );
+ }
+ break;
+
+ case LDAP_COMPARE_FALSE:
+ if ( !op->o_bd->be_ctrls[cid] && (*ctrls)->ldctl_iscritical ) {
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ if ( get_whatFailed( op ) ) {
+ char *oids[ 2 ];
+ oids[ 0 ] = (*ctrls)->ldctl_oid;
+ oids[ 1 ] = NULL;
+ slap_ctrl_whatFailed_add( op, rs, oids );
+ }
+#endif
+ /* RFC 4511 allows unavailableCriticalExtension to be
+ * returned when the server is unwilling to perform
+ * an operation extended by a recognized critical
+ * control.
+ */
+ rs->sr_text = "critical control unavailable in context";
+ rs->sr_err = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ goto done;
+ }
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ break;
+
+ default:
+ /* unreachable */
+ Debug( LDAP_DEBUG_ANY,
+ "backend_check_controls: unable to check control: %s\n",
+ (*ctrls)->ldctl_oid );
+ assert( 0 );
+
+ rs->sr_text = "unable to check control";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ }
+
+#if 0 /* temporarily removed */
+ /* check should be generalized */
+ if( get_relax(op) && !be_isroot(op)) {
+ rs->sr_text = "requires manager authorization";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+#endif
+
+done:;
+ return rs->sr_err;
+}
+
+int
+backend_check_restrictions(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *opdata )
+{
+ slap_mask_t restrictops;
+ slap_mask_t requires;
+ slap_mask_t opflag;
+ slap_mask_t exopflag = 0;
+ slap_ssf_set_t ssfs, *ssf;
+ int updateop = 0;
+ int starttls = 0;
+ int session = 0;
+
+ restrictops = frontendDB->be_restrictops;
+ requires = frontendDB->be_requires;
+ ssfs = frontendDB->be_ssf_set;
+ ssf = &ssfs;
+
+ if ( op->o_bd ) {
+ slap_ssf_t *fssf, *bssf;
+ int rc = SLAP_CB_CONTINUE, i;
+
+ if ( op->o_bd->be_chk_controls ) {
+ rc = ( *op->o_bd->be_chk_controls )( op, rs );
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = backend_check_controls( op, rs );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ restrictops |= op->o_bd->be_restrictops;
+ requires |= op->o_bd->be_requires;
+ bssf = &op->o_bd->be_ssf_set.sss_ssf;
+ fssf = &ssfs.sss_ssf;
+ for ( i=0; i < (int)(sizeof(ssfs)/sizeof(slap_ssf_t)); i++ ) {
+ if ( bssf[i] ) fssf[i] = bssf[i];
+ }
+ }
+
+ switch( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ opflag = SLAP_RESTRICT_OP_ADD;
+ updateop++;
+ break;
+ case LDAP_REQ_BIND:
+ opflag = SLAP_RESTRICT_OP_BIND;
+ session++;
+ break;
+ case LDAP_REQ_COMPARE:
+ opflag = SLAP_RESTRICT_OP_COMPARE;
+ break;
+ case LDAP_REQ_DELETE:
+ updateop++;
+ opflag = SLAP_RESTRICT_OP_DELETE;
+ break;
+ case LDAP_REQ_EXTENDED:
+ opflag = SLAP_RESTRICT_OP_EXTENDED;
+
+ if( !opdata ) {
+ /* treat unspecified as a modify */
+ opflag = SLAP_RESTRICT_OP_MODIFY;
+ updateop++;
+ break;
+ }
+
+ if( bvmatch( opdata, &slap_EXOP_START_TLS ) ) {
+ session++;
+ starttls++;
+ exopflag = SLAP_RESTRICT_EXOP_START_TLS;
+ break;
+ }
+
+ if( bvmatch( opdata, &slap_EXOP_WHOAMI ) ) {
+ exopflag = SLAP_RESTRICT_EXOP_WHOAMI;
+ break;
+ }
+
+ if ( bvmatch( opdata, &slap_EXOP_CANCEL ) ) {
+ exopflag = SLAP_RESTRICT_EXOP_CANCEL;
+ break;
+ }
+
+ if ( bvmatch( opdata, &slap_EXOP_MODIFY_PASSWD ) ) {
+ exopflag = SLAP_RESTRICT_EXOP_MODIFY_PASSWD;
+ updateop++;
+ break;
+ }
+
+ /* treat everything else as a modify */
+ opflag = SLAP_RESTRICT_OP_MODIFY;
+ updateop++;
+ break;
+
+ case LDAP_REQ_MODIFY:
+ updateop++;
+ opflag = SLAP_RESTRICT_OP_MODIFY;
+ break;
+ case LDAP_REQ_RENAME:
+ updateop++;
+ opflag = SLAP_RESTRICT_OP_RENAME;
+ break;
+ case LDAP_REQ_SEARCH:
+ opflag = SLAP_RESTRICT_OP_SEARCH;
+ break;
+ case LDAP_REQ_UNBIND:
+ session++;
+ opflag = 0;
+ break;
+ default:
+ rs->sr_text = "restrict operations internal error";
+ rs->sr_err = LDAP_OTHER;
+ return rs->sr_err;
+ }
+
+ if ( !starttls ) {
+ /* these checks don't apply to StartTLS */
+
+ rs->sr_err = LDAP_CONFIDENTIALITY_REQUIRED;
+ if( op->o_transport_ssf < ssf->sss_transport ) {
+ rs->sr_text = op->o_transport_ssf
+ ? "stronger transport confidentiality required"
+ : "transport confidentiality required";
+ return rs->sr_err;
+ }
+
+ if( op->o_tls_ssf < ssf->sss_tls ) {
+ rs->sr_text = op->o_tls_ssf
+ ? "stronger TLS confidentiality required"
+ : "TLS confidentiality required";
+ return rs->sr_err;
+ }
+
+
+ if( op->o_tag == LDAP_REQ_BIND && opdata == NULL ) {
+ /* simple bind specific check */
+ if( op->o_ssf < ssf->sss_simple_bind ) {
+ rs->sr_text = op->o_ssf
+ ? "stronger confidentiality required"
+ : "confidentiality required";
+ return rs->sr_err;
+ }
+ }
+
+ if( op->o_tag != LDAP_REQ_BIND || opdata == NULL ) {
+ /* these checks don't apply to SASL bind */
+
+ if( op->o_sasl_ssf < ssf->sss_sasl ) {
+ rs->sr_text = op->o_sasl_ssf
+ ? "stronger SASL confidentiality required"
+ : "SASL confidentiality required";
+ return rs->sr_err;
+ }
+
+ if( op->o_ssf < ssf->sss_ssf ) {
+ rs->sr_text = op->o_ssf
+ ? "stronger confidentiality required"
+ : "confidentiality required";
+ return rs->sr_err;
+ }
+ }
+
+ if( updateop ) {
+ if( op->o_transport_ssf < ssf->sss_update_transport ) {
+ rs->sr_text = op->o_transport_ssf
+ ? "stronger transport confidentiality required for update"
+ : "transport confidentiality required for update";
+ return rs->sr_err;
+ }
+
+ if( op->o_tls_ssf < ssf->sss_update_tls ) {
+ rs->sr_text = op->o_tls_ssf
+ ? "stronger TLS confidentiality required for update"
+ : "TLS confidentiality required for update";
+ return rs->sr_err;
+ }
+
+ if( op->o_sasl_ssf < ssf->sss_update_sasl ) {
+ rs->sr_text = op->o_sasl_ssf
+ ? "stronger SASL confidentiality required for update"
+ : "SASL confidentiality required for update";
+ return rs->sr_err;
+ }
+
+ if( op->o_ssf < ssf->sss_update_ssf ) {
+ rs->sr_text = op->o_ssf
+ ? "stronger confidentiality required for update"
+ : "confidentiality required for update";
+ return rs->sr_err;
+ }
+
+ if( !( global_allows & SLAP_ALLOW_UPDATE_ANON ) &&
+ BER_BVISEMPTY( &op->o_ndn ) )
+ {
+ rs->sr_text = "modifications require authentication";
+ rs->sr_err = LDAP_STRONG_AUTH_REQUIRED;
+ return rs->sr_err;
+ }
+
+#ifdef SLAP_X_LISTENER_MOD
+ if ( op->o_conn->c_listener &&
+ ! ( op->o_conn->c_listener->sl_perms & ( !BER_BVISEMPTY( &op->o_ndn )
+ ? (S_IWUSR|S_IWOTH) : S_IWOTH ) ) )
+ {
+ /* no "w" mode means readonly */
+ rs->sr_text = "modifications not allowed on this listener";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err;
+ }
+#endif /* SLAP_X_LISTENER_MOD */
+ }
+ }
+
+ if ( !session ) {
+ /* these checks don't apply to Bind, StartTLS, or Unbind */
+
+ if( requires & SLAP_REQUIRE_STRONG ) {
+ /* should check mechanism */
+ if( ( op->o_transport_ssf < ssf->sss_transport
+ && op->o_authtype == LDAP_AUTH_SIMPLE )
+ || BER_BVISEMPTY( &op->o_dn ) )
+ {
+ rs->sr_text = "strong(er) authentication required";
+ rs->sr_err = LDAP_STRONG_AUTH_REQUIRED;
+ return rs->sr_err;
+ }
+ }
+
+ if( requires & SLAP_REQUIRE_SASL ) {
+ if( op->o_authtype != LDAP_AUTH_SASL || BER_BVISEMPTY( &op->o_dn ) ) {
+ rs->sr_text = "SASL authentication required";
+ rs->sr_err = LDAP_STRONG_AUTH_REQUIRED;
+ return rs->sr_err;
+ }
+ }
+
+ if( requires & SLAP_REQUIRE_AUTHC ) {
+ if( BER_BVISEMPTY( &op->o_dn ) ) {
+ rs->sr_text = "authentication required";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err;
+ }
+ }
+
+ if( requires & SLAP_REQUIRE_BIND ) {
+ int version;
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ version = op->o_conn->c_protocol;
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ if( !version ) {
+ /* no bind has occurred */
+ rs->sr_text = "BIND required";
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ return rs->sr_err;
+ }
+ }
+
+ if( requires & SLAP_REQUIRE_LDAP_V3 ) {
+ if( op->o_protocol < LDAP_VERSION3 ) {
+ /* no bind has occurred */
+ rs->sr_text = "operation restricted to LDAPv3 clients";
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ return rs->sr_err;
+ }
+ }
+
+#ifdef SLAP_X_LISTENER_MOD
+ if ( !starttls && BER_BVISEMPTY( &op->o_dn ) ) {
+ if ( op->o_conn->c_listener &&
+ !( op->o_conn->c_listener->sl_perms & S_IXOTH ))
+ {
+ /* no "x" mode means bind required */
+ rs->sr_text = "bind required on this listener";
+ rs->sr_err = LDAP_STRONG_AUTH_REQUIRED;
+ return rs->sr_err;
+ }
+ }
+
+ if ( !starttls && !updateop ) {
+ if ( op->o_conn->c_listener &&
+ !( op->o_conn->c_listener->sl_perms &
+ ( !BER_BVISEMPTY( &op->o_dn )
+ ? (S_IRUSR|S_IROTH) : S_IROTH )))
+ {
+ /* no "r" mode means no read */
+ rs->sr_text = "read not allowed on this listener";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err;
+ }
+ }
+#endif /* SLAP_X_LISTENER_MOD */
+
+ }
+
+ if( ( restrictops & opflag )
+ || ( exopflag && ( restrictops & exopflag ) )
+ || (( restrictops & SLAP_RESTRICT_READONLY ) && updateop )) {
+ if( ( restrictops & SLAP_RESTRICT_OP_MASK) == SLAP_RESTRICT_OP_READS ) {
+ rs->sr_text = "read operations restricted";
+ } else if ( restrictops & exopflag ) {
+ rs->sr_text = "extended operation restricted";
+ } else {
+ rs->sr_text = "operation restricted";
+ }
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ return rs->sr_err;
+}
+
+int backend_check_referrals( Operation *op, SlapReply *rs )
+{
+ rs->sr_err = LDAP_SUCCESS;
+
+ if( op->o_bd->be_chk_referrals ) {
+ rs->sr_err = op->o_bd->be_chk_referrals( op, rs );
+
+ if( rs->sr_err != LDAP_SUCCESS && rs->sr_err != LDAP_REFERRAL ) {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return rs->sr_err;
+}
+
+int
+be_entry_get_rw(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **e )
+{
+ *e = NULL;
+
+ if ( op->o_bd == NULL ) {
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( op->o_bd->be_fetch ) {
+ return op->o_bd->be_fetch( op, ndn, oc, at, rw, e );
+ }
+
+ return LDAP_UNWILLING_TO_PERFORM;
+}
+
+int
+fe_acl_group(
+ Operation *op,
+ Entry *target,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at )
+{
+ Entry *e;
+ void *o_priv = op->o_private, *e_priv = NULL;
+ Attribute *a;
+ int rc;
+ GroupAssertion *g;
+ Backend *be = op->o_bd;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) {
+ if ( oex->oe_key == (void *)backend_group )
+ break;
+ }
+
+ if ( oex && ((OpExtraDB *)oex)->oe_db )
+ op->o_bd = ((OpExtraDB *)oex)->oe_db;
+
+ if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd ))
+ op->o_bd = select_backend( gr_ndn, 0 );
+
+ for ( g = op->o_groups; g; g = g->ga_next ) {
+ if ( g->ga_be != op->o_bd || g->ga_oc != group_oc ||
+ g->ga_at != group_at || g->ga_len != gr_ndn->bv_len )
+ {
+ continue;
+ }
+ if ( strcmp( g->ga_ndn, gr_ndn->bv_val ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( g ) {
+ rc = g->ga_res;
+ goto done;
+ }
+
+ if ( target && dn_match( &target->e_nname, gr_ndn ) ) {
+ e = target;
+ rc = 0;
+
+ } else {
+ op->o_private = NULL;
+ rc = be_entry_get_rw( op, gr_ndn, group_oc, group_at, 0, &e );
+ e_priv = op->o_private;
+ op->o_private = o_priv;
+ }
+
+ if ( e ) {
+ a = attr_find( e->e_attrs, group_at );
+ if ( a ) {
+ /* If the attribute is a subtype of labeledURI,
+ * treat this as a dynamic group ala groupOfURLs
+ */
+ if ( is_at_subtype( group_at->ad_type,
+ slap_schema.si_ad_labeledURI->ad_type ) )
+ {
+ int i;
+ LDAPURLDesc *ludp;
+ struct berval bv, nbase;
+ Filter *filter;
+ Entry *user = NULL;
+ void *user_priv = NULL;
+ Backend *b2 = op->o_bd;
+
+ if ( target && dn_match( &target->e_nname, op_ndn ) ) {
+ user = target;
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+ for ( i = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) {
+ if ( ldap_url_parse( a->a_vals[i].bv_val, &ludp ) !=
+ LDAP_URL_SUCCESS )
+ {
+ continue;
+ }
+
+ BER_BVZERO( &nbase );
+
+ /* host, attrs and extensions parts must be empty */
+ if ( ( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs
+ || ludp->lud_exts )
+ {
+ goto loopit;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &nbase,
+ op->o_tmpmemctx ) != LDAP_SUCCESS )
+ {
+ goto loopit;
+ }
+
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( !dn_match( &nbase, op_ndn ) ) {
+ goto loopit;
+ }
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ dnParent( op_ndn, &bv );
+ if ( !dn_match( &nbase, &bv ) ) {
+ goto loopit;
+ }
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if ( !dnIsSuffix( op_ndn, &nbase ) ) {
+ goto loopit;
+ }
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( dn_match( &nbase, op_ndn ) ||
+ !dnIsSuffix( op_ndn, &nbase ) )
+ {
+ goto loopit;
+ }
+ }
+
+ /* NOTE: this could be NULL
+ * if no filter is provided,
+ * or if filter parsing fails.
+ * In the latter case,
+ * we should give up. */
+ if ( ludp->lud_filter != NULL && *ludp->lud_filter != '\0') {
+ filter = str2filter_x( op, ludp->lud_filter );
+ if ( filter == NULL ) {
+ /* give up... */
+ rc = LDAP_OTHER;
+ goto loopit;
+ }
+
+ /* only get user if required
+ * and not available yet */
+ if ( user == NULL ) {
+ int rc2;
+
+ op->o_bd = select_backend( op_ndn, 0 );
+ op->o_private = NULL;
+ rc2 = be_entry_get_rw( op, op_ndn, NULL, NULL, 0, &user );
+ user_priv = op->o_private;
+ op->o_private = o_priv;
+ if ( rc2 != 0 ) {
+ /* give up... */
+ rc = (rc2 == LDAP_NO_SUCH_OBJECT) ? rc2 : LDAP_OTHER;
+ goto nouser;
+ }
+ }
+
+ if ( test_filter( NULL, user, filter ) ==
+ LDAP_COMPARE_TRUE )
+ {
+ rc = 0;
+ }
+nouser:
+ filter_free_x( op, filter, 1 );
+ }
+loopit:
+ ldap_free_urldesc( ludp );
+ if ( !BER_BVISNULL( &nbase ) ) {
+ op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx );
+ }
+ if ( rc != LDAP_COMPARE_FALSE ) {
+ break;
+ }
+ }
+
+ if ( user != NULL && user != target ) {
+ op->o_private = user_priv;
+ be_entry_release_r( op, user );
+ op->o_private = o_priv;
+ }
+ op->o_bd = b2;
+
+ } else {
+ rc = attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ op_ndn, NULL, op->o_tmpmemctx );
+ if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) {
+ rc = LDAP_COMPARE_FALSE;
+ }
+ }
+
+ } else {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ( e != target ) {
+ op->o_private = e_priv;
+ be_entry_release_r( op, e );
+ op->o_private = o_priv;
+ }
+
+ } else {
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( op->o_tag != LDAP_REQ_BIND && !op->o_do_not_cache ) {
+ g = op->o_tmpalloc( sizeof( GroupAssertion ) + gr_ndn->bv_len,
+ op->o_tmpmemctx );
+ g->ga_be = op->o_bd;
+ g->ga_oc = group_oc;
+ g->ga_at = group_at;
+ g->ga_res = rc;
+ g->ga_len = gr_ndn->bv_len;
+ strcpy( g->ga_ndn, gr_ndn->bv_val );
+ g->ga_next = op->o_groups;
+ op->o_groups = g;
+ }
+
+done:
+ op->o_bd = be;
+ return rc;
+}
+
+int
+backend_group(
+ Operation *op,
+ Entry *target,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at )
+{
+ int rc;
+ BackendDB *be_orig;
+ OpExtraDB oex;
+
+ if ( op->o_abandon ) {
+ return SLAPD_ABANDON;
+ }
+
+ oex.oe_db = op->o_bd;
+ oex.oe.oe_key = (void *)backend_group;
+ LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next);
+
+ be_orig = op->o_bd;
+ op->o_bd = frontendDB;
+ rc = frontendDB->be_group( op, target, gr_ndn,
+ op_ndn, group_oc, group_at );
+ op->o_bd = be_orig;
+ LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next);
+
+ return rc;
+}
+
+int
+fe_acl_attribute(
+ Operation *op,
+ Entry *target,
+ struct berval *edn,
+ AttributeDescription *entry_at,
+ BerVarray *vals,
+ slap_access_t access )
+{
+ Entry *e = NULL;
+ void *o_priv = op->o_private, *e_priv = NULL;
+ Attribute *a = NULL;
+ int freeattr = 0, i, j, rc = LDAP_SUCCESS;
+ AccessControlState acl_state = ACL_STATE_INIT;
+ Backend *be = op->o_bd;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) {
+ if ( oex->oe_key == (void *)backend_attribute )
+ break;
+ }
+
+ if ( oex && ((OpExtraDB *)oex)->oe_db )
+ op->o_bd = ((OpExtraDB *)oex)->oe_db;
+
+ if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd ))
+ op->o_bd = select_backend( edn, 0 );
+
+ if ( target && dn_match( &target->e_nname, edn ) ) {
+ e = target;
+
+ } else {
+ op->o_private = NULL;
+ rc = be_entry_get_rw( op, edn, NULL, entry_at, 0, &e );
+ e_priv = op->o_private;
+ op->o_private = o_priv;
+ }
+
+ if ( e ) {
+ if ( entry_at == slap_schema.si_ad_entry || entry_at == slap_schema.si_ad_children ) {
+ assert( vals == NULL );
+
+ rc = LDAP_SUCCESS;
+ if ( op->o_conn && access > ACL_NONE &&
+ access_allowed( op, e, entry_at, NULL,
+ access, &acl_state ) == 0 )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ }
+ goto freeit;
+ }
+
+ a = attr_find( e->e_attrs, entry_at );
+ if ( a == NULL ) {
+ SlapReply rs = { REP_SEARCH };
+ AttributeName anlist[ 2 ];
+
+ anlist[ 0 ].an_name = entry_at->ad_cname;
+ anlist[ 0 ].an_desc = entry_at;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+ rs.sr_attrs = anlist;
+
+ /* NOTE: backend_operational() is also called
+ * when returning results, so it's supposed
+ * to do no harm to entries */
+ rs.sr_entry = e;
+ rc = backend_operational( op, &rs );
+
+ if ( rc == LDAP_SUCCESS ) {
+ if ( rs.sr_operational_attrs ) {
+ freeattr = 1;
+ a = rs.sr_operational_attrs;
+
+ } else {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ }
+ }
+
+ if ( a ) {
+ BerVarray v;
+
+ if ( op->o_conn && access > ACL_NONE &&
+ access_allowed( op, e, entry_at, NULL,
+ access, &acl_state ) == 0 )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto freeit;
+ }
+
+ i = a->a_numvals;
+ v = op->o_tmpalloc( sizeof(struct berval) * ( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ )
+ {
+ if ( op->o_conn && access > ACL_NONE &&
+ access_allowed( op, e, entry_at,
+ &a->a_nvals[i],
+ access,
+ &acl_state ) == 0 )
+ {
+ continue;
+ }
+ ber_dupbv_x( &v[j], &a->a_nvals[i],
+ op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &v[j] ) ) {
+ j++;
+ }
+ }
+ if ( j == 0 ) {
+ op->o_tmpfree( v, op->o_tmpmemctx );
+ *vals = NULL;
+ rc = LDAP_INSUFFICIENT_ACCESS;
+
+ } else {
+ BER_BVZERO( &v[j] );
+ *vals = v;
+ rc = LDAP_SUCCESS;
+ }
+ }
+freeit: if ( e != target ) {
+ op->o_private = e_priv;
+ be_entry_release_r( op, e );
+ op->o_private = o_priv;
+ }
+ if ( freeattr ) {
+ attr_free( a );
+ }
+ }
+
+ op->o_bd = be;
+ return rc;
+}
+
+int
+backend_attribute(
+ Operation *op,
+ Entry *target,
+ struct berval *edn,
+ AttributeDescription *entry_at,
+ BerVarray *vals,
+ slap_access_t access )
+{
+ int rc;
+ BackendDB *be_orig;
+ OpExtraDB oex;
+
+ oex.oe_db = op->o_bd;
+ oex.oe.oe_key = (void *)backend_attribute;
+ LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next);
+
+ be_orig = op->o_bd;
+ op->o_bd = frontendDB;
+ rc = frontendDB->be_attribute( op, target, edn,
+ entry_at, vals, access );
+ op->o_bd = be_orig;
+ LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next);
+
+ return rc;
+}
+
+int
+backend_access(
+ Operation *op,
+ Entry *target,
+ struct berval *edn,
+ AttributeDescription *entry_at,
+ struct berval *nval,
+ slap_access_t access,
+ slap_mask_t *mask )
+{
+ Entry *e = NULL;
+ void *o_priv, *e_priv = NULL;
+ int rc = LDAP_INSUFFICIENT_ACCESS;
+ Backend *be;
+
+ /* pedantic */
+ assert( op != NULL );
+ assert( op->o_conn != NULL );
+ assert( edn != NULL );
+ assert( access > ACL_NONE );
+
+ be = op->o_bd;
+ o_priv = op->o_private;
+
+ if ( !op->o_bd ) {
+ op->o_bd = select_backend( edn, 0 );
+ }
+
+ if ( target && dn_match( &target->e_nname, edn ) ) {
+ e = target;
+
+ } else {
+ op->o_private = NULL;
+ rc = be_entry_get_rw( op, edn, NULL, entry_at, 0, &e );
+ e_priv = op->o_private;
+ op->o_private = o_priv;
+ }
+
+ if ( e ) {
+ Attribute *a = NULL;
+ int freeattr = 0;
+
+ if ( entry_at == NULL ) {
+ entry_at = slap_schema.si_ad_entry;
+ }
+
+ if ( entry_at == slap_schema.si_ad_entry || entry_at == slap_schema.si_ad_children )
+ {
+ if ( access_allowed_mask( op, e, entry_at,
+ NULL, access, NULL, mask ) == 0 )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+
+ } else {
+ rc = LDAP_SUCCESS;
+ }
+
+ } else {
+ a = attr_find( e->e_attrs, entry_at );
+ if ( a == NULL ) {
+ SlapReply rs = { REP_SEARCH };
+ AttributeName anlist[ 2 ];
+
+ anlist[ 0 ].an_name = entry_at->ad_cname;
+ anlist[ 0 ].an_desc = entry_at;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+ rs.sr_attrs = anlist;
+
+ rs.sr_attr_flags = slap_attr_flags( rs.sr_attrs );
+
+ /* NOTE: backend_operational() is also called
+ * when returning results, so it's supposed
+ * to do no harm to entries */
+ rs.sr_entry = e;
+ rc = backend_operational( op, &rs );
+
+ if ( rc == LDAP_SUCCESS ) {
+ if ( rs.sr_operational_attrs ) {
+ freeattr = 1;
+ a = rs.sr_operational_attrs;
+
+ } else {
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+ }
+
+ if ( a ) {
+ if ( access_allowed_mask( op, e, entry_at,
+ nval, access, NULL, mask ) == 0 )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto freeit;
+ }
+ rc = LDAP_SUCCESS;
+ }
+ }
+freeit: if ( e != target ) {
+ op->o_private = e_priv;
+ be_entry_release_r( op, e );
+ op->o_private = o_priv;
+ }
+ if ( freeattr ) {
+ attr_free( a );
+ }
+ }
+
+ op->o_bd = be;
+ return rc;
+}
+
+int
+fe_aux_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ Attribute **ap;
+ int rc = LDAP_SUCCESS;
+ BackendDB *be_orig = op->o_bd;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH(oex, &op->o_extra, oe_next) {
+ if ( oex->oe_key == (void *)backend_operational )
+ break;
+ }
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
+ /* just count them */ ;
+
+ /*
+ * If operational attributes (allegedly) are required,
+ * and the backend supports specific operational attributes,
+ * add them to the attribute list
+ */
+ if ( !( rs->sr_flags & REP_NO_ENTRYDN )
+ && ( SLAP_OPATTRS( rs->sr_attr_flags ) || ( rs->sr_attrs &&
+ ad_inlist( slap_schema.si_ad_entryDN, rs->sr_attrs ) ) ) )
+ {
+ *ap = slap_operational_entryDN( rs->sr_entry );
+ ap = &(*ap)->a_next;
+ }
+
+ if ( !( rs->sr_flags & REP_NO_SUBSCHEMA)
+ && ( SLAP_OPATTRS( rs->sr_attr_flags ) || ( rs->sr_attrs &&
+ ad_inlist( slap_schema.si_ad_subschemaSubentry, rs->sr_attrs ) ) ) )
+ {
+ *ap = slap_operational_subschemaSubentry( op->o_bd );
+ ap = &(*ap)->a_next;
+ }
+
+ /* Let the overlays have a chance at this */
+ if ( oex && ((OpExtraDB *)oex)->oe_db )
+ op->o_bd = ((OpExtraDB *)oex)->oe_db;
+
+ if ( !op->o_bd || !SLAP_DBHIDDEN( op->o_bd ))
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+
+ if ( op->o_bd != NULL && !be_match( op->o_bd, frontendDB ) &&
+ ( SLAP_OPATTRS( rs->sr_attr_flags ) || rs->sr_attrs ) &&
+ op->o_bd->be_operational != NULL )
+ {
+ rc = op->o_bd->be_operational( op, rs );
+ }
+ op->o_bd = be_orig;
+
+ return rc;
+}
+
+int backend_operational( Operation *op, SlapReply *rs )
+{
+ int rc;
+ BackendDB *be_orig;
+ OpExtraDB oex;
+
+ oex.oe_db = op->o_bd;
+ oex.oe.oe_key = (void *)backend_operational;
+ LDAP_SLIST_INSERT_HEAD(&op->o_extra, &oex.oe, oe_next);
+
+ /* Moved this into the frontend so global overlays are called */
+
+ be_orig = op->o_bd;
+ op->o_bd = frontendDB;
+ rc = frontendDB->be_operational( op, rs );
+ op->o_bd = be_orig;
+ LDAP_SLIST_REMOVE(&op->o_extra, &oex.oe, OpExtra, oe_next);
+
+ return rc;
+}
+
+/* helper that calls the bi_tool_entry_first_x() variant with default args;
+ * use to initialize a backend's bi_tool_entry_first() when appropriate
+ */
+ID
+backend_tool_entry_first( BackendDB *be )
+{
+ return be->bd_info->bi_tool_entry_first_x( be,
+ NULL, LDAP_SCOPE_DEFAULT, NULL );
+}
diff --git a/servers/slapd/backglue.c b/servers/slapd/backglue.c
new file mode 100644
index 0000000..00a672d
--- /dev/null
+++ b/servers/slapd/backglue.c
@@ -0,0 +1,1603 @@
+/* backglue.c - backend glue */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/*
+ * Functions to glue a bunch of other backends into a single tree.
+ * All of the glued backends must share a common suffix. E.g., you
+ * can glue o=foo and ou=bar,o=foo but you can't glue o=foo and o=bar.
+ *
+ * The purpose of these functions is to allow you to split a single database
+ * into pieces (for load balancing purposes, whatever) but still be able
+ * to treat it as a single database after it's been split. As such, each
+ * of the glued backends should have identical rootdn.
+ * -- Howard Chu
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#define SLAPD_TOOLS
+#include "slap.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+typedef struct gluenode {
+ BackendDB *gn_be;
+ struct berval gn_pdn;
+} gluenode;
+
+typedef struct glueinfo {
+ int gi_nodes;
+ struct berval gi_pdn;
+ gluenode gi_n[1];
+} glueinfo;
+
+static slap_overinst glue;
+
+static int glueMode;
+static BackendDB *glueBack;
+static BackendDB glueBackDone;
+#define GLUEBACK_DONE (&glueBackDone)
+
+static slap_overinst * glue_tool_inst( BackendInfo *bi);
+
+static slap_response glue_op_response;
+
+/* Just like select_backend, but only for our backends */
+static BackendDB *
+glue_back_select (
+ BackendDB *be,
+ struct berval *dn
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ glueinfo *gi = (glueinfo *)on->on_bi.bi_private;
+ int i;
+
+ for (i = gi->gi_nodes-1; i >= 0; i--) {
+ assert( gi->gi_n[i].gn_be->be_nsuffix != NULL );
+
+ if (dnIsSuffix(dn, &gi->gi_n[i].gn_be->be_nsuffix[0])) {
+ return gi->gi_n[i].gn_be;
+ }
+ }
+ be->bd_info = on->on_info->oi_orig;
+ return be;
+}
+
+
+typedef struct glue_state {
+ char *matched;
+ BerVarray refs;
+ LDAPControl **ctrls;
+ int err;
+ int matchlen;
+ int nrefs;
+ int nctrls;
+} glue_state;
+
+typedef struct glue_entry {
+ BackendDB *ge_be;
+ BackendInfo *ge_bi;
+ void *ge_orig;
+} glue_entry;
+
+static int
+glue_op_cleanup( Operation *op, SlapReply *rs )
+{
+ /* This is not a final result */
+ if (rs->sr_type == REP_RESULT )
+ rs->sr_type = REP_GLUE_RESULT;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+glue_op_response ( Operation *op, SlapReply *rs )
+{
+ glue_state *gs = op->o_callback->sc_private;
+
+ switch(rs->sr_type) {
+ case REP_SEARCH:
+ case REP_SEARCHREF:
+ case REP_INTERMEDIATE:
+ return SLAP_CB_CONTINUE;
+
+ default:
+ if (rs->sr_err == LDAP_SUCCESS ||
+ rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ||
+ rs->sr_err == LDAP_TIMELIMIT_EXCEEDED ||
+ rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ||
+ rs->sr_err == LDAP_NO_SUCH_OBJECT ||
+ gs->err != LDAP_SUCCESS)
+ gs->err = rs->sr_err;
+ if (gs->err == LDAP_SUCCESS && gs->matched) {
+ ch_free (gs->matched);
+ gs->matched = NULL;
+ gs->matchlen = 0;
+ }
+ if (gs->err != LDAP_SUCCESS && rs->sr_matched) {
+ int len;
+ len = strlen (rs->sr_matched);
+ if (len > gs->matchlen) {
+ if (gs->matched)
+ ch_free (gs->matched);
+ gs->matched = ch_strdup (rs->sr_matched);
+ gs->matchlen = len;
+ }
+ }
+ if (rs->sr_ref) {
+ int i, j, k;
+ BerVarray new;
+
+ for (i=0; rs->sr_ref[i].bv_val; i++);
+
+ j = gs->nrefs;
+ if (!j) {
+ new = ch_malloc ((i+1)*sizeof(struct berval));
+ } else {
+ new = ch_realloc(gs->refs,
+ (j+i+1)*sizeof(struct berval));
+ }
+ for (k=0; k<i; j++,k++) {
+ ber_dupbv( &new[j], &rs->sr_ref[k] );
+ }
+ new[j].bv_val = NULL;
+ gs->nrefs = j;
+ gs->refs = new;
+ }
+ if (rs->sr_ctrls) {
+ int i, j, k;
+ LDAPControl **newctrls;
+
+ for (i=0; rs->sr_ctrls[i]; i++);
+
+ j = gs->nctrls;
+ if (!j) {
+ newctrls = op->o_tmpalloc((i+1)*sizeof(LDAPControl *),
+ op->o_tmpmemctx);
+ } else {
+ /* Forget old pagedResults response if we're sending
+ * a new one now
+ */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ int newpage = 0;
+ for ( k=0; k<i; k++ ) {
+ if ( !strcmp(rs->sr_ctrls[k]->ldctl_oid,
+ LDAP_CONTROL_PAGEDRESULTS )) {
+ newpage = 1;
+ break;
+ }
+ }
+ if ( newpage ) {
+ for ( k=0; k<j; k++ ) {
+ if ( !strcmp(gs->ctrls[k]->ldctl_oid,
+ LDAP_CONTROL_PAGEDRESULTS ))
+ {
+ op->o_tmpfree(gs->ctrls[k], op->o_tmpmemctx);
+ gs->ctrls[k] = gs->ctrls[--j];
+ gs->ctrls[j] = NULL;
+ break;
+ }
+ }
+ }
+ }
+ newctrls = op->o_tmprealloc(gs->ctrls,
+ (j+i+1)*sizeof(LDAPControl *), op->o_tmpmemctx);
+ }
+ for (k=0; k<i; j++,k++) {
+ ber_len_t oidlen = strlen( rs->sr_ctrls[k]->ldctl_oid );
+ newctrls[j] = op->o_tmpalloc(sizeof(LDAPControl) + oidlen + 1 + rs->sr_ctrls[k]->ldctl_value.bv_len + 1,
+ op->o_tmpmemctx);
+ newctrls[j]->ldctl_iscritical = rs->sr_ctrls[k]->ldctl_iscritical;
+ newctrls[j]->ldctl_oid = (char *)&newctrls[j][1];
+ lutil_strcopy( newctrls[j]->ldctl_oid, rs->sr_ctrls[k]->ldctl_oid );
+ if ( !BER_BVISNULL( &rs->sr_ctrls[k]->ldctl_value ) ) {
+ newctrls[j]->ldctl_value.bv_val = &newctrls[j]->ldctl_oid[oidlen + 1];
+ newctrls[j]->ldctl_value.bv_len = rs->sr_ctrls[k]->ldctl_value.bv_len;
+ lutil_memcopy( newctrls[j]->ldctl_value.bv_val,
+ rs->sr_ctrls[k]->ldctl_value.bv_val,
+ rs->sr_ctrls[k]->ldctl_value.bv_len + 1 );
+ } else {
+ BER_BVZERO( &newctrls[j]->ldctl_value );
+ }
+ }
+ newctrls[j] = NULL;
+ gs->nctrls = j;
+ gs->ctrls = newctrls;
+ }
+ }
+ return 0;
+}
+
+static int
+glue_op_func ( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ BackendDB *b0 = op->o_bd;
+ BackendInfo *bi0 = op->o_bd->bd_info, *bi1;
+ slap_operation_t which = op_bind;
+ int rc;
+
+ op->o_bd = glue_back_select (b0, &op->o_req_ndn);
+
+ /* If we're on the primary backend, let overlay framework handle it */
+ if ( op->o_bd == b0 )
+ return SLAP_CB_CONTINUE;
+
+ b0->bd_info = on->on_info->oi_orig;
+
+ switch(op->o_tag) {
+ case LDAP_REQ_ADD: which = op_add; break;
+ case LDAP_REQ_DELETE: which = op_delete; break;
+ case LDAP_REQ_MODIFY: which = op_modify; break;
+ case LDAP_REQ_MODRDN: which = op_modrdn; break;
+ case LDAP_REQ_EXTENDED: which = op_extended; break;
+ default: assert( 0 ); break;
+ }
+
+ bi1 = op->o_bd->bd_info;
+ rc = (&bi1->bi_op_bind)[ which ] ?
+ (&bi1->bi_op_bind)[ which ]( op, rs ) : SLAP_CB_BYPASS;
+
+ op->o_bd = b0;
+ op->o_bd->bd_info = bi0;
+ return rc;
+}
+
+static int
+glue_op_abandon( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ glueinfo *gi = (glueinfo *)on->on_bi.bi_private;
+ BackendDB *b0 = op->o_bd;
+ BackendInfo *bi0 = op->o_bd->bd_info;
+ int i;
+
+ b0->bd_info = on->on_info->oi_orig;
+
+ for (i = gi->gi_nodes-1; i >= 0; i--) {
+ assert( gi->gi_n[i].gn_be->be_nsuffix != NULL );
+ op->o_bd = gi->gi_n[i].gn_be;
+ if ( op->o_bd == b0 )
+ continue;
+ if ( op->o_bd->bd_info->bi_op_abandon )
+ op->o_bd->bd_info->bi_op_abandon( op, rs );
+ }
+ op->o_bd = b0;
+ op->o_bd->bd_info = bi0;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+glue_response ( Operation *op, SlapReply *rs )
+{
+ BackendDB *be = op->o_bd;
+ be = glue_back_select (op->o_bd, &op->o_req_ndn);
+
+ /* If we're on the primary backend, let overlay framework handle it.
+ * Otherwise, bail out.
+ */
+ return ( op->o_bd == be ) ? SLAP_CB_CONTINUE : SLAP_CB_BYPASS;
+}
+
+static int
+glue_chk_referrals ( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ BackendDB *b0 = op->o_bd;
+ BackendInfo *bi0 = op->o_bd->bd_info;
+ int rc;
+
+ op->o_bd = glue_back_select (b0, &op->o_req_ndn);
+ if ( op->o_bd == b0 )
+ return SLAP_CB_CONTINUE;
+
+ b0->bd_info = on->on_info->oi_orig;
+
+ if ( op->o_bd->bd_info->bi_chk_referrals )
+ rc = ( *op->o_bd->bd_info->bi_chk_referrals )( op, rs );
+ else
+ rc = SLAP_CB_CONTINUE;
+
+ op->o_bd = b0;
+ op->o_bd->bd_info = bi0;
+ return rc;
+}
+
+static int
+glue_chk_controls ( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ BackendDB *b0 = op->o_bd;
+ BackendInfo *bi0 = op->o_bd->bd_info;
+ int rc = SLAP_CB_CONTINUE;
+
+ op->o_bd = glue_back_select (b0, &op->o_req_ndn);
+ if ( op->o_bd == b0 )
+ return SLAP_CB_CONTINUE;
+
+ b0->bd_info = on->on_info->oi_orig;
+
+ /* if the subordinate database has overlays, the bi_chk_controls()
+ * hook is actually over_aux_chk_controls(); in case it actually
+ * wraps a missing hok, we need to mimic the behavior
+ * of the frontend applied to that database */
+ if ( op->o_bd->bd_info->bi_chk_controls ) {
+ rc = ( *op->o_bd->bd_info->bi_chk_controls )( op, rs );
+ }
+
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = backend_check_controls( op, rs );
+ }
+
+ op->o_bd = b0;
+ op->o_bd->bd_info = bi0;
+ return rc;
+}
+
+/* ITS#4615 - overlays configured above the glue overlay should be
+ * invoked for the entire glued tree. Overlays configured below the
+ * glue overlay should only be invoked on the primary backend.
+ * So, if we're searching on any subordinates, we need to force the
+ * current overlay chain to stop processing, without stopping the
+ * overall callback flow.
+ */
+static int
+glue_sub_search( Operation *op, SlapReply *rs, BackendDB *b0,
+ slap_overinst *on )
+{
+ /* Process any overlays on the primary backend */
+ if ( op->o_bd == b0 && on->on_next ) {
+ BackendInfo *bi = op->o_bd->bd_info;
+ int rc = SLAP_CB_CONTINUE;
+ for ( on=on->on_next; on; on=on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( on->on_bi.bi_op_search ) {
+ rc = on->on_bi.bi_op_search( op, rs );
+ if ( rc != SLAP_CB_CONTINUE )
+ break;
+ }
+ }
+ op->o_bd->bd_info = bi;
+ if ( rc != SLAP_CB_CONTINUE )
+ return rc;
+ }
+ return op->o_bd->be_search( op, rs );
+}
+
+static const ID glueID = NOID;
+static const struct berval gluecookie = { sizeof( glueID ), (char *)&glueID };
+
+static int
+glue_op_search ( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ glueinfo *gi = (glueinfo *)on->on_bi.bi_private;
+ BackendDB *b0 = op->o_bd;
+ BackendDB *b1 = NULL, *btmp;
+ BackendInfo *bi0 = op->o_bd->bd_info;
+ int i;
+ long stoptime = 0, starttime;
+ glue_state gs = {NULL, NULL, NULL, 0, 0, 0, 0};
+ slap_callback cb = { NULL, glue_op_response, glue_op_cleanup, NULL };
+ int scope0, tlimit0;
+ struct berval dn, ndn, *pdn;
+
+ cb.sc_private = &gs;
+
+ cb.sc_next = op->o_callback;
+
+ starttime = op->o_time;
+ stoptime = slap_get_time () + op->ors_tlimit;
+
+ /* reset dummy cookie used to keep paged results going across databases */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED
+ && bvmatch( &((PagedResultsState *)op->o_pagedresults_state)->ps_cookieval, &gluecookie ) )
+ {
+ PagedResultsState *ps = op->o_pagedresults_state;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval cookie = BER_BVC(""), value;
+ int c;
+
+ for (c = 0; op->o_ctrls[c] != NULL; c++) {
+ if (strcmp(op->o_ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0)
+ break;
+ }
+
+ assert( op->o_ctrls[c] != NULL );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_printf( ber, "{iO}", ps->ps_size, &cookie );
+ ber_flatten2( ber, &value, 0 );
+ assert( op->o_ctrls[c]->ldctl_value.bv_len >= value.bv_len );
+ op->o_ctrls[c]->ldctl_value.bv_len = value.bv_len;
+ lutil_memcopy( op->o_ctrls[c]->ldctl_value.bv_val,
+ value.bv_val, value.bv_len );
+ ber_free_buf( ber );
+
+ ps->ps_cookie = (PagedResultsCookie)0;
+ BER_BVZERO( &ps->ps_cookieval );
+ }
+
+ op->o_bd = glue_back_select (b0, &op->o_req_ndn);
+ b0->bd_info = on->on_info->oi_orig;
+
+ switch (op->ors_scope) {
+ case LDAP_SCOPE_BASE:
+ if ( op->o_bd == b0 )
+ return SLAP_CB_CONTINUE;
+
+ if (op->o_bd && op->o_bd->be_search) {
+ rs->sr_err = op->o_bd->be_search( op, rs );
+ } else {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+ return rs->sr_err;
+
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE: /* FIXME */
+ op->o_callback = &cb;
+ rs->sr_err = gs.err = LDAP_UNWILLING_TO_PERFORM;
+ scope0 = op->ors_scope;
+ tlimit0 = op->ors_tlimit;
+ dn = op->o_req_dn;
+ ndn = op->o_req_ndn;
+ b1 = op->o_bd;
+
+ /*
+ * Execute in reverse order, most specific first
+ */
+ for (i = gi->gi_nodes; i >= 0; i--) {
+ if ( i == gi->gi_nodes ) {
+ btmp = b0;
+ pdn = &gi->gi_pdn;
+ } else {
+ btmp = gi->gi_n[i].gn_be;
+ pdn = &gi->gi_n[i].gn_pdn;
+ }
+ if (!btmp || !btmp->be_search)
+ continue;
+ if (!dnIsSuffix(&btmp->be_nsuffix[0], &b1->be_nsuffix[0]))
+ continue;
+ if (get_no_subordinate_glue(op) && btmp != b1)
+ continue;
+ /* If we remembered which backend we were on before,
+ * skip down to it now
+ */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED &&
+ op->o_conn->c_pagedresults_state.ps_be &&
+ op->o_conn->c_pagedresults_state.ps_be != btmp )
+ continue;
+
+ if (tlimit0 != SLAP_NO_LIMIT) {
+ op->o_time = slap_get_time();
+ op->ors_tlimit = stoptime - op->o_time;
+ if (op->ors_tlimit <= 0) {
+ rs->sr_err = gs.err = LDAP_TIMELIMIT_EXCEEDED;
+ break;
+ }
+ }
+ rs->sr_err = 0;
+ /*
+ * check for abandon
+ */
+ if (op->o_abandon) {
+ goto end_of_loop;
+ }
+ op->o_bd = btmp;
+
+ assert( op->o_bd->be_suffix != NULL );
+ assert( op->o_bd->be_nsuffix != NULL );
+
+ if (scope0 == LDAP_SCOPE_ONELEVEL &&
+ dn_match(pdn, &ndn))
+ {
+ struct berval mdn, mndn;
+ op->ors_scope = LDAP_SCOPE_BASE;
+ mdn = op->o_req_dn = op->o_bd->be_suffix[0];
+ mndn = op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ rs->sr_err = op->o_bd->be_search(op, rs);
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ gs.err = LDAP_SUCCESS;
+ }
+ op->ors_scope = LDAP_SCOPE_ONELEVEL;
+ if ( op->o_req_dn.bv_val == mdn.bv_val )
+ op->o_req_dn = dn;
+ if ( op->o_req_ndn.bv_val == mndn.bv_val )
+ op->o_req_ndn = ndn;
+
+ } else if (scope0 == LDAP_SCOPE_SUBTREE &&
+ dn_match(&op->o_bd->be_nsuffix[0], &ndn))
+ {
+ rs->sr_err = glue_sub_search( op, rs, b0, on );
+
+ } else if (scope0 == LDAP_SCOPE_SUBTREE &&
+ dnIsSuffix(&op->o_bd->be_nsuffix[0], &ndn))
+ {
+ struct berval mdn, mndn;
+ mdn = op->o_req_dn = op->o_bd->be_suffix[0];
+ mndn = op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ rs->sr_err = glue_sub_search( op, rs, b0, on );
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ gs.err = LDAP_SUCCESS;
+ }
+ if ( op->o_req_dn.bv_val == mdn.bv_val )
+ op->o_req_dn = dn;
+ if ( op->o_req_ndn.bv_val == mndn.bv_val )
+ op->o_req_ndn = ndn;
+
+ } else if (dnIsSuffix(&ndn, &op->o_bd->be_nsuffix[0])) {
+ rs->sr_err = glue_sub_search( op, rs, b0, on );
+ }
+
+ if ( rs->sr_err == SLAPD_NO_REPLY ) {
+ gs.err = rs->sr_err;
+ break;
+ }
+
+ switch ( gs.err ) {
+
+ /*
+ * Add errors that should result in dropping
+ * the search
+ */
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_ADMINLIMIT_EXCEEDED:
+ case LDAP_NO_SUCH_OBJECT:
+#ifdef LDAP_CONTROL_X_CHAINING_BEHAVIOR
+ case LDAP_X_CANNOT_CHAIN:
+#endif /* LDAP_CONTROL_X_CHAINING_BEHAVIOR */
+ goto end_of_loop;
+
+ case LDAP_SUCCESS:
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* Assume this backend can be forgotten now */
+ op->o_conn->c_pagedresults_state.ps_be = NULL;
+
+ /* If we have a full page, exit the loop. We may
+ * need to remember this backend so we can continue
+ * from here on a subsequent request.
+ */
+ if ( rs->sr_nentries >= ps->ps_size ) {
+ PagedResultsState *cps = &op->o_conn->c_pagedresults_state;
+
+ /* Don't bother to remember the first backend.
+ * Only remember the last one if there's more state left.
+ */
+ if ( op->o_bd != b0 &&
+ ( cps->ps_cookie != NOID
+ || !BER_BVISNULL( &cps->ps_cookieval )
+ || op->o_bd != gi->gi_n[0].gn_be ) )
+ {
+ op->o_conn->c_pagedresults_state.ps_be = op->o_bd;
+ }
+
+ /* Check whether the cookie is empty,
+ * and give remaining databases a chance
+ */
+ if ( op->o_bd != gi->gi_n[0].gn_be || cps->ps_cookie == NOID ) {
+ int c;
+
+ for ( c = 0; gs.ctrls[c] != NULL; c++ ) {
+ if ( strcmp( gs.ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( gs.ctrls[c] != NULL ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_int_t size;
+ struct berval cookie, value;
+
+ ber_init2( ber, &gs.ctrls[c]->ldctl_value, LBER_USE_DER );
+
+ tag = ber_scanf( ber, "{im}", &size, &cookie );
+ assert( tag != LBER_ERROR );
+
+ if ( BER_BVISEMPTY( &cookie ) && op->o_bd != gi->gi_n[0].gn_be ) {
+ /* delete old, create new cookie with NOID */
+ PagedResultsCookie respcookie = (PagedResultsCookie)NOID;
+ ber_len_t oidlen = strlen( gs.ctrls[c]->ldctl_oid );
+ LDAPControl *newctrl;
+
+ /* it's next database's turn */
+ if ( btmp == b0 ) {
+ op->o_conn->c_pagedresults_state.ps_be = gi->gi_n[gi->gi_nodes - 1].gn_be;
+
+ } else {
+ op->o_conn->c_pagedresults_state.ps_be = gi->gi_n[(i > 0 ? i - 1: 0)].gn_be;
+ }
+
+ cookie.bv_val = (char *)&respcookie;
+ cookie.bv_len = sizeof( PagedResultsCookie );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_printf( ber, "{iO}", 0, &cookie );
+ ber_flatten2( ber, &value, 0 );
+
+ newctrl = op->o_tmprealloc( gs.ctrls[c],
+ sizeof(LDAPControl) + oidlen + 1 + value.bv_len + 1,
+ op->o_tmpmemctx);
+ newctrl->ldctl_iscritical = gs.ctrls[c]->ldctl_iscritical;
+ newctrl->ldctl_oid = (char *)&newctrl[1];
+ lutil_strcopy( newctrl->ldctl_oid, gs.ctrls[c]->ldctl_oid );
+ newctrl->ldctl_value.bv_len = value.bv_len;
+ lutil_memcopy( newctrl->ldctl_value.bv_val,
+ value.bv_val, value.bv_len );
+
+ gs.ctrls[c] = newctrl;
+
+ ber_free_buf( ber );
+
+ } else if ( !BER_BVISEMPTY( &cookie ) && op->o_bd != b0 ) {
+ /* if cookie not empty, it's again this database's turn */
+ op->o_conn->c_pagedresults_state.ps_be = op->o_bd;
+ }
+ }
+ }
+
+ goto end_of_loop;
+ }
+
+ /* This backend has run out of entries, but more responses
+ * can fit in the page. Fake a reset of the state so the
+ * next backend will start up properly. Only back-[bh]db
+ * and back-sql look at this state info.
+ */
+ ps->ps_cookie = (PagedResultsCookie)0;
+ BER_BVZERO( &ps->ps_cookieval );
+
+ {
+ /* change the size of the page in the request
+ * that will be propagated, and reset the cookie */
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ int size = ps->ps_size - rs->sr_nentries;
+ struct berval cookie = BER_BVC(""), value;
+ int c;
+
+ for (c = 0; op->o_ctrls[c] != NULL; c++) {
+ if (strcmp(op->o_ctrls[c]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0)
+ break;
+ }
+
+ assert( op->o_ctrls[c] != NULL );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_printf( ber, "{iO}", size, &cookie );
+ ber_flatten2( ber, &value, 0 );
+ assert( op->o_ctrls[c]->ldctl_value.bv_len >= value.bv_len );
+ op->o_ctrls[c]->ldctl_value.bv_len = value.bv_len;
+ lutil_memcopy( op->o_ctrls[c]->ldctl_value.bv_val,
+ value.bv_val, value.bv_len );
+ ber_free_buf( ber );
+ }
+ }
+
+ default:
+ break;
+ }
+ }
+end_of_loop:;
+ op->ors_scope = scope0;
+ op->ors_tlimit = tlimit0;
+ op->o_time = starttime;
+
+ break;
+ }
+
+ op->o_callback = cb.sc_next;
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ } else {
+ rs->sr_err = gs.err;
+ rs->sr_matched = gs.matched;
+ rs->sr_ref = gs.refs;
+ }
+ rs->sr_ctrls = gs.ctrls;
+
+ if ( rs->sr_err != SLAPD_NO_REPLY )
+ send_ldap_result( op, rs );
+
+ op->o_bd = b0;
+ op->o_bd->bd_info = bi0;
+ if (gs.matched)
+ free (gs.matched);
+ if (gs.refs)
+ ber_bvarray_free(gs.refs);
+ if (gs.ctrls) {
+ for (i = gs.nctrls; --i >= 0; ) {
+ op->o_tmpfree(gs.ctrls[i], op->o_tmpmemctx);
+ }
+ op->o_tmpfree(gs.ctrls, op->o_tmpmemctx);
+ }
+ return rs->sr_err;
+}
+
+static BackendDB toolDB;
+
+static int
+glue_tool_entry_open (
+ BackendDB *b0,
+ int mode
+)
+{
+ slap_overinfo *oi = (slap_overinfo *)b0->bd_info;
+
+ /* We don't know which backend to talk to yet, so just
+ * remember the mode and move on...
+ */
+
+ glueMode = mode;
+ glueBack = NULL;
+ toolDB = *b0;
+ toolDB.bd_info = oi->oi_orig;
+
+ /* Sanity checks */
+ {
+ slap_overinst *on = glue_tool_inst( b0->bd_info );
+ glueinfo *gi = on->on_bi.bi_private;
+
+ int i;
+ for (i = 0; i < gi->gi_nodes; i++) {
+ BackendDB *bd;
+ struct berval pdn;
+
+ dnParent( &gi->gi_n[i].gn_be->be_nsuffix[0], &pdn );
+ bd = select_backend( &pdn, 0 );
+ if ( bd ) {
+ ID id;
+ BackendDB db;
+
+ if ( overlay_is_over( bd ) ) {
+ slap_overinfo *oi = (slap_overinfo *)bd->bd_info;
+ db = *bd;
+ db.bd_info = oi->oi_orig;
+ bd = &db;
+ }
+
+ if ( !bd->bd_info->bi_tool_dn2id_get
+ || !bd->bd_info->bi_tool_entry_open
+ || !bd->bd_info->bi_tool_entry_close )
+ {
+ continue;
+ }
+
+ bd->bd_info->bi_tool_entry_open( bd, 0 );
+ id = bd->bd_info->bi_tool_dn2id_get( bd, &gi->gi_n[i].gn_be->be_nsuffix[0] );
+ bd->bd_info->bi_tool_entry_close( bd );
+ if ( id != NOID ) {
+ Debug( LDAP_DEBUG_ANY,
+ "glue_tool_entry_open: subordinate database suffix entry DN=\"%s\" also present in superior database rooted at DN=\"%s\"\n",
+ gi->gi_n[i].gn_be->be_suffix[0].bv_val, bd->be_suffix[0].bv_val );
+ return LDAP_OTHER;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+glue_tool_entry_close (
+ BackendDB *b0
+)
+{
+ int rc = 0;
+
+ if (glueBack && glueBack != GLUEBACK_DONE) {
+ if (!glueBack->be_entry_close)
+ return 0;
+ rc = glueBack->be_entry_close (glueBack);
+ }
+ return rc;
+}
+
+static slap_overinst *
+glue_tool_inst(
+ BackendInfo *bi
+)
+{
+ slap_overinfo *oi = (slap_overinfo *)bi;
+ slap_overinst *on;
+
+ for ( on = oi->oi_list; on; on=on->on_next ) {
+ if ( !strcmp( on->on_bi.bi_type, glue.on_bi.bi_type ))
+ return on;
+ }
+ return NULL;
+}
+
+/* This function will only be called in tool mode */
+static int
+glue_open (
+ BackendInfo *bi
+)
+{
+ slap_overinst *on = glue_tool_inst( bi );
+ glueinfo *gi = on->on_bi.bi_private;
+ static int glueOpened = 0;
+ int i, j, same, bsame = 0, rc = 0;
+ ConfigReply cr = {0};
+
+ if (glueOpened) return 0;
+
+ glueOpened = 1;
+
+ /* If we were invoked in tool mode, open all the underlying backends */
+ if (slapMode & SLAP_TOOL_MODE) {
+ for (i = 0; i<gi->gi_nodes; i++) {
+ same = 0;
+ /* Same bi_open as our main backend? */
+ if ( gi->gi_n[i].gn_be->bd_info->bi_open ==
+ on->on_info->oi_orig->bi_open )
+ bsame = 1;
+
+ /* Loop thru the bd_info's and make sure we only
+ * invoke their bi_open functions once each.
+ */
+ for ( j = 0; j<i; j++ ) {
+ if ( gi->gi_n[i].gn_be->bd_info->bi_open ==
+ gi->gi_n[j].gn_be->bd_info->bi_open ) {
+ same = 1;
+ break;
+ }
+ }
+ /* OK, it's unique and non-NULL, call it. */
+ if ( !same && gi->gi_n[i].gn_be->bd_info->bi_open )
+ rc = gi->gi_n[i].gn_be->bd_info->bi_open(
+ gi->gi_n[i].gn_be->bd_info );
+ /* Let backend.c take care of the rest of startup */
+ if ( !rc )
+ rc = backend_startup_one( gi->gi_n[i].gn_be, &cr );
+ if ( rc ) break;
+ }
+ if ( !rc && !bsame && on->on_info->oi_orig->bi_open )
+ rc = on->on_info->oi_orig->bi_open( on->on_info->oi_orig );
+
+ } /* other case is impossible */
+ return rc;
+}
+
+/* This function will only be called in tool mode */
+static int
+glue_close (
+ BackendInfo *bi
+)
+{
+ static int glueClosed = 0;
+ int rc = 0;
+
+ if (glueClosed) return 0;
+
+ glueClosed = 1;
+
+ if (slapMode & SLAP_TOOL_MODE) {
+ rc = backend_shutdown( NULL );
+ }
+ return rc;
+}
+
+static int
+glue_entry_get_rw (
+ Operation *op,
+ struct berval *dn,
+ ObjectClass *oc,
+ AttributeDescription *ad,
+ int rw,
+ Entry **e )
+{
+ int rc;
+ BackendDB *b0 = op->o_bd;
+ op->o_bd = glue_back_select( b0, dn );
+
+ if ( op->o_bd->be_fetch ) {
+ rc = op->o_bd->be_fetch( op, dn, oc, ad, rw, e );
+ if ( rc == LDAP_SUCCESS && *e ) {
+ glue_entry *ge = op->o_tmpcalloc( 1, sizeof(glue_entry),
+ op->o_tmpmemctx );
+
+ if ( !ge ) {
+ if ( op->o_bd->be_release ) {
+ op->o_bd->be_release( op, *e, rw );
+ } else {
+ entry_free( *e );
+ }
+ rc = LDAP_OTHER;
+ goto out;
+ }
+
+ /* b0 is on overlay_entry_get_ov's stack, we'll be passed a fresh
+ * one at release time */
+ if ( op->o_bd != b0 ) {
+ ge->ge_be = op->o_bd;
+ }
+ ge->ge_bi = op->o_bd->bd_info;
+ ge->ge_orig = (*e)->e_private;
+ (*e)->e_private = ge;
+ }
+ } else {
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+out:
+ op->o_bd = b0;
+ return rc;
+}
+
+static int
+glue_entry_release_rw (
+ Operation *op,
+ Entry *e,
+ int rw
+)
+{
+ glue_entry *ge = e->e_private;
+ BackendDB *b0 = op->o_bd;
+ int rc = -1;
+
+ if ( glueBack ) {
+ op->o_bd = glueBack;
+ } else if ( ge ) {
+ assert( ge->ge_bi != NULL );
+ if ( ge->ge_be )
+ op->o_bd = ge->ge_be;
+ op->o_bd->bd_info = ge->ge_bi;
+ e->e_private = ge->ge_orig;
+ op->o_tmpfree( ge, op->o_tmpmemctx );
+ } else {
+ op->o_bd = glue_back_select( b0, &e->e_nname );
+ }
+
+ if ( op->o_bd->be_release ) {
+ rc = op->o_bd->be_release( op, e, rw );
+
+ } else {
+ /* FIXME: mimic be_entry_release_rw
+ * when no be_release() available */
+ /* free entry */
+ entry_free( e );
+ rc = 0;
+ }
+ op->o_bd = b0;
+ return rc;
+}
+
+static struct berval *glue_base;
+static int glue_scope;
+static Filter *glue_filter;
+
+static ID
+glue_tool_entry_first (
+ BackendDB *b0
+)
+{
+ slap_overinst *on = glue_tool_inst( b0->bd_info );
+ glueinfo *gi = on->on_bi.bi_private;
+ int i;
+ ID rc;
+
+ /* If we're starting from scratch, start at the most general */
+ if (!glueBack) {
+ if ( toolDB.be_entry_open && toolDB.be_entry_first ) {
+ glueBack = &toolDB;
+ } else {
+ for (i = gi->gi_nodes-1; i >= 0; i--) {
+ if (gi->gi_n[i].gn_be->be_entry_open &&
+ gi->gi_n[i].gn_be->be_entry_first) {
+ glueBack = gi->gi_n[i].gn_be;
+ break;
+ }
+ }
+ }
+ }
+ if (!glueBack || !glueBack->be_entry_open || !glueBack->be_entry_first ||
+ glueBack->be_entry_open (glueBack, glueMode) != 0)
+ return NOID;
+
+ rc = glueBack->be_entry_first (glueBack);
+ while ( rc == NOID ) {
+ if ( glueBack && glueBack->be_entry_close )
+ glueBack->be_entry_close (glueBack);
+ for (i=0; i<gi->gi_nodes; i++) {
+ if (gi->gi_n[i].gn_be == glueBack)
+ break;
+ }
+ if (i == 0) {
+ glueBack = GLUEBACK_DONE;
+ break;
+ } else {
+ glueBack = gi->gi_n[i-1].gn_be;
+ rc = glue_tool_entry_first (b0);
+ if ( glueBack == GLUEBACK_DONE ) {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+static ID
+glue_tool_entry_first_x (
+ BackendDB *b0,
+ struct berval *base,
+ int scope,
+ Filter *f
+)
+{
+ slap_overinst *on = glue_tool_inst( b0->bd_info );
+ glueinfo *gi = on->on_bi.bi_private;
+ int i;
+ ID rc;
+
+ glue_base = base;
+ glue_scope = scope;
+ glue_filter = f;
+
+ /* If we're starting from scratch, start at the most general */
+ if (!glueBack) {
+ if ( toolDB.be_entry_open && toolDB.be_entry_first_x ) {
+ glueBack = &toolDB;
+ } else {
+ for (i = gi->gi_nodes-1; i >= 0; i--) {
+ if (gi->gi_n[i].gn_be->be_entry_open &&
+ gi->gi_n[i].gn_be->be_entry_first_x)
+ {
+ glueBack = gi->gi_n[i].gn_be;
+ break;
+ }
+ }
+ }
+ }
+ if (!glueBack || !glueBack->be_entry_open || !glueBack->be_entry_first_x ||
+ glueBack->be_entry_open (glueBack, glueMode) != 0)
+ return NOID;
+
+ rc = glueBack->be_entry_first_x (glueBack,
+ glue_base, glue_scope, glue_filter);
+ while ( rc == NOID ) {
+ if ( glueBack && glueBack->be_entry_close )
+ glueBack->be_entry_close (glueBack);
+ for (i=0; i<gi->gi_nodes; i++) {
+ if (gi->gi_n[i].gn_be == glueBack)
+ break;
+ }
+ if (i == 0) {
+ glueBack = GLUEBACK_DONE;
+ break;
+ } else {
+ glueBack = gi->gi_n[i-1].gn_be;
+ rc = glue_tool_entry_first_x (b0,
+ glue_base, glue_scope, glue_filter);
+ if ( glueBack == GLUEBACK_DONE ) {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+static ID
+glue_tool_entry_next (
+ BackendDB *b0
+)
+{
+ slap_overinst *on = glue_tool_inst( b0->bd_info );
+ glueinfo *gi = on->on_bi.bi_private;
+ int i;
+ ID rc;
+
+ if (!glueBack || !glueBack->be_entry_next)
+ return NOID;
+
+ rc = glueBack->be_entry_next (glueBack);
+
+ /* If we ran out of entries in one database, move on to the next */
+ while (rc == NOID) {
+ if ( glueBack && glueBack->be_entry_close )
+ glueBack->be_entry_close (glueBack);
+ for (i=0; i<gi->gi_nodes; i++) {
+ if (gi->gi_n[i].gn_be == glueBack)
+ break;
+ }
+ if (i == 0) {
+ glueBack = GLUEBACK_DONE;
+ break;
+ } else {
+ glueBack = gi->gi_n[i-1].gn_be;
+ if ( glue_base || glue_filter ) {
+ /* using entry_first_x() */
+ rc = glue_tool_entry_first_x (b0,
+ glue_base, glue_scope, glue_filter);
+
+ } else {
+ /* using entry_first() */
+ rc = glue_tool_entry_first (b0);
+ }
+ if ( glueBack == GLUEBACK_DONE ) {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+static ID
+glue_tool_dn2id_get (
+ BackendDB *b0,
+ struct berval *dn
+)
+{
+ BackendDB *be, b2;
+ int rc = -1;
+
+ b2 = *b0;
+ b2.bd_info = (BackendInfo *)glue_tool_inst( b0->bd_info );
+ be = glue_back_select (&b2, dn);
+ if ( be == &b2 ) be = &toolDB;
+
+ if (!be->be_dn2id_get)
+ return NOID;
+
+ if (!glueBack) {
+ if ( be->be_entry_open ) {
+ rc = be->be_entry_open (be, glueMode);
+ }
+ if (rc != 0) {
+ return NOID;
+ }
+ } else if (be != glueBack) {
+ /* If this entry belongs in a different branch than the
+ * previous one, close the current database and open the
+ * new one.
+ */
+ if ( glueBack->be_entry_close ) {
+ glueBack->be_entry_close (glueBack);
+ }
+ if ( be->be_entry_open ) {
+ rc = be->be_entry_open (be, glueMode);
+ }
+ if (rc != 0) {
+ return NOID;
+ }
+ }
+ glueBack = be;
+ return be->be_dn2id_get (be, dn);
+}
+
+static Entry *
+glue_tool_entry_get (
+ BackendDB *b0,
+ ID id
+)
+{
+ if (!glueBack || !glueBack->be_entry_get)
+ return NULL;
+
+ return glueBack->be_entry_get (glueBack, id);
+}
+
+static ID
+glue_tool_entry_put (
+ BackendDB *b0,
+ Entry *e,
+ struct berval *text
+)
+{
+ BackendDB *be, b2;
+ int rc = -1;
+
+ b2 = *b0;
+ b2.bd_info = (BackendInfo *)glue_tool_inst( b0->bd_info );
+ be = glue_back_select (&b2, &e->e_nname);
+ if ( be == &b2 ) be = &toolDB;
+
+ if (!be->be_entry_put)
+ return NOID;
+
+ if (!glueBack) {
+ if ( be->be_entry_open ) {
+ rc = be->be_entry_open (be, glueMode);
+ }
+ if (rc != 0) {
+ return NOID;
+ }
+ } else if (be != glueBack) {
+ /* If this entry belongs in a different branch than the
+ * previous one, close the current database and open the
+ * new one.
+ */
+ if ( glueBack->be_entry_close ) {
+ glueBack->be_entry_close (glueBack);
+ }
+ if ( be->be_entry_open ) {
+ rc = be->be_entry_open (be, glueMode);
+ }
+ if (rc != 0) {
+ return NOID;
+ }
+ }
+ glueBack = be;
+ return be->be_entry_put (be, e, text);
+}
+
+static ID
+glue_tool_entry_modify (
+ BackendDB *b0,
+ Entry *e,
+ struct berval *text
+)
+{
+ if (!glueBack || !glueBack->be_entry_modify)
+ return NOID;
+
+ return glueBack->be_entry_modify (glueBack, e, text);
+}
+
+static int
+glue_tool_entry_reindex (
+ BackendDB *b0,
+ ID id,
+ AttributeDescription **adv
+)
+{
+ if (!glueBack || !glueBack->be_entry_reindex)
+ return -1;
+
+ return glueBack->be_entry_reindex (glueBack, id, adv);
+}
+
+static int
+glue_tool_sync (
+ BackendDB *b0
+)
+{
+ slap_overinst *on = glue_tool_inst( b0->bd_info );
+ glueinfo *gi = on->on_bi.bi_private;
+ BackendInfo *bi = b0->bd_info;
+ int i;
+
+ /* just sync everyone */
+ for (i = 0; i<gi->gi_nodes; i++)
+ if (gi->gi_n[i].gn_be->be_sync)
+ gi->gi_n[i].gn_be->be_sync (gi->gi_n[i].gn_be);
+ b0->bd_info = on->on_info->oi_orig;
+ if ( b0->be_sync )
+ b0->be_sync( b0 );
+ b0->bd_info = bi;
+ return 0;
+}
+
+typedef struct glue_Addrec {
+ struct glue_Addrec *ga_next;
+ BackendDB *ga_be;
+} glue_Addrec;
+
+/* List of added subordinates */
+static glue_Addrec *ga_list;
+static int ga_adding;
+
+static int
+glue_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ slap_overinfo *oi = on->on_info;
+ BackendInfo *bi = oi->oi_orig;
+ glueinfo *gi;
+
+ if ( SLAP_GLUE_SUBORDINATE( be )) {
+ Debug( LDAP_DEBUG_ANY, "glue: backend %s is already subordinate, "
+ "cannot have glue overlay!\n",
+ be->be_suffix[0].bv_val );
+ return LDAP_OTHER;
+ }
+
+ gi = ch_calloc( 1, sizeof(glueinfo));
+ on->on_bi.bi_private = gi;
+ dnParent( be->be_nsuffix, &gi->gi_pdn );
+
+ /* Currently the overlay framework doesn't handle these entry points
+ * but we need them....
+ */
+ oi->oi_bi.bi_open = glue_open;
+ oi->oi_bi.bi_close = glue_close;
+
+ /* Only advertise these if the root DB supports them */
+ if ( bi->bi_tool_entry_open )
+ oi->oi_bi.bi_tool_entry_open = glue_tool_entry_open;
+ if ( bi->bi_tool_entry_close )
+ oi->oi_bi.bi_tool_entry_close = glue_tool_entry_close;
+ if ( bi->bi_tool_entry_first )
+ oi->oi_bi.bi_tool_entry_first = glue_tool_entry_first;
+ /* FIXME: check whether all support bi_tool_entry_first_x() ? */
+ if ( bi->bi_tool_entry_first_x )
+ oi->oi_bi.bi_tool_entry_first_x = glue_tool_entry_first_x;
+ if ( bi->bi_tool_entry_next )
+ oi->oi_bi.bi_tool_entry_next = glue_tool_entry_next;
+ if ( bi->bi_tool_entry_get )
+ oi->oi_bi.bi_tool_entry_get = glue_tool_entry_get;
+ if ( bi->bi_tool_dn2id_get )
+ oi->oi_bi.bi_tool_dn2id_get = glue_tool_dn2id_get;
+ if ( bi->bi_tool_entry_put )
+ oi->oi_bi.bi_tool_entry_put = glue_tool_entry_put;
+ if ( bi->bi_tool_entry_reindex )
+ oi->oi_bi.bi_tool_entry_reindex = glue_tool_entry_reindex;
+ if ( bi->bi_tool_entry_modify )
+ oi->oi_bi.bi_tool_entry_modify = glue_tool_entry_modify;
+ if ( bi->bi_tool_sync )
+ oi->oi_bi.bi_tool_sync = glue_tool_sync;
+
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_INSTANCE;
+
+ if ( ga_list && ( slapMode & SLAP_SERVER_MODE ) ) {
+ be->bd_info = (BackendInfo *)oi;
+ glue_sub_attach( 1 );
+ }
+
+ return 0;
+}
+
+static int
+glue_db_destroy (
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ glueinfo *gi = (glueinfo *)on->on_bi.bi_private;
+
+ free (gi);
+ return 0;
+}
+
+static int
+glue_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+
+ on->on_info->oi_bi.bi_db_close = 0;
+ return 0;
+}
+
+int
+glue_sub_del( BackendDB *b0 )
+{
+ BackendDB *be;
+ int rc = 0;
+
+ /* Find the top backend for this subordinate */
+ be = b0;
+ while ( (be=LDAP_STAILQ_NEXT( be, be_next )) != NULL ) {
+ slap_overinfo *oi;
+ slap_overinst *on;
+ glueinfo *gi;
+ int i;
+
+ if ( SLAP_GLUE_SUBORDINATE( be ))
+ continue;
+ if ( !SLAP_GLUE_INSTANCE( be ))
+ continue;
+ if ( !dnIsSuffix( &b0->be_nsuffix[0], &be->be_nsuffix[0] ))
+ continue;
+
+ /* OK, got the right backend, find the overlay */
+ oi = (slap_overinfo *)be->bd_info;
+ for ( on=oi->oi_list; on; on=on->on_next ) {
+ if ( on->on_bi.bi_type == glue.on_bi.bi_type )
+ break;
+ }
+ assert( on != NULL );
+ gi = on->on_bi.bi_private;
+ for ( i=0; i < gi->gi_nodes; i++ ) {
+ if ( gi->gi_n[i].gn_be == b0 ) {
+ int j;
+
+ for (j=i+1; j < gi->gi_nodes; j++)
+ gi->gi_n[j-1] = gi->gi_n[j];
+
+ gi->gi_nodes--;
+ }
+ }
+ /* Mark as no longer linked/sub */
+ b0->be_flags &= ~(SLAP_DBFLAG_GLUE_SUBORDINATE|SLAP_DBFLAG_GLUE_LINKED|
+ SLAP_DBFLAG_GLUE_ADVERTISE);
+ b0->be_pcsn_p = &b0->be_pcsn_st;
+ break;
+ }
+ if ( be == NULL )
+ rc = LDAP_NO_SUCH_OBJECT;
+
+ return rc;
+}
+
+
+/* Attach all the subordinate backends to their superior */
+int
+glue_sub_attach( int online )
+{
+ glue_Addrec *ga, *gnext = NULL;
+ int rc = 0;
+
+ if ( ga_adding )
+ return 0;
+
+ ga_adding = 1;
+
+ /* For all the subordinate backends */
+ for ( ga=ga_list; ga != NULL; ga = gnext ) {
+ BackendDB *be;
+
+ gnext = ga->ga_next;
+
+ /* Find the top backend for this subordinate */
+ be = ga->ga_be;
+ while ( (be=LDAP_STAILQ_NEXT( be, be_next )) != NULL ) {
+ slap_overinfo *oi;
+ slap_overinst *on;
+ glueinfo *gi;
+
+ if ( SLAP_GLUE_SUBORDINATE( be ))
+ continue;
+ if ( !dnIsSuffix( &ga->ga_be->be_nsuffix[0], &be->be_nsuffix[0] ))
+ continue;
+
+ /* If it's not already configured, set up the overlay */
+ if ( !SLAP_GLUE_INSTANCE( be )) {
+ rc = overlay_config( be, glue.on_bi.bi_type, -1, NULL, NULL);
+ if ( rc )
+ break;
+ }
+ /* Find the overlay instance */
+ oi = (slap_overinfo *)be->bd_info;
+ for ( on=oi->oi_list; on; on=on->on_next ) {
+ if ( on->on_bi.bi_type == glue.on_bi.bi_type )
+ break;
+ }
+ assert( on != NULL );
+ gi = on->on_bi.bi_private;
+ gi = (glueinfo *)ch_realloc( gi, sizeof(glueinfo) +
+ gi->gi_nodes * sizeof(gluenode));
+ gi->gi_n[gi->gi_nodes].gn_be = ga->ga_be;
+ dnParent( &ga->ga_be->be_nsuffix[0],
+ &gi->gi_n[gi->gi_nodes].gn_pdn );
+ gi->gi_nodes++;
+ on->on_bi.bi_private = gi;
+ ga->ga_be->be_pcsn_p = be->be_pcsn_p;
+ ga->ga_be->be_flags |= SLAP_DBFLAG_GLUE_LINKED;
+ break;
+ }
+ if ( !be ) {
+ Debug( LDAP_DEBUG_ANY, "glue: no superior found for sub %s!\n",
+ ga->ga_be->be_suffix[0].bv_val );
+ /* allow this for now, assume a superior will
+ * be added later
+ */
+ if ( online ) {
+ rc = 0;
+ gnext = ga_list;
+ break;
+ }
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ ch_free( ga );
+ if ( rc ) break;
+ }
+
+ ga_list = gnext;
+
+ ga_adding = 0;
+
+ return rc;
+}
+
+int
+glue_sub_add( BackendDB *be, int advert, int online )
+{
+ glue_Addrec *ga;
+ int rc = 0;
+
+ if ( overlay_is_inst( be, "glue" )) {
+ Debug( LDAP_DEBUG_ANY, "glue: backend %s already has glue overlay, "
+ "cannot be a subordinate!\n",
+ be->be_suffix[0].bv_val );
+ return LDAP_OTHER;
+ }
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_SUBORDINATE;
+ if ( advert )
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLUE_ADVERTISE;
+
+ ga = ch_malloc( sizeof( glue_Addrec ));
+ ga->ga_next = ga_list;
+ ga->ga_be = be;
+ ga_list = ga;
+
+ if ( online )
+ rc = glue_sub_attach( online );
+
+ return rc;
+}
+
+static int
+glue_access_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ BackendDB *b0, *be = glue_back_select( op->o_bd, &e->e_nname );
+ int rc;
+
+ if ( be == NULL || be == op->o_bd || be->bd_info->bi_access_allowed == NULL )
+ return SLAP_CB_CONTINUE;
+
+ b0 = op->o_bd;
+ op->o_bd = be;
+ rc = be->bd_info->bi_access_allowed ( op, e, desc, val, access, state, maskp );
+ op->o_bd = b0;
+ return rc;
+}
+
+int
+glue_sub_init()
+{
+ glue.on_bi.bi_type = "glue";
+
+ glue.on_bi.bi_db_init = glue_db_init;
+ glue.on_bi.bi_db_close = glue_db_close;
+ glue.on_bi.bi_db_destroy = glue_db_destroy;
+
+ glue.on_bi.bi_op_search = glue_op_search;
+ glue.on_bi.bi_op_modify = glue_op_func;
+ glue.on_bi.bi_op_modrdn = glue_op_func;
+ glue.on_bi.bi_op_add = glue_op_func;
+ glue.on_bi.bi_op_delete = glue_op_func;
+ glue.on_bi.bi_op_abandon = glue_op_abandon;
+ glue.on_bi.bi_extended = glue_op_func;
+
+ glue.on_bi.bi_chk_referrals = glue_chk_referrals;
+ glue.on_bi.bi_chk_controls = glue_chk_controls;
+ glue.on_bi.bi_entry_get_rw = glue_entry_get_rw;
+ glue.on_bi.bi_entry_release_rw = glue_entry_release_rw;
+ glue.on_bi.bi_access_allowed = glue_access_allowed;
+
+ glue.on_response = glue_response;
+
+ return overlay_register( &glue );
+}
diff --git a/servers/slapd/backover.c b/servers/slapd/backover.c
new file mode 100644
index 0000000..dbf67e6
--- /dev/null
+++ b/servers/slapd/backover.c
@@ -0,0 +1,1484 @@
+/* backover.c - backend overlay routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* Functions to overlay other modules over a backend. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#define SLAPD_TOOLS
+#include "slap.h"
+#include "slap-config.h"
+
+static slap_overinst *overlays;
+
+static int
+over_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ slap_overinfo *oi = be->bd_info->bi_private;
+ slap_overinst *on = oi->oi_list;
+ BackendInfo *bi_orig = be->bd_info;
+ struct ConfigOCs *be_cf_ocs = be->be_cf_ocs;
+ ConfigArgs ca = {0};
+ int rc = 0;
+
+ if ( oi->oi_orig->bi_db_config ) {
+ be->bd_info = oi->oi_orig;
+ be->be_cf_ocs = oi->oi_orig->bi_cf_ocs;
+ rc = oi->oi_orig->bi_db_config( be, fname, lineno,
+ argc, argv );
+
+ if ( be->bd_info != oi->oi_orig ) {
+ slap_overinfo *oi2;
+ slap_overinst *on2, **onp;
+ BackendDB be2 = *be;
+ int i;
+
+ /* a database added an overlay;
+ * work it around... */
+ assert( overlay_is_over( be ) );
+
+ oi2 = ( slap_overinfo * )be->bd_info->bi_private;
+ on2 = oi2->oi_list;
+
+ /* need to put a uniqueness check here as well;
+ * note that in principle there could be more than
+ * one overlay as a result of multiple calls to
+ * overlay_config() */
+ be2.bd_info = (BackendInfo *)oi;
+
+ for ( i = 0, onp = &on2; *onp; i++, onp = &(*onp)->on_next ) {
+ if ( overlay_is_inst( &be2, (*onp)->on_bi.bi_type ) ) {
+ Debug( LDAP_DEBUG_ANY, "over_db_config(): "
+ "warning, freshly added "
+ "overlay #%d \"%s\" is already in list\n",
+ i, (*onp)->on_bi.bi_type );
+
+ /* NOTE: if the overlay already exists,
+ * there is no way to merge the results
+ * of the configuration that may have
+ * occurred during bi_db_config(); we
+ * just issue a warning, and the
+ * administrator should deal with this */
+ }
+ }
+ *onp = oi->oi_list;
+
+ oi->oi_list = on2;
+
+ ch_free( be->bd_info );
+ }
+
+ be->bd_info = (BackendInfo *)oi;
+ if ( rc != SLAP_CONF_UNKNOWN ) return rc;
+ }
+
+ ca.argv = argv;
+ ca.argc = argc;
+ ca.fname = fname;
+ ca.lineno = lineno;
+ ca.be = be;
+ snprintf( ca.log, sizeof( ca.log ), "%s: line %d",
+ ca.fname, ca.lineno );
+ ca.op = SLAP_CONFIG_ADD;
+ ca.valx = -1;
+
+ for (; on; on=on->on_next) {
+ rc = SLAP_CONF_UNKNOWN;
+ if (on->on_bi.bi_cf_ocs) {
+ ConfigTable *ct;
+ ca.bi = &on->on_bi;
+ ct = config_find_keyword( on->on_bi.bi_cf_ocs->co_table, &ca );
+ if ( ct ) {
+ ca.table = on->on_bi.bi_cf_ocs->co_type;
+ rc = config_add_vals( ct, &ca );
+ if ( rc != SLAP_CONF_UNKNOWN )
+ break;
+ }
+ }
+ if (on->on_bi.bi_db_config && rc == SLAP_CONF_UNKNOWN) {
+ be->bd_info = &on->on_bi;
+ rc = on->on_bi.bi_db_config( be, fname, lineno,
+ argc, argv );
+ if ( rc != SLAP_CONF_UNKNOWN ) break;
+ }
+ }
+ be->bd_info = bi_orig;
+ be->be_cf_ocs = be_cf_ocs;
+
+ return rc;
+}
+
+static int
+over_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinfo *oi = be->bd_info->bi_private;
+ slap_overinst *on = oi->oi_list;
+ BackendDB db = *be;
+ int rc = 0;
+
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ db.bd_info = oi->oi_orig;
+ if ( db.bd_info->bi_db_open ) {
+ rc = db.bd_info->bi_db_open( &db, cr );
+ }
+
+ for (; on && rc == 0; on=on->on_next) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ db.bd_info = &on->on_bi;
+ if ( db.bd_info->bi_db_open ) {
+ rc = db.bd_info->bi_db_open( &db, cr );
+ }
+ }
+
+ return rc;
+}
+
+static int
+over_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinfo *oi = be->bd_info->bi_private;
+ slap_overinst *on = oi->oi_list;
+ BackendInfo *bi_orig = be->bd_info;
+ int rc = 0;
+
+ for (; on && rc == 0; on=on->on_next) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ be->bd_info = &on->on_bi;
+ if ( be->bd_info->bi_db_close ) {
+ rc = be->bd_info->bi_db_close( be, cr );
+ }
+ }
+
+ if ( oi->oi_orig->bi_db_close ) {
+ be->bd_info = oi->oi_orig;
+ rc = be->bd_info->bi_db_close( be, cr );
+ }
+
+ be->bd_info = bi_orig;
+ return rc;
+}
+
+static int
+over_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinfo *oi = be->bd_info->bi_private;
+ slap_overinst *on = oi->oi_list, *next;
+ BackendInfo *bi_orig = be->bd_info;
+ int rc = 0;
+
+ be->bd_info = oi->oi_orig;
+ if ( be->bd_info->bi_db_destroy ) {
+ rc = be->bd_info->bi_db_destroy( be, cr );
+ }
+
+ for (; on && rc == 0; on=on->on_next) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ be->bd_info = &on->on_bi;
+ if ( be->bd_info->bi_db_destroy ) {
+ rc = be->bd_info->bi_db_destroy( be, cr );
+ }
+ }
+
+ on = oi->oi_list;
+ if ( on ) {
+ for (next = on->on_next; on; on=next) {
+ next = on->on_next;
+ free( on );
+ }
+ }
+ be->bd_info = bi_orig;
+ free( oi );
+ return rc;
+}
+
+static int
+over_back_response ( Operation *op, SlapReply *rs )
+{
+ slap_overinfo *oi = op->o_callback->sc_private;
+ slap_overinst *on = oi->oi_list;
+ int rc = SLAP_CB_CONTINUE;
+ BackendDB *be = op->o_bd, db = *op->o_bd;
+
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ for (; on; on=on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_response ) {
+ db.bd_info = (BackendInfo *)on;
+ rc = on->on_response( op, rs );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+ /* Bypass the remaining on_response layers, but allow
+ * normal execution to continue.
+ */
+ if ( rc == SLAP_CB_BYPASS )
+ rc = SLAP_CB_CONTINUE;
+ op->o_bd = be;
+ return rc;
+}
+
+static int
+over_access_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+ BackendInfo *bi;
+ BackendDB *be = op->o_bd, db;
+ int rc = SLAP_CB_CONTINUE;
+
+ /* FIXME: used to happen for instance during abandon
+ * when global overlays are used... */
+ assert( op->o_bd != NULL );
+
+ bi = op->o_bd->bd_info;
+ /* Were we invoked on the frontend? */
+ if ( !bi->bi_access_allowed ) {
+ oi = frontendDB->bd_info->bi_private;
+ } else {
+ oi = op->o_bd->bd_info->bi_private;
+ }
+ on = oi->oi_list;
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_bi.bi_access_allowed ) {
+ /* NOTE: do not copy the structure until required */
+ if ( !SLAP_ISOVERLAY( op->o_bd ) ) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = on->on_bi.bi_access_allowed( op, e,
+ desc, val, access, state, maskp );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ BI_access_allowed *bi_access_allowed;
+
+ /* if the database structure was changed, o_bd points to a
+ * copy of the structure; put the original bd_info in place */
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = oi->oi_orig;
+ }
+
+ if ( oi->oi_orig->bi_access_allowed ) {
+ bi_access_allowed = oi->oi_orig->bi_access_allowed;
+ } else {
+ bi_access_allowed = slap_access_allowed;
+ }
+
+ rc = bi_access_allowed( op, e,
+ desc, val, access, state, maskp );
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ /* access not allowed */
+ rc = 0;
+ }
+
+ op->o_bd = be;
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = bi;
+ }
+
+ return rc;
+}
+
+int
+overlay_entry_get_ov(
+ Operation *op,
+ struct berval *dn,
+ ObjectClass *oc,
+ AttributeDescription *ad,
+ int rw,
+ Entry **e,
+ slap_overinst *on )
+{
+ slap_overinfo *oi = on->on_info;
+ BackendDB *be = op->o_bd, db;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int rc = SLAP_CB_CONTINUE;
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_bi.bi_entry_get_rw ) {
+ /* NOTE: do not copy the structure until required */
+ if ( !SLAP_ISOVERLAY( op->o_bd ) ) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = on->on_bi.bi_entry_get_rw( op, dn,
+ oc, ad, rw, e );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ /* if the database structure was changed, o_bd points to a
+ * copy of the structure; put the original bd_info in place */
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = oi->oi_orig;
+ }
+
+ if ( oi->oi_orig->bi_entry_get_rw ) {
+ rc = oi->oi_orig->bi_entry_get_rw( op, dn,
+ oc, ad, rw, e );
+ }
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ op->o_bd = be;
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = bi;
+ }
+
+ return rc;
+}
+
+static int
+over_entry_get_rw(
+ Operation *op,
+ struct berval *dn,
+ ObjectClass *oc,
+ AttributeDescription *ad,
+ int rw,
+ Entry **e )
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+
+ assert( op->o_bd != NULL );
+
+ oi = op->o_bd->bd_info->bi_private;
+ on = oi->oi_list;
+
+ return overlay_entry_get_ov( op, dn, oc, ad, rw, e, on );
+}
+
+int
+overlay_entry_release_ov(
+ Operation *op,
+ Entry *e,
+ int rw,
+ slap_overinst *on )
+{
+ slap_overinfo *oi = on->on_info;
+ BackendDB *be = op->o_bd, db;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int rc = SLAP_CB_CONTINUE;
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_bi.bi_entry_release_rw ) {
+ /* NOTE: do not copy the structure until required */
+ if ( !SLAP_ISOVERLAY( op->o_bd ) ) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = on->on_bi.bi_entry_release_rw( op, e, rw );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ /* if the database structure was changed, o_bd points to a
+ * copy of the structure; put the original bd_info in place */
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = oi->oi_orig;
+ }
+
+ if ( oi->oi_orig->bi_entry_release_rw ) {
+ rc = oi->oi_orig->bi_entry_release_rw( op, e, rw );
+ }
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ entry_free( e );
+ rc = 0;
+ }
+
+ op->o_bd = be;
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = bi;
+ }
+
+ return rc;
+}
+
+static int
+over_entry_release_rw(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+
+ assert( op->o_bd != NULL );
+
+ oi = op->o_bd->bd_info->bi_private;
+ on = oi->oi_list;
+
+ return overlay_entry_release_ov( op, e, rw, on );
+}
+
+static int
+over_acl_group(
+ Operation *op,
+ Entry *e,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at )
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+ BackendInfo *bi;
+ BackendDB *be = op->o_bd, db;
+ int rc = SLAP_CB_CONTINUE;
+
+ /* FIXME: used to happen for instance during abandon
+ * when global overlays are used... */
+ assert( be != NULL );
+
+ bi = be->bd_info;
+ oi = bi->bi_private;
+ on = oi->oi_list;
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_bi.bi_acl_group ) {
+ /* NOTE: do not copy the structure until required */
+ if ( !SLAP_ISOVERLAY( op->o_bd ) ) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = on->on_bi.bi_acl_group( op, e,
+ gr_ndn, op_ndn, group_oc, group_at );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ BI_acl_group *bi_acl_group;
+
+ /* if the database structure was changed, o_bd points to a
+ * copy of the structure; put the original bd_info in place */
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = oi->oi_orig;
+ }
+
+ if ( oi->oi_orig->bi_acl_group ) {
+ bi_acl_group = oi->oi_orig->bi_acl_group;
+ } else {
+ bi_acl_group = backend_group;
+ }
+
+ rc = bi_acl_group( op, e,
+ gr_ndn, op_ndn, group_oc, group_at );
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ /* access not allowed */
+ rc = 0;
+ }
+
+ op->o_bd = be;
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = bi;
+ }
+
+ return rc;
+}
+
+static int
+over_acl_attribute(
+ Operation *op,
+ Entry *target,
+ struct berval *entry_ndn,
+ AttributeDescription *entry_at,
+ BerVarray *vals,
+ slap_access_t access )
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+ BackendInfo *bi;
+ BackendDB *be = op->o_bd, db;
+ int rc = SLAP_CB_CONTINUE;
+
+ /* FIXME: used to happen for instance during abandon
+ * when global overlays are used... */
+ assert( be != NULL );
+
+ bi = be->bd_info;
+ oi = bi->bi_private;
+ on = oi->oi_list;
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ if ( on->on_bi.bi_acl_attribute ) {
+ /* NOTE: do not copy the structure until required */
+ if ( !SLAP_ISOVERLAY( op->o_bd ) ) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = on->on_bi.bi_acl_attribute( op, target,
+ entry_ndn, entry_at, vals, access );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ if ( rc == SLAP_CB_CONTINUE ) {
+ BI_acl_attribute *bi_acl_attribute;
+
+ /* if the database structure was changed, o_bd points to a
+ * copy of the structure; put the original bd_info in place */
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = oi->oi_orig;
+ }
+
+ if ( oi->oi_orig->bi_acl_attribute ) {
+ bi_acl_attribute = oi->oi_orig->bi_acl_attribute;
+ } else {
+ bi_acl_attribute = backend_attribute;
+ }
+
+ rc = bi_acl_attribute( op, target,
+ entry_ndn, entry_at, vals, access );
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ /* access not allowed */
+ rc = 0;
+ }
+
+ op->o_bd = be;
+ if ( SLAP_ISOVERLAY( op->o_bd ) ) {
+ op->o_bd->bd_info = bi;
+ }
+
+ return rc;
+}
+
+int
+overlay_callback_after_backover( Operation *op, slap_callback *sc, int append )
+{
+ slap_callback **scp;
+
+ for ( scp = &op->o_callback; *scp != NULL; scp = &(*scp)->sc_next ) {
+ if ( (*scp)->sc_response == over_back_response ) {
+ sc->sc_next = (*scp)->sc_next;
+ (*scp)->sc_next = sc;
+ return 0;
+ }
+ }
+
+ if ( append ) {
+ *scp = sc;
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * default return code in case of missing backend function
+ * and overlay stack returning SLAP_CB_CONTINUE
+ */
+static int op_rc[ op_last ] = {
+ LDAP_UNWILLING_TO_PERFORM, /* bind */
+ LDAP_UNWILLING_TO_PERFORM, /* unbind */
+ LDAP_UNWILLING_TO_PERFORM, /* search */
+ SLAP_CB_CONTINUE, /* compare; pass to frontend */
+ LDAP_UNWILLING_TO_PERFORM, /* modify */
+ LDAP_UNWILLING_TO_PERFORM, /* modrdn */
+ LDAP_UNWILLING_TO_PERFORM, /* add */
+ LDAP_UNWILLING_TO_PERFORM, /* delete */
+ LDAP_UNWILLING_TO_PERFORM, /* abandon */
+ LDAP_UNWILLING_TO_PERFORM, /* cancel */
+ LDAP_UNWILLING_TO_PERFORM, /* extended */
+ LDAP_SUCCESS, /* aux_operational */
+ LDAP_SUCCESS, /* aux_chk_referrals */
+ SLAP_CB_CONTINUE /* aux_chk_controls; pass to frontend */
+};
+
+int overlay_op_walk(
+ Operation *op,
+ SlapReply *rs,
+ slap_operation_t which,
+ slap_overinfo *oi,
+ slap_overinst *on
+)
+{
+ BackendInfo *bi;
+ int rc = SLAP_CB_CONTINUE;
+
+ for (; on; on=on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ bi = &on->on_bi;
+ if ( (&bi->bi_op_bind)[ which ] ) {
+ op->o_bd->bd_info = (BackendInfo *)on;
+ rc = (&bi->bi_op_bind)[ which ]( op, rs );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+ if ( rc == SLAP_CB_BYPASS )
+ rc = SLAP_CB_CONTINUE;
+ /* if an overlay halted processing, make sure
+ * any previously set cleanup handlers are run
+ */
+ if ( rc != SLAP_CB_CONTINUE )
+ goto cleanup;
+
+ bi = oi->oi_orig;
+ if ( (&bi->bi_op_bind)[ which ] ) {
+ op->o_bd->bd_info = bi;
+ rc = (&bi->bi_op_bind)[ which ]( op, rs );
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = op_rc[ which ];
+ }
+
+ /* The underlying backend didn't handle the request, make sure
+ * overlay cleanup is processed.
+ */
+ if ( rc == LDAP_UNWILLING_TO_PERFORM ) {
+ slap_callback *sc_next;
+cleanup:
+ for ( ; op->o_callback && op->o_callback->sc_response !=
+ over_back_response; op->o_callback = sc_next ) {
+ sc_next = op->o_callback->sc_next;
+ if ( op->o_callback->sc_cleanup ) {
+ op->o_callback->sc_cleanup( op, rs );
+ }
+ }
+ }
+ return rc;
+}
+
+static int
+over_op_func(
+ Operation *op,
+ SlapReply *rs,
+ slap_operation_t which
+)
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+ BackendDB *be = op->o_bd, db;
+ slap_callback **sc;
+ slap_callback *cb;
+ int rc = SLAP_CB_CONTINUE;
+
+ /* FIXME: used to happen for instance during abandon
+ * when global overlays are used... */
+ assert( op->o_bd != NULL );
+
+ oi = op->o_bd->bd_info->bi_private;
+ on = oi->oi_list;
+
+ if ( !SLAP_ISOVERLAY( op->o_bd )) {
+ db = *op->o_bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &db;
+ }
+ if ( op->o_tag != LDAP_REQ_ABANDON && op->o_tag != LDAP_REQ_UNBIND ) {
+ cb = (slap_callback *)op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = NULL;
+ cb->sc_response = over_back_response;
+ cb->sc_writewait = NULL;
+ cb->sc_next = op->o_callback;
+ cb->sc_private = oi;
+ op->o_callback = cb;
+ }
+
+ rc = overlay_op_walk( op, rs, which, oi, on );
+ if ( rc != SLAPD_ASYNCOP && op->o_tag != LDAP_REQ_ABANDON && op->o_tag != LDAP_REQ_UNBIND ) {
+ for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) {
+ if ( *sc == cb ) {
+ *sc = cb->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ break;
+ }
+ }
+ }
+
+ op->o_bd = be;
+ return rc;
+}
+
+static int
+over_op_bind( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_bind );
+}
+
+static int
+over_op_unbind( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_unbind );
+}
+
+static int
+over_op_search( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_search );
+}
+
+static int
+over_op_compare( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_compare );
+}
+
+static int
+over_op_modify( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_modify );
+}
+
+static int
+over_op_modrdn( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_modrdn );
+}
+
+static int
+over_op_add( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_add );
+}
+
+static int
+over_op_delete( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_delete );
+}
+
+static int
+over_op_abandon( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_abandon );
+}
+
+static int
+over_op_cancel( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_cancel );
+}
+
+static int
+over_op_extended( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_extended );
+}
+
+static int
+over_aux_operational( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_aux_operational );
+}
+
+static int
+over_aux_chk_referrals( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_aux_chk_referrals );
+}
+
+static int
+over_aux_chk_controls( Operation *op, SlapReply *rs )
+{
+ return over_op_func( op, rs, op_aux_chk_controls );
+}
+
+enum conn_which {
+ conn_init = 0,
+ conn_destroy,
+ conn_last
+};
+
+static int
+over_connection_func(
+ BackendDB *bd,
+ Connection *conn,
+ enum conn_which which
+)
+{
+ slap_overinfo *oi;
+ slap_overinst *on;
+ BackendDB db;
+ int rc = SLAP_CB_CONTINUE;
+ BI_connection_init **func;
+
+ /* FIXME: used to happen for instance during abandon
+ * when global overlays are used... */
+ assert( bd != NULL );
+
+ oi = bd->bd_info->bi_private;
+ on = oi->oi_list;
+
+ if ( !SLAP_ISOVERLAY( bd ) ) {
+ db = *bd;
+ db.be_flags |= SLAP_DBFLAG_OVERLAY;
+ bd = &db;
+ }
+
+ for ( ; on; on = on->on_next ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DISABLED )
+ continue;
+ func = &on->on_bi.bi_connection_init;
+ if ( func[ which ] ) {
+ bd->bd_info = (BackendInfo *)on;
+ rc = func[ which ]( bd, conn );
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ }
+
+ func = &oi->oi_orig->bi_connection_init;
+ if ( func[ which ] && rc == SLAP_CB_CONTINUE ) {
+ bd->bd_info = oi->oi_orig;
+ rc = func[ which ]( bd, conn );
+ }
+ /* should not fall thru this far without anything happening... */
+ if ( rc == SLAP_CB_CONTINUE ) {
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return rc;
+}
+
+static int
+over_connection_init(
+ BackendDB *bd,
+ Connection *conn
+)
+{
+ return over_connection_func( bd, conn, conn_init );
+}
+
+static int
+over_connection_destroy(
+ BackendDB *bd,
+ Connection *conn
+)
+{
+ return over_connection_func( bd, conn, conn_destroy );
+}
+
+int
+overlay_register(
+ slap_overinst *on
+)
+{
+ slap_overinst *tmp;
+
+ /* FIXME: check for duplicates? */
+ for ( tmp = overlays; tmp != NULL; tmp = tmp->on_next ) {
+ if ( strcmp( on->on_bi.bi_type, tmp->on_bi.bi_type ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "overlay_register(\"%s\"): "
+ "name already in use.\n",
+ on->on_bi.bi_type );
+ return -1;
+ }
+
+ if ( on->on_bi.bi_obsolete_names != NULL ) {
+ int i;
+
+ for ( i = 0; on->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) {
+ if ( strcmp( on->on_bi.bi_obsolete_names[ i ], tmp->on_bi.bi_type ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "overlay_register(\"%s\"): "
+ "obsolete name \"%s\" already in use "
+ "by overlay \"%s\".\n",
+ on->on_bi.bi_type,
+ on->on_bi.bi_obsolete_names[ i ],
+ tmp->on_bi.bi_type );
+ return -1;
+ }
+ }
+ }
+
+ if ( tmp->on_bi.bi_obsolete_names != NULL ) {
+ int i;
+
+ for ( i = 0; tmp->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) {
+ int j;
+
+ if ( strcmp( on->on_bi.bi_type, tmp->on_bi.bi_obsolete_names[ i ] ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "overlay_register(\"%s\"): "
+ "name already in use "
+ "as obsolete by overlay \"%s\".\n",
+ on->on_bi.bi_type,
+ tmp->on_bi.bi_obsolete_names[ i ] );
+ return -1;
+ }
+
+ if ( on->on_bi.bi_obsolete_names != NULL ) {
+ for ( j = 0; on->on_bi.bi_obsolete_names[ j ] != NULL; j++ ) {
+ if ( strcmp( on->on_bi.bi_obsolete_names[ j ], tmp->on_bi.bi_obsolete_names[ i ] ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "overlay_register(\"%s\"): "
+ "obsolete name \"%s\" already in use "
+ "as obsolete by overlay \"%s\".\n",
+ on->on_bi.bi_type,
+ on->on_bi.bi_obsolete_names[ j ],
+ tmp->on_bi.bi_type );
+ return -1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ on->on_next = overlays;
+ overlays = on;
+ return 0;
+}
+
+/*
+ * iterator on registered overlays; overlay_next( NULL ) returns the first
+ * overlay; subsequent calls with the previously returned value allow to
+ * iterate over the entire list; returns NULL when no more overlays are
+ * registered.
+ */
+
+slap_overinst *
+overlay_next(
+ slap_overinst *on
+)
+{
+ if ( on == NULL ) {
+ return overlays;
+ }
+
+ return on->on_next;
+}
+
+/*
+ * returns a specific registered overlay based on the type; NULL if not
+ * registered.
+ */
+
+slap_overinst *
+overlay_find( const char *over_type )
+{
+ slap_overinst *on = overlays;
+
+ assert( over_type != NULL );
+
+ for ( ; on; on = on->on_next ) {
+ if ( strcmp( on->on_bi.bi_type, over_type ) == 0 ) {
+ goto foundit;
+ }
+
+ if ( on->on_bi.bi_obsolete_names != NULL ) {
+ int i;
+
+ for ( i = 0; on->on_bi.bi_obsolete_names[ i ] != NULL; i++ ) {
+ if ( strcmp( on->on_bi.bi_obsolete_names[ i ], over_type ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "overlay_find(\"%s\"): "
+ "obsolete name for \"%s\".\n",
+ on->on_bi.bi_obsolete_names[ i ],
+ on->on_bi.bi_type );
+ goto foundit;
+ }
+ }
+ }
+ }
+
+foundit:;
+ return on;
+}
+
+static const char overtype[] = "over";
+
+/*
+ * returns TRUE (1) if the database is actually an overlay instance;
+ * FALSE (0) otherwise.
+ */
+
+int
+overlay_is_over( BackendDB *be )
+{
+ return be->bd_info->bi_type == overtype;
+}
+
+/*
+ * returns TRUE (1) if the given database is actually an overlay
+ * instance and, somewhere in the list, contains the requested overlay;
+ * FALSE (0) otherwise.
+ */
+
+int
+overlay_is_inst( BackendDB *be, const char *over_type )
+{
+ slap_overinst *on;
+
+ assert( be != NULL );
+
+ if ( !overlay_is_over( be ) ) {
+ return 0;
+ }
+
+ on = ((slap_overinfo *)be->bd_info->bi_private)->oi_list;
+ for ( ; on; on = on->on_next ) {
+ if ( strcmp( on->on_bi.bi_type, over_type ) == 0 ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+overlay_register_control( BackendDB *be, const char *oid )
+{
+ int gotit = 0;
+ int cid;
+
+ if ( slap_find_control_id( oid, &cid ) == LDAP_CONTROL_NOT_FOUND ) {
+ return -1;
+ }
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ BackendDB *bd;
+
+ /* add to all backends... */
+ LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) {
+ if ( bd == be->bd_self ) {
+ gotit = 1;
+ }
+
+ /* overlays can be instantiated multiple times, use
+ * be_ctrls[ cid ] as an instance counter, so that the
+ * overlay's controls are only really disabled after the
+ * last instance called overlay_register_control() */
+ bd->be_ctrls[ cid ]++;
+ bd->be_ctrls[ SLAP_MAX_CIDS ] = 1;
+ }
+
+ }
+
+ if ( !gotit ) {
+ /* overlays can be instantiated multiple times, use
+ * be_ctrls[ cid ] as an instance counter, so that the
+ * overlay's controls are only really unregistered after the
+ * last instance called overlay_register_control() */
+ be->bd_self->be_ctrls[ cid ]++;
+ be->bd_self->be_ctrls[ SLAP_MAX_CIDS ] = 1;
+ }
+
+ return 0;
+}
+
+#ifdef SLAP_CONFIG_DELETE
+void
+overlay_unregister_control( BackendDB *be, const char *oid )
+{
+ int gotit = 0;
+ int cid;
+
+ if ( slap_find_control_id( oid, &cid ) == LDAP_CONTROL_NOT_FOUND ) {
+ return;
+ }
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ BackendDB *bd;
+
+ /* remove from all backends... */
+ LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) {
+ if ( bd == be->bd_self ) {
+ gotit = 1;
+ }
+
+ bd->be_ctrls[ cid ]--;
+ }
+ }
+
+ if ( !gotit ) {
+ be->bd_self->be_ctrls[ cid ]--;
+ }
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+void
+overlay_destroy_one( BackendDB *be, slap_overinst *on )
+{
+ slap_overinfo *oi = on->on_info;
+ slap_overinst **oidx;
+
+ for ( oidx = &oi->oi_list; *oidx; oidx = &(*oidx)->on_next ) {
+ if ( *oidx == on ) {
+ *oidx = on->on_next;
+ if ( on->on_bi.bi_db_destroy ) {
+ BackendInfo *bi_orig = be->bd_info;
+ be->bd_info = (BackendInfo *)on;
+ on->on_bi.bi_db_destroy( be, NULL );
+ be->bd_info = bi_orig;
+ }
+ free( on );
+ break;
+ }
+ }
+}
+
+#ifdef SLAP_CONFIG_DELETE
+typedef struct ov_remove_ctx {
+ BackendDB be;
+ slap_overinst *on;
+} ov_remove_ctx;
+
+int
+overlay_remove_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ ov_remove_ctx *rm_ctx = (ov_remove_ctx*) op->o_callback->sc_private;
+
+ op->o_callback = sc->sc_next;
+ rm_ctx->be.bd_info = (BackendInfo*) rm_ctx->on;
+
+ if ( rm_ctx->on->on_bi.bi_db_close ) {
+ rm_ctx->on->on_bi.bi_db_close( &rm_ctx->be, NULL );
+ }
+ if ( rm_ctx->on->on_bi.bi_db_destroy ) {
+ rm_ctx->on->on_bi.bi_db_destroy( &rm_ctx->be, NULL );
+ }
+
+ /* clean up after removing last overlay */
+ if ( ! rm_ctx->on->on_info->oi_list ) {
+ ch_free(rm_ctx->on->on_info);
+ }
+ ch_free( rm_ctx->on );
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+}
+
+void
+overlay_remove( BackendDB *be, slap_overinst *on, Operation *op )
+{
+ slap_overinfo *oi = on->on_info;
+ slap_overinst **oidx;
+ ov_remove_ctx *rm_ctx;
+ slap_callback *rm_cb, *cb;
+
+ /* remove overlay from oi_list */
+ for ( oidx = &oi->oi_list; *oidx; oidx = &(*oidx)->on_next ) {
+ if ( *oidx == on ) {
+ *oidx = on->on_next;
+ break;
+ }
+ }
+
+ /* The db_close and db_destroy handlers to cleanup a release
+ * the overlay's resources are called from the cleanup callback
+ */
+
+ rm_cb = op->o_tmpalloc( sizeof( slap_callback ) + sizeof( ov_remove_ctx ), op->o_tmpmemctx );
+ rm_cb->sc_next = NULL;
+ rm_cb->sc_cleanup = overlay_remove_cb;
+ rm_cb->sc_response = NULL;
+ rm_cb->sc_private = (void*) ( rm_cb + 1 );
+ rm_cb->sc_writewait = NULL;
+
+ rm_ctx = rm_cb->sc_private;
+ rm_ctx->be = *be;
+ rm_ctx->on = on;
+
+ /* Append callback to the end of the list */
+ if ( !op->o_callback ) {
+ op->o_callback = rm_cb;
+ } else {
+ for ( cb = op->o_callback; cb->sc_next; cb = cb->sc_next );
+ cb->sc_next = rm_cb;
+ }
+
+ /* if this is the last overlay */
+ if ( ! on->on_info->oi_list ) {
+ /* reset db flags and bd_info to orig */
+ SLAP_DBFLAGS( be ) &= ~SLAP_DBFLAG_GLOBAL_OVERLAY;
+ be->bd_info = on->on_info->oi_orig;
+ }
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+void
+overlay_insert( BackendDB *be, slap_overinst *on2, slap_overinst ***prev,
+ int idx )
+{
+ slap_overinfo *oi = (slap_overinfo *)be->bd_info;
+
+ if ( idx == -1 ) {
+ on2->on_next = oi->oi_list;
+ oi->oi_list = on2;
+ } else {
+ int i, novs;
+ slap_overinst *on, **prev;
+
+ /* Since the list is in reverse order and is singly linked,
+ * we have to count the overlays and then insert backwards.
+ * Adding on overlay at a specific point should be a pretty
+ * infrequent occurrence.
+ */
+ novs = 0;
+ for ( on = oi->oi_list; on; on=on->on_next )
+ novs++;
+
+ if (idx > novs)
+ idx = 0;
+ else
+ idx = novs - idx;
+
+ /* advance to insertion point */
+ prev = &oi->oi_list;
+ for ( i=0; i<idx; i++ ) {
+ on = *prev;
+ prev = &on->on_next;
+ }
+ /* insert */
+ on2->on_next = *prev;
+ *prev = on2;
+ }
+}
+
+void
+overlay_move( BackendDB *be, slap_overinst *on, int idx )
+{
+ slap_overinfo *oi = (slap_overinfo *)be->bd_info;
+ slap_overinst **onp;
+
+ for (onp = &oi->oi_list; *onp; onp= &(*onp)->on_next) {
+ if ( *onp == on ) {
+ *onp = on->on_next;
+ break;
+ }
+ }
+ overlay_insert( be, on, &onp, idx );
+}
+
+/* add an overlay to a particular backend. */
+int
+overlay_config( BackendDB *be, const char *ov, int idx, BackendInfo **res, ConfigReply *cr )
+{
+ slap_overinst *on = NULL, *on2 = NULL, **prev;
+ slap_overinfo *oi = NULL;
+ BackendInfo *bi = NULL;
+
+ if ( res )
+ *res = NULL;
+
+ on = overlay_find( ov );
+ if ( !on ) {
+ Debug( LDAP_DEBUG_ANY, "overlay \"%s\" not found\n", ov );
+ return 1;
+ }
+
+ /* If this is the first overlay on this backend, set up the
+ * overlay info structure
+ */
+ if ( !overlay_is_over( be ) ) {
+ int isglobal = 0;
+
+ /* NOTE: the first time a global overlay is configured,
+ * frontendDB gets this flag; it is used later by overlays
+ * to determine if they're stacked on top of the frontendDB */
+ if ( be->bd_info == frontendDB->bd_info || SLAP_ISGLOBALOVERLAY( be ) ) {
+ isglobal = 1;
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_DBONLY ) {
+ snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): "
+ "overlay \"%s\" cannot be global.", ov );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg );
+ return 1;
+ }
+
+ } else if ( on->on_bi.bi_flags & SLAPO_BFLAG_GLOBONLY ) {
+ snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): "
+ "overlay \"%s\" can only be global.", ov );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg );
+ return 1;
+ }
+
+ oi = ch_malloc( sizeof( slap_overinfo ) );
+ oi->oi_orig = be->bd_info;
+ oi->oi_bi = *be->bd_info;
+ oi->oi_origdb = be;
+
+ if ( isglobal ) {
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_GLOBAL_OVERLAY;
+ }
+
+ /* Save a pointer to ourself in bi_private.
+ */
+ oi->oi_bi.bi_private = oi;
+ oi->oi_list = NULL;
+ bi = (BackendInfo *)oi;
+
+ bi->bi_type = (char *)overtype;
+
+ bi->bi_db_config = over_db_config;
+ bi->bi_db_open = over_db_open;
+ bi->bi_db_close = over_db_close;
+ bi->bi_db_destroy = over_db_destroy;
+
+ bi->bi_op_bind = over_op_bind;
+ bi->bi_op_unbind = over_op_unbind;
+ bi->bi_op_search = over_op_search;
+ bi->bi_op_compare = over_op_compare;
+ bi->bi_op_modify = over_op_modify;
+ bi->bi_op_modrdn = over_op_modrdn;
+ bi->bi_op_add = over_op_add;
+ bi->bi_op_delete = over_op_delete;
+ bi->bi_op_abandon = over_op_abandon;
+ bi->bi_op_cancel = over_op_cancel;
+
+ bi->bi_extended = over_op_extended;
+
+ /*
+ * this is fine because it has the same
+ * args of the operations; we need to rework
+ * all the hooks to share the same args
+ * of the operations...
+ */
+ bi->bi_operational = over_aux_operational;
+ bi->bi_chk_referrals = over_aux_chk_referrals;
+ bi->bi_chk_controls = over_aux_chk_controls;
+
+ /* these have specific arglists */
+ bi->bi_entry_get_rw = over_entry_get_rw;
+ bi->bi_entry_release_rw = over_entry_release_rw;
+ bi->bi_access_allowed = over_access_allowed;
+ bi->bi_acl_group = over_acl_group;
+ bi->bi_acl_attribute = over_acl_attribute;
+
+ bi->bi_connection_init = over_connection_init;
+ bi->bi_connection_destroy = over_connection_destroy;
+
+ be->bd_info = bi;
+
+ } else {
+ if ( overlay_is_inst( be, ov ) ) {
+ if ( on->on_bi.bi_flags & SLAPO_BFLAG_SINGLE ) {
+ snprintf( cr->msg, sizeof( cr->msg ), "overlay_config(): "
+ "overlay \"%s\" already in list", ov );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg );
+ return 1;
+ }
+ }
+
+ oi = be->bd_info->bi_private;
+ }
+
+ /* Insert new overlay into list. By default overlays are
+ * added to head of list and executed in LIFO order.
+ */
+ on2 = ch_calloc( 1, sizeof(slap_overinst) );
+ *on2 = *on;
+ on2->on_info = oi;
+
+ prev = &oi->oi_list;
+ /* Do we need to find the insertion point? */
+ if ( idx >= 0 ) {
+ int i;
+
+ /* count current overlays */
+ for ( i=0, on=oi->oi_list; on; on=on->on_next, i++ );
+
+ /* are we just appending a new one? */
+ if ( idx >= i )
+ idx = -1;
+ }
+ overlay_insert( be, on2, &prev, idx );
+
+ /* Any initialization needed? */
+ if ( on2->on_bi.bi_db_init ) {
+ int rc;
+ be->bd_info = (BackendInfo *)on2;
+ rc = on2->on_bi.bi_db_init( be, cr);
+ be->bd_info = (BackendInfo *)oi;
+ if ( rc ) {
+ *prev = on2->on_next;
+ ch_free( on2 );
+ on2 = NULL;
+ return rc;
+ }
+ }
+
+ if ( res )
+ *res = &on2->on_bi;
+
+ return 0;
+}
diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c
new file mode 100644
index 0000000..58ce3a5
--- /dev/null
+++ b/servers/slapd/bconfig.c
@@ -0,0 +1,8140 @@
+/* bconfig.c - the config backend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/dirent.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+
+#include <ldif.h>
+#include <lutil.h>
+
+#include "slap-config.h"
+#include "slap-cfglog.h"
+
+#define CONFIG_RDN "cn=config"
+#define SCHEMA_RDN "cn=schema"
+
+static struct berval config_rdn = BER_BVC(CONFIG_RDN);
+static struct berval schema_rdn = BER_BVC(SCHEMA_RDN);
+
+extern int slap_DN_strict; /* dn.c */
+
+#ifdef SLAPD_MODULES
+typedef struct modpath_s {
+ struct modpath_s *mp_next;
+ struct berval mp_path;
+ BerVarray mp_loads;
+} ModPaths;
+
+static ModPaths modpaths, *modlast = &modpaths, *modcur = &modpaths;
+#endif
+
+typedef struct ConfigFile {
+ struct ConfigFile *c_sibs;
+ struct ConfigFile *c_kids;
+ struct berval c_file;
+ AttributeType *c_at_head, *c_at_tail;
+ ContentRule *c_cr_head, *c_cr_tail;
+ ObjectClass *c_oc_head, *c_oc_tail;
+ OidMacro *c_om_head, *c_om_tail;
+ Syntax *c_syn_head, *c_syn_tail;
+ BerVarray c_dseFiles;
+} ConfigFile;
+
+typedef struct {
+ ConfigFile *cb_config;
+ CfEntryInfo *cb_root;
+ BackendDB cb_db; /* underlying database */
+ int cb_got_ldif;
+ int cb_use_ldif;
+ ldap_pvt_thread_rdwr_t cb_rwlock;
+} CfBackInfo;
+
+static CfBackInfo cfBackInfo;
+
+static char *passwd_salt;
+static AccessControl *defacl_parsed = NULL;
+
+static struct berval cfdir;
+
+/* Private state */
+static AttributeDescription *cfAd_backend, *cfAd_database, *cfAd_overlay,
+ *cfAd_include, *cfAd_attr, *cfAd_oc, *cfAd_om, *cfAd_syntax;
+
+static ConfigFile *cfn;
+
+static Avlnode *CfOcTree;
+
+/* System schema state */
+extern AttributeType *at_sys_tail; /* at.c */
+extern ObjectClass *oc_sys_tail; /* oc.c */
+extern OidMacro *om_sys_tail; /* oidm.c */
+extern Syntax *syn_sys_tail; /* syntax.c */
+static AttributeType *cf_at_tail;
+static ObjectClass *cf_oc_tail;
+static OidMacro *cf_om_tail;
+static Syntax *cf_syn_tail;
+
+static int config_add_internal( CfBackInfo *cfb, Entry *e, ConfigArgs *ca,
+ SlapReply *rs, int *renumber, Operation *op );
+
+static int config_check_schema( Operation *op, CfBackInfo *cfb );
+
+static ConfigDriver config_fname;
+static ConfigDriver config_cfdir;
+static ConfigDriver config_generic;
+static ConfigDriver config_search_base;
+static ConfigDriver config_passwd_hash;
+static ConfigDriver config_schema_dn;
+static ConfigDriver config_sizelimit;
+static ConfigDriver config_timelimit;
+static ConfigDriver config_overlay;
+static ConfigDriver config_subordinate;
+static ConfigDriver config_suffix;
+#ifdef LDAP_TCP_BUFFER
+static ConfigDriver config_tcp_buffer;
+#endif /* LDAP_TCP_BUFFER */
+static ConfigDriver config_rootdn;
+static ConfigDriver config_rootpw;
+static ConfigDriver config_restrict;
+static ConfigDriver config_allows;
+static ConfigDriver config_disallows;
+static ConfigDriver config_requires;
+static ConfigDriver config_security;
+static ConfigDriver config_referral;
+static ConfigDriver config_updatedn;
+static ConfigDriver config_updateref;
+static ConfigDriver config_extra_attrs;
+static ConfigDriver config_include;
+static ConfigDriver config_obsolete;
+#ifdef HAVE_TLS
+static ConfigDriver config_tls_option;
+static ConfigDriver config_tls_config;
+#endif
+extern ConfigDriver syncrepl_config;
+
+enum {
+ CFG_ACL = 1,
+ CFG_BACKEND,
+ CFG_DATABASE,
+ CFG_TLS_RAND,
+ CFG_TLS_CIPHER,
+ CFG_TLS_PROTOCOL_MIN,
+ CFG_TLS_CERT_FILE,
+ CFG_TLS_CERT_KEY,
+ CFG_TLS_CA_PATH,
+ CFG_TLS_CA_FILE,
+ CFG_TLS_DH_FILE,
+ CFG_TLS_VERIFY,
+ CFG_TLS_CRLCHECK,
+ CFG_TLS_CRL_FILE,
+ CFG_CONCUR,
+ CFG_THREADS,
+ CFG_SALT,
+ CFG_LIMITS,
+ CFG_RO,
+ CFG_REWRITE,
+ CFG_DEPTH,
+ CFG_OID,
+ CFG_OC,
+ CFG_DIT,
+ CFG_ATTR,
+ CFG_ATOPT,
+ CFG_ROOTDSE,
+ CFG_PLUGIN,
+ CFG_MODLOAD,
+ CFG_MODPATH,
+ CFG_LASTMOD,
+ CFG_LASTBIND,
+ CFG_LASTBIND_PRECISION,
+ CFG_AZPOLICY,
+ CFG_AZREGEXP,
+ CFG_AZDUC,
+ CFG_AZDUC_IGNORE,
+ CFG_SASLSECP,
+ CFG_SSTR_IF_MAX,
+ CFG_SSTR_IF_MIN,
+ CFG_TTHREADS,
+ CFG_MULTIPROVIDER,
+ CFG_HIDDEN,
+ CFG_MONITORING,
+ CFG_SERVERID,
+ CFG_SORTVALS,
+ CFG_IX_INTLEN,
+ CFG_SYNTAX,
+ CFG_ACL_ADD,
+ CFG_SYNC_SUBENTRY,
+ CFG_LTHREADS,
+ CFG_IX_HASH64,
+ CFG_DISABLED,
+ CFG_THREADQS,
+ CFG_TLS_ECNAME,
+ CFG_TLS_CACERT,
+ CFG_TLS_CERT,
+ CFG_TLS_KEY,
+
+ CFG_LAST
+};
+
+typedef struct {
+ char *name, *oid;
+} OidRec;
+
+static OidRec OidMacros[] = {
+ /* OpenLDAProot:1.12.2 */
+ { "OLcfg", "1.3.6.1.4.1.4203.1.12.2" },
+ { "OLcfgAt", "OLcfg:3" },
+ { "OLcfgGlAt", "OLcfgAt:0" },
+ { "OLcfgBkAt", "OLcfgAt:1" },
+ { "OLcfgDbAt", "OLcfgAt:2" },
+ { "OLcfgOvAt", "OLcfgAt:3" },
+ { "OLcfgCtAt", "OLcfgAt:4" }, /* contrib modules */
+ { "OLcfgOc", "OLcfg:4" },
+ { "OLcfgGlOc", "OLcfgOc:0" },
+ { "OLcfgBkOc", "OLcfgOc:1" },
+ { "OLcfgDbOc", "OLcfgOc:2" },
+ { "OLcfgOvOc", "OLcfgOc:3" },
+ { "OLcfgCtOc", "OLcfgOc:4" }, /* contrib modules */
+
+ /* Syntaxes. We should just start using the standard names and
+ * document that they are predefined and available for users
+ * to reference in their own schema. Defining schema without
+ * OID macros is for masochists...
+ */
+ { "OMsyn", "1.3.6.1.4.1.1466.115.121.1" },
+ { "OMsBoolean", "OMsyn:7" },
+ { "OMsDN", "OMsyn:12" },
+ { "OMsDirectoryString", "OMsyn:15" },
+ { "OMsIA5String", "OMsyn:26" },
+ { "OMsInteger", "OMsyn:27" },
+ { "OMsOID", "OMsyn:38" },
+ { "OMsOctetString", "OMsyn:40" },
+ { NULL, NULL }
+};
+
+/*
+ * Backend/Database registry
+ *
+ * OLcfg{Bk|Db}{Oc|At}:0 -> common
+ * OLcfg{Bk|Db}{Oc|At}:1 -> back-bdb(/back-hdb) (removed)
+ * OLcfg{Bk|Db}{Oc|At}:2 -> back-ldif
+ * OLcfg{Bk|Db}{Oc|At}:3 -> back-ldap/meta
+ * OLcfg{Bk|Db}{Oc|At}:4 -> back-monitor
+ * OLcfg{Bk|Db}{Oc|At}:5 -> back-relay
+ * OLcfg{Bk|Db}{Oc|At}:6 -> back-sql
+ * OLcfg{Bk|Db}{Oc|At}:7 -> back-sock
+ * OLcfg{Bk|Db}{Oc|At}:8 -> back-null
+ * OLcfg{Bk|Db}{Oc|At}:9 -> back-passwd
+ * OLcfg{Bk|Db}{Oc|At}:10 -> back-shell
+ * OLcfg{Bk|Db}{Oc|At}:11 -> back-perl
+ * OLcfg{Bk|Db}{Oc|At}:12 -> back-mdb
+ * OLcfg{Bk|Db}{Oc|At}:13 -> lloadd
+ */
+
+/*
+ * Overlay registry
+ *
+ * OLcfgOv{Oc|At}:1 -> syncprov
+ * OLcfgOv{Oc|At}:2 -> pcache
+ * OLcfgOv{Oc|At}:3 -> chain
+ * OLcfgOv{Oc|At}:4 -> accesslog
+ * OLcfgOv{Oc|At}:5 -> valsort
+ * OLcfgOv{Oc|At}:7 -> distproc
+ * OLcfgOv{Oc|At}:8 -> dynlist
+ * OLcfgOv{Oc|At}:9 -> dds
+ * OLcfgOv{Oc|At}:10 -> unique
+ * OLcfgOv{Oc|At}:11 -> refint
+ * OLcfgOv{Oc|At}:12 -> ppolicy
+ * OLcfgOv{Oc|At}:13 -> constraint
+ * OLcfgOv{Oc|At}:14 -> translucent
+ * OLcfgOv{Oc|At}:15 -> auditlog
+ * OLcfgOv{Oc|At}:16 -> rwm
+ * OLcfgOv{Oc|At}:17 -> dyngroup
+ * OLcfgOv{Oc|At}:18 -> memberof
+ * OLcfgOv{Oc|At}:19 -> collect
+ * OLcfgOv{Oc|At}:20 -> retcode
+ * OLcfgOv{Oc|At}:21 -> sssvlv
+ * OLcfgOv{Oc|At}:22 -> autoca
+ * OLcfgOv{Oc|At}:24 -> remoteauth
+ */
+
+/* alphabetical ordering */
+
+static ConfigTable config_back_cf_table[] = {
+ /* This attr is read-only */
+ { "", "", 0, 0, 0, ARG_MAGIC,
+ &config_fname, "( OLcfgGlAt:78 NAME 'olcConfigFile' "
+ "DESC 'File for slapd configuration directives' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "", "", 0, 0, 0, ARG_MAGIC,
+ &config_cfdir, "( OLcfgGlAt:79 NAME 'olcConfigDir' "
+ "DESC 'Directory for slapd configuration backend' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "access", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC|CFG_ACL,
+ &config_generic, "( OLcfgGlAt:1 NAME 'olcAccess' "
+ "DESC 'Access Control List' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "add_content_acl", NULL, 0, 0, 0, ARG_MAY_DB|ARG_ON_OFF|ARG_MAGIC|CFG_ACL_ADD,
+ &config_generic, "( OLcfgGlAt:86 NAME 'olcAddContentAcl' "
+ "DESC 'Check ACLs against content of Add ops' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "allows", "features", 2, 0, 5, ARG_PRE_DB|ARG_MAGIC,
+ &config_allows, "( OLcfgGlAt:2 NAME 'olcAllows' "
+ "DESC 'Allowed set of deprecated features' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "argsfile", "file", 2, 2, 0, ARG_STRING,
+ &slapd_args_file, "( OLcfgGlAt:3 NAME 'olcArgsFile' "
+ "DESC 'File for slapd command line options' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "attributeoptions", NULL, 0, 0, 0, ARG_MAGIC|CFG_ATOPT,
+ &config_generic, "( OLcfgGlAt:5 NAME 'olcAttributeOptions' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "attribute", "attribute", 2, 0, STRLENOF( "attribute" ),
+ ARG_PAREN|ARG_MAGIC|CFG_ATTR,
+ &config_generic, "( OLcfgGlAt:4 NAME 'olcAttributeTypes' "
+ "DESC 'OpenLDAP attributeTypes' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "authid-rewrite", "rewrite", 2, 0, STRLENOF( "authid-rewrite" ),
+ ARG_MAGIC|CFG_REWRITE, &config_generic,
+ "( OLcfgGlAt:6 NAME 'olcAuthIDRewrite' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "authz-policy", "policy", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_AZPOLICY,
+ &config_generic, "( OLcfgGlAt:7 NAME 'olcAuthzPolicy' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "authz-regexp", "regexp> <DN", 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP,
+ &config_generic, "( OLcfgGlAt:8 NAME 'olcAuthzRegexp' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "backend", "type", 2, 2, 0, ARG_PRE_DB|ARG_MAGIC|CFG_BACKEND,
+ &config_generic, "( OLcfgGlAt:9 NAME 'olcBackend' "
+ "DESC 'A type of backend' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE X-ORDERED 'SIBLINGS' )",
+ NULL, NULL },
+ { "concurrency", "level", 2, 2, 0, ARG_INT|ARG_MAGIC|CFG_CONCUR,
+ &config_generic, "( OLcfgGlAt:10 NAME 'olcConcurrency' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "conn_max_pending", "max", 2, 2, 0, ARG_INT,
+ &slap_conn_max_pending, "( OLcfgGlAt:11 NAME 'olcConnMaxPending' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "conn_max_pending_auth", "max", 2, 2, 0, ARG_INT,
+ &slap_conn_max_pending_auth, "( OLcfgGlAt:12 NAME 'olcConnMaxPendingAuth' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "database", "type", 2, 2, 0, ARG_MAGIC|CFG_DATABASE,
+ &config_generic, "( OLcfgGlAt:13 NAME 'olcDatabase' "
+ "DESC 'The backend type for a database instance' "
+ "SUP olcBackend SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+ { "defaultSearchBase", "dn", 2, 2, 0, ARG_PRE_BI|ARG_PRE_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC,
+ &config_search_base, "( OLcfgGlAt:14 NAME 'olcDefaultSearchBase' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "disabled", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_DISABLED,
+ &config_generic, "( OLcfgDbAt:0.21 NAME 'olcDisabled' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "disallows", "features", 2, 0, 8, ARG_PRE_DB|ARG_MAGIC,
+ &config_disallows, "( OLcfgGlAt:15 NAME 'olcDisallows' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "ditcontentrule", NULL, 0, 0, 0, ARG_MAGIC|CFG_DIT|ARG_NO_DELETE|ARG_NO_INSERT,
+ &config_generic, "( OLcfgGlAt:16 NAME 'olcDitContentRules' "
+ "DESC 'OpenLDAP DIT content rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "extra_attrs", "attrlist", 2, 2, 0, ARG_DB|ARG_MAGIC,
+ &config_extra_attrs, "( OLcfgDbAt:0.20 NAME 'olcExtraAttrs' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "gentlehup", "on|off", 2, 2, 0,
+#ifdef SIGHUP
+ ARG_ON_OFF, &global_gentlehup,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:17 NAME 'olcGentleHUP' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "hidden", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_HIDDEN,
+ &config_generic, "( OLcfgDbAt:0.17 NAME 'olcHidden' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "idletimeout", "timeout", 2, 2, 0, ARG_INT,
+ &global_idletimeout, "( OLcfgGlAt:18 NAME 'olcIdleTimeout' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "include", "file", 2, 2, 0, ARG_MAGIC,
+ &config_include, "( OLcfgGlAt:19 NAME 'olcInclude' "
+ "SUP labeledURI )", NULL, NULL },
+ { "index_hash64", "on|off", 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|CFG_IX_HASH64,
+ &config_generic, "( OLcfgGlAt:94 NAME 'olcIndexHash64' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "index_substr_if_minlen", "min", 2, 2, 0, ARG_UINT|ARG_NONZERO|ARG_MAGIC|CFG_SSTR_IF_MIN,
+ &config_generic, "( OLcfgGlAt:20 NAME 'olcIndexSubstrIfMinLen' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = SLAP_INDEX_SUBSTR_IF_MINLEN_DEFAULT }
+ },
+ { "index_substr_if_maxlen", "max", 2, 2, 0, ARG_UINT|ARG_NONZERO|ARG_MAGIC|CFG_SSTR_IF_MAX,
+ &config_generic, "( OLcfgGlAt:21 NAME 'olcIndexSubstrIfMaxLen' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = SLAP_INDEX_SUBSTR_IF_MAXLEN_DEFAULT }
+ },
+ { "index_substr_any_len", "len", 2, 2, 0, ARG_UINT|ARG_NONZERO,
+ &index_substr_any_len, "( OLcfgGlAt:22 NAME 'olcIndexSubstrAnyLen' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = SLAP_INDEX_SUBSTR_ANY_LEN_DEFAULT } },
+ { "index_substr_any_step", "step", 2, 2, 0, ARG_UINT|ARG_NONZERO,
+ &index_substr_any_step, "( OLcfgGlAt:23 NAME 'olcIndexSubstrAnyStep' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = SLAP_INDEX_SUBSTR_ANY_STEP_DEFAULT } },
+ { "index_intlen", "len", 2, 2, 0, ARG_UINT|ARG_MAGIC|CFG_IX_INTLEN,
+ &config_generic, "( OLcfgGlAt:84 NAME 'olcIndexIntLen' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "lastmod", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_LASTMOD,
+ &config_generic, "( OLcfgDbAt:0.4 NAME 'olcLastMod' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "lastbind", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_LASTBIND,
+ &config_generic, "( OLcfgDbAt:0.22 NAME 'olcLastBind' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "lastbind-precision", "seconds difference", 2, 2, 0,
+ ARG_DB|ARG_MAGIC|ARG_UINT|CFG_LASTBIND_PRECISION,
+ &config_generic, "( OLcfgDbAt:0.23 NAME 'olcLastBindPrecision' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = 0 }
+ },
+ { "ldapsyntax", "syntax", 2, 0, 0,
+ ARG_PAREN|ARG_MAGIC|CFG_SYNTAX,
+ &config_generic, "( OLcfgGlAt:85 NAME 'olcLdapSyntaxes' "
+ "DESC 'OpenLDAP ldapSyntax' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "limits", "limits", 2, 0, 0, ARG_DB|ARG_MAGIC|CFG_LIMITS,
+ &config_generic, "( OLcfgDbAt:0.5 NAME 'olcLimits' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "listener-threads", "count", 2, 0, 0,
+ ARG_UINT|ARG_MAGIC|CFG_LTHREADS, &config_generic,
+ "( OLcfgGlAt:93 NAME 'olcListenerThreads' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_uint = 1 }
+ },
+ { "localSSF", "ssf", 2, 2, 0, ARG_INT,
+ &local_ssf, "( OLcfgGlAt:26 NAME 'olcLocalSSF' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = LDAP_PVT_SASL_LOCAL_SSF } },
+ { "logfile", "file", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_LOGFILE,
+ &config_logging, "( OLcfgGlAt:27 NAME 'olcLogFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logfile-format", "debug|syslog-utc|syslog-localtime", 2, 2, 0, ARG_MAGIC|CFG_LOGFILE_FORMAT,
+ &config_logging, "( OLcfgGlAt:104 NAME 'olcLogFileFormat' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logfile-only", "on|off", 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|CFG_LOGFILE_ONLY,
+ &config_logging, "( OLcfgGlAt:102 NAME 'olcLogFileOnly' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "logfile-rotate", "max> <Mbyte> <hours", 4, 4, 0, ARG_MAGIC|CFG_LOGFILE_ROTATE,
+ &config_logging, "( OLcfgGlAt:103 NAME 'olcLogFileRotate' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "loglevel", "level", 2, 0, 0, ARG_MAGIC|CFG_LOGLEVEL,
+ &config_logging, "( OLcfgGlAt:28 NAME 'olcLogLevel' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "maxDerefDepth", "depth", 2, 2, 0, ARG_DB|ARG_INT|ARG_MAGIC|CFG_DEPTH,
+ &config_generic, "( OLcfgDbAt:0.6 NAME 'olcMaxDerefDepth' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SLAPD_DEFAULT_MAXDEREFDEPTH }
+ },
+ { "maxFilterDepth", "depth", 2, 2, 0, ARG_INT,
+ &slap_max_filter_depth, "( OLcfgGlAt:101 NAME 'olcMaxFilterDepth' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SLAP_MAX_FILTER_DEPTH_DEFAULT }
+ },
+ { "multiprovider", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_MULTIPROVIDER,
+ &config_generic, "( OLcfgDbAt:0.16 NAME ( 'olcMultiProvider' 'olcMirrorMode' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "moduleload", "file", 2, 0, 0,
+#ifdef SLAPD_MODULES
+ ARG_MAGIC|CFG_MODLOAD|ARG_NO_DELETE, &config_generic,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:30 NAME 'olcModuleLoad' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "modulepath", "path", 2, 2, 0,
+#ifdef SLAPD_MODULES
+ ARG_MAGIC|CFG_MODPATH|ARG_NO_DELETE|ARG_NO_INSERT, &config_generic,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:31 NAME 'olcModulePath' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "monitoring", "TRUE|FALSE", 2, 2, 0,
+ ARG_MAGIC|CFG_MONITORING|ARG_DB|ARG_ON_OFF, &config_generic,
+ "( OLcfgDbAt:0.18 NAME 'olcMonitoring' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "objectclass", "objectclass", 2, 0, 0, ARG_PAREN|ARG_MAGIC|CFG_OC,
+ &config_generic, "( OLcfgGlAt:32 NAME 'olcObjectClasses' "
+ "DESC 'OpenLDAP object classes' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "objectidentifier", "name> <oid", 3, 3, 0, ARG_MAGIC|CFG_OID,
+ &config_generic, "( OLcfgGlAt:33 NAME 'olcObjectIdentifier' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "overlay", "overlay", 2, 2, 0, ARG_MAGIC,
+ &config_overlay, "( OLcfgGlAt:34 NAME 'olcOverlay' "
+ "SUP olcDatabase SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+ { "password-crypt-salt-format", "salt", 2, 2, 0, ARG_STRING|ARG_MAGIC|CFG_SALT,
+ &config_generic, "( OLcfgGlAt:35 NAME 'olcPasswordCryptSaltFormat' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "password-hash", "hash", 2, 0, 0, ARG_MAGIC,
+ &config_passwd_hash, "( OLcfgGlAt:36 NAME 'olcPasswordHash' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pidfile", "file", 2, 2, 0, ARG_STRING,
+ &slapd_pid_file, "( OLcfgGlAt:37 NAME 'olcPidFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "plugin", NULL, 0, 0, 0,
+#ifdef LDAP_SLAPI
+ ARG_MAGIC|CFG_PLUGIN, &config_generic,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:38 NAME 'olcPlugin' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "pluginlog", "filename", 2, 2, 0,
+#ifdef LDAP_SLAPI
+ ARG_STRING, &slapi_log_file,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:39 NAME 'olcPluginLogFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "readonly", "on|off", 2, 2, 0, ARG_MAY_DB|ARG_ON_OFF|ARG_MAGIC|CFG_RO,
+ &config_generic, "( OLcfgGlAt:40 NAME 'olcReadOnly' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "referral", "url", 2, 2, 0, ARG_MAGIC,
+ &config_referral, "( OLcfgGlAt:41 NAME 'olcReferral' "
+ "SUP labeledURI SINGLE-VALUE )", NULL, NULL },
+ { "replica", "host or uri", 2, 0, 0, ARG_DB|ARG_MAGIC,
+ &config_obsolete, "( OLcfgDbAt:0.7 NAME 'olcReplica' "
+ "EQUALITY caseIgnoreMatch "
+ "SUP labeledURI X-ORDERED 'VALUES' )", NULL, NULL },
+ { "replica-argsfile", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_obsolete, "( OLcfgGlAt:43 NAME 'olcReplicaArgsFile' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "replica-pidfile", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_obsolete, "( OLcfgGlAt:44 NAME 'olcReplicaPidFile' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "replicationInterval", NULL, 0, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_obsolete, "( OLcfgGlAt:45 NAME 'olcReplicationInterval' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "replogfile", "filename", 2, 2, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_obsolete, "( OLcfgGlAt:46 NAME 'olcReplogFile' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "require", "features", 2, 0, 7, ARG_MAY_DB|ARG_MAGIC,
+ &config_requires, "( OLcfgGlAt:47 NAME 'olcRequires' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "restrict", "op_list", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_restrict, "( OLcfgGlAt:48 NAME 'olcRestrict' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "reverse-lookup", "on|off", 2, 2, 0,
+#ifdef SLAPD_RLOOKUPS
+ ARG_ON_OFF, &use_reverse_lookup,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:49 NAME 'olcReverseLookup' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "rootdn", "dn", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC,
+ &config_rootdn, "( OLcfgDbAt:0.8 NAME 'olcRootDN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "rootDSE", "file", 2, 2, 0, ARG_MAGIC|CFG_ROOTDSE,
+ &config_generic, "( OLcfgGlAt:51 NAME 'olcRootDSE' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "rootpw", "password", 2, 2, 0, ARG_BERVAL|ARG_DB|ARG_MAGIC,
+ &config_rootpw, "( OLcfgDbAt:0.9 NAME 'olcRootPW' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX OMsOctetString SINGLE-VALUE )", NULL, NULL },
+ { "sasl-authz-policy", NULL, 2, 2, 0, ARG_MAGIC|CFG_AZPOLICY,
+ &config_generic, NULL, NULL, NULL },
+ { "sasl-auxprops", NULL, 2, 0, 0,
+#ifdef HAVE_CYRUS_SASL
+ ARG_STRING|ARG_UNIQUE, &slap_sasl_auxprops,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:89 NAME 'olcSaslAuxprops' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sasl-auxprops-dontusecopy", NULL, 2, 0, 0,
+#if defined(HAVE_CYRUS_SASL) && defined(SLAP_AUXPROP_DONTUSECOPY)
+ ARG_MAGIC|CFG_AZDUC, &config_generic,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:91 NAME 'olcSaslAuxpropsDontUseCopy' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "sasl-auxprops-dontusecopy-ignore", "true|FALSE", 2, 0, 0,
+#if defined(HAVE_CYRUS_SASL) && defined(SLAP_AUXPROP_DONTUSECOPY)
+ ARG_ON_OFF|CFG_AZDUC_IGNORE, &slap_dontUseCopy_ignore,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:92 NAME 'olcSaslAuxpropsDontUseCopyIgnore' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "sasl-cbinding", NULL, 2, 2, 0,
+#ifdef HAVE_CYRUS_SASL
+ ARG_STRING, &sasl_cbinding,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:100 NAME 'olcSaslCBinding' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sasl-host", "host", 2, 2, 0,
+#ifdef HAVE_CYRUS_SASL
+ ARG_STRING|ARG_UNIQUE, &sasl_host,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:53 NAME 'olcSaslHost' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sasl-realm", "realm", 2, 2, 0,
+#ifdef HAVE_CYRUS_SASL
+ ARG_STRING|ARG_UNIQUE, &global_realm,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:54 NAME 'olcSaslRealm' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sasl-regexp", NULL, 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP,
+ &config_generic, NULL, NULL, NULL },
+ { "sasl-secprops", "properties", 2, 2, 0,
+#ifdef HAVE_CYRUS_SASL
+ ARG_MAGIC|CFG_SASLSECP, &config_generic,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:56 NAME 'olcSaslSecProps' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "saslRegexp", NULL, 3, 3, 0, ARG_MAGIC|CFG_AZREGEXP,
+ &config_generic, NULL, NULL, NULL },
+ { "schemadn", "dn", 2, 2, 0, ARG_MAY_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC,
+ &config_schema_dn, "( OLcfgGlAt:58 NAME 'olcSchemaDN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "security", "factors", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_security, "( OLcfgGlAt:59 NAME 'olcSecurity' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "serverID", "number> <[URI]", 2, 3, 0, ARG_MAGIC|CFG_SERVERID,
+ &config_generic, "( OLcfgGlAt:81 NAME 'olcServerID' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "sizelimit", "limit", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_sizelimit, "( OLcfgGlAt:60 NAME 'olcSizeLimit' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sockbuf_max_incoming", "max", 2, 2, 0, ARG_BER_LEN_T,
+ &sockbuf_max_incoming, "( OLcfgGlAt:61 NAME 'olcSockbufMaxIncoming' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_ber_t = SLAP_SB_MAX_INCOMING_DEFAULT } },
+ { "sockbuf_max_incoming_auth", "max", 2, 2, 0, ARG_BER_LEN_T,
+ &sockbuf_max_incoming_auth, "( OLcfgGlAt:62 NAME 'olcSockbufMaxIncomingAuth' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_ber_t = SLAP_SB_MAX_INCOMING_AUTH } },
+ { "sortvals", "attr", 2, 0, 0, ARG_MAGIC|CFG_SORTVALS,
+ &config_generic, "( OLcfgGlAt:83 NAME 'olcSortVals' "
+ "DESC 'Attributes whose values will always be sorted' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "subordinate", "[advertise]", 1, 2, 0, ARG_DB|ARG_MAGIC,
+ &config_subordinate, "( OLcfgDbAt:0.15 NAME 'olcSubordinate' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "suffix", "suffix", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC,
+ &config_suffix, "( OLcfgDbAt:0.10 NAME 'olcSuffix' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN )", NULL, NULL },
+ { "sync_use_subentry", NULL, 0, 0, 0, ARG_ON_OFF|ARG_DB|ARG_MAGIC|CFG_SYNC_SUBENTRY,
+ &config_generic, "( OLcfgDbAt:0.19 NAME 'olcSyncUseSubentry' "
+ "DESC 'Store sync context in a subentry' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "syncrepl", NULL, 0, 0, 0, ARG_DB|ARG_MAGIC,
+ &syncrepl_config, "( OLcfgDbAt:0.11 NAME 'olcSyncrepl' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
+ { "tcp-buffer", "[listener=<listener>] [{read|write}=]size", 0, 0, 0,
+#ifndef LDAP_TCP_BUFFER
+ ARG_IGNORED, NULL,
+#else /* LDAP_TCP_BUFFER */
+ ARG_MAGIC, &config_tcp_buffer,
+#endif /* LDAP_TCP_BUFFER */
+ "( OLcfgGlAt:90 NAME 'olcTCPBuffer' "
+ "EQUALITY caseExactMatch "
+ "DESC 'Custom TCP buffer size' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "threads", "count", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|CFG_THREADS, &config_generic,
+ "( OLcfgGlAt:66 NAME 'olcThreads' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SLAP_MAX_WORKER_THREADS }
+ },
+ { "threadqueues", "count", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|CFG_THREADQS, &config_generic,
+ "( OLcfgGlAt:95 NAME 'olcThreadQueues' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = 1 }
+ },
+ { "timelimit", "limit", 2, 0, 0, ARG_MAY_DB|ARG_MAGIC,
+ &config_timelimit, "( OLcfgGlAt:67 NAME 'olcTimeLimit' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCACertificate", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CACERT|ARG_BINARY|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:97 NAME 'olcTLSCACertificate' "
+ "DESC 'X.509 certificate, must use ;binary' "
+ "EQUALITY certificateExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 SINGLE-VALUE )", NULL, NULL },
+ { "TLSCACertificateFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CA_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:68 NAME 'olcTLSCACertificateFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCACertificatePath", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CA_PATH|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:69 NAME 'olcTLSCACertificatePath' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCertificate", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT|ARG_BINARY|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:98 NAME 'olcTLSCertificate' "
+ "DESC 'X.509 certificate, must use ;binary' "
+ "EQUALITY certificateExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 SINGLE-VALUE )", NULL, NULL },
+ { "TLSCertificateFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:70 NAME 'olcTLSCertificateFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCertificateKey", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_KEY|ARG_BINARY|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:99 NAME 'olcTLSCertificateKey' "
+ "DESC 'X.509 privateKey, must use ;binary' "
+ "EQUALITY privateKeyMatch "
+ "SYNTAX 1.2.840.113549.1.8.1.1 SINGLE-VALUE )", NULL, NULL },
+ { "TLSCertificateKeyFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CERT_KEY|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:71 NAME 'olcTLSCertificateKeyFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCipherSuite", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_CIPHER|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:72 NAME 'olcTLSCipherSuite' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCRLCheck", NULL, 2, 2, 0,
+#if defined(HAVE_TLS) && defined(HAVE_OPENSSL)
+ CFG_TLS_CRLCHECK|ARG_STRING|ARG_MAGIC, &config_tls_config,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:73 NAME 'olcTLSCRLCheck' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSCRLFile", NULL, 2, 2, 0,
+#if defined(HAVE_GNUTLS)
+ CFG_TLS_CRL_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:82 NAME 'olcTLSCRLFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSRandFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_RAND|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:74 NAME 'olcTLSRandFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSVerifyClient", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_VERIFY|ARG_STRING|ARG_MAGIC, &config_tls_config,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:75 NAME 'olcTLSVerifyClient' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSDHParamFile", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_DH_FILE|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:77 NAME 'olcTLSDHParamFile' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSECName", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_ECNAME|ARG_STRING|ARG_MAGIC, &config_tls_option,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:96 NAME 'olcTLSECName' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "TLSProtocolMin", NULL, 2, 2, 0,
+#ifdef HAVE_TLS
+ CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC, &config_tls_config,
+#else
+ ARG_IGNORED, NULL,
+#endif
+ "( OLcfgGlAt:87 NAME 'olcTLSProtocolMin' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "tool-threads", "count", 2, 2, 0, ARG_INT|ARG_MAGIC|CFG_TTHREADS,
+ &config_generic, "( OLcfgGlAt:80 NAME 'olcToolThreads' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = 1 }
+ },
+ { "ucdata-path", "path", 2, 2, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL },
+ { "updatedn", "dn", 2, 2, 0, ARG_DB|ARG_DN|ARG_QUOTE|ARG_MAGIC,
+ &config_updatedn, "( OLcfgDbAt:0.12 NAME 'olcUpdateDN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "updateref", "url", 2, 2, 0, ARG_DB|ARG_MAGIC,
+ &config_updateref, "( OLcfgDbAt:0.13 NAME 'olcUpdateRef' "
+ "EQUALITY caseIgnoreMatch "
+ "SUP labeledURI )", NULL, NULL },
+ { "writetimeout", "timeout", 2, 2, 0, ARG_INT,
+ &global_writetimeout, "( OLcfgGlAt:88 NAME 'olcWriteTimeout' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ /* Legacy keywords */
+ { "mirrormode", "on|off", 2, 2, 0, ARG_DB|ARG_ON_OFF|ARG_MAGIC|CFG_MULTIPROVIDER,
+ &config_generic, NULL, NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+/* Need to no-op this keyword for dynamic config */
+ConfigTable olcDatabaseDummy[] = {
+ { "", "", 0, 0, 0, ARG_IGNORED,
+ NULL, "( OLcfgGlAt:13 NAME 'olcDatabase' "
+ "DESC 'The backend type for a database instance' "
+ "SUP olcBackend SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+/* Routines to check if a child can be added to this type */
+static ConfigLDAPadd cfAddSchema, cfAddInclude, cfAddDatabase,
+ cfAddBackend, cfAddModule, cfAddOverlay;
+
+/* NOTE: be careful when defining array members
+ * that can be conditionally compiled */
+#define CFOC_GLOBAL cf_ocs[1]
+#define CFOC_SCHEMA cf_ocs[2]
+#define CFOC_BACKEND cf_ocs[3]
+#define CFOC_DATABASE cf_ocs[4]
+#define CFOC_OVERLAY cf_ocs[5]
+#define CFOC_INCLUDE cf_ocs[6]
+#define CFOC_FRONTEND cf_ocs[7]
+#ifdef SLAPD_MODULES
+#define CFOC_MODULE cf_ocs[8]
+#endif /* SLAPD_MODULES */
+
+static ConfigOCs cf_ocs[] = {
+ { "( OLcfgGlOc:0 "
+ "NAME 'olcConfig' "
+ "DESC 'OpenLDAP configuration object' "
+ "ABSTRACT SUP top )", Cft_Abstract, NULL },
+ { "( OLcfgGlOc:1 "
+ "NAME 'olcGlobal' "
+ "DESC 'OpenLDAP Global configuration options' "
+ "SUP olcConfig STRUCTURAL "
+ "MAY ( cn $ olcConfigFile $ olcConfigDir $ olcAllows $ olcArgsFile $ "
+ "olcAttributeOptions $ olcAuthIDRewrite $ "
+ "olcAuthzPolicy $ olcAuthzRegexp $ olcConcurrency $ "
+ "olcConnMaxPending $ olcConnMaxPendingAuth $ "
+ "olcDisallows $ olcGentleHUP $ olcIdleTimeout $ "
+ "olcIndexSubstrIfMaxLen $ olcIndexSubstrIfMinLen $ "
+ "olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexHash64 $ "
+ "olcIndexIntLen $ "
+ "olcListenerThreads $ olcLocalSSF $ olcLogFile $ olcLogFileFormat $ olcLogLevel $ "
+ "olcLogFileOnly $ olcLogFileRotate $ olcMaxFilterDepth $ "
+ "olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ "
+ "olcPluginLogFile $ olcReadOnly $ olcReferral $ "
+ "olcReplogFile $ olcRequires $ olcRestrict $ olcReverseLookup $ "
+ "olcRootDSE $ "
+ "olcSaslAuxprops $ olcSaslAuxpropsDontUseCopy $ olcSaslAuxpropsDontUseCopyIgnore $ "
+ "olcSaslCBinding $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ "
+ "olcSecurity $ olcServerID $ olcSizeLimit $ "
+ "olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ "
+ "olcTCPBuffer $ "
+ "olcThreads $ olcThreadQueues $ "
+ "olcTimeLimit $ olcTLSCACertificateFile $ "
+ "olcTLSCACertificatePath $ olcTLSCertificateFile $ "
+ "olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ "
+ "olcTLSCACertificate $ olcTLSCertificate $ olcTLSCertificateKey $ "
+ "olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSECName $ "
+ "olcTLSCRLFile $ olcTLSProtocolMin $ olcToolThreads $ olcWriteTimeout $ "
+ "olcObjectIdentifier $ olcAttributeTypes $ olcObjectClasses $ "
+ "olcDitContentRules $ olcLdapSyntaxes ) )", Cft_Global },
+ { "( OLcfgGlOc:2 "
+ "NAME 'olcSchemaConfig' "
+ "DESC 'OpenLDAP schema object' "
+ "SUP olcConfig STRUCTURAL "
+ "MAY ( cn $ olcObjectIdentifier $ olcLdapSyntaxes $ "
+ "olcAttributeTypes $ olcObjectClasses $ olcDitContentRules ) )",
+ Cft_Schema, NULL, cfAddSchema },
+ { "( OLcfgGlOc:3 "
+ "NAME 'olcBackendConfig' "
+ "DESC 'OpenLDAP Backend-specific options' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST olcBackend )", Cft_Backend, NULL, cfAddBackend },
+ { "( OLcfgGlOc:4 "
+ "NAME 'olcDatabaseConfig' "
+ "DESC 'OpenLDAP Database-specific options' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST olcDatabase "
+ "MAY ( olcDisabled $ olcHidden $ olcSuffix $ olcSubordinate $ olcAccess $ "
+ "olcAddContentAcl $ olcLastMod $ olcLastBind $ olcLastBindPrecision $ "
+ "olcLimits $ olcMaxDerefDepth $ olcPlugin $ olcReadOnly $ olcReplica $ "
+ "olcReplicaArgsFile $ olcReplicaPidFile $ olcReplicationInterval $ "
+ "olcReplogFile $ olcRequires $ olcRestrict $ olcRootDN $ olcRootPW $ "
+ "olcSchemaDN $ olcSecurity $ olcSizeLimit $ olcSyncUseSubentry $ olcSyncrepl $ "
+ "olcTimeLimit $ olcUpdateDN $ olcUpdateRef $ olcMultiProvider $ "
+ "olcMonitoring $ olcExtraAttrs ) )",
+ Cft_Database, NULL, cfAddDatabase },
+ { "( OLcfgGlOc:5 "
+ "NAME 'olcOverlayConfig' "
+ "DESC 'OpenLDAP Overlay-specific options' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST olcOverlay "
+ "MAY olcDisabled )", Cft_Overlay, NULL, cfAddOverlay },
+ { "( OLcfgGlOc:6 "
+ "NAME 'olcIncludeFile' "
+ "DESC 'OpenLDAP configuration include file' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST olcInclude "
+ "MAY ( cn $ olcRootDSE ) )",
+ /* Used to be Cft_Include, that def has been removed */
+ Cft_Abstract, NULL, cfAddInclude },
+ /* This should be STRUCTURAL like all the other database classes, but
+ * that would mean inheriting all of the olcDatabaseConfig attributes,
+ * which causes them to be merged twice in config_build_entry.
+ */
+ { "( OLcfgGlOc:7 "
+ "NAME 'olcFrontendConfig' "
+ "DESC 'OpenLDAP frontend configuration' "
+ "AUXILIARY "
+ "MAY ( olcDefaultSearchBase $ olcPasswordHash $ olcSortVals ) )",
+ Cft_Database, NULL, NULL },
+ { "( OLcfgGlOc:8 "
+ "NAME 'olcModuleList' "
+ "DESC 'OpenLDAP dynamic module info' "
+ "SUP olcConfig STRUCTURAL "
+ "MAY ( cn $ olcModulePath $ olcModuleLoad ) )",
+ Cft_Module, NULL, cfAddModule },
+ { NULL, 0, NULL }
+};
+
+typedef struct ServerID {
+ struct ServerID *si_next;
+ struct berval si_url;
+ int si_num;
+} ServerID;
+
+static ServerID *sid_list;
+static ServerID *sid_set;
+
+typedef struct voidList {
+ struct voidList *vl_next;
+ void *vl_ptr;
+} voidList;
+
+typedef struct ADlist {
+ struct ADlist *al_next;
+ AttributeDescription *al_desc;
+} ADlist;
+
+static ADlist *sortVals;
+
+static int new_daemon_threads;
+
+static int
+config_resize_lthreads(ConfigArgs *c)
+{
+ return slapd_daemon_resize( new_daemon_threads );
+}
+
+static int
+config_substr_if_check( ConfigArgs *c )
+{
+ if ( index_substr_if_maxlen < index_substr_if_minlen ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "attempted to set olcIndexSubstrIfMaxLen shorter than "
+ "olcIndexSubstrIfMinLen: %u < %u",
+ index_substr_if_maxlen, index_substr_if_minlen );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+ return LDAP_SUCCESS;
+}
+
+#define GOT_CONFIG 1
+#define GOT_FRONTEND 2
+static int
+config_unique_db;
+
+static int
+config_generic(ConfigArgs *c) {
+ int i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ int rc = 0;
+ switch(c->type) {
+ case CFG_CONCUR:
+ c->value_int = ldap_pvt_thread_get_concurrency();
+ break;
+ case CFG_THREADS:
+ c->value_int = connection_pool_max;
+ break;
+ case CFG_THREADQS:
+ c->value_int = connection_pool_queues;
+ break;
+ case CFG_TTHREADS:
+ c->value_int = slap_tool_thread_max;
+ break;
+ case CFG_LTHREADS:
+ c->value_uint = slapd_daemon_threads;
+ break;
+ case CFG_SALT:
+ if ( passwd_salt )
+ c->value_string = ch_strdup( passwd_salt );
+ else
+ rc = 1;
+ break;
+ case CFG_LIMITS:
+ if ( c->be->be_limits ) {
+ char buf[4096*3];
+ struct berval bv;
+
+ for ( i=0; c->be->be_limits[i]; i++ ) {
+ bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ if ( bv.bv_len >= sizeof( buf ) ) {
+ ber_bvarray_free_x( c->rvalue_vals, NULL );
+ c->rvalue_vals = NULL;
+ rc = 1;
+ break;
+ }
+ bv.bv_val = buf + bv.bv_len;
+ limits_unparse( c->be->be_limits[i], &bv,
+ sizeof( buf ) - ( bv.bv_val - buf ) );
+ bv.bv_len += bv.bv_val - buf;
+ bv.bv_val = buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ }
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+ case CFG_RO:
+ c->value_int = (c->be->be_restrictops & SLAP_RESTRICT_READONLY);
+ break;
+ case CFG_AZPOLICY:
+ c->value_string = ch_strdup( slap_sasl_getpolicy());
+ break;
+ case CFG_AZREGEXP:
+ slap_sasl_regexp_unparse( &c->rvalue_vals );
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+#ifdef HAVE_CYRUS_SASL
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ case CFG_AZDUC: {
+ static int duc_done = 0;
+
+ /* take the opportunity to initialize with known values */
+ if ( !duc_done ) {
+ struct berval duc[] = { BER_BVC("cmusaslsecretOTP"), BER_BVNULL };
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &duc[ i ] ); i++ ) {
+ const char *text = NULL;
+ AttributeDescription *ad = NULL;
+
+ if ( slap_bv2ad( &duc[ i ], &ad, &text ) == LDAP_SUCCESS ) {
+ int gotit = 0;
+ if ( slap_dontUseCopy_propnames ) {
+ int j;
+
+ for ( j = 0; !BER_BVISNULL( &slap_dontUseCopy_propnames[ j ] ); j++ ) {
+ if ( bvmatch( &slap_dontUseCopy_propnames[ j ], &ad->ad_cname ) ) {
+ gotit = 1;
+ }
+ }
+ }
+
+ if ( !gotit ) {
+ value_add_one( &slap_dontUseCopy_propnames, &ad->ad_cname );
+ }
+ }
+ }
+
+ duc_done = 1;
+ }
+
+ if ( slap_dontUseCopy_propnames != NULL ) {
+ ber_bvarray_dup_x( &c->rvalue_vals, slap_dontUseCopy_propnames, NULL );
+ } else {
+ rc = 1;
+ }
+ } break;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+ case CFG_SASLSECP: {
+ struct berval bv = BER_BVNULL;
+ slap_sasl_secprops_unparse( &bv );
+ if ( !BER_BVISNULL( &bv )) {
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ }
+ break;
+#endif
+ case CFG_DEPTH:
+ c->value_int = c->be->be_max_deref_depth;
+ break;
+ case CFG_DISABLED:
+ if ( c->bi ) {
+ /* overlay */
+ if ( c->bi->bi_flags & SLAPO_BFLAG_DISABLED ) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ } else {
+ /* database */
+ if ( SLAP_DBDISABLED( c->be )) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ }
+ break;
+ case CFG_HIDDEN:
+ if ( SLAP_DBHIDDEN( c->be )) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ break;
+ case CFG_OID: {
+ ConfigFile *cf = c->ca_private;
+ if ( !cf )
+ oidm_unparse( &c->rvalue_vals, NULL, NULL, 1 );
+ else if ( cf->c_om_head )
+ oidm_unparse( &c->rvalue_vals, cf->c_om_head,
+ cf->c_om_tail, 0 );
+ if ( !c->rvalue_vals )
+ rc = 1;
+ }
+ break;
+ case CFG_ATOPT:
+ ad_unparse_options( &c->rvalue_vals );
+ break;
+ case CFG_OC: {
+ ConfigFile *cf = c->ca_private;
+ if ( !cf )
+ oc_unparse( &c->rvalue_vals, NULL, NULL, 1 );
+ else if ( cf->c_oc_head )
+ oc_unparse( &c->rvalue_vals, cf->c_oc_head,
+ cf->c_oc_tail, 0 );
+ if ( !c->rvalue_vals )
+ rc = 1;
+ }
+ break;
+ case CFG_ATTR: {
+ ConfigFile *cf = c->ca_private;
+ if ( !cf )
+ at_unparse( &c->rvalue_vals, NULL, NULL, 1 );
+ else if ( cf->c_at_head )
+ at_unparse( &c->rvalue_vals, cf->c_at_head,
+ cf->c_at_tail, 0 );
+ if ( !c->rvalue_vals )
+ rc = 1;
+ }
+ break;
+ case CFG_SYNTAX: {
+ ConfigFile *cf = c->ca_private;
+ if ( !cf )
+ syn_unparse( &c->rvalue_vals, NULL, NULL, 1 );
+ else if ( cf->c_syn_head )
+ syn_unparse( &c->rvalue_vals, cf->c_syn_head,
+ cf->c_syn_tail, 0 );
+ if ( !c->rvalue_vals )
+ rc = 1;
+ }
+ break;
+ case CFG_DIT: {
+ ConfigFile *cf = c->ca_private;
+ if ( !cf )
+ cr_unparse( &c->rvalue_vals, NULL, NULL, 1 );
+ else if ( cf->c_cr_head )
+ cr_unparse( &c->rvalue_vals, cf->c_cr_head,
+ cf->c_cr_tail, 0 );
+ if ( !c->rvalue_vals )
+ rc = 1;
+ }
+ break;
+
+ case CFG_ACL: {
+ AccessControl *a;
+ char *src, *dst, ibuf[11];
+ struct berval bv, abv;
+ for (i=0, a=c->be->be_acl; a; i++,a=a->acl_next) {
+ abv.bv_len = snprintf( ibuf, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i );
+ if ( abv.bv_len >= sizeof( ibuf ) ) {
+ ber_bvarray_free_x( c->rvalue_vals, NULL );
+ c->rvalue_vals = NULL;
+ i = 0;
+ break;
+ }
+ acl_unparse( a, &bv );
+ abv.bv_val = ch_malloc( abv.bv_len + bv.bv_len + 1 );
+ AC_MEMCPY( abv.bv_val, ibuf, abv.bv_len );
+ /* Turn TAB / EOL into plain space */
+ for (src=bv.bv_val,dst=abv.bv_val+abv.bv_len; *src; src++) {
+ if (isspace((unsigned char)*src)) *dst++ = ' ';
+ else *dst++ = *src;
+ }
+ *dst = '\0';
+ if (dst[-1] == ' ') {
+ dst--;
+ *dst = '\0';
+ }
+ abv.bv_len = dst - abv.bv_val;
+ ber_bvarray_add( &c->rvalue_vals, &abv );
+ }
+ rc = (!i);
+ break;
+ }
+ case CFG_ACL_ADD:
+ c->value_int = (SLAP_DBACL_ADD(c->be) != 0);
+ break;
+ case CFG_ROOTDSE: {
+ ConfigFile *cf = c->ca_private;
+ if ( cf->c_dseFiles ) {
+ value_add( &c->rvalue_vals, cf->c_dseFiles );
+ } else {
+ rc = 1;
+ }
+ }
+ break;
+ case CFG_SERVERID:
+ if ( sid_list ) {
+ ServerID *si;
+ struct berval bv;
+
+ for ( si = sid_list; si; si=si->si_next ) {
+ assert( si->si_num >= 0 && si->si_num <= SLAP_SYNC_SID_MAX );
+ if ( !BER_BVISEMPTY( &si->si_url )) {
+ bv.bv_len = si->si_url.bv_len + 6;
+ bv.bv_val = ch_malloc( bv.bv_len );
+ bv.bv_len = sprintf( bv.bv_val, "%d %s", si->si_num,
+ si->si_url.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ } else {
+ char buf[5];
+ bv.bv_val = buf;
+ bv.bv_len = sprintf( buf, "%d", si->si_num );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+ case CFG_LASTMOD:
+ c->value_int = (SLAP_NOLASTMOD(c->be) == 0);
+ break;
+ case CFG_LASTBIND:
+ c->value_int = (SLAP_LASTBIND(c->be) != 0);
+ break;
+ case CFG_LASTBIND_PRECISION:
+ c->value_uint = c->be->be_lastbind_precision;
+ break;
+ case CFG_SYNC_SUBENTRY:
+ c->value_int = (SLAP_SYNC_SUBENTRY(c->be) != 0);
+ break;
+ case CFG_MULTIPROVIDER:
+ if ( SLAP_SHADOW(c->be))
+ c->value_int = (SLAP_MULTIPROVIDER(c->be) != 0);
+ else
+ rc = 1;
+ break;
+ case CFG_MONITORING:
+ c->value_int = (SLAP_DBMONITORING(c->be) != 0);
+ break;
+ case CFG_SSTR_IF_MAX:
+ c->value_uint = index_substr_if_maxlen;
+ break;
+ case CFG_SSTR_IF_MIN:
+ c->value_uint = index_substr_if_minlen;
+ break;
+ case CFG_IX_HASH64:
+ c->value_int = slap_hash64( -1 );
+ break;
+ case CFG_IX_INTLEN:
+ c->value_int = index_intlen;
+ break;
+ case CFG_SORTVALS: {
+ ADlist *sv;
+ rc = 1;
+ for ( sv = sortVals; sv; sv = sv->al_next ) {
+ value_add_one( &c->rvalue_vals, &sv->al_desc->ad_cname );
+ rc = 0;
+ }
+ } break;
+#ifdef SLAPD_MODULES
+ case CFG_MODLOAD: {
+ ModPaths *mp = c->ca_private;
+ if (mp->mp_loads) {
+ int i;
+ for (i=0; !BER_BVISNULL(&mp->mp_loads[i]); i++) {
+ struct berval bv;
+ bv.bv_val = c->log;
+ bv.bv_len = snprintf( bv.bv_val, sizeof( c->log ),
+ SLAP_X_ORDERED_FMT "%s", i,
+ mp->mp_loads[i].bv_val );
+ if ( bv.bv_len >= sizeof( c->log ) ) {
+ ber_bvarray_free_x( c->rvalue_vals, NULL );
+ c->rvalue_vals = NULL;
+ break;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ }
+
+ rc = c->rvalue_vals ? 0 : 1;
+ }
+ break;
+ case CFG_MODPATH: {
+ ModPaths *mp = c->ca_private;
+ if ( !BER_BVISNULL( &mp->mp_path ))
+ value_add_one( &c->rvalue_vals, &mp->mp_path );
+
+ rc = c->rvalue_vals ? 0 : 1;
+ }
+ break;
+#endif
+#ifdef LDAP_SLAPI
+ case CFG_PLUGIN:
+ slapi_int_plugin_unparse( c->be, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) rc = 1;
+ break;
+#endif
+ case CFG_REWRITE:
+ rc = slap_sasl_rewrite_unparse( &c->rvalue_vals );
+ break;
+ default:
+ rc = 1;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ int rc = 0;
+ switch(c->type) {
+ /* single-valued attrs */
+ case CFG_CONCUR:
+ /* FIXME: There is currently no way to retrieve the default? */
+ break;
+
+ case CFG_THREADS:
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_maxthreads(&connection_pool,
+ SLAP_MAX_WORKER_THREADS);
+ connection_pool_max = SLAP_MAX_WORKER_THREADS; /* save for reference */
+ break;
+
+ case CFG_THREADQS:
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_queues(&connection_pool, 1);
+ connection_pool_queues = 1; /* save for reference */
+ break;
+
+ case CFG_TTHREADS:
+ slap_tool_thread_max = 1;
+ break;
+
+ case CFG_LTHREADS:
+ new_daemon_threads = 1;
+ config_push_cleanup( c, config_resize_lthreads );
+ break;
+
+ case CFG_AZPOLICY:
+ slap_sasl_setpolicy( "none" );
+ break;
+
+ case CFG_DEPTH:
+ c->be->be_max_deref_depth = c->ca_desc->arg_default.v_int;
+ break;
+
+ case CFG_LASTMOD:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_NOLASTMOD;
+ break;
+
+ case CFG_LASTBIND:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_LASTBIND;
+ break;
+
+ case CFG_MONITORING:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MONITORING;
+ break;
+
+ case CFG_SASLSECP:
+#ifdef HAVE_CYRUS_SASL
+ slap_sasl_secprops( "" );
+#endif
+ break;
+
+ case CFG_SSTR_IF_MAX:
+ index_substr_if_maxlen = c->ca_desc->arg_default.v_uint;
+ /* ITS#7215 Postpone range check until the entire modify is finished */
+ config_push_cleanup( c, config_substr_if_check );
+ break;
+
+ case CFG_SSTR_IF_MIN:
+ index_substr_if_minlen = c->ca_desc->arg_default.v_uint;
+ /* ITS#7215 Postpone range check until the entire modify is finished */
+ config_push_cleanup( c, config_substr_if_check );
+ break;
+
+ case CFG_ACL_ADD:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_ACL_ADD;
+ break;
+
+ case CFG_SYNC_SUBENTRY:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_SYNC_SUBENTRY;
+ break;
+
+ case CFG_LASTBIND_PRECISION:
+ c->be->be_lastbind_precision = 0;
+ break;
+
+ case CFG_RO:
+ c->be->be_restrictops &= ~SLAP_RESTRICT_READONLY;
+ break;
+
+#ifdef LDAP_SLAPI
+ case CFG_PLUGIN:
+ slapi_int_unregister_plugins(c->be, c->valx);
+ break;
+#endif
+
+ /* no-op, requires slapd restart */
+ case CFG_MODLOAD:
+ snprintf(c->log, sizeof( c->log ), "change requires slapd restart");
+ break;
+
+ case CFG_MULTIPROVIDER:
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MULTI_SHADOW;
+ if(SLAP_SHADOW(c->be))
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW;
+ break;
+
+#if defined(HAVE_CYRUS_SASL) && defined(SLAP_AUXPROP_DONTUSECOPY)
+ case CFG_AZDUC:
+ if ( c->valx < 0 ) {
+ if ( slap_dontUseCopy_propnames != NULL ) {
+ ber_bvarray_free( slap_dontUseCopy_propnames );
+ slap_dontUseCopy_propnames = NULL;
+ }
+
+ } else {
+ int i;
+
+ if ( slap_dontUseCopy_propnames == NULL ) {
+ rc = 1;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &slap_dontUseCopy_propnames[ i ] ) && i < c->valx; i++ );
+ if ( i < c->valx ) {
+ rc = 1;
+ break;
+ }
+ ber_memfree( slap_dontUseCopy_propnames[ i ].bv_val );
+ for ( ; !BER_BVISNULL( &slap_dontUseCopy_propnames[ i + 1 ] ); i++ ) {
+ slap_dontUseCopy_propnames[ i ] = slap_dontUseCopy_propnames[ i + 1 ];
+ }
+ BER_BVZERO( &slap_dontUseCopy_propnames[ i ] );
+ }
+ break;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ case CFG_AZREGEXP:
+ rc = slap_sasl_regexp_delete( c->valx );
+ break;
+
+ case CFG_REWRITE:
+ rc = slap_sasl_rewrite_delete( c->valx );
+ break;
+
+ case CFG_SALT:
+ ch_free( passwd_salt );
+ passwd_salt = NULL;
+ break;
+
+ case CFG_SERVERID: {
+ ServerID *si, **sip;
+
+ for ( i=0, si = sid_list, sip = &sid_list;
+ si; si = *sip, i++ ) {
+ if ( c->valx == -1 || i == c->valx ) {
+ *sip = si->si_next;
+ if ( sid_set == si )
+ sid_set = NULL;
+ ch_free( si );
+ if ( c->valx >= 0 )
+ break;
+ } else {
+ sip = &si->si_next;
+ }
+ }
+ }
+ break;
+ case CFG_HIDDEN:
+ c->be->be_flags &= ~SLAP_DBFLAG_HIDDEN;
+ break;
+
+ case CFG_DISABLED:
+ if ( c->bi ) {
+ c->bi->bi_flags &= ~SLAP_DBFLAG_DISABLED;
+ if ( c->bi->bi_db_open ) {
+ BackendInfo *bi_orig = c->be->bd_info;
+ c->be->bd_info = c->bi;
+ rc = c->bi->bi_db_open( c->be, &c->reply );
+ c->be->bd_info = bi_orig;
+ }
+ } else {
+ c->be->be_flags &= ~SLAP_DBFLAG_DISABLED;
+ rc = backend_startup_one( c->be, &c->reply );
+ }
+ break;
+
+ case CFG_IX_HASH64:
+ slap_hash64( 0 );
+ break;
+
+ case CFG_IX_INTLEN:
+ index_intlen = SLAP_INDEX_INTLEN_DEFAULT;
+ index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN(
+ SLAP_INDEX_INTLEN_DEFAULT );
+ break;
+
+ case CFG_ACL:
+ if ( c->valx < 0 ) {
+ acl_destroy( c->be->be_acl );
+ c->be->be_acl = NULL;
+
+ } else {
+ AccessControl **prev, *a;
+ int i;
+ for (i=0, prev = &c->be->be_acl; i < c->valx;
+ i++ ) {
+ a = *prev;
+ prev = &a->acl_next;
+ }
+ a = *prev;
+ *prev = a->acl_next;
+ acl_free( a );
+ }
+ if ( SLAP_CONFIG( c->be ) && !c->be->be_acl ) {
+ Debug( LDAP_DEBUG_CONFIG, "config_generic (CFG_ACL): "
+ "Last explicit ACL for back-config removed. "
+ "Using hardcoded default\n" );
+ c->be->be_acl = defacl_parsed;
+ }
+ break;
+
+ case CFG_OC: {
+ CfEntryInfo *ce;
+ /* Can be NULL when undoing a failed add */
+ if ( c->ca_entry ) {
+ ce = c->ca_entry->e_private;
+ /* can't modify the hardcoded schema */
+ if ( ce->ce_parent->ce_type == Cft_Global )
+ return 1;
+ }
+ }
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ ObjectClass *oc;
+
+ for( oc = cfn->c_oc_head; oc; oc_next( &oc )) {
+ oc_delete( oc );
+ if ( oc == cfn->c_oc_tail )
+ break;
+ }
+ cfn->c_oc_head = cfn->c_oc_tail = NULL;
+ } else {
+ ObjectClass *oc, *prev = NULL;
+
+ for ( i=0, oc=cfn->c_oc_head; i<c->valx; i++) {
+ prev = oc;
+ oc_next( &oc );
+ }
+ oc_delete( oc );
+ if ( cfn->c_oc_tail == oc ) {
+ cfn->c_oc_tail = prev;
+ }
+ if ( cfn->c_oc_head == oc ) {
+ oc_next( &oc );
+ cfn->c_oc_head = oc;
+ }
+ }
+ break;
+
+ case CFG_ATTR: {
+ CfEntryInfo *ce;
+ /* Can be NULL when undoing a failed add */
+ if ( c->ca_entry ) {
+ ce = c->ca_entry->e_private;
+ /* can't modify the hardcoded schema */
+ if ( ce->ce_parent->ce_type == Cft_Global )
+ return 1;
+ }
+ }
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ AttributeType *at;
+
+ for( at = cfn->c_at_head; at; at_next( &at )) {
+ at_delete( at );
+ if ( at == cfn->c_at_tail )
+ break;
+ }
+ cfn->c_at_head = cfn->c_at_tail = NULL;
+ } else {
+ AttributeType *at, *prev = NULL;
+
+ for ( i=0, at=cfn->c_at_head; i<c->valx; i++) {
+ prev = at;
+ at_next( &at );
+ }
+ at_delete( at );
+ if ( cfn->c_at_tail == at ) {
+ cfn->c_at_tail = prev;
+ }
+ if ( cfn->c_at_head == at ) {
+ at_next( &at );
+ cfn->c_at_head = at;
+ }
+ }
+ break;
+
+ case CFG_SYNTAX: {
+ CfEntryInfo *ce;
+ /* Can be NULL when undoing a failed add */
+ if ( c->ca_entry ) {
+ ce = c->ca_entry->e_private;
+ /* can't modify the hardcoded schema */
+ if ( ce->ce_parent->ce_type == Cft_Global )
+ return 1;
+ }
+ }
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ Syntax *syn;
+
+ for( syn = cfn->c_syn_head; syn; syn_next( &syn )) {
+ syn_delete( syn );
+ if ( syn == cfn->c_syn_tail )
+ break;
+ }
+ cfn->c_syn_head = cfn->c_syn_tail = NULL;
+ } else {
+ Syntax *syn, *prev = NULL;
+
+ for ( i = 0, syn = cfn->c_syn_head; i < c->valx; i++) {
+ prev = syn;
+ syn_next( &syn );
+ }
+ syn_delete( syn );
+ if ( cfn->c_syn_tail == syn ) {
+ cfn->c_syn_tail = prev;
+ }
+ if ( cfn->c_syn_head == syn ) {
+ syn_next( &syn );
+ cfn->c_syn_head = syn;
+ }
+ }
+ break;
+ case CFG_SORTVALS:
+ if ( c->valx < 0 ) {
+ ADlist *sv;
+ for ( sv = sortVals; sv; sv = sortVals ) {
+ sortVals = sv->al_next;
+ sv->al_desc->ad_type->sat_flags &= ~SLAP_AT_SORTED_VAL;
+ ch_free( sv );
+ }
+ } else {
+ ADlist *sv, **prev;
+ int i = 0;
+
+ for ( prev = &sortVals, sv = sortVals; i < c->valx; i++ ) {
+ prev = &sv->al_next;
+ sv = sv->al_next;
+ }
+ sv->al_desc->ad_type->sat_flags &= ~SLAP_AT_SORTED_VAL;
+ *prev = sv->al_next;
+ ch_free( sv );
+ }
+ break;
+
+ case CFG_LIMITS:
+ /* FIXME: there is no limits_free function */
+ if ( c->valx < 0 ) {
+ limits_destroy( c->be->be_limits );
+ c->be->be_limits = NULL;
+
+ } else {
+ int cnt, num = -1;
+
+ if ( c->be->be_limits ) {
+ for ( num = 0; c->be->be_limits[ num ]; num++ )
+ /* just count */ ;
+ }
+
+ if ( c->valx >= num ) {
+ return 1;
+ }
+
+ if ( num == 1 ) {
+ limits_destroy( c->be->be_limits );
+ c->be->be_limits = NULL;
+
+ } else {
+ limits_free_one( c->be->be_limits[ c->valx ] );
+
+ for ( cnt = c->valx; cnt < num; cnt++ ) {
+ c->be->be_limits[ cnt ] = c->be->be_limits[ cnt + 1 ];
+ }
+ }
+ }
+ break;
+
+ case CFG_ATOPT:
+ /* FIXME: there is no ad_option_free function */
+ case CFG_ROOTDSE:
+ /* FIXME: there is no way to remove attributes added by
+ a DSE file */
+ case CFG_OID:
+ case CFG_DIT:
+ case CFG_MODPATH:
+ default:
+ rc = 1;
+ break;
+ }
+ return rc;
+ }
+
+ switch(c->type) {
+ case CFG_BACKEND:
+ if(!(c->bi = backend_info(c->argv[1]))) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> failed init", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ if ( c->bi->bi_flags & SLAP_BFLAG_STANDALONE ) {
+ c->bi->bi_nDB++;
+ nbackends++;
+ }
+ c->be = NULL;
+ break;
+
+ case CFG_DATABASE:
+ c->bi = NULL;
+ /* NOTE: config is always the first backend!
+ */
+ if ( !strcasecmp( c->argv[1], "config" )) {
+ if (config_unique_db & GOT_CONFIG) {
+ sprintf( c->cr_msg, "config DB already defined");
+ return(1);
+ }
+ c->be = LDAP_STAILQ_FIRST(&backendDB);
+ config_unique_db |= GOT_CONFIG;
+ } else if ( !strcasecmp( c->argv[1], "frontend" )) {
+ if (config_unique_db & GOT_FRONTEND) {
+ sprintf( c->cr_msg, "frontend DB already defined");
+ return(1);
+ }
+ c->be = frontendDB;
+ config_unique_db |= GOT_FRONTEND;
+ } else {
+ c->be = backend_db_init(c->argv[1], NULL, c->valx, &c->reply);
+ if ( !c->be ) {
+ if ( c->cr_msg[0] == 0 )
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> failed init", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n", c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ }
+ break;
+
+ case CFG_CONCUR:
+ ldap_pvt_thread_set_concurrency(c->value_int);
+ break;
+
+ case CFG_THREADS:
+ if ( c->value_int < 2 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "threads=%d smaller than minimum value 2",
+ c->value_int );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+
+ } else if ( c->value_int > 2 * SLAP_MAX_WORKER_THREADS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning, threads=%d larger than twice the default (2*%d=%d); YMMV",
+ c->value_int, SLAP_MAX_WORKER_THREADS, 2 * SLAP_MAX_WORKER_THREADS );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ }
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_maxthreads(&connection_pool, c->value_int);
+ connection_pool_max = c->value_int; /* save for reference */
+ break;
+
+ case CFG_THREADQS:
+ if ( c->value_int < 1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "threadqueues=%d smaller than minimum value 1",
+ c->value_int );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ if ( slapMode & SLAP_SERVER_MODE )
+ ldap_pvt_thread_pool_queues(&connection_pool, c->value_int);
+ connection_pool_queues = c->value_int; /* save for reference */
+ break;
+
+ case CFG_TTHREADS:
+ if ( slapMode & SLAP_TOOL_MODE )
+ ldap_pvt_thread_pool_maxthreads(&connection_pool, c->value_int);
+ slap_tool_thread_max = c->value_int; /* save for reference */
+ break;
+
+ case CFG_LTHREADS:
+ if ( c->value_uint < 1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "listenerthreads=%u smaller than minimum value 1",
+ c->value_uint );
+ Debug(LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ { int mask = 0;
+ /* use a power of two */
+ while (c->value_uint > 1) {
+ c->value_uint >>= 1;
+ mask <<= 1;
+ mask |= 1;
+ }
+ new_daemon_threads = mask+1;
+ config_push_cleanup( c, config_resize_lthreads );
+ }
+ break;
+
+ case CFG_SALT:
+ if ( passwd_salt ) ch_free( passwd_salt );
+ passwd_salt = c->value_string;
+ lutil_salt_format(passwd_salt);
+ break;
+
+ case CFG_LIMITS:
+ if(limits_parse(c->be, c->fname, c->lineno, c->argc, c->argv))
+ return(1);
+ break;
+
+ case CFG_RO:
+ if(c->value_int)
+ c->be->be_restrictops |= SLAP_RESTRICT_READONLY;
+ else
+ c->be->be_restrictops &= ~SLAP_RESTRICT_READONLY;
+ break;
+
+ case CFG_AZPOLICY:
+ ch_free(c->value_string);
+ if (slap_sasl_setpolicy( c->argv[1] )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ break;
+
+ case CFG_AZREGEXP:
+ if (slap_sasl_regexp_config( c->argv[1], c->argv[2], c->valx ))
+ return(1);
+ break;
+
+#ifdef HAVE_CYRUS_SASL
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ case CFG_AZDUC: {
+ int arg, cnt;
+
+ for ( arg = 1; arg < c->argc; arg++ ) {
+ int duplicate = 0, err;
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ err = slap_str2ad( c->argv[ arg ], &ad, &text );
+ if ( err != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s>: attr #%d (\"%s\") unknown (err=%d \"%s\"; ignored)",
+ c->argv[0], arg, c->argv[ arg ], err, text );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+
+ } else {
+ if ( slap_dontUseCopy_propnames != NULL ) {
+ for ( cnt = 0; !BER_BVISNULL( &slap_dontUseCopy_propnames[ cnt ] ); cnt++ ) {
+ if ( bvmatch( &slap_dontUseCopy_propnames[ cnt ], &ad->ad_cname ) ) {
+ duplicate = 1;
+ break;
+ }
+ }
+ }
+
+ if ( duplicate ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s>: attr #%d (\"%s\") already defined (ignored)",
+ c->argv[0], arg, ad->ad_cname.bv_val);
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ continue;
+ }
+
+ value_add_one( &slap_dontUseCopy_propnames, &ad->ad_cname );
+ }
+ }
+
+ } break;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ case CFG_SASLSECP:
+ {
+ char *txt = slap_sasl_secprops( c->argv[1] );
+ if ( txt ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> %s",
+ c->argv[0], txt );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return(1);
+ }
+ break;
+ }
+#endif
+
+ case CFG_DEPTH:
+ c->be->be_max_deref_depth = c->value_int;
+ break;
+
+ case CFG_OID: {
+ OidMacro *om;
+
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ if(parse_oidm(c, 1, &om))
+ return(1);
+ if (!cfn->c_om_head) cfn->c_om_head = om;
+ cfn->c_om_tail = om;
+ }
+ break;
+
+ case CFG_OC: {
+ ObjectClass *oc, *prev;
+
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ prev = cfn->c_oc_tail;
+ } else {
+ prev = NULL;
+ /* If adding anything after the first, prev is easy */
+ if ( c->valx ) {
+ int i;
+ for (i=0, oc = cfn->c_oc_head; i<c->valx; i++) {
+ prev = oc;
+ if ( !oc_next( &oc ))
+ break;
+ }
+ } else
+ /* If adding the first, and head exists, find its prev */
+ if (cfn->c_oc_head) {
+ for ( oc_start( &oc ); oc != cfn->c_oc_head; ) {
+ prev = oc;
+ oc_next( &oc );
+ }
+ }
+ /* else prev is NULL, append to end of global list */
+ }
+ if(parse_oc(c, &oc, prev)) return(1);
+ if (!cfn->c_oc_head || !c->valx) cfn->c_oc_head = oc;
+ if (cfn->c_oc_tail == prev) cfn->c_oc_tail = oc;
+ }
+ break;
+
+ case CFG_ATTR: {
+ AttributeType *at, *prev;
+
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ prev = cfn->c_at_tail;
+ } else {
+ prev = NULL;
+ /* If adding anything after the first, prev is easy */
+ if ( c->valx ) {
+ int i;
+ for (i=0, at = cfn->c_at_head; i<c->valx; i++) {
+ prev = at;
+ if ( !at_next( &at ))
+ break;
+ }
+ } else
+ /* If adding the first, and head exists, find its prev */
+ if (cfn->c_at_head) {
+ for ( at_start( &at ); at != cfn->c_at_head; ) {
+ prev = at;
+ at_next( &at );
+ }
+ }
+ /* else prev is NULL, append to end of global list */
+ }
+ if(parse_at(c, &at, prev)) return(1);
+ if (!cfn->c_at_head || !c->valx) cfn->c_at_head = at;
+ if (cfn->c_at_tail == prev) cfn->c_at_tail = at;
+ }
+ break;
+
+ case CFG_SYNTAX: {
+ Syntax *syn, *prev;
+
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ if ( c->valx < 0 ) {
+ prev = cfn->c_syn_tail;
+ } else {
+ prev = NULL;
+ /* If adding anything after the first, prev is easy */
+ if ( c->valx ) {
+ int i;
+ for ( i = 0, syn = cfn->c_syn_head; i < c->valx; i++ ) {
+ prev = syn;
+ if ( !syn_next( &syn ))
+ break;
+ }
+ } else
+ /* If adding the first, and head exists, find its prev */
+ if (cfn->c_syn_head) {
+ for ( syn_start( &syn ); syn != cfn->c_syn_head; ) {
+ prev = syn;
+ syn_next( &syn );
+ }
+ }
+ /* else prev is NULL, append to end of global list */
+ }
+ if ( parse_syn( c, &syn, prev ) ) return(1);
+ if ( !cfn->c_syn_head || !c->valx ) cfn->c_syn_head = syn;
+ if ( cfn->c_syn_tail == prev ) cfn->c_syn_tail = syn;
+ }
+ break;
+
+ case CFG_DIT: {
+ ContentRule *cr;
+
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ if(parse_cr(c, &cr)) return(1);
+ if (!cfn->c_cr_head) cfn->c_cr_head = cr;
+ cfn->c_cr_tail = cr;
+ }
+ break;
+
+ case CFG_ATOPT:
+ ad_define_option(NULL, NULL, 0);
+ for(i = 1; i < c->argc; i++)
+ if(ad_define_option(c->argv[i], c->fname, c->lineno))
+ return(1);
+ break;
+
+ case CFG_IX_HASH64:
+ if ( slap_hash64( c->value_int != 0 ))
+ return 1;
+ break;
+
+ case CFG_IX_INTLEN:
+ if ( c->value_int < SLAP_INDEX_INTLEN_DEFAULT )
+ c->value_int = SLAP_INDEX_INTLEN_DEFAULT;
+ else if ( c->value_int > 255 )
+ c->value_int = 255;
+ index_intlen = c->value_int;
+ index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN(
+ index_intlen );
+ break;
+
+ case CFG_SORTVALS: {
+ ADlist *svnew = NULL, *svtail, *sv;
+
+ for ( i = 1; i < c->argc; i++ ) {
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( c->argv[i], &ad, &text );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown attribute type #%d",
+ c->argv[0], i );
+sortval_reject:
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i] );
+ for ( sv = svnew; sv; sv = svnew ) {
+ svnew = sv->al_next;
+ ch_free( sv );
+ }
+ return 1;
+ }
+ if (( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) ||
+ ad->ad_type->sat_single_value ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> inappropriate attribute type #%d",
+ c->argv[0], i );
+ goto sortval_reject;
+ }
+ sv = ch_malloc( sizeof( ADlist ));
+ sv->al_desc = ad;
+ if ( !svnew ) {
+ svnew = sv;
+ } else {
+ svtail->al_next = sv;
+ }
+ svtail = sv;
+ }
+ sv->al_next = NULL;
+ for ( sv = svnew; sv; sv = sv->al_next )
+ sv->al_desc->ad_type->sat_flags |= SLAP_AT_SORTED_VAL;
+ for ( sv = sortVals; sv && sv->al_next; sv = sv->al_next );
+ if ( sv )
+ sv->al_next = svnew;
+ else
+ sortVals = svnew;
+ }
+ break;
+
+ case CFG_ACL:
+ if ( SLAP_CONFIG( c->be ) && c->be->be_acl == defacl_parsed) {
+ c->be->be_acl = NULL;
+ }
+ /* Don't append to the global ACL if we're on a specific DB */
+ i = c->valx;
+ if ( c->valx == -1 ) {
+ AccessControl *a;
+ i = 0;
+ for ( a=c->be->be_acl; a; a = a->acl_next )
+ i++;
+ }
+ if ( parse_acl( c, i ) ) {
+ if ( SLAP_CONFIG( c->be ) && !c->be->be_acl) {
+ c->be->be_acl = defacl_parsed;
+ }
+ return 1;
+ }
+ break;
+
+ case CFG_ACL_ADD:
+ if(c->value_int)
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_ACL_ADD;
+ else
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_ACL_ADD;
+ break;
+
+ case CFG_ROOTDSE:
+ if(root_dse_read_file(c->argv[1])) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> could not read file", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ {
+ struct berval bv;
+ ber_str2bv( c->argv[1], 0, 1, &bv );
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && cfn != c->ca_private )
+ cfn = c->ca_private;
+ ber_bvarray_add( &cfn->c_dseFiles, &bv );
+ }
+ break;
+
+ case CFG_SERVERID:
+ {
+ ServerID *si, **sip;
+ LDAPURLDesc *lud;
+ int num;
+ if (( lutil_atoi( &num, c->argv[1] ) &&
+ lutil_atoix( &num, c->argv[1], 16 )) ||
+ num < 0 || num > SLAP_SYNC_SID_MAX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> illegal server ID", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return 1;
+ }
+ /* only one value allowed if no URL is given */
+ if ( c->argc > 2 ) {
+ int len;
+
+ if ( sid_list && BER_BVISEMPTY( &sid_list->si_url )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> only one server ID allowed now", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return 1;
+ }
+
+ if ( ldap_url_parse( c->argv[2], &lud )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid URL", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[2] );
+ return 1;
+ }
+ len = strlen( c->argv[2] );
+ si = ch_malloc( sizeof(ServerID) + len + 1 );
+ si->si_url.bv_val = (char *)(si+1);
+ si->si_url.bv_len = len;
+ strcpy( si->si_url.bv_val, c->argv[2] );
+ } else {
+ if ( sid_list ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unqualified server ID not allowed now", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return 1;
+ }
+ si = ch_malloc( sizeof(ServerID) );
+ BER_BVZERO( &si->si_url );
+ slap_serverID = num;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: SID=0x%03x\n",
+ c->log, slap_serverID );
+ sid_set = si;
+ }
+ si->si_next = NULL;
+ si->si_num = num;
+ for ( sip = &sid_list; *sip; sip = &(*sip)->si_next );
+ *sip = si;
+
+ if (( slapMode & SLAP_SERVER_MODE ) && c->argc > 2 ) {
+ Listener *l = config_check_my_url( c->argv[2], lud );
+ if ( l ) {
+ if ( sid_set ) {
+ ldap_free_urldesc( lud );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> multiple server ID URLs matched, only one is allowed", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return 1;
+ }
+ slap_serverID = si->si_num;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: SID=0x%03x (listener=%s)\n",
+ c->log, slap_serverID,
+ l->sl_url.bv_val );
+ sid_set = si;
+ }
+ }
+ if ( c->argc > 2 )
+ ldap_free_urldesc( lud );
+ }
+ break;
+
+ case CFG_LASTMOD:
+ if(SLAP_NOLASTMODCMD(c->be)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> not available for %s database",
+ c->argv[0], c->be->bd_info->bi_type );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+ if(c->value_int)
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_NOLASTMOD;
+ else
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_NOLASTMOD;
+ break;
+
+ case CFG_LASTBIND:
+ if (c->value_int)
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_LASTBIND;
+ else
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_LASTBIND;
+ break;
+
+ case CFG_LASTBIND_PRECISION:
+ c->be->be_lastbind_precision = c->value_uint;
+ break;
+
+ case CFG_MULTIPROVIDER:
+ if(c->value_int && !SLAP_SHADOW(c->be)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> database is not a shadow",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+ if(c->value_int) {
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_SINGLE_SHADOW;
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_MULTI_SHADOW;
+ } else {
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW;
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MULTI_SHADOW;
+ }
+ break;
+
+ case CFG_MONITORING:
+ if(c->value_int)
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_MONITORING;
+ else
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_MONITORING;
+ break;
+
+ case CFG_DISABLED:
+ if ( c->bi ) {
+ if (c->value_int) {
+ if ( c->bi->bi_db_close ) {
+ BackendInfo *bi_orig = c->be->bd_info;
+ c->be->bd_info = c->bi;
+ c->bi->bi_db_close( c->be, &c->reply );
+ c->be->bd_info = bi_orig;
+ }
+ c->bi->bi_flags |= SLAPO_BFLAG_DISABLED;
+ } else {
+ c->bi->bi_flags &= ~SLAPO_BFLAG_DISABLED;
+ }
+ } else {
+ if (c->value_int) {
+ backend_shutdown( c->be );
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_DISABLED;
+ } else {
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_DISABLED;
+ }
+ }
+ break;
+
+ case CFG_HIDDEN:
+ if (c->value_int)
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_HIDDEN;
+ else
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_HIDDEN;
+ break;
+
+ case CFG_SYNC_SUBENTRY:
+ if (c->value_int)
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SYNC_SUBENTRY;
+ else
+ SLAP_DBFLAGS(c->be) &= ~SLAP_DBFLAG_SYNC_SUBENTRY;
+ break;
+
+ case CFG_SSTR_IF_MAX:
+ index_substr_if_maxlen = c->value_uint;
+ /* ITS#7215 Postpone range check until the entire modify is finished */
+ config_push_cleanup( c, config_substr_if_check );
+ break;
+
+ case CFG_SSTR_IF_MIN:
+ index_substr_if_minlen = c->value_uint;
+ /* ITS#7215 Postpone range check until the entire modify is finished */
+ config_push_cleanup( c, config_substr_if_check );
+ break;
+
+#ifdef SLAPD_MODULES
+ case CFG_MODLOAD:
+ /* If we're just adding a module on an existing modpath,
+ * make sure we've selected the current path.
+ */
+ if ( c->op == LDAP_MOD_ADD && c->ca_private && modcur != c->ca_private ) {
+ modcur = c->ca_private;
+ /* This should never fail */
+ if ( module_path( modcur->mp_path.bv_val )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> module path no longer valid",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n",
+ c->log, c->cr_msg, modcur->mp_path.bv_val );
+ return(1);
+ }
+ }
+ if(module_load(c->argv[1], c->argc - 2, (c->argc > 2) ? c->argv + 2 : NULL))
+ return(1);
+ /* Record this load on the current path */
+ {
+ struct berval bv;
+ char *ptr;
+ if ( c->op == SLAP_CONFIG_ADD ) {
+ ptr = c->line + STRLENOF("moduleload");
+ while (!isspace((unsigned char) *ptr)) ptr++;
+ while (isspace((unsigned char) *ptr)) ptr++;
+ } else {
+ ptr = c->line;
+ }
+ ber_str2bv(ptr, 0, 1, &bv);
+ ber_bvarray_add( &modcur->mp_loads, &bv );
+ }
+ /* Check for any new hardcoded schema */
+ if ( c->op == LDAP_MOD_ADD && CONFIG_ONLINE_ADD( c )) {
+ config_check_schema( NULL, &cfBackInfo );
+ }
+ break;
+
+ case CFG_MODPATH:
+ if(module_path(c->argv[1])) return(1);
+ /* Record which path was used with each module */
+ {
+ ModPaths *mp;
+
+ if (!modpaths.mp_loads) {
+ mp = &modpaths;
+ } else {
+ mp = ch_malloc( sizeof( ModPaths ));
+ modlast->mp_next = mp;
+ }
+ ber_str2bv(c->argv[1], 0, 1, &mp->mp_path);
+ mp->mp_next = NULL;
+ mp->mp_loads = NULL;
+ modlast = mp;
+ c->ca_private = mp;
+ modcur = mp;
+ }
+
+ break;
+#endif
+
+#ifdef LDAP_SLAPI
+ case CFG_PLUGIN:
+ if(slapi_int_read_config(c) != LDAP_SUCCESS)
+ return(1);
+ slapi_plugins_used++;
+ break;
+#endif
+
+ case CFG_REWRITE: {
+ int rc;
+
+ if ( c->op == LDAP_MOD_ADD ) {
+ c->argv++;
+ c->argc--;
+ }
+ rc = slap_sasl_rewrite_config(c->fname, c->lineno, c->argc, c->argv, c->valx);
+ if ( c->op == LDAP_MOD_ADD ) {
+ c->argv--;
+ c->argc++;
+ }
+ return rc;
+ }
+
+
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "%s: unknown CFG_TYPE %d.\n",
+ c->log, c->type );
+ return 1;
+
+ }
+ return(0);
+}
+
+
+static int
+config_fname(ConfigArgs *c) {
+ if(c->op == SLAP_CONFIG_EMIT) {
+ if (c->ca_private) {
+ ConfigFile *cf = c->ca_private;
+ value_add_one( &c->rvalue_vals, &cf->c_file );
+ return 0;
+ }
+ return 1;
+ }
+ return(0);
+}
+
+static int
+config_cfdir(ConfigArgs *c) {
+ if(c->op == SLAP_CONFIG_EMIT) {
+ if ( !BER_BVISEMPTY( &cfdir )) {
+ value_add_one( &c->rvalue_vals, &cfdir );
+ return 0;
+ }
+ return 1;
+ }
+ return(0);
+}
+
+static int
+config_search_base(ConfigArgs *c) {
+ if(c->op == SLAP_CONFIG_EMIT) {
+ int rc = 1;
+ if (!BER_BVISEMPTY(&default_search_base)) {
+ value_add_one(&c->rvalue_vals, &default_search_base);
+ value_add_one(&c->rvalue_nvals, &default_search_nbase);
+ rc = 0;
+ }
+ return rc;
+ } else if( c->op == LDAP_MOD_DELETE ) {
+ ch_free( default_search_base.bv_val );
+ ch_free( default_search_nbase.bv_val );
+ BER_BVZERO( &default_search_base );
+ BER_BVZERO( &default_search_nbase );
+ return 0;
+ }
+
+ if(c->bi || c->be != frontendDB) {
+ Debug(LDAP_DEBUG_ANY, "%s: defaultSearchBase line must appear "
+ "prior to any backend or database definition\n",
+ c->log );
+ return(1);
+ }
+
+ if(default_search_nbase.bv_len) {
+ free(default_search_base.bv_val);
+ free(default_search_nbase.bv_val);
+ }
+
+ default_search_base = c->value_dn;
+ default_search_nbase = c->value_ndn;
+ return(0);
+}
+
+/* For RE23 compatibility we allow this in the global entry
+ * but we now defer it to the frontend entry to allow modules
+ * to load new hash types.
+ */
+static int
+config_passwd_hash(ConfigArgs *c) {
+ int i;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ struct berval bv;
+ /* Don't generate it in the global entry */
+ if ( c->table == Cft_Global )
+ return 1;
+ for (i=0; default_passwd_hash && default_passwd_hash[i]; i++) {
+ ber_str2bv(default_passwd_hash[i], 0, 0, &bv);
+ value_add_one(&c->rvalue_vals, &bv);
+ }
+ return i ? 0 : 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* Deleting from global is a no-op, only the frontendDB entry matters */
+ if ( c->table == Cft_Global )
+ return 0;
+ if ( c->valx < 0 ) {
+ ldap_charray_free( default_passwd_hash );
+ default_passwd_hash = NULL;
+ } else {
+ i = c->valx;
+ ch_free( default_passwd_hash[i] );
+ for (; default_passwd_hash[i]; i++ )
+ default_passwd_hash[i] = default_passwd_hash[i+1];
+ }
+ return 0;
+ }
+ if ( c->op == LDAP_MOD_ADD && c->table == Cft_Global ) {
+ Debug( LDAP_DEBUG_ANY, "%s: setting password scheme in the global "
+ "entry is deprecated. The server may refuse to start if "
+ "it is provided by a loadable module, please move it to "
+ "the frontend database instead\n",
+ c->log );
+ }
+ for(i = 1; i < c->argc; i++) {
+ if(!lutil_passwd_scheme(c->argv[i])) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> scheme not available", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n",
+ c->log, c->cr_msg, c->argv[i]);
+ } else {
+ ldap_charray_add(&default_passwd_hash, c->argv[i]);
+ }
+ }
+ if(!default_passwd_hash) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> no valid hashes found", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+ return(0);
+}
+
+static int
+config_schema_dn(ConfigArgs *c) {
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ int rc = 1;
+ if ( !BER_BVISEMPTY( &c->be->be_schemadn )) {
+ value_add_one(&c->rvalue_vals, &c->be->be_schemadn);
+ value_add_one(&c->rvalue_nvals, &c->be->be_schemandn);
+ rc = 0;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ ch_free( c->be->be_schemadn.bv_val );
+ ch_free( c->be->be_schemandn.bv_val );
+ BER_BVZERO( &c->be->be_schemadn );
+ BER_BVZERO( &c->be->be_schemandn );
+ return 0;
+ }
+ ch_free( c->be->be_schemadn.bv_val );
+ ch_free( c->be->be_schemandn.bv_val );
+ c->be->be_schemadn = c->value_dn;
+ c->be->be_schemandn = c->value_ndn;
+ return(0);
+}
+
+static int
+config_sizelimit(ConfigArgs *c) {
+ int i, rc = 0;
+ struct slap_limits_set *lim = &c->be->be_def_limit;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ char buf[8192];
+ struct berval bv;
+ bv.bv_val = buf;
+ bv.bv_len = 0;
+ limits_unparse_one( lim, SLAP_LIMIT_SIZE, &bv, sizeof( buf ) );
+ if ( !BER_BVISEMPTY( &bv ))
+ value_add_one( &c->rvalue_vals, &bv );
+ else
+ rc = 1;
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* Reset to defaults or values from frontend */
+ if ( c->be == frontendDB ) {
+ lim->lms_s_soft = SLAPD_DEFAULT_SIZELIMIT;
+ lim->lms_s_hard = 0;
+ lim->lms_s_unchecked = -1;
+ lim->lms_s_pr = 0;
+ lim->lms_s_pr_hide = 0;
+ lim->lms_s_pr_total = 0;
+ } else {
+ lim->lms_s_soft = frontendDB->be_def_limit.lms_s_soft;
+ lim->lms_s_hard = frontendDB->be_def_limit.lms_s_hard;
+ lim->lms_s_unchecked = frontendDB->be_def_limit.lms_s_unchecked;
+ lim->lms_s_pr = frontendDB->be_def_limit.lms_s_pr;
+ lim->lms_s_pr_hide = frontendDB->be_def_limit.lms_s_pr_hide;
+ lim->lms_s_pr_total = frontendDB->be_def_limit.lms_s_pr_total;
+ }
+ goto ok;
+ }
+ for(i = 1; i < c->argc; i++) {
+ if(!strncasecmp(c->argv[i], "size", 4)) {
+ rc = limits_parse_one(c->argv[i], lim);
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ } else {
+ if(!strcasecmp(c->argv[i], "unlimited")) {
+ lim->lms_s_soft = -1;
+ } else {
+ if ( lutil_atoix( &lim->lms_s_soft, c->argv[i], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse limit", c->argv[0]);
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ }
+ lim->lms_s_hard = 0;
+ }
+ }
+
+ok:
+ if ( ( c->be == frontendDB ) && ( c->ca_entry ) ) {
+ /* This is a modification to the global limits apply it to
+ * the other databases as needed */
+ AttributeDescription *ad=NULL;
+ const char *text = NULL;
+ CfEntryInfo *ce = c->ca_entry->e_private;
+
+ slap_str2ad(c->argv[0], &ad, &text);
+ /* if we got here... */
+ assert( ad != NULL );
+
+ if ( ce->ce_type == Cft_Global ){
+ ce = ce->ce_kids;
+ }
+ for (; ce; ce=ce->ce_sibs) {
+ Entry *dbe = ce->ce_entry;
+ if ( (ce->ce_type == Cft_Database) && (ce->ce_be != frontendDB)
+ && (!attr_find(dbe->e_attrs, ad)) ) {
+ ce->ce_be->be_def_limit.lms_s_soft = lim->lms_s_soft;
+ ce->ce_be->be_def_limit.lms_s_hard = lim->lms_s_hard;
+ ce->ce_be->be_def_limit.lms_s_unchecked =lim->lms_s_unchecked;
+ ce->ce_be->be_def_limit.lms_s_pr =lim->lms_s_pr;
+ ce->ce_be->be_def_limit.lms_s_pr_hide =lim->lms_s_pr_hide;
+ ce->ce_be->be_def_limit.lms_s_pr_total =lim->lms_s_pr_total;
+ }
+ }
+ }
+ return(0);
+}
+
+static int
+config_timelimit(ConfigArgs *c) {
+ int i, rc = 0;
+ struct slap_limits_set *lim = &c->be->be_def_limit;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ char buf[8192];
+ struct berval bv;
+ bv.bv_val = buf;
+ bv.bv_len = 0;
+ limits_unparse_one( lim, SLAP_LIMIT_TIME, &bv, sizeof( buf ) );
+ if ( !BER_BVISEMPTY( &bv ))
+ value_add_one( &c->rvalue_vals, &bv );
+ else
+ rc = 1;
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ /* Reset to defaults or values from frontend */
+ if ( c->be == frontendDB ) {
+ lim->lms_t_soft = SLAPD_DEFAULT_TIMELIMIT;
+ lim->lms_t_hard = 0;
+ } else {
+ lim->lms_t_soft = frontendDB->be_def_limit.lms_t_soft;
+ lim->lms_t_hard = frontendDB->be_def_limit.lms_t_hard;
+ }
+ goto ok;
+ }
+ for(i = 1; i < c->argc; i++) {
+ if(!strncasecmp(c->argv[i], "time", 4)) {
+ rc = limits_parse_one(c->argv[i], lim);
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse value", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ } else {
+ if(!strcasecmp(c->argv[i], "unlimited")) {
+ lim->lms_t_soft = -1;
+ } else {
+ if ( lutil_atoix( &lim->lms_t_soft, c->argv[i], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse limit", c->argv[0]);
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ }
+ lim->lms_t_hard = 0;
+ }
+ }
+
+ok:
+ if ( ( c->be == frontendDB ) && ( c->ca_entry ) ) {
+ /* This is a modification to the global limits apply it to
+ * the other databases as needed */
+ AttributeDescription *ad=NULL;
+ const char *text = NULL;
+ CfEntryInfo *ce = c->ca_entry->e_private;
+
+ slap_str2ad(c->argv[0], &ad, &text);
+ /* if we got here... */
+ assert( ad != NULL );
+
+ if ( ce->ce_type == Cft_Global ){
+ ce = ce->ce_kids;
+ }
+ for (; ce; ce=ce->ce_sibs) {
+ Entry *dbe = ce->ce_entry;
+ if ( (ce->ce_type == Cft_Database) && (ce->ce_be != frontendDB)
+ && (!attr_find(dbe->e_attrs, ad)) ) {
+ ce->ce_be->be_def_limit.lms_t_soft = lim->lms_t_soft;
+ ce->ce_be->be_def_limit.lms_t_hard = lim->lms_t_hard;
+ }
+ }
+ }
+ return(0);
+}
+
+static int
+config_overlay(ConfigArgs *c) {
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ assert(0);
+ }
+ if(c->argv[1][0] == '-' && overlay_config(c->be, &c->argv[1][1],
+ c->valx, &c->bi, &c->reply)) {
+ /* log error */
+ Debug( LDAP_DEBUG_ANY,
+ "%s: (optional) %s overlay \"%s\" configuration failed.\n",
+ c->log, c->be == frontendDB ? "global " : "", &c->argv[1][1]);
+ return 1;
+ } else if(overlay_config(c->be, c->argv[1], c->valx, &c->bi, &c->reply)) {
+ return(1);
+ }
+ return(0);
+}
+
+static int
+config_subordinate(ConfigArgs *c)
+{
+ int rc = 1;
+ int advertise = 0;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( SLAP_GLUE_SUBORDINATE( c->be )) {
+ struct berval bv;
+
+ bv.bv_val = SLAP_GLUE_ADVERTISE( c->be ) ? "advertise" : "TRUE";
+ bv.bv_len = SLAP_GLUE_ADVERTISE( c->be ) ? STRLENOF("advertise") :
+ STRLENOF("TRUE");
+
+ value_add_one( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( !c->line || strcasecmp( c->line, "advertise" )) {
+ glue_sub_del( c->be );
+ } else {
+ SLAP_DBFLAGS( c->be ) &= ~SLAP_DBFLAG_GLUE_ADVERTISE;
+ }
+ rc = 0;
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( c->be->be_nsuffix == NULL ) {
+ /* log error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "subordinate configuration needs a suffix" );
+ Debug( LDAP_DEBUG_ANY,
+ "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ break;
+ }
+
+ if ( c->argc == 2 ) {
+ if ( strcasecmp( c->argv[1], "advertise" ) == 0 ) {
+ advertise = 1;
+
+ } else if ( strcasecmp( c->argv[1], "TRUE" ) != 0 ) {
+ /* log error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "subordinate must be \"TRUE\" or \"advertise\"" );
+ Debug( LDAP_DEBUG_ANY,
+ "%s: suffix \"%s\": %s.\n",
+ c->log, c->be->be_suffix[0].bv_val, c->cr_msg );
+ rc = 1;
+ break;
+ }
+ }
+
+ rc = glue_sub_add( c->be, advertise, CONFIG_ONLINE_ADD( c ));
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * [listener=<listener>] [{read|write}=]<size>
+ */
+
+#ifdef LDAP_TCP_BUFFER
+static BerVarray tcp_buffer;
+static int tcp_buffer_num;
+
+#define SLAP_TCP_RMEM (0x1U)
+#define SLAP_TCP_WMEM (0x2U)
+
+static int
+tcp_buffer_parse( struct berval *val, int argc, char **argv,
+ int *size, int *rw, Listener **l )
+{
+ int i, rc = LDAP_SUCCESS;
+ LDAPURLDesc *lud = NULL;
+ char *ptr;
+
+ if ( val != NULL && argv == NULL ) {
+ char *s = val->bv_val;
+
+ argv = ldap_str2charray( s, " \t" );
+ if ( argv == NULL ) {
+ return LDAP_OTHER;
+ }
+ }
+
+ i = 0;
+ if ( strncasecmp( argv[ i ], "listener=", STRLENOF( "listener=" ) )
+ == 0 )
+ {
+ char *url = argv[ i ] + STRLENOF( "listener=" );
+
+ if ( ldap_url_parse( url, &lud ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ *l = config_check_my_url( url, lud );
+ if ( *l == NULL ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ i++;
+ }
+
+ ptr = argv[ i ];
+ if ( strncasecmp( ptr, "read=", STRLENOF( "read=" ) ) == 0 ) {
+ *rw |= SLAP_TCP_RMEM;
+ ptr += STRLENOF( "read=" );
+
+ } else if ( strncasecmp( ptr, "write=", STRLENOF( "write=" ) ) == 0 ) {
+ *rw |= SLAP_TCP_WMEM;
+ ptr += STRLENOF( "write=" );
+
+ } else {
+ *rw |= ( SLAP_TCP_RMEM | SLAP_TCP_WMEM );
+ }
+
+ /* accept any base */
+ if ( lutil_atoix( size, ptr, 0 ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+done:;
+ if ( val != NULL && argv != NULL ) {
+ ldap_charray_free( argv );
+ }
+
+ if ( lud != NULL ) {
+ ldap_free_urldesc( lud );
+ }
+
+ return rc;
+}
+
+static int
+tcp_buffer_delete_one( struct berval *val )
+{
+ int rc = 0;
+ int size = -1, rw = 0;
+ Listener *l = NULL;
+
+ rc = tcp_buffer_parse( val, 0, NULL, &size, &rw, &l );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ if ( l != NULL ) {
+ int i;
+ Listener **ll = slapd_get_listeners();
+
+ for ( i = 0; ll[ i ] != NULL; i++ ) {
+ if ( ll[ i ] == l ) break;
+ }
+
+ if ( ll[ i ] == NULL ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = -1;
+
+ for ( i++ ; ll[ i ] != NULL && bvmatch( &l->sl_url, &ll[ i ]->sl_url ); i++ ) {
+ if ( rw & SLAP_TCP_RMEM ) ll[ i ]->sl_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) ll[ i ]->sl_tcp_wmem = -1;
+ }
+
+ } else {
+ /* NOTE: this affects listeners without a specific setting,
+ * does not reset all listeners. If a listener without
+ * specific settings was assigned a buffer because of
+ * a global setting, it will not be reset. In any case,
+ * buffer changes will only take place at restart. */
+ if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = -1;
+ if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = -1;
+ }
+
+ return rc;
+}
+
+static int
+tcp_buffer_delete( BerVarray vals )
+{
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ tcp_buffer_delete_one( &vals[ i ] );
+ }
+
+ return 0;
+}
+
+static int
+tcp_buffer_unparse( int size, int rw, Listener *l, struct berval *val )
+{
+ char buf[sizeof("2147483648")], *ptr;
+
+ /* unparse for later use */
+ val->bv_len = snprintf( buf, sizeof( buf ), "%d", size );
+ if ( l != NULL ) {
+ val->bv_len += STRLENOF( "listener=" " " ) + l->sl_url.bv_len;
+ }
+
+ if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) {
+ if ( rw & SLAP_TCP_RMEM ) {
+ val->bv_len += STRLENOF( "read=" );
+ } else if ( rw & SLAP_TCP_WMEM ) {
+ val->bv_len += STRLENOF( "write=" );
+ }
+ }
+
+ val->bv_val = ch_malloc( val->bv_len + 1 );
+
+ ptr = val->bv_val;
+
+ if ( l != NULL ) {
+ ptr = lutil_strcopy( ptr, "listener=" );
+ ptr = lutil_strncopy( ptr, l->sl_url.bv_val, l->sl_url.bv_len );
+ *ptr++ = ' ';
+ }
+
+ if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) {
+ if ( rw & SLAP_TCP_RMEM ) {
+ ptr = lutil_strcopy( ptr, "read=" );
+ } else if ( rw & SLAP_TCP_WMEM ) {
+ ptr = lutil_strcopy( ptr, "write=" );
+ }
+ }
+
+ ptr = lutil_strcopy( ptr, buf );
+ *ptr = '\0';
+
+ assert( val->bv_val + val->bv_len == ptr );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+tcp_buffer_add_one( int argc, char **argv )
+{
+ int rc = 0;
+ int size = -1, rw = 0;
+ Listener *l = NULL;
+
+ struct berval val;
+
+ /* parse */
+ rc = tcp_buffer_parse( NULL, argc, argv, &size, &rw, &l );
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ /* unparse for later use */
+ rc = tcp_buffer_unparse( size, rw, l, &val );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ /* use parsed values */
+ if ( l != NULL ) {
+ int i;
+ Listener **ll = slapd_get_listeners();
+
+ for ( i = 0; ll[ i ] != NULL; i++ ) {
+ if ( ll[ i ] == l ) break;
+ }
+
+ if ( ll[ i ] == NULL ) {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ /* buffer only applies to TCP listeners;
+ * we do not do any check here, and delegate them
+ * to setsockopt(2) */
+ if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = size;
+
+ for ( i++ ; ll[ i ] != NULL && bvmatch( &l->sl_url, &ll[ i ]->sl_url ); i++ ) {
+ if ( rw & SLAP_TCP_RMEM ) ll[ i ]->sl_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) ll[ i ]->sl_tcp_wmem = size;
+ }
+
+ } else {
+ /* NOTE: this affects listeners without a specific setting,
+ * does not set all listeners */
+ if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = size;
+ if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = size;
+ }
+
+ tcp_buffer = ch_realloc( tcp_buffer, sizeof( struct berval ) * ( tcp_buffer_num + 2 ) );
+ /* append */
+ tcp_buffer[ tcp_buffer_num ] = val;
+
+ tcp_buffer_num++;
+ BER_BVZERO( &tcp_buffer[ tcp_buffer_num ] );
+
+ return rc;
+}
+
+static int
+config_tcp_buffer( ConfigArgs *c )
+{
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( tcp_buffer == NULL || BER_BVISNULL( &tcp_buffer[ 0 ] ) ) {
+ return 1;
+ }
+ value_add( &c->rvalue_vals, tcp_buffer );
+ value_add( &c->rvalue_nvals, tcp_buffer );
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ tcp_buffer_delete( tcp_buffer );
+ ber_bvarray_free( tcp_buffer );
+ tcp_buffer = NULL;
+ tcp_buffer_num = 0;
+
+ } else {
+ int rc = 0;
+ int size = -1, rw = 0;
+ Listener *l = NULL;
+
+ struct berval val = BER_BVNULL;
+
+ int i;
+
+ if ( tcp_buffer_num == 0 ) {
+ return 1;
+ }
+
+ /* parse */
+ rc = tcp_buffer_parse( NULL, c->argc - 1, &c->argv[ 1 ], &size, &rw, &l );
+ if ( rc != 0 ) {
+ return 1;
+ }
+
+ /* unparse for later use */
+ rc = tcp_buffer_unparse( size, rw, l, &val );
+ if ( rc != LDAP_SUCCESS ) {
+ return 1;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &tcp_buffer[ i ] ); i++ ) {
+ if ( bvmatch( &tcp_buffer[ i ], &val ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &tcp_buffer[ i ] ) ) {
+ /* not found */
+ rc = 1;
+ goto done;
+ }
+
+ tcp_buffer_delete_one( &tcp_buffer[ i ] );
+ ber_memfree( tcp_buffer[ i ].bv_val );
+ for ( ; i < tcp_buffer_num; i++ ) {
+ tcp_buffer[ i ] = tcp_buffer[ i + 1 ];
+ }
+ tcp_buffer_num--;
+
+done:;
+ if ( !BER_BVISNULL( &val ) ) {
+ SLAP_FREE( val.bv_val );
+ }
+
+ }
+
+ } else {
+ int rc;
+
+ rc = tcp_buffer_add_one( c->argc - 1, &c->argv[ 1 ] );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to add value #%d",
+ c->argv[0], tcp_buffer_num );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+#endif /* LDAP_TCP_BUFFER */
+
+static int
+config_suffix(ConfigArgs *c)
+{
+ Backend *tbe;
+ struct berval pdn, ndn;
+ char *notallowed = NULL;
+
+ if ( c->be == frontendDB ) {
+ notallowed = "frontend";
+
+ } else if ( SLAP_MONITOR(c->be) ) {
+ notallowed = "monitor";
+
+ } else if ( SLAP_CONFIG(c->be) ) {
+ notallowed = "config";
+ }
+
+ if ( notallowed != NULL ) {
+ char buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ switch ( c->op ) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ case LDAP_MOD_INCREMENT:
+ case SLAP_CONFIG_ADD:
+ if ( !BER_BVISNULL( &c->value_dn ) ) {
+ snprintf( buf, sizeof( buf ), "<%s> ",
+ c->value_dn.bv_val );
+ }
+
+ Debug(LDAP_DEBUG_ANY,
+ "%s: suffix %snot allowed in %s database.\n",
+ c->log, buf, notallowed );
+ break;
+
+ case SLAP_CONFIG_EMIT:
+ /* don't complain when emitting... */
+ break;
+
+ default:
+ /* FIXME: don't know what values may be valid;
+ * please remove assertion, or add legal values
+ * to either block */
+ assert( 0 );
+ break;
+ }
+
+ return 1;
+ }
+
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if ( c->be->be_suffix == NULL
+ || BER_BVISNULL( &c->be->be_suffix[0] ) )
+ {
+ return 1;
+ } else {
+ value_add( &c->rvalue_vals, c->be->be_suffix );
+ value_add( &c->rvalue_nvals, c->be->be_nsuffix );
+ return 0;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ ber_bvarray_free( c->be->be_suffix );
+ ber_bvarray_free( c->be->be_nsuffix );
+ c->be->be_suffix = NULL;
+ c->be->be_nsuffix = NULL;
+ } else {
+ int i = c->valx;
+ ch_free( c->be->be_suffix[i].bv_val );
+ ch_free( c->be->be_nsuffix[i].bv_val );
+ do {
+ c->be->be_suffix[i] = c->be->be_suffix[i+1];
+ c->be->be_nsuffix[i] = c->be->be_nsuffix[i+1];
+ i++;
+ } while ( !BER_BVISNULL( &c->be->be_suffix[i] ) );
+ }
+ return 0;
+ }
+
+#ifdef SLAPD_MONITOR_DN
+ if(!strcasecmp(c->argv[1], SLAPD_MONITOR_DN)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> DN is reserved for monitoring slapd",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n",
+ c->log, c->cr_msg, SLAPD_MONITOR_DN);
+ return(1);
+ }
+#endif
+
+ if (SLAP_DB_ONE_SUFFIX( c->be ) && c->be->be_suffix &&
+ !BER_BVISNULL( &c->be->be_suffix[0] )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> Only one suffix is allowed on this %s backend",
+ c->argv[0], c->be->bd_info->bi_type );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+
+ pdn = c->value_dn;
+ ndn = c->value_ndn;
+
+ if (SLAP_DBHIDDEN( c->be ))
+ tbe = NULL;
+ else
+ tbe = select_backend(&ndn, 0);
+ if(tbe == c->be) {
+ Debug( LDAP_DEBUG_ANY, "%s: suffix already served by this backend!.\n",
+ c->log );
+ free(pdn.bv_val);
+ free(ndn.bv_val);
+ return 1;
+ } else if(tbe) {
+ BackendDB *b2 = tbe;
+
+ /* Does tbe precede be? */
+ while (( b2 = LDAP_STAILQ_NEXT(b2, be_next )) && b2 && b2 != c->be );
+
+ if ( b2 ) {
+ char *type = tbe->bd_info->bi_type;
+
+ if ( overlay_is_over( tbe ) ) {
+ slap_overinfo *oi = (slap_overinfo *)tbe->bd_info->bi_private;
+ type = oi->oi_orig->bi_type;
+ }
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> namingContext \"%s\" "
+ "already served by a preceding %s database",
+ c->argv[0], pdn.bv_val, type );
+ Debug(LDAP_DEBUG_ANY, "%s: %s serving namingContext \"%s\"\n",
+ c->log, c->cr_msg, tbe->be_suffix[0].bv_val);
+ free(pdn.bv_val);
+ free(ndn.bv_val);
+ return(1);
+ }
+ }
+ if(pdn.bv_len == 0 && default_search_nbase.bv_len) {
+ Debug(LDAP_DEBUG_ANY, "%s: suffix DN empty and default search "
+ "base provided \"%s\" (assuming okay)\n",
+ c->log, default_search_base.bv_val );
+ }
+ ber_bvarray_add(&c->be->be_suffix, &pdn);
+ ber_bvarray_add(&c->be->be_nsuffix, &ndn);
+ return(0);
+}
+
+static int
+config_rootdn(ConfigArgs *c) {
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if ( !BER_BVISNULL( &c->be->be_rootdn )) {
+ value_add_one(&c->rvalue_vals, &c->be->be_rootdn);
+ value_add_one(&c->rvalue_nvals, &c->be->be_rootndn);
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ ch_free( c->be->be_rootdn.bv_val );
+ ch_free( c->be->be_rootndn.bv_val );
+ BER_BVZERO( &c->be->be_rootdn );
+ BER_BVZERO( &c->be->be_rootndn );
+ return 0;
+ }
+ if ( !BER_BVISNULL( &c->be->be_rootdn )) {
+ ch_free( c->be->be_rootdn.bv_val );
+ ch_free( c->be->be_rootndn.bv_val );
+ }
+ c->be->be_rootdn = c->value_dn;
+ c->be->be_rootndn = c->value_ndn;
+ return(0);
+}
+
+static int
+config_rootpw(ConfigArgs *c) {
+ Backend *tbe;
+
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if (!BER_BVISEMPTY(&c->be->be_rootpw)) {
+ /* don't copy, because "rootpw" is marked
+ * as CFG_BERVAL */
+ c->value_bv = c->be->be_rootpw;
+ return 0;
+ }
+ return 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ ch_free( c->be->be_rootpw.bv_val );
+ BER_BVZERO( &c->be->be_rootpw );
+ return 0;
+ }
+
+ tbe = select_backend(&c->be->be_rootndn, 0);
+ if(tbe != c->be && !SLAP_DBHIDDEN( c->be )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> can only be set when rootdn is under suffix",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+ if ( !BER_BVISNULL( &c->be->be_rootpw ))
+ ch_free( c->be->be_rootpw.bv_val );
+ c->be->be_rootpw = c->value_bv;
+ return(0);
+}
+
+static int
+config_restrict(ConfigArgs *c) {
+ slap_mask_t restrictops = 0;
+ int i;
+ slap_verbmasks restrictable_ops[] = {
+ { BER_BVC("bind"), SLAP_RESTRICT_OP_BIND },
+ { BER_BVC("add"), SLAP_RESTRICT_OP_ADD },
+ { BER_BVC("modify"), SLAP_RESTRICT_OP_MODIFY },
+ { BER_BVC("rename"), SLAP_RESTRICT_OP_RENAME },
+ { BER_BVC("modrdn"), 0 },
+ { BER_BVC("delete"), SLAP_RESTRICT_OP_DELETE },
+ { BER_BVC("search"), SLAP_RESTRICT_OP_SEARCH },
+ { BER_BVC("compare"), SLAP_RESTRICT_OP_COMPARE },
+ { BER_BVC("read"), SLAP_RESTRICT_OP_READS },
+ { BER_BVC("write"), SLAP_RESTRICT_OP_WRITES },
+ { BER_BVC("extended"), SLAP_RESTRICT_OP_EXTENDED },
+ { BER_BVC("extended=" LDAP_EXOP_START_TLS ), SLAP_RESTRICT_EXOP_START_TLS },
+ { BER_BVC("extended=" LDAP_EXOP_MODIFY_PASSWD ), SLAP_RESTRICT_EXOP_MODIFY_PASSWD },
+ { BER_BVC("extended=" LDAP_EXOP_X_WHO_AM_I ), SLAP_RESTRICT_EXOP_WHOAMI },
+ { BER_BVC("extended=" LDAP_EXOP_X_CANCEL ), SLAP_RESTRICT_EXOP_CANCEL },
+ { BER_BVC("all"), SLAP_RESTRICT_OP_ALL },
+ { BER_BVNULL, 0 }
+ };
+
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return mask_to_verbs( restrictable_ops, c->be->be_restrictops,
+ &c->rvalue_vals );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ c->be->be_restrictops = 0;
+ } else {
+ i = verb_to_mask( c->line, restrictable_ops );
+ c->be->be_restrictops &= ~restrictable_ops[i].mask;
+ }
+ return 0;
+ }
+ i = verbs_to_mask( c->argc, c->argv, restrictable_ops, &restrictops );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown operation", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ if ( restrictops & SLAP_RESTRICT_OP_EXTENDED )
+ restrictops &= ~SLAP_RESTRICT_EXOP_MASK;
+ c->be->be_restrictops |= restrictops;
+ return(0);
+}
+
+static int
+config_allows(ConfigArgs *c) {
+ slap_mask_t allows = 0;
+ int i;
+ slap_verbmasks allowable_ops[] = {
+ { BER_BVC("bind_v2"), SLAP_ALLOW_BIND_V2 },
+ { BER_BVC("bind_anon_cred"), SLAP_ALLOW_BIND_ANON_CRED },
+ { BER_BVC("bind_anon_dn"), SLAP_ALLOW_BIND_ANON_DN },
+ { BER_BVC("update_anon"), SLAP_ALLOW_UPDATE_ANON },
+ { BER_BVC("proxy_authz_anon"), SLAP_ALLOW_PROXY_AUTHZ_ANON },
+ { BER_BVNULL, 0 }
+ };
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return mask_to_verbs( allowable_ops, global_allows, &c->rvalue_vals );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ global_allows = 0;
+ } else {
+ i = verb_to_mask( c->line, allowable_ops );
+ global_allows &= ~allowable_ops[i].mask;
+ }
+ return 0;
+ }
+ i = verbs_to_mask(c->argc, c->argv, allowable_ops, &allows);
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ global_allows |= allows;
+ return(0);
+}
+
+static int
+config_disallows(ConfigArgs *c) {
+ slap_mask_t disallows = 0;
+ int i;
+ slap_verbmasks disallowable_ops[] = {
+ { BER_BVC("bind_anon"), SLAP_DISALLOW_BIND_ANON },
+ { BER_BVC("bind_simple"), SLAP_DISALLOW_BIND_SIMPLE },
+ { BER_BVC("tls_2_anon"), SLAP_DISALLOW_TLS_2_ANON },
+ { BER_BVC("tls_authc"), SLAP_DISALLOW_TLS_AUTHC },
+ { BER_BVC("proxy_authz_non_critical"), SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT },
+ { BER_BVC("dontusecopy_non_critical"), SLAP_DISALLOW_DONTUSECOPY_N_CRIT },
+ { BER_BVNULL, 0 }
+ };
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return mask_to_verbs( disallowable_ops, global_disallows, &c->rvalue_vals );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ global_disallows = 0;
+ } else {
+ i = verb_to_mask( c->line, disallowable_ops );
+ global_disallows &= ~disallowable_ops[i].mask;
+ }
+ return 0;
+ }
+ i = verbs_to_mask(c->argc, c->argv, disallowable_ops, &disallows);
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ global_disallows |= disallows;
+ return(0);
+}
+
+static int
+config_requires(ConfigArgs *c) {
+ slap_mask_t requires = frontendDB->be_requires;
+ int i, argc = c->argc;
+ char **argv = c->argv;
+
+ slap_verbmasks requires_ops[] = {
+ { BER_BVC("bind"), SLAP_REQUIRE_BIND },
+ { BER_BVC("LDAPv3"), SLAP_REQUIRE_LDAP_V3 },
+ { BER_BVC("authc"), SLAP_REQUIRE_AUTHC },
+ { BER_BVC("sasl"), SLAP_REQUIRE_SASL },
+ { BER_BVC("strong"), SLAP_REQUIRE_STRONG },
+ { BER_BVNULL, 0 }
+ };
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return mask_to_verbs( requires_ops, c->be->be_requires, &c->rvalue_vals );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( !c->line ) {
+ c->be->be_requires = 0;
+ } else {
+ i = verb_to_mask( c->line, requires_ops );
+ c->be->be_requires &= ~requires_ops[i].mask;
+ }
+ return 0;
+ }
+ /* "none" can only be first, to wipe out default/global values */
+ if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) {
+ argv++;
+ argc--;
+ requires = 0;
+ }
+ i = verbs_to_mask(argc, argv, requires_ops, &requires);
+ if ( i ) {
+ if (strcasecmp( c->argv[ i ], "none" ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> \"none\" (#%d) must be listed first", c->argv[0], i - 1 );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown feature #%d", c->argv[0], i - 1 );
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ }
+ return(1);
+ }
+ c->be->be_requires = requires;
+ return(0);
+}
+
+static int
+config_extra_attrs(ConfigArgs *c)
+{
+ assert( c->be != NULL );
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ int i;
+
+ if ( c->be->be_extra_anlist == NULL ) {
+ return 1;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ); i++ ) {
+ value_add_one( &c->rvalue_vals, &c->be->be_extra_anlist[i].an_name );
+ }
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->be->be_extra_anlist == NULL ) {
+ return 1;
+ }
+
+ if ( c->valx < 0 ) {
+ anlist_free( c->be->be_extra_anlist, 1, NULL );
+ c->be->be_extra_anlist = NULL;
+
+ } else {
+ int i;
+
+ for ( i = 0; i < c->valx && !BER_BVISNULL( &c->be->be_extra_anlist[i + 1].an_name ); i++ )
+ ;
+
+ if ( BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ) ) {
+ return 1;
+ }
+
+ ch_free( c->be->be_extra_anlist[i].an_name.bv_val );
+
+ for ( ; !BER_BVISNULL( &c->be->be_extra_anlist[i].an_name ); i++ ) {
+ c->be->be_extra_anlist[i] = c->be->be_extra_anlist[i + 1];
+ }
+ }
+
+ } else {
+ c->be->be_extra_anlist = str2anlist( c->be->be_extra_anlist, c->argv[1], " ,\t" );
+ if ( c->be->be_extra_anlist == NULL ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+config_referral(ConfigArgs *c) {
+ struct berval val;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if ( default_referral ) {
+ value_add( &c->rvalue_vals, default_referral );
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ ber_bvarray_free( default_referral );
+ default_referral = NULL;
+ } else {
+ int i = c->valx;
+ ch_free( default_referral[i].bv_val );
+ for (; default_referral[i].bv_val; i++ )
+ default_referral[i] = default_referral[i+1];
+ }
+ return 0;
+ }
+ if(validate_global_referral(c->argv[1])) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid URL", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n",
+ c->log, c->cr_msg, c->argv[1]);
+ return(1);
+ }
+
+ ber_str2bv(c->argv[1], 0, 0, &val);
+ if(value_add_one(&default_referral, &val)) return(LDAP_OTHER);
+ return(0);
+}
+
+static struct {
+ struct berval key;
+ int off;
+} sec_keys[] = {
+ { BER_BVC("ssf="), offsetof(slap_ssf_set_t, sss_ssf) },
+ { BER_BVC("transport="), offsetof(slap_ssf_set_t, sss_transport) },
+ { BER_BVC("tls="), offsetof(slap_ssf_set_t, sss_tls) },
+ { BER_BVC("sasl="), offsetof(slap_ssf_set_t, sss_sasl) },
+ { BER_BVC("update_ssf="), offsetof(slap_ssf_set_t, sss_update_ssf) },
+ { BER_BVC("update_transport="), offsetof(slap_ssf_set_t, sss_update_transport) },
+ { BER_BVC("update_tls="), offsetof(slap_ssf_set_t, sss_update_tls) },
+ { BER_BVC("update_sasl="), offsetof(slap_ssf_set_t, sss_update_sasl) },
+ { BER_BVC("simple_bind="), offsetof(slap_ssf_set_t, sss_simple_bind) },
+ { BER_BVNULL, 0 }
+};
+
+static int
+config_security(ConfigArgs *c) {
+ slap_ssf_set_t *set = &c->be->be_ssf_set;
+ char *next;
+ int i, j;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ char numbuf[32];
+ struct berval bv;
+ slap_ssf_t *tgt;
+ int rc = 1;
+
+ for (i=0; !BER_BVISNULL( &sec_keys[i].key ); i++) {
+ tgt = (slap_ssf_t *)((char *)set + sec_keys[i].off);
+ if ( *tgt ) {
+ rc = 0;
+ bv.bv_len = snprintf( numbuf, sizeof( numbuf ), "%u", *tgt );
+ if ( bv.bv_len >= sizeof( numbuf ) ) {
+ ber_bvarray_free_x( c->rvalue_vals, NULL );
+ c->rvalue_vals = NULL;
+ rc = 1;
+ break;
+ }
+ bv.bv_len += sec_keys[i].key.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1);
+ next = lutil_strcopy( bv.bv_val, sec_keys[i].key.bv_val );
+ strcpy( next, numbuf );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ }
+ return rc;
+ }
+ for(i = 1; i < c->argc; i++) {
+ slap_ssf_t *tgt = NULL;
+ char *src = NULL;
+ for ( j=0; !BER_BVISNULL( &sec_keys[j].key ); j++ ) {
+ if(!strncasecmp(c->argv[i], sec_keys[j].key.bv_val,
+ sec_keys[j].key.bv_len)) {
+ src = c->argv[i] + sec_keys[j].key.bv_len;
+ tgt = (slap_ssf_t *)((char *)set + sec_keys[j].off);
+ break;
+ }
+ }
+ if ( !tgt ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown factor", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+
+ if ( lutil_atou( tgt, src ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse factor", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+char *
+anlist_unparse( AttributeName *an, char *ptr, ber_len_t buflen ) {
+ int comma = 0;
+ char *start = ptr;
+
+ for (; !BER_BVISNULL( &an->an_name ); an++) {
+ /* if buflen == 0, assume the buffer size has been
+ * already checked otherwise */
+ if ( buflen > 0 && buflen - ( ptr - start ) < comma + an->an_name.bv_len ) return NULL;
+ if ( comma ) *ptr++ = ',';
+ ptr = lutil_strcopy( ptr, an->an_name.bv_val );
+ comma = 1;
+ }
+ return ptr;
+}
+
+int
+slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out )
+{
+ int i;
+ BerVarray bva = NULL;
+ char ibuf[32], *ptr;
+ struct berval idx;
+
+ assert( in != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ )
+ /* count'em */ ;
+
+ if ( i == 0 ) {
+ return 1;
+ }
+
+ idx.bv_val = ibuf;
+
+ bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) );
+ BER_BVZERO( &bva[ 0 ] );
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+ idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i );
+ if ( idx.bv_len >= sizeof( ibuf ) ) {
+ ber_bvarray_free( bva );
+ return 1;
+ }
+
+ bva[i].bv_len = idx.bv_len + in[i].bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ ptr = lutil_strcopy( bva[i].bv_val, ibuf );
+ ptr = lutil_strcopy( ptr, in[i].bv_val );
+ *ptr = '\0';
+ BER_BVZERO( &bva[ i + 1 ] );
+ }
+
+ *out = bva;
+ return 0;
+}
+
+static int
+config_updatedn(ConfigArgs *c) {
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if (!BER_BVISEMPTY(&c->be->be_update_ndn)) {
+ value_add_one(&c->rvalue_vals, &c->be->be_update_ndn);
+ value_add_one(&c->rvalue_nvals, &c->be->be_update_ndn);
+ return 0;
+ }
+ return 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ ch_free( c->be->be_update_ndn.bv_val );
+ BER_BVZERO( &c->be->be_update_ndn );
+ SLAP_DBFLAGS(c->be) ^= (SLAP_DBFLAG_SHADOW | SLAP_DBFLAG_SLURP_SHADOW);
+ return 0;
+ }
+ if(SLAP_SHADOW(c->be)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> database already shadowed", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+
+ ber_memfree_x( c->value_dn.bv_val, NULL );
+ if ( !BER_BVISNULL( &c->be->be_update_ndn ) ) {
+ ber_memfree_x( c->be->be_update_ndn.bv_val, NULL );
+ }
+ c->be->be_update_ndn = c->value_ndn;
+ BER_BVZERO( &c->value_dn );
+ BER_BVZERO( &c->value_ndn );
+
+ return config_slurp_shadow( c );
+}
+
+int
+config_shadow( ConfigArgs *c, slap_mask_t flag )
+{
+ char *notallowed = NULL;
+
+ if ( c->be == frontendDB ) {
+ notallowed = "frontend";
+
+ } else if ( SLAP_MONITOR(c->be) ) {
+ notallowed = "monitor";
+ }
+
+ if ( notallowed != NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s database cannot be shadow.\n", c->log, notallowed );
+ return 1;
+ }
+
+ if ( SLAP_SHADOW(c->be) ) {
+ /* if already shadow, only check consistency */
+ if ( ( SLAP_DBFLAGS(c->be) & flag ) != flag ) {
+ Debug( LDAP_DEBUG_ANY, "%s: inconsistent shadow flag 0x%lx.\n",
+ c->log, flag );
+ return 1;
+ }
+
+ } else {
+ SLAP_DBFLAGS(c->be) |= (SLAP_DBFLAG_SHADOW | flag);
+ if ( !SLAP_MULTIPROVIDER( c->be ))
+ SLAP_DBFLAGS(c->be) |= SLAP_DBFLAG_SINGLE_SHADOW;
+ }
+
+ return 0;
+}
+
+static int
+config_updateref(ConfigArgs *c) {
+ struct berval val;
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if ( c->be->be_update_refs ) {
+ value_add( &c->rvalue_vals, c->be->be_update_refs );
+ return 0;
+ } else {
+ return 1;
+ }
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ ber_bvarray_free( c->be->be_update_refs );
+ c->be->be_update_refs = NULL;
+ } else {
+ int i = c->valx;
+ ch_free( c->be->be_update_refs[i].bv_val );
+ for (; c->be->be_update_refs[i].bv_val; i++)
+ c->be->be_update_refs[i] = c->be->be_update_refs[i+1];
+ }
+ return 0;
+ }
+ if(!SLAP_SHADOW(c->be) && !c->be->be_syncinfo) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must appear after syncrepl or updatedn",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(1);
+ }
+
+ if(validate_global_referral(c->argv[1])) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid URL", c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)\n",
+ c->log, c->cr_msg, c->argv[1]);
+ return(1);
+ }
+ ber_str2bv(c->argv[1], 0, 0, &val);
+ if(value_add_one(&c->be->be_update_refs, &val)) return(LDAP_OTHER);
+ return(0);
+}
+
+static int
+config_obsolete(ConfigArgs *c) {
+ if (c->op == SLAP_CONFIG_EMIT)
+ return 1;
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> keyword is obsolete (ignored)",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return(0);
+}
+
+static int
+config_include(ConfigArgs *c) {
+ int savelineno = c->lineno;
+ int rc;
+ ConfigFile *cf;
+ ConfigFile *cfsave = cfn;
+ ConfigFile *cf2 = NULL;
+
+ /* Leftover from RE23. No dynamic config for include files */
+ if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE )
+ return 1;
+
+ cf = ch_calloc( 1, sizeof(ConfigFile));
+ if ( cfn->c_kids ) {
+ for (cf2=cfn->c_kids; cf2 && cf2->c_sibs; cf2=cf2->c_sibs) ;
+ cf2->c_sibs = cf;
+ } else {
+ cfn->c_kids = cf;
+ }
+ cfn = cf;
+ ber_str2bv( c->argv[1], 0, 1, &cf->c_file );
+ rc = read_config_file(c->argv[1], c->depth + 1, c, config_back_cf_table);
+ c->lineno = savelineno - 1;
+ cfn = cfsave;
+ if ( rc ) {
+ if ( cf2 ) cf2->c_sibs = NULL;
+ else cfn->c_kids = NULL;
+ ch_free( cf->c_file.bv_val );
+ ch_free( cf );
+ } else {
+ c->ca_private = cf;
+ }
+ return(rc);
+}
+
+#ifdef HAVE_TLS
+static int
+config_tls_cleanup(ConfigArgs *c) {
+ int rc = 0;
+
+ if ( slap_tls_ld ) {
+ int opt = 1;
+
+ ldap_pvt_tls_ctx_free( slap_tls_ctx );
+ slap_tls_ctx = NULL;
+
+ /* Force new ctx to be created */
+ rc = ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if( rc == 0 ) {
+ /* The ctx's refcount is bumped up here */
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CTX, &slap_tls_ctx );
+ /* This is a no-op if it's already loaded */
+ load_extop( &slap_EXOP_START_TLS, 0, starttls_extop );
+ } else {
+ if ( rc == LDAP_NOT_SUPPORTED )
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ else
+ rc = LDAP_OTHER;
+ }
+ }
+ return rc;
+}
+
+static int
+config_tls_option(ConfigArgs *c) {
+ int flag, rc;
+ int berval = 0;
+ LDAP *ld = slap_tls_ld;
+ switch(c->type) {
+ case CFG_TLS_RAND: flag = LDAP_OPT_X_TLS_RANDOM_FILE; ld = NULL; break;
+ case CFG_TLS_CIPHER: flag = LDAP_OPT_X_TLS_CIPHER_SUITE; break;
+ case CFG_TLS_CERT_FILE: flag = LDAP_OPT_X_TLS_CERTFILE; break;
+ case CFG_TLS_CERT_KEY: flag = LDAP_OPT_X_TLS_KEYFILE; break;
+ case CFG_TLS_CA_PATH: flag = LDAP_OPT_X_TLS_CACERTDIR; break;
+ case CFG_TLS_CA_FILE: flag = LDAP_OPT_X_TLS_CACERTFILE; break;
+ case CFG_TLS_DH_FILE: flag = LDAP_OPT_X_TLS_DHFILE; break;
+ case CFG_TLS_ECNAME: flag = LDAP_OPT_X_TLS_ECNAME; break;
+#ifdef HAVE_GNUTLS
+ case CFG_TLS_CRL_FILE: flag = LDAP_OPT_X_TLS_CRLFILE; break;
+#endif
+ case CFG_TLS_CACERT: flag = LDAP_OPT_X_TLS_CACERT; berval = 1; break;
+ case CFG_TLS_CERT: flag = LDAP_OPT_X_TLS_CERT; berval = 1; break;
+ case CFG_TLS_KEY: flag = LDAP_OPT_X_TLS_KEY; berval = 1; break;
+ default: Debug(LDAP_DEBUG_ANY, "%s: "
+ "unknown tls_option <0x%x>\n",
+ c->log, c->type );
+ return 1;
+ }
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return ldap_pvt_tls_get_option( ld, flag, berval ? (void *)&c->value_bv : (void *)&c->value_string );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ config_push_cleanup( c, config_tls_cleanup );
+ return ldap_pvt_tls_set_option( ld, flag, NULL );
+ }
+ if ( !berval ) ch_free(c->value_string);
+ config_push_cleanup( c, config_tls_cleanup );
+ rc = ldap_pvt_tls_set_option(ld, flag, berval ? (void *)&c->value_bv : (void *)c->argv[1]);
+ if ( berval ) ch_free(c->value_bv.bv_val);
+ return rc;
+}
+
+/* FIXME: this ought to be provided by libldap */
+static int
+config_tls_config(ConfigArgs *c) {
+ int i, flag;
+ switch(c->type) {
+ case CFG_TLS_CRLCHECK: flag = LDAP_OPT_X_TLS_CRLCHECK; break;
+ case CFG_TLS_VERIFY: flag = LDAP_OPT_X_TLS_REQUIRE_CERT; break;
+ case CFG_TLS_PROTOCOL_MIN: flag = LDAP_OPT_X_TLS_PROTOCOL_MIN; break;
+ default:
+ Debug(LDAP_DEBUG_ANY, "%s: "
+ "unknown tls_option <0x%x>\n",
+ c->log, c->type );
+ return 1;
+ }
+ if (c->op == SLAP_CONFIG_EMIT) {
+ return slap_tls_get_config( slap_tls_ld, flag, &c->value_string );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ int i = 0;
+ config_push_cleanup( c, config_tls_cleanup );
+ return ldap_pvt_tls_set_option( slap_tls_ld, flag, &i );
+ }
+ ch_free( c->value_string );
+ config_push_cleanup( c, config_tls_cleanup );
+ if ( isdigit( (unsigned char)c->argv[1][0] ) && c->type != CFG_TLS_PROTOCOL_MIN ) {
+ if ( lutil_atoi( &i, c->argv[1] ) != 0 ) {
+ Debug(LDAP_DEBUG_ANY, "%s: "
+ "unable to parse %s \"%s\"\n",
+ c->log, c->argv[0], c->argv[1] );
+ return 1;
+ }
+ return(ldap_pvt_tls_set_option(slap_tls_ld, flag, &i));
+ } else {
+ return(ldap_pvt_tls_config(slap_tls_ld, flag, c->argv[1]));
+ }
+}
+#endif
+
+static CfEntryInfo *
+config_find_base( CfEntryInfo *root, struct berval *dn, CfEntryInfo **last,
+ Operation *op )
+{
+ struct berval cdn;
+ char *c;
+
+ if ( !root ) {
+ *last = NULL;
+ return NULL;
+ }
+
+ if ( dn_match( &root->ce_entry->e_nname, dn ))
+ return root;
+
+ c = dn->bv_val+dn->bv_len;
+ for (;*c != ',';c--);
+
+ while(root) {
+ if ( !op || access_allowed( op, root->ce_entry,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) ) {
+ /*
+ * ITS#10139: Only record the lowermost entry that the user has
+ * disclose access to
+ */
+ *last = root;
+ }
+ for (--c;c>dn->bv_val && *c != ',';c--);
+ cdn.bv_val = c;
+ if ( *c == ',' )
+ cdn.bv_val++;
+ cdn.bv_len = dn->bv_len - (cdn.bv_val - dn->bv_val);
+
+ root = root->ce_kids;
+
+ for (;root;root=root->ce_sibs) {
+ if ( dn_match( &root->ce_entry->e_nname, &cdn )) {
+ if ( cdn.bv_val == dn->bv_val ) {
+ return root;
+ }
+ break;
+ }
+ }
+ }
+ return root;
+}
+
+typedef struct setup_cookie {
+ CfBackInfo *cfb;
+ ConfigArgs *ca;
+ Entry *frontend;
+ Entry *config;
+ int got_frontend;
+ int got_config;
+} setup_cookie;
+
+static int
+config_ldif_resp( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ setup_cookie *sc = op->o_callback->sc_private;
+ struct berval pdn;
+
+ sc->cfb->cb_got_ldif = 1;
+ /* Does the frontend exist? */
+ if ( !sc->got_frontend ) {
+ if ( !strncmp( rs->sr_entry->e_nname.bv_val,
+ "olcDatabase", STRLENOF( "olcDatabase" )))
+ {
+ if ( strncmp( rs->sr_entry->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "={-1}frontend",
+ STRLENOF( "={-1}frontend" )))
+ {
+ struct berval rdn;
+ int i = op->o_noop;
+ sc->ca->be = frontendDB;
+ sc->ca->bi = frontendDB->bd_info;
+ frontendDB->be_cf_ocs = &CFOC_FRONTEND;
+ rdn.bv_val = sc->ca->log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( sc->ca->log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s",
+ cfAd_database->ad_cname.bv_val, -1,
+ sc->ca->bi->bi_type);
+ op->o_noop = 1;
+ sc->frontend = config_build_entry( op, rs,
+ sc->cfb->cb_root, sc->ca, &rdn, &CFOC_DATABASE,
+ sc->ca->be->be_cf_ocs );
+ op->o_noop = i;
+ sc->got_frontend++;
+ } else {
+ sc->got_frontend++;
+ goto ok;
+ }
+ }
+ }
+
+ dnParent( &rs->sr_entry->e_nname, &pdn );
+
+ /* Does the configDB exist? */
+ if ( sc->got_frontend && !sc->got_config &&
+ !strncmp( rs->sr_entry->e_nname.bv_val,
+ "olcDatabase", STRLENOF( "olcDatabase" )) &&
+ dn_match( &config_rdn, &pdn ) )
+ {
+ if ( strncmp( rs->sr_entry->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "={0}config",
+ STRLENOF( "={0}config" )))
+ {
+ struct berval rdn;
+ int i = op->o_noop;
+ sc->ca->be = LDAP_STAILQ_FIRST( &backendDB );
+ sc->ca->bi = sc->ca->be->bd_info;
+ rdn.bv_val = sc->ca->log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( sc->ca->log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s",
+ cfAd_database->ad_cname.bv_val, 0,
+ sc->ca->bi->bi_type);
+ op->o_noop = 1;
+ sc->config = config_build_entry( op, rs, sc->cfb->cb_root,
+ sc->ca, &rdn, &CFOC_DATABASE, sc->ca->be->be_cf_ocs );
+ op->o_noop = i;
+ }
+ sc->got_config++;
+ }
+
+ok:
+ rs->sr_err = config_add_internal( sc->cfb, rs->sr_entry, sc->ca, NULL, NULL, NULL );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "config error processing %s: %s\n",
+ rs->sr_entry->e_name.bv_val, sc->ca->cr_msg );
+ }
+ }
+ return rs->sr_err;
+}
+
+/* Configure and read the underlying back-ldif store */
+static int
+config_setup_ldif( BackendDB *be, const char *dir, int readit ) {
+ CfBackInfo *cfb = be->be_private;
+ ConfigArgs c = {0};
+ ConfigTable *ct;
+ char *argv[3];
+ int rc = 0;
+ setup_cookie sc;
+ slap_callback cb = { NULL, config_ldif_resp, NULL, NULL };
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ SlapReply rs = {REP_RESULT};
+ Filter filter = { LDAP_FILTER_PRESENT };
+ struct berval filterstr = BER_BVC("(objectclass=*)");
+ struct stat st;
+
+ /* Is the config directory available? */
+ if ( stat( dir, &st ) < 0 ) {
+ /* No, so don't bother using the backing store.
+ * All changes will be in-memory only.
+ */
+ return 0;
+ }
+
+ cfb->cb_db.bd_info = backend_info( "ldif" );
+ if ( !cfb->cb_db.bd_info )
+ return 0; /* FIXME: eventually this will be a fatal error */
+
+ if ( backend_db_init( "ldif", &cfb->cb_db, -1, NULL ) == NULL )
+ return 1;
+
+ cfb->cb_db.be_suffix = be->be_suffix;
+ cfb->cb_db.be_nsuffix = be->be_nsuffix;
+
+ /* The suffix is always "cn=config". The underlying DB's rootdn
+ * is always the same as the suffix.
+ */
+ cfb->cb_db.be_rootdn = be->be_suffix[0];
+ cfb->cb_db.be_rootndn = be->be_nsuffix[0];
+
+ ber_str2bv( dir, 0, 1, &cfdir );
+
+ c.be = &cfb->cb_db;
+ c.fname = "slapd";
+ c.argc = 2;
+ argv[0] = "directory";
+ argv[1] = (char *)dir;
+ argv[2] = NULL;
+ c.argv = argv;
+ c.reply.err = 0;
+ c.reply.msg[0] = 0;
+ c.table = Cft_Database;
+
+ ct = config_find_keyword( c.be->be_cf_ocs->co_table, &c );
+ if ( !ct )
+ return 1;
+
+ if ( config_add_vals( ct, &c ))
+ return 1;
+
+ if ( backend_startup_one( &cfb->cb_db, &c.reply ))
+ return 1;
+
+ if ( readit ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+ int prev_DN_strict;
+
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ filter.f_desc = slap_schema.si_ad_objectClass;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+
+ op->ors_filter = &filter;
+ op->ors_filterstr = filterstr;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+
+ op->o_dn = c.be->be_rootdn;
+ op->o_ndn = c.be->be_rootndn;
+
+ op->o_req_dn = be->be_suffix[0];
+ op->o_req_ndn = be->be_nsuffix[0];
+
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = SLAP_NO_LIMIT;
+
+ op->ors_attrs = slap_anlist_all_attributes;
+ op->ors_attrsonly = 0;
+
+ op->o_callback = &cb;
+ sc.cfb = cfb;
+ sc.ca = &c;
+ cb.sc_private = &sc;
+ sc.got_frontend = 0;
+ sc.got_config = 0;
+ sc.frontend = NULL;
+ sc.config = NULL;
+
+ op->o_bd = &cfb->cb_db;
+
+ /* Allow unknown attrs in DNs */
+ prev_DN_strict = slap_DN_strict;
+ slap_DN_strict = 0;
+
+ rc = op->o_bd->be_search( op, &rs );
+
+ /* Restore normal DN validation */
+ slap_DN_strict = prev_DN_strict;
+
+ op->o_tag = LDAP_REQ_ADD;
+ if ( rc == LDAP_SUCCESS && sc.frontend ) {
+ rs_reinit( &rs, REP_RESULT );
+ op->ora_e = sc.frontend;
+ rc = op->o_bd->be_add( op, &rs );
+ }
+ if ( rc == LDAP_SUCCESS && sc.config ) {
+ rs_reinit( &rs, REP_RESULT );
+ op->ora_e = sc.config;
+ rc = op->o_bd->be_add( op, &rs );
+ }
+ ldap_pvt_thread_pool_context_reset( thrctx );
+ } else {
+ /* ITS#9016 Check directory is empty (except perhaps hidden files) */
+ DIR *dir_of_path;
+ struct dirent *entry;
+
+ dir_of_path = opendir( dir );
+ while ( (entry = readdir( dir_of_path )) != NULL ) {
+ if ( entry->d_name[0] != '.' ) {
+ Debug( LDAP_DEBUG_ANY, "config_setup_ldif: "
+ "expected directory %s to be empty!\n",
+ dir );
+ rc = LDAP_ALREADY_EXISTS;
+ break;
+ }
+ }
+ closedir( dir_of_path );
+ }
+
+ /* ITS#4194 - only use if it's present, or we're converting. */
+ if ( !readit || rc == LDAP_SUCCESS )
+ cfb->cb_use_ldif = 1;
+
+ return rc;
+}
+
+static int
+CfOc_cmp( const void *c1, const void *c2 ) {
+ const ConfigOCs *co1 = c1;
+ const ConfigOCs *co2 = c2;
+
+ return ber_bvcmp( co1->co_name, co2->co_name );
+}
+
+int
+config_register_schema(ConfigTable *ct, ConfigOCs *ocs) {
+ int i;
+
+ i = init_config_attrs( ct );
+ if ( i ) return i;
+
+ /* set up the objectclasses */
+ i = init_config_ocs( ocs );
+ if ( i ) return i;
+
+ for (i=0; ocs[i].co_def; i++) {
+ if ( ocs[i].co_oc ) {
+ ocs[i].co_name = &ocs[i].co_oc->soc_cname;
+ if ( !ocs[i].co_table )
+ ocs[i].co_table = ct;
+ ldap_avl_insert( &CfOcTree, &ocs[i], CfOc_cmp, ldap_avl_dup_error );
+ }
+ }
+ return 0;
+}
+
+int
+read_config(const char *fname, const char *dir) {
+ BackendDB *be;
+ CfBackInfo *cfb;
+ const char *cfdir, *cfname;
+ int rc;
+
+ /* Setup the config backend */
+ be = backend_db_init( "config", NULL, 0, NULL );
+ if ( !be )
+ return 1;
+
+ cfb = be->be_private;
+ be->be_dfltaccess = ACL_NONE;
+
+ /* If no .conf, or a dir was specified, setup the dir */
+ if ( !fname || dir ) {
+ if ( dir ) {
+ /* If explicitly given, check for existence */
+ struct stat st;
+
+ if ( stat( dir, &st ) < 0 ) {
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_ANY,
+ "invalid config directory %s, error %d\n",
+ dir, saved_errno );
+ return 1;
+ }
+ cfdir = dir;
+ } else {
+ cfdir = SLAPD_DEFAULT_CONFIGDIR;
+ }
+ /* if fname is defaulted, try reading .d */
+ rc = config_setup_ldif( be, cfdir, !fname );
+
+ if ( rc ) {
+ /* It may be OK if the base object doesn't exist yet. */
+ if ( rc != LDAP_NO_SUCH_OBJECT )
+ return 1;
+ /* ITS#4194: But if dir was specified and no fname,
+ * then we were supposed to read the dir. Unless we're
+ * trying to slapadd the dir...
+ */
+ if ( dir && !fname ) {
+ if ( slapMode & (SLAP_SERVER_MODE|SLAP_TOOL_READMAIN|SLAP_TOOL_READONLY))
+ return 1;
+ /* Assume it's slapadd with a config dir, let it continue */
+ rc = 0;
+ cfb->cb_got_ldif = 1;
+ cfb->cb_use_ldif = 1;
+ goto done;
+ }
+ }
+
+ /* If we read the config from back-ldif, nothing to do here */
+ if ( cfb->cb_got_ldif ) {
+ rc = 0;
+ goto done;
+ }
+ }
+
+ if ( fname )
+ cfname = fname;
+ else
+ cfname = SLAPD_DEFAULT_CONFIGFILE;
+
+ rc = read_config_file(cfname, 0, NULL, config_back_cf_table);
+
+ if ( rc == 0 )
+ ber_str2bv( cfname, 0, 1, &cfb->cb_config->c_file );
+
+done:
+ if ( rc == 0 && BER_BVISNULL( &frontendDB->be_schemadn ) ) {
+ ber_str2bv( SLAPD_SCHEMA_DN, STRLENOF( SLAPD_SCHEMA_DN ), 1,
+ &frontendDB->be_schemadn );
+ rc = dnNormalize( 0, NULL, NULL, &frontendDB->be_schemadn, &frontendDB->be_schemandn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY, "read_config: "
+ "unable to normalize default schema DN \"%s\"\n",
+ frontendDB->be_schemadn.bv_val );
+ /* must not happen */
+ assert( 0 );
+ }
+ }
+ if ( rc == 0 && ( slapMode & SLAP_SERVER_MODE ) && sid_list ) {
+ if ( !BER_BVISEMPTY( &sid_list->si_url ) && !sid_set ) {
+ Debug(LDAP_DEBUG_ANY, "read_config: no serverID / URL match found. "
+ "Check slapd -h arguments.\n" );
+ rc = LDAP_OTHER;
+ }
+ }
+ return rc;
+}
+
+static int
+config_back_bind( Operation *op, SlapReply *rs )
+{
+ if ( be_isroot_pw( op ) ) {
+ ber_dupbv( &op->orb_edn, be_root_dn( op->o_bd ));
+ /* frontend sends result */
+ return LDAP_SUCCESS;
+ }
+
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+}
+
+static int
+config_send( Operation *op, SlapReply *rs, CfEntryInfo *ce, int depth )
+{
+ int rc = 0;
+
+ if ( test_filter( op, ce->ce_entry, op->ors_filter ) == LDAP_COMPARE_TRUE )
+ {
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_entry = ce->ce_entry;
+ rs->sr_flags = 0;
+ rc = send_search_entry( op, rs );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+ if ( op->ors_scope == LDAP_SCOPE_SUBTREE ) {
+ if ( ce->ce_kids ) {
+ rc = config_send( op, rs, ce->ce_kids, 1 );
+ if ( rc ) return rc;
+ }
+ if ( depth ) {
+ for (ce=ce->ce_sibs; ce; ce=ce->ce_sibs) {
+ rc = config_send( op, rs, ce, 0 );
+ if ( rc ) break;
+ }
+ }
+ }
+ return rc;
+}
+
+static ConfigTable *
+config_find_table( ConfigOCs **colst, int nocs, AttributeDescription *ad,
+ ConfigArgs *ca )
+{
+ int i, j;
+ if (ad->ad_flags & SLAP_DESC_BINARY)
+ ad = ad->ad_type->sat_ad;
+
+ for (j=0; j<nocs; j++) {
+ for (i=0; colst[j]->co_table[i].name; i++)
+ if ( colst[j]->co_table[i].ad == ad ) {
+ ca->table = colst[j]->co_type;
+ ca->ca_desc = colst[j]->co_table+i;
+ return &colst[j]->co_table[i];
+ }
+ }
+ return NULL;
+}
+
+/* Sort the attributes of the entry according to the order defined
+ * in the objectclass, with required attributes occurring before
+ * allowed attributes. For any attributes with sequencing dependencies
+ * (e.g., rootDN must be defined after suffix) the objectclass must
+ * list the attributes in the desired sequence.
+ */
+static void
+sort_attrs( Entry *e, ConfigOCs **colst, int nocs )
+{
+ Attribute *a, *head = NULL, *tail = NULL, **prev;
+ int i, j;
+
+ for (i=0; i<nocs; i++) {
+ if ( colst[i]->co_oc->soc_required ) {
+ AttributeType **at = colst[i]->co_oc->soc_required;
+ for (j=0; at[j]; j++) {
+ for (a=e->e_attrs, prev=&e->e_attrs; a;
+ prev = &(*prev)->a_next, a=a->a_next) {
+ if ( a->a_desc == at[j]->sat_ad ) {
+ *prev = a->a_next;
+ if (!head) {
+ head = a;
+ tail = a;
+ } else {
+ tail->a_next = a;
+ tail = a;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if ( colst[i]->co_oc->soc_allowed ) {
+ AttributeType **at = colst[i]->co_oc->soc_allowed;
+ for (j=0; at[j]; j++) {
+ for (a=e->e_attrs, prev=&e->e_attrs; a;
+ prev = &(*prev)->a_next, a=a->a_next) {
+ if ( a->a_desc == at[j]->sat_ad ) {
+ *prev = a->a_next;
+ if (!head) {
+ head = a;
+ tail = a;
+ } else {
+ tail->a_next = a;
+ tail = a;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ if ( tail ) {
+ tail->a_next = e->e_attrs;
+ e->e_attrs = head;
+ }
+}
+
+static int
+check_vals( ConfigTable *ct, ConfigArgs *ca, void *ptr, int isAttr )
+{
+ Attribute *a = NULL;
+ AttributeDescription *ad;
+ BerVarray vals;
+
+ int i, rc = 0;
+
+ if ( isAttr ) {
+ a = ptr;
+ ad = a->a_desc;
+ vals = a->a_vals;
+ } else {
+ Modifications *ml = ptr;
+ ad = ml->sml_desc;
+ vals = ml->sml_values;
+ }
+
+ if ( a && ( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL )) {
+ rc = ordered_value_sort( a, 1 );
+ if ( rc ) {
+ snprintf(ca->cr_msg, sizeof( ca->cr_msg ), "ordered_value_sort failed on attr %s\n",
+ ad->ad_cname.bv_val );
+ return rc;
+ }
+ }
+ for ( i=0; vals[i].bv_val; i++ ) {
+ ca->line = vals[i].bv_val;
+ ca->linelen = vals[i].bv_len;
+ if (( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) &&
+ ca->line[0] == '{' ) {
+ char *idx = strchr( ca->line, '}' );
+ if ( idx ) {
+ ca->linelen -= (idx+1) - ca->line;
+ ca->line = idx+1;
+ }
+ }
+ rc = config_parse_vals( ct, ca, i );
+ if ( rc ) {
+ break;
+ }
+ }
+ return rc;
+}
+
+static int
+config_rename_attr( SlapReply *rs, Entry *e, struct berval *rdn,
+ Attribute **at )
+{
+ struct berval rtype, rval;
+ Attribute *a;
+ AttributeDescription *ad = NULL;
+
+ dnRdn( &e->e_name, rdn );
+ rval.bv_val = strchr(rdn->bv_val, '=' ) + 1;
+ rval.bv_len = rdn->bv_len - (rval.bv_val - rdn->bv_val);
+ rtype.bv_val = rdn->bv_val;
+ rtype.bv_len = rval.bv_val - rtype.bv_val - 1;
+
+ /* Find attr */
+ slap_bv2ad( &rtype, &ad, &rs->sr_text );
+ a = attr_find( e->e_attrs, ad );
+ if (!a ) return LDAP_NAMING_VIOLATION;
+ *at = a;
+
+ return 0;
+}
+
+static void
+config_rename_kids( CfEntryInfo *ce )
+{
+ CfEntryInfo *ce2;
+ struct berval rdn, nrdn;
+
+ for (ce2 = ce->ce_kids; ce2; ce2 = ce2->ce_sibs) {
+ struct berval newdn, newndn;
+ dnRdn ( &ce2->ce_entry->e_name, &rdn );
+ dnRdn ( &ce2->ce_entry->e_nname, &nrdn );
+ build_new_dn( &newdn, &ce->ce_entry->e_name, &rdn, NULL );
+ build_new_dn( &newndn, &ce->ce_entry->e_nname, &nrdn, NULL );
+ free( ce2->ce_entry->e_name.bv_val );
+ free( ce2->ce_entry->e_nname.bv_val );
+ ce2->ce_entry->e_name = newdn;
+ ce2->ce_entry->e_nname = newndn;
+ config_rename_kids( ce2 );
+ }
+}
+
+static int
+config_rename_one( Operation *op, SlapReply *rs, Entry *e,
+ CfEntryInfo *parent, Attribute *a, struct berval *newrdn,
+ struct berval *nnewrdn, int use_ldif )
+{
+ int cnt, rc = 0;
+ struct berval odn, ondn;
+ const char *text = "";
+ LDAPRDN rDN;
+
+ odn = e->e_name;
+ ondn = e->e_nname;
+ build_new_dn( &e->e_name, &parent->ce_entry->e_name, newrdn, NULL );
+ build_new_dn( &e->e_nname, &parent->ce_entry->e_nname, nnewrdn, NULL );
+
+ /* Replace attr */
+ rc = ldap_bv2rdn( &e->e_name, &rDN, (char **)&text, LDAP_DN_FORMAT_LDAP );
+ if ( rc ) {
+ return rc;
+ }
+ for ( cnt = 0; rDN[cnt]; cnt++ ) {
+ AttributeDescription *ad = NULL;
+ LDAPAVA *ava = rDN[cnt];
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc ) {
+ break;
+ }
+
+ if ( ad != a->a_desc ) continue;
+
+ free( a->a_vals[0].bv_val );
+ ber_dupbv( &a->a_vals[0], &ava->la_value );
+ if ( a->a_nvals != a->a_vals ) {
+ free( a->a_nvals[0].bv_val );
+ rc = attr_normalize_one( ad, &ava->la_value, &a->a_nvals[0], NULL );
+ if ( rc ) {
+ break;
+ }
+ }
+
+ /* attributes with X-ORDERED 'SIBLINGS' are single-valued, we're done */
+ break;
+ }
+ /* the attribute must be present in rDN */
+ assert( rDN[cnt] );
+ ldap_rdnfree( rDN );
+ if ( rc ) {
+ return rc;
+ }
+
+ if ( use_ldif ) {
+ CfBackInfo *cfb = (CfBackInfo *)op->o_bd->be_private;
+ BackendDB *be = op->o_bd;
+ slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp;
+ struct berval dn, ndn, xdn, xndn;
+
+ op->o_bd = &cfb->cb_db;
+
+ /* Save current rootdn; use the underlying DB's rootdn */
+ dn = op->o_dn;
+ ndn = op->o_ndn;
+ xdn = op->o_req_dn;
+ xndn = op->o_req_ndn;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_req_dn = odn;
+ op->o_req_ndn = ondn;
+
+ scp = op->o_callback;
+ op->o_callback = &sc;
+ op->orr_newrdn = *newrdn;
+ op->orr_nnewrdn = *nnewrdn;
+ op->orr_newSup = NULL;
+ op->orr_nnewSup = NULL;
+ op->orr_newDN = e->e_name;
+ op->orr_nnewDN = e->e_nname;
+ op->orr_deleteoldrdn = 1;
+ op->orr_modlist = NULL;
+ slap_modrdn2mods( op, rs );
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+ rc = op->o_bd->be_modrdn( op, rs );
+ slap_mods_free( op->orr_modlist, 1 );
+
+ op->o_bd = be;
+ op->o_callback = scp;
+ op->o_dn = dn;
+ op->o_ndn = ndn;
+ op->o_req_dn = xdn;
+ op->o_req_ndn = xndn;
+ }
+ free( odn.bv_val );
+ free( ondn.bv_val );
+ if ( e->e_private )
+ config_rename_kids( e->e_private );
+ return rc;
+}
+
+static int
+config_renumber_one( Operation *op, SlapReply *rs, CfEntryInfo *parent,
+ Entry *e, int idx, int tailindex, int use_ldif )
+{
+ struct berval ival, newrdn, nnewrdn;
+ struct berval rdn;
+ Attribute *a;
+ char ibuf[32], *ptr1, *ptr2 = NULL;
+ int rc = 0;
+
+ rc = config_rename_attr( rs, e, &rdn, &a );
+ if ( rc ) return rc;
+
+ ival.bv_val = ibuf;
+ ival.bv_len = snprintf( ibuf, sizeof( ibuf ), SLAP_X_ORDERED_FMT, idx );
+ if ( ival.bv_len >= sizeof( ibuf ) ) {
+ return LDAP_NAMING_VIOLATION;
+ }
+
+ newrdn.bv_len = rdn.bv_len + ival.bv_len;
+ newrdn.bv_val = ch_malloc( newrdn.bv_len+1 );
+
+ if ( tailindex ) {
+ ptr1 = lutil_strncopy( newrdn.bv_val, rdn.bv_val, rdn.bv_len );
+ ptr1 = lutil_strcopy( ptr1, ival.bv_val );
+ } else {
+ int xlen;
+ ptr2 = ber_bvchr( &rdn, '}' );
+ if ( ptr2 ) {
+ ptr2++;
+ } else {
+ ptr2 = rdn.bv_val + a->a_desc->ad_cname.bv_len + 1;
+ }
+ xlen = rdn.bv_len - (ptr2 - rdn.bv_val);
+ ptr1 = lutil_strncopy( newrdn.bv_val, a->a_desc->ad_cname.bv_val,
+ a->a_desc->ad_cname.bv_len );
+ *ptr1++ = '=';
+ ptr1 = lutil_strcopy( ptr1, ival.bv_val );
+ ptr1 = lutil_strncopy( ptr1, ptr2, xlen );
+ *ptr1 = '\0';
+ }
+
+ /* Do the equivalent of ModRDN */
+ /* Replace DN / NDN */
+ newrdn.bv_len = ptr1 - newrdn.bv_val;
+ rc = rdnNormalize( 0, NULL, NULL, &newrdn, &nnewrdn, NULL );
+ if ( rc ) {
+ free( newrdn.bv_val );
+ return LDAP_NAMING_VIOLATION;
+ }
+ rc = config_rename_one( op, rs, e, parent, a, &newrdn, &nnewrdn, use_ldif );
+
+ free( nnewrdn.bv_val );
+ free( newrdn.bv_val );
+ return rc;
+}
+
+static int
+check_name_index( CfEntryInfo *parent, ConfigType ce_type, Entry *e,
+ SlapReply *rs, int *renum, int *ibase )
+{
+ CfEntryInfo *ce;
+ int index = -1, gotindex = 0, nsibs, rc = 0;
+ int renumber = 0, tailindex = 0, isfrontend = 0, isconfig = 0;
+ char *ptr1, *ptr2 = NULL;
+ struct berval rdn;
+
+ if ( renum ) *renum = 0;
+
+ /* These entries don't get indexed/renumbered */
+ if ( ce_type == Cft_Global ) return 0;
+ if ( ce_type == Cft_Schema && parent->ce_type == Cft_Global ) return 0;
+
+ if ( ce_type == Cft_Module )
+ tailindex = 1;
+
+ /* See if the rdn has an index already */
+ dnRdn( &e->e_name, &rdn );
+ if ( ce_type == Cft_Database ) {
+ if ( !strncmp( rdn.bv_val + rdn.bv_len - STRLENOF("frontend"),
+ "frontend", STRLENOF("frontend") ))
+ isfrontend = 1;
+ else if ( !strncmp( rdn.bv_val + rdn.bv_len - STRLENOF("config"),
+ "config", STRLENOF("config") ))
+ isconfig = 1;
+ }
+ ptr1 = ber_bvchr( &e->e_name, '{' );
+ if ( ptr1 && ptr1 < &e->e_name.bv_val[ rdn.bv_len ] ) {
+ char *next;
+ ptr2 = strchr( ptr1, '}' );
+ if ( !ptr2 || ptr2 > &e->e_name.bv_val[ rdn.bv_len ] )
+ return LDAP_NAMING_VIOLATION;
+ if ( ptr2-ptr1 == 1)
+ return LDAP_NAMING_VIOLATION;
+ gotindex = 1;
+ index = strtol( ptr1 + 1, &next, 10 );
+ if ( next == ptr1 + 1 || next[ 0 ] != '}' ) {
+ return LDAP_NAMING_VIOLATION;
+ }
+ if ( index < 0 ) {
+ /* Special case, we allow -1 for the frontendDB */
+ if ( index != -1 || !isfrontend )
+ return LDAP_NAMING_VIOLATION;
+ }
+ if ( isconfig && index != 0 ){
+ return LDAP_NAMING_VIOLATION;
+ }
+ }
+
+ /* count related kids.
+ * For entries of type Cft_Misc, only count siblings with same RDN type
+ */
+ if ( ce_type == Cft_Misc ) {
+ rdn.bv_val = e->e_nname.bv_val;
+ ptr1 = strchr( rdn.bv_val, '=' );
+ assert( ptr1 != NULL );
+
+ rdn.bv_len = ptr1 - rdn.bv_val;
+
+ for (nsibs=0, ce=parent->ce_kids; ce; ce=ce->ce_sibs) {
+ struct berval rdn2;
+ if ( ce->ce_type != ce_type )
+ continue;
+
+ dnRdn( &ce->ce_entry->e_nname, &rdn2 );
+
+ ptr1 = strchr( rdn2.bv_val, '=' );
+ assert( ptr1 != NULL );
+
+ rdn2.bv_len = ptr1 - rdn2.bv_val;
+ if ( bvmatch( &rdn, &rdn2 ))
+ nsibs++;
+ }
+ } else {
+ for (nsibs=0, ce=parent->ce_kids; ce; ce=ce->ce_sibs) {
+ if ( ce->ce_type == ce_type ) nsibs++;
+ }
+ }
+
+ /* account for -1 frontend */
+ if ( ce_type == Cft_Database )
+ nsibs--;
+
+ if ( index != nsibs || isfrontend ) {
+ if ( gotindex ) {
+ if ( index < nsibs ) {
+ if ( tailindex ) return LDAP_NAMING_VIOLATION;
+ /* Siblings need to be renumbered */
+ if ( index != -1 || !isfrontend )
+ renumber = 1;
+ }
+ }
+ /* config DB is always "0" */
+ if ( isconfig && index == -1 ) {
+ index = 0;
+ }
+ if (( !isfrontend && index == -1 ) || ( index > nsibs ) ){
+ index = nsibs;
+ }
+
+ /* just make index = nsibs */
+ if ( !renumber ) {
+ rc = config_renumber_one( NULL, rs, parent, e, index, tailindex, 0 );
+ }
+ }
+ if ( ibase ) *ibase = index;
+ if ( renum ) *renum = renumber;
+ return rc;
+}
+
+/* Insert all superior classes of the given class */
+static int
+count_oc( ObjectClass *oc, ConfigOCs ***copp, int *nocs )
+{
+ ConfigOCs co, *cop;
+ ObjectClass **sups;
+
+ for ( sups = oc->soc_sups; sups && *sups; sups++ ) {
+ if ( count_oc( *sups, copp, nocs ) ) {
+ return -1;
+ }
+ }
+
+ co.co_name = &oc->soc_cname;
+ cop = ldap_avl_find( CfOcTree, &co, CfOc_cmp );
+ if ( cop ) {
+ int i;
+
+ /* check for duplicates */
+ for ( i = 0; i < *nocs; i++ ) {
+ if ( *copp && (*copp)[i] == cop ) {
+ break;
+ }
+ }
+
+ if ( i == *nocs ) {
+ ConfigOCs **tmp = ch_realloc( *copp, (*nocs + 1)*sizeof( ConfigOCs * ) );
+ if ( tmp == NULL ) {
+ return -1;
+ }
+ *copp = tmp;
+ (*copp)[*nocs] = cop;
+ (*nocs)++;
+ }
+ }
+
+ return 0;
+}
+
+/* Find all superior classes of the given objectclasses,
+ * return list in order of most-subordinate first.
+ *
+ * Special / auxiliary / Cft_Misc classes always take precedence.
+ */
+static ConfigOCs **
+count_ocs( Attribute *oc_at, int *nocs )
+{
+ int i, j, misc = -1;
+ ConfigOCs **colst = NULL;
+
+ *nocs = 0;
+
+ for ( i = oc_at->a_numvals; i--; ) {
+ ObjectClass *oc = oc_bvfind( &oc_at->a_nvals[i] );
+
+ assert( oc != NULL );
+ if ( count_oc( oc, &colst, nocs ) ) {
+ ch_free( colst );
+ return NULL;
+ }
+ }
+
+ /* invert order */
+ i = 0;
+ j = *nocs - 1;
+ while ( i < j ) {
+ ConfigOCs *tmp = colst[i];
+ colst[i] = colst[j];
+ colst[j] = tmp;
+ if (tmp->co_type == Cft_Misc)
+ misc = j;
+ i++; j--;
+ }
+ /* Move misc class to front of list */
+ if (misc > 0) {
+ ConfigOCs *tmp = colst[misc];
+ for (i=misc; i>0; i--)
+ colst[i] = colst[i-1];
+ colst[0] = tmp;
+ }
+
+ return colst;
+}
+
+static int
+cfAddInclude( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ /* Leftover from RE23. Never parse this entry */
+ return LDAP_COMPARE_TRUE;
+}
+
+static int
+cfAddSchema( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ ConfigFile *cfo;
+
+ /* This entry is hardcoded, don't re-parse it */
+ if ( p->ce_type == Cft_Global ) {
+ cfn = p->ce_private;
+ ca->ca_private = cfn;
+ return LDAP_COMPARE_TRUE;
+ }
+ if ( p->ce_type != Cft_Schema )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ cfn = ch_calloc( 1, sizeof(ConfigFile) );
+ ca->ca_private = cfn;
+ cfo = p->ce_private;
+ cfn->c_sibs = cfo->c_kids;
+ cfo->c_kids = cfn;
+ return LDAP_SUCCESS;
+}
+
+static int
+cfAddDatabase( CfEntryInfo *p, Entry *e, struct config_args_s *ca )
+{
+ if ( p->ce_type != Cft_Global ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ /* config must be {0}, nothing else allowed */
+ if ( !strncmp( e->e_nname.bv_val, "olcDatabase={0}", STRLENOF("olcDatabase={0}")) &&
+ strncmp( e->e_nname.bv_val + STRLENOF("olcDatabase={0}"), "config,", STRLENOF("config,") )) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ ca->be = frontendDB; /* just to get past check_vals */
+ return LDAP_SUCCESS;
+}
+
+static int
+cfAddBackend( CfEntryInfo *p, Entry *e, struct config_args_s *ca )
+{
+ if ( p->ce_type != Cft_Global ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+cfAddModule( CfEntryInfo *p, Entry *e, struct config_args_s *ca )
+{
+ if ( p->ce_type != Cft_Global ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+cfAddOverlay( CfEntryInfo *p, Entry *e, struct config_args_s *ca )
+{
+ if ( p->ce_type != Cft_Database ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ ca->be = p->ce_be;
+ return LDAP_SUCCESS;
+}
+
+static void
+schema_destroy_one( ConfigArgs *ca, ConfigOCs **colst, int nocs,
+ CfEntryInfo *p )
+{
+ ConfigTable *ct;
+ ConfigFile *cfo;
+ AttributeDescription *ad;
+ const char *text;
+
+ ca->valx = -1;
+ ca->line = NULL;
+ ca->argc = 1;
+ if ( cfn->c_cr_head ) {
+ struct berval bv = BER_BVC("olcDitContentRules");
+ ad = NULL;
+ slap_bv2ad( &bv, &ad, &text );
+ ct = config_find_table( colst, nocs, ad, ca );
+ config_del_vals( ct, ca );
+ }
+ if ( cfn->c_oc_head ) {
+ struct berval bv = BER_BVC("olcObjectClasses");
+ ad = NULL;
+ slap_bv2ad( &bv, &ad, &text );
+ ct = config_find_table( colst, nocs, ad, ca );
+ config_del_vals( ct, ca );
+ }
+ if ( cfn->c_at_head ) {
+ struct berval bv = BER_BVC("olcAttributeTypes");
+ ad = NULL;
+ slap_bv2ad( &bv, &ad, &text );
+ ct = config_find_table( colst, nocs, ad, ca );
+ config_del_vals( ct, ca );
+ }
+ if ( cfn->c_syn_head ) {
+ struct berval bv = BER_BVC("olcLdapSyntaxes");
+ ad = NULL;
+ slap_bv2ad( &bv, &ad, &text );
+ ct = config_find_table( colst, nocs, ad, ca );
+ config_del_vals( ct, ca );
+ }
+ if ( cfn->c_om_head ) {
+ struct berval bv = BER_BVC("olcObjectIdentifier");
+ ad = NULL;
+ slap_bv2ad( &bv, &ad, &text );
+ ct = config_find_table( colst, nocs, ad, ca );
+ config_del_vals( ct, ca );
+ }
+ cfo = p->ce_private;
+ cfo->c_kids = cfn->c_sibs;
+ ch_free( cfn );
+}
+
+static int
+config_add_oc( ConfigOCs **cop, CfEntryInfo *last, Entry *e, ConfigArgs *ca )
+{
+ int rc = LDAP_CONSTRAINT_VIOLATION;
+ ObjectClass **ocp;
+
+ if ( (*cop)->co_ldadd ) {
+ rc = (*cop)->co_ldadd( last, e, ca );
+ if ( rc != LDAP_CONSTRAINT_VIOLATION ) {
+ return rc;
+ }
+ }
+
+ for ( ocp = (*cop)->co_oc->soc_sups; ocp && *ocp; ocp++ ) {
+ ConfigOCs co = { 0 };
+
+ co.co_name = &(*ocp)->soc_cname;
+ *cop = ldap_avl_find( CfOcTree, &co, CfOc_cmp );
+ if ( *cop == NULL ) {
+ return rc;
+ }
+
+ rc = config_add_oc( cop, last, e, ca );
+ if ( rc != LDAP_CONSTRAINT_VIOLATION ) {
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static BackendDB *configDB; /* only set by slapadd */
+
+/* Parse an LDAP entry into config directives */
+static int
+config_add_internal( CfBackInfo *cfb, Entry *e, ConfigArgs *ca, SlapReply *rs,
+ int *renum, Operation *op )
+{
+ CfEntryInfo *ce, *last = NULL;
+ ConfigOCs co, *coptr, **colst;
+ Attribute *a, *oc_at, *soc_at;
+ int i, ibase = -1, nocs, rc = 0;
+ struct berval pdn;
+ ConfigTable *ct;
+ char *ptr, *log_prefix = op ? op->o_log_prefix : "";
+
+ memset( ca, 0, sizeof(ConfigArgs));
+
+ /* Make sure parent exists and entry does not. But allow
+ * Databases and Overlays to be inserted. Don't do any
+ * auto-renumbering if manageDSAit control is present.
+ */
+ ce = config_find_base( cfb->cb_root, &e->e_nname, &last, op );
+ if ( ce ) {
+ if ( ( op && op->o_managedsait ) ||
+ ( ce->ce_type != Cft_Database && ce->ce_type != Cft_Overlay &&
+ ce->ce_type != Cft_Module ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" already exists\n",
+ log_prefix, e->e_name.bv_val );
+ /* global schema ignores all writes */
+ if ( ce->ce_type == Cft_Schema && ce->ce_parent->ce_type == Cft_Global )
+ return LDAP_COMPARE_TRUE;
+ return LDAP_ALREADY_EXISTS;
+ }
+ }
+
+ dnParent( &e->e_nname, &pdn );
+
+ /* If last is NULL, the new entry is the root/suffix entry,
+ * otherwise last should be the parent.
+ */
+ if ( cfb->cb_root && ( !last || !dn_match( &last->ce_entry->e_nname, &pdn ) ) ) {
+ if ( last && rs ) {
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ }
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" not child of DN=\"%s\"\n",
+ log_prefix, e->e_name.bv_val,
+ last ? last->ce_entry->e_name.bv_val : "" );
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( op ) {
+ /* No parent, must be root. This will never happen... */
+ if ( !last && !be_isroot( op ) && !be_shadow_update( op ) ) {
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( last && !access_allowed( op, last->ce_entry,
+ slap_schema.si_ad_children, NULL, ACL_WADD, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no write access to \"children\" of parent\n",
+ log_prefix, e->e_name.bv_val );
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+ }
+
+ oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass );
+ if ( !oc_at ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no objectClass\n",
+ log_prefix, e->e_name.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ soc_at = attr_find( e->e_attrs, slap_schema.si_ad_structuralObjectClass );
+ if ( !soc_at ) {
+ ObjectClass *soc = NULL;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ const char *text = textbuf;
+
+ /* FIXME: check result */
+ rc = structural_class( oc_at->a_nvals, &soc, NULL,
+ &text, textbuf, sizeof(textbuf), NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no structural objectClass (%s)\n",
+ log_prefix, e->e_name.bv_val, text );
+ return rc;
+ }
+ attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, &soc->soc_cname, NULL );
+ soc_at = attr_find( e->e_attrs, slap_schema.si_ad_structuralObjectClass );
+ if ( soc_at == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no structural objectClass; "
+ "unable to merge computed class %s\n",
+ log_prefix, e->e_name.bv_val,
+ soc->soc_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no structural objectClass; "
+ "computed objectClass %s merged\n",
+ log_prefix, e->e_name.bv_val,
+ soc->soc_cname.bv_val );
+ }
+
+ /* Fake the coordinates based on whether we're part of an
+ * LDAP Add or if reading the config dir
+ */
+ if ( rs ) {
+ ca->fname = "slapd";
+ ca->lineno = 0;
+ } else {
+ ca->fname = cfdir.bv_val;
+ ca->lineno = 1;
+ }
+ ca->ca_op = op;
+
+ {
+ ObjectClass *soc = oc_bvfind( &soc_at->a_nvals[0] );
+ if ( !soc ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" invalid structural objectClass %s\n",
+ log_prefix, e->e_name.bv_val, soc_at->a_vals[0].bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ co.co_name = &soc->soc_cname;
+ }
+ coptr = ldap_avl_find( CfOcTree, &co, CfOc_cmp );
+ if ( coptr == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no structural objectClass in configuration table\n",
+ log_prefix, e->e_name.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* Only the root can be Cft_Global, everything else must
+ * have a parent. Only limited nesting arrangements are allowed.
+ */
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( coptr->co_type == Cft_Global && !last ) {
+ cfn = cfb->cb_config;
+ ca->ca_private = cfn;
+ ca->be = frontendDB; /* just to get past check_vals */
+ rc = LDAP_SUCCESS;
+ }
+
+ colst = count_ocs( oc_at, &nocs );
+
+ /* Check whether the Add is allowed by its parent, and do
+ * any necessary arg setup
+ */
+ if ( last ) {
+ rc = config_add_oc( &coptr, last, e, ca );
+ if ( rc == LDAP_CONSTRAINT_VIOLATION ) {
+ for ( i = 0; i<nocs; i++ ) {
+ /* Already checked these */
+ if ( colst[i]->co_oc->soc_kind == LDAP_SCHEMA_STRUCTURAL )
+ continue;
+ if ( colst[i]->co_ldadd &&
+ ( rc = colst[i]->co_ldadd( last, e, ca ))
+ != LDAP_CONSTRAINT_VIOLATION ) {
+ coptr = colst[i];
+ break;
+ }
+ }
+ }
+ if ( rc == LDAP_CONSTRAINT_VIOLATION ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: config_add_internal: "
+ "DN=\"%s\" no structural objectClass add function\n",
+ log_prefix, e->e_name.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ }
+
+ /* Add the entry but don't parse it, we already have its contents */
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rc = LDAP_SUCCESS;
+ goto ok;
+ }
+
+ if ( rc != LDAP_SUCCESS )
+ goto done_noop;
+
+ /* Parse all the values and check for simple syntax errors before
+ * performing any set actions.
+ *
+ * If doing an LDAPadd, check for indexed names and any necessary
+ * renaming/renumbering. Entries that don't need indexed names are
+ * ignored. Entries that need an indexed name and arrive without one
+ * are assigned to the end. Entries that arrive with an index may
+ * cause the following entries to be renumbered/bumped down.
+ *
+ * Note that "pseudo-indexed" entries (cn=Include{xx}, cn=Module{xx})
+ * don't allow Adding an entry with an index that's already in use.
+ * This is flagged as an error (LDAP_ALREADY_EXISTS) up above.
+ *
+ * These entries can have auto-assigned indexes (appended to the end)
+ * but only the other types support auto-renumbering of siblings.
+ */
+ {
+ rc = check_name_index( last, coptr->co_type, e, rs, renum,
+ &ibase );
+ if ( rc ) {
+ goto done_noop;
+ }
+ if ( renum && *renum && coptr->co_type != Cft_Database &&
+ coptr->co_type != Cft_Overlay )
+ {
+ snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "operation requires sibling renumbering" );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done_noop;
+ }
+ }
+
+ init_config_argv( ca );
+
+ /* Make sure we process attrs in the required order */
+ sort_attrs( e, colst, nocs );
+
+ for ( a = e->e_attrs; a; a = a->a_next ) {
+ if ( a == oc_at ) continue;
+ ct = config_find_table( colst, nocs, a->a_desc, ca );
+ if ( !ct ) continue; /* user data? */
+ rc = check_vals( ct, ca, a, 1 );
+ if ( rc ) goto done_noop;
+ }
+
+ /* Basic syntax checks are OK. Do the actual settings. */
+ for ( a=e->e_attrs; a; a=a->a_next ) {
+ if ( a == oc_at ) continue;
+ ct = config_find_table( colst, nocs, a->a_desc, ca );
+ if ( !ct ) continue; /* user data? */
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ char *iptr = NULL;
+ ca->valx = -1;
+ ca->line = a->a_vals[i].bv_val;
+ ca->linelen = a->a_vals[i].bv_len;
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+ ptr = strchr( ca->line, '}' );
+ if ( ptr ) {
+ iptr = strchr( ca->line, '{' );
+ ca->linelen -= (ptr+1) - ca->line;
+ ca->line = ptr+1;
+ }
+ }
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED_SIB ) {
+ if ( iptr ) {
+ ca->valx = strtol( iptr+1, NULL, 0 );
+ }
+ } else {
+ ca->valx = i;
+ }
+ rc = config_parse_add( ct, ca, i );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+ }
+ok:
+ /* Newly added databases and overlays need to be started up */
+ if ( CONFIG_ONLINE_ADD( ca )) {
+ if ( coptr->co_type == Cft_Database ) {
+ rc = backend_startup_one( ca->be, &ca->reply );
+
+ } else if ( coptr->co_type == Cft_Backend ) {
+ if ( ca->bi->bi_open ) {
+ rc = ca->bi->bi_open( ca->bi );
+ }
+
+ } else if ( coptr->co_type == Cft_Overlay ) {
+ if ( ca->bi->bi_db_open ) {
+ BackendInfo *bi_orig = ca->be->bd_info;
+ ca->be->bd_info = ca->bi;
+ rc = ca->bi->bi_db_open( ca->be, &ca->reply );
+ ca->be->bd_info = bi_orig;
+ }
+ } else if ( ca->num_cleanups ) {
+ rc = config_run_cleanup( ca );
+ }
+ if ( rc ) {
+ if (ca->cr_msg[0] == '\0')
+ snprintf( ca->cr_msg, sizeof( ca->cr_msg ), "<%s> failed startup", ca->argv[0] );
+
+ Debug(LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ ca->log, ca->cr_msg, ca->argv[1] );
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+ ca->valx = ibase;
+ ce = ch_calloc( 1, sizeof(CfEntryInfo) );
+ ce->ce_parent = last;
+ ce->ce_entry = entry_dup( e );
+ ce->ce_entry->e_private = ce;
+ ce->ce_type = coptr->co_type;
+ ce->ce_be = ca->be;
+ ce->ce_bi = ca->bi;
+ ce->ce_private = ca->ca_private;
+ ca->ca_entry = ce->ce_entry;
+ if ( !last ) {
+ cfb->cb_root = ce;
+ } else if ( last->ce_kids ) {
+ CfEntryInfo *c2, **cprev;
+
+ /* Advance to first of this type */
+ cprev = &last->ce_kids;
+ for ( c2 = *cprev; c2 && c2->ce_type < ce->ce_type; ) {
+ cprev = &c2->ce_sibs;
+ c2 = c2->ce_sibs;
+ }
+ /* Account for the (-1) frontendDB entry */
+ if ( ce->ce_type == Cft_Database ) {
+ if ( ca->be == frontendDB )
+ ibase = 0;
+ else if ( ibase != -1 )
+ ibase++;
+ }
+ /* Append */
+ if ( ibase < 0 ) {
+ for (c2 = *cprev; c2 && c2->ce_type == ce->ce_type;) {
+ cprev = &c2->ce_sibs;
+ c2 = c2->ce_sibs;
+ }
+ } else {
+ /* Insert */
+ int i;
+ for ( i=0; i<ibase; i++ ) {
+ c2 = *cprev;
+ cprev = &c2->ce_sibs;
+ }
+ }
+ ce->ce_sibs = *cprev;
+ *cprev = ce;
+ } else {
+ last->ce_kids = ce;
+ }
+
+done:
+ if ( rc ) {
+ if ( (coptr->co_type == Cft_Database) && ca->be ) {
+ if ( ca->be != frontendDB && ca->be != configDB )
+ backend_destroy_one( ca->be, 1 );
+ } else if ( (coptr->co_type == Cft_Overlay) && ca->bi ) {
+ overlay_destroy_one( ca->be, (slap_overinst *)ca->bi );
+ } else if ( coptr->co_type == Cft_Schema ) {
+ schema_destroy_one( ca, colst, nocs, last );
+ } else if ( ca->num_cleanups ) {
+ ca->reply.err = rc;
+ config_run_cleanup( ca );
+ }
+ }
+done_noop:
+
+ ch_free( ca->argv );
+ if ( colst ) ch_free( colst );
+ return rc;
+}
+
+#define BIGTMP 10000
+static int
+config_rename_add( Operation *op, SlapReply *rs, CfEntryInfo *ce,
+ int base, int rebase, int max, int use_ldif )
+{
+ CfEntryInfo *ce2, *ce3, *cetmp = NULL, *cerem = NULL;
+ ConfigType etype = ce->ce_type;
+ int count = 0, rc = 0;
+
+ /* Reverse ce list */
+ for (ce2 = ce->ce_sibs;ce2;ce2 = ce3) {
+ if (ce2->ce_type != etype) {
+ cerem = ce2;
+ break;
+ }
+ ce3 = ce2->ce_sibs;
+ ce2->ce_sibs = cetmp;
+ cetmp = ce2;
+ count++;
+ if ( max && count >= max ) {
+ cerem = ce3;
+ break;
+ }
+ }
+
+ /* Move original to a temp name until increments are done */
+ if ( rebase ) {
+ ce->ce_entry->e_private = NULL;
+ rc = config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry,
+ base+BIGTMP, 0, use_ldif );
+ ce->ce_entry->e_private = ce;
+ }
+ /* start incrementing */
+ for (ce2=cetmp; ce2; ce2=ce3) {
+ ce3 = ce2->ce_sibs;
+ ce2->ce_sibs = cerem;
+ cerem = ce2;
+ if ( rc == 0 )
+ rc = config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry,
+ count+base, 0, use_ldif );
+ count--;
+ }
+ if ( rebase )
+ rc = config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry,
+ base, 0, use_ldif );
+ return rc;
+}
+
+static int
+config_rename_del( Operation *op, SlapReply *rs, CfEntryInfo *ce,
+ CfEntryInfo *ce2, int old, int use_ldif )
+{
+ int count = 0;
+
+ /* Renumber original to a temp value */
+ ce->ce_entry->e_private = NULL;
+ config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry,
+ old+BIGTMP, 0, use_ldif );
+ ce->ce_entry->e_private = ce;
+
+ /* start decrementing */
+ for (; ce2 != ce; ce2=ce2->ce_sibs) {
+ config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry,
+ count+old, 0, use_ldif );
+ count++;
+ }
+ return config_renumber_one( op, rs, ce->ce_parent, ce->ce_entry,
+ count+old, 0, use_ldif );
+}
+
+/* Parse an LDAP entry into config directives, then store in underlying
+ * database.
+ */
+static int
+config_back_add( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ int renumber, dopause = 1;
+ ConfigArgs ca;
+
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ if ( !access_allowed( op, op->ora_e, slap_schema.si_ad_entry,
+ NULL, ACL_WADD, NULL )) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto out;
+ }
+
+ /*
+ * Check for attribute ACL
+ */
+ if ( !acl_check_modlist( op, op->ora_e, op->orm_modlist )) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "no write access to attribute";
+ goto out;
+ }
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ /* add opattrs for syncprov */
+ {
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof textbuf;
+ rs->sr_err = entry_schema_check(op, op->ora_e, NULL, 0, 1, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS )
+ goto out;
+ rs->sr_err = slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ LDAP_XSTRING(config_back_add) ": entry failed op attrs add: "
+ "%s (%d)\n", rs->sr_text, rs->sr_err );
+ goto out;
+ }
+ }
+
+ /*
+ * ITS#10045 Pre-check for abandon but be willing to handle that the
+ * operation might be abandoned while waiting for the server to pause.
+ */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ dopause = 0;
+ goto out;
+ }
+ if ( slap_pause_server() < 0 )
+ dopause = 0;
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ goto unpause;
+ }
+
+ ldap_pvt_thread_rdwr_wlock( &cfb->cb_rwlock );
+
+ /* Strategy:
+ * 1) check for existence of entry
+ * 2) check for sibling renumbering
+ * 3) perform internal add
+ * 4) perform any necessary renumbering
+ * 5) store entry in underlying database
+ */
+ rs->sr_err = config_add_internal( cfb, op->ora_e, &ca, rs, &renumber, op );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = ca.cr_msg;
+ goto out2;
+ }
+
+ if ( renumber ) {
+ CfEntryInfo *ce = ca.ca_entry->e_private;
+ req_add_s addr = op->oq_add;
+ op->o_tag = LDAP_REQ_MODRDN;
+ rs->sr_err = config_rename_add( op, rs, ce, ca.valx, 0, 0, cfb->cb_use_ldif );
+ op->o_tag = LDAP_REQ_ADD;
+ op->oq_add = addr;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto out2;
+ }
+ }
+
+ if ( cfb->cb_use_ldif ) {
+ BackendDB *be = op->o_bd;
+ slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp;
+ struct berval dn, ndn;
+
+ op->o_bd = &cfb->cb_db;
+
+ /* Save current rootdn; use the underlying DB's rootdn */
+ dn = op->o_dn;
+ ndn = op->o_ndn;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ scp = op->o_callback;
+ op->o_callback = &sc;
+ op->o_bd->be_add( op, rs );
+ op->o_bd = be;
+ op->o_callback = scp;
+ op->o_dn = dn;
+ op->o_ndn = ndn;
+ } else if ( op->o_postread ) {
+ if ( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, op->ora_e,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_add: "
+ "post-read failed \"%s\"\n",
+ op->ora_e->e_name.bv_val );
+ if ( op->o_postread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ }
+ }
+ }
+
+out2:;
+ ldap_pvt_thread_rdwr_wunlock( &cfb->cb_rwlock );
+
+unpause:;
+ if ( dopause )
+ slap_unpause_server();
+
+out:;
+ { int repl = op->o_dont_replicate;
+ if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ rs->sr_text = NULL; /* Set after config_add_internal */
+ rs->sr_err = LDAP_SUCCESS;
+ op->o_dont_replicate = 1;
+ }
+ send_ldap_result( op, rs );
+ op->o_dont_replicate = repl;
+ }
+ slap_graduate_commit_csn( op );
+ return rs->sr_err;
+}
+
+typedef struct delrec {
+ struct delrec *next;
+ int nidx;
+ int idx[1];
+} delrec;
+
+static int
+config_modify_add( ConfigTable *ct, ConfigArgs *ca, AttributeDescription *ad,
+ int i )
+{
+ int rc;
+
+ ca->valx = -1;
+ if (ad->ad_type->sat_flags & SLAP_AT_ORDERED &&
+ ca->line[0] == '{' )
+ {
+ char *ptr = strchr( ca->line + 1, '}' );
+ if ( ptr ) {
+ char *next;
+
+ ca->valx = strtol( ca->line + 1, &next, 0 );
+ if ( next == ca->line + 1 || next[ 0 ] != '}' ) {
+ return LDAP_OTHER;
+ }
+ ca->linelen -= (ptr+1) - ca->line;
+ ca->line = ptr+1;
+ }
+ }
+ rc = config_parse_add( ct, ca, i );
+ if ( rc ) {
+ rc = LDAP_OTHER;
+ }
+ return rc;
+}
+
+static int
+config_modify_internal( CfEntryInfo *ce, Operation *op, SlapReply *rs,
+ ConfigArgs *ca )
+{
+ int rc = LDAP_UNWILLING_TO_PERFORM;
+ Modifications *ml;
+ Entry *e = ce->ce_entry;
+ Attribute *save_attrs = e->e_attrs, *oc_at, *s, *a;
+ ConfigTable *ct;
+ ConfigOCs **colst;
+ int i, nocs;
+ char *ptr;
+ delrec *dels = NULL, *deltail = NULL;
+
+ oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass );
+ if ( !oc_at ) return LDAP_OBJECT_CLASS_VIOLATION;
+
+ for (ml = op->orm_modlist; ml; ml=ml->sml_next) {
+ if (ml->sml_desc == slap_schema.si_ad_objectClass) {
+ /* We'd be fine comparing the structural objectclass before and
+ * after, but AUXILIARY ocs exist so we have to check them all */
+ unsigned int i, j;
+
+ if ( ml->sml_numvals != oc_at->a_numvals ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "objectclass modification disallowed" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ for ( i = 0; i < oc_at->a_numvals; i++ ) {
+ ObjectClass *new_oc, *old_oc = oc_bvfind( &oc_at->a_vals[i] );
+ int found = 0;
+
+ if ( old_oc == NULL ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "no objectClass named %s",
+ oc_at->a_vals[i].bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ for ( j = 0; j < ml->sml_numvals; j++ ) {
+ new_oc = oc_bvfind( &ml->sml_values[j] );
+ if ( new_oc == NULL ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "no objectClass named %s",
+ ml->sml_values[j].bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if ( old_oc == new_oc ) {
+ found = 1;
+ break;
+ }
+ }
+
+ if ( !found ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "objectclass modification disallowed" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ }
+ }
+ }
+
+ colst = count_ocs( oc_at, &nocs );
+
+ /* make sure add/del flags are clear; should always be true */
+ for ( s = save_attrs; s; s = s->a_next ) {
+ s->a_flags &= ~(SLAP_ATTR_IXADD|SLAP_ATTR_IXDEL);
+ }
+
+ e->e_attrs = attrs_dup( e->e_attrs );
+
+ init_config_argv( ca );
+ ca->be = ce->ce_be;
+ ca->bi = ce->ce_bi;
+ ca->ca_private = ce->ce_private;
+ ca->ca_entry = e;
+ ca->fname = "slapd";
+ ca->ca_op = op;
+ strcpy( ca->log, "back-config" );
+
+ for (ml = op->orm_modlist; ml; ml=ml->sml_next) {
+ ct = config_find_table( colst, nocs, ml->sml_desc, ca );
+ switch (ml->sml_op) {
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ case SLAP_MOD_SOFTDEL:
+ {
+ BerVarray vals = NULL, nvals = NULL;
+ int *idx = NULL;
+ if ( ct && ( ct->arg_type & ARG_NO_DELETE )) {
+ rc = LDAP_OTHER;
+ snprintf(ca->cr_msg, sizeof(ca->cr_msg), "cannot delete %s",
+ ml->sml_desc->ad_cname.bv_val );
+ goto out_noop;
+ }
+ if ( ml->sml_op == LDAP_MOD_REPLACE ) {
+ vals = ml->sml_values;
+ nvals = ml->sml_nvalues;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ }
+ /* If we're deleting by values, remember the indexes of the
+ * values we deleted.
+ */
+ if ( ct && ml->sml_values ) {
+ delrec *d;
+ i = ml->sml_numvals;
+ d = ch_malloc( sizeof(delrec) + (i - 1)* sizeof(int));
+ d->nidx = i;
+ d->next = NULL;
+ if ( dels ) {
+ deltail->next = d;
+ } else {
+ dels = d;
+ }
+ deltail = d;
+ idx = d->idx;
+ }
+ rc = modify_delete_vindex(e, &ml->sml_mod,
+ get_permissiveModify(op),
+ &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg), idx );
+ if ( ml->sml_op == LDAP_MOD_REPLACE ) {
+ ml->sml_values = vals;
+ ml->sml_nvalues = nvals;
+ }
+ if ( rc == LDAP_NO_SUCH_ATTRIBUTE && ml->sml_op == SLAP_MOD_SOFTDEL )
+ {
+ rc = LDAP_SUCCESS;
+ }
+ /* FIXME: check rc before fallthru? */
+ if ( !vals )
+ break;
+ }
+ /* FALLTHRU: LDAP_MOD_REPLACE && vals */
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( ml->sml_op == SLAP_MOD_ADD_IF_NOT_PRESENT
+ && attr_find( e->e_attrs, ml->sml_desc ) )
+ {
+ rc = LDAP_SUCCESS;
+ break;
+ }
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: {
+ int mop = ml->sml_op;
+ int navals = -1;
+ ml->sml_op = LDAP_MOD_ADD;
+ if ( ct ) {
+ if ( ct->arg_type & ARG_NO_INSERT ) {
+ Attribute *a = attr_find( e->e_attrs, ml->sml_desc );
+ if ( a ) {
+ navals = a->a_numvals;
+ }
+ }
+ for ( i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++ ) {
+ if ( ml->sml_values[i].bv_val[0] == '{' &&
+ navals >= 0 )
+ {
+ char *next, *val = ml->sml_values[i].bv_val + 1;
+ int j;
+
+ j = strtol( val, &next, 0 );
+ if ( next == val || next[ 0 ] != '}' || j < navals ) {
+ rc = LDAP_OTHER;
+ snprintf(ca->cr_msg, sizeof(ca->cr_msg), "cannot insert %s",
+ ml->sml_desc->ad_cname.bv_val );
+ goto out_noop;
+ }
+ }
+ rc = check_vals( ct, ca, ml, 0 );
+ if ( rc ) goto out_noop;
+ }
+ }
+ rc = modify_add_values(e, &ml->sml_mod,
+ get_permissiveModify(op),
+ &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg) );
+
+ /* If value already exists, show success here
+ * and ignore this operation down below.
+ */
+ if ( mop == SLAP_MOD_SOFTADD ) {
+ if ( rc == LDAP_TYPE_OR_VALUE_EXISTS )
+ rc = LDAP_SUCCESS;
+ else
+ mop = LDAP_MOD_ADD;
+ }
+ ml->sml_op = mop;
+ break;
+ }
+
+ break;
+ case LDAP_MOD_INCREMENT: /* FIXME */
+ break;
+ default:
+ break;
+ }
+ if(rc != LDAP_SUCCESS) break;
+ }
+
+ if ( rc == LDAP_SUCCESS) {
+ /* check that the entry still obeys the schema */
+ rc = entry_schema_check(op, e, NULL, 0, 0, NULL,
+ &rs->sr_text, ca->cr_msg, sizeof(ca->cr_msg) );
+ }
+ if ( rc ) goto out_noop;
+
+ /* Basic syntax checks are OK. Do the actual settings. */
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ ct = config_find_table( colst, nocs, ml->sml_desc, ca );
+ if ( !ct ) continue;
+
+ s = attr_find( save_attrs, ml->sml_desc );
+ a = attr_find( e->e_attrs, ml->sml_desc );
+
+ switch (ml->sml_op) {
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE: {
+ BerVarray vals = NULL, nvals = NULL;
+ delrec *d = NULL;
+
+ if ( ml->sml_op == LDAP_MOD_REPLACE ) {
+ vals = ml->sml_values;
+ nvals = ml->sml_nvalues;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ }
+
+ if ( ml->sml_values )
+ d = dels;
+
+ /* If we didn't delete the whole attribute */
+ if ( ml->sml_values && a ) {
+ struct berval *mvals;
+ int j;
+
+ if ( ml->sml_nvalues )
+ mvals = ml->sml_nvalues;
+ else
+ mvals = ml->sml_values;
+
+ /* use the indexes we saved up above */
+ for (i=0; i < d->nidx; i++) {
+ struct berval bv = *mvals++;
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED &&
+ bv.bv_val[0] == '{' ) {
+ ptr = strchr( bv.bv_val, '}' ) + 1;
+ bv.bv_len -= ptr - bv.bv_val;
+ bv.bv_val = ptr;
+ }
+ ca->line = bv.bv_val;
+ ca->linelen = bv.bv_len;
+ ca->valx = d->idx[i];
+ config_parse_vals(ct, ca, d->idx[i] );
+ rc = config_del_vals( ct, ca );
+ if ( rc != LDAP_SUCCESS ) break;
+ if ( s )
+ s->a_flags |= SLAP_ATTR_IXDEL;
+ for (j=i+1; j < d->nidx; j++)
+ if ( d->idx[j] >d->idx[i] )
+ d->idx[j]--;
+ }
+ } else {
+ ca->valx = -1;
+ ca->line = NULL;
+ ca->argc = 1;
+ rc = config_del_vals( ct, ca );
+ if ( rc ) rc = LDAP_OTHER;
+ if ( s )
+ s->a_flags |= SLAP_ATTR_IXDEL;
+ }
+ if ( ml->sml_values ) {
+ d = d->next;
+ ch_free( dels );
+ dels = d;
+ }
+ if ( ml->sml_op == LDAP_MOD_REPLACE ) {
+ ml->sml_values = vals;
+ ml->sml_nvalues = nvals;
+ }
+ if ( !vals || rc != LDAP_SUCCESS )
+ break;
+ }
+ /* FALLTHRU: LDAP_MOD_REPLACE && vals */
+
+ case LDAP_MOD_ADD:
+ if ( !a )
+ break;
+ for (i=0; ml->sml_values[i].bv_val; i++) {
+ ca->line = ml->sml_values[i].bv_val;
+ ca->linelen = ml->sml_values[i].bv_len;
+ ca->valx = -1;
+ rc = config_modify_add( ct, ca, ml->sml_desc, i );
+ if ( rc )
+ goto out;
+ a->a_flags |= SLAP_ATTR_IXADD;
+ }
+ break;
+ }
+ }
+
+ /* Apply pending changes */
+ if ( rc == LDAP_SUCCESS && ca->num_cleanups ) {
+ rc = config_run_cleanup( ca );
+ }
+
+out:
+ /* Undo for a failed operation */
+ if ( rc != LDAP_SUCCESS ) {
+ ConfigReply msg = ca->reply;
+ for ( s = save_attrs; s; s = s->a_next ) {
+ if ( s->a_flags & SLAP_ATTR_IXDEL ) {
+ s->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD);
+ ct = config_find_table( colst, nocs, s->a_desc, ca );
+ a = attr_find( e->e_attrs, s->a_desc );
+ if ( a ) {
+ /* clear the flag so the add check below will skip it */
+ a->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD);
+ ca->valx = -1;
+ ca->line = NULL;
+ ca->argc = 1;
+ config_del_vals( ct, ca );
+ }
+ for ( i=0; !BER_BVISNULL( &s->a_vals[i] ); i++ ) {
+ ca->line = s->a_vals[i].bv_val;
+ ca->linelen = s->a_vals[i].bv_len;
+ ca->valx = -1;
+ config_modify_add( ct, ca, s->a_desc, i );
+ }
+ }
+ }
+ for ( a = e->e_attrs; a; a = a->a_next ) {
+ if ( a->a_flags & SLAP_ATTR_IXADD ) {
+ ct = config_find_table( colst, nocs, a->a_desc, ca );
+ ca->valx = -1;
+ ca->line = NULL;
+ ca->argc = 1;
+ config_del_vals( ct, ca );
+ s = attr_find( save_attrs, a->a_desc );
+ if ( s ) {
+ s->a_flags &= ~(SLAP_ATTR_IXDEL|SLAP_ATTR_IXADD);
+ for ( i=0; !BER_BVISNULL( &s->a_vals[i] ); i++ ) {
+ ca->line = s->a_vals[i].bv_val;
+ ca->linelen = s->a_vals[i].bv_len;
+ ca->valx = -1;
+ config_modify_add( ct, ca, s->a_desc, i );
+ }
+ }
+ }
+ }
+ if ( ca->num_cleanups ) {
+ ca->reply.err = rc;
+ config_run_cleanup( ca );
+ }
+ ca->reply = msg;
+ }
+out_noop:
+ if ( rc == LDAP_SUCCESS ) {
+ attrs_free( save_attrs );
+ rs->sr_text = NULL;
+ } else {
+ attrs_free( e->e_attrs );
+ e->e_attrs = save_attrs;
+ }
+ ch_free( ca->argv );
+ if ( colst ) ch_free( colst );
+ while( dels ) {
+ deltail = dels->next;
+ ch_free( dels );
+ dels = deltail;
+ }
+
+ return rc;
+}
+
+static int
+config_back_modify( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ Modifications *ml;
+ ConfigArgs ca = {0};
+ struct berval rdn;
+ char *ptr;
+ AttributeDescription *rad = NULL;
+ int do_pause = 1;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last, op );
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto out;
+ }
+
+ if ( !acl_check_modlist( op, ce->ce_entry, op->orm_modlist )) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto out;
+ }
+
+ /* Get type of RDN */
+ rdn = ce->ce_entry->e_nname;
+ ptr = strchr( rdn.bv_val, '=' );
+ rdn.bv_len = ptr - rdn.bv_val;
+ rs->sr_err = slap_bv2ad( &rdn, &rad, &rs->sr_text );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto out;
+ }
+
+ /* Some basic validation... */
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ /* Don't allow Modify of RDN; must use ModRdn for that. */
+ if ( ml->sml_desc == rad ) {
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_RDN;
+ rs->sr_text = "Use modrdn to change the entry name";
+ goto out;
+ }
+ /* Internal update of contextCSN? */
+ if ( ml->sml_desc == slap_schema.si_ad_contextCSN && op->o_conn->c_conn_idx == -1 ) {
+ do_pause = 0;
+ break;
+ }
+ }
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+
+ /* If we have a backend, it will handle the control */
+ if ( !cfb->cb_use_ldif && op->o_preread ) {
+ if ( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, ce->ce_entry,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_modify: "
+ "pre-read failed \"%s\"\n",
+ ce->ce_entry->e_name.bv_val );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto out;
+ }
+ }
+ }
+
+ if ( do_pause ) {
+ /*
+ * ITS#10045 Pre-check for abandon but be willing to handle that the
+ * operation might be abandoned while waiting for the server to pause.
+ */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ do_pause = 0;
+ goto out;
+ }
+ if ( slap_pause_server() < 0 )
+ do_pause = 0;
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ goto unpause;
+ }
+ }
+ ldap_pvt_thread_rdwr_wlock( &cfb->cb_rwlock );
+
+ /* Strategy:
+ * 1) perform the Modify on the cached Entry.
+ * 2) verify that the Entry still satisfies the schema.
+ * 3) perform the individual config operations.
+ * 4) store Modified entry in underlying LDIF backend.
+ */
+ rs->sr_err = config_modify_internal( ce, op, rs, &ca );
+ if ( rs->sr_err ) {
+ rs->sr_text = ca.cr_msg;
+ } else if ( cfb->cb_use_ldif ) {
+ BackendDB *be = op->o_bd;
+ slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp;
+ struct berval dn, ndn;
+
+ op->o_bd = &cfb->cb_db;
+
+ dn = op->o_dn;
+ ndn = op->o_ndn;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ scp = op->o_callback;
+ op->o_callback = &sc;
+ op->o_bd->be_modify( op, rs );
+ op->o_bd = be;
+ op->o_callback = scp;
+ op->o_dn = dn;
+ op->o_ndn = ndn;
+ } else if ( op->o_postread ) {
+ if ( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, ce->ce_entry,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_modify: "
+ "post-read failed \"%s\"\n",
+ ce->ce_entry->e_name.bv_val );
+ }
+ }
+
+ ldap_pvt_thread_rdwr_wunlock( &cfb->cb_rwlock );
+unpause:;
+ if ( do_pause )
+ slap_unpause_server();
+out:
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+ return rs->sr_err;
+}
+
+static int
+config_back_modrdn( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ struct berval rdn;
+ int ixold, ixnew, dopause = 1;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl **postread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last, op );
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto out;
+ }
+ if ( !access_allowed( op, ce->ce_entry, slap_schema.si_ad_entry,
+ NULL, ACL_WRITE, NULL )) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto out;
+ }
+ { Entry *parent;
+ if ( ce->ce_parent )
+ parent = ce->ce_parent->ce_entry;
+ else
+ parent = (Entry *)&slap_entry_root;
+ if ( !access_allowed( op, parent, slap_schema.si_ad_children,
+ NULL, ACL_WRITE, NULL )) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto out;
+ }
+ }
+
+ /* We don't allow moving objects to new parents.
+ * Generally we only allow reordering a set of ordered entries.
+ */
+ if ( op->orr_newSup ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+
+ /* If newRDN == oldRDN, quietly succeed */
+ dnRdn( &op->o_req_ndn, &rdn );
+ if ( dn_match( &rdn, &op->orr_nnewrdn )) {
+ rs->sr_err = LDAP_SUCCESS;
+ goto out;
+ }
+
+ /* Current behavior, subject to change as needed:
+ *
+ * For backends and overlays, we only allow renumbering.
+ * For schema, we allow renaming with the same number.
+ * Otherwise, the op is not allowed.
+ */
+
+ if ( ce->ce_type == Cft_Schema ) {
+ char *ptr1, *ptr2;
+ int len;
+
+ /* Can't alter the main cn=schema entry */
+ if ( ce->ce_parent->ce_type == Cft_Global ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "renaming not allowed for this entry";
+ goto out;
+ }
+
+ /* We could support this later if desired */
+ ptr1 = ber_bvchr( &rdn, '}' );
+ ptr2 = ber_bvchr( &op->orr_newrdn, '}' );
+ len = ptr1 - rdn.bv_val;
+ if ( len != ptr2 - op->orr_newrdn.bv_val ||
+ strncmp( rdn.bv_val, op->orr_newrdn.bv_val, len )) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "schema reordering not supported";
+ goto out;
+ }
+ } else if ( ce->ce_type == Cft_Database ||
+ ce->ce_type == Cft_Overlay ) {
+ char *ptr1, *ptr2, *iptr1, *iptr2;
+ int len1, len2;
+
+ iptr2 = ber_bvchr( &op->orr_newrdn, '=' ) + 1;
+ if ( *iptr2 != '{' ) {
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ rs->sr_text = "new ordering index is required";
+ goto out;
+ }
+ iptr2++;
+ iptr1 = ber_bvchr( &rdn, '{' ) + 1;
+ ptr1 = ber_bvchr( &rdn, '}' );
+ ptr2 = ber_bvchr( &op->orr_newrdn, '}' );
+ if ( !ptr2 ) {
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ rs->sr_text = "new ordering index is required";
+ goto out;
+ }
+
+ len1 = ptr1 - rdn.bv_val;
+ len2 = ptr2 - op->orr_newrdn.bv_val;
+
+ if ( rdn.bv_len - len1 != op->orr_newrdn.bv_len - len2 ||
+ strncmp( ptr1, ptr2, rdn.bv_len - len1 )) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "changing database/overlay type not allowed";
+ goto out;
+ }
+ ixold = strtol( iptr1, NULL, 0 );
+ ixnew = strtol( iptr2, &ptr1, 0 );
+ if ( ptr1 != ptr2 || ixold < 0 || ixnew < 0 ) {
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ goto out;
+ }
+ /* config DB is always 0, cannot be changed */
+ if ( ce->ce_type == Cft_Database && ( ixold == 0 || ixnew == 0 )) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ goto out;
+ }
+ } else {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "renaming not supported for this entry";
+ goto out;
+ }
+
+ /* If we have a backend, it will handle the control */
+ if ( !cfb->cb_use_ldif && op->o_preread ) {
+ if ( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, ce->ce_entry,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_modrdn: "
+ "pre-read failed \"%s\"\n",
+ ce->ce_entry->e_name.bv_val );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto out;
+ }
+ }
+ }
+
+ /*
+ * ITS#10045 Pre-check for abandon but be willing to handle that the
+ * operation might be abandoned while waiting for the server to pause.
+ */
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ dopause = 0;
+ goto out;
+ }
+ if ( slap_pause_server() < 0 )
+ dopause = 0;
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ goto unpause;
+ }
+
+ ldap_pvt_thread_rdwr_wlock( &cfb->cb_rwlock );
+
+ if ( ce->ce_type == Cft_Schema ) {
+ req_modrdn_s modr = op->oq_modrdn;
+ struct berval rdn;
+ Attribute *a;
+ rs->sr_err = config_rename_attr( rs, ce->ce_entry, &rdn, &a );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = config_rename_one( op, rs, ce->ce_entry,
+ ce->ce_parent, a, &op->orr_newrdn, &op->orr_nnewrdn,
+ cfb->cb_use_ldif );
+ }
+ op->oq_modrdn = modr;
+ } else {
+ CfEntryInfo *ce2, **cprev, **cbprev, *ceold;
+ req_modrdn_s modr = op->oq_modrdn;
+ int i;
+
+ /* Advance to first of this type */
+ cprev = &ce->ce_parent->ce_kids;
+ for ( ce2 = *cprev; ce2 && ce2->ce_type != ce->ce_type; ) {
+ cprev = &ce2->ce_sibs;
+ ce2 = ce2->ce_sibs;
+ }
+ /* Skip the -1 entry */
+ if ( ce->ce_type == Cft_Database ) {
+ cprev = &ce2->ce_sibs;
+ ce2 = ce2->ce_sibs;
+ }
+ cbprev = cprev;
+
+ /* Remove from old slot */
+ for ( ce2 = *cprev; ce2 && ce2 != ce; ce2 = ce2->ce_sibs )
+ cprev = &ce2->ce_sibs;
+ *cprev = ce->ce_sibs;
+ ceold = ce->ce_sibs;
+
+ /* Insert into new slot */
+ cprev = cbprev;
+ for ( i=0; i<ixnew; i++ ) {
+ ce2 = *cprev;
+ if ( !ce2 )
+ break;
+ cprev = &ce2->ce_sibs;
+ }
+ ce->ce_sibs = *cprev;
+ *cprev = ce;
+
+ ixnew = i;
+
+ /* NOTE: These should be encoded in the OC tables, not inline here */
+ if ( ce->ce_type == Cft_Database )
+ backend_db_move( ce->ce_be, ixnew );
+ else if ( ce->ce_type == Cft_Overlay )
+ overlay_move( ce->ce_be, (slap_overinst *)ce->ce_bi, ixnew );
+
+ if ( ixold < ixnew ) {
+ rs->sr_err = config_rename_del( op, rs, ce, ceold, ixold,
+ cfb->cb_use_ldif );
+ } else {
+ rs->sr_err = config_rename_add( op, rs, ce, ixnew, 1,
+ ixold - ixnew, cfb->cb_use_ldif );
+ }
+ op->oq_modrdn = modr;
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS && !cfb->cb_use_ldif && op->o_postread ) {
+ if ( postread_ctrl == NULL ) {
+ postread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, ce->ce_entry,
+ &slap_post_read_bv, postread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_modrdn: "
+ "post-read failed \"%s\"\n",
+ ce->ce_entry->e_name.bv_val );
+ }
+ }
+
+ ldap_pvt_thread_rdwr_wunlock( &cfb->cb_rwlock );
+
+unpause:
+ if ( dopause )
+ slap_unpause_server();
+out:
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+static int
+config_back_delete( Operation *op, SlapReply *rs )
+{
+#ifdef SLAP_CONFIG_DELETE
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last, *ce2;
+ int dopause = 1;
+
+ LDAPControl **preread_ctrl = NULL;
+ LDAPControl *ctrls[SLAP_MAX_RESPONSE_CONTROLS];
+ int num_ctrls = 0;
+
+ ctrls[num_ctrls] = NULL;
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ /* If we have a backend, it will handle the control */
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last, op );
+ if ( ce && !cfb->cb_use_ldif && op->o_preread ) {
+ if ( preread_ctrl == NULL ) {
+ preread_ctrl = &ctrls[num_ctrls++];
+ ctrls[num_ctrls] = NULL;
+ }
+ if ( slap_read_controls( op, rs, ce->ce_entry,
+ &slap_pre_read_bv, preread_ctrl ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "config_back_delete: "
+ "pre-read failed \"%s\"\n",
+ ce->ce_entry->e_name.bv_val );
+ if ( op->o_preread & SLAP_CONTROL_CRITICAL ) {
+ /* FIXME: is it correct to abort
+ * operation if control fails? */
+ goto out;
+ }
+ }
+ }
+
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else if ( ce->ce_kids ) {
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ } else if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ } else if ( ce->ce_type == Cft_Overlay ||
+ ce->ce_type == Cft_Database ||
+ ce->ce_type == Cft_Misc ){
+ char *iptr;
+ int count, ixold;
+
+ if ( slap_pause_server() < 0 )
+ dopause = 0;
+
+ ldap_pvt_thread_rdwr_wlock( &cfb->cb_rwlock );
+
+ if ( op->o_abandon ) {
+ rs->sr_err = SLAPD_ABANDON;
+ goto out2;
+ }
+
+ if ( ce->ce_type == Cft_Overlay ){
+ overlay_remove( ce->ce_be, (slap_overinst *)ce->ce_bi, op );
+ } else if ( ce->ce_type == Cft_Misc ) {
+ /*
+ * only Cft_Misc objects that have a co_lddel handler set in
+ * the ConfigOCs struct can be deleted. This code also
+ * assumes that the entry can be only have one objectclass
+ * with co_type == Cft_Misc
+ */
+ ConfigOCs co, *coptr;
+ Attribute *oc_at;
+ int i;
+
+ oc_at = attr_find( ce->ce_entry->e_attrs,
+ slap_schema.si_ad_objectClass );
+ if ( !oc_at ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "objectclass not found";
+ goto out2;
+ }
+ for ( i=0; !BER_BVISNULL(&oc_at->a_nvals[i]); i++ ) {
+ co.co_name = &oc_at->a_nvals[i];
+ coptr = ldap_avl_find( CfOcTree, &co, CfOc_cmp );
+ if ( coptr == NULL || coptr->co_type != Cft_Misc ) {
+ continue;
+ }
+ if ( ! coptr->co_lddel || coptr->co_lddel( ce, op ) ){
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ if ( ! coptr->co_lddel ) {
+ rs->sr_text = "No delete handler found";
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ /* FIXME: We should return a helpful error message
+ * here */
+ }
+ goto out2;
+ }
+ break;
+ }
+ } else if (ce->ce_type == Cft_Database ) {
+ if ( ce->ce_be == frontendDB || ce->ce_be == op->o_bd ){
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Cannot delete config or frontend database";
+ goto out2;
+ }
+ if ( ce->ce_be->bd_info->bi_db_close ) {
+ ce->ce_be->bd_info->bi_db_close( ce->ce_be, NULL );
+ }
+ backend_destroy_one( ce->ce_be, 1);
+ }
+
+ /* remove CfEntryInfo from the siblings list */
+ if ( ce->ce_parent->ce_kids == ce ) {
+ ce->ce_parent->ce_kids = ce->ce_sibs;
+ } else {
+ for ( ce2 = ce->ce_parent->ce_kids ; ce2; ce2 = ce2->ce_sibs ) {
+ if ( ce2->ce_sibs == ce ) {
+ ce2->ce_sibs = ce->ce_sibs;
+ break;
+ }
+ }
+ }
+
+ /* remove from underlying database */
+ if ( cfb->cb_use_ldif ) {
+ BackendDB *be = op->o_bd;
+ slap_callback sc = { NULL, slap_null_cb, NULL, NULL }, *scp;
+ struct berval dn, ndn, req_dn, req_ndn;
+
+ op->o_bd = &cfb->cb_db;
+
+ dn = op->o_dn;
+ ndn = op->o_ndn;
+ req_dn = op->o_req_dn;
+ req_ndn = op->o_req_ndn;
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_req_dn = ce->ce_entry->e_name;
+ op->o_req_ndn = ce->ce_entry->e_nname;
+
+ scp = op->o_callback;
+ op->o_callback = &sc;
+ op->o_bd->be_delete( op, rs );
+ op->o_bd = be;
+ op->o_callback = scp;
+ op->o_dn = dn;
+ op->o_ndn = ndn;
+ op->o_req_dn = req_dn;
+ op->o_req_ndn = req_ndn;
+ }
+
+ /* renumber siblings */
+ iptr = ber_bvchr( &op->o_req_ndn, '{' ) + 1;
+ ixold = strtol( iptr, NULL, 0 );
+ for (ce2 = ce->ce_sibs, count=0; ce2; ce2=ce2->ce_sibs) {
+ config_renumber_one( op, rs, ce2->ce_parent, ce2->ce_entry,
+ count+ixold, 0, cfb->cb_use_ldif );
+ count++;
+ }
+
+ ce->ce_entry->e_private=NULL;
+ entry_free(ce->ce_entry);
+ ch_free(ce);
+out2:
+ ldap_pvt_thread_rdwr_wunlock( &cfb->cb_rwlock );
+ if ( dopause ) slap_unpause_server();
+ } else {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+out:
+ if ( num_ctrls ) rs->sr_ctrls = ctrls;
+#else
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+#endif /* SLAP_CONFIG_DELETE */
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+static int
+config_back_search( Operation *op, SlapReply *rs )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ slap_mask_t mask;
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ ldap_pvt_thread_rdwr_rlock( &cfb->cb_rwlock );
+ ce = config_find_base( cfb->cb_root, &op->o_req_ndn, &last, op );
+ if ( !ce ) {
+ if ( last )
+ rs->sr_matched = last->ce_entry->e_name.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ goto out;
+ }
+ if ( !access_allowed_mask( op, ce->ce_entry, slap_schema.si_ad_entry, NULL,
+ ACL_SEARCH, NULL, &mask ))
+ {
+ if ( !ACL_GRANT( mask, ACL_DISCLOSE )) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ }
+ goto out;
+ }
+ switch ( op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_SUBTREE:
+ rs->sr_err = config_send( op, rs, ce, 0 );
+ break;
+
+ case LDAP_SCOPE_ONELEVEL:
+ for (ce = ce->ce_kids; ce; ce=ce->ce_sibs) {
+ rs->sr_err = config_send( op, rs, ce, 1 );
+ if ( rs->sr_err ) {
+ break;
+ }
+ }
+ break;
+ }
+
+out:
+ ldap_pvt_thread_rdwr_runlock( &cfb->cb_rwlock );
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+}
+
+int config_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( !e->e_private ) {
+ BackendDB *be = op->o_bd;
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_entry_release_rw ) {
+ op->o_bd = &cfb->cb_db;
+ rc = bi->bi_entry_release_rw( op, e, rw );
+ op->o_bd = be;
+ } else {
+ entry_free( e );
+ }
+ } else {
+ entry_free( e );
+ }
+ return rc;
+}
+
+/* return LDAP_SUCCESS IFF we can retrieve the specified entry.
+ */
+int config_back_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ CfBackInfo *cfb;
+ CfEntryInfo *ce, *last;
+ Entry *e = NULL;
+ int paused = 0, rc = LDAP_NO_SUCH_OBJECT;
+
+ cfb = (CfBackInfo *)op->o_bd->be_private;
+
+ if ( ldap_pvt_thread_pool_query( &connection_pool,
+ LDAP_PVT_THREAD_POOL_PARAM_PAUSED, &paused ) ) {
+ return -1;
+ }
+ if ( !paused ) {
+ ldap_pvt_thread_rdwr_rlock( &cfb->cb_rwlock );
+ }
+ ce = config_find_base( cfb->cb_root, ndn, &last, op );
+ if ( ce ) {
+ e = ce->ce_entry;
+ if ( e ) {
+ rc = LDAP_SUCCESS;
+ if ( oc && !is_entry_objectclass_or_sub( e, oc ) ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ e = NULL;
+ }
+ }
+ }
+ if ( e ) {
+ *ent = entry_dup( e );
+ }
+ if ( !paused )
+ ldap_pvt_thread_rdwr_runlock( &cfb->cb_rwlock );
+
+ return rc;
+}
+
+static int
+config_build_attrs( Entry *e, AttributeType **at, AttributeDescription *ad,
+ ConfigTable *ct, ConfigArgs *c )
+{
+ int i, rc;
+
+ for (; at && *at; at++) {
+ /* Skip the naming attr */
+ if ((*at)->sat_ad == ad || (*at)->sat_ad == slap_schema.si_ad_cn )
+ continue;
+ for (i=0;ct[i].name;i++) {
+ if (ct[i].ad == (*at)->sat_ad) {
+ c->ca_desc = &ct[i];
+ rc = config_get_vals(&ct[i], c);
+ /* NOTE: tolerate that config_get_vals()
+ * returns success with no values */
+ if (rc == LDAP_SUCCESS && c->rvalue_vals != NULL ) {
+ if ( c->rvalue_nvals )
+ rc = attr_merge(e, ct[i].ad, c->rvalue_vals,
+ c->rvalue_nvals);
+ else {
+ slap_syntax_validate_func *validate =
+ ct[i].ad->ad_type->sat_syntax->ssyn_validate;
+ if ( validate ) {
+ int j;
+ for ( j=0; c->rvalue_vals[j].bv_val; j++ ) {
+ rc = ordered_value_validate( ct[i].ad,
+ &c->rvalue_vals[j], LDAP_MOD_ADD );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "config_build_attrs: error %d on %s value #%d\n",
+ rc, ct[i].ad->ad_cname.bv_val, j );
+ return rc;
+ }
+ }
+ }
+
+ rc = attr_merge_normalize(e, ct[i].ad,
+ c->rvalue_vals, NULL);
+ }
+ ber_bvarray_free( c->rvalue_nvals );
+ ber_bvarray_free( c->rvalue_vals );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "config_build_attrs: error %d on %s\n",
+ rc, ct[i].ad->ad_cname.bv_val );
+ return rc;
+ }
+ }
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+/* currently (2010) does not access rs except possibly writing rs->sr_err */
+
+Entry *
+config_build_entry( Operation *op, SlapReply *rs, CfEntryInfo *parent,
+ ConfigArgs *c, struct berval *rdn, ConfigOCs *main, ConfigOCs *extra )
+{
+ Entry *e = entry_alloc();
+ CfEntryInfo *ce = ch_calloc( 1, sizeof(CfEntryInfo) );
+ AttributeDescription *ad = NULL;
+ int cnt, rc;
+ const char *text = "";
+ Attribute *oc_at;
+ struct berval pdn;
+ ObjectClass *oc;
+ CfEntryInfo *ceprev = NULL;
+ LDAPRDN rDN;
+
+ Debug( LDAP_DEBUG_TRACE, "config_build_entry: \"%s\"\n", rdn->bv_val );
+ e->e_private = ce;
+ ce->ce_entry = e;
+ ce->ce_type = main->co_type;
+ ce->ce_parent = parent;
+ if ( parent ) {
+ pdn = parent->ce_entry->e_nname;
+ if ( parent->ce_kids && parent->ce_kids->ce_type <= ce->ce_type )
+ for ( ceprev = parent->ce_kids; ceprev->ce_sibs &&
+ ceprev->ce_type <= ce->ce_type;
+ ceprev = ceprev->ce_sibs );
+ } else {
+ BER_BVZERO( &pdn );
+ }
+
+ ce->ce_private = c->ca_private;
+ ce->ce_be = c->be;
+ ce->ce_bi = c->bi;
+
+ build_new_dn( &e->e_name, &pdn, rdn, NULL );
+ ber_dupbv( &e->e_nname, &e->e_name );
+
+ attr_merge_normalize_one(e, slap_schema.si_ad_objectClass,
+ main->co_name, NULL );
+ if ( extra )
+ attr_merge_normalize_one(e, slap_schema.si_ad_objectClass,
+ extra->co_name, NULL );
+
+ rc = ldap_bv2rdn( rdn, &rDN, (char **)&text, LDAP_DN_FORMAT_LDAP );
+ if ( rc ) {
+ goto fail;
+ }
+ for ( cnt = 0; rDN[cnt]; cnt++ ) {
+ LDAPAVA *ava = rDN[cnt];
+
+ ad = NULL;
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc ) {
+ break;
+ }
+ if ( !ad->ad_type->sat_equality ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ text = "attribute has no equality matching rule";
+ break;
+ }
+ if ( !ad->ad_type->sat_equality->smr_match ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ text = "attribute has unsupported equality matching rule";
+ break;
+ }
+ attr_merge_normalize_one(e, ad, &ava->la_value, NULL );
+ }
+ ldap_rdnfree( rDN );
+ if ( rc ) {
+ goto fail;
+ }
+
+ oc = main->co_oc;
+ c->table = main->co_type;
+ if ( oc->soc_required ) {
+ rc = config_build_attrs( e, oc->soc_required, ad, main->co_table, c );
+ if ( rc ) goto fail;
+ }
+
+ if ( oc->soc_allowed ) {
+ rc = config_build_attrs( e, oc->soc_allowed, ad, main->co_table, c );
+ if ( rc ) goto fail;
+ }
+
+ if ( extra ) {
+ oc = extra->co_oc;
+ c->table = extra->co_type;
+ if ( oc->soc_required ) {
+ rc = config_build_attrs( e, oc->soc_required, ad, extra->co_table, c );
+ if ( rc ) goto fail;
+ }
+
+ if ( oc->soc_allowed ) {
+ rc = config_build_attrs( e, oc->soc_allowed, ad, extra->co_table, c );
+ if ( rc ) goto fail;
+ }
+ }
+
+ oc_at = attr_find( e->e_attrs, slap_schema.si_ad_objectClass );
+ rc = structural_class(oc_at->a_vals, &oc, NULL, &text, c->cr_msg,
+ sizeof(c->cr_msg), op ? op->o_tmpmemctx : NULL );
+ if ( rc != LDAP_SUCCESS ) {
+fail:
+ Debug( LDAP_DEBUG_ANY,
+ "config_build_entry: build \"%s\" failed: \"%s\"\n",
+ rdn->bv_val, text );
+ return NULL;
+ }
+ attr_merge_normalize_one(e, slap_schema.si_ad_structuralObjectClass, &oc->soc_cname, NULL );
+ if ( op ) {
+ op->ora_e = e;
+ op->ora_modlist = NULL;
+ slap_add_opattrs( op, NULL, NULL, 0, 0 );
+ if ( !op->o_noop ) {
+ SlapReply rs2 = {REP_RESULT};
+ op->o_bd->be_add( op, &rs2 );
+ rs->sr_err = rs2.sr_err;
+ rs_assert_done( &rs2 );
+ if ( ( rs2.sr_err != LDAP_SUCCESS )
+ && (rs2.sr_err != LDAP_ALREADY_EXISTS) ) {
+ goto fail;
+ }
+ }
+ }
+ if ( ceprev ) {
+ ce->ce_sibs = ceprev->ce_sibs;
+ ceprev->ce_sibs = ce;
+ } else if ( parent ) {
+ ce->ce_sibs = parent->ce_kids;
+ parent->ce_kids = ce;
+ }
+
+ return e;
+}
+
+static int
+config_build_schema_inc( ConfigArgs *c, CfEntryInfo *ceparent,
+ Operation *op, SlapReply *rs )
+{
+ Entry *e;
+ ConfigFile *cf = c->ca_private;
+ char *ptr;
+ struct berval bv, rdn;
+
+ for (; cf; cf=cf->c_sibs, c->depth++) {
+ if ( !cf->c_at_head && !cf->c_cr_head && !cf->c_oc_head &&
+ !cf->c_om_head && !cf->c_syn_head && !cf->c_kids ) continue;
+ c->value_dn.bv_val = c->log;
+ LUTIL_SLASHPATH( cf->c_file.bv_val );
+ bv.bv_val = strrchr(cf->c_file.bv_val, LDAP_DIRSEP[0]);
+ if ( !bv.bv_val ) {
+ bv = cf->c_file;
+ } else {
+ bv.bv_val++;
+ bv.bv_len = cf->c_file.bv_len - (bv.bv_val - cf->c_file.bv_val);
+ }
+ ptr = strchr( bv.bv_val, '.' );
+ if ( ptr )
+ bv.bv_len = ptr - bv.bv_val;
+ c->value_dn.bv_len = snprintf(c->value_dn.bv_val, sizeof( c->log ), "cn=" SLAP_X_ORDERED_FMT, c->depth);
+ if ( c->value_dn.bv_len >= sizeof( c->log ) ) {
+ /* FIXME: how can indicate error? */
+ return -1;
+ }
+ strncpy( c->value_dn.bv_val + c->value_dn.bv_len, bv.bv_val,
+ bv.bv_len );
+ c->value_dn.bv_len += bv.bv_len;
+ c->value_dn.bv_val[c->value_dn.bv_len] ='\0';
+ if ( rdnNormalize( 0, NULL, NULL, &c->value_dn, &rdn, NULL )) {
+ Debug( LDAP_DEBUG_ANY,
+ "config_build_schema_inc: invalid schema name \"%s\"\n",
+ bv.bv_val );
+ return -1;
+ }
+
+ c->ca_private = cf;
+ e = config_build_entry( op, rs, ceparent, c, &rdn,
+ &CFOC_SCHEMA, NULL );
+ ch_free( rdn.bv_val );
+ if ( !e ) {
+ return -1;
+ } else if ( e && cf->c_kids ) {
+ c->ca_private = cf->c_kids;
+ config_build_schema_inc( c, e->e_private, op, rs );
+ }
+ }
+ return 0;
+}
+
+#ifdef SLAPD_MODULES
+
+static int
+config_build_modules( ConfigArgs *c, CfEntryInfo *ceparent,
+ Operation *op, SlapReply *rs )
+{
+ int i;
+ ModPaths *mp;
+
+ for (i=0, mp=&modpaths; mp; mp=mp->mp_next, i++) {
+ if ( BER_BVISNULL( &mp->mp_path ) && !mp->mp_loads )
+ continue;
+ c->value_dn.bv_val = c->log;
+ c->value_dn.bv_len = snprintf(c->value_dn.bv_val, sizeof( c->log ), "cn=module" SLAP_X_ORDERED_FMT, i);
+ if ( c->value_dn.bv_len >= sizeof( c->log ) ) {
+ /* FIXME: how can indicate error? */
+ return -1;
+ }
+ c->ca_private = mp;
+ if ( ! config_build_entry( op, rs, ceparent, c, &c->value_dn, &CFOC_MODULE, NULL )) {
+ return -1;
+ }
+ }
+ return 0;
+}
+#endif
+
+static int
+config_check_schema(Operation *op, CfBackInfo *cfb)
+{
+ struct berval schema_dn = BER_BVC(SCHEMA_RDN "," CONFIG_RDN);
+ ConfigArgs c = {0};
+ CfEntryInfo *ce, *last;
+ Entry *e;
+
+ /* If there's no root entry, we must be in the midst of converting */
+ if ( !cfb->cb_root )
+ return 0;
+
+ /* Make sure the main schema entry exists */
+ ce = config_find_base( cfb->cb_root, &schema_dn, &last, op );
+ if ( ce ) {
+ Attribute *a;
+ struct berval *bv;
+
+ e = ce->ce_entry;
+
+ /* Make sure it's up to date */
+ if ( cf_om_tail != om_sys_tail ) {
+ a = attr_find( e->e_attrs, cfAd_om );
+ if ( a ) {
+ if ( a->a_nvals != a->a_vals )
+ ber_bvarray_free( a->a_nvals );
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+ oidm_unparse( &bv, NULL, NULL, 1 );
+ attr_merge_normalize( e, cfAd_om, bv, NULL );
+ ber_bvarray_free( bv );
+ cf_om_tail = om_sys_tail;
+ }
+ if ( cf_at_tail != at_sys_tail ) {
+ a = attr_find( e->e_attrs, cfAd_attr );
+ if ( a ) {
+ if ( a->a_nvals != a->a_vals )
+ ber_bvarray_free( a->a_nvals );
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+ at_unparse( &bv, NULL, NULL, 1 );
+ attr_merge_normalize( e, cfAd_attr, bv, NULL );
+ ber_bvarray_free( bv );
+ cf_at_tail = at_sys_tail;
+ }
+ if ( cf_oc_tail != oc_sys_tail ) {
+ a = attr_find( e->e_attrs, cfAd_oc );
+ if ( a ) {
+ if ( a->a_nvals != a->a_vals )
+ ber_bvarray_free( a->a_nvals );
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+ oc_unparse( &bv, NULL, NULL, 1 );
+ attr_merge_normalize( e, cfAd_oc, bv, NULL );
+ ber_bvarray_free( bv );
+ cf_oc_tail = oc_sys_tail;
+ }
+ if ( cf_syn_tail != syn_sys_tail ) {
+ a = attr_find( e->e_attrs, cfAd_syntax );
+ if ( a ) {
+ if ( a->a_nvals != a->a_vals )
+ ber_bvarray_free( a->a_nvals );
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+ syn_unparse( &bv, NULL, NULL, 1 );
+ attr_merge_normalize( e, cfAd_syntax, bv, NULL );
+ ber_bvarray_free( bv );
+ cf_syn_tail = syn_sys_tail;
+ }
+ } else {
+ SlapReply rs = {REP_RESULT};
+ c.ca_private = NULL;
+ e = config_build_entry( op, &rs, cfb->cb_root, &c, &schema_rdn,
+ &CFOC_SCHEMA, NULL );
+ if ( !e ) {
+ return -1;
+ }
+ ce = e->e_private;
+ ce->ce_private = cfb->cb_config;
+ cf_at_tail = at_sys_tail;
+ cf_oc_tail = oc_sys_tail;
+ cf_om_tail = om_sys_tail;
+ cf_syn_tail = syn_sys_tail;
+ }
+ return 0;
+}
+
+static const char *defacl[] = {
+ NULL, "to", "*", "by", "*", "none", NULL
+};
+
+static int
+config_back_db_open( BackendDB *be, ConfigReply *cr )
+{
+ CfBackInfo *cfb = be->be_private;
+ struct berval rdn;
+ Entry *e;
+ CfEntryInfo *ce, *ceparent;
+ int i, unsupp = 0;
+ BackendInfo *bi;
+ ConfigArgs c;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ SlapReply rs = {REP_RESULT};
+ void *thrctx = NULL;
+ AccessControl *save_access;
+
+ Debug( LDAP_DEBUG_TRACE, "config_back_db_open\n" );
+
+ /* If we have no explicitly configured ACLs, don't just use
+ * the global ACLs. Explicitly deny access to everything.
+ */
+ save_access = be->bd_self->be_acl;
+ be->bd_self->be_acl = NULL;
+ c.be = be->bd_self;
+ c.fname = "config_back_db_open";
+ c.lineno = 0;
+ c.argc = 6;
+ c.argv = (char **)defacl;
+ parse_acl( &c, 0 );
+ defacl_parsed = be->bd_self->be_acl;
+ if ( save_access ) {
+ be->bd_self->be_acl = save_access;
+ } else {
+ Debug( LDAP_DEBUG_CONFIG, "config_back_db_open: "
+ "No explicit ACL for back-config configured. "
+ "Using hardcoded default\n" );
+ }
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_callback = &cb;
+ op->o_bd = &cfb->cb_db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ if ( !cfb->cb_use_ldif ) {
+ op->o_noop = 1;
+ }
+
+ /* If we read the config from back-ldif, do some quick sanity checks */
+ if ( cfb->cb_got_ldif ) {
+ return config_check_schema( op, cfb );
+ }
+
+ /* create root of tree */
+ rdn = config_rdn;
+ c.ca_private = cfb->cb_config;
+ c.be = frontendDB;
+ e = config_build_entry( op, &rs, NULL, &c, &rdn, &CFOC_GLOBAL, NULL );
+ if ( !e ) {
+ return -1;
+ }
+ ce = e->e_private;
+ cfb->cb_root = ce;
+
+ ceparent = ce;
+
+#ifdef SLAPD_MODULES
+ /* Create Module nodes... */
+ if ( modpaths.mp_loads ) {
+ if ( config_build_modules( &c, ceparent, op, &rs ) ){
+ return -1;
+ }
+ }
+#endif
+
+ /* Create schema nodes... cn=schema will contain the hardcoded core
+ * schema, read-only. Child objects will contain runtime loaded schema
+ * files.
+ */
+ rdn = schema_rdn;
+ c.ca_private = NULL;
+ e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_SCHEMA, NULL );
+ if ( !e ) {
+ return -1;
+ }
+ ce = e->e_private;
+ ce->ce_private = cfb->cb_config;
+ cf_at_tail = at_sys_tail;
+ cf_oc_tail = oc_sys_tail;
+ cf_om_tail = om_sys_tail;
+ cf_syn_tail = syn_sys_tail;
+
+ /* Create schema nodes for included schema... */
+ if ( cfb->cb_config->c_kids ) {
+ int rc;
+ c.depth = 0;
+ c.ca_private = cfb->cb_config->c_kids;
+ rc = config_build_schema_inc( &c, ce, op, &rs );
+ if ( rc ) {
+ return -1;
+ }
+ }
+
+ /* Create backend nodes. Skip if they don't provide a cf_table.
+ * There usually aren't any of these.
+ */
+
+ c.line = 0;
+ i = 0;
+ LDAP_STAILQ_FOREACH( bi, &backendInfo, bi_next) {
+ if (!bi->bi_cf_ocs) {
+ /* If it only supports the old config mech, complain. */
+ if ( bi->bi_config ) {
+ Debug( LDAP_DEBUG_ANY,
+ "WARNING: No dynamic config support for backend %s.\n",
+ bi->bi_type );
+ unsupp++;
+ }
+ continue;
+ }
+ if ( !bi->bi_private && !(bi->bi_flags & SLAP_BFLAG_STANDALONE) ) continue;
+
+ rdn.bv_val = c.log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s", cfAd_backend->ad_cname.bv_val,
+ i, bi->bi_type);
+ if ( rdn.bv_len >= sizeof( c.log ) ) {
+ /* FIXME: holler ... */ ;
+ }
+ c.bi = bi;
+ e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_BACKEND,
+ bi->bi_cf_ocs );
+ if ( !e ) {
+ return -1;
+ }
+ if ( bi->bi_cf_ocs && bi->bi_cf_ocs->co_cfadd ) {
+ rs_reinit( &rs, REP_RESULT );
+ bi->bi_cf_ocs->co_cfadd( op, &rs, e, &c );
+ }
+ i++;
+ }
+
+ /* Create database nodes... */
+ frontendDB->be_cf_ocs = &CFOC_FRONTEND;
+ LDAP_STAILQ_NEXT(frontendDB, be_next) = LDAP_STAILQ_FIRST(&backendDB);
+ for ( i = -1, be = frontendDB ; be;
+ i++, be = LDAP_STAILQ_NEXT( be, be_next )) {
+ slap_overinfo *oi = NULL;
+
+ if ( overlay_is_over( be )) {
+ oi = be->bd_info->bi_private;
+ bi = oi->oi_orig;
+ } else {
+ bi = be->bd_info;
+ }
+
+ /* If this backend supports the old config mechanism, but not
+ * the new mech, complain.
+ */
+ if ( !be->be_cf_ocs && bi->bi_db_config ) {
+ Debug( LDAP_DEBUG_ANY,
+ "WARNING: No dynamic config support for database %s.\n",
+ bi->bi_type );
+ unsupp++;
+ }
+ rdn.bv_val = c.log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s", cfAd_database->ad_cname.bv_val,
+ i, bi->bi_type);
+ if ( rdn.bv_len >= sizeof( c.log ) ) {
+ /* FIXME: holler ... */ ;
+ }
+ c.be = be;
+ c.bi = bi;
+ e = config_build_entry( op, &rs, ceparent, &c, &rdn, &CFOC_DATABASE,
+ be->be_cf_ocs );
+ if ( !e ) {
+ return -1;
+ }
+ ce = e->e_private;
+ if ( be->be_cf_ocs && be->be_cf_ocs->co_cfadd ) {
+ rs_reinit( &rs, REP_RESULT );
+ be->be_cf_ocs->co_cfadd( op, &rs, e, &c );
+ }
+ /* Iterate through overlays */
+ if ( oi ) {
+ slap_overinst *on;
+ Entry *oe;
+ int j;
+ voidList *vl, *v0 = NULL;
+
+ /* overlays are in LIFO order, must reverse stack */
+ for (on=oi->oi_list; on; on=on->on_next) {
+ vl = ch_malloc( sizeof( voidList ));
+ vl->vl_next = v0;
+ v0 = vl;
+ vl->vl_ptr = on;
+ }
+ for (j=0; vl; j++,vl=v0) {
+ on = vl->vl_ptr;
+ v0 = vl->vl_next;
+ ch_free( vl );
+ if ( on->on_bi.bi_db_config && !on->on_bi.bi_cf_ocs ) {
+ Debug( LDAP_DEBUG_ANY,
+ "WARNING: No dynamic config support for overlay %s.\n",
+ on->on_bi.bi_type );
+ unsupp++;
+ }
+ rdn.bv_val = c.log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( c.log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s",
+ cfAd_overlay->ad_cname.bv_val, j, on->on_bi.bi_type );
+ if ( rdn.bv_len >= sizeof( c.log ) ) {
+ /* FIXME: holler ... */ ;
+ }
+ c.be = be;
+ c.bi = &on->on_bi;
+ oe = config_build_entry( op, &rs, ce, &c, &rdn,
+ &CFOC_OVERLAY, c.bi->bi_cf_ocs );
+ if ( !oe ) {
+ return -1;
+ }
+ if ( c.bi->bi_cf_ocs && c.bi->bi_cf_ocs->co_cfadd ) {
+ rs_reinit( &rs, REP_RESULT );
+ c.bi->bi_cf_ocs->co_cfadd( op, &rs, oe, &c );
+ }
+ }
+ }
+ }
+ if ( thrctx )
+ ldap_pvt_thread_pool_context_reset( thrctx );
+
+ if ( unsupp && cfb->cb_use_ldif ) {
+ Debug( LDAP_DEBUG_ANY, "\nWARNING: The converted cn=config "
+ "directory is incomplete and may not work.\n\n" );
+ }
+
+ return 0;
+}
+
+static void
+cfb_free_cffile( ConfigFile *cf )
+{
+ ConfigFile *next;
+
+ for (; cf; cf=next) {
+ next = cf->c_sibs;
+ if ( cf->c_kids )
+ cfb_free_cffile( cf->c_kids );
+ ch_free( cf->c_file.bv_val );
+ ber_bvarray_free( cf->c_dseFiles );
+ ch_free( cf );
+ }
+}
+
+static void
+cfb_free_entries( CfEntryInfo *ce )
+{
+ CfEntryInfo *next;
+
+ for (; ce; ce=next) {
+ next = ce->ce_sibs;
+ if ( ce->ce_kids )
+ cfb_free_entries( ce->ce_kids );
+ ce->ce_entry->e_private = NULL;
+ entry_free( ce->ce_entry );
+ ch_free( ce );
+ }
+}
+
+static int
+config_back_db_close( BackendDB *be, ConfigReply *cr )
+{
+ CfBackInfo *cfb = be->be_private;
+
+ cfb_free_entries( cfb->cb_root );
+ cfb->cb_root = NULL;
+
+ if ( cfb->cb_db.bd_info ) {
+ backend_shutdown( &cfb->cb_db );
+ }
+
+ if ( defacl_parsed && be->be_acl != defacl_parsed ) {
+ acl_free( defacl_parsed );
+ defacl_parsed = NULL;
+ }
+
+ return 0;
+}
+
+static int
+config_back_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ CfBackInfo *cfb = be->be_private;
+
+ cfb_free_cffile( cfb->cb_config );
+
+ ch_free( cfdir.bv_val );
+
+ ldap_pvt_thread_rdwr_destroy( &cfb->cb_rwlock );
+
+ ldap_avl_free( CfOcTree, NULL );
+
+ if ( cfb->cb_db.bd_info ) {
+ cfb->cb_db.be_suffix = NULL;
+ cfb->cb_db.be_nsuffix = NULL;
+ BER_BVZERO( &cfb->cb_db.be_rootdn );
+ BER_BVZERO( &cfb->cb_db.be_rootndn );
+
+ backend_destroy_one( &cfb->cb_db, 0 );
+ }
+
+ slap_loglevel_destroy();
+
+ return 0;
+}
+
+static int
+config_back_db_init( BackendDB *be, ConfigReply* cr )
+{
+ struct berval dn;
+ CfBackInfo *cfb;
+
+ cfb = &cfBackInfo;
+ cfb->cb_config = ch_calloc( 1, sizeof(ConfigFile));
+ cfn = cfb->cb_config;
+ be->be_private = cfb;
+
+ ber_dupbv( &be->be_rootdn, &config_rdn );
+ ber_dupbv( &be->be_rootndn, &be->be_rootdn );
+ ber_dupbv( &dn, &be->be_rootdn );
+ ber_bvarray_add( &be->be_suffix, &dn );
+ ber_dupbv( &dn, &be->be_rootdn );
+ ber_bvarray_add( &be->be_nsuffix, &dn );
+
+ ldap_pvt_thread_rdwr_init( &cfb->cb_rwlock );
+
+ /* Hide from namingContexts */
+ SLAP_BFLAGS(be) |= SLAP_BFLAG_CONFIG;
+
+ /* Check ACLs on content of Adds by default */
+ SLAP_DBFLAGS(be) |= SLAP_DBFLAG_ACL_ADD;
+
+ return 0;
+}
+
+static int
+config_back_destroy( BackendInfo *bi )
+{
+ ldif_must_b64_encode_release();
+ return 0;
+}
+
+static int
+config_tool_entry_open( BackendDB *be, int mode )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ configDB = be;
+ if ( bi && bi->bi_tool_entry_open )
+ return bi->bi_tool_entry_open( &cfb->cb_db, mode );
+ else
+ return -1;
+
+}
+
+static int
+config_tool_entry_close( BackendDB *be )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_entry_close )
+ return bi->bi_tool_entry_close( &cfb->cb_db );
+ else
+ return -1;
+}
+
+static ID
+config_tool_entry_first( BackendDB *be )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_entry_first ) {
+ return bi->bi_tool_entry_first( &cfb->cb_db );
+ }
+ if ( bi && bi->bi_tool_entry_first_x ) {
+ return bi->bi_tool_entry_first_x( &cfb->cb_db,
+ NULL, LDAP_SCOPE_DEFAULT, NULL );
+ }
+ return NOID;
+}
+
+static ID
+config_tool_entry_first_x(
+ BackendDB *be,
+ struct berval *base,
+ int scope,
+ Filter *f )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_entry_first_x ) {
+ return bi->bi_tool_entry_first_x( &cfb->cb_db, base, scope, f );
+ }
+ return NOID;
+}
+
+static ID
+config_tool_entry_next( BackendDB *be )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_entry_next )
+ return bi->bi_tool_entry_next( &cfb->cb_db );
+ else
+ return NOID;
+}
+
+static ID
+config_tool_dn2id_get( Backend *be, struct berval *dn )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_dn2id_get )
+ return bi->bi_tool_dn2id_get( &cfb->cb_db, dn );
+
+ return NOID;
+}
+
+static Entry *
+config_tool_entry_get( BackendDB *be, ID id )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+
+ if ( bi && bi->bi_tool_entry_get )
+ return bi->bi_tool_entry_get( &cfb->cb_db, id );
+ else
+ return NULL;
+}
+
+static int entry_put_got_frontend=0;
+static int entry_put_got_config=0;
+static ID
+config_tool_entry_put( BackendDB *be, Entry *e, struct berval *text )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+ int rc;
+ struct berval rdn;
+ ConfigArgs ca;
+ OperationBuffer opbuf;
+ Entry *ce;
+ Connection conn = {0};
+ Operation *op = NULL;
+ void *thrctx;
+ int isFrontend = 0;
+ int isFrontendChild = 0;
+
+ /* Create entry for frontend database if it does not exist already */
+ if ( !entry_put_got_frontend ) {
+ if ( !strncmp( e->e_nname.bv_val, "olcDatabase",
+ STRLENOF( "olcDatabase" ))) {
+ if ( strncmp( e->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "={-1}frontend",
+ STRLENOF( "={-1}frontend" )) &&
+ strncmp( e->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "=frontend",
+ STRLENOF( "=frontend" ))) {
+ memset( &ca, 0, sizeof(ConfigArgs));
+ ca.be = frontendDB;
+ ca.bi = frontendDB->bd_info;
+ ca.be->be_cf_ocs = &CFOC_FRONTEND;
+ rdn.bv_val = ca.log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( ca.log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s",
+ cfAd_database->ad_cname.bv_val, -1,
+ ca.bi->bi_type);
+ ce = config_build_entry( NULL, NULL, cfb->cb_root, &ca, &rdn,
+ &CFOC_DATABASE, ca.be->be_cf_ocs );
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx,0 );
+ op = &opbuf.ob_op;
+ op->o_bd = &cfb->cb_db;
+ op->o_tag = LDAP_REQ_ADD;
+ op->ora_e = ce;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+ rc = slap_add_opattrs(op, NULL, NULL, 0, 0);
+ if ( rc != LDAP_SUCCESS ) {
+ text->bv_val = "autocreation of \"olcDatabase={-1}frontend\" failed";
+ text->bv_len = STRLENOF("autocreation of \"olcDatabase={-1}frontend\" failed");
+ return NOID;
+ }
+
+ if ( ce && bi && bi->bi_tool_entry_put &&
+ bi->bi_tool_entry_put( &cfb->cb_db, ce, text ) != NOID ) {
+ entry_put_got_frontend++;
+ } else {
+ text->bv_val = "autocreation of \"olcDatabase={-1}frontend\" failed";
+ text->bv_len = STRLENOF("autocreation of \"olcDatabase={-1}frontend\" failed");
+ return NOID;
+ }
+ } else {
+ entry_put_got_frontend++;
+ isFrontend = 1;
+ }
+ }
+ }
+
+ /* Child entries of the frontend database, e.g. slapo-chain's back-ldap
+ * instances, may appear before the config database entry in the ldif, skip
+ * auto-creation of olcDatabase={0}config in such a case */
+ if ( !entry_put_got_config &&
+ !strncmp( e->e_nname.bv_val, "olcDatabase", STRLENOF( "olcDatabase" ))) {
+ struct berval pdn;
+ dnParent( &e->e_nname, &pdn );
+ while ( pdn.bv_len ) {
+ if ( !strncmp( pdn.bv_val, "olcDatabase",
+ STRLENOF( "olcDatabase" ))) {
+ if ( !strncmp( pdn.bv_val +
+ STRLENOF( "olcDatabase" ), "={-1}frontend",
+ STRLENOF( "={-1}frontend" )) ||
+ !strncmp( pdn.bv_val +
+ STRLENOF( "olcDatabase" ), "=frontend",
+ STRLENOF( "=frontend" ))) {
+
+ isFrontendChild = 1;
+ break;
+ }
+ }
+ dnParent( &pdn, &pdn );
+ }
+ }
+
+ /* Create entry for config database if it does not exist already */
+ if ( !entry_put_got_config && !isFrontend && !isFrontendChild ) {
+ if ( !strncmp( e->e_nname.bv_val, "olcDatabase",
+ STRLENOF( "olcDatabase" ))) {
+ if ( strncmp( e->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "={0}config",
+ STRLENOF( "={0}config" )) &&
+ strncmp( e->e_nname.bv_val +
+ STRLENOF( "olcDatabase" ), "=config",
+ STRLENOF( "=config" )) ) {
+ memset( &ca, 0, sizeof(ConfigArgs));
+ ca.be = LDAP_STAILQ_FIRST( &backendDB );
+ ca.bi = ca.be->bd_info;
+ rdn.bv_val = ca.log;
+ rdn.bv_len = snprintf(rdn.bv_val, sizeof( ca.log ),
+ "%s=" SLAP_X_ORDERED_FMT "%s",
+ cfAd_database->ad_cname.bv_val, 0,
+ ca.bi->bi_type);
+ ce = config_build_entry( NULL, NULL, cfb->cb_root, &ca, &rdn, &CFOC_DATABASE,
+ ca.be->be_cf_ocs );
+ if ( ! op ) {
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx,0 );
+ op = &opbuf.ob_op;
+ op->o_bd = &cfb->cb_db;
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+ }
+ op->ora_e = ce;
+ rc = slap_add_opattrs(op, NULL, NULL, 0, 0);
+ if ( rc != LDAP_SUCCESS ) {
+ text->bv_val = "autocreation of \"olcDatabase={0}config\" failed";
+ text->bv_len = STRLENOF("autocreation of \"olcDatabase={0}config\" failed");
+ return NOID;
+ }
+ if (ce && bi && bi->bi_tool_entry_put &&
+ bi->bi_tool_entry_put( &cfb->cb_db, ce, text ) != NOID ) {
+ entry_put_got_config++;
+ } else {
+ text->bv_val = "autocreation of \"olcDatabase={0}config\" failed";
+ text->bv_len = STRLENOF("autocreation of \"olcDatabase={0}config\" failed");
+ return NOID;
+ }
+ } else {
+ entry_put_got_config++;
+ }
+ }
+ }
+ if ( bi && bi->bi_tool_entry_put &&
+ config_add_internal( cfb, e, &ca, NULL, NULL, NULL ) == 0 )
+ return bi->bi_tool_entry_put( &cfb->cb_db, e, text );
+ else {
+ ber_str2bv( ca.cr_msg, 0, 0, text );
+ return NOID;
+ }
+}
+
+static ID
+config_tool_entry_modify( BackendDB *be, Entry *e, struct berval *text )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+ CfEntryInfo *ce, *last;
+
+ ce = config_find_base( cfb->cb_root, &e->e_nname, &last, NULL );
+
+ if ( ce && bi && bi->bi_tool_entry_modify )
+ return bi->bi_tool_entry_modify( &cfb->cb_db, e, text );
+
+ return NOID;
+}
+
+static int
+config_tool_entry_delete( BackendDB *be, struct berval *ndn, struct berval *text )
+{
+ CfBackInfo *cfb = be->be_private;
+ BackendInfo *bi = cfb->cb_db.bd_info;
+ CfEntryInfo *ce, *last;
+
+ ce = config_find_base( cfb->cb_root, ndn, &last, NULL );
+
+ if ( ce && bi && bi->bi_tool_entry_delete )
+ return bi->bi_tool_entry_delete( &cfb->cb_db, ndn, text );
+
+ return LDAP_OTHER;
+}
+
+static struct {
+ char *name;
+ AttributeDescription **desc;
+} ads[] = {
+ { "attribute", &cfAd_attr },
+ { "backend", &cfAd_backend },
+ { "database", &cfAd_database },
+ { "include", &cfAd_include },
+ { "ldapsyntax", &cfAd_syntax },
+ { "objectclass", &cfAd_oc },
+ { "objectidentifier", &cfAd_om },
+ { "overlay", &cfAd_overlay },
+ { NULL, NULL }
+};
+
+/* Notes:
+ * add / delete: all types that may be added or deleted must use an
+ * X-ORDERED attributeType for their RDN. Adding and deleting entries
+ * should automatically renumber the index of any siblings as needed,
+ * so that no gaps in the numbering sequence exist after the add/delete
+ * is completed.
+ * What can be added:
+ * schema objects
+ * backend objects for backend-specific config directives
+ * database objects
+ * overlay objects
+ *
+ * delete: probably no support this time around.
+ *
+ * modrdn: generally not done. Will be invoked automatically by add/
+ * delete to update numbering sequence. Perform as an explicit operation
+ * so that the renumbering effect may be replicated. Subtree rename must
+ * be supported, since renumbering a database will affect all its child
+ * overlays.
+ *
+ * modify: must be fully supported.
+ */
+
+int
+config_back_initialize( BackendInfo *bi )
+{
+ ConfigTable *ct = config_back_cf_table;
+ ConfigArgs ca;
+ char *argv[4];
+ int i;
+ AttributeDescription *ad = NULL;
+ const char *text;
+ static char *controls[] = {
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_PRE_READ,
+ LDAP_CONTROL_POST_READ,
+ NULL
+ };
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( CFG_LAST );
+
+ bi->bi_controls = controls;
+
+ bi->bi_open = 0;
+ bi->bi_close = 0;
+ bi->bi_config = 0;
+ bi->bi_destroy = config_back_destroy;
+
+ bi->bi_db_init = config_back_db_init;
+ bi->bi_db_config = 0;
+ bi->bi_db_open = config_back_db_open;
+ bi->bi_db_close = config_back_db_close;
+ bi->bi_db_destroy = config_back_db_destroy;
+
+ bi->bi_op_bind = config_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = config_back_search;
+ bi->bi_op_compare = 0;
+ bi->bi_op_modify = config_back_modify;
+ bi->bi_op_modrdn = config_back_modrdn;
+ bi->bi_op_add = config_back_add;
+ bi->bi_op_delete = config_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_access_allowed = slap_access_allowed;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ bi->bi_entry_release_rw = config_entry_release;
+ bi->bi_entry_get_rw = config_back_entry_get;
+
+ bi->bi_tool_entry_open = config_tool_entry_open;
+ bi->bi_tool_entry_close = config_tool_entry_close;
+ bi->bi_tool_entry_first = config_tool_entry_first;
+ bi->bi_tool_entry_first_x = config_tool_entry_first_x;
+ bi->bi_tool_entry_next = config_tool_entry_next;
+ bi->bi_tool_dn2id_get = config_tool_dn2id_get;
+ bi->bi_tool_entry_get = config_tool_entry_get;
+ bi->bi_tool_entry_put = config_tool_entry_put;
+ bi->bi_tool_entry_modify = config_tool_entry_modify;
+ bi->bi_tool_entry_delete = config_tool_entry_delete;
+
+ ca.argv = argv;
+ argv[ 0 ] = "slapd";
+ ca.argv = argv;
+ ca.argc = 3;
+ ca.fname = argv[0];
+
+ argv[3] = NULL;
+ for (i=0; OidMacros[i].name; i++ ) {
+ argv[1] = OidMacros[i].name;
+ argv[2] = OidMacros[i].oid;
+ parse_oidm( &ca, 0, NULL );
+ }
+
+ bi->bi_cf_ocs = cf_ocs;
+
+ i = config_register_schema( ct, cf_ocs );
+ if ( i ) return i;
+
+ i = slap_str2ad( "olcDatabase", &olcDatabaseDummy[0].ad, &text );
+ if ( i ) return i;
+
+ /* setup olcRootPW to be base64-encoded when written in LDIF form;
+ * basically, we don't care if it fails */
+ i = slap_str2ad( "olcRootPW", &ad, &text );
+ if ( i ) {
+ Debug( LDAP_DEBUG_ANY, "config_back_initialize: "
+ "warning, unable to get \"olcRootPW\" "
+ "attribute description: %d: %s\n",
+ i, text );
+ } else {
+ (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val,
+ ad->ad_type->sat_oid );
+ }
+
+ /* set up the notable AttributeDescriptions */
+ i = 0;
+ for (;ct->name;ct++) {
+ if (strcmp(ct->name, ads[i].name)) continue;
+ *ads[i].desc = ct->ad;
+ i++;
+ if (!ads[i].name) break;
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/bind.c b/servers/slapd/bind.c
new file mode 100644
index 0000000..19598f1
--- /dev/null
+++ b/servers/slapd/bind.c
@@ -0,0 +1,555 @@
+/* bind.c - decode an ldap bind operation and pass it to a backend db */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+
+int
+do_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ BerElement *ber = op->o_ber;
+ ber_int_t version;
+ ber_tag_t method;
+ struct berval mech = BER_BVNULL;
+ struct berval dn = BER_BVNULL;
+ ber_tag_t tag;
+ Backend *be = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_bind\n",
+ op->o_log_prefix );
+
+ /*
+ * Force the connection to "anonymous" until bind succeeds.
+ */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ if ( op->o_conn->c_sasl_bind_in_progress ) {
+ be = op->o_conn->c_authz_backend;
+ }
+ if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) ) {
+ /* log authorization identity demotion */
+ Debug( LDAP_DEBUG_STATS,
+ "%s BIND anonymous mech=implicit bind_ssf=0 ssf=%d\n",
+ op->o_log_prefix, op->o_conn->c_ssf );
+ }
+ connection2anonymous( op->o_conn );
+ if ( op->o_conn->c_sasl_bind_in_progress ) {
+ op->o_conn->c_authz_backend = be;
+ }
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ if ( !BER_BVISNULL( &op->o_dn ) ) {
+ /* NOTE: temporarily wasting few bytes
+ * (until bind is completed), but saving
+ * a couple of ch_free() and ch_strdup("") */
+ op->o_dn.bv_val[0] = '\0';
+ op->o_dn.bv_len = 0;
+ }
+ if ( !BER_BVISNULL( &op->o_ndn ) ) {
+ op->o_ndn.bv_val[0] = '\0';
+ op->o_ndn.bv_len = 0;
+ }
+
+ /*
+ * Parse the bind request. It looks like this:
+ *
+ * BindRequest ::= SEQUENCE {
+ * version INTEGER, -- version
+ * name DistinguishedName, -- dn
+ * authentication CHOICE {
+ * simple [0] OCTET STRING -- passwd
+ * krbv42ldap [1] OCTET STRING -- OBSOLETE
+ * krbv42dsa [2] OCTET STRING -- OBSOLETE
+ * SASL [3] SaslCredentials
+ * }
+ * }
+ *
+ * SaslCredentials ::= SEQUENCE {
+ * mechanism LDAPString,
+ * credentials OCTET STRING OPTIONAL
+ * }
+ */
+
+ tag = ber_scanf( ber, "{imt" /*}*/, &version, &dn, &method );
+
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_bind: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+
+ op->o_protocol = version;
+ op->orb_method = method;
+
+ if( op->orb_method != LDAP_AUTH_SASL ) {
+ tag = ber_scanf( ber, /*{*/ "m}", &op->orb_cred );
+
+ } else {
+ tag = ber_scanf( ber, "{m" /*}*/, &mech );
+
+ if ( tag != LBER_ERROR ) {
+ ber_len_t len;
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag == LDAP_TAG_LDAPCRED ) {
+ tag = ber_scanf( ber, "m", &op->orb_cred );
+ } else {
+ tag = LDAP_TAG_LDAPCRED;
+ BER_BVZERO( &op->orb_cred );
+ }
+
+ if ( tag != LBER_ERROR ) {
+ tag = ber_scanf( ber, /*{{*/ "}}" );
+ }
+ }
+ }
+
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_bind: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_bind: get_ctrls failed\n",
+ op->o_log_prefix );
+ goto cleanup;
+ }
+
+ /* We use the tmpmemctx here because it speeds up normalization.
+ * However, we must dup with regular malloc when storing any
+ * resulting DNs in the op or conn structures.
+ */
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_bind: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto cleanup;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s BIND dn=\"%s\" method=%ld\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ (unsigned long) op->orb_method );
+
+ if( op->orb_method == LDAP_AUTH_SASL ) {
+ Debug( LDAP_DEBUG_TRACE, "do_bind: dn (%s) SASL mech %s\n",
+ op->o_req_dn.bv_val, mech.bv_val );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "do_bind: version=%ld dn=\"%s\" method=%ld\n",
+ (unsigned long) version, op->o_req_dn.bv_val,
+ (unsigned long) op->orb_method );
+ }
+
+ if ( version < LDAP_VERSION_MIN || version > LDAP_VERSION_MAX ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_bind: unknown version=%ld\n",
+ op->o_log_prefix, (unsigned long) version );
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
+ "requested protocol version not supported" );
+ goto cleanup;
+
+ } else if (!( global_allows & SLAP_ALLOW_BIND_V2 ) &&
+ version < LDAP_VERSION3 )
+ {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
+ "historical protocol version requested, use LDAPv3 instead" );
+ goto cleanup;
+ }
+
+ /*
+ * we set connection version regardless of whether bind succeeds or not.
+ */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ op->o_conn->c_protocol = version;
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ op->orb_mech = mech;
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_bind( op, rs );
+
+cleanup:
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ if ( op->orb_method != LDAP_AUTH_SASL ) {
+ ber_dupbv( &op->o_conn->c_authmech, &mech );
+ }
+ op->o_conn->c_authtype = op->orb_method;
+ }
+
+ if( !BER_BVISNULL( &op->o_req_dn ) ) {
+ slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_dn );
+ }
+ if( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ }
+
+ return rs->sr_err;
+}
+
+int
+fe_op_bind( Operation *op, SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ /* check for inappropriate controls */
+ if( get_manageDSAit( op ) == SLAP_CONTROL_CRITICAL ) {
+ send_ldap_error( op, rs,
+ LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "manageDSAit control inappropriate" );
+ goto cleanup;
+ }
+
+ if ( op->orb_method == LDAP_AUTH_SASL ) {
+ if ( op->o_protocol < LDAP_VERSION3 ) {
+ Debug( LDAP_DEBUG_ANY, "do_bind: sasl with LDAPv%ld\n",
+ (unsigned long)op->o_protocol );
+ send_ldap_discon( op, rs,
+ LDAP_PROTOCOL_ERROR, "SASL bind requires LDAPv3" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+
+ if( BER_BVISNULL( &op->orb_mech ) || BER_BVISEMPTY( &op->orb_mech ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "do_bind: no sasl mechanism provided\n" );
+ send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED,
+ "no SASL mechanism provided" );
+ goto cleanup;
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, &op->orb_mech ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ if ( op->o_conn->c_sasl_bind_in_progress ) {
+ if( !bvmatch( &op->o_conn->c_sasl_bind_mech, &op->orb_mech ) ) {
+ /* mechanism changed between bind steps */
+ slap_sasl_reset(op->o_conn);
+ }
+ } else {
+ ber_dupbv(&op->o_conn->c_sasl_bind_mech, &op->orb_mech);
+ }
+
+ /* Set the bindop for the benefit of in-directory SASL lookups */
+ op->o_conn->c_sasl_bindop = op;
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ rs->sr_err = slap_sasl_bind( op, rs );
+
+ goto cleanup;
+
+ } else {
+ /* Not SASL, cancel any in-progress bind */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ if ( !BER_BVISNULL( &op->o_conn->c_sasl_bind_mech ) ) {
+ free( op->o_conn->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &op->o_conn->c_sasl_bind_mech );
+ }
+ op->o_conn->c_sasl_bind_in_progress = 0;
+
+ slap_sasl_reset( op->o_conn );
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ }
+
+ if ( op->orb_method == LDAP_AUTH_SIMPLE ) {
+ BER_BVSTR( &op->orb_mech, "SIMPLE" );
+ /* accept "anonymous" binds */
+ if ( BER_BVISEMPTY( &op->orb_cred ) || BER_BVISEMPTY( &op->o_req_ndn ) ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ if( !BER_BVISEMPTY( &op->orb_cred ) &&
+ !( global_allows & SLAP_ALLOW_BIND_ANON_CRED ))
+ {
+ /* cred is not empty, disallow */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+
+ } else if ( !BER_BVISEMPTY( &op->o_req_ndn ) &&
+ !( global_allows & SLAP_ALLOW_BIND_ANON_DN ))
+ {
+ /* DN is not empty, disallow */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text =
+ "unauthenticated bind (DN with no password) disallowed";
+
+ } else if ( global_disallows & SLAP_DISALLOW_BIND_ANON ) {
+ /* disallow */
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ rs->sr_text = "anonymous bind disallowed";
+
+ } else {
+ backend_check_restrictions( op, rs, &op->orb_mech );
+ }
+
+ /*
+ * we already forced connection to "anonymous",
+ * just need to send success
+ */
+ send_ldap_result( op, rs );
+ Debug( LDAP_DEBUG_TRACE, "do_bind: v%d anonymous bind\n",
+ op->o_protocol );
+ goto cleanup;
+
+ } else if ( global_disallows & SLAP_DISALLOW_BIND_SIMPLE ) {
+ /* disallow simple authentication */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "unwilling to perform simple authentication";
+
+ send_ldap_result( op, rs );
+ Debug( LDAP_DEBUG_TRACE,
+ "do_bind: v%d simple bind(%s) disallowed\n",
+ op->o_protocol, op->o_req_ndn.bv_val );
+ goto cleanup;
+ }
+
+ } else {
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ rs->sr_text = "unknown authentication method";
+
+ send_ldap_result( op, rs );
+ Debug( LDAP_DEBUG_TRACE,
+ "do_bind: v%d unknown authentication method (%d)\n",
+ op->o_protocol, op->orb_method );
+ goto cleanup;
+ }
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+
+ if ( (op->o_bd = select_backend( &op->o_req_ndn, 0 )) == NULL ) {
+ /* don't return referral for bind requests */
+ /* noSuchObject is not allowed to be returned by bind */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ op->o_bd = bd;
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ if( op->o_bd->be_bind ) {
+ op->o_conn->c_authz_cookie = NULL;
+
+ rs->sr_err = (op->o_bd->be_bind)( op, rs );
+
+ if ( rs->sr_err == 0 ) {
+ (void)fe_op_bind_success( op, rs );
+
+ } else if ( !BER_BVISNULL( &op->orb_edn ) ) {
+ free( op->orb_edn.bv_val );
+ BER_BVZERO( &op->orb_edn );
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within naming context" );
+ }
+
+cleanup:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
+
+int
+fe_op_lastbind( Operation *op )
+{
+ Operation op2 = *op;
+ SlapReply r2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ LDAPControl c, *ca[2];
+ Modifications *m;
+ Entry *e;
+ Attribute *a;
+ char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval timestamp;
+ time_t bindtime = (time_t)-1;
+ int rc;
+
+ rc = be_entry_get_rw( op, &op->o_conn->c_ndn, NULL, NULL, 0, &e );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ /* get authTimestamp attribute, if it exists */
+ if ( (a = attr_find( e->e_attrs, slap_schema.si_ad_pwdLastSuccess )) != NULL ) {
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+
+ if ( lutil_parsetime( a->a_nvals[0].bv_val, &tm ) == 0 ) {
+ lutil_tm2time( &tm, &tt );
+ bindtime = tt.tt_sec;
+ }
+ Debug( LDAP_DEBUG_TRACE, "fe_op_lastbind: "
+ "old pwdLastSuccess value=%s %lds ago\n",
+ a->a_nvals[0].bv_val, bindtime == (time_t)-1 ? -1 : op->o_time - bindtime );
+
+ /*
+ * TODO: If the recorded bind time is within configurable precision,
+ * it doesn't need to be updated (save a write for nothing)
+ */
+ if ( bindtime != (time_t)-1 &&
+ op->o_time <= bindtime + op->o_bd->be_lastbind_precision ) {
+ be_entry_release_r( op, e );
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* update the authTimestamp in the user's entry with the current time */
+ timestamp.bv_val = nowstr;
+ timestamp.bv_len = sizeof(nowstr);
+ slap_timestamp( &op->o_time, &timestamp );
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = 0;
+ m->sml_type = slap_schema.si_ad_pwdLastSuccess->ad_cname;
+ m->sml_desc = slap_schema.si_ad_pwdLastSuccess;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+
+ ber_dupbv( &m->sml_values[0], &timestamp );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp );
+
+ be_entry_release_r( op, e );
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_req_dn = op->o_conn->c_dn;
+ op2.o_req_ndn = op->o_conn->c_ndn;
+ op2.o_callback = &cb;
+ op2.orm_modlist = m;
+ op2.orm_no_opattrs = 0;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ /*
+ * Code for forwarding of updates adapted from ppolicy.c of slapo-ppolicy
+ *
+ * If this server is a shadow and forward_updates is true,
+ * use the frontend to perform this modify. That will trigger
+ * the update referral, which can then be forwarded by the
+ * chain overlay. Obviously the updateref and chain overlay
+ * must be configured appropriately for this to be useful.
+ */
+ if ( SLAP_SHADOW( op->o_bd ) ) {
+ op2.o_bd = frontendDB;
+
+ /* Must use Relax control since these are no-user-mod */
+ op2.o_relax = SLAP_CONTROL_CRITICAL;
+ op2.o_ctrls = ca;
+ ca[0] = &c;
+ ca[1] = NULL;
+ BER_BVZERO( &c.ldctl_value );
+ c.ldctl_iscritical = 1;
+ c.ldctl_oid = LDAP_CONTROL_RELAX;
+ } else {
+ /* If not forwarding, don't update opattrs and don't replicate */
+ if ( SLAP_SINGLE_SHADOW( op->o_bd )) {
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+ }
+ }
+
+ rc = op2.o_bd->be_modify( &op2, &r2 );
+ slap_mods_free( m, 1 );
+
+done:
+ return rc;
+}
+
+int
+fe_op_bind_success( Operation *op, SlapReply *rs )
+{
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ if( op->o_conn->c_authz_backend == NULL ) {
+ op->o_conn->c_authz_backend = op->o_bd;
+ }
+
+ /* be_bind returns regular/global edn */
+ if( !BER_BVISEMPTY( &op->orb_edn ) ) {
+ op->o_conn->c_dn = op->orb_edn;
+ } else {
+ ber_dupbv(&op->o_conn->c_dn, &op->o_req_dn);
+ }
+
+ ber_dupbv( &op->o_conn->c_ndn, &op->o_req_ndn );
+
+ /* op->o_conn->c_sb may be 0 for internal operations */
+ if( !BER_BVISEMPTY( &op->o_conn->c_dn ) && op->o_conn->c_sb != 0 ) {
+ ber_len_t max = sockbuf_max_incoming_auth;
+ ber_sockbuf_ctrl( op->o_conn->c_sb,
+ LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ /* log authorization identity */
+ Debug( LDAP_DEBUG_STATS,
+ "%s BIND dn=\"%s\" mech=%s bind_ssf=0 ssf=%d\n",
+ op->o_log_prefix,
+ op->o_conn->c_dn.bv_val, op->orb_mech.bv_val, op->o_conn->c_ssf );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "do_bind: v%d bind: \"%s\" to \"%s\"\n",
+ op->o_protocol, op->o_req_dn.bv_val, op->o_conn->c_dn.bv_val );
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ if ( SLAP_LASTBIND( op->o_bd ) ) {
+ fe_op_lastbind( op );
+ }
+
+ /* send this here to avoid a race condition */
+ send_ldap_result( op, rs );
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/cancel.c b/servers/slapd/cancel.c
new file mode 100644
index 0000000..3e5ee99
--- /dev/null
+++ b/servers/slapd/cancel.c
@@ -0,0 +1,157 @@
+/* cancel.c - LDAP cancel extended operation */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+
+#include <lber_pvt.h>
+#include <lutil.h>
+
+const struct berval slap_EXOP_CANCEL = BER_BVC(LDAP_EXOP_CANCEL);
+
+int cancel_extop( Operation *op, SlapReply *rs )
+{
+ Operation *o;
+ int rc;
+ int opid;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ assert( ber_bvcmp( &slap_EXOP_CANCEL, &op->ore_reqoid ) == 0 );
+
+ if ( op->ore_reqdata == NULL ) {
+ rs->sr_text = "no message ID supplied";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->ore_reqdata->bv_len == 0 ) {
+ rs->sr_text = "empty request data field";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, op->ore_reqdata, 0 );
+
+ if ( ber_scanf( ber, "{i}", &opid ) == LBER_ERROR ) {
+ rs->sr_text = "message ID parse failed";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s CANCEL msg=%d\n",
+ op->o_log_prefix, opid );
+
+ if ( opid < 0 ) {
+ rs->sr_text = "message ID invalid";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( opid == op->o_msgid ) {
+ op->o_cancel = SLAP_CANCEL_DONE;
+ return LDAP_SUCCESS;
+ }
+
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ if ( op->o_abandon ) {
+ /* FIXME: Should instead reject the cancel/abandon of this op, but
+ * it seems unsafe to reset op->o_abandon once it is set. ITS#6138.
+ */
+ rc = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "tried to abandon or cancel this operation";
+ goto out;
+ }
+
+ LDAP_STAILQ_FOREACH( o, &op->o_conn->c_pending_ops, o_next ) {
+ if ( o->o_msgid == opid ) {
+ /* TODO: We could instead remove the cancelled operation
+ * from c_pending_ops like Abandon does, and send its
+ * response here. Not if it is pending because of a
+ * congested connection though.
+ */
+ rc = LDAP_CANNOT_CANCEL;
+ rs->sr_text = "too busy for Cancel, try Abandon instead";
+ goto out;
+ }
+ }
+
+ LDAP_STAILQ_FOREACH( o, &op->o_conn->c_ops, o_next ) {
+ if ( o->o_msgid == opid ) {
+ break;
+ }
+ }
+
+ if ( o == NULL ) {
+ rc = LDAP_NO_SUCH_OPERATION;
+ rs->sr_text = "message ID not found";
+
+ } else if ( o->o_tag == LDAP_REQ_BIND
+ || o->o_tag == LDAP_REQ_UNBIND
+ || o->o_tag == LDAP_REQ_ABANDON ) {
+ rc = LDAP_CANNOT_CANCEL;
+
+ } else if ( o->o_cancel != SLAP_CANCEL_NONE ) {
+ rc = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "message ID already being cancelled";
+
+ } else if ( o->o_abandon ) {
+ rc = LDAP_TOO_LATE;
+
+ } else {
+ rc = LDAP_SUCCESS;
+ o->o_cancel = SLAP_CANCEL_REQ;
+ o->o_abandon = 1;
+ }
+
+ out:
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ if ( rc == LDAP_SUCCESS ) {
+ LDAP_STAILQ_FOREACH( op->o_bd, &backendDB, be_next ) {
+ if( !op->o_bd->be_cancel ) continue;
+
+ op->oq_cancel.rs_msgid = opid;
+ if ( op->o_bd->be_cancel( op, rs ) == LDAP_SUCCESS ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ do {
+ /* Fake a cond_wait with thread_yield, then
+ * verify the result properly mutex-protected.
+ */
+ while ( o->o_cancel == SLAP_CANCEL_REQ )
+ ldap_pvt_thread_yield();
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ rc = o->o_cancel;
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ } while ( rc == SLAP_CANCEL_REQ );
+
+ if ( rc == SLAP_CANCEL_ACK ) {
+ rc = LDAP_SUCCESS;
+ }
+
+ o->o_cancel = SLAP_CANCEL_DONE;
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/ch_malloc.c b/servers/slapd/ch_malloc.c
new file mode 100644
index 0000000..6acf831
--- /dev/null
+++ b/servers/slapd/ch_malloc.c
@@ -0,0 +1,142 @@
+/* ch_malloc.c - malloc routines that test returns from malloc and friends */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#define CH_FREE 1
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+BerMemoryFunctions ch_mfuncs = {
+ (BER_MEMALLOC_FN *)ch_malloc,
+ (BER_MEMCALLOC_FN *)ch_calloc,
+ (BER_MEMREALLOC_FN *)ch_realloc,
+ (BER_MEMFREE_FN *)ch_free
+};
+
+void *
+ch_malloc(
+ ber_len_t size
+)
+{
+ void *new;
+
+ if ( (new = (void *) ber_memalloc_x( size, NULL )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "ch_malloc of %lu bytes failed\n",
+ (long) size );
+ assert( 0 );
+ exit( EXIT_FAILURE );
+ }
+
+ return( new );
+}
+
+void *
+ch_realloc(
+ void *block,
+ ber_len_t size
+)
+{
+ void *new, *ctx;
+
+ if ( block == NULL ) {
+ return( ch_malloc( size ) );
+ }
+
+ if( size == 0 ) {
+ ch_free( block );
+ return NULL;
+ }
+
+ ctx = slap_sl_context( block );
+ if ( ctx ) {
+ return slap_sl_realloc( block, size, ctx );
+ }
+
+ if ( (new = (void *) ber_memrealloc_x( block, size, NULL )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "ch_realloc of %lu bytes failed\n",
+ (long) size );
+ assert( 0 );
+ exit( EXIT_FAILURE );
+ }
+
+ return( new );
+}
+
+void *
+ch_calloc(
+ ber_len_t nelem,
+ ber_len_t size
+)
+{
+ void *new;
+
+ if ( (new = (void *) ber_memcalloc_x( nelem, size, NULL )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "ch_calloc of %lu elems of %lu bytes failed\n",
+ (long) nelem, (long) size );
+ assert( 0 );
+ exit( EXIT_FAILURE );
+ }
+
+ return( new );
+}
+
+char *
+ch_strdup(
+ const char *string
+)
+{
+ char *new;
+
+ if ( (new = ber_strdup_x( string, NULL )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "ch_strdup(%s) failed\n", string );
+ assert( 0 );
+ exit( EXIT_FAILURE );
+ }
+
+ return( new );
+}
+
+void
+ch_free( void *ptr )
+{
+ void *ctx;
+
+ ctx = slap_sl_context( ptr );
+ if (ctx) {
+ slap_sl_free( ptr, ctx );
+ } else {
+ ber_memfree_x( ptr, NULL );
+ }
+}
+
diff --git a/servers/slapd/compare.c b/servers/slapd/compare.c
new file mode 100644
index 0000000..e7a4f88
--- /dev/null
+++ b/servers/slapd/compare.c
@@ -0,0 +1,413 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+
+int
+do_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval dn = BER_BVNULL;
+ struct berval desc = BER_BVNULL;
+ struct berval value = BER_BVNULL;
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_compare\n",
+ op->o_log_prefix );
+ /*
+ * Parse the compare request. It looks like this:
+ *
+ * CompareRequest := [APPLICATION 14] SEQUENCE {
+ * entry DistinguishedName,
+ * ava SEQUENCE {
+ * type AttributeType,
+ * value AttributeValue
+ * }
+ * }
+ */
+
+ if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ if ( ber_scanf( op->o_ber, "{mm}", &desc, &value ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_compare: get ava failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ if ( ber_scanf( op->o_ber, /*{*/ "}" ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_compare: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_compare: get_ctrls failed\n",
+ op->o_log_prefix );
+ goto cleanup;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_compare: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto cleanup;
+ }
+
+ Debug( LDAP_DEBUG_STATS,
+ "%s CMP dn=\"%s\" attr=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ desc.bv_val );
+
+ rs->sr_err = slap_bv2ad( &desc, &ava.aa_desc, &rs->sr_text );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = slap_bv2undef_ad( &desc, &ava.aa_desc,
+ &rs->sr_text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+ }
+
+ rs->sr_err = asserted_value_validate_normalize( ava.aa_desc,
+ ava.aa_desc->ad_type->sat_equality,
+ SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &value, &ava.aa_value, &rs->sr_text, op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ op->orc_ava = &ava;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "do_compare: dn (%s) attr (%s) value (%s)\n",
+ op->o_req_dn.bv_val,
+ ava.aa_desc->ad_cname.bv_val, ava.aa_value.bv_val );
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_compare( op, rs );
+ if ( rs->sr_err == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+cleanup:;
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &ava.aa_value ) ) {
+ op->o_tmpfree( ava.aa_value.bv_val, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+int
+fe_op_compare( Operation *op, SlapReply *rs )
+{
+ Entry *entry = NULL;
+ AttributeAssertion *ava = op->orc_ava;
+ BackendDB *bd = op->o_bd;
+
+ if( strcasecmp( op->o_req_ndn.bv_val, LDAP_ROOT_DSE ) == 0 ) {
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ rs->sr_err = root_dse_info( op->o_conn, &entry, &rs->sr_text );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ rs->sr_err = 0;
+ goto cleanup;
+ }
+
+ rs->sr_err = schema_info( &entry, &rs->sr_text );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ rs->sr_err = 0;
+ goto cleanup;
+ }
+ }
+
+ if( entry ) {
+ rs->sr_err = slap_compare_entry( op, entry, ava );
+ entry_free( entry );
+
+ send_ldap_result( op, rs );
+
+ if( rs->sr_err == LDAP_COMPARE_TRUE ||
+ rs->sr_err == LDAP_COMPARE_FALSE )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ goto cleanup;
+ }
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL ) {
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+
+ rs->sr_err = LDAP_REFERRAL;
+ if (!rs->sr_ref) rs->sr_ref = default_referral;
+ op->o_bd = bd;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref );
+ rs->sr_err = 0;
+ goto cleanup;
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check for referrals */
+ if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ if ( SLAP_SHADOW(op->o_bd) && get_dontUseCopy(op) ) {
+ /* don't use shadow copy */
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "copy not used" );
+
+ } else if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "entryDN compare not supported" );
+
+ } else if ( ava->aa_desc == slap_schema.si_ad_subschemaSubentry ) {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "subschemaSubentry compare not supported" );
+
+#ifndef SLAP_COMPARE_IN_FRONTEND
+ } else if ( ava->aa_desc == slap_schema.si_ad_hasSubordinates
+ && op->o_bd->be_has_subordinates )
+ {
+ int rc, hasSubordinates = LDAP_SUCCESS;
+
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &entry );
+ if ( rc == 0 && entry ) {
+ if ( ! access_allowed( op, entry,
+ ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
+ {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+
+ } else {
+ rc = rs->sr_err = op->o_bd->be_has_subordinates( op,
+ entry, &hasSubordinates );
+ be_entry_release_r( op, entry );
+ }
+ }
+
+ if ( rc == 0 ) {
+ int asserted;
+
+ asserted = bvmatch( &ava->aa_value, &slap_true_bv )
+ ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ if ( hasSubordinates == asserted ) {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+
+ } else {
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ }
+
+ } else {
+ /* return error only if "disclose"
+ * is granted on the object */
+ if ( backend_access( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) == LDAP_INSUFFICIENT_ACCESS )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rc == 0 ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ } else if ( op->o_bd->be_compare ) {
+ rs->sr_err = op->o_bd->be_compare( op, rs );
+
+#endif /* ! SLAP_COMPARE_IN_FRONTEND */
+ } else {
+ rs->sr_err = SLAP_CB_CONTINUE;
+ }
+
+ if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+ /* do our best to compare that AVA
+ *
+ * NOTE: this code is used only
+ * if SLAP_COMPARE_IN_FRONTEND
+ * is #define'd (it's not by default)
+ * or if op->o_bd->be_compare is NULL.
+ *
+ * FIXME: one potential issue is that
+ * if SLAP_COMPARE_IN_FRONTEND overlays
+ * are not executed for compare. */
+ BerVarray vals = NULL;
+ int rc = LDAP_OTHER;
+
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ ava->aa_desc, &vals, ACL_COMPARE );
+ switch ( rs->sr_err ) {
+ default:
+ /* return error only if "disclose"
+ * is granted on the object */
+ if ( backend_access( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL )
+ == LDAP_INSUFFICIENT_ACCESS )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ break;
+
+ case LDAP_SUCCESS:
+ if ( value_find_ex( op->oq_compare.rs_ava->aa_desc,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ vals, &ava->aa_value, op->o_tmpmemctx ) == 0 )
+ {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+
+ } else {
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ }
+ rc = LDAP_SUCCESS;
+ break;
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rc == 0 ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ if ( vals ) {
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+cleanup:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
+
+int slap_compare_entry(
+ Operation *op,
+ Entry *e,
+ AttributeAssertion *ava )
+{
+ int rc = LDAP_COMPARE_FALSE;
+ Attribute *a;
+
+ if ( ! access_allowed( op, e,
+ ava->aa_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ rc = LDAP_ASSERTION_FAILED;
+ goto done;
+ }
+
+ a = attrs_find( e->e_attrs, ava->aa_desc );
+ if( a == NULL ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ for(;
+ a != NULL;
+ a = attrs_find( a->a_next, ava->aa_desc ))
+ {
+ if (( ava->aa_desc != a->a_desc ) && ! access_allowed( op,
+ e, a->a_desc, &ava->aa_value, ACL_COMPARE, NULL ) )
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ break;
+ }
+
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &ava->aa_value, NULL, op->o_tmpmemctx ) == 0 )
+ {
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+
+done:
+ if( rc != LDAP_COMPARE_TRUE && rc != LDAP_COMPARE_FALSE ) {
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/component.c b/servers/slapd/component.c
new file mode 100644
index 0000000..37ce97d
--- /dev/null
+++ b/servers/slapd/component.c
@@ -0,0 +1,1393 @@
+/* component.c -- Component Filter Match Routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 by IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include <ldap.h>
+#include "slap.h"
+
+#ifdef LDAP_COMP_MATCH
+
+#include "component.h"
+
+/*
+ * Following function pointers are initialized
+ * when a component module is loaded
+ */
+alloc_nibble_func* nibble_mem_allocator = NULL;
+free_nibble_func* nibble_mem_free = NULL;
+convert_attr_to_comp_func* attr_converter = NULL;
+convert_assert_to_comp_func* assert_converter = NULL ;
+free_component_func* component_destructor = NULL ;
+test_component_func* test_components = NULL;
+test_membership_func* is_aliased_attribute = NULL;
+component_encoder_func* component_encoder = NULL;
+get_component_info_func* get_component_description = NULL;
+#define OID_ALL_COMP_MATCH "1.2.36.79672281.1.13.6"
+#define OID_COMP_FILTER_MATCH "1.2.36.79672281.1.13.2"
+#define MAX_LDAP_STR_LEN 128
+
+static int
+peek_componentId_type( ComponentAssertionValue* cav );
+
+static int
+strip_cav_str( ComponentAssertionValue* cav, char* str);
+
+static int
+peek_cav_str( ComponentAssertionValue* cav, char* str );
+
+static int
+parse_comp_filter( Operation* op, ComponentAssertionValue* cav,
+ ComponentFilter** filt, const char** text );
+
+static void
+free_comp_filter( ComponentFilter* f );
+
+static int
+test_comp_filter( Syntax *syn, ComponentSyntaxInfo *a, ComponentFilter *f );
+
+int
+componentCertificateValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ return LDAP_SUCCESS;
+}
+
+int
+componentFilterValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ return LDAP_SUCCESS;
+}
+
+int
+allComponentsValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ return LDAP_SUCCESS;
+}
+
+int
+componentFilterMatch (
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ ComponentSyntaxInfo *csi_attr = (ComponentSyntaxInfo*)value;
+ MatchingRuleAssertion * ma = (MatchingRuleAssertion*)assertedValue;
+ int rc;
+
+ if ( !mr || !ma->ma_cf ) return LDAP_INAPPROPRIATE_MATCHING;
+
+ /* Check if the component module is loaded */
+ if ( !attr_converter || !nibble_mem_allocator ) {
+ return LDAP_OTHER;
+ }
+
+ rc = test_comp_filter( syntax, csi_attr, ma->ma_cf );
+
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ *matchp = 0;
+ return LDAP_SUCCESS;
+ }
+ else if ( rc == LDAP_COMPARE_FALSE ) {
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+ else {
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+}
+
+int
+directoryComponentsMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ /* Only for registration */
+ *matchp = 0;
+ return LDAP_SUCCESS;
+}
+
+int
+allComponentsMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ /* Only for registration */
+ *matchp = 0;
+ return LDAP_SUCCESS;
+}
+
+static int
+slapd_ber2cav( struct berval* bv, ComponentAssertionValue* cav )
+{
+ cav->cav_ptr = cav->cav_buf = bv->bv_val;
+ cav->cav_end = bv->bv_val + bv->bv_len;
+
+ return LDAP_SUCCESS;
+}
+
+ComponentReference*
+dup_comp_ref ( Operation* op, ComponentReference* cr )
+{
+ ComponentReference* dup_cr;
+ ComponentId* ci_curr;
+ ComponentId** ci_temp;
+
+ dup_cr = op->o_tmpalloc( sizeof( ComponentReference ), op->o_tmpmemctx );
+
+ dup_cr->cr_len = cr->cr_len;
+ dup_cr->cr_string = cr->cr_string;
+
+ ci_temp = &dup_cr->cr_list;
+ ci_curr = cr->cr_list;
+
+ for ( ; ci_curr != NULL ;
+ ci_curr = ci_curr->ci_next, ci_temp = &(*ci_temp)->ci_next )
+ {
+ *ci_temp = op->o_tmpalloc( sizeof( ComponentId ), op->o_tmpmemctx );
+ if ( !*ci_temp ) return NULL;
+ **ci_temp = *ci_curr;
+ }
+
+ dup_cr->cr_curr = dup_cr->cr_list;
+
+ return dup_cr;
+}
+
+static int
+dup_comp_filter_list (
+ Operation *op,
+ struct berval *bv,
+ ComponentFilter* in_f,
+ ComponentFilter** out_f )
+{
+ ComponentFilter **new, *f;
+ int rc;
+
+ new = out_f;
+ for ( f = in_f; f != NULL; f = f->cf_next ) {
+ rc = dup_comp_filter( op, bv, f, new );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ new = &(*new)->cf_next;
+ }
+ return LDAP_SUCCESS;
+}
+
+int
+get_len_of_next_assert_value ( struct berval* bv, char separator )
+{
+ ber_len_t i = 0;
+ while (1) {
+ if ( (bv->bv_val[ i ] == separator) || ( i >= bv->bv_len) )
+ break;
+ i++;
+ }
+ bv->bv_val += (i + 1);
+ bv->bv_len -= (i + 1);
+ return i;
+}
+
+int
+dup_comp_filter_item (
+ Operation *op,
+ struct berval* assert_bv,
+ ComponentAssertion* in_ca,
+ ComponentAssertion** out_ca )
+{
+ int len;
+
+ if ( !in_ca->ca_comp_ref ) return SLAPD_DISCONNECT;
+
+ *out_ca = op->o_tmpalloc( sizeof( ComponentAssertion ), op->o_tmpmemctx );
+ if ( !(*out_ca) ) return LDAP_NO_MEMORY;
+
+ (*out_ca)->ca_comp_data.cd_tree = NULL;
+ (*out_ca)->ca_comp_data.cd_mem_op = NULL;
+
+ (*out_ca)->ca_comp_ref = dup_comp_ref ( op, in_ca->ca_comp_ref );
+ (*out_ca)->ca_use_def = 0;
+ (*out_ca)->ca_ma_rule = in_ca->ca_ma_rule;
+
+ (*out_ca)->ca_ma_value.bv_val = assert_bv->bv_val;
+ len = get_len_of_next_assert_value ( assert_bv, '$' );
+ if ( len <= 0 ) return SLAPD_DISCONNECT;
+ (*out_ca)->ca_ma_value.bv_len = len;
+
+ return LDAP_SUCCESS;
+}
+
+int
+dup_comp_filter (
+ Operation* op,
+ struct berval *bv,
+ ComponentFilter *in_f,
+ ComponentFilter **out_f )
+{
+ int rc;
+ ComponentFilter dup_f = {0};
+
+ if ( !in_f ) return LDAP_PROTOCOL_ERROR;
+
+ switch ( in_f->cf_choice ) {
+ case LDAP_COMP_FILTER_AND:
+ rc = dup_comp_filter_list( op, bv, in_f->cf_and, &dup_f.cf_and);
+ dup_f.cf_choice = LDAP_COMP_FILTER_AND;
+ break;
+ case LDAP_COMP_FILTER_OR:
+ rc = dup_comp_filter_list( op, bv, in_f->cf_or, &dup_f.cf_or);
+ dup_f.cf_choice = LDAP_COMP_FILTER_OR;
+ break;
+ case LDAP_COMP_FILTER_NOT:
+ rc = dup_comp_filter( op, bv, in_f->cf_not, &dup_f.cf_not);
+ dup_f.cf_choice = LDAP_COMP_FILTER_NOT;
+ break;
+ case LDAP_COMP_FILTER_ITEM:
+ rc = dup_comp_filter_item( op, bv, in_f->cf_ca ,&dup_f.cf_ca );
+ dup_f.cf_choice = LDAP_COMP_FILTER_ITEM;
+ break;
+ default:
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ *out_f = op->o_tmpalloc( sizeof(dup_f), op->o_tmpmemctx );
+ **out_f = dup_f;
+ }
+
+ return( rc );
+}
+
+int
+get_aliased_filter_aa ( Operation* op, AttributeAssertion* a_assert, AttributeAliasing* aa, const char** text )
+{
+ struct berval assert_bv;
+
+ Debug( LDAP_DEBUG_FILTER, "get_aliased_filter\n" );
+
+ if ( !aa->aa_cf )
+ return LDAP_PROTOCOL_ERROR;
+
+ assert_bv = a_assert->aa_value;
+ /*
+ * Duplicate aa->aa_cf to ma->ma_cf by replacing the
+ * the component assertion value in assert_bv
+ * Multiple values may be separated with '$'
+ */
+ return dup_comp_filter ( op, &assert_bv, aa->aa_cf, &a_assert->aa_cf );
+}
+
+int
+get_aliased_filter( Operation* op,
+ MatchingRuleAssertion* ma, AttributeAliasing* aa,
+ const char** text )
+{
+ struct berval assert_bv;
+
+ Debug( LDAP_DEBUG_FILTER, "get_aliased_filter\n" );
+
+ if ( !aa->aa_cf ) return LDAP_PROTOCOL_ERROR;
+
+ assert_bv = ma->ma_value;
+ /* Attribute Description is replaced with aliased one */
+ ma->ma_desc = aa->aa_aliased_ad;
+ ma->ma_rule = aa->aa_mr;
+ /*
+ * Duplicate aa->aa_cf to ma->ma_cf by replacing the
+ * the component assertion value in assert_bv
+ * Multiple values may be separated with '$'
+ */
+ return dup_comp_filter ( op, &assert_bv, aa->aa_cf, &ma->ma_cf );
+}
+
+int
+get_comp_filter( Operation* op, struct berval* bv,
+ ComponentFilter** filt, const char **text )
+{
+ ComponentAssertionValue cav;
+ int rc;
+
+ Debug( LDAP_DEBUG_FILTER, "get_comp_filter\n" );
+ if ( (rc = slapd_ber2cav(bv, &cav) ) != LDAP_SUCCESS ) {
+ return rc;
+ }
+ rc = parse_comp_filter( op, &cav, filt, text );
+ /* bv->bv_val = cav.cav_ptr; */
+
+ return rc;
+}
+
+static void
+eat_whsp( ComponentAssertionValue* cav )
+{
+ for ( ; ( *cav->cav_ptr == ' ' ) && ( cav->cav_ptr < cav->cav_end ) ; ) {
+ cav->cav_ptr++;
+ }
+}
+
+static int
+cav_cur_len( ComponentAssertionValue* cav )
+{
+ return cav->cav_end - cav->cav_ptr;
+}
+
+static ber_tag_t
+comp_first_element( ComponentAssertionValue* cav )
+{
+ eat_whsp( cav );
+ if ( cav_cur_len( cav ) >= 8 && strncmp( cav->cav_ptr, "item", 4 ) == 0 ) {
+ return LDAP_COMP_FILTER_ITEM;
+
+ } else if ( cav_cur_len( cav ) >= 7 &&
+ strncmp( cav->cav_ptr, "and", 3 ) == 0 )
+ {
+ return LDAP_COMP_FILTER_AND;
+
+ } else if ( cav_cur_len( cav ) >= 6 &&
+ strncmp( cav->cav_ptr, "or" , 2 ) == 0 )
+ {
+ return LDAP_COMP_FILTER_OR;
+
+ } else if ( cav_cur_len( cav ) >= 7 &&
+ strncmp( cav->cav_ptr, "not", 3 ) == 0 )
+ {
+ return LDAP_COMP_FILTER_NOT;
+
+ } else {
+ return LDAP_COMP_FILTER_UNDEFINED;
+ }
+}
+
+static ber_tag_t
+comp_next_element( ComponentAssertionValue* cav )
+{
+ eat_whsp( cav );
+ if ( *(cav->cav_ptr) == ',' ) {
+ /* move pointer to the next CA */
+ cav->cav_ptr++;
+ return comp_first_element( cav );
+ }
+ else return LDAP_COMP_FILTER_UNDEFINED;
+}
+
+static int
+get_comp_filter_list( Operation *op, ComponentAssertionValue *cav,
+ ComponentFilter** f, const char** text )
+{
+ ComponentFilter **new;
+ int err;
+ ber_tag_t tag;
+
+ Debug( LDAP_DEBUG_FILTER, "get_comp_filter_list\n" );
+ new = f;
+ for ( tag = comp_first_element( cav );
+ tag != LDAP_COMP_FILTER_UNDEFINED;
+ tag = comp_next_element( cav ) )
+ {
+ err = parse_comp_filter( op, cav, new, text );
+ if ( err != LDAP_SUCCESS ) return ( err );
+ new = &(*new)->cf_next;
+ }
+ *new = NULL;
+
+ return( LDAP_SUCCESS );
+}
+
+static int
+get_componentId( Operation *op, ComponentAssertionValue* cav,
+ ComponentId ** cid, const char** text )
+{
+ ber_tag_t type;
+ ComponentId _cid;
+ int len;
+
+ type = peek_componentId_type( cav );
+
+ Debug( LDAP_DEBUG_FILTER, "get_compId [%lu]\n",
+ (unsigned long) type );
+ len = 0;
+ _cid.ci_type = type;
+ _cid.ci_next = NULL;
+ switch ( type ) {
+ case LDAP_COMPREF_IDENTIFIER :
+ _cid.ci_val.ci_identifier.bv_val = cav->cav_ptr;
+ for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' &&
+ cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ );
+ _cid.ci_val.ci_identifier.bv_len = len;
+ cav->cav_ptr += len;
+ break;
+ case LDAP_COMPREF_FROM_BEGINNING :
+ for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' &&
+ cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ );
+ _cid.ci_val.ci_from_beginning = strtol( cav->cav_ptr, NULL, 0 );
+ cav->cav_ptr += len;
+ break;
+ case LDAP_COMPREF_FROM_END :
+ for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' &&
+ cav->cav_ptr[len] != '.' && cav->cav_ptr[len] != '\"' ; len++ );
+ _cid.ci_val.ci_from_end = strtol( cav->cav_ptr, NULL, 0 );
+ cav->cav_ptr += len;
+ break;
+ case LDAP_COMPREF_COUNT :
+ _cid.ci_val.ci_count = 0;
+ cav->cav_ptr++;
+ break;
+ case LDAP_COMPREF_CONTENT :
+ _cid.ci_val.ci_content = 1;
+ cav->cav_ptr += strlen("content");
+ break;
+ case LDAP_COMPREF_SELECT :
+ if ( cav->cav_ptr[len] != '(' ) return LDAP_COMPREF_UNDEFINED;
+ for( ;cav->cav_ptr[len] != ' ' && cav->cav_ptr[len] != '\0' &&
+ cav->cav_ptr[len] != '\"' && cav->cav_ptr[len] != ')'
+ ; len++ );
+ _cid.ci_val.ci_select_value.bv_val = cav->cav_ptr + 1;
+ _cid.ci_val.ci_select_value.bv_len = len - 1 ;
+ cav->cav_ptr += len + 1;
+ break;
+ case LDAP_COMPREF_ALL :
+ _cid.ci_val.ci_all = '*';
+ cav->cav_ptr++;
+ break;
+ default :
+ return LDAP_COMPREF_UNDEFINED;
+ }
+
+ if ( op ) {
+ *cid = op->o_tmpalloc( sizeof( ComponentId ), op->o_tmpmemctx );
+ } else {
+ *cid = SLAP_MALLOC( sizeof( ComponentId ) );
+ }
+ if (*cid == NULL) {
+ return LDAP_NO_MEMORY;
+ }
+ **cid = _cid;
+ return LDAP_SUCCESS;
+}
+
+static int
+peek_componentId_type( ComponentAssertionValue* cav )
+{
+ eat_whsp( cav );
+
+ if ( cav->cav_ptr[0] == '-' ) {
+ return LDAP_COMPREF_FROM_END;
+
+ } else if ( cav->cav_ptr[0] == '(' ) {
+ return LDAP_COMPREF_SELECT;
+
+ } else if ( cav->cav_ptr[0] == '*' ) {
+ return LDAP_COMPREF_ALL;
+
+ } else if ( cav->cav_ptr[0] == '0' ) {
+ return LDAP_COMPREF_COUNT;
+
+ } else if ( cav->cav_ptr[0] > '0' && cav->cav_ptr[0] <= '9' ) {
+ return LDAP_COMPREF_FROM_BEGINNING;
+
+ } else if ( (cav->cav_end - cav->cav_ptr) >= 7 &&
+ strncmp(cav->cav_ptr,"content",7) == 0 )
+ {
+ return LDAP_COMPREF_CONTENT;
+ } else if ( (cav->cav_ptr[0] >= 'a' && cav->cav_ptr[0] <= 'z') ||
+ (cav->cav_ptr[0] >= 'A' && cav->cav_ptr[0] <= 'Z') )
+ {
+ return LDAP_COMPREF_IDENTIFIER;
+ }
+
+ return LDAP_COMPREF_UNDEFINED;
+}
+
+static ber_tag_t
+comp_next_id( ComponentAssertionValue* cav )
+{
+ if ( *(cav->cav_ptr) == '.' ) {
+ cav->cav_ptr++;
+ return LDAP_COMPREF_DEFINED;
+ }
+
+ return LDAP_COMPREF_UNDEFINED;
+}
+
+
+
+static int
+get_component_reference(
+ Operation *op,
+ ComponentAssertionValue* cav,
+ ComponentReference** cr,
+ const char** text )
+{
+ int rc, count = 0;
+ ber_int_t type;
+ ComponentReference* ca_comp_ref;
+ ComponentId** cr_list;
+ char* start, *end;
+
+ eat_whsp( cav );
+
+ start = cav->cav_ptr;
+ if ( ( rc = strip_cav_str( cav,"\"") ) != LDAP_SUCCESS ) return rc;
+ if ( op ) {
+ ca_comp_ref = op->o_tmpalloc( sizeof( ComponentReference ),
+ op->o_tmpmemctx );
+ } else {
+ ca_comp_ref = SLAP_MALLOC( sizeof( ComponentReference ) );
+ }
+
+ if ( !ca_comp_ref ) return LDAP_NO_MEMORY;
+
+ cr_list = &ca_comp_ref->cr_list;
+
+ for ( type = peek_componentId_type( cav ) ; type != LDAP_COMPREF_UNDEFINED
+ ; type = comp_next_id( cav ), count++ )
+ {
+ rc = get_componentId( op, cav, cr_list, text );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( count == 0 ) ca_comp_ref->cr_curr = ca_comp_ref->cr_list;
+ cr_list = &(*cr_list)->ci_next;
+
+ } else if ( rc == LDAP_COMPREF_UNDEFINED ) {
+ if ( op ) {
+ op->o_tmpfree( ca_comp_ref , op->o_tmpmemctx );
+ } else {
+ free( ca_comp_ref );
+ }
+ return rc;
+ }
+ }
+ ca_comp_ref->cr_len = count;
+ end = cav->cav_ptr;
+ if ( ( rc = strip_cav_str( cav,"\"") ) != LDAP_SUCCESS ) {
+ if ( op ) {
+ op->o_tmpfree( ca_comp_ref , op->o_tmpmemctx );
+ } else {
+ free( ca_comp_ref );
+ }
+ return rc;
+ }
+
+ *cr = ca_comp_ref;
+ **cr = *ca_comp_ref;
+
+ (*cr)->cr_string.bv_val = start;
+ (*cr)->cr_string.bv_len = end - start + 1;
+
+ return rc;
+}
+
+int
+insert_component_reference(
+ ComponentReference *cr,
+ ComponentReference** cr_list)
+{
+ if ( !cr ) return LDAP_PARAM_ERROR;
+
+ if ( !(*cr_list) ) {
+ *cr_list = cr;
+ cr->cr_next = NULL;
+ } else {
+ cr->cr_next = *cr_list;
+ *cr_list = cr;
+ }
+ return LDAP_SUCCESS;
+}
+
+/*
+ * If there is '.' in the name of a given attribute
+ * the first '.'- following characters are considered
+ * as a component reference of the attribute
+ * EX) userCertificate.toBeSigned.serialNumber
+ * attribute : userCertificate
+ * component reference : toBeSigned.serialNumber
+ */
+int
+is_component_reference( char* attr ) {
+ int i;
+ for ( i=0; attr[i] != '\0' ; i++ ) {
+ if ( attr[i] == '.' ) return (1);
+ }
+ return (0);
+}
+
+int
+extract_component_reference(
+ char* attr,
+ ComponentReference** cr )
+{
+ int i, rc;
+ char* cr_ptr;
+ int cr_len;
+ ComponentAssertionValue cav;
+ char text[1][128];
+
+ for ( i=0; attr[i] != '\0' ; i++ ) {
+ if ( attr[i] == '.' ) break;
+ }
+
+ if (attr[i] != '.' ) return LDAP_PARAM_ERROR;
+ attr[i] = '\0';
+
+ cr_ptr = attr + i + 1 ;
+ cr_len = strlen ( cr_ptr );
+ if ( cr_len <= 0 ) return LDAP_PARAM_ERROR;
+
+ /* enclosed between double quotes*/
+ cav.cav_ptr = cav.cav_buf = ch_malloc (cr_len+2);
+ memcpy( cav.cav_buf+1, cr_ptr, cr_len );
+ cav.cav_buf[0] = '"';
+ cav.cav_buf[cr_len+1] = '"';
+ cav.cav_end = cr_ptr + cr_len + 2;
+
+ rc = get_component_reference ( NULL, &cav, cr, (const char**)text );
+ if ( rc != LDAP_SUCCESS ) return rc;
+ (*cr)->cr_string.bv_val = cav.cav_buf;
+ (*cr)->cr_string.bv_len = cr_len + 2;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+get_ca_use_default( Operation *op,
+ ComponentAssertionValue* cav,
+ int* ca_use_def, const char** text )
+{
+ strip_cav_str( cav, "useDefaultValues" );
+
+ if ( peek_cav_str( cav, "TRUE" ) == LDAP_SUCCESS ) {
+ strip_cav_str( cav, "TRUE" );
+ *ca_use_def = 1;
+
+ } else if ( peek_cav_str( cav, "FALSE" ) == LDAP_SUCCESS ) {
+ strip_cav_str( cav, "FALSE" );
+ *ca_use_def = 0;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+get_matching_rule( Operation *op, ComponentAssertionValue* cav,
+ MatchingRule** mr, const char** text )
+{
+ int count = 0;
+ struct berval rule_text = { 0L, NULL };
+
+ eat_whsp( cav );
+
+ for ( ; ; count++ ) {
+ if ( cav->cav_ptr[count] == ' ' || cav->cav_ptr[count] == ',' ||
+ cav->cav_ptr[count] == '\0' || cav->cav_ptr[count] == '{' ||
+ cav->cav_ptr[count] == '}' || cav->cav_ptr[count] == '\n' )
+ {
+ break;
+ }
+ }
+
+ if ( count == 0 ) {
+ *text = "component matching rule not recognized";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ rule_text.bv_len = count;
+ rule_text.bv_val = cav->cav_ptr;
+ *mr = mr_bvfind( &rule_text );
+ cav->cav_ptr += count;
+ Debug( LDAP_DEBUG_FILTER, "get_matching_rule: %s\n",
+ (*mr)->smr_mrule.mr_oid );
+ if ( *mr == NULL ) {
+ *text = "component matching rule not recognized";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+get_GSER_value( ComponentAssertionValue* cav, struct berval* bv )
+{
+ int count, sequent_dquote, unclosed_brace, succeed;
+
+ eat_whsp( cav );
+ /*
+ * Four cases of GSER <Values>
+ * 1) "..." :
+ * StringVal, GeneralizedTimeVal, UTCTimeVal, ObjectDescriptorVal
+ * 2) '...'B or '...'H :
+ * BitStringVal, OctetStringVal
+ * 3) {...} :
+ * SEQUENCE, SEQUENCEOF, SETOF, SET, CHOICE
+ * 4) Between two white spaces
+ * INTEGER, BOOLEAN, NULL,ENUMERATE, etc
+ */
+
+ succeed = 0;
+ if ( cav->cav_ptr[0] == '"' ) {
+ for( count = 1, sequent_dquote = 0 ; ; count++ ) {
+ /* In order to find escaped double quote */
+ if ( cav->cav_ptr[count] == '"' ) sequent_dquote++;
+ else sequent_dquote = 0;
+
+ if ( cav->cav_ptr[count] == '\0' ||
+ (cav->cav_ptr+count) > cav->cav_end )
+ {
+ break;
+ }
+
+ if ( ( cav->cav_ptr[count] == '"' &&
+ cav->cav_ptr[count-1] != '"') ||
+ ( sequent_dquote > 2 && (sequent_dquote%2) == 1 ) )
+ {
+ succeed = 1;
+ break;
+ }
+ }
+
+ if ( !succeed || cav->cav_ptr[count] != '"' ) {
+ return LDAP_FILTER_ERROR;
+ }
+
+ bv->bv_val = cav->cav_ptr + 1;
+ bv->bv_len = count - 1; /* exclude '"' */
+
+ } else if ( cav->cav_ptr[0] == '\'' ) {
+ for( count = 1 ; ; count++ ) {
+ if ( cav->cav_ptr[count] == '\0' ||
+ (cav->cav_ptr+count) > cav->cav_end )
+ {
+ break;
+ }
+ if ((cav->cav_ptr[count-1] == '\'' && cav->cav_ptr[count] == 'B') ||
+ (cav->cav_ptr[count-1] == '\'' && cav->cav_ptr[count] == 'H') )
+ {
+ succeed = 1;
+ break;
+ }
+ }
+
+ if ( !succeed ||
+ !(cav->cav_ptr[count] == 'H' || cav->cav_ptr[count] == 'B') )
+ {
+ return LDAP_FILTER_ERROR;
+ }
+
+ bv->bv_val = cav->cav_ptr + 1;/*the next to '"' */
+ bv->bv_len = count - 2;/* exclude "'H" or "'B" */
+
+ } else if ( cav->cav_ptr[0] == '{' ) {
+ for( count = 1, unclosed_brace = 1 ; ; count++ ) {
+ if ( cav->cav_ptr[count] == '{' ) unclosed_brace++;
+ if ( cav->cav_ptr[count] == '}' ) unclosed_brace--;
+
+ if ( cav->cav_ptr[count] == '\0' ||
+ (cav->cav_ptr+count) > cav->cav_end )
+ {
+ break;
+ }
+ if ( unclosed_brace == 0 ) {
+ succeed = 1;
+ break;
+ }
+ }
+
+ if ( !succeed || cav->cav_ptr[count] != '}' ) return LDAP_FILTER_ERROR;
+
+ bv->bv_val = cav->cav_ptr + 1;/*the next to '"' */
+ bv->bv_len = count - 1;/* exclude "'B" */
+
+ } else {
+ succeed = 1;
+ /*Find following white space where the value is ended*/
+ for( count = 1 ; ; count++ ) {
+ if ( cav->cav_ptr[count] == '\0' ||
+ cav->cav_ptr[count] == ' ' || cav->cav_ptr[count] == '}' ||
+ cav->cav_ptr[count] == '{' ||
+ (cav->cav_ptr+count) > cav->cav_end )
+ {
+ break;
+ }
+ }
+ bv->bv_val = cav->cav_ptr;
+ bv->bv_len = count;
+ }
+
+ cav->cav_ptr += bv->bv_len;
+ return LDAP_SUCCESS;
+}
+
+static int
+get_matching_value( Operation *op, ComponentAssertion* ca,
+ ComponentAssertionValue* cav, struct berval* bv,
+ const char** text )
+{
+ if ( !(ca->ca_ma_rule->smr_usage & (SLAP_MR_COMPONENT)) ) {
+ if ( get_GSER_value( cav, bv ) != LDAP_SUCCESS ) {
+ return LDAP_FILTER_ERROR;
+ }
+
+ } else {
+ /* embedded componentFilterMatch Description */
+ bv->bv_val = cav->cav_ptr;
+ bv->bv_len = cav_cur_len( cav );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Don't move the position pointer, just peek given string */
+static int
+peek_cav_str( ComponentAssertionValue* cav, char* str )
+{
+ eat_whsp( cav );
+ if ( cav_cur_len( cav ) >= strlen( str ) &&
+ strncmp( cav->cav_ptr, str, strlen( str ) ) == 0 )
+ {
+ return LDAP_SUCCESS;
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+strip_cav_str( ComponentAssertionValue* cav, char* str)
+{
+ eat_whsp( cav );
+ if ( cav_cur_len( cav ) >= strlen( str ) &&
+ strncmp( cav->cav_ptr, str, strlen( str ) ) == 0 )
+ {
+ cav->cav_ptr += strlen( str );
+ return LDAP_SUCCESS;
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+/*
+ * TAG : "item", "and", "or", "not"
+ */
+static ber_tag_t
+strip_cav_tag( ComponentAssertionValue* cav )
+{
+ int rc;
+
+ eat_whsp( cav );
+ if ( cav_cur_len( cav ) >= 8 && strncmp( cav->cav_ptr, "item", 4 ) == 0 ) {
+ if ( strip_cav_str( cav , "item:" ))
+ goto fail;
+ return LDAP_COMP_FILTER_ITEM;
+
+ } else if ( cav_cur_len( cav ) >= 7 &&
+ strncmp( cav->cav_ptr, "and", 3 ) == 0 )
+ {
+ if ( strip_cav_str( cav , "and:" ))
+ goto fail;
+ return LDAP_COMP_FILTER_AND;
+
+ } else if ( cav_cur_len( cav ) >= 6 &&
+ strncmp( cav->cav_ptr, "or" , 2 ) == 0 )
+ {
+ if ( strip_cav_str( cav , "or:" ))
+ goto fail;
+ return LDAP_COMP_FILTER_OR;
+
+ } else if ( cav_cur_len( cav ) >= 7 &&
+ strncmp( cav->cav_ptr, "not", 3 ) == 0 )
+ {
+ if ( strip_cav_str( cav , "not:" ))
+ goto fail;
+ return LDAP_COMP_FILTER_NOT;
+ }
+
+fail:
+ return LBER_ERROR;
+}
+
+/*
+ * when encoding, "item" is denotation of ComponentAssertion
+ * ComponentAssertion :: SEQUENCE {
+ * component ComponentReference (SIZE(1..MAX)) OPTIONAL,
+ * useDefaultValues BOOLEAN DEFAULT TRUE,
+ * rule MATCHING-RULE.&id,
+ * value MATCHING-RULE.&AssertionType }
+ */
+static int
+get_item( Operation *op, ComponentAssertionValue* cav, ComponentAssertion** ca,
+ const char** text )
+{
+ int rc, freeval = 0;
+ ComponentAssertion* _ca;
+ struct berval value;
+ MatchingRule* mr;
+
+ Debug( LDAP_DEBUG_FILTER, "get_item \n" );
+ if ( op )
+ _ca = op->o_tmpalloc( sizeof( ComponentAssertion ), op->o_tmpmemctx );
+ else
+ _ca = SLAP_MALLOC( sizeof( ComponentAssertion ) );
+
+ if ( !_ca ) return LDAP_NO_MEMORY;
+
+ _ca->ca_comp_data.cd_tree = NULL;
+ _ca->ca_comp_data.cd_mem_op = NULL;
+ BER_BVZERO( &_ca->ca_ma_value );
+
+ rc = peek_cav_str( cav, "component" );
+ if ( rc == LDAP_SUCCESS ) {
+ strip_cav_str( cav, "component" );
+ rc = get_component_reference( op, cav, &_ca->ca_comp_ref, text );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+fail:
+ if ( freeval )
+ op->o_tmpfree( _ca->ca_ma_value.bv_val, op->o_tmpmemctx );
+ if ( op )
+ op->o_tmpfree( _ca, op->o_tmpmemctx );
+ else
+ free( _ca );
+ return rc;
+ }
+ if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS )
+ goto fail;
+ } else {
+ _ca->ca_comp_ref = NULL;
+ }
+
+ rc = peek_cav_str( cav, "useDefaultValues");
+ if ( rc == LDAP_SUCCESS ) {
+ rc = get_ca_use_default( op, cav, &_ca->ca_use_def, text );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto fail;
+ }
+ if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS )
+ goto fail;
+ }
+ else _ca->ca_use_def = 1;
+
+ if ( !( strip_cav_str( cav, "rule" ) == LDAP_SUCCESS &&
+ get_matching_rule( op, cav , &_ca->ca_ma_rule, text ) == LDAP_SUCCESS )) {
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto fail;
+ }
+
+ if ( ( rc = strip_cav_str( cav,",") ) != LDAP_SUCCESS )
+ goto fail;
+ if ( !(strip_cav_str( cav, "value" ) == LDAP_SUCCESS &&
+ get_matching_value( op, _ca, cav,&value ,text ) == LDAP_SUCCESS )) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto fail;
+ }
+
+ /*
+ * Normalize the value of this component assertion when the matching
+ * rule is one of existing matching rules
+ */
+ mr = _ca->ca_ma_rule;
+ if ( op && !(mr->smr_usage & (SLAP_MR_COMPONENT)) && mr->smr_normalize ) {
+
+ value.bv_val[value.bv_len] = '\0';
+ rc = mr->smr_normalize (
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ NULL, mr,
+ &value, &_ca->ca_ma_value, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS )
+ goto fail;
+ freeval = 1;
+ }
+ else
+ _ca->ca_ma_value = value;
+ /*
+ * Validate the value of this component assertion
+ */
+ if ( op && mr->smr_syntax->ssyn_validate( mr->smr_syntax, &_ca->ca_ma_value) != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto fail;
+ }
+
+
+ /* componentFilterMatch contains componentFilterMatch in it */
+ if ( strcmp(_ca->ca_ma_rule->smr_mrule.mr_oid, OID_COMP_FILTER_MATCH ) == 0) {
+ struct berval bv;
+ bv.bv_val = cav->cav_ptr;
+ bv.bv_len = cav_cur_len( cav );
+ rc = get_comp_filter( op, &bv,(ComponentFilter**)&_ca->ca_cf, text );
+ if ( rc != LDAP_SUCCESS )
+ goto fail;
+ cav->cav_ptr = bv.bv_val;
+ assert( cav->cav_end >= bv.bv_val );
+ }
+
+ *ca = _ca;
+ return LDAP_SUCCESS;
+}
+
+static int
+parse_comp_filter( Operation* op, ComponentAssertionValue* cav,
+ ComponentFilter** filt, const char** text )
+{
+ /*
+ * A component filter looks like this coming in:
+ * Filter ::= CHOICE {
+ * item [0] ComponentAssertion,
+ * and [1] SEQUENCE OF ComponentFilter,
+ * or [2] SEQUENCE OF ComponentFilter,
+ * not [3] ComponentFilter,
+ * }
+ */
+
+ ber_tag_t tag;
+ int err = LDAP_SUCCESS;
+ ComponentFilter f;
+ /* TAG : item, and, or, not in RFC 4515 */
+ tag = strip_cav_tag( cav );
+
+ if ( tag == LBER_ERROR ) {
+ *text = "error decoding comp filter";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( tag != LDAP_COMP_FILTER_NOT ) {
+ err = strip_cav_str( cav, "{");
+ if ( err )
+ goto invalid;
+ }
+
+ f.cf_next = NULL;
+ f.cf_choice = tag;
+
+ switch ( f.cf_choice ) {
+ case LDAP_COMP_FILTER_AND:
+ Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_AND\n" );
+ err = get_comp_filter_list( op, cav, &f.cf_and, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( f.cf_and == NULL ) {
+ f.cf_choice = SLAPD_FILTER_COMPUTED;
+ f.cf_result = LDAP_COMPARE_TRUE;
+ }
+ break;
+
+ case LDAP_COMP_FILTER_OR:
+ Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_OR\n" );
+ err = get_comp_filter_list( op, cav, &f.cf_or, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( f.cf_or == NULL ) {
+ f.cf_choice = SLAPD_FILTER_COMPUTED;
+ f.cf_result = LDAP_COMPARE_FALSE;
+ }
+ /* no assert - list could be empty */
+ break;
+
+ case LDAP_COMP_FILTER_NOT:
+ Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_NOT\n" );
+ err = parse_comp_filter( op, cav, &f.cf_not, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( f.cf_not != NULL );
+ if ( f.cf_not->cf_choice == SLAPD_FILTER_COMPUTED ) {
+ int fresult = f.cf_not->cf_result;
+ f.cf_choice = SLAPD_FILTER_COMPUTED;
+ op->o_tmpfree( f.cf_not, op->o_tmpmemctx );
+ f.cf_not = NULL;
+
+ switch ( fresult ) {
+ case LDAP_COMPARE_TRUE:
+ f.cf_result = LDAP_COMPARE_FALSE;
+ break;
+ case LDAP_COMPARE_FALSE:
+ f.cf_result = LDAP_COMPARE_TRUE;
+ break;
+ default: ;
+ /* (!Undefined) is Undefined */
+ }
+ }
+ break;
+
+ case LDAP_COMP_FILTER_ITEM:
+ Debug( LDAP_DEBUG_FILTER, "LDAP_COMP_FILTER_ITEM\n" );
+ err = get_item( op, cav, &f.cf_ca, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( f.cf_ca != NULL );
+ break;
+
+ default:
+ f.cf_choice = SLAPD_FILTER_COMPUTED;
+ f.cf_result = SLAPD_COMPARE_UNDEFINED;
+ break;
+ }
+
+invalid:
+ if ( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) {
+ *text = "Component Filter Syntax Error";
+ return err;
+ }
+
+ if ( tag != LDAP_COMP_FILTER_NOT )
+ err = strip_cav_str( cav, "}");
+
+ if ( err == LDAP_SUCCESS ) {
+ if ( op ) {
+ *filt = op->o_tmpalloc( sizeof(f), op->o_tmpmemctx );
+ } else {
+ *filt = SLAP_MALLOC( sizeof(f) );
+ }
+ if ( *filt == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+ **filt = f;
+ }
+
+ return( err );
+}
+
+static int
+test_comp_filter_and(
+ Syntax *syn,
+ ComponentSyntaxInfo *a,
+ ComponentFilter *flist )
+{
+ ComponentFilter *f;
+ int rtn = LDAP_COMPARE_TRUE;
+
+ for ( f = flist ; f != NULL; f = f->cf_next ) {
+ int rc = test_comp_filter( syn, a, f );
+ if ( rc == LDAP_COMPARE_FALSE ) {
+ rtn = rc;
+ break;
+ }
+
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ rtn = rc;
+ }
+ }
+
+ return rtn;
+}
+
+static int
+test_comp_filter_or(
+ Syntax *syn,
+ ComponentSyntaxInfo *a,
+ ComponentFilter *flist )
+{
+ ComponentFilter *f;
+ int rtn = LDAP_COMPARE_TRUE;
+
+ for ( f = flist ; f != NULL; f = f->cf_next ) {
+ int rc = test_comp_filter( syn, a, f );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rtn = rc;
+ break;
+ }
+
+ if ( rc != LDAP_COMPARE_FALSE ) {
+ rtn = rc;
+ }
+ }
+
+ return rtn;
+}
+
+int
+csi_value_match( MatchingRule *mr, struct berval* bv_attr,
+ struct berval* bv_assert )
+{
+ int rc;
+ int match;
+
+ assert( mr != NULL );
+ assert( !(mr->smr_usage & SLAP_MR_COMPONENT) );
+
+ if( !mr->smr_match ) return LDAP_INAPPROPRIATE_MATCHING;
+
+ rc = (mr->smr_match)( &match, 0, NULL /*ad->ad_type->sat_syntax*/,
+ mr, bv_attr, bv_assert );
+
+ if ( rc != LDAP_SUCCESS ) return rc;
+
+ return match ? LDAP_COMPARE_FALSE : LDAP_COMPARE_TRUE;
+}
+
+/*
+ * return codes : LDAP_COMPARE_TRUE, LDAP_COMPARE_FALSE
+ */
+static int
+test_comp_filter_item(
+ Syntax *syn,
+ ComponentSyntaxInfo *csi_attr,
+ ComponentAssertion *ca )
+{
+ int rc;
+ void *attr_nm, *assert_nm;
+
+ if ( strcmp(ca->ca_ma_rule->smr_mrule.mr_oid,
+ OID_COMP_FILTER_MATCH ) == 0 && ca->ca_cf ) {
+ /* componentFilterMatch inside of componentFilterMatch */
+ rc = test_comp_filter( syn, csi_attr, ca->ca_cf );
+ return rc;
+ }
+
+ /* Memory for storing will-be-extracted attribute values */
+ attr_nm = nibble_mem_allocator ( 1024*4 , 1024 );
+ if ( !attr_nm ) return LDAP_PROTOCOL_ERROR;
+
+ /* Memory for storing component assertion values */
+ if( !ca->ca_comp_data.cd_mem_op ) {
+ assert_nm = nibble_mem_allocator ( 256, 64 );
+ if ( !assert_nm ) {
+ nibble_mem_free ( attr_nm );
+ return LDAP_PROTOCOL_ERROR;
+ }
+ ca->ca_comp_data.cd_mem_op = assert_nm;
+
+ } else {
+ assert_nm = ca->ca_comp_data.cd_mem_op;
+ }
+
+ /* component reference initialization */
+ if ( ca->ca_comp_ref ) {
+ ca->ca_comp_ref->cr_curr = ca->ca_comp_ref->cr_list;
+ }
+ rc = test_components( attr_nm, assert_nm, csi_attr, ca );
+
+ /* free memory used for storing extracted attribute value */
+ nibble_mem_free ( attr_nm );
+ return rc;
+}
+
+static int
+test_comp_filter(
+ Syntax *syn,
+ ComponentSyntaxInfo *a,
+ ComponentFilter *f )
+{
+ int rc;
+
+ if ( !f ) return LDAP_PROTOCOL_ERROR;
+
+ Debug( LDAP_DEBUG_FILTER, "test_comp_filter\n" );
+ switch ( f->cf_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ rc = f->cf_result;
+ break;
+ case LDAP_COMP_FILTER_AND:
+ rc = test_comp_filter_and( syn, a, f->cf_and );
+ break;
+ case LDAP_COMP_FILTER_OR:
+ rc = test_comp_filter_or( syn, a, f->cf_or );
+ break;
+ case LDAP_COMP_FILTER_NOT:
+ rc = test_comp_filter( syn, a, f->cf_not );
+
+ switch ( rc ) {
+ case LDAP_COMPARE_TRUE:
+ rc = LDAP_COMPARE_FALSE;
+ break;
+ case LDAP_COMPARE_FALSE:
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+ break;
+ case LDAP_COMP_FILTER_ITEM:
+ rc = test_comp_filter_item( syn, a, f->cf_ca );
+ break;
+ default:
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+
+ return( rc );
+}
+
+static void
+free_comp_filter_list( ComponentFilter* f )
+{
+ ComponentFilter* tmp;
+ for ( tmp = f; tmp; tmp = tmp->cf_next ) {
+ free_comp_filter( tmp );
+ }
+}
+
+static void
+free_comp_filter( ComponentFilter* f )
+{
+ if ( !f ) {
+ Debug( LDAP_DEBUG_FILTER,
+ "free_comp_filter: Invalid filter so failed to release memory\n" );
+ return;
+ }
+ switch ( f->cf_choice ) {
+ case LDAP_COMP_FILTER_AND:
+ case LDAP_COMP_FILTER_OR:
+ free_comp_filter_list( f->cf_any );
+ break;
+ case LDAP_COMP_FILTER_NOT:
+ free_comp_filter( f->cf_any );
+ break;
+ case LDAP_COMP_FILTER_ITEM:
+ if ( nibble_mem_free && f->cf_ca->ca_comp_data.cd_mem_op ) {
+ nibble_mem_free( f->cf_ca->ca_comp_data.cd_mem_op );
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void
+component_free( ComponentFilter *f ) {
+ free_comp_filter( f );
+}
+
+void
+free_ComponentData( Attribute *a ) {
+ if ( a->a_comp_data->cd_mem_op )
+ component_destructor( a->a_comp_data->cd_mem_op );
+ free ( a->a_comp_data );
+ a->a_comp_data = NULL;
+}
+#endif
diff --git a/servers/slapd/component.h b/servers/slapd/component.h
new file mode 100644
index 0000000..202773e
--- /dev/null
+++ b/servers/slapd/component.h
@@ -0,0 +1,76 @@
+/* component.h */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 by IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _H_SLAPD_COMPONENT
+#define _H_SLAPD_COMPONENT
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ldap_pvt.h>
+#include "lutil.h"
+#include <ldap.h>
+#include "slap.h"
+
+typedef enum { ASN_BASIC, ASN_COMPOSITE } AsnType;
+/*
+ * Decoder Modes
+ * Different operation is required to handle Decoding(2), Extracted Component
+ * decoding(0), ANY DEFINED TYPe(2)
+ * b0 : Component Alloc(yes)
+ * Constructed type : Component Alloc (Yes)
+ * Primitive type : Component Alloc (Yes)
+ * set to mode 2 in inner decoders
+ * b1 : Component Alloc (No)
+ * Constructed type : Component Alloc (No)
+ * Primitive type : Component Alloc (No)
+ * set to mode 2 in inner decoders
+ * b2 : Default Mode
+ * Constructed type : Component Alloc (Yes)
+ * Primitive type : Component Alloc (No)
+ * in addition to above modes, the 4th bit has special meaning,
+ * b4 : if the 4th bit is clear, DecxxxContent is called
+ * b4 : if the 4th bit is set, Decxxx is called, then it is cleared.
+ */
+#define DEC_ALLOC_MODE_0 0x01
+#define DEC_ALLOC_MODE_1 0x02
+#define DEC_ALLOC_MODE_2 0x04
+#define CALL_TAG_DECODER 0x08
+#define CALL_CONTENT_DECODER ~0x08
+/*
+ * For Attribute Aliasing
+ */
+#define MAX_ALIASING_ENTRY 128
+typedef struct comp_attribute_aliasing {
+ AttributeDescription* aa_aliasing_ad;
+ AttributeDescription* aa_aliased_ad;
+ ComponentFilter* aa_cf;
+ MatchingRule* aa_mr;
+ char* aa_cf_str;
+} AttributeAliasing;
+
+typedef struct comp_matchingrule_aliasing {
+ MatchingRule* mra_aliasing_attr;
+ MatchingRule* mra_aliased_attr;
+ AttributeDescription* mra_attr;
+ ComponentFilter* mra_cf;
+ MatchingRule* mra_mr;
+ char* mra_cf_str;
+} MatchingRuleAliasing;
+
+#endif
diff --git a/servers/slapd/config.c b/servers/slapd/config.c
new file mode 100644
index 0000000..80333f3
--- /dev/null
+++ b/servers/slapd/config.c
@@ -0,0 +1,2368 @@
+/* config.c - configuration file handling routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+#endif
+
+#include "slap.h"
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "ldif.h"
+#include "slap-config.h"
+
+#ifdef _WIN32
+#define LUTIL_ATOULX lutil_atoullx
+#define Z "I"
+#else
+#define LUTIL_ATOULX lutil_atoulx
+#define Z "z"
+#endif
+
+#define ARGS_STEP 512
+
+/*
+ * defaults for various global variables
+ */
+slap_mask_t global_allows = 0;
+slap_mask_t global_disallows = 0;
+int global_gentlehup = 0;
+int global_idletimeout = 0;
+int global_writetimeout = 0;
+char *global_host = NULL;
+struct berval global_host_bv = BER_BVNULL;
+char *global_realm = NULL;
+char *sasl_host = NULL;
+char *sasl_cbinding = NULL;
+char **default_passwd_hash = NULL;
+struct berval default_search_base = BER_BVNULL;
+struct berval default_search_nbase = BER_BVNULL;
+
+ber_len_t sockbuf_max_incoming = SLAP_SB_MAX_INCOMING_DEFAULT;
+ber_len_t sockbuf_max_incoming_auth= SLAP_SB_MAX_INCOMING_AUTH;
+
+int slap_conn_max_pending = SLAP_CONN_MAX_PENDING_DEFAULT;
+int slap_conn_max_pending_auth = SLAP_CONN_MAX_PENDING_AUTH;
+
+int slap_max_filter_depth = SLAP_MAX_FILTER_DEPTH_DEFAULT;
+
+char *slapd_pid_file = NULL;
+char *slapd_args_file = NULL;
+
+int use_reverse_lookup = 0;
+
+#ifdef LDAP_SLAPI
+int slapi_plugins_used = 0;
+#endif
+
+static int fp_getline(FILE *fp, ConfigArgs *c);
+static void fp_getline_init(ConfigArgs *c);
+
+static char *strtok_quote(char *line, char *sep, char **quote_ptr, int *inquote);
+static char *strtok_quote_ldif(char **line);
+
+ConfigArgs *
+new_config_args( BackendDB *be, const char *fname, int lineno, int argc, char **argv )
+{
+ ConfigArgs *c;
+ c = ch_calloc( 1, sizeof( ConfigArgs ) );
+ if ( c == NULL ) return(NULL);
+ c->be = be;
+ c->fname = fname;
+ c->argc = argc;
+ c->argv = argv;
+ c->lineno = lineno;
+ snprintf( c->log, sizeof( c->log ), "%s: line %d", fname, lineno );
+ return(c);
+}
+
+void
+init_config_argv( ConfigArgs *c )
+{
+ c->argv = ch_calloc( ARGS_STEP + 1, sizeof( *c->argv ) );
+ c->argv_size = ARGS_STEP + 1;
+}
+
+ConfigTable *config_find_keyword(ConfigTable *Conf, ConfigArgs *c) {
+ int i;
+
+ for(i = 0; Conf[i].name; i++)
+ if( (Conf[i].length && (!strncasecmp(c->argv[0], Conf[i].name, Conf[i].length))) ||
+ (!strcasecmp(c->argv[0], Conf[i].name)) ) break;
+ if ( !Conf[i].name ) return NULL;
+ if (( Conf[i].arg_type & ARGS_TYPES ) == ARG_BINARY ) {
+ size_t decode_len = LUTIL_BASE64_DECODE_LEN(c->linelen);
+ ch_free( c->tline );
+ c->tline = ch_malloc( decode_len+1 );
+ c->linelen = lutil_b64_pton( c->line, c->tline, decode_len );
+ if ( c->linelen < 0 )
+ {
+ ch_free( c->tline );
+ c->tline = NULL;
+ return NULL;
+ }
+ c->line = c->tline;
+ }
+ c->ca_desc = Conf+i;
+ return c->ca_desc;
+}
+
+int config_check_vals(ConfigTable *Conf, ConfigArgs *c, int check_only ) {
+ int rc, arg_user, arg_type, arg_syn, iarg;
+ unsigned uiarg;
+ long larg;
+ size_t ularg;
+ ber_len_t barg;
+
+ if(Conf->arg_type == ARG_IGNORED) {
+ Debug(LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n",
+ c->log, Conf->name );
+ return(0);
+ }
+ arg_type = Conf->arg_type & ARGS_TYPES;
+ arg_user = Conf->arg_type & ARGS_USERLAND;
+ arg_syn = Conf->arg_type & ARGS_SYNTAX;
+
+ if((arg_type == ARG_DN) && c->argc == 1) {
+ c->argc = 2;
+ c->argv[1] = "";
+ }
+ if(Conf->min_args && (c->argc < Conf->min_args)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> missing <%s> argument",
+ c->argv[0], Conf->what ? Conf->what : "" );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if(Conf->max_args && (c->argc > Conf->max_args)) {
+ char *ignored = " ignored";
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> extra cruft after <%s>",
+ c->argv[0], Conf->what );
+
+ ignored = "";
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s%s.\n",
+ c->log, c->cr_msg, ignored );
+ return(ARG_BAD_CONF);
+ }
+ if((arg_syn & ARG_DB) && !c->be) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> only allowed within database declaration",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if((arg_syn & ARG_PRE_BI) && c->bi) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must occur before any backend %sdeclaration",
+ c->argv[0], (arg_syn & ARG_PRE_DB) ? "or database " : "" );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if((arg_syn & ARG_PRE_DB) && c->be && c->be != frontendDB) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> must occur before any database declaration",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if((arg_syn & ARG_PAREN) && *c->argv[1] != '(' /*')'*/) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> old format not supported", c->argv[0] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if(arg_type && !Conf->arg_item && !(arg_syn & ARG_OFFSET)) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid config_table, arg_item is NULL",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ c->type = arg_user;
+ memset(&c->values, 0, sizeof(c->values));
+ if(arg_type == ARG_STRING) {
+ assert( c->argc == 2 );
+ if ( !check_only )
+ c->value_string = ch_strdup(c->argv[1]);
+ } else if(arg_type == ARG_BERVAL) {
+ assert( c->argc == 2 );
+ if ( !check_only )
+ ber_str2bv( c->argv[1], 0, 1, &c->value_bv );
+ } else if(arg_type == ARG_BINARY) {
+ assert( c->argc == 2 );
+ if ( !check_only ) {
+ c->value_bv.bv_len = c->linelen;
+ c->value_bv.bv_val = ch_malloc( c->linelen );
+ AC_MEMCPY( c->value_bv.bv_val, c->line, c->linelen );
+ }
+ } else if(arg_type == ARG_DN) {
+ struct berval bv;
+ assert( c->argc == 2 );
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ rc = dnPrettyNormal( NULL, &bv, &c->value_dn, &c->value_ndn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid DN %d (%s)",
+ c->argv[0], rc, ldap_err2string( rc ));
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n" , c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ if ( check_only ) {
+ ch_free( c->value_ndn.bv_val );
+ ch_free( c->value_dn.bv_val );
+ }
+ } else if(arg_type == ARG_ATDESC) {
+ const char *text = NULL;
+ assert( c->argc == 2 );
+ c->value_ad = NULL;
+ rc = slap_str2ad( c->argv[1], &c->value_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid AttributeDescription %d (%s)",
+ c->argv[0], rc, text );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n" , c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ } else { /* all numeric */
+ int j;
+ iarg = 0; larg = 0; barg = 0;
+ switch(arg_type) {
+ case ARG_INT:
+ assert( c->argc == 2 );
+ if ( lutil_atoix( &iarg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to parse \"%s\" as int",
+ c->argv[0], c->argv[1] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ break;
+ case ARG_UINT:
+ assert( c->argc == 2 );
+ if ( lutil_atoux( &uiarg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to parse \"%s\" as unsigned int",
+ c->argv[0], c->argv[1] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ break;
+ case ARG_LONG:
+ assert( c->argc == 2 );
+ if ( lutil_atolx( &larg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to parse \"%s\" as long",
+ c->argv[0], c->argv[1] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ break;
+ case ARG_ULONG:
+ assert( c->argc == 2 );
+ if ( LUTIL_ATOULX( &ularg, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to parse \"%s\" as unsigned long",
+ c->argv[0], c->argv[1] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ break;
+ case ARG_BER_LEN_T: {
+ unsigned long l;
+ assert( c->argc == 2 );
+ if ( lutil_atoulx( &l, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> unable to parse \"%s\" as ber_len_t",
+ c->argv[0], c->argv[1] );
+ Debug(LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ barg = (ber_len_t)l;
+ } break;
+ case ARG_ON_OFF:
+ /* note: this is an explicit exception
+ * to the "need exactly 2 args" rule */
+ if (c->argc == 1) {
+ iarg = 1;
+ } else if ( !strcasecmp(c->argv[1], "on") ||
+ !strcasecmp(c->argv[1], "true") ||
+ !strcasecmp(c->argv[1], "yes") )
+ {
+ iarg = 1;
+ } else if ( !strcasecmp(c->argv[1], "off") ||
+ !strcasecmp(c->argv[1], "false") ||
+ !strcasecmp(c->argv[1], "no") )
+ {
+ iarg = 0;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ break;
+ }
+ j = (arg_type & ARG_NONZERO) ? 1 : 0;
+ if(iarg < j && larg < j && barg < (unsigned)j ) {
+ larg = larg ? larg : (barg ? (long)barg : iarg);
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> invalid value",
+ c->argv[0] );
+ Debug(LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ switch(arg_type) {
+ case ARG_ON_OFF:
+ case ARG_INT: c->value_int = iarg; break;
+ case ARG_UINT: c->value_uint = uiarg; break;
+ case ARG_LONG: c->value_long = larg; break;
+ case ARG_ULONG: c->value_ulong = ularg; break;
+ case ARG_BER_LEN_T: c->value_ber_t = barg; break;
+ }
+ }
+ return 0;
+}
+
+int config_set_vals(ConfigTable *Conf, ConfigArgs *c) {
+ int rc, arg_type;
+ void *ptr = NULL;
+
+ arg_type = Conf->arg_type;
+ if(arg_type & ARG_MAGIC) {
+ if(!c->be) c->be = frontendDB;
+ c->cr_msg[0] = '\0';
+ rc = (*((ConfigDriver*)Conf->arg_item))(c);
+#if 0
+ if(c->be == frontendDB) c->be = NULL;
+#endif
+ if(rc) {
+ if ( !c->cr_msg[0] ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> handler exited with %d",
+ c->argv[0], rc );
+ Debug( LDAP_DEBUG_ANY, "%s: %s!\n",
+ c->log, c->cr_msg );
+ }
+ return(ARG_BAD_CONF);
+ }
+ return(0);
+ }
+ if(arg_type & ARG_OFFSET) {
+ if (c->be && c->table == Cft_Database)
+ ptr = c->be->be_private;
+ else if (c->bi)
+ ptr = c->bi->bi_private;
+ else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> offset is missing base pointer",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s!\n",
+ c->log, c->cr_msg );
+ return(ARG_BAD_CONF);
+ }
+ ptr = (void *)((char *)ptr + (long)Conf->arg_item);
+ } else if (arg_type & ARGS_TYPES) {
+ ptr = Conf->arg_item;
+ }
+ if(arg_type & ARGS_TYPES)
+ switch(arg_type & ARGS_TYPES) {
+ case ARG_ON_OFF:
+ case ARG_INT: *(int*)ptr = c->value_int; break;
+ case ARG_UINT: *(unsigned*)ptr = c->value_uint; break;
+ case ARG_LONG: *(long*)ptr = c->value_long; break;
+ case ARG_ULONG: *(size_t*)ptr = c->value_ulong; break;
+ case ARG_BER_LEN_T: *(ber_len_t*)ptr = c->value_ber_t; break;
+ case ARG_STRING: {
+ char *cc = *(char**)ptr;
+ if(cc) {
+ if ((arg_type & ARG_UNIQUE) && c->op == SLAP_CONFIG_ADD ) {
+ Debug(LDAP_DEBUG_CONFIG, "%s: already set %s!\n",
+ c->log, Conf->name );
+ return(ARG_BAD_CONF);
+ }
+ ch_free(cc);
+ }
+ *(char **)ptr = c->value_string;
+ break;
+ }
+ case ARG_BERVAL:
+ case ARG_BINARY:
+ *(struct berval *)ptr = c->value_bv;
+ break;
+ case ARG_ATDESC:
+ *(AttributeDescription **)ptr = c->value_ad;
+ break;
+ }
+ return(0);
+}
+
+int config_add_vals(ConfigTable *Conf, ConfigArgs *c) {
+ int rc, arg_type;
+
+ arg_type = Conf->arg_type;
+ if(arg_type == ARG_IGNORED) {
+ Debug(LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n",
+ c->log, Conf->name );
+ return(0);
+ }
+ rc = config_check_vals( Conf, c, 0 );
+ if ( rc ) return rc;
+ return config_set_vals( Conf, c );
+}
+
+int
+config_del_vals(ConfigTable *cf, ConfigArgs *c)
+{
+ int rc = 0;
+ void *ptr;
+
+ if ( cf->arg_type & ARG_MAGIC ) {
+ c->argv[0] = cf->ad->ad_cname.bv_val;
+ c->op = LDAP_MOD_DELETE;
+ c->type = cf->arg_type & ARGS_USERLAND;
+ rc = (*((ConfigDriver*)cf->arg_item))(c);
+ return rc;
+ }
+ /* If there is no handler, just zero it */
+ if ( cf->arg_type & ARG_OFFSET ) {
+ if ( c->be && c->table == Cft_Database )
+ ptr = c->be->be_private;
+ else if ( c->bi )
+ ptr = c->bi->bi_private;
+ else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> offset is missing base pointer",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s!\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ ptr = (void *)((char *)ptr + (long)cf->arg_item);
+ } else if ( cf->arg_type & ARGS_TYPES ) {
+ ptr = cf->arg_item;
+ }
+ if ( cf->arg_type & ARGS_TYPES )
+ switch ( cf->arg_type & ARGS_TYPES ) {
+ case ARG_ON_OFF:
+ case ARG_INT: *(int *)ptr = cf->arg_default.v_int; break;
+ case ARG_UINT: *(unsigned *)ptr = cf->arg_default.v_uint; break;
+ case ARG_LONG: *(long *)ptr = cf->arg_default.v_long; break;
+ case ARG_ULONG: *(size_t *)ptr = cf->arg_default.v_ulong; break;
+ case ARG_BER_LEN_T: *(ber_len_t *)ptr = cf->arg_default.v_ber_t; break;
+ case ARG_STRING:
+ ch_free( *(char**)ptr );
+ if ( cf->arg_default.v_string ) {
+ *(char **)ptr = ch_strdup( cf->arg_default.v_string );
+ } else {
+ *(char **)ptr = NULL;
+ }
+ break;
+ case ARG_BERVAL:
+ case ARG_BINARY:
+ ch_free( ((struct berval *)ptr)->bv_val );
+ if ( !BER_BVISNULL( &cf->arg_default.v_bv ) ) {
+ ber_dupbv( (struct berval *)ptr, &cf->arg_default.v_bv );
+ } else {
+ BER_BVZERO( (struct berval *)ptr );
+ }
+ break;
+ case ARG_ATDESC:
+ *(AttributeDescription **)ptr = cf->arg_default.v_ad;
+ break;
+ }
+ return rc;
+}
+
+int
+config_get_vals(ConfigTable *cf, ConfigArgs *c)
+{
+ int rc = 0;
+ struct berval bv;
+ void *ptr;
+
+ if ( cf->arg_type & ARG_IGNORED ) {
+ return 1;
+ }
+
+ memset(&c->values, 0, sizeof(c->values));
+ c->rvalue_vals = NULL;
+ c->rvalue_nvals = NULL;
+ c->op = SLAP_CONFIG_EMIT;
+ c->type = cf->arg_type & ARGS_USERLAND;
+
+ if ( cf->arg_type & ARG_MAGIC ) {
+ rc = (*((ConfigDriver*)cf->arg_item))(c);
+ if ( rc ) return rc;
+ } else {
+ if ( cf->arg_type & ARG_OFFSET ) {
+ if (c->be && c->table == Cft_Database)
+ ptr = c->be->be_private;
+ else if ( c->bi )
+ ptr = c->bi->bi_private;
+ else
+ return 1;
+ ptr = (void *)((char *)ptr + (long)cf->arg_item);
+ } else {
+ ptr = cf->arg_item;
+ }
+
+ switch(cf->arg_type & ARGS_TYPES) {
+ case ARG_ON_OFF:
+ case ARG_INT: c->value_int = *(int *)ptr; break;
+ case ARG_UINT: c->value_uint = *(unsigned *)ptr; break;
+ case ARG_LONG: c->value_long = *(long *)ptr; break;
+ case ARG_ULONG: c->value_ulong = *(size_t *)ptr; break;
+ case ARG_BER_LEN_T: c->value_ber_t = *(ber_len_t *)ptr; break;
+ case ARG_STRING:
+ if ( *(char **)ptr )
+ c->value_string = ch_strdup(*(char **)ptr);
+ break;
+ case ARG_BERVAL:
+ c->value_bv = *((struct berval *)ptr); break;
+ case ARG_ATDESC:
+ c->value_ad = *(AttributeDescription **)ptr; break;
+ }
+ }
+ if ( cf->arg_type & ARGS_TYPES) {
+ bv.bv_len = 0;
+ bv.bv_val = c->log;
+ switch(cf->arg_type & ARGS_TYPES) {
+ case ARG_INT: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%d", c->value_int); break;
+ case ARG_UINT: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%u", c->value_uint); break;
+ case ARG_LONG: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%ld", c->value_long); break;
+ case ARG_ULONG: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%" Z "u", c->value_ulong); break;
+ case ARG_BER_LEN_T: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%ld", c->value_ber_t); break;
+ case ARG_ON_OFF: bv.bv_len = snprintf(bv.bv_val, sizeof( c->log ), "%s",
+ c->value_int ? "TRUE" : "FALSE"); break;
+ case ARG_STRING:
+ if ( c->value_string && c->value_string[0]) {
+ ber_str2bv( c->value_string, 0, 0, &bv);
+ } else {
+ return 1;
+ }
+ break;
+ case ARG_BERVAL:
+ if ( !BER_BVISEMPTY( &c->value_bv )) {
+ bv = c->value_bv;
+ } else {
+ return 1;
+ }
+ break;
+ case ARG_ATDESC:
+ if ( c->value_ad ) {
+ bv = c->value_ad->ad_cname;
+ } else {
+ return 1;
+ }
+ break;
+ default:
+ bv.bv_val = NULL;
+ break;
+ }
+ if (bv.bv_val == c->log && bv.bv_len >= sizeof( c->log ) ) {
+ return 1;
+ }
+ if (( cf->arg_type & ARGS_TYPES ) == ARG_STRING ) {
+ ber_bvarray_add(&c->rvalue_vals, &bv);
+ } else if ( !BER_BVISNULL( &bv ) ) {
+ value_add_one(&c->rvalue_vals, &bv);
+ }
+ /* else: maybe c->rvalue_vals already set? */
+ }
+ return rc;
+}
+
+int
+config_push_cleanup(ConfigArgs *ca, ConfigDriver *cleanup)
+{
+ int i;
+ /* silently ignore redundant push */
+ for (i=0; i < ca->num_cleanups; i++) {
+ if ( ca->cleanups[i] == cleanup )
+ return 0;
+ }
+
+ if (ca->num_cleanups >= SLAP_CONFIG_CLEANUP_MAX)
+ return -1;
+ ca->cleanups[ca->num_cleanups++] = cleanup;
+ return 0;
+}
+
+int
+config_run_cleanup(ConfigArgs *ca)
+{
+ int i, rc = 0;
+
+ for (i=0; i < ca->num_cleanups; i++) {
+ rc = ca->cleanups[i](ca);
+ if (rc)
+ break;
+ }
+ return rc;
+}
+
+int
+init_config_attrs(ConfigTable *ct) {
+ int i, code;
+
+ for (i=0; ct[i].name; i++ ) {
+ if ( !ct[i].attribute ) continue;
+ code = register_at( ct[i].attribute, &ct[i].ad, 1 );
+ if ( code ) {
+ fprintf( stderr, "init_config_attrs: register_at failed\n" );
+ return code;
+ }
+ if (( ct[i].arg_type & ARGS_TYPES ) == ARG_BINARY ) {
+ ldif_must_b64_encode_register( ct[i].ad->ad_cname.bv_val,
+ ct[i].ad->ad_type->sat_oid );
+ }
+ }
+
+ return 0;
+}
+
+int
+init_config_ocs( ConfigOCs *ocs ) {
+ int i, code;
+
+ for (i=0;ocs[i].co_def;i++) {
+ code = register_oc( ocs[i].co_def, &ocs[i].co_oc, 1 );
+ if ( code ) {
+ fprintf( stderr, "init_config_ocs: register_oc failed\n" );
+ return code;
+ }
+ }
+ return 0;
+}
+
+/* Split an LDIF line into space-separated tokens. Words may be grouped
+ * by quotes. A quoted string may begin in the middle of a word, but must
+ * end at the end of the word (be followed by whitespace or EOS). Any other
+ * quotes are passed through unchanged. All other characters are passed
+ * through unchanged.
+ */
+static char *
+strtok_quote_ldif( char **line )
+{
+ char *beg, *ptr, *quote=NULL;
+ int inquote=0;
+
+ ptr = *line;
+
+ if ( !ptr || !*ptr )
+ return NULL;
+
+ while( isspace( (unsigned char) *ptr )) ptr++;
+
+ if ( *ptr == '"' ) {
+ inquote = 1;
+ ptr++;
+ }
+
+ beg = ptr;
+
+ for (;*ptr;ptr++) {
+ if ( *ptr == '"' ) {
+ if ( inquote && ( !ptr[1] || isspace((unsigned char) ptr[1]))) {
+ *ptr++ = '\0';
+ break;
+ }
+ inquote = 1;
+ quote = ptr;
+ continue;
+ }
+ if ( inquote )
+ continue;
+ if ( isspace( (unsigned char) *ptr )) {
+ *ptr++ = '\0';
+ break;
+ }
+ }
+ if ( quote ) {
+ while ( quote < ptr ) {
+ *quote = quote[1];
+ quote++;
+ }
+ }
+ if ( !*ptr ) {
+ *line = NULL;
+ } else {
+ while ( isspace( (unsigned char) *ptr )) ptr++;
+ *line = ptr;
+ }
+ return beg;
+}
+
+void
+config_parse_ldif( ConfigArgs *c )
+{
+ char *next;
+ c->tline = ch_strdup(c->line);
+ next = c->tline;
+
+ while ((c->argv[c->argc] = strtok_quote_ldif( &next )) != NULL) {
+ c->argc++;
+ if ( c->argc >= c->argv_size ) {
+ char **tmp = ch_realloc( c->argv, (c->argv_size + ARGS_STEP) *
+ sizeof( *c->argv ));
+ c->argv = tmp;
+ c->argv_size += ARGS_STEP;
+ }
+ }
+ c->argv[c->argc] = NULL;
+}
+
+int
+config_parse_vals(ConfigTable *ct, ConfigArgs *c, int valx)
+{
+ int rc = 0;
+ int arg_type = ct->arg_type & ARGS_TYPES;
+
+ snprintf( c->log, sizeof( c->log ), "%s: value #%d",
+ ct->ad->ad_cname.bv_val, valx );
+ c->argc = 1;
+ c->argv[0] = ct->ad->ad_cname.bv_val;
+
+ if ( (( ct->arg_type & ARG_QUOTE ) && c->line[ 0 ] != '"' ) ||
+ (arg_type == ARG_BERVAL || arg_type == ARG_BINARY)) {
+ c->argv[c->argc] = c->line;
+ c->argc++;
+ c->argv[c->argc] = NULL;
+ c->tline = NULL;
+ } else {
+ config_parse_ldif( c );
+ }
+ rc = config_check_vals( ct, c, 1 );
+ ch_free( c->tline );
+ c->tline = NULL;
+
+ if ( rc )
+ rc = LDAP_CONSTRAINT_VIOLATION;
+
+ return rc;
+}
+
+int
+config_parse_add(ConfigTable *ct, ConfigArgs *c, int valx)
+{
+ int rc = 0;
+ int arg_type = ct->arg_type & ARGS_TYPES;
+
+ snprintf( c->log, sizeof( c->log ), "%s: value #%d",
+ ct->ad->ad_cname.bv_val, valx );
+ c->argc = 1;
+ c->argv[0] = ct->ad->ad_cname.bv_val;
+
+ if ( (( ct->arg_type & ARG_QUOTE ) && c->line[ 0 ] != '"' ) ||
+ (arg_type == ARG_BERVAL || arg_type == ARG_BINARY)) {
+ c->argv[c->argc] = c->line;
+ c->argc++;
+ c->argv[c->argc] = NULL;
+ c->tline = NULL;
+ } else {
+ config_parse_ldif( c );
+ }
+ c->op = LDAP_MOD_ADD;
+ rc = config_add_vals( ct, c );
+ ch_free( c->tline );
+
+ return rc;
+}
+
+int
+read_config_file(const char *fname, int depth, ConfigArgs *cf, ConfigTable *cft)
+{
+ FILE *fp;
+ ConfigTable *ct;
+ ConfigArgs *c;
+ int rc;
+ struct stat s;
+
+ c = ch_calloc( 1, sizeof( ConfigArgs ) );
+ if ( c == NULL ) {
+ return 1;
+ }
+
+ if ( depth ) {
+ memcpy( c, cf, sizeof( ConfigArgs ) );
+ } else {
+ c->depth = depth; /* XXX */
+ c->bi = NULL;
+ c->be = NULL;
+ }
+
+ c->valx = -1;
+ c->fname = fname;
+ init_config_argv( c );
+
+ if ( stat( fname, &s ) != 0 ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ ldap_syslog = 1;
+ Debug(LDAP_DEBUG_ANY,
+ "could not stat config file \"%s\": %s (%d)\n",
+ fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ), saved_errno);
+ ch_free( c->argv );
+ ch_free( c );
+ return(1);
+ }
+
+ if ( !S_ISREG( s.st_mode ) ) {
+ ldap_syslog = 1;
+ Debug(LDAP_DEBUG_ANY,
+ "regular file expected, got \"%s\"\n",
+ fname );
+ ch_free( c->argv );
+ ch_free( c );
+ return(1);
+ }
+
+ fp = fopen( fname, "r" );
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ ldap_syslog = 1;
+ Debug(LDAP_DEBUG_ANY,
+ "could not open config file \"%s\": %s (%d)\n",
+ fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ), saved_errno);
+ ch_free( c->argv );
+ ch_free( c );
+ return(1);
+ }
+
+ Debug(LDAP_DEBUG_CONFIG, "reading config file %s\n", fname );
+
+ fp_getline_init(c);
+
+ c->tline = NULL;
+
+ while ( fp_getline( fp, c ) ) {
+ /* skip comments and blank lines */
+ if ( c->line[0] == '#' || c->line[0] == '\0' ) {
+ continue;
+ }
+
+ snprintf( c->log, sizeof( c->log ), "%s: line %d",
+ c->fname, c->lineno );
+
+ c->argc = 0;
+ ch_free( c->tline );
+ if ( config_fp_parse_line( c ) ) {
+ rc = 1;
+ goto done;
+ }
+
+ if ( c->argc < 1 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: bad config line.\n",
+ c->log );
+ rc = 1;
+ goto done;
+ }
+
+ c->op = SLAP_CONFIG_ADD;
+
+ ct = config_find_keyword( cft, c );
+ if ( ct ) {
+ c->table = Cft_Global;
+ rc = config_add_vals( ct, c );
+ if ( !rc ) continue;
+
+ if ( rc & ARGS_USERLAND ) {
+ /* XXX a usertype would be opaque here */
+ Debug(LDAP_DEBUG_CONFIG, "%s: unknown user type <%s>\n",
+ c->log, c->argv[0] );
+ rc = 1;
+ goto done;
+
+ } else if ( rc == ARG_BAD_CONF ) {
+ rc = 1;
+ goto done;
+ }
+
+ } else if ( ( c->bi && !c->be ) || ( c->bi && c->bi->bi_flags & SLAP_BFLAG_STANDALONE ) ) {
+ rc = SLAP_CONF_UNKNOWN;
+ if ( c->bi->bi_cf_ocs ) {
+ ct = config_find_keyword( c->bi->bi_cf_ocs->co_table, c );
+ if ( ct ) {
+ c->table = c->bi->bi_cf_ocs->co_type;
+ rc = config_add_vals( ct, c );
+ }
+ }
+ if ( c->bi->bi_config && rc == SLAP_CONF_UNKNOWN ) {
+ rc = (*c->bi->bi_config)(c->bi, c->fname, c->lineno,
+ c->argc, c->argv);
+ }
+ if ( rc ) {
+ switch(rc) {
+ case SLAP_CONF_UNKNOWN:
+ Debug( LDAP_DEBUG_ANY, "%s: unknown directive "
+ "<%s> inside backend info definition.\n",
+ c->log, *c->argv );
+ default:
+ rc = 1;
+ goto done;
+ }
+ }
+
+ } else if ( c->be && c->be != frontendDB ) {
+ rc = SLAP_CONF_UNKNOWN;
+ if ( c->be->be_cf_ocs ) {
+ ct = config_find_keyword( c->be->be_cf_ocs->co_table, c );
+ if ( ct ) {
+ c->table = c->be->be_cf_ocs->co_type;
+ rc = config_add_vals( ct, c );
+ }
+ }
+ if ( c->be->be_config && rc == SLAP_CONF_UNKNOWN ) {
+ rc = (*c->be->be_config)(c->be, c->fname, c->lineno,
+ c->argc, c->argv);
+ }
+ if ( rc == SLAP_CONF_UNKNOWN && SLAP_ISGLOBALOVERLAY( frontendDB ) )
+ {
+ /* global overlays may need
+ * definitions inside other databases...
+ */
+ rc = (*frontendDB->be_config)( frontendDB,
+ c->fname, (int)c->lineno, c->argc, c->argv );
+ }
+
+ switch ( rc ) {
+ case 0:
+ break;
+
+ case SLAP_CONF_UNKNOWN:
+ Debug( LDAP_DEBUG_ANY, "%s: unknown directive "
+ "<%s> inside backend database definition.\n",
+ c->log, *c->argv );
+
+ default:
+ rc = 1;
+ goto done;
+ }
+
+ } else if ( frontendDB->be_config ) {
+ rc = (*frontendDB->be_config)( frontendDB,
+ c->fname, (int)c->lineno, c->argc, c->argv);
+ if ( rc ) {
+ switch(rc) {
+ case SLAP_CONF_UNKNOWN:
+ Debug( LDAP_DEBUG_ANY, "%s: unknown directive "
+ "<%s> inside global database definition.\n",
+ c->log, *c->argv );
+
+ default:
+ rc = 1;
+ goto done;
+ }
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: unknown directive "
+ "<%s> outside backend info and database definitions.\n",
+ c->log, *c->argv );
+ rc = 1;
+ goto done;
+ }
+ }
+
+ rc = 0;
+
+done:
+ if ( cf ) {
+ cf->be = c->be;
+ cf->bi = c->bi;
+ }
+ ch_free(c->tline);
+ fclose(fp);
+ ch_free(c->argv);
+ ch_free(c);
+ return(rc);
+}
+
+/* restrictops, allows, disallows, requires, loglevel */
+
+static slap_verbmasks slap_ldap_response_code_[] = {
+ { BER_BVC("success"), LDAP_SUCCESS },
+
+ { BER_BVC("operationsError"), LDAP_OPERATIONS_ERROR },
+ { BER_BVC("protocolError"), LDAP_PROTOCOL_ERROR },
+ { BER_BVC("timelimitExceeded"), LDAP_TIMELIMIT_EXCEEDED },
+ { BER_BVC("sizelimitExceeded"), LDAP_SIZELIMIT_EXCEEDED },
+ { BER_BVC("compareFalse"), LDAP_COMPARE_FALSE },
+ { BER_BVC("compareTrue"), LDAP_COMPARE_TRUE },
+
+ { BER_BVC("authMethodNotSupported"), LDAP_AUTH_METHOD_NOT_SUPPORTED },
+ { BER_BVC("strongAuthNotSupported"), LDAP_STRONG_AUTH_NOT_SUPPORTED },
+ { BER_BVC("strongAuthRequired"), LDAP_STRONG_AUTH_REQUIRED },
+ { BER_BVC("strongerAuthRequired"), LDAP_STRONGER_AUTH_REQUIRED },
+#if 0 /* not LDAPv3 */
+ { BER_BVC("partialResults"), LDAP_PARTIAL_RESULTS },
+#endif
+
+ { BER_BVC("referral"), LDAP_REFERRAL },
+ { BER_BVC("adminlimitExceeded"), LDAP_ADMINLIMIT_EXCEEDED },
+ { BER_BVC("unavailableCriticalExtension"), LDAP_UNAVAILABLE_CRITICAL_EXTENSION },
+ { BER_BVC("confidentialityRequired"), LDAP_CONFIDENTIALITY_REQUIRED },
+ { BER_BVC("saslBindInProgress"), LDAP_SASL_BIND_IN_PROGRESS },
+
+ { BER_BVC("noSuchAttribute"), LDAP_NO_SUCH_ATTRIBUTE },
+ { BER_BVC("undefinedType"), LDAP_UNDEFINED_TYPE },
+ { BER_BVC("inappropriateMatching"), LDAP_INAPPROPRIATE_MATCHING },
+ { BER_BVC("constraintViolation"), LDAP_CONSTRAINT_VIOLATION },
+ { BER_BVC("typeOrValueExists"), LDAP_TYPE_OR_VALUE_EXISTS },
+ { BER_BVC("invalidSyntax"), LDAP_INVALID_SYNTAX },
+
+ { BER_BVC("noSuchObject"), LDAP_NO_SUCH_OBJECT },
+ { BER_BVC("aliasProblem"), LDAP_ALIAS_PROBLEM },
+ { BER_BVC("invalidDnSyntax"), LDAP_INVALID_DN_SYNTAX },
+#if 0 /* not LDAPv3 */
+ { BER_BVC("isLeaf"), LDAP_IS_LEAF },
+#endif
+ { BER_BVC("aliasDerefProblem"), LDAP_ALIAS_DEREF_PROBLEM },
+
+ { BER_BVC("proxyAuthzFailure"), LDAP_X_PROXY_AUTHZ_FAILURE },
+ { BER_BVC("inappropriateAuth"), LDAP_INAPPROPRIATE_AUTH },
+ { BER_BVC("invalidCredentials"), LDAP_INVALID_CREDENTIALS },
+ { BER_BVC("insufficientAccess"), LDAP_INSUFFICIENT_ACCESS },
+
+ { BER_BVC("busy"), LDAP_BUSY },
+ { BER_BVC("unavailable"), LDAP_UNAVAILABLE },
+ { BER_BVC("unwillingToPerform"), LDAP_UNWILLING_TO_PERFORM },
+ { BER_BVC("loopDetect"), LDAP_LOOP_DETECT },
+
+ { BER_BVC("namingViolation"), LDAP_NAMING_VIOLATION },
+ { BER_BVC("objectClassViolation"), LDAP_OBJECT_CLASS_VIOLATION },
+ { BER_BVC("notAllowedOnNonleaf"), LDAP_NOT_ALLOWED_ON_NONLEAF },
+ { BER_BVC("notAllowedOnRdn"), LDAP_NOT_ALLOWED_ON_RDN },
+ { BER_BVC("alreadyExists"), LDAP_ALREADY_EXISTS },
+ { BER_BVC("noObjectClassMods"), LDAP_NO_OBJECT_CLASS_MODS },
+ { BER_BVC("resultsTooLarge"), LDAP_RESULTS_TOO_LARGE },
+ { BER_BVC("affectsMultipleDsas"), LDAP_AFFECTS_MULTIPLE_DSAS },
+
+ { BER_BVC("other"), LDAP_OTHER },
+
+ /* extension-specific */
+
+ { BER_BVC("cupResourcesExhausted"), LDAP_CUP_RESOURCES_EXHAUSTED },
+ { BER_BVC("cupSecurityViolation"), LDAP_CUP_SECURITY_VIOLATION },
+ { BER_BVC("cupInvalidData"), LDAP_CUP_INVALID_DATA },
+ { BER_BVC("cupUnsupportedScheme"), LDAP_CUP_UNSUPPORTED_SCHEME },
+ { BER_BVC("cupReloadRequired"), LDAP_CUP_RELOAD_REQUIRED },
+
+ { BER_BVC("cancelled"), LDAP_CANCELLED },
+ { BER_BVC("noSuchOperation"), LDAP_NO_SUCH_OPERATION },
+ { BER_BVC("tooLate"), LDAP_TOO_LATE },
+ { BER_BVC("cannotCancel"), LDAP_CANNOT_CANCEL },
+
+ { BER_BVC("assertionFailed"), LDAP_ASSERTION_FAILED },
+
+ { BER_BVC("proxiedAuthorizationDenied"), LDAP_PROXIED_AUTHORIZATION_DENIED },
+
+ { BER_BVC("syncRefreshRequired"), LDAP_SYNC_REFRESH_REQUIRED },
+
+ { BER_BVC("noOperation"), LDAP_X_NO_OPERATION },
+
+ { BER_BVNULL, 0 }
+};
+
+slap_verbmasks *slap_ldap_response_code = slap_ldap_response_code_;
+
+int
+slap_ldap_response_code_register( struct berval *bv, int err )
+{
+ return slap_verbmask_register( slap_ldap_response_code_,
+ &slap_ldap_response_code, bv, err );
+}
+
+#ifdef HAVE_TLS
+static slap_verbmasks tlskey[] = {
+ { BER_BVC("no"), SB_TLS_OFF },
+ { BER_BVC("yes"), SB_TLS_ON },
+ { BER_BVC("critical"), SB_TLS_CRITICAL },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks crlkeys[] = {
+ { BER_BVC("none"), LDAP_OPT_X_TLS_CRL_NONE },
+ { BER_BVC("peer"), LDAP_OPT_X_TLS_CRL_PEER },
+ { BER_BVC("all"), LDAP_OPT_X_TLS_CRL_ALL },
+ { BER_BVNULL, 0 }
+ };
+
+static slap_verbmasks vfykeys[] = {
+ { BER_BVC("never"), LDAP_OPT_X_TLS_NEVER },
+ { BER_BVC("allow"), LDAP_OPT_X_TLS_ALLOW },
+ { BER_BVC("try"), LDAP_OPT_X_TLS_TRY },
+ { BER_BVC("demand"), LDAP_OPT_X_TLS_DEMAND },
+ { BER_BVC("hard"), LDAP_OPT_X_TLS_HARD },
+ { BER_BVC("true"), LDAP_OPT_X_TLS_HARD },
+ { BER_BVNULL, 0 }
+ };
+#endif
+
+static slap_verbmasks methkey[] = {
+ { BER_BVC("none"), LDAP_AUTH_NONE },
+ { BER_BVC("simple"), LDAP_AUTH_SIMPLE },
+#ifdef HAVE_CYRUS_SASL
+ { BER_BVC("sasl"), LDAP_AUTH_SASL },
+#endif
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks versionkey[] = {
+ { BER_BVC("2"), LDAP_VERSION2 },
+ { BER_BVC("3"), LDAP_VERSION3 },
+ { BER_BVNULL, 0 }
+};
+
+int
+slap_keepalive_parse(
+ struct berval *val,
+ void *bc,
+ slap_cf_aux_table *tab0,
+ const char *tabmsg,
+ int unparse )
+{
+ if ( unparse ) {
+ slap_keepalive *sk = (slap_keepalive *)bc;
+ int rc = snprintf( val->bv_val, val->bv_len, "%d:%d:%d",
+ sk->sk_idle, sk->sk_probes, sk->sk_interval );
+ if ( rc < 0 ) {
+ return -1;
+ }
+
+ if ( (unsigned)rc >= val->bv_len ) {
+ return -1;
+ }
+
+ val->bv_len = rc;
+
+ } else {
+ char *s = val->bv_val;
+ char *next;
+ slap_keepalive *sk = (slap_keepalive *)bc;
+ slap_keepalive sk2;
+
+ if ( s[0] == ':' ) {
+ sk2.sk_idle = 0;
+ s++;
+
+ } else {
+ sk2.sk_idle = strtol( s, &next, 10 );
+ if ( next == s || next[0] != ':' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_idle < 0 ) {
+ return -1;
+ }
+
+ s = ++next;
+ }
+
+ if ( s[0] == ':' ) {
+ sk2.sk_probes = 0;
+ s++;
+
+ } else {
+ sk2.sk_probes = strtol( s, &next, 10 );
+ if ( next == s || next[0] != ':' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_probes < 0 ) {
+ return -1;
+ }
+
+ s = ++next;
+ }
+
+ if ( *s == '\0' ) {
+ sk2.sk_interval = 0;
+
+ } else {
+ sk2.sk_interval = strtol( s, &next, 10 );
+ if ( next == s || next[0] != '\0' ) {
+ return -1;
+ }
+
+ if ( sk2.sk_interval < 0 ) {
+ return -1;
+ }
+ }
+
+ *sk = sk2;
+
+ ber_memfree( val->bv_val );
+ BER_BVZERO( val );
+ }
+
+ return 0;
+}
+
+static int
+slap_sb_uri(
+ struct berval *val,
+ void *bcp,
+ slap_cf_aux_table *tab0,
+ const char *tabmsg,
+ int unparse )
+{
+ slap_bindconf *bc = bcp;
+ if ( unparse ) {
+ if ( bc->sb_uri.bv_len >= val->bv_len )
+ return -1;
+ val->bv_len = bc->sb_uri.bv_len;
+ AC_MEMCPY( val->bv_val, bc->sb_uri.bv_val, val->bv_len );
+ } else {
+ bc->sb_uri = *val;
+#ifdef HAVE_TLS
+ if ( ldap_is_ldaps_url( val->bv_val ))
+ bc->sb_tls_do_init = 1;
+#endif
+ }
+ return 0;
+}
+
+static slap_cf_aux_table bindkey[] = {
+ { BER_BVC("uri="), 0, 'x', 1, slap_sb_uri },
+ { BER_BVC("version="), offsetof(slap_bindconf, sb_version), 'i', 0, versionkey },
+ { BER_BVC("bindmethod="), offsetof(slap_bindconf, sb_method), 'i', 0, methkey },
+ { BER_BVC("timeout="), offsetof(slap_bindconf, sb_timeout_api), 'i', 0, NULL },
+ { BER_BVC("network-timeout="), offsetof(slap_bindconf, sb_timeout_net), 'i', 0, NULL },
+ { BER_BVC("binddn="), offsetof(slap_bindconf, sb_binddn), 'b', 1, (slap_verbmasks *)dnNormalize },
+ { BER_BVC("credentials="), offsetof(slap_bindconf, sb_cred), 'b', 1, NULL },
+ { BER_BVC("saslmech="), offsetof(slap_bindconf, sb_saslmech), 'b', 0, NULL },
+ { BER_BVC("secprops="), offsetof(slap_bindconf, sb_secprops), 's', 0, NULL },
+ { BER_BVC("realm="), offsetof(slap_bindconf, sb_realm), 'b', 0, NULL },
+ { BER_BVC("authcID="), offsetof(slap_bindconf, sb_authcId), 'b', 1, NULL },
+ { BER_BVC("authzID="), offsetof(slap_bindconf, sb_authzId), 'b', 1, (slap_verbmasks *)authzNormalize },
+ { BER_BVC("keepalive="), offsetof(slap_bindconf, sb_keepalive), 'x', 0, (slap_verbmasks *)slap_keepalive_parse },
+ { BER_BVC("tcp-user-timeout="), offsetof(slap_bindconf, sb_tcp_user_timeout), 'u', 0, NULL },
+#ifdef HAVE_TLS
+ /* NOTE: replace "14" with the actual index
+ * of the first TLS-related line */
+#define aux_TLS (bindkey+14) /* beginning of TLS keywords */
+
+ { BER_BVC("starttls="), offsetof(slap_bindconf, sb_tls), 'i', 0, tlskey },
+ { BER_BVC("tls_cert="), offsetof(slap_bindconf, sb_tls_cert), 's', 1, NULL },
+ { BER_BVC("tls_key="), offsetof(slap_bindconf, sb_tls_key), 's', 1, NULL },
+ { BER_BVC("tls_cacert="), offsetof(slap_bindconf, sb_tls_cacert), 's', 1, NULL },
+ { BER_BVC("tls_cacertdir="), offsetof(slap_bindconf, sb_tls_cacertdir), 's', 1, NULL },
+ { BER_BVC("tls_reqcert="), offsetof(slap_bindconf, sb_tls_reqcert), 's', 0, NULL },
+ { BER_BVC("tls_reqsan="), offsetof(slap_bindconf, sb_tls_reqsan), 's', 0, NULL },
+ { BER_BVC("tls_cipher_suite="), offsetof(slap_bindconf, sb_tls_cipher_suite), 's', 0, NULL },
+ { BER_BVC("tls_protocol_min="), offsetof(slap_bindconf, sb_tls_protocol_min), 's', 0, NULL },
+ { BER_BVC("tls_ecname="), offsetof(slap_bindconf, sb_tls_ecname), 's', 0, NULL },
+#ifdef HAVE_OPENSSL
+ { BER_BVC("tls_crlcheck="), offsetof(slap_bindconf, sb_tls_crlcheck), 's', 0, NULL },
+#endif
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+/*
+ * 's': char *
+ * 'b': struct berval; if !NULL, normalize using ((slap_mr_normalize_func *)aux)
+ * 'i': int; if !NULL, compute using ((slap_verbmasks *)aux)
+ * 'u': unsigned
+ * 'I': long
+ * 'U': unsigned long
+ */
+
+int
+slap_cf_aux_table_parse( const char *word, void *dst, slap_cf_aux_table *tab0, LDAP_CONST char *tabmsg )
+{
+ int rc = SLAP_CONF_UNKNOWN;
+ slap_cf_aux_table *tab;
+
+ for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) {
+ if ( !strncasecmp( word, tab->key.bv_val, tab->key.bv_len ) ) {
+ char **cptr;
+ int *iptr, j;
+ unsigned *uptr;
+ long *lptr;
+ unsigned long *ulptr;
+ struct berval *bptr;
+ const char *val = word + tab->key.bv_len;
+
+ switch ( tab->type ) {
+ case 's':
+ cptr = (char **)((char *)dst + tab->off);
+ *cptr = ch_strdup( val );
+ rc = 0;
+ break;
+
+ case 'b':
+ bptr = (struct berval *)((char *)dst + tab->off);
+ if ( tab->aux != NULL ) {
+ struct berval dn;
+ slap_mr_normalize_func *normalize = (slap_mr_normalize_func *)tab->aux;
+
+ ber_str2bv( val, 0, 0, &dn );
+ rc = normalize( 0, NULL, NULL, &dn, bptr, NULL );
+
+ } else {
+ ber_str2bv( val, 0, 1, bptr );
+ rc = 0;
+ }
+ break;
+
+ case 'i':
+ iptr = (int *)((char *)dst + tab->off);
+
+ if ( tab->aux != NULL ) {
+ slap_verbmasks *aux = (slap_verbmasks *)tab->aux;
+
+ assert( aux != NULL );
+
+ rc = 1;
+ for ( j = 0; !BER_BVISNULL( &aux[j].word ); j++ ) {
+ if ( !strcasecmp( val, aux[j].word.bv_val ) ) {
+ *iptr = aux[j].mask;
+ rc = 0;
+ break;
+ }
+ }
+
+ } else {
+ rc = lutil_atoix( iptr, val, 0 );
+ }
+ break;
+
+ case 'u':
+ uptr = (unsigned *)((char *)dst + tab->off);
+
+ rc = lutil_atoux( uptr, val, 0 );
+ break;
+
+ case 'I':
+ lptr = (long *)((char *)dst + tab->off);
+
+ rc = lutil_atolx( lptr, val, 0 );
+ break;
+
+ case 'U':
+ ulptr = (unsigned long *)((char *)dst + tab->off);
+
+ rc = lutil_atoulx( ulptr, val, 0 );
+ break;
+
+ case 'x':
+ if ( tab->aux != NULL ) {
+ struct berval value;
+ slap_cf_aux_table_parse_x *func = (slap_cf_aux_table_parse_x *)tab->aux;
+
+ ber_str2bv( val, 0, 1, &value );
+
+ rc = func( &value, (void *)((char *)dst + tab->off), tab, tabmsg, 0 );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "invalid %s value %s\n",
+ tabmsg, word );
+ }
+
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+int
+slap_cf_aux_table_unparse( void *src, struct berval *bv, slap_cf_aux_table *tab0 )
+{
+ char buf[AC_LINE_MAX], *ptr;
+ slap_cf_aux_table *tab;
+ struct berval tmp;
+
+ ptr = buf;
+ for (tab = tab0; !BER_BVISNULL(&tab->key); tab++ ) {
+ char **cptr;
+ int *iptr, i;
+ unsigned *uptr;
+ long *lptr;
+ unsigned long *ulptr;
+ struct berval *bptr;
+
+ cptr = (char **)((char *)src + tab->off);
+
+ switch ( tab->type ) {
+ case 'b':
+ bptr = (struct berval *)((char *)src + tab->off);
+ cptr = &bptr->bv_val;
+
+ case 's':
+ if ( *cptr ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ if ( tab->quote ) *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, *cptr );
+ if ( tab->quote ) *ptr++ = '"';
+ }
+ break;
+
+ case 'i':
+ iptr = (int *)((char *)src + tab->off);
+
+ if ( tab->aux != NULL ) {
+ slap_verbmasks *aux = (slap_verbmasks *)tab->aux;
+
+ for ( i = 0; !BER_BVISNULL( &aux[i].word ); i++ ) {
+ if ( *iptr == aux[i].mask ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr = lutil_strcopy( ptr, aux[i].word.bv_val );
+ break;
+ }
+ }
+
+ } else {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%d", *iptr );
+ }
+ break;
+
+ case 'u':
+ uptr = (unsigned *)((char *)src + tab->off);
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%u", *uptr );
+ break;
+
+ case 'I':
+ lptr = (long *)((char *)src + tab->off);
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%ld", *lptr );
+ break;
+
+ case 'U':
+ ulptr = (unsigned long *)((char *)src + tab->off);
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ ptr += snprintf( ptr, sizeof( buf ) - ( ptr - buf ), "%lu", *ulptr );
+ break;
+
+ case 'x':
+ {
+ char *saveptr=ptr;
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, tab->key.bv_val );
+ if ( tab->quote ) *ptr++ = '"';
+ if ( tab->aux != NULL ) {
+ struct berval value;
+ slap_cf_aux_table_parse_x *func = (slap_cf_aux_table_parse_x *)tab->aux;
+ int rc;
+
+ value.bv_val = ptr;
+ value.bv_len = buf + sizeof( buf ) - ptr;
+
+ rc = func( &value, (void *)((char *)src + tab->off), tab, "(unparse)", 1 );
+ if ( rc == 0 ) {
+ if (value.bv_len) {
+ ptr += value.bv_len;
+ } else {
+ ptr = saveptr;
+ break;
+ }
+ }
+ }
+ if ( tab->quote ) *ptr++ = '"';
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+ tmp.bv_val = buf;
+ tmp.bv_len = ptr - buf;
+ ber_dupbv( bv, &tmp );
+ return 0;
+}
+
+int
+slap_tls_get_config( LDAP *ld, int opt, char **val )
+{
+#ifdef HAVE_TLS
+ slap_verbmasks *keys;
+ int i, ival;
+
+ *val = NULL;
+ switch( opt ) {
+ case LDAP_OPT_X_TLS_CRLCHECK:
+ keys = crlkeys;
+ break;
+ case LDAP_OPT_X_TLS_REQUIRE_CERT:
+ keys = vfykeys;
+ break;
+ case LDAP_OPT_X_TLS_PROTOCOL_MIN: {
+ char buf[8];
+ ldap_pvt_tls_get_option( ld, opt, &ival );
+ snprintf( buf, sizeof( buf ), "%d.%d",
+ ( ival >> 8 ) & 0xff, ival & 0xff );
+ *val = ch_strdup( buf );
+ return 0;
+ }
+ default:
+ return -1;
+ }
+ ldap_pvt_tls_get_option( ld, opt, &ival );
+ for (i=0; !BER_BVISNULL(&keys[i].word); i++) {
+ if (keys[i].mask == ival) {
+ *val = ch_strdup( keys[i].word.bv_val );
+ return 0;
+ }
+ }
+#endif
+ return -1;
+}
+
+int
+bindconf_tls_parse( const char *word, slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ if ( slap_cf_aux_table_parse( word, bc, aux_TLS, "tls config" ) == 0 ) {
+ bc->sb_tls_do_init = 1;
+ return 0;
+ }
+#endif
+ return -1;
+}
+
+int
+bindconf_tls_unparse( slap_bindconf *bc, struct berval *bv )
+{
+#ifdef HAVE_TLS
+ return slap_cf_aux_table_unparse( bc, bv, aux_TLS );
+#endif
+ return -1;
+}
+
+int
+bindconf_parse( const char *word, slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ /* Detect TLS config changes explicitly */
+ if ( bindconf_tls_parse( word, bc ) == 0 ) {
+ return 0;
+ }
+#endif
+ return slap_cf_aux_table_parse( word, bc, bindkey, "bind config" );
+}
+
+int
+bindconf_unparse( slap_bindconf *bc, struct berval *bv )
+{
+ return slap_cf_aux_table_unparse( bc, bv, bindkey );
+}
+
+void bindconf_free( slap_bindconf *bc ) {
+ if ( !BER_BVISNULL( &bc->sb_uri ) ) {
+ ch_free( bc->sb_uri.bv_val );
+ BER_BVZERO( &bc->sb_uri );
+ }
+ if ( !BER_BVISNULL( &bc->sb_binddn ) ) {
+ ch_free( bc->sb_binddn.bv_val );
+ BER_BVZERO( &bc->sb_binddn );
+ }
+ if ( !BER_BVISNULL( &bc->sb_cred ) ) {
+ ch_free( bc->sb_cred.bv_val );
+ BER_BVZERO( &bc->sb_cred );
+ }
+ if ( !BER_BVISNULL( &bc->sb_saslmech ) ) {
+ ch_free( bc->sb_saslmech.bv_val );
+ BER_BVZERO( &bc->sb_saslmech );
+ }
+ if ( bc->sb_secprops ) {
+ ch_free( bc->sb_secprops );
+ bc->sb_secprops = NULL;
+ }
+ if ( !BER_BVISNULL( &bc->sb_realm ) ) {
+ ch_free( bc->sb_realm.bv_val );
+ BER_BVZERO( &bc->sb_realm );
+ }
+ if ( !BER_BVISNULL( &bc->sb_authcId ) ) {
+ ch_free( bc->sb_authcId.bv_val );
+ BER_BVZERO( &bc->sb_authcId );
+ }
+ if ( !BER_BVISNULL( &bc->sb_authzId ) ) {
+ ch_free( bc->sb_authzId.bv_val );
+ BER_BVZERO( &bc->sb_authzId );
+ }
+#ifdef HAVE_TLS
+ if ( bc->sb_tls_cert ) {
+ ch_free( bc->sb_tls_cert );
+ bc->sb_tls_cert = NULL;
+ }
+ if ( bc->sb_tls_key ) {
+ ch_free( bc->sb_tls_key );
+ bc->sb_tls_key = NULL;
+ }
+ if ( bc->sb_tls_cacert ) {
+ ch_free( bc->sb_tls_cacert );
+ bc->sb_tls_cacert = NULL;
+ }
+ if ( bc->sb_tls_cacertdir ) {
+ ch_free( bc->sb_tls_cacertdir );
+ bc->sb_tls_cacertdir = NULL;
+ }
+ if ( bc->sb_tls_reqcert ) {
+ ch_free( bc->sb_tls_reqcert );
+ bc->sb_tls_reqcert = NULL;
+ }
+ if ( bc->sb_tls_reqsan ) {
+ ch_free( bc->sb_tls_reqsan );
+ bc->sb_tls_reqsan = NULL;
+ }
+ if ( bc->sb_tls_cipher_suite ) {
+ ch_free( bc->sb_tls_cipher_suite );
+ bc->sb_tls_cipher_suite = NULL;
+ }
+ if ( bc->sb_tls_protocol_min ) {
+ ch_free( bc->sb_tls_protocol_min );
+ bc->sb_tls_protocol_min = NULL;
+ }
+ if ( bc->sb_tls_ecname ) {
+ ch_free( bc->sb_tls_ecname );
+ bc->sb_tls_ecname = NULL;
+ }
+#ifdef HAVE_OPENSSL
+ if ( bc->sb_tls_crlcheck ) {
+ ch_free( bc->sb_tls_crlcheck );
+ bc->sb_tls_crlcheck = NULL;
+ }
+#endif
+ if ( bc->sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( bc->sb_tls_ctx );
+ bc->sb_tls_ctx = NULL;
+ }
+#endif
+}
+
+void
+bindconf_tls_defaults( slap_bindconf *bc )
+{
+#ifdef HAVE_TLS
+ if ( bc->sb_tls_do_init ) {
+ if ( !bc->sb_tls_cacert )
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CACERTFILE,
+ &bc->sb_tls_cacert );
+ if ( !bc->sb_tls_cacertdir )
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CACERTDIR,
+ &bc->sb_tls_cacertdir );
+ if ( !bc->sb_tls_cert )
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CERTFILE,
+ &bc->sb_tls_cert );
+ if ( !bc->sb_tls_key )
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_KEYFILE,
+ &bc->sb_tls_key );
+ if ( !bc->sb_tls_cipher_suite )
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CIPHER_SUITE,
+ &bc->sb_tls_cipher_suite );
+ if ( !bc->sb_tls_reqcert )
+ bc->sb_tls_reqcert = ch_strdup("demand");
+ if ( !bc->sb_tls_reqsan )
+ bc->sb_tls_reqsan = ch_strdup("allow");
+ if ( !bc->sb_tls_ecname )
+ slap_tls_get_config( slap_tls_ld, LDAP_OPT_X_TLS_ECNAME,
+ &bc->sb_tls_ecname );
+#ifdef HAVE_OPENSSL
+ if ( !bc->sb_tls_crlcheck )
+ slap_tls_get_config( slap_tls_ld, LDAP_OPT_X_TLS_CRLCHECK,
+ &bc->sb_tls_crlcheck );
+#endif
+ }
+#endif
+}
+
+#ifdef HAVE_TLS
+static struct {
+ const char *key;
+ size_t offset;
+ int opt;
+} bindtlsopts[] = {
+ { "tls_cert", offsetof(slap_bindconf, sb_tls_cert), LDAP_OPT_X_TLS_CERTFILE },
+ { "tls_key", offsetof(slap_bindconf, sb_tls_key), LDAP_OPT_X_TLS_KEYFILE },
+ { "tls_cacert", offsetof(slap_bindconf, sb_tls_cacert), LDAP_OPT_X_TLS_CACERTFILE },
+ { "tls_cacertdir", offsetof(slap_bindconf, sb_tls_cacertdir), LDAP_OPT_X_TLS_CACERTDIR },
+ { "tls_cipher_suite", offsetof(slap_bindconf, sb_tls_cipher_suite), LDAP_OPT_X_TLS_CIPHER_SUITE },
+ { "tls_ecname", offsetof(slap_bindconf, sb_tls_ecname), LDAP_OPT_X_TLS_ECNAME },
+ {0, 0}
+};
+
+int bindconf_tls_set( slap_bindconf *bc, LDAP *ld )
+{
+ int i, rc, newctx = 0, res = 0;
+ char *ptr = (char *)bc, **word;
+
+ if ( bc->sb_tls_do_init ) {
+ for (i=0; bindtlsopts[i].opt; i++) {
+ word = (char **)(ptr + bindtlsopts[i].offset);
+ if ( *word ) {
+ rc = ldap_set_option( ld, bindtlsopts[i].opt, *word );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bindconf_tls_set: failed to set %s to %s\n",
+ bindtlsopts[i].key, *word );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+ }
+ if ( bc->sb_tls_reqcert ) {
+ rc = ldap_pvt_tls_config( ld, LDAP_OPT_X_TLS_REQUIRE_CERT,
+ bc->sb_tls_reqcert );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bindconf_tls_set: failed to set tls_reqcert to %s\n",
+ bc->sb_tls_reqcert );
+ res = -1;
+ } else {
+ newctx = 1;
+ /* retrieve the parsed setting for later use */
+ ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &bc->sb_tls_int_reqcert );
+ }
+ }
+ if ( bc->sb_tls_reqsan ) {
+ rc = ldap_pvt_tls_config( ld, LDAP_OPT_X_TLS_REQUIRE_SAN,
+ bc->sb_tls_reqsan );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bindconf_tls_set: failed to set tls_reqsan to %s\n",
+ bc->sb_tls_reqsan );
+ res = -1;
+ } else {
+ newctx = 1;
+ /* retrieve the parsed setting for later use */
+ ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_SAN, &bc->sb_tls_int_reqsan );
+ }
+ }
+ if ( bc->sb_tls_protocol_min ) {
+ rc = ldap_pvt_tls_config( ld, LDAP_OPT_X_TLS_PROTOCOL_MIN,
+ bc->sb_tls_protocol_min );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bindconf_tls_set: failed to set tls_protocol_min to %s\n",
+ bc->sb_tls_protocol_min );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+#ifdef HAVE_OPENSSL
+ if ( bc->sb_tls_crlcheck ) {
+ rc = ldap_pvt_tls_config( ld, LDAP_OPT_X_TLS_CRLCHECK,
+ bc->sb_tls_crlcheck );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "bindconf_tls_set: failed to set tls_crlcheck to %s\n",
+ bc->sb_tls_crlcheck );
+ res = -1;
+ } else
+ newctx = 1;
+ }
+#endif
+ if ( !res )
+ bc->sb_tls_do_init = 0;
+ }
+
+ if ( newctx ) {
+ int opt = 0;
+
+ if ( bc->sb_tls_ctx ) {
+ ldap_pvt_tls_ctx_free( bc->sb_tls_ctx );
+ bc->sb_tls_ctx = NULL;
+ }
+ rc = ldap_set_option( ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if ( rc )
+ res = rc;
+ else
+ ldap_get_option( ld, LDAP_OPT_X_TLS_CTX, &bc->sb_tls_ctx );
+ } else if ( bc->sb_tls_ctx ) {
+ rc = ldap_set_option( ld, LDAP_OPT_X_TLS_CTX, bc->sb_tls_ctx );
+ if ( rc == LDAP_SUCCESS ) {
+ /* these options aren't actually inside the ctx, so have to be set again */
+ ldap_set_option( ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &bc->sb_tls_int_reqcert );
+ ldap_set_option( ld, LDAP_OPT_X_TLS_REQUIRE_SAN, &bc->sb_tls_int_reqsan );
+ } else
+ res = rc;
+ }
+
+ return res;
+}
+#endif
+
+/*
+ * set connection keepalive options
+ */
+void
+slap_client_keepalive(LDAP *ld, slap_keepalive *sk)
+{
+ if (!sk) return;
+
+ if ( sk->sk_idle ) {
+ ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_IDLE, &sk->sk_idle );
+ }
+
+ if ( sk->sk_probes ) {
+ ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_PROBES, &sk->sk_probes );
+ }
+
+ if ( sk->sk_interval ) {
+ ldap_set_option( ld, LDAP_OPT_X_KEEPALIVE_INTERVAL, &sk->sk_interval );
+ }
+
+ return;
+}
+
+/*
+ * connect to a client using the bindconf data
+ * note: should move "version" into bindconf...
+ */
+int
+slap_client_connect( LDAP **ldp, slap_bindconf *sb )
+{
+ LDAP *ld = NULL;
+ int rc;
+ struct timeval tv;
+
+ /* Init connection to provider */
+ rc = ldap_initialize( &ld, sb->sb_uri.bv_val );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_client_connect: "
+ "ldap_initialize(%s) failed (%d)\n",
+ sb->sb_uri.bv_val, rc );
+ return rc;
+ }
+
+ if ( sb->sb_version != 0 ) {
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION,
+ (const void *)&sb->sb_version );
+ }
+
+ if ( sb->sb_timeout_api ) {
+ tv.tv_sec = sb->sb_timeout_api;
+ tv.tv_usec = 0;
+ ldap_set_option( ld, LDAP_OPT_TIMEOUT, &tv );
+ }
+
+ if ( sb->sb_timeout_net ) {
+ tv.tv_sec = sb->sb_timeout_net;
+ tv.tv_usec = 0;
+ ldap_set_option( ld, LDAP_OPT_NETWORK_TIMEOUT, &tv );
+ }
+
+ /* setting network keepalive options */
+ slap_client_keepalive(ld, &sb->sb_keepalive);
+
+#ifdef HAVE_TLS
+ rc = bindconf_tls_set( sb, ld );
+ if ( rc ) {
+ char *errmsg = NULL;
+ ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &errmsg );
+ Debug( LDAP_DEBUG_ANY,
+ "slap_client_connect: "
+ "URI=%s TLS context initialization failed (%d) %s\n",
+ sb->sb_uri.bv_val, rc, errmsg ? errmsg : "" );
+ ldap_memfree( errmsg );
+ goto done;
+ }
+#endif
+
+ /* Bind */
+ if ( sb->sb_tls ) {
+ rc = ldap_start_tls_s( ld, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_client_connect: URI=%s "
+ "%s, ldap_start_tls failed (%d)\n",
+ sb->sb_uri.bv_val,
+ sb->sb_tls == SB_TLS_CRITICAL ?
+ "Error" : "Warning",
+ rc );
+ if ( sb->sb_tls == SB_TLS_CRITICAL ) {
+ goto done;
+ }
+ }
+ }
+
+ if ( sb->sb_method == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults;
+
+ if ( sb->sb_secprops != NULL ) {
+ rc = ldap_set_option( ld,
+ LDAP_OPT_X_SASL_SECPROPS, sb->sb_secprops);
+
+ if( rc != LDAP_OPT_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_client_connect: "
+ "error, ldap_set_option "
+ "(%s,SECPROPS,\"%s\") failed!\n",
+ sb->sb_uri.bv_val, sb->sb_secprops );
+ goto done;
+ }
+ }
+
+ defaults = lutil_sasl_defaults( ld,
+ sb->sb_saslmech.bv_val,
+ sb->sb_realm.bv_val,
+ sb->sb_authcId.bv_val,
+ sb->sb_cred.bv_val,
+ sb->sb_authzId.bv_val );
+ if ( defaults == NULL ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ rc = ldap_sasl_interactive_bind_s( ld,
+ sb->sb_binddn.bv_val,
+ sb->sb_saslmech.bv_val,
+ NULL, NULL,
+ LDAP_SASL_QUIET,
+ lutil_sasl_interact,
+ defaults );
+
+ lutil_sasl_freedefs( defaults );
+
+ /* FIXME: different error behaviors according to
+ * 1) return code
+ * 2) on err policy : exit, retry, backoff ...
+ */
+ if ( rc != LDAP_SUCCESS ) {
+ static struct berval bv_GSSAPI = BER_BVC( "GSSAPI" );
+
+ Debug( LDAP_DEBUG_ANY, "slap_client_connect: URI=%s "
+ "ldap_sasl_interactive_bind_s failed (%d)\n",
+ sb->sb_uri.bv_val, rc );
+
+ /* FIXME (see above comment) */
+ /* if Kerberos credentials cache is not active, retry */
+ if ( ber_bvcmp( &sb->sb_saslmech, &bv_GSSAPI ) == 0 &&
+ rc == LDAP_LOCAL_ERROR )
+ {
+ rc = LDAP_SERVER_DOWN;
+ }
+
+ goto done;
+ }
+#else /* HAVE_CYRUS_SASL */
+ /* Should never get here, we trapped this at config time */
+ assert(0);
+ Debug( LDAP_DEBUG_SYNC, "not compiled with SASL support\n" );
+ rc = LDAP_OTHER;
+ goto done;
+#endif
+
+ } else if ( sb->sb_method == LDAP_AUTH_SIMPLE ) {
+ rc = ldap_sasl_bind_s( ld,
+ sb->sb_binddn.bv_val, LDAP_SASL_SIMPLE,
+ &sb->sb_cred, NULL, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slap_client_connect: "
+ "URI=%s DN=\"%s\" "
+ "ldap_sasl_bind_s failed (%d)\n",
+ sb->sb_uri.bv_val, sb->sb_binddn.bv_val, rc );
+ goto done;
+ }
+ }
+
+done:;
+ if ( rc ) {
+ if ( ld ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ *ldp = NULL;
+ }
+
+ } else {
+ *ldp = ld;
+ }
+
+ return rc;
+}
+
+/* -------------------------------------- */
+
+
+static char *
+strtok_quote( char *line, char *sep, char **quote_ptr, int *iqp )
+{
+ int inquote;
+ char *tmp;
+ static char *next;
+
+ *quote_ptr = NULL;
+ if ( line != NULL ) {
+ next = line;
+ }
+ while ( *next && strchr( sep, *next ) ) {
+ next++;
+ }
+
+ if ( *next == '\0' ) {
+ next = NULL;
+ return( NULL );
+ }
+ tmp = next;
+
+ for ( inquote = 0; *next; ) {
+ switch ( *next ) {
+ case '"':
+ if ( inquote ) {
+ inquote = 0;
+ } else {
+ inquote = 1;
+ }
+ AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
+ break;
+
+ case '\\':
+ if ( next[1] )
+ AC_MEMCPY( next,
+ next + 1, strlen( next + 1 ) + 1 );
+ next++; /* dont parse the escaped character */
+ break;
+
+ default:
+ if ( ! inquote ) {
+ if ( strchr( sep, *next ) != NULL ) {
+ *quote_ptr = next;
+ *next++ = '\0';
+ return( tmp );
+ }
+ }
+ next++;
+ break;
+ }
+ }
+ *iqp = inquote;
+
+ return( tmp );
+}
+
+static char buf[AC_LINE_MAX];
+static char *line;
+static size_t lmax, lcur;
+
+#define CATLINE( buf ) \
+ do { \
+ size_t len = strlen( buf ); \
+ while ( lcur + len + 1 > lmax ) { \
+ lmax += AC_LINE_MAX; \
+ line = (char *) ch_realloc( line, lmax ); \
+ } \
+ strcpy( line + lcur, buf ); \
+ lcur += len; \
+ } while( 0 )
+
+static void
+fp_getline_init(ConfigArgs *c) {
+ c->lineno = -1;
+ buf[0] = '\0';
+}
+
+static int
+fp_getline( FILE *fp, ConfigArgs *c )
+{
+ char *p;
+
+ lcur = 0;
+ CATLINE(buf);
+ c->lineno++;
+
+ /* avoid stack of bufs */
+ if ( strncasecmp( line, "include", STRLENOF( "include" ) ) == 0 ) {
+ buf[0] = '\0';
+ c->line = line;
+ return(1);
+ }
+
+ while ( fgets( buf, sizeof( buf ), fp ) ) {
+ p = strchr( buf, '\n' );
+ if ( p ) {
+ if ( p > buf && p[-1] == '\r' ) {
+ --p;
+ }
+ *p = '\0';
+ }
+ /* XXX ugly */
+ c->line = line;
+ if ( line[0]
+ && ( p = line + strlen( line ) - 1 )[0] == '\\'
+ && p[-1] != '\\' )
+ {
+ p[0] = '\0';
+ lcur--;
+
+ } else {
+ if ( !isspace( (unsigned char)buf[0] ) ) {
+ return(1);
+ }
+ buf[0] = ' ';
+ }
+ CATLINE(buf);
+ c->lineno++;
+ }
+
+ buf[0] = '\0';
+ c->line = line;
+ return(line[0] ? 1 : 0);
+}
+
+int
+config_fp_parse_line(ConfigArgs *c)
+{
+ char *token;
+ static char *const hide[] = {
+ "rootpw", "replica", "syncrepl", /* in slapd */
+ "acl-bind", "acl-method", "idassert-bind", /* in back-ldap */
+ "acl-passwd", "bindpw", /* in back-<ldap/meta> */
+ "pseudorootpw", /* in back-meta */
+ "dbpasswd", /* in back-sql */
+ NULL
+ };
+ static char *const raw[] = {
+ "attributetype", "objectclass", "ditcontentrule", "ldapsyntax", NULL };
+ char *quote_ptr;
+ int i = (int)(sizeof(hide)/sizeof(hide[0])) - 1;
+ int inquote = 0;
+
+ c->tline = ch_strdup(c->line);
+ c->linelen = strlen(c->line);
+ token = strtok_quote(c->tline, " \t", &quote_ptr, &inquote);
+
+ if(token) for(i = 0; hide[i]; i++) if(!strcasecmp(token, hide[i])) break;
+ if(quote_ptr) *quote_ptr = ' ';
+ Debug(LDAP_DEBUG_CONFIG, "%s (%s%s)\n", c->log,
+ hide[i] ? hide[i] : c->line, hide[i] ? " ***" : "");
+ if(quote_ptr) *quote_ptr = '\0';
+
+ for(;; token = strtok_quote(NULL, " \t", &quote_ptr, &inquote)) {
+ if(c->argc >= c->argv_size) {
+ char **tmp;
+ tmp = ch_realloc(c->argv, (c->argv_size + ARGS_STEP) * sizeof(*c->argv));
+ if(!tmp) {
+ Debug(LDAP_DEBUG_ANY, "%s: out of memory\n", c->log );
+ return -1;
+ }
+ c->argv = tmp;
+ c->argv_size += ARGS_STEP;
+ }
+ if(token == NULL)
+ break;
+ c->argv[c->argc++] = token;
+ }
+ c->argv[c->argc] = NULL;
+ if (inquote) {
+ /* these directives parse c->line independently of argv tokenizing */
+ for(i = 0; raw[i]; i++) if (!strcasecmp(c->argv[0], raw[i])) return 0;
+
+ Debug(LDAP_DEBUG_ANY, "%s: unterminated quoted string \"%s\"\n", c->log, c->argv[c->argc-1] );
+ return -1;
+ }
+ return(0);
+}
+
+void
+config_destroy( )
+{
+ ucdata_unload( UCDATA_ALL );
+ if ( frontendDB ) {
+ /* NOTE: in case of early exit, frontendDB can be NULL */
+ if ( frontendDB->be_schemandn.bv_val )
+ free( frontendDB->be_schemandn.bv_val );
+ if ( frontendDB->be_schemadn.bv_val )
+ free( frontendDB->be_schemadn.bv_val );
+ if ( frontendDB->be_acl )
+ acl_destroy( frontendDB->be_acl );
+ }
+ free( line );
+ if ( slapd_args_file )
+ free ( slapd_args_file );
+ if ( slapd_pid_file )
+ free ( slapd_pid_file );
+ if ( default_passwd_hash )
+ ldap_charray_free( default_passwd_hash );
+}
+
+char **
+slap_str2clist( char ***out, char *in, const char *brkstr )
+{
+ char *str;
+ char *s;
+ char *lasts;
+ int i, j;
+ char **new;
+
+ /* find last element in list */
+ for (i = 0; *out && (*out)[i]; i++);
+
+ /* protect the input string from strtok */
+ str = ch_strdup( in );
+
+ if ( *str == '\0' ) {
+ free( str );
+ return( *out );
+ }
+
+ /* Count words in string */
+ j=1;
+ for ( s = str; *s; s++ ) {
+ if ( strchr( brkstr, *s ) != NULL ) {
+ j++;
+ }
+ }
+
+ *out = ch_realloc( *out, ( i + j + 1 ) * sizeof( char * ) );
+ new = *out + i;
+ for ( s = ldap_pvt_strtok( str, brkstr, &lasts );
+ s != NULL;
+ s = ldap_pvt_strtok( NULL, brkstr, &lasts ) )
+ {
+ *new = ch_strdup( s );
+ new++;
+ }
+
+ *new = NULL;
+ free( str );
+ return( *out );
+}
+
+int config_generic_wrapper( Backend *be, const char *fname, int lineno,
+ int argc, char **argv )
+{
+ ConfigArgs c = { 0 };
+ ConfigTable *ct;
+ int rc;
+
+ c.be = be;
+ c.fname = fname;
+ c.lineno = lineno;
+ c.argc = argc;
+ c.argv = argv;
+ c.valx = -1;
+ c.line = line;
+ c.op = SLAP_CONFIG_ADD;
+ snprintf( c.log, sizeof( c.log ), "%s: line %d", fname, lineno );
+
+ rc = SLAP_CONF_UNKNOWN;
+ ct = config_find_keyword( be->be_cf_ocs->co_table, &c );
+ if ( ct ) {
+ c.table = be->be_cf_ocs->co_type;
+ rc = config_add_vals( ct, &c );
+ }
+ return rc;
+}
+
+/* See if the given URL (in plain and parsed form) matches
+ * any of the server's listener addresses. Return matching
+ * Listener or NULL for no match.
+ */
+Listener *config_check_my_url( const char *url, LDAPURLDesc *lud )
+{
+ Listener **l = slapd_get_listeners();
+ int i, isMe;
+
+ /* Try a straight compare with Listener strings */
+ for ( i=0; l && l[i]; i++ ) {
+ if ( !strcasecmp( url, l[i]->sl_url.bv_val )) {
+ return l[i];
+ }
+ }
+
+ isMe = 0;
+ /* If hostname is empty, or is localhost, or matches
+ * our hostname, this url refers to this host.
+ * Compare it against listeners and ports.
+ */
+ if ( !lud->lud_host || !lud->lud_host[0] ||
+ !strncasecmp("localhost", lud->lud_host,
+ STRLENOF("localhost")) ||
+ !strcasecmp( global_host, lud->lud_host )) {
+
+ for ( i=0; l && l[i]; i++ ) {
+ LDAPURLDesc *lu2;
+ ldap_url_parse( l[i]->sl_url.bv_val, &lu2 );
+ do {
+ if ( strcasecmp( lud->lud_scheme,
+ lu2->lud_scheme ))
+ break;
+ if ( lud->lud_port != lu2->lud_port )
+ break;
+ /* Listener on ANY address */
+ if ( !lu2->lud_host || !lu2->lud_host[0] ) {
+ isMe = 1;
+ break;
+ }
+ /* URL on ANY address */
+ if ( !lud->lud_host || !lud->lud_host[0] ) {
+ isMe = 1;
+ break;
+ }
+ /* Listener has specific host, must
+ * match it
+ */
+ if ( !strcasecmp( lud->lud_host,
+ lu2->lud_host )) {
+ isMe = 1;
+ break;
+ }
+ } while(0);
+ ldap_free_urldesc( lu2 );
+ if ( isMe ) {
+ return l[i];
+ }
+ }
+ }
+ return NULL;
+}
diff --git a/servers/slapd/connection.c b/servers/slapd/connection.c
new file mode 100644
index 0000000..9b363fe
--- /dev/null
+++ b/servers/slapd/connection.c
@@ -0,0 +1,2119 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lutil.h"
+#include "slap.h"
+
+#ifdef LDAP_CONNECTIONLESS
+#include "../../libraries/liblber/lber-int.h" /* ber_int_sb_read() */
+#endif
+
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+
+static Connection *connections = NULL;
+
+static ldap_pvt_thread_mutex_t conn_nextid_mutex;
+static unsigned long conn_nextid = SLAPD_SYNC_SYNCCONN_OFFSET;
+
+static const char conn_lost_str[] = "connection lost";
+
+const char *
+connection_state2str( int state )
+{
+ switch( state ) {
+ case SLAP_C_INVALID: return "!";
+ case SLAP_C_INACTIVE: return "|";
+ case SLAP_C_CLOSING: return "C";
+ case SLAP_C_ACTIVE: return "";
+ case SLAP_C_BINDING: return "B";
+ case SLAP_C_CLIENT: return "L";
+ }
+
+ return "?";
+}
+
+static Connection* connection_get( ber_socket_t s );
+
+typedef struct conn_readinfo {
+ Operation *op;
+ ldap_pvt_thread_start_t *func;
+ void *arg;
+ void *ctx;
+ int nullop;
+} conn_readinfo;
+
+static int connection_input( Connection *c, conn_readinfo *cri );
+static void connection_close( Connection *c );
+
+static int connection_op_activate( Operation *op );
+static void connection_op_queue( Operation *op );
+static int connection_resched( Connection *conn );
+static void connection_abandon( Connection *conn );
+static void connection_destroy( Connection *c );
+
+static ldap_pvt_thread_start_t connection_operation;
+
+/*
+ * Initialize connection management infrastructure.
+ */
+int connections_init(void)
+{
+ int i;
+
+ assert( connections == NULL );
+
+ if( connections != NULL) {
+ Debug( LDAP_DEBUG_ANY, "connections_init: already initialized.\n" );
+ return -1;
+ }
+
+ /* should check return of every call */
+ ldap_pvt_thread_mutex_init( &conn_nextid_mutex );
+
+ connections = (Connection *) ch_calloc( dtblsize, sizeof(Connection) );
+
+ if( connections == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "connections_init: "
+ "allocation (%d*%ld) of connection array failed\n",
+ dtblsize, (long) sizeof(Connection) );
+ return -1;
+ }
+
+ for (i=0; i<dtblsize; i++) {
+ connections[i].c_conn_idx = i;
+ ldap_pvt_thread_mutex_init( &connections[i].c_mutex );
+ ldap_pvt_thread_mutex_init( &connections[i].c_write1_mutex );
+ ldap_pvt_thread_cond_init( &connections[i].c_write1_cv );
+ }
+
+
+ /*
+ * per entry initialization of the Connection array initialization
+ * will be done by connection_init()
+ */
+
+ return 0;
+}
+
+/*
+ * Destroy connection management infrastructure.
+ */
+
+int connections_destroy(void)
+{
+ ber_socket_t i;
+
+ /* should check return of every call */
+
+ if( connections == NULL) {
+ Debug( LDAP_DEBUG_ANY, "connections_destroy: nothing to destroy.\n" );
+ return -1;
+ }
+
+ for ( i = 0; i < dtblsize; i++ ) {
+ ldap_pvt_thread_mutex_destroy( &connections[i].c_mutex );
+ ldap_pvt_thread_mutex_destroy( &connections[i].c_write1_mutex );
+ ldap_pvt_thread_cond_destroy( &connections[i].c_write1_cv );
+ if( connections[i].c_sb ) {
+ ber_sockbuf_free( connections[i].c_sb );
+#ifdef LDAP_SLAPI
+ if ( slapi_plugins_used ) {
+ slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION,
+ &connections[i] );
+ }
+#endif
+ }
+ }
+
+ free( connections );
+ connections = NULL;
+
+ ldap_pvt_thread_mutex_destroy( &conn_nextid_mutex );
+ return 0;
+}
+
+/*
+ * shutdown all connections
+ */
+int connections_shutdown(void)
+{
+ ber_socket_t i;
+
+ for ( i = 0; i < dtblsize; i++ ) {
+ ldap_pvt_thread_mutex_lock( &connections[i].c_mutex );
+ if( connections[i].c_conn_state > SLAP_C_INVALID ) {
+
+ /* give persistent clients a chance to cleanup */
+ if( connections[i].c_conn_state == SLAP_C_CLIENT ) {
+ ldap_pvt_thread_pool_submit( &connection_pool,
+ connections[i].c_clientfunc, connections[i].c_clientarg );
+ } else {
+ /* c_mutex is locked */
+ connection_closing( &connections[i], "slapd shutdown" );
+ connection_close( &connections[i] );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &connections[i].c_mutex );
+ }
+
+ return 0;
+}
+
+/*
+ * Timeout idle connections.
+ */
+int connections_timeout_idle(time_t now)
+{
+ int i = 0;
+ ber_socket_t connindex;
+ Connection* c;
+
+ for( c = connection_first( &connindex );
+ c != NULL;
+ c = connection_next( c, &connindex ) )
+ {
+ /* Don't timeout a slow-running request or a persistent
+ * outbound connection.
+ */
+ if( c->c_n_ops_executing || c->c_n_ops_async
+ || c->c_conn_state == SLAP_C_CLIENT ) {
+ continue;
+ }
+
+ if( global_idletimeout &&
+ difftime( c->c_activitytime+global_idletimeout, now) < 0 ) {
+ /* close it */
+ connection_closing( c, "idletimeout" );
+ connection_close( c );
+ i++;
+ continue;
+ }
+ }
+ connection_done( c );
+
+ return i;
+}
+
+/* Drop all client connections */
+void connections_drop()
+{
+ Connection* c;
+ ber_socket_t connindex;
+
+ for( c = connection_first( &connindex );
+ c != NULL;
+ c = connection_next( c, &connindex ) )
+ {
+ /* Don't close a slow-running request or a persistent
+ * outbound connection.
+ */
+ if( c->c_n_ops_executing || c->c_n_ops_async
+ || c->c_conn_state == SLAP_C_CLIENT ) {
+ continue;
+ }
+ connection_closing( c, "dropping" );
+ connection_close( c );
+ }
+ connection_done( c );
+}
+
+static Connection* connection_get( ber_socket_t s )
+{
+ Connection *c;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "connection_get(%ld)\n",
+ (long) s );
+
+ assert( connections != NULL );
+
+ if(s == AC_SOCKET_INVALID) return NULL;
+
+ assert( s < dtblsize );
+ c = &connections[s];
+
+ if( c != NULL ) {
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+
+ if( c->c_conn_state == SLAP_C_INVALID ) {
+ /* connection must have been closed due to resched */
+
+ Debug( LDAP_DEBUG_CONNS,
+ "connection_get(%d): connection not used\n",
+ s );
+ assert( c->c_sd == AC_SOCKET_INVALID );
+
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+ return NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "connection_get(%d): got connid=%lu\n",
+ s, c->c_connid );
+
+ c->c_n_get++;
+
+ assert( c->c_conn_state != SLAP_C_INVALID );
+ assert( c->c_sd != AC_SOCKET_INVALID );
+
+ c->c_activitytime = slap_get_time();
+ }
+
+ return c;
+}
+
+static void connection_return( Connection *c )
+{
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+}
+
+Connection * connection_init(
+ ber_socket_t s,
+ Listener *listener,
+ const char* dnsname,
+ const char* peername,
+ int flags,
+ slap_ssf_t ssf,
+ struct berval *authid
+ LDAP_PF_LOCAL_SENDMSG_ARG(struct berval *peerbv))
+{
+ unsigned long id;
+ Connection *c;
+ ber_socket_t sfd = SLAP_FD2SOCK(s);
+
+ assert( connections != NULL );
+
+ assert( listener != NULL );
+ assert( dnsname != NULL );
+ assert( peername != NULL );
+
+#ifndef HAVE_TLS
+ assert( !( flags & CONN_IS_TLS ));
+#endif
+
+ if( s == AC_SOCKET_INVALID ) {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_init: init of socket %ld invalid.\n", (long)s );
+ return NULL;
+ }
+
+ assert( s >= 0 );
+ assert( s < dtblsize );
+ c = &connections[s];
+
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+
+ if( !c->c_sb ) {
+ c->c_send_ldap_result = slap_send_ldap_result;
+ c->c_send_search_entry = slap_send_search_entry;
+ c->c_send_search_reference = slap_send_search_reference;
+ c->c_send_ldap_extended = slap_send_ldap_extended;
+ c->c_send_ldap_intermediate = slap_send_ldap_intermediate;
+
+ BER_BVZERO( &c->c_authmech );
+ BER_BVZERO( &c->c_dn );
+ BER_BVZERO( &c->c_ndn );
+
+ c->c_listener = NULL;
+ BER_BVZERO( &c->c_peer_domain );
+ BER_BVZERO( &c->c_peer_name );
+
+ LDAP_STAILQ_INIT(&c->c_ops);
+ LDAP_STAILQ_INIT(&c->c_pending_ops);
+
+ c->c_txn = CONN_TXN_INACTIVE;
+ c->c_txn_backend = NULL;
+ LDAP_STAILQ_INIT(&c->c_txn_ops);
+
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ c->c_sasl_done = 0;
+ c->c_sasl_authctx = NULL;
+ c->c_sasl_sockctx = NULL;
+ c->c_sasl_extra = NULL;
+ c->c_sasl_bindop = NULL;
+ c->c_sasl_cbind = NULL;
+
+ c->c_sb = ber_sockbuf_alloc( );
+
+ {
+ ber_len_t max = sockbuf_max_incoming;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ c->c_currentber = NULL;
+
+#ifdef LDAP_SLAPI
+ if ( slapi_plugins_used ) {
+ slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, c );
+ }
+#endif
+ }
+
+ assert( BER_BVISNULL( &c->c_authmech ) );
+ assert( BER_BVISNULL( &c->c_dn ) );
+ assert( BER_BVISNULL( &c->c_ndn ) );
+ assert( c->c_listener == NULL );
+ assert( BER_BVISNULL( &c->c_peer_domain ) );
+ assert( BER_BVISNULL( &c->c_peer_name ) );
+ assert( LDAP_STAILQ_EMPTY(&c->c_ops) );
+ assert( LDAP_STAILQ_EMPTY(&c->c_pending_ops) );
+ assert( c->c_txn == CONN_TXN_INACTIVE );
+ assert( c->c_txn_backend == NULL );
+ assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) );
+ assert( BER_BVISNULL( &c->c_sasl_bind_mech ) );
+ assert( c->c_sasl_done == 0 );
+ assert( c->c_sasl_authctx == NULL );
+ assert( c->c_sasl_sockctx == NULL );
+ assert( c->c_sasl_extra == NULL );
+ assert( c->c_sasl_bindop == NULL );
+ assert( c->c_sasl_cbind == NULL );
+ assert( c->c_currentber == NULL );
+ assert( c->c_writewaiter == 0);
+ assert( c->c_writers == 0);
+
+ c->c_listener = listener;
+ c->c_sd = s;
+
+ if ( flags & CONN_IS_CLIENT ) {
+ c->c_connid = 0;
+ c->c_conn_state = SLAP_C_CLIENT;
+ c->c_close_reason = "?"; /* should never be needed */
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_FD, &sfd );
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+
+ return c;
+ }
+
+ ber_str2bv( dnsname, 0, 1, &c->c_peer_domain );
+ ber_str2bv( peername, 0, 1, &c->c_peer_name );
+
+ c->c_n_ops_received = 0;
+ c->c_n_ops_executing = 0;
+ c->c_n_ops_pending = 0;
+ c->c_n_ops_completed = 0;
+ c->c_n_ops_async = 0;
+
+ c->c_n_get = 0;
+ c->c_n_read = 0;
+ c->c_n_write = 0;
+
+ /* set to zero until bind, implies LDAP_VERSION3 */
+ c->c_protocol = 0;
+
+ c->c_activitytime = c->c_starttime = slap_get_time();
+
+#ifdef LDAP_CONNECTIONLESS
+ c->c_is_udp = 0;
+ if( flags & CONN_IS_UDP ) {
+ c->c_is_udp = 1;
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ LBER_SBIOD_LEVEL_PROVIDER, (void*)"udp_" );
+#endif
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_udp,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd );
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_readahead,
+ LBER_SBIOD_LEVEL_PROVIDER, NULL );
+ } else
+#endif /* LDAP_CONNECTIONLESS */
+#ifdef LDAP_PF_LOCAL
+ if ( flags & CONN_IS_IPC ) {
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ LBER_SBIOD_LEVEL_PROVIDER, (void*)"ipc_" );
+#endif
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_fd,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd );
+#ifdef LDAP_PF_LOCAL_SENDMSG
+ if ( !BER_BVISEMPTY( peerbv ))
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_UNGET_BUF, peerbv );
+#endif
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ LBER_SBIOD_LEVEL_PROVIDER, (void*)"tcp_" );
+#endif
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_tcp,
+ LBER_SBIOD_LEVEL_PROVIDER, (void *)&sfd );
+ }
+
+#ifdef LDAP_DEBUG
+ ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
+ INT_MAX, (void*)"ldap_" );
+#endif
+
+ if( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_NONBLOCK,
+ c /* non-NULL */ ) < 0 )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_init(%d, %s): set nonblocking failed\n",
+ s, c->c_peer_name.bv_val );
+
+ c->c_listener = NULL;
+ if(c->c_peer_domain.bv_val != NULL) {
+ free(c->c_peer_domain.bv_val);
+ }
+ BER_BVZERO( &c->c_peer_domain );
+ if(c->c_peer_name.bv_val != NULL) {
+ free(c->c_peer_name.bv_val);
+ }
+ BER_BVZERO( &c->c_peer_name );
+
+ ber_sockbuf_free( c->c_sb );
+ c->c_sb = ber_sockbuf_alloc( );
+ {
+ ber_len_t max = sockbuf_max_incoming;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ c->c_sd = AC_SOCKET_INVALID;
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+
+ return NULL;
+ }
+
+ ldap_pvt_thread_mutex_lock( &conn_nextid_mutex );
+ id = c->c_connid = conn_nextid++;
+ ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex );
+
+ c->c_conn_state = SLAP_C_INACTIVE;
+ c->c_close_reason = "?"; /* should never be needed */
+
+ c->c_ssf = c->c_transport_ssf = ssf;
+ c->c_tls_ssf = c->c_sasl_ssf = 0;
+
+#ifdef HAVE_TLS
+ if ( flags & CONN_IS_TLS ) {
+ c->c_is_tls = 1;
+ c->c_needs_tls_accept = 1;
+ } else {
+ c->c_is_tls = 0;
+ c->c_needs_tls_accept = 0;
+ }
+#endif
+
+ slap_sasl_open( c, 0 );
+ slap_sasl_external( c, ssf, authid );
+
+ slapd_add_internal( s, 1 );
+
+ backend_connection_init(c);
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+
+ if ( !(flags & CONN_IS_UDP ))
+ Debug( LDAP_DEBUG_STATS,
+ "conn=%ld fd=%ld ACCEPT from %s (%s)\n",
+ id, (long) s, peername, listener->sl_name.bv_val );
+
+ return c;
+}
+
+void connection2anonymous( Connection *c )
+{
+ assert( connections != NULL );
+ assert( c != NULL );
+
+ {
+ ber_len_t max = sockbuf_max_incoming;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ if ( !BER_BVISNULL( &c->c_authmech ) ) {
+ ch_free(c->c_authmech.bv_val);
+ }
+ BER_BVZERO( &c->c_authmech );
+
+ if ( !BER_BVISNULL( &c->c_dn ) ) {
+ ch_free(c->c_dn.bv_val);
+ }
+ BER_BVZERO( &c->c_dn );
+
+ if ( !BER_BVISNULL( &c->c_ndn ) ) {
+ ch_free(c->c_ndn.bv_val);
+ }
+ BER_BVZERO( &c->c_ndn );
+
+ if ( !BER_BVISNULL( &c->c_sasl_authz_dn ) ) {
+ ber_memfree_x( c->c_sasl_authz_dn.bv_val, NULL );
+ }
+ BER_BVZERO( &c->c_sasl_authz_dn );
+
+ c->c_authz_backend = NULL;
+}
+
+static void
+connection_destroy( Connection *c )
+{
+ unsigned long connid;
+ const char *close_reason;
+ Sockbuf *sb;
+ ber_socket_t sd;
+
+ assert( connections != NULL );
+ assert( c != NULL );
+ assert( c->c_conn_state != SLAP_C_INVALID );
+ assert( LDAP_STAILQ_EMPTY(&c->c_ops) );
+ assert( LDAP_STAILQ_EMPTY(&c->c_pending_ops) );
+ assert( c->c_txn == CONN_TXN_INACTIVE );
+ assert( c->c_txn_backend == NULL );
+ assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) );
+ assert( c->c_writewaiter == 0);
+ assert( c->c_writers == 0);
+
+ /* only for stats (print -1 as "%lu" may give unexpected results ;) */
+ connid = c->c_connid;
+ close_reason = c->c_close_reason;
+
+ backend_connection_destroy(c);
+
+ c->c_protocol = 0;
+ c->c_connid = -1;
+
+ c->c_activitytime = c->c_starttime = 0;
+
+ connection2anonymous( c );
+ c->c_listener = NULL;
+
+ if(c->c_peer_domain.bv_val != NULL) {
+ free(c->c_peer_domain.bv_val);
+ }
+ BER_BVZERO( &c->c_peer_domain );
+ if(c->c_peer_name.bv_val != NULL) {
+ free(c->c_peer_name.bv_val);
+ }
+ BER_BVZERO( &c->c_peer_name );
+
+ c->c_sasl_bind_in_progress = 0;
+ if(c->c_sasl_bind_mech.bv_val != NULL) {
+ free(c->c_sasl_bind_mech.bv_val);
+ }
+ BER_BVZERO( &c->c_sasl_bind_mech );
+
+ slap_sasl_close( c );
+
+ if ( c->c_currentber != NULL ) {
+ ber_free( c->c_currentber, 1 );
+ c->c_currentber = NULL;
+ }
+
+
+#ifdef LDAP_SLAPI
+ /* call destructors, then constructors; avoids unnecessary allocation */
+ if ( slapi_plugins_used ) {
+ slapi_int_clear_object_extensions( SLAPI_X_EXT_CONNECTION, c );
+ }
+#endif
+
+ sd = c->c_sd;
+ c->c_sd = AC_SOCKET_INVALID;
+ c->c_close_reason = "?"; /* should never be needed */
+
+ sb = c->c_sb;
+ c->c_sb = ber_sockbuf_alloc( );
+ {
+ ber_len_t max = sockbuf_max_incoming;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+ c->c_conn_state = SLAP_C_INVALID;
+
+ /* c must be fully reset by this point; when we call slapd_remove
+ * it may get immediately reused by a new connection.
+ */
+ if ( sd != AC_SOCKET_INVALID ) {
+ slapd_remove( sd, sb, 1, 0, 0 );
+
+ if ( close_reason == NULL ) {
+ Debug( LDAP_DEBUG_STATS, "conn=%lu fd=%ld closed\n",
+ connid, (long) sd );
+ } else {
+ Debug( LDAP_DEBUG_STATS, "conn=%lu fd=%ld closed (%s)\n",
+ connid, (long) sd, close_reason );
+ }
+ }
+}
+
+int connection_is_active( ber_socket_t s )
+{
+ Connection *c;
+
+ assert( s < dtblsize );
+ c = &connections[s];
+ return c->c_conn_state == SLAP_C_CLOSING ||
+ c->c_conn_state == SLAP_C_BINDING ||
+ c->c_conn_state == SLAP_C_ACTIVE ;
+}
+
+int connection_valid( Connection *c )
+{
+ /* c_mutex must be locked by caller */
+
+ assert( c != NULL );
+
+ return c->c_conn_state >= SLAP_C_ACTIVE &&
+ c->c_conn_state <= SLAP_C_CLIENT;
+}
+
+static void connection_abandon( Connection *c )
+{
+ /* c_mutex must be locked by caller */
+
+ Operation *o, *next, op = {0};
+ Opheader ohdr = {0};
+
+ op.o_hdr = &ohdr;
+ op.o_conn = c;
+ op.o_connid = c->c_connid;
+ op.o_tag = LDAP_REQ_ABANDON;
+
+ for ( o = LDAP_STAILQ_FIRST( &c->c_ops ); o; o=next ) {
+ SlapReply rs = {REP_RESULT};
+
+ next = LDAP_STAILQ_NEXT( o, o_next );
+ /* don't abandon an op twice */
+ if ( o->o_abandon )
+ continue;
+ op.orn_msgid = o->o_msgid;
+ o->o_abandon = 1;
+ op.o_bd = frontendDB;
+ frontendDB->be_abandon( &op, &rs );
+ }
+
+ /* remove operations in pending transaction */
+ while ( (o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL) {
+ LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next );
+ LDAP_STAILQ_NEXT(o, o_next) = NULL;
+ slap_op_free( o, NULL );
+ }
+
+ /* clear transaction */
+ c->c_txn_backend = NULL;
+ c->c_txn = CONN_TXN_INACTIVE;
+
+ /* remove pending operations */
+ while ( (o = LDAP_STAILQ_FIRST( &c->c_pending_ops )) != NULL) {
+ LDAP_STAILQ_REMOVE_HEAD( &c->c_pending_ops, o_next );
+ LDAP_STAILQ_NEXT(o, o_next) = NULL;
+ slap_op_free( o, NULL );
+ }
+ c->c_n_ops_pending = 0;
+}
+
+static void
+connection_wake_writers( Connection *c )
+{
+ /* wake write blocked operations */
+ ldap_pvt_thread_mutex_lock( &c->c_write1_mutex );
+ if ( c->c_writers > 0 ) {
+ c->c_writers = -c->c_writers;
+ ldap_pvt_thread_cond_broadcast( &c->c_write1_cv );
+ ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex );
+ if ( c->c_writewaiter ) {
+ slapd_shutsock( c->c_sd );
+ }
+ ldap_pvt_thread_mutex_lock( &c->c_write1_mutex );
+ while ( c->c_writers ) {
+ ldap_pvt_thread_cond_wait( &c->c_write1_cv, &c->c_write1_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex );
+ } else {
+ ldap_pvt_thread_mutex_unlock( &c->c_write1_mutex );
+ slapd_clr_write( c->c_sd, 1 );
+ }
+}
+
+void connection_closing( Connection *c, const char *why )
+{
+ assert( connections != NULL );
+ assert( c != NULL );
+
+ if( c->c_conn_state == SLAP_C_INVALID )
+ return;
+
+ /* c_mutex must be locked by caller */
+
+ if( c->c_conn_state != SLAP_C_CLOSING ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "connection_closing: readying conn=%lu sd=%d for close\n",
+ c->c_connid, c->c_sd );
+ /* update state to closing */
+ c->c_conn_state = SLAP_C_CLOSING;
+ c->c_close_reason = why;
+
+ /* don't listen on this port anymore */
+ slapd_clr_read( c->c_sd, 0 );
+
+ /* abandon active operations */
+ connection_abandon( c );
+
+ /* wake write blocked operations */
+ connection_wake_writers( c );
+
+ } else if( why == NULL && c->c_close_reason == conn_lost_str ) {
+ /* Client closed connection after doing Unbind. */
+ c->c_close_reason = NULL;
+ }
+}
+
+static void
+connection_close( Connection *c )
+{
+ assert( connections != NULL );
+ assert( c != NULL );
+
+ if ( c->c_conn_state != SLAP_C_CLOSING )
+ return;
+
+ /* NOTE: c_mutex should be locked by caller */
+
+ if ( !LDAP_STAILQ_EMPTY(&c->c_ops) ||
+ !LDAP_STAILQ_EMPTY(&c->c_pending_ops) )
+ {
+ Debug( LDAP_DEBUG_CONNS,
+ "connection_close: deferring conn=%lu sd=%d\n",
+ c->c_connid, c->c_sd );
+ return;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "connection_close: conn=%lu sd=%d\n",
+ c->c_connid, c->c_sd );
+
+ connection_destroy( c );
+}
+
+unsigned long connections_nextid(void)
+{
+ unsigned long id;
+ assert( connections != NULL );
+
+ ldap_pvt_thread_mutex_lock( &conn_nextid_mutex );
+
+ id = conn_nextid;
+
+ ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex );
+
+ return id;
+}
+
+/*
+ * Loop through the connections:
+ *
+ * for (c = connection_first(&i); c; c = connection_next(c, &i)) ...;
+ * connection_done(c);
+ *
+ * 'i' is the cursor, initialized by connection_first().
+ * 'c_mutex' is locked in the returned connection. The functions must
+ * be passed the previous return value so they can unlock it again.
+ */
+
+Connection* connection_first( ber_socket_t *index )
+{
+ assert( connections != NULL );
+ assert( index != NULL );
+
+ for( *index = 0; *index < dtblsize; (*index)++) {
+ if( connections[*index].c_sb ) {
+ break;
+ }
+ }
+
+ return connection_next(NULL, index);
+}
+
+/* Next connection in loop, see connection_first() */
+Connection* connection_next( Connection *c, ber_socket_t *index )
+{
+ assert( connections != NULL );
+ assert( index != NULL );
+ assert( *index <= dtblsize );
+
+ if( c != NULL ) ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+
+ c = NULL;
+
+ for(; *index < dtblsize; (*index)++) {
+ if( connections[*index].c_sb ) {
+ c = &connections[*index];
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+ if ( c->c_conn_state == SLAP_C_INVALID ) {
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+ c = NULL;
+ continue;
+ }
+ (*index)++;
+ break;
+ }
+ }
+
+ return c;
+}
+
+/* End connection loop, see connection_first() */
+void connection_done( Connection *c )
+{
+ assert( connections != NULL );
+
+ if( c != NULL ) ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+}
+
+/*
+ * connection_activity - handle the request operation op on connection
+ * conn. This routine figures out what kind of operation it is and
+ * calls the appropriate stub to handle it.
+ */
+
+/* FIXME: returns 0 in case of failure */
+#define INCR_OP_INITIATED(index) \
+ do { \
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); \
+ ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_initiated_[(index)], 1); \
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); \
+ } while (0)
+#define INCR_OP_COMPLETED(index) \
+ do { \
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex ); \
+ ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_completed, 1); \
+ ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_completed_[(index)], 1); \
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex ); \
+ } while (0)
+
+/*
+ * NOTE: keep in sync with enum in slapd.h
+ */
+static BI_op_func *opfun[] = {
+ do_bind,
+ do_unbind,
+ do_search,
+ do_compare,
+ do_modify,
+ do_modrdn,
+ do_add,
+ do_delete,
+ do_abandon,
+ do_extended,
+ NULL
+};
+
+/* Counters are per-thread, not per-connection.
+ */
+static void
+conn_counter_destroy( void *key, void *data )
+{
+ slap_counters_t **prev, *sc;
+
+ ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex );
+ for ( prev = &slap_counters.sc_next, sc = slap_counters.sc_next; sc;
+ prev = &sc->sc_next, sc = sc->sc_next ) {
+ if ( sc == data ) {
+ int i;
+
+ *prev = sc->sc_next;
+ /* Copy data to main counter */
+ ldap_pvt_mp_add( slap_counters.sc_bytes, sc->sc_bytes );
+ ldap_pvt_mp_add( slap_counters.sc_pdu, sc->sc_pdu );
+ ldap_pvt_mp_add( slap_counters.sc_entries, sc->sc_entries );
+ ldap_pvt_mp_add( slap_counters.sc_refs, sc->sc_refs );
+ ldap_pvt_mp_add( slap_counters.sc_ops_initiated, sc->sc_ops_initiated );
+ ldap_pvt_mp_add( slap_counters.sc_ops_completed, sc->sc_ops_completed );
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ ldap_pvt_mp_add( slap_counters.sc_ops_initiated_[ i ], sc->sc_ops_initiated_[ i ] );
+ ldap_pvt_mp_add( slap_counters.sc_ops_initiated_[ i ], sc->sc_ops_completed_[ i ] );
+ }
+ slap_counters_destroy( sc );
+ ber_memfree_x( data, NULL );
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex );
+}
+
+void
+operation_counter_init( Operation *op, void *ctx )
+{
+ slap_counters_t *sc;
+ void *vsc = NULL;
+
+ if ( ldap_pvt_thread_pool_getkey(
+ ctx, (void *)operation_counter_init, &vsc, NULL ) || !vsc ) {
+ vsc = ch_malloc( sizeof( slap_counters_t ));
+ sc = vsc;
+ slap_counters_init( sc );
+ ldap_pvt_thread_pool_setkey( ctx, (void*)operation_counter_init, vsc,
+ conn_counter_destroy, NULL, NULL );
+
+ ldap_pvt_thread_mutex_lock( &slap_counters.sc_mutex );
+ sc->sc_next = slap_counters.sc_next;
+ slap_counters.sc_next = sc;
+ ldap_pvt_thread_mutex_unlock( &slap_counters.sc_mutex );
+ }
+ op->o_counters = vsc;
+}
+
+void
+connection_op_finish( Operation *op, int lock )
+{
+ Connection *conn = op->o_conn;
+ void *memctx_null = NULL;
+ slap_op_t opidx = slap_req2op( op->o_tag );
+ assert( opidx != SLAP_OP_LAST );
+
+ INCR_OP_COMPLETED( opidx );
+
+ if ( lock )
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+
+ if ( op->o_tag == LDAP_REQ_BIND && conn->c_conn_state == SLAP_C_BINDING )
+ conn->c_conn_state = SLAP_C_ACTIVE;
+
+ ber_set_option( op->o_ber, LBER_OPT_BER_MEMCTX, &memctx_null );
+
+ LDAP_STAILQ_REMOVE( &conn->c_ops, op, Operation, o_next);
+ LDAP_STAILQ_NEXT(op, o_next) = NULL;
+ conn->c_n_ops_async--;
+ conn->c_n_ops_completed++;
+ connection_resched( conn );
+ if ( lock )
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+}
+
+static void *
+connection_operation( void *ctx, void *arg_v )
+{
+ int rc = LDAP_OTHER, cancel;
+ Operation *op = arg_v;
+ SlapReply rs = {REP_RESULT};
+ ber_tag_t tag = op->o_tag;
+ slap_op_t opidx = SLAP_OP_LAST;
+ Connection *conn = op->o_conn;
+ void *memctx = NULL;
+ void *memctx_null = NULL;
+ ber_len_t memsiz;
+
+ gettimeofday( &op->o_qtime, NULL );
+ op->o_qtime.tv_usec -= op->o_tusec;
+ if ( op->o_qtime.tv_usec < 0 ) {
+ op->o_qtime.tv_usec += 1000000;
+ op->o_qtime.tv_sec--;
+ }
+ op->o_qtime.tv_sec -= op->o_time;
+ operation_counter_init( op, ctx );
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex );
+ /* FIXME: returns 0 in case of failure */
+ ldap_pvt_mp_add_ulong(op->o_counters->sc_ops_initiated, 1);
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex );
+
+ op->o_threadctx = ctx;
+ op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+
+ switch ( tag ) {
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_UNBIND:
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODDN:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_SEARCH:
+ case LDAP_REQ_ABANDON:
+ case LDAP_REQ_EXTENDED:
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "connection_operation: "
+ "conn %lu unknown LDAP request 0x%lx\n",
+ conn->c_connid, tag );
+ op->o_tag = LBER_ERROR;
+ rs.sr_err = LDAP_PROTOCOL_ERROR;
+ rs.sr_text = "unknown LDAP request";
+ send_ldap_disconnect( op, &rs );
+ rc = SLAPD_DISCONNECT;
+ goto operations_error;
+ }
+
+ if( conn->c_sasl_bind_in_progress && tag != LDAP_REQ_BIND ) {
+ Debug( LDAP_DEBUG_ANY, "connection_operation: "
+ "error: SASL bind in progress (tag=%ld).\n",
+ (long) tag );
+ send_ldap_error( op, &rs, LDAP_OPERATIONS_ERROR,
+ "SASL bind in progress" );
+ rc = LDAP_OPERATIONS_ERROR;
+ goto operations_error;
+ }
+
+ if (( conn->c_txn == CONN_TXN_SPECIFY ) && (
+ ( tag == LDAP_REQ_ADD ) ||
+ ( tag == LDAP_REQ_DELETE ) ||
+ ( tag == LDAP_REQ_MODIFY ) ||
+ ( tag == LDAP_REQ_MODRDN )))
+ {
+ /* Disable SLAB allocator for all update operations
+ issued inside of a transaction */
+ op->o_tmpmemctx = NULL;
+ op->o_tmpmfuncs = &ch_mfuncs;
+ } else {
+ /* We can use Thread-Local storage for most mallocs. We can
+ * also use TL for ber parsing, but not on Add or Modify.
+ */
+#if 0
+ memsiz = ber_len( op->o_ber ) * 64;
+ if ( SLAP_SLAB_SIZE > memsiz ) memsiz = SLAP_SLAB_SIZE;
+#endif
+ memsiz = SLAP_SLAB_SIZE;
+
+ memctx = slap_sl_mem_create( memsiz, SLAP_SLAB_STACK, ctx, 1 );
+ op->o_tmpmemctx = memctx;
+ op->o_tmpmfuncs = &slap_sl_mfuncs;
+ if ( tag != LDAP_REQ_ADD && tag != LDAP_REQ_MODIFY ) {
+ /* Note - the ber and its buffer are already allocated from
+ * regular memory; this only affects subsequent mallocs that
+ * ber_scanf may invoke.
+ */
+ ber_set_option( op->o_ber, LBER_OPT_BER_MEMCTX, &memctx );
+ }
+ }
+
+ opidx = slap_req2op( tag );
+ assert( opidx != SLAP_OP_LAST );
+ INCR_OP_INITIATED( opidx );
+ rc = (*(opfun[opidx]))( op, &rs );
+
+operations_error:
+ if ( rc == SLAPD_DISCONNECT ) {
+ tag = LBER_ERROR;
+
+ } else if ( rc == SLAPD_ASYNCOP ) {
+ /* someone has claimed ownership of the op
+ * to complete it later. Don't do anything
+ * else with it now. Detach memctx too.
+ */
+ slap_sl_mem_setctx( ctx, NULL );
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+ conn->c_n_ops_executing--;
+ conn->c_n_ops_async++;
+ connection_resched( conn );
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+ return NULL;
+
+ } else if ( opidx != SLAP_OP_LAST ) {
+ /* increment completed operations count
+ * only if operation was initiated
+ * and rc != SLAPD_DISCONNECT */
+ INCR_OP_COMPLETED( opidx );
+ }
+
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+
+ if ( opidx == SLAP_OP_BIND && conn->c_conn_state == SLAP_C_BINDING )
+ conn->c_conn_state = SLAP_C_ACTIVE;
+
+ cancel = op->o_cancel;
+ if ( cancel != SLAP_CANCEL_NONE && cancel != SLAP_CANCEL_DONE ) {
+ if ( cancel == SLAP_CANCEL_REQ ) {
+ op->o_cancel = rc == SLAPD_ABANDON
+ ? SLAP_CANCEL_ACK : LDAP_TOO_LATE;
+ }
+
+ do {
+ /* Fake a cond_wait with thread_yield, then
+ * verify the result properly mutex-protected.
+ */
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+ do {
+ ldap_pvt_thread_yield();
+ } while ( (cancel = op->o_cancel) != SLAP_CANCEL_NONE
+ && cancel != SLAP_CANCEL_DONE );
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+ } while ( (cancel = op->o_cancel) != SLAP_CANCEL_NONE
+ && cancel != SLAP_CANCEL_DONE );
+ }
+
+ ber_set_option( op->o_ber, LBER_OPT_BER_MEMCTX, &memctx_null );
+
+ if ( rc != LDAP_TXN_SPECIFY_OKAY ) {
+ LDAP_STAILQ_REMOVE( &conn->c_ops, op, Operation, o_next);
+ LDAP_STAILQ_NEXT(op, o_next) = NULL;
+ }
+ conn->c_n_ops_executing--;
+ conn->c_n_ops_completed++;
+
+ switch( tag ) {
+ case LBER_ERROR:
+ case LDAP_REQ_UNBIND:
+ /* c_mutex is locked */
+ connection_closing( conn,
+ tag == LDAP_REQ_UNBIND ? NULL : "operations error" );
+ break;
+ }
+
+ connection_resched( conn );
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+ if ( rc != LDAP_TXN_SPECIFY_OKAY ) {
+ slap_op_free( op, ctx );
+ }
+ return NULL;
+}
+
+static const Listener dummy_list = { BER_BVC(""), BER_BVC("") };
+
+Connection *connection_client_setup(
+ ber_socket_t s,
+ ldap_pvt_thread_start_t *func,
+ void *arg )
+{
+ Connection *c;
+ ber_socket_t sfd = SLAP_SOCKNEW( s );
+
+ c = connection_init( sfd, (Listener *)&dummy_list, "", "",
+ CONN_IS_CLIENT, 0, NULL
+ LDAP_PF_LOCAL_SENDMSG_ARG(NULL));
+ if ( c ) {
+ c->c_clientfunc = func;
+ c->c_clientarg = arg;
+
+ slapd_add_internal( sfd, 0 );
+ }
+ return c;
+}
+
+void connection_client_enable(
+ Connection *c )
+{
+ slapd_set_read( c->c_sd, 1 );
+}
+
+void connection_client_stop(
+ Connection *c )
+{
+ Sockbuf *sb;
+ ber_socket_t s = c->c_sd;
+
+ /* get (locked) connection */
+ c = connection_get( s );
+
+ assert( c->c_conn_state == SLAP_C_CLIENT );
+
+ c->c_listener = NULL;
+ c->c_sd = AC_SOCKET_INVALID;
+ c->c_close_reason = "?"; /* should never be needed */
+ sb = c->c_sb;
+ c->c_sb = ber_sockbuf_alloc( );
+ {
+ ber_len_t max = sockbuf_max_incoming;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+ c->c_conn_state = SLAP_C_INVALID;
+ slapd_remove( s, sb, 0, 1, 0 );
+
+ connection_return( c );
+}
+
+static int connection_read( ber_socket_t s, conn_readinfo *cri );
+
+static void* connection_read_thread( void* ctx, void* argv )
+{
+ int rc ;
+ conn_readinfo cri = { NULL, NULL, NULL, NULL, 0 };
+ ber_socket_t s = (long)argv;
+
+ /*
+ * read incoming LDAP requests. If there is more than one,
+ * the first one is returned with new_op
+ */
+ cri.ctx = ctx;
+ if( ( rc = connection_read( s, &cri ) ) < 0 ) {
+ Debug( LDAP_DEBUG_CONNS, "connection_read(%d) error\n", s );
+ return (void*)(long)rc;
+ }
+
+ /* execute a single queued request in the same thread */
+ if( cri.op && !cri.nullop ) {
+ rc = (long)connection_operation( ctx, cri.op );
+ } else if ( cri.func ) {
+ rc = (long)cri.func( ctx, cri.arg );
+ }
+
+ return (void*)(long)rc;
+}
+
+int connection_read_activate( ber_socket_t s )
+{
+ int rc;
+
+ /*
+ * suspend reading on this file descriptor until a connection processing
+ * thread reads data on it. Otherwise the listener thread will repeatedly
+ * submit the same event on it to the pool.
+ */
+ rc = slapd_clr_read( s, 0 );
+ if ( rc )
+ return rc;
+
+ rc = ldap_pvt_thread_pool_submit( &connection_pool,
+ connection_read_thread, (void *)(long)s );
+
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_read_activate(%d): submit failed (%d)\n",
+ s, rc );
+ }
+
+ return rc;
+}
+
+static int
+connection_read( ber_socket_t s, conn_readinfo *cri )
+{
+ int rc = 0;
+ Connection *c;
+
+ assert( connections != NULL );
+
+ /* get (locked) connection */
+ c = connection_get( s );
+
+ if( c == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_read(%ld): no connection!\n",
+ (long) s );
+
+ return -1;
+ }
+
+ c->c_n_read++;
+
+ if( c->c_conn_state == SLAP_C_CLOSING ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "connection_read(%d): closing, ignoring input for id=%lu\n",
+ s, c->c_connid );
+ connection_return( c );
+ return 0;
+ }
+
+ if ( c->c_conn_state == SLAP_C_CLIENT ) {
+ cri->func = c->c_clientfunc;
+ cri->arg = c->c_clientarg;
+ /* read should already be cleared */
+ connection_return( c );
+ return 0;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "connection_read(%d): checking for input on id=%lu\n",
+ s, c->c_connid );
+
+#ifdef HAVE_TLS
+ if ( c->c_is_tls && c->c_needs_tls_accept ) {
+ rc = ldap_pvt_tls_accept( c->c_sb, slap_tls_ctx );
+ if ( rc < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "connection_read(%d): TLS accept failure "
+ "error=%d id=%lu, closing\n",
+ s, rc, c->c_connid );
+
+ c->c_needs_tls_accept = 0;
+ /* c_mutex is locked */
+ connection_closing( c, "TLS negotiation failure" );
+ connection_close( c );
+ connection_return( c );
+ return 0;
+
+ } else if ( rc == 0 ) {
+ void *ssl;
+ struct berval authid = BER_BVNULL;
+ char msgbuf[32];
+
+ c->c_needs_tls_accept = 0;
+
+ /* we need to let SASL know */
+ ssl = ldap_pvt_tls_sb_ctx( c->c_sb );
+
+ c->c_tls_ssf = (slap_ssf_t) ldap_pvt_tls_get_strength( ssl );
+ if( c->c_tls_ssf > c->c_ssf ) {
+ c->c_ssf = c->c_tls_ssf;
+ }
+
+ rc = dnX509peerNormalize( ssl, &authid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "connection_read(%d): "
+ "unable to get TLS client DN, error=%d id=%lu\n",
+ s, rc, c->c_connid );
+ }
+ sprintf(msgbuf, "tls_ssf=%u ssf=%u", c->c_tls_ssf, c->c_ssf);
+ Debug( LDAP_DEBUG_STATS,
+ "conn=%lu fd=%d TLS established %s tls_proto=%s tls_cipher=%s\n",
+ c->c_connid, (int) s,
+ msgbuf, ldap_pvt_tls_get_version( ssl ), ldap_pvt_tls_get_cipher( ssl ));
+ slap_sasl_external( c, c->c_tls_ssf, &authid );
+ if ( authid.bv_val ) free( authid.bv_val );
+
+ slap_sasl_cbinding( c, ssl );
+
+ } else if ( rc == 1 && ber_sockbuf_ctrl( c->c_sb,
+ LBER_SB_OPT_NEEDS_WRITE, NULL )) { /* need to retry */
+ slapd_set_write( s, 1 );
+ connection_return( c );
+ return 0;
+ }
+
+ /* if success and data is ready, fall thru to data input loop */
+ if( !ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_DATA_READY, NULL ) )
+ {
+ slapd_set_read( s, 1 );
+ connection_return( c );
+ return 0;
+ }
+ }
+#endif
+
+#ifdef HAVE_CYRUS_SASL
+ if ( c->c_sasl_layers ) {
+ /* If previous layer is not removed yet, give up for now */
+ if ( !c->c_sasl_sockctx ) {
+ slapd_set_read( s, 1 );
+ connection_return( c );
+ return 0;
+ }
+
+ c->c_sasl_layers = 0;
+
+ rc = ldap_pvt_sasl_install( c->c_sb, c->c_sasl_sockctx );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "connection_read(%d): SASL install error "
+ "error=%d id=%lu, closing\n",
+ s, rc, c->c_connid );
+
+ /* c_mutex is locked */
+ connection_closing( c, "SASL layer install failure" );
+ connection_close( c );
+ connection_return( c );
+ return 0;
+ }
+ }
+#endif
+
+#define CONNECTION_INPUT_LOOP 1
+/* #define DATA_READY_LOOP 1 */
+
+ do {
+ /* How do we do this without getting into a busy loop ? */
+ rc = connection_input( c, cri );
+ }
+#ifdef DATA_READY_LOOP
+ while( !rc && ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_DATA_READY, NULL ));
+#elif defined CONNECTION_INPUT_LOOP
+ while(!rc);
+#else
+ while(0);
+#endif
+
+ if( rc < 0 ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "connection_read(%d): input error=%d id=%lu, closing.\n",
+ s, rc, c->c_connid );
+
+ /* c_mutex is locked */
+ connection_closing( c, conn_lost_str );
+ connection_close( c );
+ connection_return( c );
+ return 0;
+ }
+
+ if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) {
+ slapd_set_write( s, 0 );
+ }
+
+ slapd_set_read( s, 1 );
+ connection_return( c );
+
+ return 0;
+}
+
+static int
+connection_input( Connection *conn , conn_readinfo *cri )
+{
+ Operation *op;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t msgid;
+ BerElement *ber;
+ int rc;
+#ifdef LDAP_CONNECTIONLESS
+ Sockaddr peeraddr;
+ char *cdn = NULL;
+#endif
+ char *defer = NULL;
+ void *ctx;
+
+ if ( conn->c_currentber == NULL &&
+ ( conn->c_currentber = ber_alloc()) == NULL )
+ {
+ Debug( LDAP_DEBUG_ANY, "ber_alloc failed\n" );
+ return -1;
+ }
+
+ sock_errset(0);
+
+#ifdef LDAP_CONNECTIONLESS
+ if ( conn->c_is_udp ) {
+#if defined(LDAP_PF_INET6)
+ char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")];
+ char addr[INET6_ADDRSTRLEN];
+#else
+ char peername[sizeof("IP=255.255.255.255:65336")];
+ char addr[INET_ADDRSTRLEN];
+#endif
+ const char *peeraddr_string = NULL;
+
+ len = ber_int_sb_read(conn->c_sb, &peeraddr, sizeof(Sockaddr));
+ if (len != sizeof(Sockaddr)) return 1;
+
+#if defined(LDAP_PF_INET6)
+ if (peeraddr.sa_addr.sa_family == AF_INET6) {
+ if ( IN6_IS_ADDR_V4MAPPED(&peeraddr.sa_in6_addr.sin6_addr) ) {
+#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
+ peeraddr_string = inet_ntop( AF_INET,
+ ((struct in_addr *)&peeraddr.sa_in6_addr.sin6_addr.s6_addr[12]),
+ addr, sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ peeraddr_string = inet_ntoa( *((struct in_addr *)
+ &peeraddr.sa_in6_addr.sin6_addr.s6_addr[12]) );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ if ( !peeraddr_string ) peeraddr_string = SLAP_STRING_UNKNOWN;
+ sprintf( peername, "IP=%s:%d", peeraddr_string,
+ (unsigned) ntohs( peeraddr.sa_in6_addr.sin6_port ) );
+ } else {
+ peeraddr_string = inet_ntop( AF_INET6,
+ &peeraddr.sa_in6_addr.sin6_addr,
+ addr, sizeof addr );
+ if ( !peeraddr_string ) peeraddr_string = SLAP_STRING_UNKNOWN;
+ sprintf( peername, "IP=[%s]:%d", peeraddr_string,
+ (unsigned) ntohs( peeraddr.sa_in6_addr.sin6_port ) );
+ }
+ } else
+#endif
+#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
+ {
+ peeraddr_string = inet_ntop( AF_INET, &peeraddr.sa_in_addr.sin_addr,
+ addr, sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ peeraddr_string = inet_ntoa( peeraddr.sa_in_addr.sin_addr );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ sprintf( peername, "IP=%s:%d",
+ peeraddr_string,
+ (unsigned) ntohs( peeraddr.sa_in_addr.sin_port ) );
+ }
+ Debug( LDAP_DEBUG_STATS,
+ "conn=%lu UDP request from %s (%s) accepted.\n",
+ conn->c_connid, peername, conn->c_sock_name.bv_val );
+ }
+#endif
+
+ tag = ber_get_next( conn->c_sb, &len, conn->c_currentber );
+ if ( tag != LDAP_TAG_MESSAGE ) {
+ int err = sock_errno();
+
+ if ( err != EWOULDBLOCK && err != EAGAIN ) {
+ char ebuf[128];
+ /* log, close and send error */
+ Debug( LDAP_DEBUG_TRACE,
+ "ber_get_next on fd %d failed errno=%d (%s)\n",
+ conn->c_sd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ ber_free( conn->c_currentber, 1 );
+ conn->c_currentber = NULL;
+
+ return -2;
+ }
+ return 1;
+ }
+
+ ber = conn->c_currentber;
+ conn->c_currentber = NULL;
+
+ if ( (tag = ber_get_int( ber, &msgid )) != LDAP_TAG_MSGID ) {
+ /* log, close and send error */
+ Debug( LDAP_DEBUG_ANY, "ber_get_int returns 0x%lx\n", tag );
+ ber_free( ber, 1 );
+ return -1;
+ }
+
+ if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR ) {
+ /* log, close and send error */
+ Debug( LDAP_DEBUG_ANY, "ber_peek_tag returns 0x%lx\n", tag );
+ ber_free( ber, 1 );
+
+ return -1;
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if( conn->c_is_udp ) {
+ if( tag == LBER_OCTETSTRING ) {
+ if ( (tag = ber_get_stringa( ber, &cdn )) != LBER_ERROR )
+ tag = ber_peek_tag( ber, &len );
+ }
+ if( tag != LDAP_REQ_ABANDON && tag != LDAP_REQ_SEARCH ) {
+ Debug( LDAP_DEBUG_ANY, "invalid req for UDP 0x%lx\n", tag );
+ ber_free( ber, 1 );
+ return 0;
+ }
+ }
+#endif
+
+ if(tag == LDAP_REQ_BIND) {
+ /* immediately abandon all existing operations upon BIND */
+ connection_abandon( conn );
+ }
+
+ ctx = cri->ctx;
+ op = slap_op_alloc( ber, msgid, tag, conn->c_n_ops_received++, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "op tag 0x%lx, time %ld\n", tag,
+ (long) op->o_time );
+
+ op->o_conn = conn;
+ /* clear state if the connection is being reused from inactive */
+ if ( conn->c_conn_state == SLAP_C_INACTIVE ) {
+ memset( &conn->c_pagedresults_state, 0,
+ sizeof( conn->c_pagedresults_state ) );
+ }
+
+ op->o_res_ber = NULL;
+
+#ifdef LDAP_CONNECTIONLESS
+ if (conn->c_is_udp) {
+ if ( cdn ) {
+ ber_str2bv( cdn, 0, 1, &op->o_dn );
+ op->o_protocol = LDAP_VERSION2;
+ }
+ op->o_res_ber = ber_alloc_t( LBER_USE_DER );
+ if (op->o_res_ber == NULL) return 1;
+
+ rc = ber_write( op->o_res_ber, (char *)&peeraddr,
+ sizeof(struct sockaddr), 0 );
+
+ if (rc != sizeof(struct sockaddr)) {
+ Debug( LDAP_DEBUG_ANY, "ber_write failed\n" );
+ return 1;
+ }
+
+ if (op->o_protocol == LDAP_VERSION2) {
+ rc = ber_printf(op->o_res_ber, "{is{" /*}}*/, op->o_msgid, "");
+ if (rc == -1) {
+ Debug( LDAP_DEBUG_ANY, "ber_write failed\n" );
+ return rc;
+ }
+ }
+ }
+#endif /* LDAP_CONNECTIONLESS */
+
+ rc = 0;
+
+ /* Don't process requests when the conn is in the middle of a
+ * Bind, or if it's closing. Also, don't let any single conn
+ * use up all the available threads, and don't execute if we're
+ * currently blocked on output. And don't execute if there are
+ * already pending ops, let them go first. Abandon operations
+ * get exceptions to some, but not all, cases.
+ */
+ switch( tag ){
+ default:
+ /* Abandon and Unbind are exempt from these checks */
+ if (conn->c_conn_state == SLAP_C_CLOSING) {
+ defer = "closing";
+ break;
+ } else if (conn->c_writewaiter) {
+ defer = "awaiting write";
+ break;
+ } else if (conn->c_n_ops_pending) {
+ defer = "pending operations";
+ break;
+ }
+ /* FALLTHRU */
+ case LDAP_REQ_ABANDON:
+ /* Unbind is exempt from these checks */
+ if (conn->c_n_ops_executing >= connection_pool_max/2) {
+ defer = "too many executing";
+ break;
+ } else if (conn->c_conn_state == SLAP_C_BINDING) {
+ defer = "binding";
+ break;
+ }
+ /* FALLTHRU */
+ case LDAP_REQ_UNBIND:
+ break;
+ }
+
+ if( defer ) {
+ int max = conn->c_dn.bv_len
+ ? slap_conn_max_pending_auth
+ : slap_conn_max_pending;
+
+ Debug( LDAP_DEBUG_ANY,
+ "connection_input: conn=%lu deferring operation: %s\n",
+ conn->c_connid, defer );
+ conn->c_n_ops_pending++;
+ LDAP_STAILQ_INSERT_TAIL( &conn->c_pending_ops, op, o_next );
+ rc = ( conn->c_n_ops_pending > max ) ? -1 : 0;
+
+ } else {
+ conn->c_n_ops_executing++;
+
+ /*
+ * The first op will be processed in the same thread context,
+ * as long as there is only one op total.
+ * Subsequent ops will be submitted to the pool by
+ * calling connection_op_activate()
+ */
+ if ( cri->op == NULL ) {
+ /* the first incoming request */
+ connection_op_queue( op );
+ cri->op = op;
+ } else {
+ if ( !cri->nullop ) {
+ cri->nullop = 1;
+ rc = ldap_pvt_thread_pool_submit( &connection_pool,
+ connection_operation, (void *) cri->op );
+ }
+ connection_op_activate( op );
+ }
+ }
+
+ return rc;
+}
+
+static int
+connection_resched( Connection *conn )
+{
+ Operation *op;
+
+ if( conn->c_writewaiter )
+ return 0;
+
+ if( conn->c_conn_state == SLAP_C_CLOSING ) {
+ Debug( LDAP_DEBUG_CONNS, "connection_resched: "
+ "attempting closing conn=%lu sd=%d\n",
+ conn->c_connid, conn->c_sd );
+ connection_close( conn );
+ return 0;
+ }
+
+ if( conn->c_conn_state != SLAP_C_ACTIVE ) {
+ /* other states need different handling */
+ return 0;
+ }
+
+ while ((op = LDAP_STAILQ_FIRST( &conn->c_pending_ops )) != NULL) {
+ if ( conn->c_n_ops_executing > connection_pool_max/2 ) break;
+
+ LDAP_STAILQ_REMOVE_HEAD( &conn->c_pending_ops, o_next );
+ LDAP_STAILQ_NEXT(op, o_next) = NULL;
+
+ /* pending operations should not be marked for abandonment */
+ assert(!op->o_abandon);
+
+ conn->c_n_ops_pending--;
+ conn->c_n_ops_executing++;
+
+ connection_op_activate( op );
+
+ if ( conn->c_conn_state == SLAP_C_BINDING ) break;
+ }
+ return 0;
+}
+
+static void
+connection_init_log_prefix( Operation *op )
+{
+ if ( op->o_connid == (unsigned long)(-1) ) {
+ snprintf( op->o_log_prefix, sizeof( op->o_log_prefix ),
+ "conn=-1 op=%lu", op->o_opid );
+
+ } else {
+ snprintf( op->o_log_prefix, sizeof( op->o_log_prefix ),
+ "conn=%lu op=%lu", op->o_connid, op->o_opid );
+ }
+}
+
+static int connection_bind_cleanup_cb( Operation *op, SlapReply *rs )
+{
+ op->o_conn->c_sasl_bindop = NULL;
+
+ ch_free( op->o_callback );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int connection_bind_cb( Operation *op, SlapReply *rs )
+{
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ op->o_conn->c_sasl_bind_in_progress =
+ ( rs->sr_err == LDAP_SASL_BIND_IN_PROGRESS );
+
+ /* Moved here from bind.c due to ITS#4158 */
+ op->o_conn->c_sasl_bindop = NULL;
+ if ( op->orb_method == LDAP_AUTH_SASL ) {
+ if( rs->sr_err == LDAP_SUCCESS ) {
+ ber_dupbv(&op->o_conn->c_dn, &op->orb_edn);
+ if( !BER_BVISEMPTY( &op->orb_edn ) ) {
+ /* edn is always normalized already */
+ ber_dupbv( &op->o_conn->c_ndn, &op->o_conn->c_dn );
+ }
+ op->o_tmpfree( op->orb_edn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->orb_edn );
+ op->o_conn->c_authmech = op->o_conn->c_sasl_bind_mech;
+ BER_BVZERO( &op->o_conn->c_sasl_bind_mech );
+
+ op->o_conn->c_sasl_ssf = op->orb_ssf;
+ if( op->orb_ssf > op->o_conn->c_ssf ) {
+ op->o_conn->c_ssf = op->orb_ssf;
+ }
+
+ if( !BER_BVISEMPTY( &op->o_conn->c_dn ) ) {
+ ber_len_t max = sockbuf_max_incoming_auth;
+ ber_sockbuf_ctrl( op->o_conn->c_sb,
+ LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ /* log authorization identity */
+ Debug( LDAP_DEBUG_STATS,
+ "%s BIND dn=\"%s\" mech=%s bind_ssf=%d ssf=%d\n",
+ op->o_log_prefix,
+ BER_BVISNULL( &op->o_conn->c_dn ) ? "<empty>" : op->o_conn->c_dn.bv_val,
+ op->o_conn->c_authmech.bv_val,
+ op->orb_ssf, op->o_conn->c_ssf );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "do_bind: SASL/%s bind: dn=\"%s\" bind_ssf=%d\n",
+ op->o_conn->c_authmech.bv_val,
+ BER_BVISNULL( &op->o_conn->c_dn ) ? "<empty>" : op->o_conn->c_dn.bv_val,
+ op->orb_ssf );
+
+ } else if ( rs->sr_err != LDAP_SASL_BIND_IN_PROGRESS ) {
+ if ( !BER_BVISNULL( &op->o_conn->c_sasl_bind_mech ) ) {
+ free( op->o_conn->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &op->o_conn->c_sasl_bind_mech );
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ ch_free( op->o_callback );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static void connection_op_queue( Operation *op )
+{
+ ber_tag_t tag = op->o_tag;
+
+ if (tag == LDAP_REQ_BIND) {
+ slap_callback *sc = ch_calloc( 1, sizeof( slap_callback ));
+ sc->sc_response = connection_bind_cb;
+ sc->sc_cleanup = connection_bind_cleanup_cb;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ op->o_conn->c_conn_state = SLAP_C_BINDING;
+ }
+
+ if (!op->o_dn.bv_len) {
+ op->o_authz = op->o_conn->c_authz;
+ if ( BER_BVISNULL( &op->o_conn->c_sasl_authz_dn )) {
+ ber_dupbv( &op->o_dn, &op->o_conn->c_dn );
+ ber_dupbv( &op->o_ndn, &op->o_conn->c_ndn );
+ } else {
+ ber_dupbv( &op->o_dn, &op->o_conn->c_sasl_authz_dn );
+ ber_dupbv( &op->o_ndn, &op->o_conn->c_sasl_authz_dn );
+ }
+ }
+
+ op->o_authtype = op->o_conn->c_authtype;
+ ber_dupbv( &op->o_authmech, &op->o_conn->c_authmech );
+
+ if (!op->o_protocol) {
+ op->o_protocol = op->o_conn->c_protocol
+ ? op->o_conn->c_protocol : LDAP_VERSION3;
+ }
+
+ if (op->o_conn->c_conn_state == SLAP_C_INACTIVE &&
+ op->o_protocol > LDAP_VERSION2)
+ {
+ op->o_conn->c_conn_state = SLAP_C_ACTIVE;
+ }
+
+ op->o_connid = op->o_conn->c_connid;
+ connection_init_log_prefix( op );
+
+ LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op, o_next );
+}
+
+static int connection_op_activate( Operation *op )
+{
+ int rc;
+
+ connection_op_queue( op );
+
+ rc = ldap_pvt_thread_pool_submit( &connection_pool,
+ connection_operation, (void *) op );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_op_activate: submit failed (%d) for conn=%lu\n",
+ rc, op->o_connid );
+ /* should move op to pending list */
+ }
+
+ return rc;
+}
+
+int connection_write(ber_socket_t s)
+{
+ Connection *c;
+ Operation *op;
+ int wantwrite;
+
+ assert( connections != NULL );
+
+ c = connection_get( s );
+ if( c == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "connection_write(%ld): no connection!\n",
+ (long)s );
+ return -1;
+ }
+
+ slapd_clr_write( s, 0 );
+
+#ifdef HAVE_TLS
+ if ( c->c_is_tls && c->c_needs_tls_accept ) {
+ connection_return( c );
+ connection_read_activate( s );
+ return 0;
+ }
+#endif
+
+ c->c_n_write++;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "connection_write(%d): waking output for id=%lu\n",
+ s, c->c_connid );
+
+ wantwrite = ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL );
+ if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_READ, NULL )) {
+ /* don't wakeup twice */
+ slapd_set_read( s, !wantwrite );
+ }
+ if ( wantwrite ) {
+ slapd_set_write( s, 1 );
+ }
+
+ /* If there are ops pending because of a writewaiter,
+ * start one up.
+ */
+ while ((op = LDAP_STAILQ_FIRST( &c->c_pending_ops )) != NULL) {
+ if ( !c->c_writewaiter ) break;
+ if ( c->c_n_ops_executing > connection_pool_max/2 ) break;
+
+ LDAP_STAILQ_REMOVE_HEAD( &c->c_pending_ops, o_next );
+ LDAP_STAILQ_NEXT(op, o_next) = NULL;
+
+ /* pending operations should not be marked for abandonment */
+ assert(!op->o_abandon);
+
+ c->c_n_ops_pending--;
+ c->c_n_ops_executing++;
+
+ connection_op_activate( op );
+
+ break;
+ }
+
+ connection_return( c );
+ return 0;
+}
+
+void connection_write_resume( Connection *c )
+{
+ Operation *op;
+
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+ /* If there are ops pending because of a writewaiter,
+ * start one up.
+ */
+ while ((op = LDAP_STAILQ_FIRST( &c->c_pending_ops )) != NULL) {
+ if ( c->c_n_ops_executing > connection_pool_max/2 ) break;
+
+ LDAP_STAILQ_REMOVE_HEAD( &c->c_pending_ops, o_next );
+ LDAP_STAILQ_NEXT(op, o_next) = NULL;
+
+ /* pending operations should not be marked for abandonment */
+ assert(!op->o_abandon);
+
+ c->c_n_ops_pending--;
+ c->c_n_ops_executing++;
+
+ connection_op_activate( op );
+
+ break;
+ }
+
+ connection_return( c );
+}
+
+#ifdef LDAP_SLAPI
+typedef struct conn_fake_extblock {
+ void *eb_conn;
+ void *eb_op;
+} conn_fake_extblock;
+
+static void
+connection_fake_destroy(
+ void *key,
+ void *data )
+{
+ Connection conn = {0};
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ conn_fake_extblock *eb = data;
+
+ op.o_hdr = &ohdr;
+ op.o_hdr->oh_extensions = eb->eb_op;
+ conn.c_extensions = eb->eb_conn;
+ op.o_conn = &conn;
+ conn.c_connid = -1;
+ op.o_connid = -1;
+
+ ber_memfree_x( eb, NULL );
+ slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, &op );
+ slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION, &conn );
+}
+#endif
+
+void
+connection_fake_init(
+ Connection *conn,
+ OperationBuffer *opbuf,
+ void *ctx )
+{
+ connection_fake_init2( conn, opbuf, ctx, 1 );
+}
+
+void
+operation_fake_init(
+ Connection *conn,
+ Operation *op,
+ void *ctx,
+ int newmem )
+{
+ /* set memory context */
+ op->o_tmpmemctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx,
+ newmem );
+ op->o_tmpmfuncs = &slap_sl_mfuncs;
+ op->o_threadctx = ctx;
+ op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+
+ op->o_counters = &slap_counters;
+ op->o_conn = conn;
+ op->o_connid = op->o_conn->c_connid;
+ connection_init_log_prefix( op );
+}
+
+
+void
+connection_fake_init2(
+ Connection *conn,
+ OperationBuffer *opbuf,
+ void *ctx,
+ int newmem )
+{
+ Operation *op = (Operation *) opbuf;
+
+ conn->c_connid = -1;
+ conn->c_conn_idx = -1;
+ conn->c_send_ldap_result = slap_send_ldap_result;
+ conn->c_send_search_entry = slap_send_search_entry;
+ conn->c_send_search_reference = slap_send_search_reference;
+ conn->c_send_ldap_extended = slap_send_ldap_extended;
+ conn->c_send_ldap_intermediate = slap_send_ldap_intermediate;
+ conn->c_listener = (Listener *)&dummy_list;
+ conn->c_peer_domain = slap_empty_bv;
+ conn->c_peer_name = slap_empty_bv;
+
+ memset( opbuf, 0, sizeof( *opbuf ));
+ op->o_hdr = &opbuf->ob_hdr;
+ op->o_controls = opbuf->ob_controls;
+
+ operation_fake_init( conn, op, ctx, newmem );
+
+#ifdef LDAP_SLAPI
+ if ( slapi_plugins_used ) {
+ conn_fake_extblock *eb;
+ void *ebx = NULL;
+
+ /* Use thread keys to make sure these eventually get cleaned up */
+ if ( ldap_pvt_thread_pool_getkey( ctx, (void *)connection_fake_init,
+ &ebx, NULL )) {
+ eb = ch_malloc( sizeof( *eb ));
+ slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, conn );
+ slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op );
+ eb->eb_conn = conn->c_extensions;
+ eb->eb_op = op->o_hdr->oh_extensions;
+ ldap_pvt_thread_pool_setkey( ctx, (void *)connection_fake_init,
+ eb, connection_fake_destroy, NULL, NULL );
+ } else {
+ eb = ebx;
+ conn->c_extensions = eb->eb_conn;
+ op->o_hdr->oh_extensions = eb->eb_op;
+ }
+ }
+#endif /* LDAP_SLAPI */
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+}
+
+void
+connection_assign_nextid( Connection *conn )
+{
+ ldap_pvt_thread_mutex_lock( &conn_nextid_mutex );
+ conn->c_connid = conn_nextid++;
+ ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex );
+}
diff --git a/servers/slapd/controls.c b/servers/slapd/controls.c
new file mode 100644
index 0000000..beabe44
--- /dev/null
+++ b/servers/slapd/controls.c
@@ -0,0 +1,2229 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "ldif.h"
+#include "lutil.h"
+
+#include "../../libraries/liblber/lber-int.h"
+
+static SLAP_CTRL_PARSE_FN parseAssert;
+static SLAP_CTRL_PARSE_FN parseDomainScope;
+static SLAP_CTRL_PARSE_FN parseDontUseCopy;
+static SLAP_CTRL_PARSE_FN parseManageDSAit;
+static SLAP_CTRL_PARSE_FN parseNoOp;
+static SLAP_CTRL_PARSE_FN parsePagedResults;
+static SLAP_CTRL_PARSE_FN parsePermissiveModify;
+static SLAP_CTRL_PARSE_FN parsePreRead, parsePostRead;
+static SLAP_CTRL_PARSE_FN parseProxyAuthz;
+static SLAP_CTRL_PARSE_FN parseRelax;
+static SLAP_CTRL_PARSE_FN parseSearchOptions;
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+static SLAP_CTRL_PARSE_FN parseSortedResults;
+#endif
+static SLAP_CTRL_PARSE_FN parseSubentries;
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+static SLAP_CTRL_PARSE_FN parseTreeDelete;
+#endif
+static SLAP_CTRL_PARSE_FN parseValuesReturnFilter;
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+static SLAP_CTRL_PARSE_FN parseSessionTracking;
+#endif
+#ifdef SLAP_CONTROL_X_WHATFAILED
+static SLAP_CTRL_PARSE_FN parseWhatFailed;
+#endif
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+static SLAP_CTRL_PARSE_FN parseLazyCommit;
+#endif
+
+#undef sc_mask /* avoid conflict with Irix 6.5 <sys/signal.h> */
+
+const struct berval slap_pre_read_bv = BER_BVC(LDAP_CONTROL_PRE_READ);
+const struct berval slap_post_read_bv = BER_BVC(LDAP_CONTROL_POST_READ);
+
+struct slap_control_ids slap_cids;
+
+struct slap_control {
+ /* Control OID */
+ char *sc_oid;
+
+ /* The controlID for this control */
+ int sc_cid;
+
+ /* Operations supported by control */
+ slap_mask_t sc_mask;
+
+ /* Extended operations supported by control */
+ char **sc_extendedops; /* input */
+ BerVarray sc_extendedopsbv; /* run-time use */
+
+ /* Control parsing callback */
+ SLAP_CTRL_PARSE_FN *sc_parse;
+
+ LDAP_SLIST_ENTRY(slap_control) sc_next;
+};
+
+static LDAP_SLIST_HEAD(ControlsList, slap_control) controls_list
+ = LDAP_SLIST_HEAD_INITIALIZER(&controls_list);
+
+/*
+ * all known request control OIDs should be added to this list
+ */
+/*
+ * NOTE: initialize num_known_controls to 1 so that cid = 0 always
+ * addresses an undefined control; this allows to safely test for
+ * well known controls even if they are not registered, e.g. if
+ * they get moved to modules. An example is sc_LDAPsync, which
+ * is implemented in the syncprov overlay and thus, if configured
+ * as dynamic module, may not be registered. One side effect is that
+ * slap_known_controls[0] == NULL, so it should always be used
+ * starting from 1.
+ * FIXME: should we define the "undefined control" oid?
+ */
+char *slap_known_controls[SLAP_MAX_CIDS+1];
+static int num_known_controls = 1;
+
+static char *proxy_authz_extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ LDAP_EXOP_WHO_AM_I,
+ LDAP_EXOP_REFRESH,
+ NULL
+};
+
+static char *manageDSAit_extops[] = {
+ LDAP_EXOP_REFRESH,
+ NULL
+};
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+static char *session_tracking_extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ LDAP_EXOP_WHO_AM_I,
+ LDAP_EXOP_REFRESH,
+ NULL
+};
+#endif
+
+static struct slap_control control_defs[] = {
+ { LDAP_CONTROL_ASSERT,
+ (int)offsetof(struct slap_control_ids, sc_assert),
+ SLAP_CTRL_UPDATE|SLAP_CTRL_COMPARE|SLAP_CTRL_SEARCH,
+ NULL, NULL,
+ parseAssert, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_PRE_READ,
+ (int)offsetof(struct slap_control_ids, sc_preRead),
+ SLAP_CTRL_DELETE|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME,
+ NULL, NULL,
+ parsePreRead, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_POST_READ,
+ (int)offsetof(struct slap_control_ids, sc_postRead),
+ SLAP_CTRL_ADD|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME,
+ NULL, NULL,
+ parsePostRead, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_VALUESRETURNFILTER,
+ (int)offsetof(struct slap_control_ids, sc_valuesReturnFilter),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH,
+ NULL, NULL,
+ parseValuesReturnFilter, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_PAGEDRESULTS,
+ (int)offsetof(struct slap_control_ids, sc_pagedResults),
+ SLAP_CTRL_SEARCH,
+ NULL, NULL,
+ parsePagedResults, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+ { LDAP_CONTROL_SORTREQUEST,
+ (int)offsetof(struct slap_control_ids, sc_sortedResults),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseSortedResults, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#endif
+ { LDAP_CONTROL_X_DOMAIN_SCOPE,
+ (int)offsetof(struct slap_control_ids, sc_domainScope),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseDomainScope, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_DONTUSECOPY,
+ (int)offsetof(struct slap_control_ids, sc_dontUseCopy),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_INTROGATE,
+ NULL, NULL,
+ parseDontUseCopy, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_X_PERMISSIVE_MODIFY,
+ (int)offsetof(struct slap_control_ids, sc_permissiveModify),
+ SLAP_CTRL_MODIFY|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parsePermissiveModify, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ { LDAP_CONTROL_X_TREE_DELETE,
+ (int)offsetof(struct slap_control_ids, sc_treeDelete),
+ SLAP_CTRL_DELETE|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseTreeDelete, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#endif
+ { LDAP_CONTROL_X_SEARCH_OPTIONS,
+ (int)offsetof(struct slap_control_ids, sc_searchOptions),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_SEARCH|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseSearchOptions, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_SUBENTRIES,
+ (int)offsetof(struct slap_control_ids, sc_subentries),
+ SLAP_CTRL_SEARCH,
+ NULL, NULL,
+ parseSubentries, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_NOOP,
+ (int)offsetof(struct slap_control_ids, sc_noOp),
+ SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseNoOp, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_RELAX,
+ (int)offsetof(struct slap_control_ids, sc_relax),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_UPDATE|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseRelax, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_TXN_SPEC,
+ (int)offsetof(struct slap_control_ids, sc_txnSpec),
+ SLAP_CTRL_UPDATE|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ txn_spec_ctrl, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_MANAGEDSAIT,
+ (int)offsetof(struct slap_control_ids, sc_manageDSAit),
+ SLAP_CTRL_ACCESS,
+ manageDSAit_extops, NULL,
+ parseManageDSAit, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+ { LDAP_CONTROL_PROXY_AUTHZ,
+ (int)offsetof(struct slap_control_ids, sc_proxyAuthz),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS,
+ proxy_authz_extops, NULL,
+ parseProxyAuthz, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ { LDAP_CONTROL_X_SESSION_TRACKING,
+ (int)offsetof(struct slap_control_ids, sc_sessionTracking),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_BIND|SLAP_CTRL_HIDE,
+ session_tracking_extops, NULL,
+ parseSessionTracking, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#endif
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ { LDAP_CONTROL_X_WHATFAILED,
+ (int)offsetof(struct slap_control_ids, sc_whatFailed),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseWhatFailed, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#endif
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+ { LDAP_CONTROL_X_LAZY_COMMIT,
+ (int)offsetof(struct slap_control_ids, sc_lazyCommit),
+ SLAP_CTRL_GLOBAL|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE,
+ NULL, NULL,
+ parseLazyCommit, LDAP_SLIST_ENTRY_INITIALIZER(next) },
+#endif
+
+ { NULL, 0, 0, NULL, 0, NULL, LDAP_SLIST_ENTRY_INITIALIZER(next) }
+};
+
+static struct slap_control *
+find_ctrl( const char *oid );
+
+/*
+ * Register a supported control.
+ *
+ * This can be called by an OpenLDAP plugin or, indirectly, by a
+ * SLAPI plugin calling slapi_register_supported_control().
+ *
+ * NOTE: if flags == 1 the control is replaced if already registered;
+ * otherwise registering an already registered control is not allowed.
+ */
+int
+register_supported_control2(const char *controloid,
+ slap_mask_t controlmask,
+ char **controlexops,
+ SLAP_CTRL_PARSE_FN *controlparsefn,
+ unsigned flags,
+ int *controlcid)
+{
+ struct slap_control *sc = NULL;
+ int i;
+ BerVarray extendedopsbv = NULL;
+
+ if ( num_known_controls >= SLAP_MAX_CIDS ) {
+ Debug( LDAP_DEBUG_ANY, "Too many controls registered."
+ " Recompile slapd with SLAP_MAX_CIDS defined > %d\n",
+ num_known_controls );
+ return LDAP_OTHER;
+ }
+
+ if ( controloid == NULL ) {
+ return LDAP_PARAM_ERROR;
+ }
+
+ /* check if already registered */
+ for ( i = 0; slap_known_controls[ i ]; i++ ) {
+ if ( strcmp( controloid, slap_known_controls[ i ] ) == 0 ) {
+ if ( flags == 1 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "Control %s already registered; replacing.\n",
+ controloid );
+ /* (find and) replace existing handler */
+ sc = find_ctrl( controloid );
+ assert( sc != NULL );
+ break;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "Control %s already registered.\n",
+ controloid );
+ return LDAP_PARAM_ERROR;
+ }
+ }
+
+ /* turn compatible extended operations into bervals */
+ if ( controlexops != NULL ) {
+ int i;
+
+ for ( i = 0; controlexops[ i ]; i++ );
+
+ extendedopsbv = ber_memcalloc( i + 1, sizeof( struct berval ) );
+ if ( extendedopsbv == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = 0; controlexops[ i ]; i++ ) {
+ ber_str2bv( controlexops[ i ], 0, 1, &extendedopsbv[ i ] );
+ }
+ }
+
+ if ( sc == NULL ) {
+ sc = (struct slap_control *)SLAP_MALLOC( sizeof( *sc ) );
+ if ( sc == NULL ) {
+ ber_bvarray_free( extendedopsbv );
+ return LDAP_NO_MEMORY;
+ }
+
+ sc->sc_oid = ch_strdup( controloid );
+ sc->sc_cid = num_known_controls;
+
+ /* Update slap_known_controls, too. */
+ slap_known_controls[num_known_controls - 1] = sc->sc_oid;
+ slap_known_controls[num_known_controls++] = NULL;
+
+ LDAP_SLIST_NEXT( sc, sc_next ) = NULL;
+ LDAP_SLIST_INSERT_HEAD( &controls_list, sc, sc_next );
+
+ } else {
+ if ( sc->sc_extendedopsbv ) {
+ ber_bvarray_free( sc->sc_extendedopsbv );
+ sc->sc_extendedopsbv = NULL;
+ sc->sc_extendedops = NULL;
+ }
+ }
+
+ sc->sc_extendedopsbv = extendedopsbv;
+ sc->sc_mask = controlmask;
+ sc->sc_parse = controlparsefn;
+ if ( controlcid ) {
+ *controlcid = sc->sc_cid;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+#ifdef SLAP_CONFIG_DELETE
+int
+unregister_supported_control( const char *controloid )
+{
+ struct slap_control *sc;
+ int i;
+
+ if ( controloid == NULL || (sc = find_ctrl( controloid )) == NULL ){
+ return -1;
+ }
+
+ for ( i = 0; slap_known_controls[ i ]; i++ ) {
+ if ( strcmp( controloid, slap_known_controls[ i ] ) == 0 ) {
+ do {
+ slap_known_controls[ i ] = slap_known_controls[ i+1 ];
+ } while ( slap_known_controls[ i++ ] );
+ num_known_controls--;
+ break;
+ }
+ }
+
+ LDAP_SLIST_REMOVE(&controls_list, sc, slap_control, sc_next);
+ ch_free( sc->sc_oid );
+ if ( sc->sc_extendedopsbv != NULL ) {
+ ber_bvarray_free( sc->sc_extendedopsbv );
+ }
+ ch_free( sc );
+
+ return 0;
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+int
+register_control_exop( const char *controloid, char *exopoid )
+{
+ struct slap_control *sc = NULL;
+ BerVarray extendedopsbv;
+ char **extendedops;
+ int i;
+
+ if ( controloid == NULL || exopoid == NULL ) {
+ return LDAP_PARAM_ERROR;
+ }
+
+ for ( i = 0; slap_known_controls[ i ]; i++ ) {
+ if ( strcmp( controloid, slap_known_controls[ i ] ) == 0 ) {
+ sc = find_ctrl( controloid );
+ assert( sc != NULL );
+ break;
+ }
+ }
+
+ if ( !sc ) {
+ Debug( LDAP_DEBUG_ANY, "register_control_exop: "
+ "Control %s not registered.\n",
+ controloid );
+ return LDAP_PARAM_ERROR;
+ }
+
+ for ( i = 0; sc->sc_extendedopsbv && !BER_BVISNULL( &sc->sc_extendedopsbv[ i ] ); i++ ) {
+ if ( strcmp( exopoid, sc->sc_extendedopsbv[ i ].bv_val ) == 0 ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ extendedopsbv = ber_memrealloc( sc->sc_extendedopsbv, (i + 2) * sizeof( struct berval ) );
+ if ( extendedopsbv == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+ sc->sc_extendedopsbv = extendedopsbv;
+
+ ber_str2bv( exopoid, 0, 1, &extendedopsbv[ i ] );
+ BER_BVZERO( &extendedopsbv[ i+1 ] );
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * One-time initialization of internal controls.
+ */
+int
+slap_controls_init( void )
+{
+ int i, rc;
+
+ rc = LDAP_SUCCESS;
+
+ for ( i = 0; control_defs[i].sc_oid != NULL; i++ ) {
+ int *cid = (int *)(((char *)&slap_cids) + control_defs[i].sc_cid );
+ rc = register_supported_control( control_defs[i].sc_oid,
+ control_defs[i].sc_mask, control_defs[i].sc_extendedops,
+ control_defs[i].sc_parse, cid );
+ if ( rc != LDAP_SUCCESS ) break;
+ }
+
+ return rc;
+}
+
+/*
+ * Free memory associated with list of supported controls.
+ */
+void
+controls_destroy( void )
+{
+ struct slap_control *sc;
+
+ while ( !LDAP_SLIST_EMPTY(&controls_list) ) {
+ sc = LDAP_SLIST_FIRST(&controls_list);
+ LDAP_SLIST_REMOVE_HEAD(&controls_list, sc_next);
+
+ ch_free( sc->sc_oid );
+ if ( sc->sc_extendedopsbv != NULL ) {
+ ber_bvarray_free( sc->sc_extendedopsbv );
+ }
+ ch_free( sc );
+ }
+}
+
+/*
+ * Format the supportedControl attribute of the root DSE,
+ * detailing which controls are supported by the directory
+ * server.
+ */
+int
+controls_root_dse_info( Entry *e )
+{
+ AttributeDescription *ad_supportedControl
+ = slap_schema.si_ad_supportedControl;
+ struct berval vals[2];
+ struct slap_control *sc;
+
+ vals[1].bv_val = NULL;
+ vals[1].bv_len = 0;
+
+ LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) {
+ if( sc->sc_mask & SLAP_CTRL_HIDE ) continue;
+
+ vals[0].bv_val = sc->sc_oid;
+ vals[0].bv_len = strlen( sc->sc_oid );
+
+ if ( attr_merge( e, ad_supportedControl, vals, NULL ) ) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Return a list of OIDs and operation masks for supported
+ * controls. Used by SLAPI.
+ */
+int
+get_supported_controls(char ***ctrloidsp,
+ slap_mask_t **ctrlmasks)
+{
+ int n;
+ char **oids;
+ slap_mask_t *masks;
+ struct slap_control *sc;
+
+ n = 0;
+
+ LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) {
+ n++;
+ }
+
+ if ( n == 0 ) {
+ *ctrloidsp = NULL;
+ *ctrlmasks = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ oids = (char **)SLAP_MALLOC( (n + 1) * sizeof(char *) );
+ if ( oids == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+ masks = (slap_mask_t *)SLAP_MALLOC( (n + 1) * sizeof(slap_mask_t) );
+ if ( masks == NULL ) {
+ SLAP_FREE( oids );
+ return LDAP_NO_MEMORY;
+ }
+
+ n = 0;
+
+ LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) {
+ oids[n] = ch_strdup( sc->sc_oid );
+ masks[n] = sc->sc_mask;
+ n++;
+ }
+ oids[n] = NULL;
+ masks[n] = 0;
+
+ *ctrloidsp = oids;
+ *ctrlmasks = masks;
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Find a control given its OID.
+ */
+static struct slap_control *
+find_ctrl( const char *oid )
+{
+ struct slap_control *sc;
+
+ LDAP_SLIST_FOREACH( sc, &controls_list, sc_next ) {
+ if ( strcmp( oid, sc->sc_oid ) == 0 ) {
+ return sc;
+ }
+ }
+
+ return NULL;
+}
+
+int
+slap_find_control_id(
+ const char *oid,
+ int *cid )
+{
+ struct slap_control *ctrl = find_ctrl( oid );
+ if ( ctrl ) {
+ if ( cid ) *cid = ctrl->sc_cid;
+ return LDAP_SUCCESS;
+ }
+ return LDAP_CONTROL_NOT_FOUND;
+}
+
+int
+slap_global_control( Operation *op, const char *oid, int *cid )
+{
+ struct slap_control *ctrl = find_ctrl( oid );
+
+ if ( ctrl == NULL ) {
+ /* should not be reachable */
+ Debug( LDAP_DEBUG_ANY,
+ "slap_global_control: unrecognized control: %s\n",
+ oid );
+ return LDAP_CONTROL_NOT_FOUND;
+ }
+
+ if ( cid ) *cid = ctrl->sc_cid;
+
+ if ( ( ctrl->sc_mask & SLAP_CTRL_GLOBAL ) ||
+ ( ( op->o_tag & LDAP_REQ_SEARCH ) &&
+ ( ctrl->sc_mask & SLAP_CTRL_GLOBAL_SEARCH ) ) )
+ {
+ return LDAP_COMPARE_TRUE;
+ }
+
+#if 0
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_global_control: unavailable control: %s\n",
+ oid );
+#endif
+
+ return LDAP_COMPARE_FALSE;
+}
+
+void slap_free_ctrls(
+ Operation *op,
+ LDAPControl **ctrls )
+{
+ int i;
+
+ if( ctrls == op->o_ctrls ) {
+ if( op->o_assertion != NULL ) {
+ filter_free_x( op, op->o_assertion, 1 );
+ op->o_assertion = NULL;
+ }
+ if( op->o_vrFilter != NULL) {
+ vrFilter_free( op, op->o_vrFilter );
+ op->o_vrFilter = NULL;
+ }
+ if( op->o_preread_attrs != NULL ) {
+ op->o_tmpfree( op->o_preread_attrs, op->o_tmpmemctx );
+ op->o_preread_attrs = NULL;
+ }
+ if( op->o_postread_attrs != NULL ) {
+ op->o_tmpfree( op->o_postread_attrs, op->o_tmpmemctx );
+ op->o_postread_attrs = NULL;
+ }
+ if( op->o_pagedresults_state != NULL ) {
+ op->o_tmpfree( op->o_pagedresults_state, op->o_tmpmemctx );
+ op->o_pagedresults_state = NULL;
+ }
+ }
+
+ for (i=0; ctrls[i]; i++) {
+ op->o_tmpfree(ctrls[i], op->o_tmpmemctx );
+ }
+ op->o_tmpfree( ctrls, op->o_tmpmemctx );
+}
+
+int slap_add_ctrls(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrls )
+{
+ int i = 0, j;
+ LDAPControl **ctrlsp;
+
+ if ( rs->sr_ctrls ) {
+ for ( ; rs->sr_ctrls[ i ]; i++ ) ;
+ }
+
+ for ( j=0; ctrls[j]; j++ ) ;
+
+ ctrlsp = op->o_tmpalloc(( i+j+1 )*sizeof(LDAPControl *), op->o_tmpmemctx );
+ i = 0;
+ if ( rs->sr_ctrls ) {
+ for ( ; rs->sr_ctrls[i]; i++ )
+ ctrlsp[i] = rs->sr_ctrls[i];
+ }
+ for ( j=0; ctrls[j]; j++)
+ ctrlsp[i++] = ctrls[j];
+ ctrlsp[i] = NULL;
+
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED )
+ op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );
+ rs->sr_ctrls = ctrlsp;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+ return i;
+}
+
+int slap_parse_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *control,
+ const char **text )
+{
+ struct slap_control *sc;
+ int rc = LDAP_SUCCESS;
+
+ sc = find_ctrl( control->ldctl_oid );
+ if( sc != NULL ) {
+ /* recognized control */
+ slap_mask_t tagmask;
+ switch( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ tagmask = SLAP_CTRL_ADD;
+ break;
+ case LDAP_REQ_BIND:
+ tagmask = SLAP_CTRL_BIND;
+ break;
+ case LDAP_REQ_COMPARE:
+ tagmask = SLAP_CTRL_COMPARE;
+ break;
+ case LDAP_REQ_DELETE:
+ tagmask = SLAP_CTRL_DELETE;
+ break;
+ case LDAP_REQ_MODIFY:
+ tagmask = SLAP_CTRL_MODIFY;
+ break;
+ case LDAP_REQ_RENAME:
+ tagmask = SLAP_CTRL_RENAME;
+ break;
+ case LDAP_REQ_SEARCH:
+ tagmask = SLAP_CTRL_SEARCH;
+ break;
+ case LDAP_REQ_UNBIND:
+ tagmask = SLAP_CTRL_UNBIND;
+ break;
+ case LDAP_REQ_ABANDON:
+ tagmask = SLAP_CTRL_ABANDON;
+ break;
+ case LDAP_REQ_EXTENDED:
+ tagmask=~0L;
+ assert( op->ore_reqoid.bv_val != NULL );
+ if( sc->sc_extendedopsbv != NULL ) {
+ int i;
+ for( i=0; !BER_BVISNULL( &sc->sc_extendedopsbv[i] ); i++ ) {
+ if( bvmatch( &op->ore_reqoid,
+ &sc->sc_extendedopsbv[i] ) )
+ {
+ tagmask=0L;
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ *text = "controls internal error";
+ return LDAP_OTHER;
+ }
+
+ if (( sc->sc_mask & tagmask ) == tagmask ) {
+ /* available extension */
+ if ( sc->sc_parse ) {
+ rc = sc->sc_parse( op, rs, control );
+ assert( rc != LDAP_UNAVAILABLE_CRITICAL_EXTENSION );
+
+ } else if ( control->ldctl_iscritical ) {
+ *text = "not yet implemented";
+ rc = LDAP_OTHER;
+ }
+
+
+ } else if ( control->ldctl_iscritical ) {
+ /* unavailable CRITICAL control */
+ *text = "critical extension is unavailable";
+ rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ }
+
+ } else if ( control->ldctl_iscritical ) {
+ /* unrecognized CRITICAL control */
+ *text = "critical extension is not recognized";
+ rc = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ }
+
+ return rc;
+}
+
+int
+get_ctrls(
+ Operation *op,
+ SlapReply *rs,
+ int sendres )
+{
+ return get_ctrls2( op, rs, sendres, LDAP_TAG_CONTROLS );
+}
+
+int
+get_ctrls2(
+ Operation *op,
+ SlapReply *rs,
+ int sendres,
+ ber_tag_t ctag )
+{
+ int nctrls = 0;
+ ber_tag_t tag;
+ ber_len_t len;
+ char *opaque;
+ BerElement *ber = op->o_ber;
+ struct berval bv;
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ /* NOTE: right now, slapd checks the validity of each control
+ * while parsing. As a consequence, it can only detect one
+ * cause of failure at a time. This results in returning
+ * exactly one OID with the whatFailed control, or no control
+ * at all.
+ */
+ char *failed_oid = NULL;
+#endif
+
+ len = ber_pvt_ber_remaining(ber);
+
+ if( len == 0) {
+ /* no controls */
+ rs->sr_err = LDAP_SUCCESS;
+ return rs->sr_err;
+ }
+
+ if(( tag = ber_peek_tag( ber, &len )) != ctag ) {
+ if( tag == LBER_ERROR ) {
+ rs->sr_err = SLAPD_DISCONNECT;
+ rs->sr_text = "unexpected data in PDU";
+ }
+
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> get_ctrls\n" );
+
+ if( op->o_protocol < LDAP_VERSION3 ) {
+ rs->sr_err = SLAPD_DISCONNECT;
+ rs->sr_text = "controls require LDAPv3";
+ goto return_results;
+ }
+
+ /* one for first control, one for termination */
+ op->o_ctrls = op->o_tmpalloc( 2 * sizeof(LDAPControl *), op->o_tmpmemctx );
+
+#if 0
+ if( op->ctrls == NULL ) {
+ rs->sr_err = LDAP_NO_MEMORY;
+ rs->sr_text = "no memory";
+ goto return_results;
+ }
+#endif
+
+ op->o_ctrls[nctrls] = NULL;
+
+ /* step through each element */
+ for( tag = ber_first_element( ber, &len, &opaque );
+ tag != LBER_ERROR;
+ tag = ber_next_element( ber, &len, opaque ) )
+ {
+ LDAPControl *c;
+ LDAPControl **tctrls;
+
+ c = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx );
+ memset(c, 0, sizeof(LDAPControl));
+
+ /* allocate pointer space for current controls (nctrls)
+ * + this control + extra NULL
+ */
+ tctrls = op->o_tmprealloc( op->o_ctrls,
+ (nctrls+2) * sizeof(LDAPControl *), op->o_tmpmemctx );
+
+#if 0
+ if( tctrls == NULL ) {
+ ch_free( c );
+ ldap_controls_free(op->o_ctrls);
+ op->o_ctrls = NULL;
+
+ rs->sr_err = LDAP_NO_MEMORY;
+ rs->sr_text = "no memory";
+ goto return_results;
+ }
+#endif
+ op->o_ctrls = tctrls;
+
+ op->o_ctrls[nctrls++] = c;
+ op->o_ctrls[nctrls] = NULL;
+
+ tag = ber_scanf( ber, "{m" /*}*/, &bv );
+ c->ldctl_oid = bv.bv_val;
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: get oid failed.\n" );
+
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ rs->sr_err = SLAPD_DISCONNECT;
+ rs->sr_text = "decoding controls error";
+ goto return_results;
+
+ } else if( c->ldctl_oid == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "get_ctrls: conn %lu got empty OID.\n",
+ op->o_connid );
+
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "OID field is empty";
+ goto return_results;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if( tag == LBER_BOOLEAN ) {
+ ber_int_t crit;
+ tag = ber_scanf( ber, "b", &crit );
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: get crit failed.\n" );
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ rs->sr_err = SLAPD_DISCONNECT;
+ rs->sr_text = "decoding controls error";
+ goto return_results;
+ }
+
+ c->ldctl_iscritical = (crit != 0);
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if( tag == LBER_OCTETSTRING ) {
+ tag = ber_scanf( ber, "m", &c->ldctl_value );
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "=> get_ctrls: conn %lu: "
+ "%s (%scritical): get value failed.\n",
+ op->o_connid, c->ldctl_oid,
+ c->ldctl_iscritical ? "" : "non" );
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ rs->sr_err = SLAPD_DISCONNECT;
+ rs->sr_text = "decoding controls error";
+ goto return_results;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> get_ctrls: oid=\"%s\" (%scritical)\n",
+ c->ldctl_oid, c->ldctl_iscritical ? "" : "non" );
+
+ rs->sr_err = slap_parse_ctrl( op, rs, c, &rs->sr_text );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ failed_oid = c->ldctl_oid;
+#endif
+ goto return_results;
+ }
+ }
+
+return_results:
+ Debug( LDAP_DEBUG_TRACE,
+ "<= get_ctrls: n=%d rc=%d err=\"%s\"\n",
+ nctrls, rs->sr_err, rs->sr_text ? rs->sr_text : "");
+
+ if( sendres && rs->sr_err != LDAP_SUCCESS ) {
+ if( rs->sr_err == SLAPD_DISCONNECT ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ send_ldap_disconnect( op, rs );
+ rs->sr_err = SLAPD_DISCONNECT;
+ } else {
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ /* might have not been parsed yet? */
+ if ( failed_oid != NULL ) {
+ if ( !get_whatFailed( op ) ) {
+ /* look it up */
+
+ /* step through each remaining element */
+ for ( ; tag != LBER_ERROR; tag = ber_next_element( ber, &len, opaque ) )
+ {
+ LDAPControl c = { 0 };
+
+ tag = ber_scanf( ber, "{m" /*}*/, &bv );
+ c.ldctl_oid = bv.bv_val;
+
+ if ( tag == LBER_ERROR ) {
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ break;
+
+ } else if ( c.ldctl_oid == NULL ) {
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ break;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_BOOLEAN ) {
+ ber_int_t crit;
+ tag = ber_scanf( ber, "b", &crit );
+ if( tag == LBER_ERROR ) {
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ break;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LBER_OCTETSTRING ) {
+ tag = ber_scanf( ber, "m", &c.ldctl_value );
+
+ if( tag == LBER_ERROR ) {
+ slap_free_ctrls( op, op->o_ctrls );
+ op->o_ctrls = NULL;
+ break;
+ }
+ }
+
+ if ( strcmp( c.ldctl_oid, LDAP_CONTROL_X_WHATFAILED ) == 0 ) {
+ const char *text;
+ slap_parse_ctrl( op, rs, &c, &text );
+ break;
+ }
+ }
+ }
+
+ if ( get_whatFailed( op ) ) {
+ char *oids[ 2 ];
+ oids[ 0 ] = failed_oid;
+ oids[ 1 ] = NULL;
+ slap_ctrl_whatFailed_add( op, rs, oids );
+ }
+ }
+#endif
+
+ send_ldap_result( op, rs );
+ }
+ }
+
+ return rs->sr_err;
+}
+
+int
+slap_remove_control(
+ Operation *op,
+ SlapReply *rs,
+ int ctrl,
+ BI_chk_controls fnc )
+{
+ int i, j;
+
+ switch ( op->o_ctrlflag[ ctrl ] ) {
+ case SLAP_CONTROL_NONCRITICAL:
+ for ( i = 0, j = -1; op->o_ctrls[ i ] != NULL; i++ ) {
+ if ( strcmp( op->o_ctrls[ i ]->ldctl_oid,
+ slap_known_controls[ ctrl - 1 ] ) == 0 )
+ {
+ j = i;
+ }
+ }
+
+ if ( j == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ break;
+ }
+
+ if ( fnc ) {
+ (void)fnc( op, rs );
+ }
+
+ op->o_tmpfree( op->o_ctrls[ j ], op->o_tmpmemctx );
+
+ if ( i > 1 ) {
+ AC_MEMCPY( &op->o_ctrls[ j ], &op->o_ctrls[ j + 1 ],
+ ( i - j ) * sizeof( LDAPControl * ) );
+
+ } else {
+ op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+ op->o_ctrls = NULL;
+ }
+
+ op->o_ctrlflag[ ctrl ] = SLAP_CONTROL_IGNORED;
+
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "non-critical control \"%s\" not supported; stripped.\n",
+ op->o_log_prefix, slap_known_controls[ ctrl ] );
+ /* fall thru */
+
+ case SLAP_CONTROL_IGNORED:
+ case SLAP_CONTROL_NONE:
+ rs->sr_err = SLAP_CB_CONTINUE;
+ break;
+
+ case SLAP_CONTROL_CRITICAL:
+ rs->sr_err = LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ if ( fnc ) {
+ (void)fnc( op, rs );
+ }
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "critical control \"%s\" not supported.\n",
+ op->o_log_prefix, slap_known_controls[ ctrl ] );
+ break;
+
+ default:
+ /* handle all cases! */
+ assert( 0 );
+ }
+
+ return rs->sr_err;
+}
+
+static int parseDontUseCopy (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_dontUseCopy != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "dontUseCopy control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "dontUseCopy control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( ( global_disallows & SLAP_DISALLOW_DONTUSECOPY_N_CRIT )
+ && !ctrl->ldctl_iscritical )
+ {
+ rs->sr_text = "dontUseCopy criticality of FALSE not allowed";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_dontUseCopy = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int parseRelax (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_relax != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "relax control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "relax control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_relax = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int parseManageDSAit (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_managedsait != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "manageDSAit control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "manageDSAit control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_managedsait = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int parseProxyAuthz (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ int rc;
+ struct berval dn = BER_BVNULL;
+
+ if ( op->o_proxy_authz != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "proxy authorization control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "proxy authorization control value absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( ( global_disallows & SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT )
+ && !ctrl->ldctl_iscritical )
+ {
+ rs->sr_text = "proxied authorization criticality of FALSE not allowed";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !( global_allows & SLAP_ALLOW_PROXY_AUTHZ_ANON )
+ && BER_BVISEMPTY( &op->o_ndn ) )
+ {
+ rs->sr_text = "anonymous proxied authorization not allowed";
+ return LDAP_PROXIED_AUTHORIZATION_DENIED;
+ }
+
+ op->o_proxy_authz = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "parseProxyAuthz: conn %lu authzid=\"%s\"\n",
+ op->o_connid,
+ ctrl->ldctl_value.bv_len ? ctrl->ldctl_value.bv_val : "anonymous" );
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ Debug( LDAP_DEBUG_TRACE,
+ "parseProxyAuthz: conn=%lu anonymous\n",
+ op->o_connid );
+
+ /* anonymous */
+ if ( !BER_BVISNULL( &op->o_ndn ) ) {
+ op->o_ndn.bv_val[ 0 ] = '\0';
+ }
+ op->o_ndn.bv_len = 0;
+
+ if ( !BER_BVISNULL( &op->o_dn ) ) {
+ op->o_dn.bv_val[ 0 ] = '\0';
+ }
+ op->o_dn.bv_len = 0;
+
+ return LDAP_SUCCESS;
+ }
+
+ rc = slap_sasl_getdn( op->o_conn, op, &ctrl->ldctl_value,
+ NULL, &dn, SLAP_GETDN_AUTHZID );
+
+ /* FIXME: empty DN in proxyAuthz control should be legal... */
+ if( rc != LDAP_SUCCESS /* || !dn.bv_len */ ) {
+ if ( dn.bv_val ) {
+ ch_free( dn.bv_val );
+ }
+ rs->sr_text = "authzId mapping failed";
+ return LDAP_PROXIED_AUTHORIZATION_DENIED;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "parseProxyAuthz: conn=%lu \"%s\"\n",
+ op->o_connid,
+ dn.bv_len ? dn.bv_val : "(NULL)" );
+
+ rc = slap_sasl_authorized( op, &op->o_ndn, &dn );
+
+ if ( rc ) {
+ ch_free( dn.bv_val );
+ rs->sr_text = "not authorized to assume identity";
+ return LDAP_PROXIED_AUTHORIZATION_DENIED;
+ }
+
+ ch_free( op->o_ndn.bv_val );
+
+ /*
+ * NOTE: since slap_sasl_getdn() returns a normalized dn,
+ * from now on op->o_dn is normalized
+ */
+ op->o_ndn = dn;
+ ber_bvreplace( &op->o_dn, &dn );
+
+ Debug( LDAP_DEBUG_STATS, "%s PROXYAUTHZ dn=\"%s\"\n",
+ op->o_log_prefix, dn.bv_val );
+
+ return LDAP_SUCCESS;
+}
+
+static int parseNoOp (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_noop != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "noop control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "noop control value not empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_noop = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int parsePagedResults (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval cookie;
+ PagedResultsState *ps;
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_int_t size;
+
+ if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "paged results control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "paged results control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "paged results control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* Parse the control value
+ * realSearchControlValue ::= SEQUENCE {
+ * size INTEGER (0..maxInt),
+ * -- requested page size from client
+ * -- result set size estimate from server
+ * cookie OCTET STRING
+ * }
+ */
+ ber_init2( ber, &ctrl->ldctl_value, LBER_USE_DER );
+
+ tag = ber_scanf( ber, "{im}", &size, &cookie );
+
+ if ( tag == LBER_ERROR ) {
+ rs->sr_text = "paged results control could not be decoded";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ if ( size < 0 ) {
+ rs->sr_text = "paged results control size invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ ps = op->o_tmpalloc( sizeof(PagedResultsState), op->o_tmpmemctx );
+ *ps = op->o_conn->c_pagedresults_state;
+ ps->ps_size = size;
+ ps->ps_cookieval = cookie;
+ op->o_pagedresults_state = ps;
+ if ( !cookie.bv_len ) {
+ ps->ps_count = 0;
+ ps->ps_cookie = 0;
+ /* taint ps_cookie, to detect whether it's set */
+ op->o_conn->c_pagedresults_state.ps_cookie = NOID;
+ }
+
+ /* NOTE: according to RFC 2696 3.:
+
+ If the page size is greater than or equal to the sizeLimit value, the
+ server should ignore the control as the request can be satisfied in a
+ single page.
+
+ * NOTE: this assumes that the op->ors_slimit be set
+ * before the controls are parsed.
+ */
+
+ if ( op->ors_slimit > 0 && size >= op->ors_slimit ) {
+ op->o_pagedresults = SLAP_CONTROL_IGNORED;
+
+ } else if ( ctrl->ldctl_iscritical ) {
+ op->o_pagedresults = SLAP_CONTROL_CRITICAL;
+
+ } else {
+ op->o_pagedresults = SLAP_CONTROL_NONCRITICAL;
+ }
+
+done:;
+ return rc;
+}
+
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+static int parseSortedResults (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( op->o_sortedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "sorted results control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* blow off parsing the value */
+
+ op->o_sortedresults = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return rc;
+}
+#endif
+
+static int parseAssert (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElement *ber;
+ struct berval fstr = BER_BVNULL;
+
+ if ( op->o_assert != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "assert control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "assert control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "assert control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber = ber_init( &(ctrl->ldctl_value) );
+ if (ber == NULL) {
+ rs->sr_text = "assert control: internal error";
+ return LDAP_OTHER;
+ }
+
+ rs->sr_err = get_filter( op, ber, (Filter **)&(op->o_assertion),
+ &rs->sr_text);
+ (void) ber_free( ber, 1 );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ if( rs->sr_err == SLAPD_DISCONNECT ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ send_ldap_disconnect( op, rs );
+ rs->sr_err = SLAPD_DISCONNECT;
+ } else {
+ send_ldap_result( op, rs );
+ }
+ if( op->o_assertion != NULL ) {
+ filter_free_x( op, op->o_assertion, 1 );
+ op->o_assertion = NULL;
+ }
+ return rs->sr_err;
+ }
+
+#ifdef LDAP_DEBUG
+ filter2bv_x( op, op->o_assertion, &fstr );
+
+ Debug( LDAP_DEBUG_ARGS, "parseAssert: conn %ld assert: %s\n",
+ op->o_connid, fstr.bv_len ? fstr.bv_val : "empty" );
+ op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx );
+#endif
+
+ op->o_assert = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ rs->sr_err = LDAP_SUCCESS;
+ return LDAP_SUCCESS;
+}
+
+#define READMSG(post, msg) \
+ ( post ? "postread control: " msg : "preread control: " msg )
+
+static int
+parseReadAttrs(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl,
+ int post )
+{
+ ber_len_t siz, off, i;
+ BerElement *ber;
+ AttributeName *an = NULL;
+
+ if ( ( post && op->o_postread != SLAP_CONTROL_NONE ) ||
+ ( !post && op->o_preread != SLAP_CONTROL_NONE ) )
+ {
+ rs->sr_text = READMSG( post, "specified multiple times" );
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = READMSG( post, "value is absent" );
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = READMSG( post, "value is empty" );
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->o_txnSpec ) { /* temporary limitation */
+ rs->sr_text = READMSG( post, "cannot perform in transaction" );
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ ber = ber_init( &ctrl->ldctl_value );
+ if ( ber == NULL ) {
+ rs->sr_text = READMSG( post, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ siz = sizeof( AttributeName );
+ off = offsetof( AttributeName, an_name );
+ if ( ber_scanf( ber, "{M}", &an, &siz, off ) == LBER_ERROR ) {
+ rs->sr_text = READMSG( post, "decoding error" );
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ for ( i = 0; i < siz; i++ ) {
+ const char *dummy = NULL;
+ int rc;
+
+ an[i].an_desc = NULL;
+ an[i].an_oc = NULL;
+ an[i].an_flags = 0;
+ rc = slap_bv2ad( &an[i].an_name, &an[i].an_desc, &dummy );
+ if ( rc == LDAP_SUCCESS ) {
+ an[i].an_name = an[i].an_desc->ad_cname;
+
+ } else {
+ int j;
+ static struct berval special_attrs[] = {
+ BER_BVC( LDAP_NO_ATTRS ),
+ BER_BVC( LDAP_ALL_USER_ATTRIBUTES ),
+ BER_BVC( LDAP_ALL_OPERATIONAL_ATTRIBUTES ),
+ BER_BVNULL
+ };
+
+ /* deal with special attribute types */
+ for ( j = 0; !BER_BVISNULL( &special_attrs[ j ] ); j++ ) {
+ if ( bvmatch( &an[i].an_name, &special_attrs[ j ] ) ) {
+ an[i].an_name = special_attrs[ j ];
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &special_attrs[ j ] ) && ctrl->ldctl_iscritical ) {
+ rs->sr_err = rc;
+ rs->sr_text = dummy ? dummy
+ : READMSG( post, "unknown attributeType" );
+ goto done;
+ }
+ }
+ }
+
+ if ( post ) {
+ op->o_postread_attrs = an;
+ op->o_postread = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+ } else {
+ op->o_preread_attrs = an;
+ op->o_preread = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+ }
+
+done:
+ (void) ber_free( ber, 1 );
+ return rs->sr_err;
+}
+
+static int parsePreRead (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ return parseReadAttrs( op, rs, ctrl, 0 );
+}
+
+static int parsePostRead (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ return parseReadAttrs( op, rs, ctrl, 1 );
+}
+
+static int parseValuesReturnFilter (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElement *ber;
+ struct berval fstr = BER_BVNULL;
+
+ if ( op->o_valuesreturnfilter != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "valuesReturnFilter control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "valuesReturnFilter control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "valuesReturnFilter control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber = ber_init( &(ctrl->ldctl_value) );
+ if (ber == NULL) {
+ rs->sr_text = "internal error";
+ return LDAP_OTHER;
+ }
+
+ rs->sr_err = get_vrFilter( op, ber,
+ (ValuesReturnFilter **)&(op->o_vrFilter), &rs->sr_text);
+
+ (void) ber_free( ber, 1 );
+
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ if( rs->sr_err == SLAPD_DISCONNECT ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ send_ldap_disconnect( op, rs );
+ rs->sr_err = SLAPD_DISCONNECT;
+ } else {
+ send_ldap_result( op, rs );
+ }
+ if( op->o_vrFilter != NULL) {
+ vrFilter_free( op, op->o_vrFilter );
+ op->o_vrFilter = NULL;
+ }
+ }
+#ifdef LDAP_DEBUG
+ else {
+ vrFilter2bv( op, op->o_vrFilter, &fstr );
+ }
+
+ Debug( LDAP_DEBUG_ARGS, " vrFilter: %s\n",
+ fstr.bv_len ? fstr.bv_val : "empty" );
+ op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx );
+#endif
+
+ op->o_valuesreturnfilter = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ rs->sr_err = LDAP_SUCCESS;
+ return LDAP_SUCCESS;
+}
+
+static int parseSubentries (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_subentries != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "subentries control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* FIXME: should use BER library */
+ if( ( ctrl->ldctl_value.bv_len != 3 )
+ || ( ctrl->ldctl_value.bv_val[0] != 0x01 )
+ || ( ctrl->ldctl_value.bv_val[1] != 0x01 ))
+ {
+ rs->sr_text = "subentries control value encoding is bogus";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_subentries = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ if (ctrl->ldctl_value.bv_val[2]) {
+ set_subentries_visibility( op );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int parsePermissiveModify (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_permissive_modify != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "permissiveModify control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "permissiveModify control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_permissive_modify = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int parseDomainScope (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_domain_scope != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "domainScope control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* this should be checking BVISNULL, but M$ clients are broken
+ * and include the value even though the M$ spec says it must be
+ * omitted. ITS#9100.
+ */
+ if ( !BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "domainScope control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_domain_scope = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+static int parseTreeDelete (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_tree_delete != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "treeDelete control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "treeDelete control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_tree_delete = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+#endif
+
+static int parseSearchOptions (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElement *ber;
+ ber_int_t search_flags;
+ ber_tag_t tag;
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "searchOptions control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "searchOptions control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber = ber_init( &ctrl->ldctl_value );
+ if( ber == NULL ) {
+ rs->sr_text = "internal error";
+ return LDAP_OTHER;
+ }
+
+ tag = ber_scanf( ber, "{i}", &search_flags );
+ (void) ber_free( ber, 1 );
+
+ if ( tag == LBER_ERROR ) {
+ rs->sr_text = "searchOptions control decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( search_flags & ~(LDAP_SEARCH_FLAG_DOMAIN_SCOPE) ) {
+ /* Search flags not recognised so far,
+ * including:
+ * LDAP_SEARCH_FLAG_PHANTOM_ROOT
+ */
+ if ( ctrl->ldctl_iscritical ) {
+ rs->sr_text = "searchOptions contained unrecognized flag";
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ /* Ignore */
+ Debug( LDAP_DEBUG_TRACE,
+ "searchOptions: conn=%lu unrecognized flag(s) 0x%x (non-critical)\n",
+ op->o_connid, (unsigned)search_flags );
+
+ return LDAP_SUCCESS;
+ }
+
+ if ( search_flags & LDAP_SEARCH_FLAG_DOMAIN_SCOPE ) {
+ if ( op->o_domain_scope != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "searchOptions control specified multiple times "
+ "or with domainScope control";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_domain_scope = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+struct berval session_tracking_formats[] = {
+ BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_SESSION_ID ),
+ BER_BVC( "RADIUS-Acct-Session-Id" ),
+ BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_RADIUS_ACCT_MULTI_SESSION_ID ),
+ BER_BVC( "RADIUS-Acct-Multi-Session-Id" ),
+ BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_USERNAME ),
+ BER_BVC( "USERNAME" ),
+
+ BER_BVNULL
+};
+
+static int parseSessionTracking(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ int i, rc;
+
+ struct berval sessionSourceIp = BER_BVNULL,
+ sessionSourceName = BER_BVNULL,
+ formatOID = BER_BVNULL,
+ sessionTrackingIdentifier = BER_BVNULL;
+
+ size_t st_len, st_pos;
+
+ if ( ctrl->ldctl_iscritical ) {
+ rs->sr_text = "sessionTracking criticality is TRUE";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sessionTracking control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sessionTracking control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* TODO: add the capability to determine if a client is allowed
+ * to use this control, based on identity, ip and so */
+
+ ber = ber_init( &ctrl->ldctl_value );
+ if ( ber == NULL ) {
+ rs->sr_text = "internal error";
+ return LDAP_OTHER;
+ }
+
+ tag = ber_skip_tag( ber, &len );
+ if ( tag != LBER_SEQUENCE ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+
+ /* sessionSourceIp */
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+
+ if ( len == 0 ) {
+ tag = ber_skip_tag( ber, &len );
+
+ } else if ( len > 128 ) {
+ rs->sr_text = "sessionTracking.sessionSourceIp too long";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto error;
+
+ } else {
+ tag = ber_scanf( ber, "m", &sessionSourceIp );
+ }
+
+ if ( ldif_is_not_printable( sessionSourceIp.bv_val, sessionSourceIp.bv_len ) ) {
+ BER_BVZERO( &sessionSourceIp );
+ }
+
+ /* sessionSourceName */
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+
+ if ( len == 0 ) {
+ tag = ber_skip_tag( ber, &len );
+
+ } else if ( len > 65536 ) {
+ rs->sr_text = "sessionTracking.sessionSourceName too long";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto error;
+
+ } else {
+ tag = ber_scanf( ber, "m", &sessionSourceName );
+ }
+
+ if ( ldif_is_not_printable( sessionSourceName.bv_val, sessionSourceName.bv_len ) ) {
+ BER_BVZERO( &sessionSourceName );
+ }
+
+ /* formatOID */
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+
+ if ( len == 0 ) {
+ rs->sr_text = "sessionTracking.formatOID empty";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto error;
+
+ } else if ( len > 1024 ) {
+ rs->sr_text = "sessionTracking.formatOID too long";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto error;
+
+ } else {
+ tag = ber_scanf( ber, "m", &formatOID );
+ }
+
+ rc = numericoidValidate( NULL, &formatOID );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = "sessionTracking.formatOID invalid";
+ goto error;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &session_tracking_formats[ i ] ); i += 2 )
+ {
+ if ( bvmatch( &formatOID, &session_tracking_formats[ i ] ) ) {
+ formatOID = session_tracking_formats[ i + 1 ];
+ break;
+ }
+ }
+
+ /* sessionTrackingIdentifier */
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+
+ if ( len == 0 ) {
+ tag = ber_skip_tag( ber, &len );
+
+ } else {
+ /* note: should not be more than 65536... */
+ tag = ber_scanf( ber, "m", &sessionTrackingIdentifier );
+ if ( ldif_is_not_printable( sessionTrackingIdentifier.bv_val, sessionTrackingIdentifier.bv_len ) ) {
+ /* we want the OID printed, at least */
+ BER_BVSTR( &sessionTrackingIdentifier, "" );
+ }
+ }
+
+ /* closure */
+ tag = ber_skip_tag( ber, &len );
+ if ( tag != LBER_DEFAULT || len != 0 ) {
+ tag = LBER_ERROR;
+ goto error;
+ }
+ tag = 0;
+
+ st_len = 0;
+ if ( !BER_BVISNULL( &sessionSourceIp ) ) {
+ st_len += STRLENOF( "IP=" ) + sessionSourceIp.bv_len;
+ }
+ if ( !BER_BVISNULL( &sessionSourceName ) ) {
+ if ( st_len ) st_len++;
+ st_len += STRLENOF( "NAME=" ) + sessionSourceName.bv_len;
+ }
+ if ( !BER_BVISNULL( &sessionTrackingIdentifier ) ) {
+ if ( st_len ) st_len++;
+ st_len += formatOID.bv_len + STRLENOF( "=" )
+ + sessionTrackingIdentifier.bv_len;
+ }
+
+ if ( st_len == 0 ) {
+ goto error;
+ }
+
+ st_len += STRLENOF( " []" );
+ st_pos = strlen( op->o_log_prefix );
+
+ if ( sizeof( op->o_log_prefix ) - st_pos > st_len ) {
+ char *ptr = &op->o_log_prefix[ st_pos ];
+
+ ptr = lutil_strcopy( ptr, " [" /*]*/ );
+
+ st_len = 0;
+ if ( !BER_BVISNULL( &sessionSourceIp ) ) {
+ ptr = lutil_strcopy( ptr, "IP=" );
+ ptr = lutil_strcopy( ptr, sessionSourceIp.bv_val );
+ st_len++;
+ }
+
+ if ( !BER_BVISNULL( &sessionSourceName ) ) {
+ if ( st_len ) *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, "NAME=" );
+ ptr = lutil_strcopy( ptr, sessionSourceName.bv_val );
+ st_len++;
+ }
+
+ if ( !BER_BVISNULL( &sessionTrackingIdentifier ) ) {
+ if ( st_len ) *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, formatOID.bv_val );
+ *ptr++ = '=';
+ ptr = lutil_strcopy( ptr, sessionTrackingIdentifier.bv_val );
+ }
+
+ *ptr++ = /*[*/ ']';
+ *ptr = '\0';
+ }
+
+error:;
+ (void)ber_free( ber, 1 );
+
+ if ( tag == LBER_ERROR ) {
+ rs->sr_text = "sessionTracking control decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+
+ return rs->sr_err;
+}
+
+int
+slap_ctrl_session_tracking_add(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ip,
+ struct berval *name,
+ struct berval *id,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ static struct berval oid = BER_BVC( LDAP_CONTROL_X_SESSION_TRACKING_USERNAME );
+
+ assert( ctrl != NULL );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ ber_printf( ber, "{OOOO}", ip, name, &oid, id );
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 0 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_X_SESSION_TRACKING;
+ ctrl->ldctl_iscritical = 0;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:;
+ return rs->sr_err;
+}
+
+int
+slap_ctrl_session_tracking_request_add( Operation *op, SlapReply *rs, LDAPControl *ctrl )
+{
+ static struct berval bv_unknown = BER_BVC( SLAP_STRING_UNKNOWN );
+ struct berval ip = BER_BVNULL,
+ name = BER_BVNULL,
+ id = BER_BVNULL;
+
+ if ( !BER_BVISNULL( &op->o_conn->c_peer_name ) &&
+ memcmp( op->o_conn->c_peer_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 )
+ {
+ char *ptr;
+
+ ip.bv_val = op->o_conn->c_peer_name.bv_val + STRLENOF( "IP=" );
+ ip.bv_len = op->o_conn->c_peer_name.bv_len - STRLENOF( "IP=" );
+
+ ptr = ber_bvchr( &ip, ':' );
+ if ( ptr ) {
+ ip.bv_len = ptr - ip.bv_val;
+ }
+ }
+
+ if ( !BER_BVISNULL( &op->o_conn->c_peer_domain ) &&
+ !bvmatch( &op->o_conn->c_peer_domain, &bv_unknown ) )
+ {
+ name = op->o_conn->c_peer_domain;
+ }
+
+ if ( !BER_BVISNULL( &op->o_dn ) && !BER_BVISEMPTY( &op->o_dn ) ) {
+ id = op->o_dn;
+ }
+
+ return slap_ctrl_session_tracking_add( op, rs, &ip, &name, &id, ctrl );
+}
+#endif
+
+#ifdef SLAP_CONTROL_X_WHATFAILED
+static int parseWhatFailed(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_whatFailed != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "\"WHat Failed?\" control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "\"What Failed?\" control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_whatFailed = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+int
+slap_ctrl_whatFailed_add(
+ Operation *op,
+ SlapReply *rs,
+ char **oids )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ LDAPControl **ctrls = NULL;
+ struct berval ctrlval;
+ int i, rc = LDAP_SUCCESS;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+ ber_printf( ber, "[" /*]*/ );
+ for ( i = 0; oids[ i ] != NULL; i++ ) {
+ ber_printf( ber, "s", oids[ i ] );
+ }
+ ber_printf( ber, /*[*/ "]" );
+
+ if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ i = 0;
+ if ( rs->sr_ctrls != NULL ) {
+ for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) {
+ if ( strcmp( rs->sr_ctrls[ i ]->ldctl_oid, LDAP_CONTROL_X_WHATFAILED ) != 0 ) {
+ /* TODO: add */
+ assert( 0 );
+ }
+ }
+ }
+
+ ctrls = op->o_tmprealloc( rs->sr_ctrls,
+ sizeof(LDAPControl *)*( i + 2 )
+ + sizeof(LDAPControl)
+ + ctrlval.bv_len + 1,
+ op->o_tmpmemctx );
+ if ( ctrls == NULL ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ ctrls[ i + 1 ] = NULL;
+ ctrls[ i ] = (LDAPControl *)&ctrls[ i + 2 ];
+ ctrls[ i ]->ldctl_oid = LDAP_CONTROL_X_WHATFAILED;
+ ctrls[ i ]->ldctl_iscritical = 0;
+ ctrls[ i ]->ldctl_value.bv_val = (char *)&ctrls[ i ][ 1 ];
+ AC_MEMCPY( ctrls[ i ]->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len + 1 );
+ ctrls[ i ]->ldctl_value.bv_len = ctrlval.bv_len;
+
+ ber_free_buf( ber );
+
+ rs->sr_ctrls = ctrls;
+
+done:;
+ return rc;
+}
+#endif
+
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+static int parseLazyCommit(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_lazyCommit != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "\"Lazy Commit?\" control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "\"Lazy Commit?\" control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_lazyCommit = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+#endif
diff --git a/servers/slapd/cr.c b/servers/slapd/cr.c
new file mode 100644
index 0000000..a828aa8
--- /dev/null
+++ b/servers/slapd/cr.c
@@ -0,0 +1,501 @@
+/* cr.c - content rule routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+struct cindexrec {
+ struct berval cir_name;
+ ContentRule *cir_cr;
+};
+
+static Avlnode *cr_index = NULL;
+static LDAP_STAILQ_HEAD(CRList, ContentRule) cr_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(cr_list);
+
+static int
+cr_index_cmp(
+ const void *v_cir1,
+ const void *v_cir2 )
+{
+ const struct cindexrec *cir1 = v_cir1;
+ const struct cindexrec *cir2 = v_cir2;
+ int i = cir1->cir_name.bv_len - cir2->cir_name.bv_len;
+ if (i) return i;
+ return strcasecmp( cir1->cir_name.bv_val, cir2->cir_name.bv_val );
+}
+
+static int
+cr_index_name_cmp(
+ const void *v_name,
+ const void *v_cir )
+{
+ const struct berval *name = v_name;
+ const struct cindexrec *cir = v_cir;
+ int i = name->bv_len - cir->cir_name.bv_len;
+ if (i) return i;
+ return strncasecmp( name->bv_val, cir->cir_name.bv_val, name->bv_len );
+}
+
+ContentRule *
+cr_find( const char *crname )
+{
+ struct berval bv;
+
+ bv.bv_val = (char *)crname;
+ bv.bv_len = strlen( crname );
+
+ return( cr_bvfind( &bv ) );
+}
+
+ContentRule *
+cr_bvfind( struct berval *crname )
+{
+ struct cindexrec *cir;
+
+ cir = ldap_avl_find( cr_index, crname, cr_index_name_cmp );
+
+ if ( cir != NULL ) {
+ return( cir->cir_cr );
+ }
+
+ return( NULL );
+}
+
+static int
+cr_destroy_one( ContentRule *c )
+{
+ assert( c != NULL );
+
+ if (c->scr_auxiliaries) ldap_memfree(c->scr_auxiliaries);
+ if (c->scr_required) ldap_memfree(c->scr_required);
+ if (c->scr_allowed) ldap_memfree(c->scr_allowed);
+ if (c->scr_precluded) ldap_memfree(c->scr_precluded);
+ ldap_contentrule_free((LDAPContentRule *)c);
+
+ return 0;
+}
+
+void
+cr_destroy( void )
+{
+ ContentRule *c;
+
+ ldap_avl_free(cr_index, ldap_memfree);
+
+ while( !LDAP_STAILQ_EMPTY(&cr_list) ) {
+ c = LDAP_STAILQ_FIRST(&cr_list);
+ LDAP_STAILQ_REMOVE_HEAD(&cr_list, scr_next);
+
+ cr_destroy_one( c );
+ }
+}
+
+static int
+cr_insert(
+ ContentRule *scr,
+ const char **err
+)
+{
+ struct cindexrec *cir;
+ char **names;
+
+ assert( scr != NULL );
+
+ if ( scr->scr_oid ) {
+ cir = (struct cindexrec *)
+ ch_calloc( 1, sizeof(struct cindexrec) );
+ cir->cir_name.bv_val = scr->scr_oid;
+ cir->cir_name.bv_len = strlen( scr->scr_oid );
+ cir->cir_cr = scr;
+
+ if ( ldap_avl_insert( &cr_index, (caddr_t) cir,
+ cr_index_cmp, ldap_avl_dup_error ) )
+ {
+ *err = scr->scr_oid;
+ ldap_memfree(cir);
+ return SLAP_SCHERR_CR_DUP;
+ }
+
+ /* FIX: temporal consistency check */
+ assert( cr_bvfind(&cir->cir_name) != NULL );
+ }
+
+ if ( (names = scr->scr_names) ) {
+ while ( *names ) {
+ cir = (struct cindexrec *)
+ ch_calloc( 1, sizeof(struct cindexrec) );
+ cir->cir_name.bv_val = *names;
+ cir->cir_name.bv_len = strlen( *names );
+ cir->cir_cr = scr;
+
+ if ( ldap_avl_insert( &cr_index, (caddr_t) cir,
+ cr_index_cmp, ldap_avl_dup_error ) )
+ {
+ *err = *names;
+ ldap_memfree(cir);
+ return SLAP_SCHERR_CR_DUP;
+ }
+
+ /* FIX: temporal consistency check */
+ assert( cr_bvfind(&cir->cir_name) != NULL );
+
+ names++;
+ }
+ }
+
+ LDAP_STAILQ_INSERT_TAIL(&cr_list, scr, scr_next);
+
+ return 0;
+}
+
+static int
+cr_add_auxiliaries(
+ ContentRule *scr,
+ int *op,
+ const char **err )
+{
+ int naux;
+
+ if( scr->scr_oc_oids_aux == NULL ) return 0;
+
+ for( naux=0; scr->scr_oc_oids_aux[naux]; naux++ ) {
+ /* count them */ ;
+ }
+
+ scr->scr_auxiliaries = ch_calloc( naux+1, sizeof(ObjectClass *));
+
+ for( naux=0; scr->scr_oc_oids_aux[naux]; naux++ ) {
+ ObjectClass *soc = scr->scr_auxiliaries[naux]
+ = oc_find(scr->scr_oc_oids_aux[naux]);
+ if ( !soc ) {
+ *err = scr->scr_oc_oids_aux[naux];
+ return SLAP_SCHERR_CLASS_NOT_FOUND;
+ }
+
+ if( soc->soc_flags & SLAP_OC_OPERATIONAL &&
+ soc != slap_schema.si_oc_extensibleObject )
+ {
+ (*op)++;
+ }
+
+ if( soc->soc_kind != LDAP_SCHEMA_AUXILIARY ) {
+ *err = scr->scr_oc_oids_aux[naux];
+ return SLAP_SCHERR_CR_BAD_AUX;
+ }
+ }
+
+ scr->scr_auxiliaries[naux] = NULL;
+ return 0;
+}
+
+static int
+cr_create_required(
+ ContentRule *scr,
+ int *op,
+ const char **err )
+{
+ char **attrs = scr->scr_at_oids_must;
+ char **attrs1;
+ AttributeType *sat;
+
+ if ( attrs ) {
+ attrs1 = attrs;
+ while ( *attrs1 ) {
+ sat = at_find(*attrs1);
+ if ( !sat ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_ATTR_NOT_FOUND;
+ }
+
+ if( is_at_operational( sat )) (*op)++;
+
+ if ( at_find_in_list(sat, scr->scr_required) < 0) {
+ if ( at_append_to_list(sat, &scr->scr_required) ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+ } else {
+ *err = *attrs1;
+ return SLAP_SCHERR_CR_BAD_AT;
+ }
+ attrs1++;
+ }
+ }
+ return 0;
+}
+
+static int
+cr_create_allowed(
+ ContentRule *scr,
+ int *op,
+ const char **err )
+{
+ char **attrs = scr->scr_at_oids_may;
+ char **attrs1;
+ AttributeType *sat;
+
+ if ( attrs ) {
+ attrs1 = attrs;
+ while ( *attrs1 ) {
+ sat = at_find(*attrs1);
+ if ( !sat ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_ATTR_NOT_FOUND;
+ }
+
+ if( is_at_operational( sat )) (*op)++;
+
+ if ( at_find_in_list(sat, scr->scr_required) < 0 &&
+ at_find_in_list(sat, scr->scr_allowed) < 0 )
+ {
+ if ( at_append_to_list(sat, &scr->scr_allowed) ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+ } else {
+ *err = *attrs1;
+ return SLAP_SCHERR_CR_BAD_AT;
+ }
+ attrs1++;
+ }
+ }
+ return 0;
+}
+
+static int
+cr_create_precluded(
+ ContentRule *scr,
+ int *op,
+ const char **err )
+{
+ char **attrs = scr->scr_at_oids_not;
+ char **attrs1;
+ AttributeType *sat;
+
+ if ( attrs ) {
+ attrs1 = attrs;
+ while ( *attrs1 ) {
+ sat = at_find(*attrs1);
+ if ( !sat ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_ATTR_NOT_FOUND;
+ }
+
+ if( is_at_operational( sat )) (*op)++;
+
+ /* FIXME: should also make sure attribute type is not
+ a required attribute of the structural class or
+ any auxiliary class */
+ if ( at_find_in_list(sat, scr->scr_required) < 0 &&
+ at_find_in_list(sat, scr->scr_allowed) < 0 &&
+ at_find_in_list(sat, scr->scr_precluded) < 0 )
+ {
+ if ( at_append_to_list(sat, &scr->scr_precluded) ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+ } else {
+ *err = *attrs1;
+ return SLAP_SCHERR_CR_BAD_AT;
+ }
+ attrs1++;
+ }
+ }
+ return 0;
+}
+
+int
+cr_add(
+ LDAPContentRule *cr,
+ int user,
+ ContentRule **rscr,
+ const char **err
+)
+{
+ ContentRule *scr;
+ int code;
+ int op = 0;
+ char *oidm = NULL;
+
+ if ( cr->cr_names != NULL ) {
+ int i;
+
+ for( i=0; cr->cr_names[i]; i++ ) {
+ if( !slap_valid_descr( cr->cr_names[i] ) ) {
+ return SLAP_SCHERR_BAD_DESCR;
+ }
+ }
+ }
+
+ if ( !OID_LEADCHAR( cr->cr_oid[0] )) {
+ /* Expand OID macros */
+ char *oid = oidm_find( cr->cr_oid );
+ if ( !oid ) {
+ *err = cr->cr_oid;
+ return SLAP_SCHERR_OIDM;
+ }
+ if ( oid != cr->cr_oid ) {
+ oidm = cr->cr_oid;
+ cr->cr_oid = oid;
+ }
+ }
+
+ scr = (ContentRule *) ch_calloc( 1, sizeof(ContentRule) );
+ AC_MEMCPY( &scr->scr_crule, cr, sizeof(LDAPContentRule) );
+
+ scr->scr_oidmacro = oidm;
+ scr->scr_sclass = oc_find(cr->cr_oid);
+ if ( !scr->scr_sclass ) {
+ *err = cr->cr_oid;
+ code = SLAP_SCHERR_CLASS_NOT_FOUND;
+ goto fail;
+ }
+
+ /* check object class usage */
+ if( scr->scr_sclass->soc_kind != LDAP_SCHEMA_STRUCTURAL )
+ {
+ *err = cr->cr_oid;
+ code = SLAP_SCHERR_CR_BAD_STRUCT;
+ goto fail;
+ }
+
+ if( scr->scr_sclass->soc_flags & SLAP_OC_OPERATIONAL ) op++;
+
+ code = cr_add_auxiliaries( scr, &op, err );
+ if ( code != 0 ) goto fail;
+
+ code = cr_create_required( scr, &op, err );
+ if ( code != 0 ) goto fail;
+
+ code = cr_create_allowed( scr, &op, err );
+ if ( code != 0 ) goto fail;
+
+ code = cr_create_precluded( scr, &op, err );
+ if ( code != 0 ) goto fail;
+
+ if( user && op ) {
+ code = SLAP_SCHERR_CR_BAD_AUX;
+ goto fail;
+ }
+
+ code = cr_insert(scr,err);
+ if ( code == 0 && rscr )
+ *rscr = scr;
+ return code;
+fail:
+ ch_free( scr );
+ return code;
+}
+
+void
+cr_unparse( BerVarray *res, ContentRule *start, ContentRule *end, int sys )
+{
+ ContentRule *cr;
+ int i, num;
+ struct berval bv, *bva = NULL, idx;
+ char ibuf[32];
+
+ if ( !start )
+ start = LDAP_STAILQ_FIRST( &cr_list );
+
+ /* count the result size */
+ i = 0;
+ for ( cr=start; cr; cr=LDAP_STAILQ_NEXT(cr, scr_next)) {
+ if ( sys && !(cr->scr_flags & SLAP_CR_HARDCODE)) continue;
+ i++;
+ if ( cr == end ) break;
+ }
+ if (!i) return;
+
+ num = i;
+ bva = ch_malloc( (num+1) * sizeof(struct berval) );
+ BER_BVZERO( bva );
+ idx.bv_val = ibuf;
+ if ( sys ) {
+ idx.bv_len = 0;
+ ibuf[0] = '\0';
+ }
+ i = 0;
+ for ( cr=start; cr; cr=LDAP_STAILQ_NEXT(cr, scr_next)) {
+ LDAPContentRule lcr, *lcrp;
+ if ( sys && !(cr->scr_flags & SLAP_CR_HARDCODE)) continue;
+ if ( cr->scr_oidmacro ) {
+ lcr = cr->scr_crule;
+ lcr.cr_oid = cr->scr_oidmacro;
+ lcrp = &lcr;
+ } else {
+ lcrp = &cr->scr_crule;
+ }
+ if ( ldap_contentrule2bv( lcrp, &bv ) == NULL ) {
+ ber_bvarray_free( bva );
+ }
+ if ( !sys ) {
+ idx.bv_len = sprintf(idx.bv_val, "{%d}", i);
+ }
+ bva[i].bv_len = idx.bv_len + bv.bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ strcpy( bva[i].bv_val, ibuf );
+ strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val );
+ i++;
+ bva[i].bv_val = NULL;
+ ldap_memfree( bv.bv_val );
+ if ( cr == end ) break;
+ }
+ *res = bva;
+}
+
+int
+cr_schema_info( Entry *e )
+{
+ AttributeDescription *ad_ditContentRules
+ = slap_schema.si_ad_ditContentRules;
+ ContentRule *cr;
+
+ struct berval val;
+ struct berval nval;
+
+ LDAP_STAILQ_FOREACH(cr, &cr_list, scr_next) {
+ if ( ldap_contentrule2bv( &cr->scr_crule, &val ) == NULL ) {
+ return -1;
+ }
+
+#if 0
+ if( cr->scr_flags & SLAP_CR_HIDE ) continue;
+#endif
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "Merging cr [%ld] %s\n",
+ (long) val.bv_len, val.bv_val );
+#endif
+
+ nval.bv_val = cr->scr_oid;
+ nval.bv_len = strlen(cr->scr_oid);
+
+ if( attr_merge_one( e, ad_ditContentRules, &val, &nval ) )
+ {
+ return -1;
+ }
+ ldap_memfree( val.bv_val );
+ }
+ return 0;
+}
diff --git a/servers/slapd/ctxcsn.c b/servers/slapd/ctxcsn.c
new file mode 100644
index 0000000..a8f73c3
--- /dev/null
+++ b/servers/slapd/ctxcsn.c
@@ -0,0 +1,218 @@
+/* ctxcsn.c -- Context CSN Management Routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "lutil_ldap.h"
+
+const struct berval slap_ldapsync_bv = BER_BVC("ldapsync");
+const struct berval slap_ldapsync_cn_bv = BER_BVC("cn=ldapsync");
+int slap_serverID;
+
+/* maxcsn->bv_val must point to a char buf[LDAP_PVT_CSNSTR_BUFSIZE] */
+void
+slap_get_commit_csn(
+ Operation *op,
+ struct berval *maxcsn,
+ int *foundit
+)
+{
+ struct slap_csn_entry *csne, *committed_csne = NULL;
+ BackendDB *be = op->o_bd->bd_self;
+ int sid = -1;
+
+ if ( maxcsn ) {
+ assert( maxcsn->bv_val != NULL );
+ assert( maxcsn->bv_len >= LDAP_PVT_CSNSTR_BUFSIZE );
+ }
+ if ( foundit ) {
+ *foundit = 0;
+ }
+
+ if ( !BER_BVISEMPTY( &op->o_csn )) {
+ sid = slap_parse_csn_sid( &op->o_csn );
+ }
+
+ ldap_pvt_thread_mutex_lock( &be->be_pcsn_p->be_pcsn_mutex );
+
+ LDAP_TAILQ_FOREACH( csne, &be->be_pcsn_p->be_pcsn_list, ce_csn_link ) {
+ if ( csne->ce_op == op ) {
+ csne->ce_state = SLAP_CSN_COMMIT;
+ if ( foundit ) *foundit = 1;
+ break;
+ }
+ }
+
+ LDAP_TAILQ_FOREACH( csne, &be->be_pcsn_p->be_pcsn_list, ce_csn_link ) {
+ if ( sid != -1 && sid == csne->ce_sid ) {
+ if ( csne->ce_state == SLAP_CSN_COMMIT ) committed_csne = csne;
+ if ( csne->ce_state == SLAP_CSN_PENDING ) break;
+ }
+ }
+
+ if ( maxcsn ) {
+ if ( committed_csne ) {
+ if ( committed_csne->ce_csn.bv_len < maxcsn->bv_len )
+ maxcsn->bv_len = committed_csne->ce_csn.bv_len;
+ AC_MEMCPY( maxcsn->bv_val, committed_csne->ce_csn.bv_val,
+ maxcsn->bv_len+1 );
+ } else {
+ maxcsn->bv_len = 0;
+ maxcsn->bv_val[0] = 0;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &be->be_pcsn_p->be_pcsn_mutex );
+}
+
+void
+slap_rewind_commit_csn( Operation *op )
+{
+ struct slap_csn_entry *csne;
+ BackendDB *be = op->o_bd->bd_self;
+
+ ldap_pvt_thread_mutex_lock( &be->be_pcsn_p->be_pcsn_mutex );
+
+ LDAP_TAILQ_FOREACH( csne, &be->be_pcsn_p->be_pcsn_list, ce_csn_link ) {
+ if ( csne->ce_op == op ) {
+ csne->ce_state = SLAP_CSN_PENDING;
+ break;
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &be->be_pcsn_p->be_pcsn_mutex );
+}
+
+void
+slap_graduate_commit_csn( Operation *op )
+{
+ struct slap_csn_entry *csne;
+ BackendDB *be;
+
+ if ( op == NULL ) return;
+ if ( op->o_bd == NULL ) return;
+ be = op->o_bd->bd_self;
+
+ ldap_pvt_thread_mutex_lock( &be->be_pcsn_p->be_pcsn_mutex );
+
+ LDAP_TAILQ_FOREACH( csne, &be->be_pcsn_p->be_pcsn_list, ce_csn_link ) {
+ if ( csne->ce_op == op ) {
+ LDAP_TAILQ_REMOVE( &be->be_pcsn_p->be_pcsn_list,
+ csne, ce_csn_link );
+ Debug( LDAP_DEBUG_SYNC, "slap_graduate_commit_csn: removing %p %s\n",
+ csne, csne->ce_csn.bv_val );
+ if ( op->o_csn.bv_val == csne->ce_csn.bv_val ) {
+ BER_BVZERO( &op->o_csn );
+ }
+ ch_free( csne->ce_csn.bv_val );
+ ch_free( csne );
+ break;
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &be->be_pcsn_p->be_pcsn_mutex );
+
+ return;
+}
+
+static struct berval ocbva[] = {
+ BER_BVC("top"),
+ BER_BVC("subentry"),
+ BER_BVC("syncProviderSubentry"),
+ BER_BVNULL
+};
+
+Entry *
+slap_create_context_csn_entry(
+ Backend *be,
+ struct berval *context_csn )
+{
+ Entry* e;
+
+ struct berval bv;
+
+ e = entry_alloc();
+
+ attr_merge( e, slap_schema.si_ad_objectClass,
+ ocbva, NULL );
+ attr_merge_one( e, slap_schema.si_ad_structuralObjectClass,
+ &ocbva[1], NULL );
+ attr_merge_one( e, slap_schema.si_ad_cn,
+ (struct berval *)&slap_ldapsync_bv, NULL );
+
+ if ( context_csn ) {
+ attr_merge_one( e, slap_schema.si_ad_contextCSN,
+ context_csn, NULL );
+ }
+
+ BER_BVSTR( &bv, "{}" );
+ attr_merge_one( e, slap_schema.si_ad_subtreeSpecification, &bv, NULL );
+
+ build_new_dn( &e->e_name, &be->be_nsuffix[0],
+ (struct berval *)&slap_ldapsync_cn_bv, NULL );
+ ber_dupbv( &e->e_nname, &e->e_name );
+
+ return e;
+}
+
+void
+slap_queue_csn(
+ Operation *op,
+ struct berval *csn )
+{
+ struct slap_csn_entry *pending;
+ BackendDB *be = op->o_bd->bd_self;
+
+ pending = (struct slap_csn_entry *) ch_calloc( 1,
+ sizeof( struct slap_csn_entry ));
+
+ Debug( LDAP_DEBUG_SYNC, "slap_queue_csn: queueing %p %s\n", pending, csn->bv_val );
+
+ ber_dupbv( &pending->ce_csn, csn );
+ ber_bvreplace_x( &op->o_csn, &pending->ce_csn, op->o_tmpmemctx );
+ pending->ce_sid = slap_parse_csn_sid( csn );
+ pending->ce_op = op;
+ pending->ce_state = SLAP_CSN_PENDING;
+
+ ldap_pvt_thread_mutex_lock( &be->be_pcsn_p->be_pcsn_mutex );
+ LDAP_TAILQ_INSERT_TAIL( &be->be_pcsn_p->be_pcsn_list,
+ pending, ce_csn_link );
+ ldap_pvt_thread_mutex_unlock( &be->be_pcsn_p->be_pcsn_mutex );
+}
+
+int
+slap_get_csn(
+ Operation *op,
+ struct berval *csn,
+ int manage_ctxcsn )
+{
+ if ( csn == NULL ) return LDAP_OTHER;
+
+ csn->bv_len = ldap_pvt_csnstr( csn->bv_val, csn->bv_len, slap_serverID, 0 );
+ Debug( LDAP_DEBUG_SYNC, "slap_get_csn: %s generated new csn=%s manage=%d\n",
+ op->o_log_prefix, csn->bv_val, manage_ctxcsn );
+ if ( manage_ctxcsn )
+ slap_queue_csn( op, csn );
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/daemon.c b/servers/slapd/daemon.c
new file mode 100644
index 0000000..18db97a
--- /dev/null
+++ b/servers/slapd/daemon.c
@@ -0,0 +1,3610 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2007 by Howard Chu, Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "ldap_pvt_thread.h"
+#include "lutil.h"
+
+#include "ldap_rq.h"
+
+#ifdef HAVE_SYSTEMD_SD_DAEMON_H
+#include <systemd/sd-daemon.h>
+#endif
+
+#ifdef HAVE_POLL
+#include <poll.h>
+#endif
+
+#ifdef HAVE_KQUEUE
+# include <sys/types.h>
+# include <sys/event.h>
+# include <sys/time.h>
+#elif defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL)
+# include <sys/epoll.h>
+#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_SYS_DEVPOLL_H) && defined(HAVE_DEVPOLL)
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <sys/devpoll.h>
+#endif /* ! kqueue && ! epoll && ! /dev/poll */
+
+#ifdef HAVE_TCPD
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_NOTICE;
+#endif /* TCP Wrappers */
+
+#ifdef LDAP_PF_LOCAL
+# include <sys/stat.h>
+/* this should go in <ldap.h> as soon as it is accepted */
+# define LDAPI_MOD_URLEXT "x-mod"
+#endif /* LDAP_PF_LOCAL */
+
+#ifdef LDAP_PF_INET6
+int slap_inet4or6 = AF_UNSPEC;
+#else /* ! INETv6 */
+int slap_inet4or6 = AF_INET;
+#endif /* ! INETv6 */
+
+/* globals */
+time_t starttime;
+ber_socket_t dtblsize;
+slap_ssf_t local_ssf = LDAP_PVT_SASL_LOCAL_SSF;
+struct runqueue_s slapd_rq;
+
+int slapd_daemon_threads = 1;
+int slapd_daemon_mask;
+
+#ifdef LDAP_TCP_BUFFER
+int slapd_tcp_rmem;
+int slapd_tcp_wmem;
+#endif /* LDAP_TCP_BUFFER */
+
+Listener **slap_listeners = NULL;
+static volatile sig_atomic_t listening = 1; /* 0 when slap_listeners closed */
+
+#ifndef SLAPD_LISTEN_BACKLOG
+#define SLAPD_LISTEN_BACKLOG 2048
+#endif /* ! SLAPD_LISTEN_BACKLOG */
+
+#define DAEMON_ID(fd) (fd & slapd_daemon_mask)
+
+typedef ber_socket_t sdpair[2];
+
+static sdpair *wake_sds;
+static ldap_pvt_thread_mutex_t emfile_mutex;
+static int emfile;
+
+static volatile int waking;
+#define WAKE_LISTENER(l,w) do { \
+ if (w) { \
+ (void)!tcp_write( SLAP_FD2SOCK(wake_sds[l][1]), "0", 1 ); \
+ } \
+} while (0)
+
+ldap_pvt_thread_mutex_t slapd_init_mutex;
+ldap_pvt_thread_cond_t slapd_init_cond;
+int slapd_ready = 0;
+
+volatile sig_atomic_t slapd_shutdown = 0;
+volatile sig_atomic_t slapd_gentle_shutdown = 0;
+volatile sig_atomic_t slapd_abrupt_shutdown = 0;
+
+#ifdef HAVE_WINSOCK
+ldap_pvt_thread_mutex_t slapd_ws_mutex;
+SOCKET *slapd_ws_sockets;
+#define SD_READ 1
+#define SD_WRITE 2
+#define SD_ACTIVE 4
+#define SD_LISTENER 8
+#endif
+
+#ifdef HAVE_TCPD
+static ldap_pvt_thread_mutex_t sd_tcpd_mutex;
+#endif /* TCP Wrappers */
+
+typedef struct slap_daemon_st {
+ ldap_pvt_thread_mutex_t sd_mutex;
+
+ ber_socket_t sd_nactives;
+ int sd_nwriters;
+ int sd_nfds;
+ ldap_pvt_thread_t sd_tid;
+
+#if defined(HAVE_KQUEUE)
+ uint8_t* sd_fdmodes; /* indexed by fd */
+ Listener** sd_l; /* indexed by fd */
+ /* Double buffer the kqueue changes to avoid holding the sd_mutex \
+ * during a kevent() call. \
+ */
+ struct kq_change {
+ struct kevent* sd_changes;
+ int sd_nchanges;
+ int sd_maxchanges;
+ } sd_kqc[2];
+ int sd_changeidx; /* index to current change buffer */
+ int sd_kq;
+#elif defined(HAVE_EPOLL)
+
+ struct epoll_event *sd_epolls;
+ int *sd_index;
+ int sd_epfd;
+#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_DEVPOLL)
+ /* eXperimental */
+ struct pollfd *sd_pollfd;
+ int *sd_index;
+ Listener **sd_l;
+ int sd_dpfd;
+#else /* ! kqueue && ! epoll && ! /dev/poll */
+#ifdef HAVE_WINSOCK
+ char *sd_flags;
+ char *sd_rflags;
+#else /* ! HAVE_WINSOCK */
+ fd_set sd_actives;
+ fd_set sd_readers;
+ fd_set sd_writers;
+#endif /* ! HAVE_WINSOCK */
+#endif /* ! kqueue && ! epoll && ! /dev/poll */
+} slap_daemon_st;
+
+static slap_daemon_st *slap_daemon;
+
+/*
+ * NOTE: naming convention for macros:
+ *
+ * - SLAP_SOCK_* and SLAP_EVENT_* for public interface that deals
+ * with file descriptors and events respectively
+ *
+ * - SLAP_<type>_* for private interface; type by now is one of
+ * EPOLL, DEVPOLL, SELECT, KQUEUE
+ *
+ * private interface should not be used in the code.
+ */
+#ifdef HAVE_KQUEUE
+# define SLAP_EVENT_FNAME "kqueue"
+# define SLAP_EVENTS_ARE_INDEXED 0
+# define SLAP_EVENT_MAX(t) (2 * dtblsize) /* each fd can have a read & a write event */
+
+# define SLAP_EVENT_DECL \
+ static struct kevent* events = NULL
+
+# define SLAP_EVENT_INIT(t) do {\
+ if (!events) { \
+ events = ch_malloc(sizeof(*events) * SLAP_EVENT_MAX(t)); \
+ } \
+} while (0)
+
+# define SLAP_SOCK_INIT(t) do { \
+ int kq_i; \
+ size_t kq_nbytes; \
+ Debug(LDAP_DEBUG_ANY, "daemon: SLAP_SOCK_INIT: dtblsize=%d\n", dtblsize); \
+ slap_daemon[t].sd_nfds = 0; \
+ slap_daemon[t].sd_changeidx = 0; \
+ for (kq_i = 0; kq_i < 2; kq_i++) { \
+ struct kq_change* kqc = &slap_daemon[t].sd_kqc[kq_i]; \
+ kqc->sd_nchanges = 0; \
+ kqc->sd_maxchanges = 256; /* will grow as needed */ \
+ kq_nbytes = sizeof(*kqc->sd_changes) * kqc->sd_maxchanges; \
+ kqc->sd_changes = ch_calloc(1, kq_nbytes); \
+ } \
+ kq_nbytes = sizeof(*slap_daemon[t].sd_fdmodes) * dtblsize; \
+ slap_daemon[t].sd_fdmodes = ch_calloc(1, kq_nbytes); \
+ kq_nbytes = sizeof(*slap_daemon[t].sd_l) * dtblsize; \
+ slap_daemon[t].sd_l = ch_calloc(1, kq_nbytes); \
+ slap_daemon[t].sd_kq = kqueue(); \
+} while (0)
+
+/* a kqueue fd obtained before a fork isn't inherited by child process.
+ * reacquire it.
+ */
+# define SLAP_SOCK_INIT2() do { \
+ slap_daemon[0].sd_kq = kqueue(); \
+} while (0)
+
+# define SLAP_SOCK_DESTROY(t) do { \
+ int kq_i; \
+ if (slap_daemon[t].sd_kq > 0) { \
+ close(slap_daemon[t].sd_kq); \
+ slap_daemon[t].sd_kq = -1; \
+ } \
+ for (kq_i = 0; kq_i < 2; kq_i++) { \
+ if (slap_daemon[t].sd_kqc[kq_i].sd_changes != NULL) { \
+ ch_free(slap_daemon[t].sd_kqc[kq_i].sd_changes); \
+ slap_daemon[t].sd_kqc[kq_i].sd_changes = NULL; \
+ } \
+ slap_daemon[t].sd_kqc[kq_i].sd_nchanges = 0; \
+ slap_daemon[t].sd_kqc[kq_i].sd_maxchanges = 0; \
+ } \
+ if (slap_daemon[t].sd_l != NULL) { \
+ ch_free(slap_daemon[t].sd_l); \
+ slap_daemon[t].sd_l = NULL; \
+ } \
+ if (slap_daemon[t].sd_fdmodes != NULL) { \
+ ch_free(slap_daemon[t].sd_fdmodes); \
+ slap_daemon[t].sd_fdmodes = NULL; \
+ } \
+ slap_daemon[t].sd_nfds = 0; \
+} while (0)
+
+# define SLAP_KQUEUE_SOCK_ACTIVE 0x01
+# define SLAP_KQUEUE_SOCK_READ_ENABLED 0x02
+# define SLAP_KQUEUE_SOCK_WRITE_ENABLED 0x04
+
+# define SLAP_SOCK_IS_ACTIVE(t,s) (slap_daemon[t].sd_fdmodes[(s)] != 0)
+# define SLAP_SOCK_NOT_ACTIVE(t,s) (slap_daemon[t].sd_fdmodes[(s)] == 0)
+# define SLAP_SOCK_IS_READ(t,s) (slap_daemon[t].sd_fdmodes[(s)] & SLAP_KQUEUE_SOCK_READ_ENABLED)
+# define SLAP_SOCK_IS_WRITE(t,s) (slap_daemon[t].sd_fdmodes[(s)] & SLAP_KQUEUE_SOCK_WRITE_ENABLED)
+
+/*
+ * SLAP_SOCK_SET_* & SLAP_SOCK_CLR_* get called a _lot_. Since kevent()
+ * processes changes before it looks for events, batch up the changes which
+ * will get submitted the next time kevent() is called for events.
+ */
+
+# define SLAP_KQUEUE_CHANGE(t, s, filter, flag) do { \
+ /* If maxchanges is reached, have to realloc to make room for more. \
+ * Ideally we'd call kevent(), but the daemon thread could be sitting \
+ * in kevent() waiting for events. \
+ */ \
+ struct kq_change* kqc = &slap_daemon[t].sd_kqc[slap_daemon[t].sd_changeidx]; \
+ if (kqc->sd_nchanges == kqc->sd_maxchanges) { \
+ /* Don't want to do this very often. Double the size. */ \
+ size_t kq_nbytes; \
+ Debug(LDAP_DEBUG_CONNS, \
+ "daemon: SLAP_KQUEUE_CHANGE: increasing slap_daemon.sd_kqc[%d].maxchanges from %d to %d\n", \
+ slap_daemon[t].sd_changeidx, kqc->sd_maxchanges, 2*kqc->sd_maxchanges); \
+ kqc->sd_maxchanges += kqc->sd_maxchanges; \
+ kq_nbytes = sizeof(*kqc->sd_changes) * kqc->sd_maxchanges; \
+ kqc->sd_changes = ch_realloc(kqc->sd_changes, kq_nbytes); \
+ } \
+ EV_SET(&kqc->sd_changes[kqc->sd_nchanges++], \
+ (s), (filter), (flag), 0, 0, slap_daemon[t].sd_l[(s)]); \
+} while (0)
+
+# define SLAP_KQUEUE_SOCK_SET(t, s, filter, mode) do { \
+ if ((slap_daemon[t].sd_fdmodes[(s)] & (mode)) != (mode)) { \
+ slap_daemon[t].sd_fdmodes[(s)] |= (mode); \
+ SLAP_KQUEUE_CHANGE(t, (s), (filter), EV_ENABLE); \
+ } \
+} while (0)
+
+# define SLAP_KQUEUE_SOCK_CLR(t, s, filter, mode) do { \
+ if (slap_daemon[t].sd_fdmodes[(s)] & (mode)) { \
+ slap_daemon[t].sd_fdmodes[(s)] &= ~(mode); \
+ SLAP_KQUEUE_CHANGE(t, (s), (filter), EV_DISABLE); \
+ } \
+} while (0)
+
+# define SLAP_SOCK_SET_READ(t, s) SLAP_KQUEUE_SOCK_SET(t, (s), EVFILT_READ, SLAP_KQUEUE_SOCK_READ_ENABLED)
+# define SLAP_SOCK_SET_WRITE(t, s) SLAP_KQUEUE_SOCK_SET(t, (s), EVFILT_WRITE, SLAP_KQUEUE_SOCK_WRITE_ENABLED)
+# define SLAP_SOCK_CLR_READ(t, s) SLAP_KQUEUE_SOCK_CLR(t, (s), EVFILT_READ, SLAP_KQUEUE_SOCK_READ_ENABLED)
+# define SLAP_SOCK_CLR_WRITE(t, s) SLAP_KQUEUE_SOCK_CLR(t, (s), EVFILT_WRITE, SLAP_KQUEUE_SOCK_WRITE_ENABLED)
+
+/* kqueue doesn't need to do anything to clear the event. */
+# define SLAP_EVENT_CLR_READ(i) do {} while (0)
+# define SLAP_EVENT_CLR_WRITE(i) do {} while (0)
+
+# define SLAP_SOCK_ADD(t, s, l) do { \
+ assert( s < dtblsize ); \
+ slap_daemon[t].sd_l[(s)] = (l); \
+ slap_daemon[t].sd_fdmodes[(s)] = SLAP_KQUEUE_SOCK_ACTIVE | SLAP_KQUEUE_SOCK_READ_ENABLED; \
+ ++slap_daemon[t].sd_nfds; \
+ SLAP_KQUEUE_CHANGE(t, (s), EVFILT_READ, EV_ADD); \
+ SLAP_KQUEUE_CHANGE(t, (s), EVFILT_WRITE, EV_ADD | EV_DISABLE); \
+} while (0)
+
+# define SLAP_SOCK_DEL(t, s) do { \
+ SLAP_KQUEUE_CHANGE(t, (s), EVFILT_READ, EV_DELETE); \
+ SLAP_KQUEUE_CHANGE(t, (s), EVFILT_WRITE, EV_DELETE); \
+ slap_daemon[t].sd_l[(s)] = NULL; \
+ slap_daemon[t].sd_fdmodes[(s)] = 0; \
+ --slap_daemon[t].sd_nfds; \
+} while (0)
+
+# define SLAP_EVENT_FD(t, i) (events[(i)].ident)
+
+# define SLAP_EVENT_IS_READ(t, i) \
+ (events[(i)].filter == EVFILT_READ && SLAP_SOCK_IS_READ(t, SLAP_EVENT_FD(0, i)))
+
+# define SLAP_EVENT_IS_WRITE(t, i) \
+ (events[(i)].filter == EVFILT_WRITE && SLAP_SOCK_IS_WRITE(t, SLAP_EVENT_FD(0, i)))
+
+# define SLAP_EVENT_IS_LISTENER(t, i) \
+ (events[(i)].udata && SLAP_SOCK_IS_READ(t, SLAP_EVENT_FD(t, i)))
+
+# define SLAP_EVENT_LISTENER(t, i) ((Listener*)(events[(i)].udata))
+
+# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \
+ struct timespec kq_ts; \
+ struct timespec* kq_tsp; \
+ int kq_idx; \
+ if (tvp) { \
+ TIMEVAL_TO_TIMESPEC((tvp), &kq_ts); \
+ kq_tsp = &kq_ts; \
+ } else { \
+ kq_tsp = NULL; \
+ } \
+ /* Save the change buffer index for use when the mutex is unlocked, \
+ * then switch the index so new changes go to the other buffer. \
+ */ \
+ ldap_pvt_thread_mutex_lock( &slap_daemon[t].sd_mutex ); \
+ kq_idx = slap_daemon[t].sd_changeidx; \
+ slap_daemon[t].sd_changeidx ^= 1; \
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[t].sd_mutex ); \
+ *(nsp) = kevent(slap_daemon[t].sd_kq, \
+ slap_daemon[t].sd_kqc[kq_idx].sd_nchanges \
+ ? slap_daemon[t].sd_kqc[kq_idx].sd_changes : NULL, \
+ slap_daemon[t].sd_kqc[kq_idx].sd_nchanges, \
+ events, SLAP_EVENT_MAX(t), kq_tsp); \
+ slap_daemon[t].sd_kqc[kq_idx].sd_nchanges = 0; \
+} while(0)
+
+/*-------------------------------------------------------------------------------*/
+
+#elif defined(HAVE_EPOLL)
+/***************************************
+ * Use epoll infrastructure - epoll(4) *
+ ***************************************/
+# define SLAP_EVENT_FNAME "epoll"
+# define SLAP_EVENTS_ARE_INDEXED 0
+# define SLAP_EPOLL_SOCK_IX(t,s) (slap_daemon[t].sd_index[(s)])
+# define SLAP_EPOLL_SOCK_EP(t,s) (slap_daemon[t].sd_epolls[SLAP_EPOLL_SOCK_IX(t,s)])
+# define SLAP_EPOLL_SOCK_EV(t,s) (SLAP_EPOLL_SOCK_EP(t,s).events)
+# define SLAP_SOCK_IS_ACTIVE(t,s) (SLAP_EPOLL_SOCK_IX(t,s) != -1)
+# define SLAP_SOCK_NOT_ACTIVE(t,s) (SLAP_EPOLL_SOCK_IX(t,s) == -1)
+# define SLAP_EPOLL_SOCK_IS_SET(t,s, mode) (SLAP_EPOLL_SOCK_EV(t,s) & (mode))
+
+# define SLAP_SOCK_IS_READ(t,s) SLAP_EPOLL_SOCK_IS_SET(t,(s), EPOLLIN)
+# define SLAP_SOCK_IS_WRITE(t,s) SLAP_EPOLL_SOCK_IS_SET(t,(s), EPOLLOUT)
+
+# define SLAP_EPOLL_SOCK_SET(t,s, mode) do { \
+ if ( (SLAP_EPOLL_SOCK_EV(t,s) & (mode)) != (mode) ) { \
+ SLAP_EPOLL_SOCK_EV(t,s) |= (mode); \
+ epoll_ctl( slap_daemon[t].sd_epfd, EPOLL_CTL_MOD, (s), \
+ &SLAP_EPOLL_SOCK_EP(t,s) ); \
+ } \
+} while (0)
+
+# define SLAP_EPOLL_SOCK_CLR(t,s, mode) do { \
+ if ( (SLAP_EPOLL_SOCK_EV(t,s) & (mode)) ) { \
+ SLAP_EPOLL_SOCK_EV(t,s) &= ~(mode); \
+ epoll_ctl( slap_daemon[t].sd_epfd, EPOLL_CTL_MOD, s, \
+ &SLAP_EPOLL_SOCK_EP(t,s) ); \
+ } \
+} while (0)
+
+# define SLAP_SOCK_SET_READ(t,s) SLAP_EPOLL_SOCK_SET(t,s, EPOLLIN)
+# define SLAP_SOCK_SET_WRITE(t,s) SLAP_EPOLL_SOCK_SET(t,s, EPOLLOUT)
+
+# define SLAP_SOCK_CLR_READ(t,s) SLAP_EPOLL_SOCK_CLR(t,(s), EPOLLIN)
+# define SLAP_SOCK_CLR_WRITE(t,s) SLAP_EPOLL_SOCK_CLR(t,(s), EPOLLOUT)
+
+# define SLAP_SOCK_SET_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] = 1 )
+# define SLAP_SOCK_CLR_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] = 0 )
+# define SLAP_SOCK_IS_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_EPOLL_SOCK_IX(t,s)] == 1 )
+
+# define SLAP_EPOLL_EVENT_CLR(i, mode) (revents[(i)].events &= ~(mode))
+
+# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds
+
+/* If a Listener address is provided, store that as the epoll data.
+ * Otherwise, store the address of this socket's slot in the
+ * index array. If we can't do this add, the system is out of
+ * resources and we need to shutdown.
+ */
+# define SLAP_SOCK_ADD(t, s, l) do { \
+ int rc; \
+ SLAP_EPOLL_SOCK_IX(t,(s)) = slap_daemon[t].sd_nfds; \
+ SLAP_EPOLL_SOCK_EP(t,(s)).data.ptr = (l) ? (l) : (void *)(&SLAP_EPOLL_SOCK_IX(t,s)); \
+ SLAP_EPOLL_SOCK_EV(t,(s)) = EPOLLIN; \
+ rc = epoll_ctl(slap_daemon[t].sd_epfd, EPOLL_CTL_ADD, \
+ (s), &SLAP_EPOLL_SOCK_EP(t,(s))); \
+ if ( rc == 0 ) { \
+ slap_daemon[t].sd_nfds++; \
+ } else { \
+ int saved_errno = errno; \
+ Debug( LDAP_DEBUG_ANY, \
+ "daemon: epoll_ctl(ADD,fd=%d) failed, errno=%d, shutting down\n", \
+ s, saved_errno ); \
+ slapd_shutdown = 2; \
+ } \
+} while (0)
+
+# define SLAP_EPOLL_EV_LISTENER(t,ptr) \
+ (((int *)(ptr) >= slap_daemon[t].sd_index && \
+ (int *)(ptr) <= &slap_daemon[t].sd_index[dtblsize]) ? 0 : 1 )
+
+# define SLAP_EPOLL_EV_PTRFD(t,ptr) (SLAP_EPOLL_EV_LISTENER(t,ptr) ? \
+ ((Listener *)ptr)->sl_sd : \
+ (ber_socket_t) ((int *)(ptr) - slap_daemon[t].sd_index))
+
+# define SLAP_SOCK_DEL(t,s) do { \
+ int fd, rc, index = SLAP_EPOLL_SOCK_IX(t,(s)); \
+ if ( index < 0 ) break; \
+ rc = epoll_ctl(slap_daemon[t].sd_epfd, EPOLL_CTL_DEL, \
+ (s), &SLAP_EPOLL_SOCK_EP(t,(s))); \
+ slap_daemon[t].sd_epolls[index] = \
+ slap_daemon[t].sd_epolls[slap_daemon[t].sd_nfds-1]; \
+ fd = SLAP_EPOLL_EV_PTRFD(t,slap_daemon[t].sd_epolls[index].data.ptr); \
+ slap_daemon[t].sd_index[fd] = index; \
+ slap_daemon[t].sd_index[(s)] = -1; \
+ slap_daemon[t].sd_nfds--; \
+} while (0)
+
+# define SLAP_EVENT_CLR_READ(i) SLAP_EPOLL_EVENT_CLR((i), EPOLLIN)
+# define SLAP_EVENT_CLR_WRITE(i) SLAP_EPOLL_EVENT_CLR((i), EPOLLOUT)
+
+# define SLAP_EPOLL_EVENT_CHK(i, mode) (revents[(i)].events & mode)
+
+# define SLAP_EVENT_IS_READ(i) SLAP_EPOLL_EVENT_CHK((i), EPOLLIN)
+# define SLAP_EVENT_IS_WRITE(i) SLAP_EPOLL_EVENT_CHK((i), EPOLLOUT)
+# define SLAP_EVENT_IS_LISTENER(t,i) SLAP_EPOLL_EV_LISTENER(t,revents[(i)].data.ptr)
+# define SLAP_EVENT_LISTENER(t,i) ((Listener *)(revents[(i)].data.ptr))
+
+# define SLAP_EVENT_FD(t,i) SLAP_EPOLL_EV_PTRFD(t,revents[(i)].data.ptr)
+
+# define SLAP_SOCK_INIT(t) do { \
+ int j; \
+ slap_daemon[t].sd_epolls = ch_calloc(1, \
+ ( sizeof(struct epoll_event) * 2 \
+ + sizeof(int) ) * dtblsize * 2); \
+ slap_daemon[t].sd_index = (int *)&slap_daemon[t].sd_epolls[ 2 * dtblsize ]; \
+ slap_daemon[t].sd_epfd = epoll_create( dtblsize / slapd_daemon_threads ); \
+ for ( j = 0; j < dtblsize; j++ ) slap_daemon[t].sd_index[j] = -1; \
+} while (0)
+
+# define SLAP_SOCK_INIT2()
+
+# define SLAP_SOCK_DESTROY(t) do { \
+ if ( slap_daemon[t].sd_epolls != NULL ) { \
+ ch_free( slap_daemon[t].sd_epolls ); \
+ slap_daemon[t].sd_epolls = NULL; \
+ slap_daemon[t].sd_index = NULL; \
+ close( slap_daemon[t].sd_epfd ); \
+ } \
+} while ( 0 )
+
+# define SLAP_EVENT_DECL struct epoll_event *revents
+
+# define SLAP_EVENT_INIT(t) do { \
+ revents = slap_daemon[t].sd_epolls + dtblsize; \
+} while (0)
+
+# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \
+ *(nsp) = epoll_wait( slap_daemon[t].sd_epfd, revents, \
+ dtblsize, (tvp) ? ((tvp)->tv_sec * 1000 + (tvp)->tv_usec / 1000) : -1 ); \
+} while (0)
+
+#elif defined(SLAP_X_DEVPOLL) && defined(HAVE_DEVPOLL)
+
+/*************************************************************
+ * Use Solaris' (>= 2.7) /dev/poll infrastructure - poll(7d) *
+ *************************************************************/
+# define SLAP_EVENT_FNAME "/dev/poll"
+# define SLAP_EVENTS_ARE_INDEXED 0
+/*
+ * - sd_index is used much like with epoll()
+ * - sd_l is maintained as an array containing the address
+ * of the listener; the index is the fd itself
+ * - sd_pollfd is used to keep track of what data has been
+ * registered in /dev/poll
+ */
+# define SLAP_DEVPOLL_SOCK_IX(t,s) (slap_daemon[t].sd_index[(s)])
+# define SLAP_DEVPOLL_SOCK_LX(t,s) (slap_daemon[t].sd_l[(s)])
+# define SLAP_DEVPOLL_SOCK_EP(t,s) (slap_daemon[t].sd_pollfd[SLAP_DEVPOLL_SOCK_IX(t,(s))])
+# define SLAP_DEVPOLL_SOCK_FD(t,s) (SLAP_DEVPOLL_SOCK_EP(t,(s)).fd)
+# define SLAP_DEVPOLL_SOCK_EV(t,s) (SLAP_DEVPOLL_SOCK_EP(t,(s)).events)
+# define SLAP_SOCK_IS_ACTIVE(t,s) (SLAP_DEVPOLL_SOCK_IX(t,(s)) != -1)
+# define SLAP_SOCK_NOT_ACTIVE(t,s) (SLAP_DEVPOLL_SOCK_IX(t,(s)) == -1)
+# define SLAP_SOCK_IS_SET(t,s, mode) (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode))
+
+# define SLAP_SOCK_IS_READ(t,s) SLAP_SOCK_IS_SET(t,(s), POLLIN)
+# define SLAP_SOCK_IS_WRITE(t,s) SLAP_SOCK_IS_SET(t,(s), POLLOUT)
+
+/* as far as I understand, any time we need to communicate with the kernel
+ * about the number and/or properties of a file descriptor we need it to
+ * wait for, we have to rewrite the whole set */
+# define SLAP_DEVPOLL_WRITE_POLLFD(t,s, pfd, n, what, shdn) do { \
+ int rc; \
+ size_t size = (n) * sizeof( struct pollfd ); \
+ /* FIXME: use pwrite? */ \
+ rc = write( slap_daemon[t].sd_dpfd, (pfd), size ); \
+ if ( rc != size ) { \
+ int saved_errno = errno; \
+ Debug( LDAP_DEBUG_ANY, "daemon: " SLAP_EVENT_FNAME ": " \
+ "%s fd=%d failed errno=%d\n", \
+ (what), (s), saved_errno ); \
+ if ( (shdn) ) { \
+ slapd_shutdown = 2; \
+ } \
+ } \
+} while (0)
+
+# define SLAP_DEVPOLL_SOCK_SET(t,s, mode) do { \
+ Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_SET_%s(%d)=%d\n", \
+ (mode) == POLLIN ? "READ" : "WRITE", (s), \
+ ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) != (mode) ) ); \
+ if ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) != (mode) ) { \
+ struct pollfd pfd; \
+ SLAP_DEVPOLL_SOCK_EV(t,(s)) |= (mode); \
+ pfd.fd = SLAP_DEVPOLL_SOCK_FD(t,(s)); \
+ pfd.events = /* (mode) */ SLAP_DEVPOLL_SOCK_EV(t,(s)); \
+ SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &pfd, 1, "SET", 0); \
+ } \
+} while (0)
+
+# define SLAP_DEVPOLL_SOCK_CLR(t,s, mode) do { \
+ Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_CLR_%s(%d)=%d\n", \
+ (mode) == POLLIN ? "READ" : "WRITE", (s), \
+ ( (SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) == (mode) ) ); \
+ if ((SLAP_DEVPOLL_SOCK_EV(t,(s)) & (mode)) == (mode) ) { \
+ struct pollfd pfd[2]; \
+ SLAP_DEVPOLL_SOCK_EV(t,(s)) &= ~(mode); \
+ pfd[0].fd = SLAP_DEVPOLL_SOCK_FD(t,(s)); \
+ pfd[0].events = POLLREMOVE; \
+ pfd[1] = SLAP_DEVPOLL_SOCK_EP(t,(s)); \
+ SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &pfd[0], 2, "CLR", 0); \
+ } \
+} while (0)
+
+# define SLAP_SOCK_SET_READ(t,s) SLAP_DEVPOLL_SOCK_SET(t,s, POLLIN)
+# define SLAP_SOCK_SET_WRITE(t,s) SLAP_DEVPOLL_SOCK_SET(t,s, POLLOUT)
+
+# define SLAP_SOCK_CLR_READ(t,s) SLAP_DEVPOLL_SOCK_CLR(t,(s), POLLIN)
+# define SLAP_SOCK_CLR_WRITE(t,s) SLAP_DEVPOLL_SOCK_CLR(t,(s), POLLOUT)
+
+# define SLAP_SOCK_SET_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] = 1 )
+# define SLAP_SOCK_CLR_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] = 0 )
+# define SLAP_SOCK_IS_SUSPEND(t,s) \
+ ( slap_daemon[t].sd_suspend[SLAP_DEVPOLL_SOCK_IX(t,(s))] == 1 )
+
+# define SLAP_DEVPOLL_EVENT_CLR(i, mode) (revents[(i)].events &= ~(mode))
+
+# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds
+
+/* If a Listener address is provided, store that in the sd_l array.
+ * If we can't do this add, the system is out of resources and we
+ * need to shutdown.
+ */
+# define SLAP_SOCK_ADD(t, s, l) do { \
+ Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_ADD(%d, %p)\n", (s), (l) ); \
+ SLAP_DEVPOLL_SOCK_IX(t,(s)) = slap_daemon[t].sd_nfds; \
+ SLAP_DEVPOLL_SOCK_LX(t,(s)) = (l); \
+ SLAP_DEVPOLL_SOCK_FD(t,(s)) = (s); \
+ SLAP_DEVPOLL_SOCK_EV(t,(s)) = POLLIN; \
+ SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &SLAP_DEVPOLL_SOCK_EP(t, (s)), 1, "ADD", 1); \
+ slap_daemon[t].sd_nfds++; \
+} while (0)
+
+# define SLAP_DEVPOLL_EV_LISTENER(ptr) ((ptr) != NULL)
+
+# define SLAP_SOCK_DEL(t,s) do { \
+ int fd, index = SLAP_DEVPOLL_SOCK_IX(t,(s)); \
+ Debug( LDAP_DEBUG_CONNS, "SLAP_SOCK_DEL(%d)\n", (s) ); \
+ if ( index < 0 ) break; \
+ if ( index < slap_daemon[t].sd_nfds - 1 ) { \
+ struct pollfd pfd = slap_daemon[t].sd_pollfd[index]; \
+ fd = slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].fd; \
+ slap_daemon[t].sd_pollfd[index] = slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1]; \
+ slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1] = pfd; \
+ slap_daemon[t].sd_index[fd] = index; \
+ } \
+ slap_daemon[t].sd_index[(s)] = -1; \
+ slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].events = POLLREMOVE; \
+ SLAP_DEVPOLL_WRITE_POLLFD(t,(s), &slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1], 1, "DEL", 0); \
+ slap_daemon[t].sd_pollfd[slap_daemon[t].sd_nfds - 1].events = 0; \
+ slap_daemon[t].sd_nfds--; \
+} while (0)
+
+# define SLAP_EVENT_CLR_READ(i) SLAP_DEVPOLL_EVENT_CLR((i), POLLIN)
+# define SLAP_EVENT_CLR_WRITE(i) SLAP_DEVPOLL_EVENT_CLR((i), POLLOUT)
+
+# define SLAP_DEVPOLL_EVENT_CHK(i, mode) (revents[(i)].events & (mode))
+
+# define SLAP_EVENT_FD(t,i) (revents[(i)].fd)
+
+# define SLAP_EVENT_IS_READ(i) SLAP_DEVPOLL_EVENT_CHK((i), POLLIN)
+# define SLAP_EVENT_IS_WRITE(i) SLAP_DEVPOLL_EVENT_CHK((i), POLLOUT)
+# define SLAP_EVENT_IS_LISTENER(t,i) SLAP_DEVPOLL_EV_LISTENER(SLAP_DEVPOLL_SOCK_LX(t, SLAP_EVENT_FD(t,(i))))
+# define SLAP_EVENT_LISTENER(t,i) SLAP_DEVPOLL_SOCK_LX(t, SLAP_EVENT_FD(t,(i)))
+
+# define SLAP_SOCK_DESTROY(t) do { \
+ if ( slap_daemon[t].sd_pollfd != NULL ) { \
+ ch_free( slap_daemon[t].sd_pollfd ); \
+ slap_daemon[t].sd_pollfd = NULL; \
+ slap_daemon[t].sd_index = NULL; \
+ slap_daemon[t].sd_l = NULL; \
+ close( slap_daemon[t].sd_dpfd ); \
+ } \
+} while ( 0 )
+
+# define SLAP_SOCK_INIT(t) do { \
+ slap_daemon[t].sd_pollfd = ch_calloc( 1, \
+ ( sizeof(struct pollfd) * 2 \
+ + sizeof( int ) \
+ + sizeof( Listener * ) ) * dtblsize ); \
+ slap_daemon[t].sd_index = (int *)&slap_daemon[t].sd_pollfd[ 2 * dtblsize ]; \
+ slap_daemon[t].sd_l = (Listener **)&slap_daemon[t].sd_index[ dtblsize ]; \
+ slap_daemon[t].sd_dpfd = open( SLAP_EVENT_FNAME, O_RDWR ); \
+ if ( slap_daemon[t].sd_dpfd == -1 ) { \
+ int saved_errno = errno; \
+ Debug( LDAP_DEBUG_ANY, "daemon: " SLAP_EVENT_FNAME ": " \
+ "open(\"" SLAP_EVENT_FNAME "\") failed errno=%d\n", \
+ saved_errno ); \
+ SLAP_SOCK_DESTROY(t); \
+ return -1; \
+ } \
+ for ( i = 0; i < dtblsize; i++ ) { \
+ slap_daemon[t].sd_pollfd[i].fd = -1; \
+ slap_daemon[t].sd_index[i] = -1; \
+ } \
+} while (0)
+
+# define SLAP_SOCK_INIT2()
+
+# define SLAP_EVENT_DECL struct pollfd *revents
+
+# define SLAP_EVENT_INIT(t) do { \
+ revents = &slap_daemon[t].sd_pollfd[ dtblsize ]; \
+} while (0)
+
+# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \
+ struct dvpoll sd_dvpoll; \
+ sd_dvpoll.dp_timeout = (tvp) ? ((tvp)->tv_sec * 1000 + (tvp)->tv_usec / 1000) : -1; \
+ sd_dvpoll.dp_nfds = dtblsize; \
+ sd_dvpoll.dp_fds = revents; \
+ *(nsp) = ioctl( slap_daemon[t].sd_dpfd, DP_POLL, &sd_dvpoll ); \
+} while (0)
+
+#else /* ! kqueue && ! epoll && ! /dev/poll */
+# ifdef HAVE_WINSOCK
+# define SLAP_EVENT_FNAME "WSselect"
+/* Winsock provides a "select" function but its fd_sets are
+ * actually arrays of sockets. Since these sockets are handles
+ * and not a contiguous range of small integers, we manage our
+ * own "fd" table of socket handles and use their indices as
+ * descriptors.
+ *
+ * All of our listener/connection structures use fds; the actual
+ * I/O functions use sockets. The SLAP_FD2SOCK macro in proto-slap.h
+ * handles the mapping.
+ *
+ * Despite the mapping overhead, this is about 45% more efficient
+ * than just using Winsock's select and FD_ISSET directly.
+ *
+ * Unfortunately Winsock's select implementation doesn't scale well
+ * as the number of connections increases. This probably needs to be
+ * rewritten to use the Winsock overlapped/asynchronous I/O functions.
+ */
+# define SLAP_EVENTS_ARE_INDEXED 1
+# define SLAP_EVENT_DECL fd_set readfds, writefds; char *rflags
+# define SLAP_EVENT_INIT(t) do { \
+ int i; \
+ FD_ZERO( &readfds ); \
+ FD_ZERO( &writefds ); \
+ rflags = slap_daemon[t].sd_rflags; \
+ memset( rflags, 0, slap_daemon[t].sd_nfds ); \
+ for ( i=0; i<slap_daemon[t].sd_nfds; i++ ) { \
+ if ( slap_daemon[t].sd_flags[i] & SD_READ ) \
+ FD_SET( slapd_ws_sockets[i], &readfds );\
+ if ( slap_daemon[t].sd_flags[i] & SD_WRITE ) \
+ FD_SET( slapd_ws_sockets[i], &writefds ); \
+ } } while ( 0 )
+
+# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds
+
+# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \
+ int i; \
+ *(nsp) = select( SLAP_EVENT_MAX(t), &readfds, \
+ nwriters > 0 ? &writefds : NULL, NULL, (tvp) ); \
+ for ( i=0; i<readfds.fd_count; i++) { \
+ int fd = slapd_sock2fd(readfds.fd_array[i]); \
+ if ( fd >= 0 ) { \
+ slap_daemon[t].sd_rflags[fd] = SD_READ; \
+ if ( fd >= *(nsp)) *(nsp) = fd+1; \
+ } \
+ } \
+ for ( i=0; i<writefds.fd_count; i++) { \
+ int fd = slapd_sock2fd(writefds.fd_array[i]); \
+ if ( fd >= 0 ) { \
+ slap_daemon[t].sd_rflags[fd] = SD_WRITE; \
+ if ( fd >= *(nsp)) *(nsp) = fd+1; \
+ } \
+ } \
+} while (0)
+
+# define SLAP_EVENT_IS_READ(fd) (rflags[fd] & SD_READ)
+# define SLAP_EVENT_IS_WRITE(fd) (rflags[fd] & SD_WRITE)
+
+# define SLAP_EVENT_CLR_READ(fd) rflags[fd] &= ~SD_READ
+# define SLAP_EVENT_CLR_WRITE(fd) rflags[fd] &= ~SD_WRITE
+
+# define SLAP_SOCK_INIT(t) do { \
+ if (!t) { \
+ ldap_pvt_thread_mutex_init( &slapd_ws_mutex ); \
+ slapd_ws_sockets = ch_malloc( dtblsize * ( sizeof(SOCKET) + 2)); \
+ memset( slapd_ws_sockets, -1, dtblsize * sizeof(SOCKET) ); \
+ } \
+ slap_daemon[t].sd_flags = (char *)(slapd_ws_sockets + dtblsize); \
+ slap_daemon[t].sd_rflags = slap_daemon[t].sd_flags + dtblsize; \
+ memset( slap_daemon[t].sd_flags, 0, dtblsize ); \
+ slapd_ws_sockets[t*2] = wake_sds[t][0]; \
+ slapd_ws_sockets[t*2+1] = wake_sds[t][1]; \
+ wake_sds[t][0] = t*2; \
+ wake_sds[t][1] = t*2+1; \
+ slap_daemon[t].sd_nfds = t*2 + 2; \
+ } while ( 0 )
+
+# define SLAP_SOCK_INIT2()
+
+# define SLAP_SOCK_DESTROY(t) do { \
+ ch_free( slapd_ws_sockets ); slapd_ws_sockets = NULL; \
+ slap_daemon[t].sd_flags = NULL; \
+ slap_daemon[t].sd_rflags = NULL; \
+ ldap_pvt_thread_mutex_destroy( &slapd_ws_mutex ); \
+ } while ( 0 )
+
+# define SLAP_SOCK_IS_ACTIVE(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_ACTIVE )
+# define SLAP_SOCK_IS_READ(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_READ )
+# define SLAP_SOCK_IS_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] & SD_WRITE )
+# define SLAP_SOCK_NOT_ACTIVE(t,fd) (!slap_daemon[t].sd_flags[fd])
+
+# define SLAP_SOCK_SET_READ(t,fd) ( slap_daemon[t].sd_flags[fd] |= SD_READ )
+# define SLAP_SOCK_SET_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] |= SD_WRITE )
+
+# define SLAP_SELECT_ADDTEST(t,s) do { \
+ if ((s) >= slap_daemon[t].sd_nfds) slap_daemon[t].sd_nfds = (s)+1; \
+} while (0)
+
+# define SLAP_SOCK_CLR_READ(t,fd) ( slap_daemon[t].sd_flags[fd] &= ~SD_READ )
+# define SLAP_SOCK_CLR_WRITE(t,fd) ( slap_daemon[t].sd_flags[fd] &= ~SD_WRITE )
+
+# define SLAP_SOCK_ADD(t,s, l) do { \
+ SLAP_SELECT_ADDTEST(t,(s)); \
+ slap_daemon[t].sd_flags[s] = SD_ACTIVE|SD_READ; \
+} while ( 0 )
+
+# define SLAP_SOCK_DEL(t,s) do { \
+ slap_daemon[t].sd_flags[s] = 0; \
+ slapd_sockdel( s ); \
+} while ( 0 )
+
+# else /* !HAVE_WINSOCK */
+
+/**************************************
+ * Use select system call - select(2) *
+ **************************************/
+# define SLAP_EVENT_FNAME "select"
+/* select */
+# define SLAP_EVENTS_ARE_INDEXED 1
+# define SLAP_EVENT_DECL fd_set readfds, writefds
+
+# define SLAP_EVENT_INIT(t) do { \
+ AC_MEMCPY( &readfds, &slap_daemon[t].sd_readers, sizeof(fd_set) ); \
+ if ( nwriters ) { \
+ AC_MEMCPY( &writefds, &slap_daemon[t].sd_writers, sizeof(fd_set) ); \
+ } else { \
+ FD_ZERO( &writefds ); \
+ } \
+} while (0)
+
+# ifdef FD_SETSIZE
+# define SLAP_SELECT_CHK_SETSIZE do { \
+ if (dtblsize > FD_SETSIZE) dtblsize = FD_SETSIZE; \
+} while (0)
+# else /* ! FD_SETSIZE */
+# define SLAP_SELECT_CHK_SETSIZE do { ; } while (0)
+# endif /* ! FD_SETSIZE */
+
+# define SLAP_SOCK_INIT(t) do { \
+ SLAP_SELECT_CHK_SETSIZE; \
+ FD_ZERO(&slap_daemon[t].sd_actives); \
+ FD_ZERO(&slap_daemon[t].sd_readers); \
+ FD_ZERO(&slap_daemon[t].sd_writers); \
+} while (0)
+
+# define SLAP_SOCK_INIT2()
+
+# define SLAP_SOCK_DESTROY(t)
+
+# define SLAP_SOCK_IS_ACTIVE(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_actives)
+# define SLAP_SOCK_IS_READ(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_readers)
+# define SLAP_SOCK_IS_WRITE(t,fd) FD_ISSET((fd), &slap_daemon[t].sd_writers)
+
+# define SLAP_SOCK_NOT_ACTIVE(t,fd) (!SLAP_SOCK_IS_ACTIVE(t,fd) && \
+ !SLAP_SOCK_IS_READ(t,fd) && !SLAP_SOCK_IS_WRITE(t,fd))
+
+# define SLAP_SOCK_SET_READ(t,fd) FD_SET((fd), &slap_daemon[t].sd_readers)
+# define SLAP_SOCK_SET_WRITE(t,fd) FD_SET((fd), &slap_daemon[t].sd_writers)
+
+# define SLAP_EVENT_MAX(t) slap_daemon[t].sd_nfds
+# define SLAP_SELECT_ADDTEST(t,s) do { \
+ if ((s) >= slap_daemon[t].sd_nfds) slap_daemon[t].sd_nfds = (s)+1; \
+} while (0)
+
+# define SLAP_SOCK_CLR_READ(t,fd) FD_CLR((fd), &slap_daemon[t].sd_readers)
+# define SLAP_SOCK_CLR_WRITE(t,fd) FD_CLR((fd), &slap_daemon[t].sd_writers)
+
+# define SLAP_SOCK_ADD(t,s, l) do { \
+ SLAP_SELECT_ADDTEST(t,(s)); \
+ FD_SET((s), &slap_daemon[t].sd_actives); \
+ FD_SET((s), &slap_daemon[t].sd_readers); \
+} while (0)
+
+# define SLAP_SOCK_DEL(t,s) do { \
+ FD_CLR((s), &slap_daemon[t].sd_actives); \
+ FD_CLR((s), &slap_daemon[t].sd_readers); \
+ FD_CLR((s), &slap_daemon[t].sd_writers); \
+} while (0)
+
+# define SLAP_EVENT_IS_READ(fd) FD_ISSET((fd), &readfds)
+# define SLAP_EVENT_IS_WRITE(fd) FD_ISSET((fd), &writefds)
+
+# define SLAP_EVENT_CLR_READ(fd) FD_CLR((fd), &readfds)
+# define SLAP_EVENT_CLR_WRITE(fd) FD_CLR((fd), &writefds)
+
+# define SLAP_EVENT_WAIT(t, tvp, nsp) do { \
+ *(nsp) = select( SLAP_EVENT_MAX(t), &readfds, \
+ nwriters > 0 ? &writefds : NULL, NULL, (tvp) ); \
+} while (0)
+# endif /* !HAVE_WINSOCK */
+#endif /* ! kqueue && ! epoll && ! /dev/poll */
+
+#ifdef HAVE_SLP
+/*
+ * SLP related functions
+ */
+#include <slp.h>
+
+#define LDAP_SRVTYPE_PREFIX "service:ldap://"
+#define LDAPS_SRVTYPE_PREFIX "service:ldaps://"
+static char** slapd_srvurls = NULL;
+static SLPHandle slapd_hslp = 0;
+int slapd_register_slp = 0;
+const char *slapd_slp_attrs = NULL;
+
+static SLPError slapd_slp_cookie;
+
+static void
+slapd_slp_init( const char* urls )
+{
+ int i;
+ SLPError err;
+
+ slapd_srvurls = ldap_str2charray( urls, " " );
+
+ if ( slapd_srvurls == NULL ) return;
+
+ /* find and expand INADDR_ANY URLs */
+ for ( i = 0; slapd_srvurls[i] != NULL; i++ ) {
+ if ( strcmp( slapd_srvurls[i], "ldap:///" ) == 0 ) {
+ slapd_srvurls[i] = (char *) ch_realloc( slapd_srvurls[i],
+ global_host_bv.bv_len +
+ sizeof( LDAP_SRVTYPE_PREFIX ) );
+ strcpy( lutil_strcopy(slapd_srvurls[i],
+ LDAP_SRVTYPE_PREFIX ), global_host_bv.bv_val );
+ } else if ( strcmp( slapd_srvurls[i], "ldaps:///" ) == 0 ) {
+ slapd_srvurls[i] = (char *) ch_realloc( slapd_srvurls[i],
+ global_host_bv.bv_len +
+ sizeof( LDAPS_SRVTYPE_PREFIX ) );
+ strcpy( lutil_strcopy(slapd_srvurls[i],
+ LDAPS_SRVTYPE_PREFIX ), global_host_bv.bv_val );
+ }
+ }
+
+ /* open the SLP handle */
+ err = SLPOpen( "en", 0, &slapd_hslp );
+
+ if ( err != SLP_OK ) {
+ Debug( LDAP_DEBUG_CONNS, "daemon: SLPOpen() failed with %ld\n",
+ (long)err );
+ }
+}
+
+static void
+slapd_slp_deinit( void )
+{
+ if ( slapd_srvurls == NULL ) return;
+
+ ldap_charray_free( slapd_srvurls );
+ slapd_srvurls = NULL;
+
+ /* close the SLP handle */
+ SLPClose( slapd_hslp );
+}
+
+static void
+slapd_slp_regreport(
+ SLPHandle hslp,
+ SLPError errcode,
+ void *cookie )
+{
+ /* return the error code in the cookie */
+ *(SLPError*)cookie = errcode;
+}
+
+static void
+slapd_slp_reg()
+{
+ int i;
+ SLPError err;
+
+ if ( slapd_srvurls == NULL ) return;
+
+ for ( i = 0; slapd_srvurls[i] != NULL; i++ ) {
+ if ( strncmp( slapd_srvurls[i], LDAP_SRVTYPE_PREFIX,
+ sizeof( LDAP_SRVTYPE_PREFIX ) - 1 ) == 0 ||
+ strncmp( slapd_srvurls[i], LDAPS_SRVTYPE_PREFIX,
+ sizeof( LDAPS_SRVTYPE_PREFIX ) - 1 ) == 0 )
+ {
+ err = SLPReg( slapd_hslp,
+ slapd_srvurls[i],
+ SLP_LIFETIME_MAXIMUM,
+ "ldap",
+ (slapd_slp_attrs) ? slapd_slp_attrs : "",
+ SLP_TRUE,
+ slapd_slp_regreport,
+ &slapd_slp_cookie );
+
+ if ( err != SLP_OK || slapd_slp_cookie != SLP_OK ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: SLPReg(%s) failed with %ld, cookie = %ld\n",
+ slapd_srvurls[i], (long)err, (long)slapd_slp_cookie );
+ }
+ }
+ }
+}
+
+static void
+slapd_slp_dereg( void )
+{
+ int i;
+ SLPError err;
+
+ if ( slapd_srvurls == NULL ) return;
+
+ for ( i = 0; slapd_srvurls[i] != NULL; i++ ) {
+ err = SLPDereg( slapd_hslp,
+ slapd_srvurls[i],
+ slapd_slp_regreport,
+ &slapd_slp_cookie );
+
+ if ( err != SLP_OK || slapd_slp_cookie != SLP_OK ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: SLPDereg(%s) failed with %ld, cookie = %ld\n",
+ slapd_srvurls[i], (long)err, (long)slapd_slp_cookie );
+ }
+ }
+}
+#endif /* HAVE_SLP */
+
+#ifdef HAVE_WINSOCK
+/* Manage the descriptor to socket table */
+ber_socket_t
+slapd_socknew( ber_socket_t s )
+{
+ ber_socket_t i;
+ ldap_pvt_thread_mutex_lock( &slapd_ws_mutex );
+ for ( i = 0; i < dtblsize && slapd_ws_sockets[i] != INVALID_SOCKET; i++ );
+ if ( i == dtblsize ) {
+ WSASetLastError( WSAEMFILE );
+ } else {
+ slapd_ws_sockets[i] = s;
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_ws_mutex );
+ return i;
+}
+
+void
+slapd_sockdel( ber_socket_t s )
+{
+ ldap_pvt_thread_mutex_lock( &slapd_ws_mutex );
+ slapd_ws_sockets[s] = INVALID_SOCKET;
+ ldap_pvt_thread_mutex_unlock( &slapd_ws_mutex );
+}
+
+ber_socket_t
+slapd_sock2fd( ber_socket_t s )
+{
+ ber_socket_t i;
+ for ( i=0; i<dtblsize && slapd_ws_sockets[i] != s; i++);
+ if ( i == dtblsize )
+ i = -1;
+ return i;
+}
+#endif
+
+#ifdef DEBUG_CLOSE
+/* Was used to find a bug causing slapd's descriptors to be closed
+ * out from under it. Tracked it down to a long-standing (from 2009)
+ * bug in Heimdal https://github.com/heimdal/heimdal/issues/431 .
+ * Leaving this here for future use, if necessary.
+ */
+#include <dlfcn.h>
+#ifndef RTLD_NEXT
+#define RTLD_NEXT (void *)-1L
+#endif
+static char *newconns;
+typedef int (closefunc)(int fd);
+static closefunc *close_ptr;
+int close( int s )
+{
+ if (newconns) {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: close(%d)\n", s );
+ if (s >= 0 && s < dtblsize && newconns[s])
+ assert(newconns[s] == 2);
+ }
+ return close_ptr ? close_ptr(s) : -1;
+}
+
+void slapd_debug_close()
+{
+ if (dtblsize)
+ newconns = ch_calloc(1, dtblsize);
+ close_ptr = dlsym(RTLD_NEXT, "close");
+}
+
+void slapd_set_close(int fd)
+{
+ newconns[fd] = 3;
+}
+#define SETUP_CLOSE() slapd_debug_close()
+#define SET_CLOSE(fd) slapd_set_close(fd)
+#define CLR_CLOSE(fd) if (newconns[fd]) newconns[fd]--
+#else
+#define SETUP_CLOSE(fd)
+#define SET_CLOSE(fd)
+#define CLR_CLOSE(fd)
+#endif
+
+/*
+ * Add a descriptor to daemon control
+ *
+ * If isactive, the descriptor is a live server session and is subject
+ * to idletimeout control. Otherwise, the descriptor is a passive
+ * listener or an outbound client session, and not subject to
+ * idletimeout. The underlying event handler may record the Listener
+ * argument to differentiate Listener's from real sessions.
+ */
+static void
+slapd_add( ber_socket_t s, int isactive, Listener *sl, int id )
+{
+ if (id < 0)
+ id = DAEMON_ID(s);
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ assert( SLAP_SOCK_NOT_ACTIVE(id, s) );
+
+ if ( isactive ) slap_daemon[id].sd_nactives++;
+
+ SLAP_SOCK_ADD(id, s, sl);
+
+ Debug( LDAP_DEBUG_CONNS, "daemon: added %ldr%s listener=%p\n",
+ (long) s, isactive ? " (active)" : "", (void *)sl );
+
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+
+ WAKE_LISTENER(id,1);
+}
+
+/*
+ * Remove the descriptor from daemon control
+ */
+void
+slapd_remove(
+ ber_socket_t s,
+ Sockbuf *sb,
+ int wasactive,
+ int wake,
+ int locked )
+{
+ int waswriter;
+ int wasreader;
+ int id = DAEMON_ID(s);
+
+ if ( !locked )
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ assert( SLAP_SOCK_IS_ACTIVE( id, s ));
+
+ if ( wasactive ) slap_daemon[id].sd_nactives--;
+
+ waswriter = SLAP_SOCK_IS_WRITE(id, s);
+ wasreader = SLAP_SOCK_IS_READ(id, s);
+
+ Debug( LDAP_DEBUG_CONNS, "daemon: removing %ld%s%s\n",
+ (long) s,
+ wasreader ? "r" : "",
+ waswriter ? "w" : "" );
+
+ if ( waswriter ) slap_daemon[id].sd_nwriters--;
+
+ SLAP_SOCK_DEL(id, s);
+ CLR_CLOSE(s);
+
+ if ( sb )
+ ber_sockbuf_free(sb);
+
+ /* If we ran out of file descriptors, we dropped a listener from
+ * the select() loop. Now that we're removing a session from our
+ * control, we can try to resume a dropped listener to use.
+ */
+ ldap_pvt_thread_mutex_lock( &emfile_mutex );
+ if ( emfile && listening ) {
+ int i;
+ for ( i = 0; slap_listeners[i] != NULL; i++ ) {
+ Listener *lr = slap_listeners[i];
+
+ if ( lr->sl_sd == AC_SOCKET_INVALID ) continue;
+ if ( lr->sl_sd == s ) continue;
+ if ( lr->sl_mute ) {
+ lr->sl_mute = 0;
+ emfile--;
+ if ( DAEMON_ID(lr->sl_sd) != id )
+ WAKE_LISTENER(DAEMON_ID(lr->sl_sd), wake);
+ break;
+ }
+ }
+ /* Walked the entire list without enabling anything; emfile
+ * counter is stale. Reset it.
+ */
+ if ( slap_listeners[i] == NULL ) emfile = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &emfile_mutex );
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+ WAKE_LISTENER(id, wake || slapd_gentle_shutdown == 2);
+}
+
+void
+slapd_clr_write( ber_socket_t s, int wake )
+{
+ int id = DAEMON_ID(s);
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ if ( SLAP_SOCK_IS_WRITE( id, s )) {
+ assert( SLAP_SOCK_IS_ACTIVE( id, s ));
+
+ SLAP_SOCK_CLR_WRITE( id, s );
+ slap_daemon[id].sd_nwriters--;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+ WAKE_LISTENER(id,wake);
+}
+
+void
+slapd_set_write( ber_socket_t s, int wake )
+{
+ int id = DAEMON_ID(s);
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ assert( SLAP_SOCK_IS_ACTIVE( id, s ));
+
+ if ( !SLAP_SOCK_IS_WRITE( id, s )) {
+ SLAP_SOCK_SET_WRITE( id, s );
+ slap_daemon[id].sd_nwriters++;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+ WAKE_LISTENER(id,wake);
+}
+
+int
+slapd_clr_read( ber_socket_t s, int wake )
+{
+ int rc = 1;
+ int id = DAEMON_ID(s);
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ if ( SLAP_SOCK_IS_ACTIVE( id, s )) {
+ SLAP_SOCK_CLR_READ( id, s );
+ rc = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+ if ( !rc )
+ WAKE_LISTENER(id,wake);
+ return rc;
+}
+
+void
+slapd_set_read( ber_socket_t s, int wake )
+{
+ int do_wake = 1;
+ int id = DAEMON_ID(s);
+ ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex );
+
+ if( SLAP_SOCK_IS_ACTIVE( id, s ) && !SLAP_SOCK_IS_READ( id, s )) {
+ SLAP_SOCK_SET_READ( id, s );
+ } else {
+ do_wake = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex );
+ if ( do_wake )
+ WAKE_LISTENER(id,wake);
+}
+
+static void
+slapd_close( ber_socket_t s )
+{
+ Debug( LDAP_DEBUG_CONNS, "daemon: closing %ld\n",
+ (long) s );
+ CLR_CLOSE( SLAP_FD2SOCK(s) );
+ tcp_close( SLAP_FD2SOCK(s) );
+#ifdef HAVE_WINSOCK
+ slapd_sockdel( s );
+#endif
+}
+
+void
+slapd_shutsock( ber_socket_t s )
+{
+ Debug( LDAP_DEBUG_CONNS, "daemon: shutdown socket %ld\n",
+ (long) s );
+ shutdown( SLAP_FD2SOCK(s), 2 );
+}
+
+static void
+slap_free_listener_addresses( struct sockaddr **sal )
+{
+ struct sockaddr **sap;
+ if (sal == NULL) return;
+ for (sap = sal; *sap != NULL; sap++) ch_free(*sap);
+ ch_free(sal);
+}
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+static int
+get_url_perms(
+ char **exts,
+ mode_t *perms,
+ int *crit )
+{
+ int i;
+
+ assert( exts != NULL );
+ assert( perms != NULL );
+ assert( crit != NULL );
+
+ *crit = 0;
+ for ( i = 0; exts[ i ]; i++ ) {
+ char *type = exts[ i ];
+ int c = 0;
+
+ if ( type[ 0 ] == '!' ) {
+ c = 1;
+ type++;
+ }
+
+ if ( strncasecmp( type, LDAPI_MOD_URLEXT "=",
+ sizeof(LDAPI_MOD_URLEXT "=") - 1 ) == 0 )
+ {
+ char *value = type + ( sizeof(LDAPI_MOD_URLEXT "=") - 1 );
+ mode_t p = 0;
+ int j;
+
+ switch (strlen(value)) {
+ case 4:
+ /* skip leading '0' */
+ if ( value[ 0 ] != '0' ) return LDAP_OTHER;
+ value++;
+
+ case 3:
+ for ( j = 0; j < 3; j++) {
+ int v;
+
+ v = value[ j ] - '0';
+
+ if ( v < 0 || v > 7 ) return LDAP_OTHER;
+
+ p |= v << 3*(2-j);
+ }
+ break;
+
+ case 10:
+ for ( j = 1; j < 10; j++ ) {
+ static mode_t m[] = { 0,
+ S_IRUSR, S_IWUSR, S_IXUSR,
+ S_IRGRP, S_IWGRP, S_IXGRP,
+ S_IROTH, S_IWOTH, S_IXOTH
+ };
+ static const char c[] = "-rwxrwxrwx";
+
+ if ( value[ j ] == c[ j ] ) {
+ p |= m[ j ];
+
+ } else if ( value[ j ] != '-' ) {
+ return LDAP_OTHER;
+ }
+ }
+ break;
+
+ default:
+ return LDAP_OTHER;
+ }
+
+ *crit = c;
+ *perms = p;
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_OTHER;
+}
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+/* port = 0 indicates AF_LOCAL */
+static int
+slap_get_listener_addresses(
+ const char *host,
+ unsigned short port,
+ struct sockaddr ***sal )
+{
+ struct sockaddr **sap;
+
+#ifdef LDAP_PF_LOCAL
+ if ( port == 0 ) {
+ sap = *sal = ch_malloc(2 * sizeof(void *));
+
+ *sap = ch_malloc(sizeof(struct sockaddr_un));
+ sap[1] = NULL;
+
+ if ( strlen(host) >
+ (sizeof(((struct sockaddr_un *)*sap)->sun_path) - 1) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: domain socket path (%s) too long in URL",
+ host );
+ goto errexit;
+ }
+
+ (void)memset( (void *)*sap, '\0', sizeof(struct sockaddr_un) );
+ (*sap)->sa_family = AF_LOCAL;
+ strcpy( ((struct sockaddr_un *)*sap)->sun_path, host );
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef HAVE_GETADDRINFO
+ struct addrinfo hints, *res, *sai;
+ int n, err;
+ char serv[7];
+
+ memset( &hints, '\0', sizeof(hints) );
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = slap_inet4or6;
+ snprintf(serv, sizeof serv, "%d", port);
+
+ if ( (err = getaddrinfo(host, serv, &hints, &res)) ) {
+ Debug( LDAP_DEBUG_ANY, "daemon: getaddrinfo() failed: %s\n",
+ AC_GAI_STRERROR(err) );
+ return -1;
+ }
+
+ sai = res;
+ for (n=2; (sai = sai->ai_next) != NULL; n++) {
+ /* EMPTY */ ;
+ }
+ sap = *sal = ch_calloc(n, sizeof(void *));
+ *sap = NULL;
+
+ for ( sai=res; sai; sai=sai->ai_next ) {
+ if( sai->ai_addr == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "slap_get_listener_addresses: "
+ "getaddrinfo ai_addr is NULL?\n" );
+ freeaddrinfo(res);
+ goto errexit;
+ }
+
+ switch (sai->ai_family) {
+# ifdef LDAP_PF_INET6
+ case AF_INET6:
+ *sap = ch_malloc(sizeof(struct sockaddr_in6));
+ *(struct sockaddr_in6 *)*sap =
+ *((struct sockaddr_in6 *)sai->ai_addr);
+ break;
+# endif /* LDAP_PF_INET6 */
+ case AF_INET:
+ *sap = ch_malloc(sizeof(struct sockaddr_in));
+ *(struct sockaddr_in *)*sap =
+ *((struct sockaddr_in *)sai->ai_addr);
+ break;
+ default:
+ *sap = NULL;
+ break;
+ }
+
+ if (*sap != NULL) {
+ (*sap)->sa_family = sai->ai_family;
+ sap++;
+ *sap = NULL;
+ }
+ }
+
+ freeaddrinfo(res);
+
+#else /* ! HAVE_GETADDRINFO */
+ int i, n = 1;
+ struct in_addr in;
+ struct hostent *he = NULL;
+
+ if ( host == NULL ) {
+ in.s_addr = htonl(INADDR_ANY);
+
+ } else if ( !inet_aton( host, &in ) ) {
+ he = gethostbyname( host );
+ if( he == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: invalid host %s", host );
+ return -1;
+ }
+ for (n = 0; he->h_addr_list[n]; n++) /* empty */;
+ }
+
+ sap = *sal = ch_malloc((n+1) * sizeof(void *));
+
+ for ( i = 0; i<n; i++ ) {
+ sap[i] = ch_calloc(1, sizeof(struct sockaddr_in));
+ sap[i]->sa_family = AF_INET;
+ ((struct sockaddr_in *)sap[i])->sin_port = htons(port);
+ AC_MEMCPY( &((struct sockaddr_in *)sap[i])->sin_addr,
+ he ? (struct in_addr *)he->h_addr_list[i] : &in,
+ sizeof(struct in_addr) );
+ }
+ sap[i] = NULL;
+#endif /* ! HAVE_GETADDRINFO */
+ }
+
+ return 0;
+
+errexit:
+ slap_free_listener_addresses(*sal);
+ return -1;
+}
+
+static int
+slap_open_listener(
+ const char* url,
+ int *listeners,
+ int *cur )
+{
+ int num, tmp, rc;
+ Listener l;
+ Listener *li;
+ LDAPURLDesc *lud;
+ unsigned short port;
+ int err, addrlen = 0;
+ struct sockaddr **sal = NULL, **psal;
+ int socktype = SOCK_STREAM; /* default to COTS */
+ ber_socket_t s;
+ char ebuf[128];
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+ /*
+ * use safe defaults
+ */
+ int crit = 1;
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+ rc = ldap_url_parse( url, &lud );
+
+ if( rc != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: listen URL \"%s\" parse error=%d\n",
+ url, rc );
+ return rc;
+ }
+
+ l.sl_url.bv_val = NULL;
+ l.sl_mute = 0;
+ l.sl_busy = 0;
+
+#ifndef HAVE_TLS
+ if( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) {
+ Debug( LDAP_DEBUG_ANY, "daemon: TLS not supported (%s)\n",
+ url );
+ ldap_free_urldesc( lud );
+ return -1;
+ }
+
+ if(! lud->lud_port ) lud->lud_port = LDAP_PORT;
+
+#else /* HAVE_TLS */
+ l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
+
+ if(! lud->lud_port ) {
+ lud->lud_port = l.sl_is_tls ? LDAPS_PORT : LDAP_PORT;
+ }
+#endif /* HAVE_TLS */
+
+ l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
+
+#ifdef LDAP_TCP_BUFFER
+ l.sl_tcp_rmem = 0;
+ l.sl_tcp_wmem = 0;
+#endif /* LDAP_TCP_BUFFER */
+
+ port = (unsigned short) lud->lud_port;
+
+ tmp = ldap_pvt_url_scheme2proto(lud->lud_scheme);
+ if ( tmp == LDAP_PROTO_IPC ) {
+#ifdef LDAP_PF_LOCAL
+ if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) {
+ err = slap_get_listener_addresses(LDAPI_SOCK, 0, &sal);
+ } else {
+ err = slap_get_listener_addresses(lud->lud_host, 0, &sal);
+ }
+#else /* ! LDAP_PF_LOCAL */
+
+ Debug( LDAP_DEBUG_ANY, "daemon: URL scheme not supported: %s",
+ url );
+ ldap_free_urldesc( lud );
+ return -1;
+#endif /* ! LDAP_PF_LOCAL */
+ } else {
+ if( lud->lud_host == NULL || lud->lud_host[0] == '\0'
+ || strcmp(lud->lud_host, "*") == 0 )
+ {
+ err = slap_get_listener_addresses(NULL, port, &sal);
+ } else {
+ err = slap_get_listener_addresses(lud->lud_host, port, &sal);
+ }
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ l.sl_is_udp = ( tmp == LDAP_PROTO_UDP );
+#endif /* LDAP_CONNECTIONLESS */
+
+#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
+ if ( lud->lud_exts ) {
+ err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit );
+ } else {
+ l.sl_perms = S_IRWXU | S_IRWXO;
+ }
+#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+
+ if ( lud->lud_dn && lud->lud_dn[0] ) {
+ sprintf( (char *)url, "%s://%s/", lud->lud_scheme, lud->lud_host );
+ Debug( LDAP_DEBUG_ANY, "daemon: listener URL %s<junk> DN must be absent (%s)\n",
+ url, lud->lud_dn );
+ ldap_free_urldesc( lud );
+ return -1;
+ }
+
+ ldap_free_urldesc( lud );
+ if ( err ) {
+ slap_free_listener_addresses(sal);
+ return -1;
+ }
+
+ /* If we got more than one address returned, we need to make space
+ * for it in the slap_listeners array.
+ */
+ for ( num=0; sal[num]; num++ ) /* empty */;
+ if ( num > 1 ) {
+ *listeners += num-1;
+ slap_listeners = ch_realloc( slap_listeners,
+ (*listeners + 1) * sizeof(Listener *) );
+ }
+
+ psal = sal;
+ while ( *sal != NULL ) {
+ char *af;
+ switch( (*sal)->sa_family ) {
+ case AF_INET:
+ af = "IPv4";
+ break;
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+ af = "IPv6";
+ break;
+#endif /* LDAP_PF_INET6 */
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+ af = "Local";
+ break;
+#endif /* LDAP_PF_LOCAL */
+ default:
+ sal++;
+ continue;
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if( l.sl_is_udp ) socktype = SOCK_DGRAM;
+#endif /* LDAP_CONNECTIONLESS */
+
+ s = socket( (*sal)->sa_family, socktype, 0);
+ if ( s == AC_SOCKET_INVALID ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: %s socket() failed errno=%d (%s)\n",
+ af, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ sal++;
+ continue;
+ }
+ l.sl_sd = SLAP_SOCKNEW( s );
+
+ if ( l.sl_sd >= dtblsize ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: listener descriptor %ld is too great %ld\n",
+ (long) l.sl_sd, (long) dtblsize );
+ tcp_close( s );
+ sal++;
+ continue;
+ }
+
+#ifdef LDAP_PF_LOCAL
+ if ( (*sal)->sa_family == AF_LOCAL ) {
+ unlink( ((struct sockaddr_un *)*sal)->sun_path );
+ } else
+#endif /* LDAP_PF_LOCAL */
+ {
+#ifdef SO_REUSEADDR
+ /* enable address reuse */
+ tmp = 1;
+ rc = setsockopt( s, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "slapd(%ld): "
+ "setsockopt(SO_REUSEADDR) failed errno=%d (%s)\n",
+ (long) l.sl_sd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+#endif /* SO_REUSEADDR */
+ }
+
+ switch( (*sal)->sa_family ) {
+ case AF_INET:
+ addrlen = sizeof(struct sockaddr_in);
+ break;
+#ifdef LDAP_PF_INET6
+ case AF_INET6:
+#ifdef IPV6_V6ONLY
+ /* Try to use IPv6 sockets for IPv6 only */
+ tmp = 1;
+ rc = setsockopt( s , IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *) &tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY, "slapd(%ld): "
+ "setsockopt(IPV6_V6ONLY) failed errno=%d (%s)\n",
+ (long) l.sl_sd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+#endif /* IPV6_V6ONLY */
+ addrlen = sizeof(struct sockaddr_in6);
+ break;
+#endif /* LDAP_PF_INET6 */
+
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+#ifdef LOCAL_CREDS
+ {
+ int one = 1;
+ setsockopt( s, 0, LOCAL_CREDS, &one, sizeof( one ) );
+ }
+#endif /* LOCAL_CREDS */
+
+ addrlen = sizeof( struct sockaddr_un );
+ break;
+#endif /* LDAP_PF_LOCAL */
+ }
+
+#ifdef LDAP_PF_LOCAL
+ /* create socket with all permissions set for those systems
+ * that honor permissions on sockets (e.g. Linux); typically,
+ * only write is required. To exploit filesystem permissions,
+ * place the socket in a directory and use directory's
+ * permissions. Need write perms to the directory to
+ * create/unlink the socket; likely need exec perms to access
+ * the socket (ITS#4709) */
+ {
+ mode_t old_umask = 0;
+
+ if ( (*sal)->sa_family == AF_LOCAL ) {
+ old_umask = umask( 0 );
+ }
+#endif /* LDAP_PF_LOCAL */
+ rc = bind( s, *sal, addrlen );
+#ifdef LDAP_PF_LOCAL
+ if ( old_umask != 0 ) {
+ umask( old_umask );
+ }
+ }
+#endif /* LDAP_PF_LOCAL */
+ if ( rc ) {
+ err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: bind(%ld) failed errno=%d (%s)\n",
+ (long)l.sl_sd, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ tcp_close( s );
+ sal++;
+ continue;
+ }
+
+ switch ( (*sal)->sa_family ) {
+#ifdef LDAP_PF_LOCAL
+ case AF_LOCAL: {
+ char *path = ((struct sockaddr_un *)*sal)->sun_path;
+ l.sl_name.bv_len = strlen(path) + STRLENOF("PATH=");
+ l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len + 1 );
+ snprintf( l.sl_name.bv_val, l.sl_name.bv_len + 1,
+ "PATH=%s", path );
+ } break;
+#endif /* LDAP_PF_LOCAL */
+
+ case AF_INET: {
+ char addr[INET_ADDRSTRLEN];
+ const char *s;
+#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
+ s = inet_ntop( AF_INET, &((struct sockaddr_in *)*sal)->sin_addr,
+ addr, sizeof(addr) );
+#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ s = inet_ntoa( ((struct sockaddr_in *) *sal)->sin_addr );
+#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */
+ if (!s) s = SLAP_STRING_UNKNOWN;
+ port = ntohs( ((struct sockaddr_in *)*sal) ->sin_port );
+ l.sl_name.bv_val =
+ ch_malloc( sizeof("IP=255.255.255.255:65535") );
+ snprintf( l.sl_name.bv_val, sizeof("IP=255.255.255.255:65535"),
+ "IP=%s:%d", s, port );
+ l.sl_name.bv_len = strlen( l.sl_name.bv_val );
+ } break;
+
+#ifdef LDAP_PF_INET6
+ case AF_INET6: {
+ char addr[INET6_ADDRSTRLEN];
+ const char *s;
+ s = inet_ntop( AF_INET6, &((struct sockaddr_in6 *)*sal)->sin6_addr,
+ addr, sizeof addr);
+ if (!s) s = SLAP_STRING_UNKNOWN;
+ port = ntohs( ((struct sockaddr_in6 *)*sal)->sin6_port );
+ l.sl_name.bv_len = strlen(s) + sizeof("IP=[]:65535");
+ l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len );
+ snprintf( l.sl_name.bv_val, l.sl_name.bv_len, "IP=[%s]:%d",
+ s, port );
+ l.sl_name.bv_len = strlen( l.sl_name.bv_val );
+ } break;
+#endif /* LDAP_PF_INET6 */
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "daemon: unsupported address family (%d)\n",
+ (int) (*sal)->sa_family );
+ break;
+ }
+
+ AC_MEMCPY(&l.sl_sa, *sal, addrlen);
+ ber_str2bv( url, 0, 1, &l.sl_url);
+ li = ch_malloc( sizeof( Listener ) );
+ *li = l;
+ slap_listeners[*cur] = li;
+ (*cur)++;
+ sal++;
+ }
+
+ slap_free_listener_addresses(psal);
+
+ if ( l.sl_url.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_open_listener: failed on %s\n", url );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "daemon: listener initialized %s\n",
+ l.sl_url.bv_val );
+ return 0;
+}
+
+static int sockinit(void);
+static int sockdestroy(void);
+
+static int daemon_inited = 0;
+
+int
+slapd_daemon_init( const char *urls )
+{
+ int i, j, n, rc;
+ char **u;
+
+ Debug( LDAP_DEBUG_ARGS, "daemon_init: %s\n",
+ urls ? urls : "<null>" );
+
+ wake_sds = ch_malloc( slapd_daemon_threads * sizeof( sdpair ));
+ for ( i=0; i<slapd_daemon_threads; i++ ) {
+ wake_sds[i][0] = AC_SOCKET_INVALID;
+ wake_sds[i][1] = AC_SOCKET_INVALID;
+ }
+
+ slap_daemon = ch_calloc( slapd_daemon_threads, sizeof( slap_daemon_st ));
+ ldap_pvt_thread_mutex_init( &slap_daemon[0].sd_mutex );
+#ifdef HAVE_TCPD
+ ldap_pvt_thread_mutex_init( &sd_tcpd_mutex );
+#endif /* TCP Wrappers */
+ ldap_pvt_thread_mutex_init( &emfile_mutex );
+
+ daemon_inited = 1;
+
+ if( (rc = sockinit()) != 0 ) return rc;
+
+#ifdef HAVE_SYSCONF
+ dtblsize = sysconf( _SC_OPEN_MAX );
+#elif defined(HAVE_GETDTABLESIZE)
+ dtblsize = getdtablesize();
+#else /* ! HAVE_SYSCONF && ! HAVE_GETDTABLESIZE */
+ dtblsize = FD_SETSIZE;
+#endif /* ! HAVE_SYSCONF && ! HAVE_GETDTABLESIZE */
+
+ SETUP_CLOSE();
+
+ /* open a pipe (or something equivalent connected to itself).
+ * we write a byte on this fd whenever we catch a signal. The main
+ * loop will be select'ing on this socket, and will wake up when
+ * this byte arrives.
+ */
+ if( (rc = lutil_pair( wake_sds[0] )) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: lutil_pair() failed rc=%d\n", rc );
+ return rc;
+ }
+ ber_pvt_socket_set_nonblock( wake_sds[0][1], 1 );
+
+ SLAP_SOCK_INIT(0);
+
+ if( urls == NULL ) urls = "ldap:///";
+
+ u = ldap_str2charray( urls, " " );
+
+ if( u == NULL || u[0] == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "daemon_init: no urls (%s) provided.\n",
+ urls );
+ if ( u )
+ ldap_charray_free( u );
+ return -1;
+ }
+
+ for( i=0; u[i] != NULL; i++ ) {
+ Debug( LDAP_DEBUG_TRACE, "daemon_init: listen on %s\n",
+ u[i] );
+ }
+
+ if( i == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "daemon_init: no listeners to open (%s)\n",
+ urls );
+ ldap_charray_free( u );
+ return -1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "daemon_init: %d listeners to open...\n",
+ i );
+ slap_listeners = ch_malloc( (i+1)*sizeof(Listener *) );
+
+ for(n = 0, j = 0; u[n]; n++ ) {
+ if ( slap_open_listener( u[n], &i, &j ) ) {
+ ldap_charray_free( u );
+ return -1;
+ }
+ }
+ slap_listeners[j] = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "daemon_init: %d listeners opened\n",
+ i );
+
+
+#ifdef HAVE_SLP
+ if( slapd_register_slp ) {
+ slapd_slp_init( urls );
+ slapd_slp_reg();
+ }
+#endif /* HAVE_SLP */
+
+ ldap_charray_free( u );
+
+ return !i;
+}
+
+/* transfer control of active sockets from old to new listener threads */
+static void
+slapd_socket_realloc( int newnum )
+{
+ int i, j, oldid, newid;
+ int newmask = newnum - 1;
+ Listener *sl;
+ int num_listeners;
+
+ for ( i=0; slap_listeners[i] != NULL; i++ ) ;
+ num_listeners = i;
+
+ for ( i=0; i<dtblsize; i++ ) {
+ int skip = 0;
+
+ /* don't bother with wake_sds, they're assigned independent of mask */
+ for (j=0; j<slapd_daemon_threads; j++) {
+ if ( i == wake_sds[j][0] || i == wake_sds[j][1] ) {
+ skip = 1;
+ break;
+ }
+ }
+ if ( skip ) continue;
+
+ oldid = DAEMON_ID(i);
+ newid = i & newmask;
+ if ( oldid == newid ) continue;
+ if ( !SLAP_SOCK_IS_ACTIVE( oldid, i )) continue;
+ sl = NULL;
+ if ( num_listeners ) {
+ for ( j=0; slap_listeners[j] != NULL; j++ ) {
+ if ( slap_listeners[j]->sl_sd == i ) {
+ sl = slap_listeners[j];
+ num_listeners--;
+ break;
+ }
+ }
+ }
+ SLAP_SOCK_ADD( newid, i, sl );
+ if ( SLAP_SOCK_IS_READ( oldid, i )) {
+ SLAP_SOCK_SET_READ( newid, i );
+ }
+ if ( SLAP_SOCK_IS_WRITE( oldid, i )) {
+ SLAP_SOCK_SET_WRITE( newid, i );
+ slap_daemon[oldid].sd_nwriters--;
+ slap_daemon[newid].sd_nwriters++;
+ }
+ if ( connection_is_active( i )) {
+ slap_daemon[oldid].sd_nactives--;
+ slap_daemon[newid].sd_nactives++;
+ }
+ SLAP_SOCK_DEL( oldid, i );
+ }
+}
+
+
+int
+slapd_daemon_destroy( void )
+{
+ connections_destroy();
+ if ( daemon_inited ) {
+ int i;
+
+ for ( i=0; i<slapd_daemon_threads; i++ ) {
+#ifdef HAVE_WINSOCK
+ if ( wake_sds[i][1] != INVALID_SOCKET &&
+ SLAP_FD2SOCK( wake_sds[i][1] ) != SLAP_FD2SOCK( wake_sds[i][0] ))
+#endif /* HAVE_WINSOCK */
+ tcp_close( SLAP_FD2SOCK(wake_sds[i][1]) );
+#ifdef HAVE_WINSOCK
+ if ( wake_sds[i][0] != INVALID_SOCKET )
+#endif /* HAVE_WINSOCK */
+ tcp_close( SLAP_FD2SOCK(wake_sds[i][0]) );
+ ldap_pvt_thread_mutex_destroy( &slap_daemon[i].sd_mutex );
+ SLAP_SOCK_DESTROY(i);
+ }
+ daemon_inited = 0;
+ ldap_pvt_thread_mutex_destroy( &emfile_mutex );
+#ifdef HAVE_TCPD
+ ldap_pvt_thread_mutex_destroy( &sd_tcpd_mutex );
+#endif /* TCP Wrappers */
+ }
+ sockdestroy();
+
+#ifdef HAVE_SLP
+ if( slapd_register_slp ) {
+ slapd_slp_dereg();
+ slapd_slp_deinit();
+ }
+#endif /* HAVE_SLP */
+
+ return 0;
+}
+
+
+static void
+close_listeners(
+ int remove )
+{
+ int l;
+
+ if ( !listening )
+ return;
+ listening = 0;
+
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ Listener *lr = slap_listeners[l];
+
+ if ( lr->sl_sd != AC_SOCKET_INVALID ) {
+ int s = lr->sl_sd;
+ lr->sl_sd = AC_SOCKET_INVALID;
+ if ( remove ) slapd_remove( s, NULL, 0, 0, 0 );
+
+#ifdef LDAP_PF_LOCAL
+ if ( lr->sl_sa.sa_addr.sa_family == AF_LOCAL ) {
+ unlink( lr->sl_sa.sa_un_addr.sun_path );
+ }
+#endif /* LDAP_PF_LOCAL */
+
+ slapd_close( s );
+ }
+ }
+}
+
+static void
+destroy_listeners( void )
+{
+ Listener *lr, **ll = slap_listeners;
+
+ if ( ll == NULL )
+ return;
+
+ while ( (lr = *ll++) != NULL ) {
+ if ( lr->sl_url.bv_val ) {
+ ber_memfree( lr->sl_url.bv_val );
+ }
+
+ if ( lr->sl_name.bv_val ) {
+ ber_memfree( lr->sl_name.bv_val );
+ }
+
+ free( lr );
+ }
+
+ free( slap_listeners );
+ slap_listeners = NULL;
+}
+
+static int
+slap_listener(
+ Listener *sl )
+{
+ Sockaddr from;
+
+ ber_socket_t s, sfd;
+ ber_socklen_t len = sizeof(from);
+ Connection *c;
+ slap_ssf_t ssf = 0;
+ struct berval authid = BER_BVNULL;
+#ifdef SLAPD_RLOOKUPS
+ char hbuf[NI_MAXHOST];
+#endif /* SLAPD_RLOOKUPS */
+
+ char *dnsname = NULL;
+ /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */
+ char peername[LDAP_IPADDRLEN];
+ struct berval peerbv = BER_BVC(peername);
+#ifdef LDAP_PF_LOCAL_SENDMSG
+ char peerbuf[8];
+#endif
+ int cflag;
+ int tid;
+ char ebuf[128];
+
+ Debug( LDAP_DEBUG_TRACE,
+ ">>> slap_listener(%s)\n",
+ sl->sl_url.bv_val );
+
+ peername[0] = '\0';
+
+#ifdef LDAP_CONNECTIONLESS
+ if ( sl->sl_is_udp ) return 1;
+#endif /* LDAP_CONNECTIONLESS */
+
+# ifdef LDAP_PF_LOCAL
+ /* FIXME: apparently accept doesn't fill
+ * the sun_path sun_path member */
+ from.sa_un_addr.sun_path[0] = '\0';
+# endif /* LDAP_PF_LOCAL */
+
+ s = accept( SLAP_FD2SOCK( sl->sl_sd ), (struct sockaddr *) &from, &len );
+ if ( s != AC_SOCKET_INVALID ) {
+ SET_CLOSE(s);
+ }
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: accept() = %d\n", s );
+
+ /* Resume the listener FD to allow concurrent-processing of
+ * additional incoming connections.
+ */
+ sl->sl_busy = 0;
+ WAKE_LISTENER(DAEMON_ID(sl->sl_sd),1);
+
+ if ( s == AC_SOCKET_INVALID ) {
+ int err = sock_errno();
+
+ if(
+#ifdef EMFILE
+ err == EMFILE ||
+#endif /* EMFILE */
+#ifdef ENFILE
+ err == ENFILE ||
+#endif /* ENFILE */
+ 0 )
+ {
+ ldap_pvt_thread_mutex_lock( &emfile_mutex );
+ emfile++;
+ /* Stop listening until an existing session closes */
+ sl->sl_mute = 1;
+ ldap_pvt_thread_mutex_unlock( &emfile_mutex );
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: accept(%ld) failed errno=%d (%s)\n",
+ (long) sl->sl_sd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ ldap_pvt_thread_yield();
+ return 0;
+ }
+ sfd = SLAP_SOCKNEW( s );
+
+ /* make sure descriptor number isn't too great */
+ if ( sfd >= dtblsize ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: %ld beyond descriptor table size %ld\n",
+ (long) sfd, (long) dtblsize );
+
+ tcp_close(s);
+ ldap_pvt_thread_yield();
+ return 0;
+ }
+ tid = DAEMON_ID(sfd);
+
+#ifdef LDAP_DEBUG
+ ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex );
+ /* newly accepted stream should not be in any of the FD SETS */
+ assert( SLAP_SOCK_NOT_ACTIVE( tid, sfd ));
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex );
+#endif /* LDAP_DEBUG */
+
+#if defined( SO_KEEPALIVE ) || defined( TCP_NODELAY )
+#ifdef LDAP_PF_LOCAL
+ /* for IPv4 and IPv6 sockets only */
+ if ( from.sa_addr.sa_family != AF_LOCAL )
+#endif /* LDAP_PF_LOCAL */
+ {
+ int rc;
+ int tmp;
+#ifdef SO_KEEPALIVE
+ /* enable keep alives */
+ tmp = 1;
+ rc = setsockopt( s, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd(%ld): setsockopt(SO_KEEPALIVE) failed "
+ "errno=%d (%s)\n", (long) sfd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ slapd_close(sfd);
+ return 0;
+ }
+#endif /* SO_KEEPALIVE */
+#ifdef TCP_NODELAY
+ /* enable no delay */
+ tmp = 1;
+ rc = setsockopt( s, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&tmp, sizeof(tmp) );
+ if ( rc == AC_SOCKET_ERROR ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd(%ld): setsockopt(TCP_NODELAY) failed "
+ "errno=%d (%s)\n", (long) sfd, err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ slapd_close(sfd);
+ return 0;
+ }
+#endif /* TCP_NODELAY */
+ }
+#endif /* SO_KEEPALIVE || TCP_NODELAY */
+
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: listen=%ld, new connection on %ld\n",
+ (long) sl->sl_sd, (long) sfd );
+
+ cflag = 0;
+ switch ( from.sa_addr.sa_family ) {
+# ifdef LDAP_PF_LOCAL
+ case AF_LOCAL:
+ cflag |= CONN_IS_IPC;
+
+ /* FIXME: apparently accept doesn't fill
+ * the sun_path sun_path member */
+ if ( from.sa_un_addr.sun_path[0] == '\0' ) {
+ AC_MEMCPY( from.sa_un_addr.sun_path,
+ sl->sl_sa.sa_un_addr.sun_path,
+ sizeof( from.sa_un_addr.sun_path ) );
+ }
+
+ sprintf( peername, "PATH=%s", from.sa_un_addr.sun_path );
+ ssf = local_ssf;
+ {
+ uid_t uid;
+ gid_t gid;
+
+#ifdef LDAP_PF_LOCAL_SENDMSG
+ peerbv.bv_val = peerbuf;
+ peerbv.bv_len = sizeof( peerbuf );
+#endif
+ if( LUTIL_GETPEEREID( s, &uid, &gid, &peerbv ) == 0 ) {
+ authid.bv_val = ch_malloc(
+ STRLENOF( "gidNumber=4294967295+uidNumber=4294967295,"
+ "cn=peercred,cn=external,cn=auth" ) + 1 );
+ authid.bv_len = sprintf( authid.bv_val,
+ "gidNumber=%d+uidNumber=%d,"
+ "cn=peercred,cn=external,cn=auth",
+ (int) gid, (int) uid );
+ assert( authid.bv_len <=
+ STRLENOF( "gidNumber=4294967295+uidNumber=4294967295,"
+ "cn=peercred,cn=external,cn=auth" ) );
+ }
+ }
+ dnsname = "local";
+ break;
+#endif /* LDAP_PF_LOCAL */
+
+# ifdef LDAP_PF_INET6
+ case AF_INET6:
+# endif /* LDAP_PF_INET6 */
+ case AF_INET:
+ if ( sl->sl_is_proxied ) {
+ if ( !proxyp( sfd, &from ) ) {
+ Debug( LDAP_DEBUG_ANY, "slapd(%ld): proxyp failed\n", (long)sfd );
+ slapd_close( sfd );
+ return 0;
+ }
+ }
+ ldap_pvt_sockaddrstr( &from, &peerbv );
+ break;
+
+ default:
+ slapd_close(sfd);
+ return 0;
+ }
+
+ if ( ( from.sa_addr.sa_family == AF_INET )
+#ifdef LDAP_PF_INET6
+ || ( from.sa_addr.sa_family == AF_INET6 )
+#endif /* LDAP_PF_INET6 */
+ )
+ {
+ dnsname = NULL;
+#ifdef SLAPD_RLOOKUPS
+ if ( use_reverse_lookup ) {
+ char *herr;
+ if (ldap_pvt_get_hname( (const struct sockaddr *)&from, len, hbuf,
+ sizeof(hbuf), &herr ) == 0) {
+ ldap_pvt_str2lower( hbuf );
+ dnsname = hbuf;
+ }
+ }
+#endif /* SLAPD_RLOOKUPS */
+
+#ifdef HAVE_TCPD
+ {
+ int rc;
+ char *peeraddr, *paend;
+ peeraddr = peerbv.bv_val + 3;
+ if ( *peeraddr == '[' ) {
+ peeraddr++;
+ paend = strrchr( peeraddr, ']' );
+ } else {
+ paend = strrchr( peeraddr, ':' );
+ }
+ if ( paend )
+ *paend = '\0';
+ ldap_pvt_thread_mutex_lock( &sd_tcpd_mutex );
+ rc = hosts_ctl("slapd",
+ dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN,
+ peeraddr,
+ SLAP_STRING_UNKNOWN );
+ ldap_pvt_thread_mutex_unlock( &sd_tcpd_mutex );
+ if ( !rc ) {
+ /* DENY ACCESS */
+ Debug( LDAP_DEBUG_STATS,
+ "fd=%ld DENIED from %s (%s)\n",
+ (long) sfd,
+ dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN,
+ peeraddr );
+ slapd_close(sfd);
+ return 0;
+ }
+ if ( paend ) {
+ if ( peeraddr[-1] == '[' )
+ *paend = ']';
+ else
+ *paend = ':';
+ }
+ }
+#endif /* HAVE_TCPD */
+ }
+
+#ifdef HAVE_TLS
+ if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS;
+#endif
+ c = connection_init(sfd, sl,
+ dnsname != NULL ? dnsname : SLAP_STRING_UNKNOWN,
+ peername, cflag, ssf,
+ authid.bv_val ? &authid : NULL
+ LDAP_PF_LOCAL_SENDMSG_ARG(&peerbv));
+
+ if( authid.bv_val ) ch_free(authid.bv_val);
+
+ if( !c ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: connection_init(%ld, %s, %s) failed.\n",
+ (long) sfd, peername, sl->sl_name.bv_val );
+ slapd_close(sfd);
+ }
+
+ return 0;
+}
+
+static void*
+slap_listener_thread(
+ void* ctx,
+ void* ptr )
+{
+ int rc;
+ Listener *sl = (Listener *)ptr;
+
+ rc = slap_listener( sl );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_listener_thread(%s): failed err=%d",
+ sl->sl_url.bv_val, rc );
+ }
+
+ return (void*)NULL;
+}
+
+static int
+slap_listener_activate(
+ Listener* sl )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "slap_listener_activate(%d): %s\n",
+ sl->sl_sd, sl->sl_busy ? "busy" : "" );
+
+ sl->sl_busy = 1;
+
+ rc = ldap_pvt_thread_pool_submit( &connection_pool,
+ slap_listener_thread, (void *) sl );
+
+ if( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_listener_activate(%d): submit failed (%d)\n",
+ sl->sl_sd, rc );
+ }
+ return rc;
+}
+
+static void *
+slapd_rtask_trampoline(
+ void *ctx,
+ void *arg )
+{
+ struct re_s *rtask = arg;
+
+ /* invalidate pool_cookie */
+ rtask->pool_cookie = NULL;
+ return rtask->routine( ctx, arg );
+}
+
+static void *
+slapd_daemon_task(
+ void *ptr )
+{
+ int l;
+ time_t last_idle_check = 0;
+ int ebadf = 0;
+ int tid = (slap_daemon_st *) ptr - slap_daemon;
+ char ebuf[128];
+
+#define SLAPD_IDLE_CHECK_LIMIT 4
+
+ slapd_add( wake_sds[tid][0], 0, NULL, tid );
+ if ( tid )
+ goto loop;
+
+ /* Init stuff done only by thread 0 */
+
+ last_idle_check = slap_get_time();
+
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ if ( slap_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue;
+
+#ifdef LDAP_CONNECTIONLESS
+ /* Since this is connectionless, the data port is the
+ * listening port. The listen() and accept() calls
+ * are unnecessary.
+ */
+ if ( slap_listeners[l]->sl_is_udp )
+ continue;
+#endif /* LDAP_CONNECTIONLESS */
+
+ /* FIXME: TCP-only! */
+#ifdef LDAP_TCP_BUFFER
+ if ( 1 ) {
+ int origsize, size, realsize, rc;
+ socklen_t optlen;
+
+ size = 0;
+ if ( slap_listeners[l]->sl_tcp_rmem > 0 ) {
+ size = slap_listeners[l]->sl_tcp_rmem;
+ } else if ( slapd_tcp_rmem > 0 ) {
+ size = slapd_tcp_rmem;
+ }
+
+ if ( size > 0 ) {
+ optlen = sizeof( origsize );
+ rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_RCVBUF,
+ (void *)&origsize,
+ &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ optlen = sizeof( size );
+ rc = setsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_RCVBUF,
+ (const void *)&size,
+ optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: setsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ optlen = sizeof( realsize );
+ rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_RCVBUF,
+ (void *)&realsize,
+ &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ Debug(LDAP_DEBUG_ANY,
+ "slapd_daemon_task: url=%s (#%d) RCVBUF original size=%d requested size=%d real size=%d\n",
+ slap_listeners[l]->sl_url.bv_val, l,
+ origsize, size, realsize );
+ }
+
+ size = 0;
+ if ( slap_listeners[l]->sl_tcp_wmem > 0 ) {
+ size = slap_listeners[l]->sl_tcp_wmem;
+ } else if ( slapd_tcp_wmem > 0 ) {
+ size = slapd_tcp_wmem;
+ }
+
+ if ( size > 0 ) {
+ optlen = sizeof( origsize );
+ rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_SNDBUF,
+ (void *)&origsize,
+ &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ optlen = sizeof( size );
+ rc = setsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_SNDBUF,
+ (const void *)&size,
+ optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: setsockopt(SO_SNDBUF) failed errno=%d (%s)",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ optlen = sizeof( realsize );
+ rc = getsockopt( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ),
+ SOL_SOCKET,
+ SO_SNDBUF,
+ (void *)&realsize,
+ &optlen );
+
+ if ( rc ) {
+ int err = sock_errno();
+ Debug( LDAP_DEBUG_ANY,
+ "slapd_daemon_task: getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+ }
+
+ Debug(LDAP_DEBUG_ANY,
+ "slapd_daemon_task: url=%s (#%d) SNDBUF original size=%d requested size=%d real size=%d\n",
+ slap_listeners[l]->sl_url.bv_val, l,
+ origsize, size, realsize );
+ }
+ }
+#endif /* LDAP_TCP_BUFFER */
+
+ if ( listen( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), SLAPD_LISTEN_BACKLOG ) == -1 ) {
+ int err = sock_errno();
+
+#ifdef LDAP_PF_INET6
+ /* If error is EADDRINUSE, we are trying to listen to INADDR_ANY and
+ * we are already listening to in6addr_any, then we want to ignore
+ * this and continue.
+ */
+ if ( err == EADDRINUSE ) {
+ int i;
+ struct sockaddr_in sa = slap_listeners[l]->sl_sa.sa_in_addr;
+ struct sockaddr_in6 sa6;
+
+ if ( sa.sin_family == AF_INET &&
+ sa.sin_addr.s_addr == htonl(INADDR_ANY) ) {
+ for ( i = 0 ; i < l; i++ ) {
+ sa6 = slap_listeners[i]->sl_sa.sa_in6_addr;
+ if ( sa6.sin6_family == AF_INET6 &&
+ !memcmp( &sa6.sin6_addr, &in6addr_any,
+ sizeof(struct in6_addr) ) )
+ {
+ break;
+ }
+ }
+
+ if ( i < l ) {
+ /* We are already listening to in6addr_any */
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: Attempt to listen to 0.0.0.0 failed, "
+ "already listening on ::, assuming IPv4 included\n" );
+ slapd_close( slap_listeners[l]->sl_sd );
+ slap_listeners[l]->sl_sd = AC_SOCKET_INVALID;
+ continue;
+ }
+ }
+ }
+#endif /* LDAP_PF_INET6 */
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: listen(%s, 5) failed errno=%d (%s)\n",
+ slap_listeners[l]->sl_url.bv_val, err,
+ sock_errstr(err, ebuf, sizeof(ebuf)) );
+ ldap_pvt_thread_mutex_lock( &slapd_init_mutex );
+ slapd_shutdown = 2;
+ ldap_pvt_thread_cond_signal( &slapd_init_cond );
+ ldap_pvt_thread_mutex_unlock( &slapd_init_mutex );
+ return (void*)-1;
+ }
+
+ /* make the listening socket non-blocking */
+ if ( ber_pvt_socket_set_nonblock( SLAP_FD2SOCK( slap_listeners[l]->sl_sd ), 1 ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "slapd_daemon_task: "
+ "set nonblocking on a listening socket failed\n" );
+ ldap_pvt_thread_mutex_lock( &slapd_init_mutex );
+ slapd_shutdown = 2;
+ ldap_pvt_thread_cond_signal( &slapd_init_cond );
+ ldap_pvt_thread_mutex_unlock( &slapd_init_mutex );
+ return (void*)-1;
+ }
+
+ slapd_add( slap_listeners[l]->sl_sd, 0, slap_listeners[l], -1 );
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_init_mutex );
+ slapd_ready = 1;
+ ldap_pvt_thread_cond_signal( &slapd_init_cond );
+ ldap_pvt_thread_mutex_unlock( &slapd_init_mutex );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ if ( started_event != NULL ) {
+ ldap_pvt_thread_cond_signal( &started_event );
+ }
+#endif /* HAVE_NT_SERVICE_MANAGER */
+
+loop:
+
+ /* initialization complete. Here comes the loop. */
+
+ while ( !slapd_shutdown ) {
+ ber_socket_t i;
+ int ns, nwriters;
+ int at;
+ ber_socket_t nfds;
+#if SLAP_EVENTS_ARE_INDEXED
+ ber_socket_t nrfds, nwfds;
+#endif /* SLAP_EVENTS_ARE_INDEXED */
+#define SLAPD_EBADF_LIMIT 16
+
+ time_t now;
+
+ SLAP_EVENT_DECL;
+
+ struct timeval tv;
+ struct timeval *tvp;
+
+ struct timeval cat;
+ time_t tdelta = 1;
+ struct re_s* rtask;
+
+ now = slap_get_time();
+
+ if ( !tid && ( global_idletimeout > 0 )) {
+ int check = 0;
+ /* Set the select timeout.
+ * Don't just truncate, preserve the fractions of
+ * seconds to prevent sleeping for zero time.
+ */
+ {
+ tv.tv_sec = global_idletimeout / SLAPD_IDLE_CHECK_LIMIT;
+ tv.tv_usec = global_idletimeout - \
+ ( tv.tv_sec * SLAPD_IDLE_CHECK_LIMIT );
+ tv.tv_usec *= 1000000 / SLAPD_IDLE_CHECK_LIMIT;
+ if ( difftime( last_idle_check +
+ global_idletimeout/SLAPD_IDLE_CHECK_LIMIT, now ) < 0 )
+ check = 1;
+ }
+ if ( check ) {
+ connections_timeout_idle( now );
+ last_idle_check = now;
+ }
+ } else {
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ }
+
+#ifdef SIGHUP
+ if ( slapd_gentle_shutdown ) {
+ ber_socket_t active;
+
+ if ( !tid && slapd_gentle_shutdown == 1 ) {
+ BackendDB *be;
+ Debug( LDAP_DEBUG_ANY, "slapd gentle shutdown\n" );
+ close_listeners( 1 );
+ frontendDB->be_restrictops |= SLAP_RESTRICT_OP_WRITES;
+ LDAP_STAILQ_FOREACH(be, &backendDB, be_next) {
+ be->be_restrictops |= SLAP_RESTRICT_OP_WRITES;
+ }
+ slapd_gentle_shutdown = 2;
+ }
+
+ ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex );
+ active = slap_daemon[tid].sd_nactives;
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex );
+
+ if ( active == 0 ) {
+ if ( !tid ) {
+ for ( l=1; l<slapd_daemon_threads; l++ ) {
+ ldap_pvt_thread_mutex_lock( &slap_daemon[l].sd_mutex );
+ active += slap_daemon[l].sd_nactives;
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[l].sd_mutex );
+ }
+ if ( !active )
+ slapd_shutdown = 1;
+ }
+ if ( !active )
+ break;
+ }
+ }
+#endif /* SIGHUP */
+ at = 0;
+
+ ldap_pvt_thread_mutex_lock( &slap_daemon[tid].sd_mutex );
+
+ nwriters = slap_daemon[tid].sd_nwriters;
+
+ if ( listening )
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ Listener *lr = slap_listeners[l];
+
+ if ( lr->sl_sd == AC_SOCKET_INVALID ) continue;
+ if ( DAEMON_ID( lr->sl_sd ) != tid ) continue;
+ if ( !SLAP_SOCK_IS_ACTIVE( tid, lr->sl_sd )) continue;
+
+ if ( lr->sl_mute || lr->sl_busy )
+ {
+ SLAP_SOCK_CLR_READ( tid, lr->sl_sd );
+ } else {
+ SLAP_SOCK_SET_READ( tid, lr->sl_sd );
+ }
+ }
+
+ SLAP_EVENT_INIT(tid);
+
+ nfds = SLAP_EVENT_MAX(tid);
+
+ if (( global_idletimeout ) && slap_daemon[tid].sd_nactives ) at = 1;
+
+ ldap_pvt_thread_mutex_unlock( &slap_daemon[tid].sd_mutex );
+
+ if ( at
+#if defined(HAVE_YIELDING_SELECT)
+ && ( tv.tv_sec || tv.tv_usec )
+#endif /* HAVE_YIELDING_SELECT */
+ )
+ {
+ tvp = &tv;
+ } else {
+ tvp = NULL;
+ }
+
+ /* Only thread 0 handles runqueue */
+ if ( !tid ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ rtask = ldap_pvt_runqueue_next_sched( &slapd_rq, &cat );
+ while ( rtask && cat.tv_sec && cat.tv_sec <= now ) {
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ } else {
+ ldap_pvt_runqueue_runtask( &slapd_rq, rtask );
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ ldap_pvt_thread_pool_submit2( &connection_pool,
+ slapd_rtask_trampoline, (void *) rtask, &rtask->pool_cookie );
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ }
+ rtask = ldap_pvt_runqueue_next_sched( &slapd_rq, &cat );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ if ( rtask && cat.tv_sec ) {
+ /* NOTE: diff __should__ always be >= 0,
+ * AFAI understand; however (ITS#4872),
+ * time_t might be unsigned in some systems,
+ * while difftime() returns a double */
+ double diff = difftime( cat.tv_sec, now );
+ if ( diff <= 0 ) {
+ diff = tdelta;
+ }
+ if ( tvp == NULL || diff < tv.tv_sec ) {
+ tv.tv_sec = diff;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ }
+ }
+ }
+
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ Listener *lr = slap_listeners[l];
+
+ if ( lr->sl_sd == AC_SOCKET_INVALID ) {
+ continue;
+ }
+
+ if ( DAEMON_ID( lr->sl_sd ) != tid ) continue;
+
+ if ( lr->sl_mute ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: " SLAP_EVENT_FNAME ": "
+ "listen=%d muted\n",
+ lr->sl_sd );
+ continue;
+ }
+
+ if ( lr->sl_busy ) {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: " SLAP_EVENT_FNAME ": "
+ "listen=%d busy\n",
+ lr->sl_sd );
+ continue;
+ }
+
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: " SLAP_EVENT_FNAME ": "
+ "listen=%d active_threads=%d tvp=%s\n",
+ lr->sl_sd, at, tvp == NULL ? "NULL" : "zero" );
+ }
+
+ SLAP_EVENT_WAIT( tid, tvp, &ns );
+ switch ( ns ) {
+ case -1: { /* failure - try again */
+ int err = sock_errno();
+
+ if ( err != EINTR ) {
+ ebadf++;
+
+ /* Don't log unless we got it twice in a row */
+ if ( !( ebadf & 1 ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: "
+ SLAP_EVENT_FNAME
+ " failed count %d "
+ "err (%d): %s\n",
+ ebadf, err,
+ sock_errstr( err, ebuf, sizeof(ebuf) ) );
+ }
+ if ( ebadf >= SLAPD_EBADF_LIMIT ) {
+ slapd_shutdown = 2;
+ }
+ }
+ }
+ continue;
+
+ case 0: /* timeout - let threads run */
+ ebadf = 0;
+#ifndef HAVE_YIELDING_SELECT
+ Debug( LDAP_DEBUG_CONNS, "daemon: " SLAP_EVENT_FNAME
+ "timeout - yielding\n" );
+
+ ldap_pvt_thread_yield();
+#endif /* ! HAVE_YIELDING_SELECT */
+ continue;
+
+ default: /* something happened - deal with it */
+ if ( slapd_shutdown ) continue;
+
+ ebadf = 0;
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: activity on %d descriptor%s\n",
+ ns, ns != 1 ? "s" : "" );
+ /* FALL THRU */
+ }
+
+#if SLAP_EVENTS_ARE_INDEXED
+ if ( SLAP_EVENT_IS_READ( wake_sds[tid][0] ) ) {
+ char c[BUFSIZ];
+ SLAP_EVENT_CLR_READ( wake_sds[tid][0] );
+ waking = 0;
+ tcp_read( SLAP_FD2SOCK(wake_sds[tid][0]), c, sizeof(c) );
+ Debug( LDAP_DEBUG_CONNS, "daemon: waked\n" );
+ continue;
+ }
+
+ /* The event slot equals the descriptor number - this is
+ * true for Unix select and poll. We treat Windows select
+ * like this too, even though it's a kludge.
+ */
+ if ( listening )
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ int rc;
+
+ if ( ns <= 0 ) break;
+ if ( slap_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue;
+ if ( DAEMON_ID( slap_listeners[l]->sl_sd ) != tid ) continue;
+#ifdef LDAP_CONNECTIONLESS
+ if ( slap_listeners[l]->sl_is_udp ) continue;
+#endif /* LDAP_CONNECTIONLESS */
+ if ( !SLAP_EVENT_IS_READ( slap_listeners[l]->sl_sd ) ) continue;
+
+ /* clear events */
+ SLAP_EVENT_CLR_READ( slap_listeners[l]->sl_sd );
+ SLAP_EVENT_CLR_WRITE( slap_listeners[l]->sl_sd );
+ ns--;
+
+ rc = slap_listener_activate( slap_listeners[l] );
+ }
+
+ /* bypass the following tests if no descriptors left */
+ if ( ns <= 0 ) {
+#ifndef HAVE_YIELDING_SELECT
+ ldap_pvt_thread_yield();
+#endif /* HAVE_YIELDING_SELECT */
+ continue;
+ }
+
+ Debug( LDAP_DEBUG_CONNS, "daemon: activity on:" );
+ nrfds = 0;
+ nwfds = 0;
+ for ( i = 0; i < nfds; i++ ) {
+ int r, w;
+
+ r = SLAP_EVENT_IS_READ( i );
+ /* writefds was not initialized if nwriters was zero */
+ w = nwriters ? SLAP_EVENT_IS_WRITE( i ) : 0;
+ if ( r || w ) {
+ Debug( LDAP_DEBUG_CONNS, " %d%s%s", i,
+ r ? "r" : "", w ? "w" : "" );
+ if ( r ) {
+ nrfds++;
+ ns--;
+ }
+ if ( w ) {
+ nwfds++;
+ ns--;
+ }
+ }
+ if ( ns <= 0 ) break;
+ }
+ Debug( LDAP_DEBUG_CONNS, "\n" );
+
+ /* loop through the writers */
+ for ( i = 0; nwfds > 0; i++ ) {
+ ber_socket_t wd;
+ if ( ! SLAP_EVENT_IS_WRITE( i ) ) continue;
+ wd = i;
+
+ SLAP_EVENT_CLR_WRITE( wd );
+ nwfds--;
+
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: write active on %d\n",
+ wd );
+
+ /*
+ * NOTE: it is possible that the connection was closed
+ * and that the stream is now inactive.
+ * connection_write() must validate the stream is still
+ * active.
+ *
+ * ITS#4338: if the stream is invalid, there is no need to
+ * close it here. It has already been closed in connection.c.
+ */
+ if ( connection_write( wd ) < 0 ) {
+ if ( SLAP_EVENT_IS_READ( wd ) ) {
+ SLAP_EVENT_CLR_READ( (unsigned) wd );
+ nrfds--;
+ }
+ }
+ }
+
+ for ( i = 0; nrfds > 0; i++ ) {
+ ber_socket_t rd;
+ if ( ! SLAP_EVENT_IS_READ( i ) ) continue;
+ rd = i;
+ SLAP_EVENT_CLR_READ( rd );
+ nrfds--;
+
+ Debug ( LDAP_DEBUG_CONNS,
+ "daemon: read activity on %d\n", rd );
+ /*
+ * NOTE: it is possible that the connection was closed
+ * and that the stream is now inactive.
+ * connection_read() must valid the stream is still
+ * active.
+ */
+
+ connection_read_activate( rd );
+ }
+#else /* !SLAP_EVENTS_ARE_INDEXED */
+ /* FIXME */
+ /* The events are returned in an arbitrary list. This is true
+ * for /dev/poll, epoll and kqueue. In order to prioritize things
+ * so that we can handle wake_sds first, listeners second, and then
+ * all other connections last (as we do for select), we would need
+ * to use multiple event handles and cascade them.
+ *
+ * That seems like a bit of hassle. So the wake_sds check has been
+ * skipped. For epoll and kqueue we can associate arbitrary data with
+ * an event, so we could use pointers to the listener structure
+ * instead of just the file descriptor. For /dev/poll we have to
+ * search the listeners array for a matching descriptor.
+ *
+ * We now handle wake events when we see them; they are not given
+ * higher priority.
+ */
+#ifdef LDAP_DEBUG
+ Debug( LDAP_DEBUG_CONNS, "daemon: activity on:" );
+
+ for ( i = 0; i < ns; i++ ) {
+ int r, w, fd;
+
+ /* Don't log listener events */
+ if ( SLAP_EVENT_IS_LISTENER( tid, i )
+#ifdef LDAP_CONNECTIONLESS
+ && !( (SLAP_EVENT_LISTENER( tid, i ))->sl_is_udp )
+#endif /* LDAP_CONNECTIONLESS */
+ )
+ {
+ continue;
+ }
+
+ fd = SLAP_EVENT_FD( tid, i );
+ /* Don't log internal wake events */
+ if ( fd == wake_sds[tid][0] ) continue;
+
+#ifdef HAVE_KQUEUE
+ r = SLAP_EVENT_IS_READ( tid, i );
+ w = SLAP_EVENT_IS_WRITE( tid, i );
+#else
+ r = SLAP_EVENT_IS_READ( i );
+ w = SLAP_EVENT_IS_WRITE( i );
+#endif /* HAVE_KQUEUE */
+ if ( r || w ) {
+ Debug( LDAP_DEBUG_CONNS, " %d%s%s", fd,
+ r ? "r" : "", w ? "w" : "" );
+ }
+ }
+ Debug( LDAP_DEBUG_CONNS, "\n" );
+#endif /* LDAP_DEBUG */
+
+ for ( i = 0; i < ns; i++ ) {
+ int rc = 1, fd, w = 0, r = 0;
+
+ if ( SLAP_EVENT_IS_LISTENER( tid, i ) ) {
+ rc = slap_listener_activate( SLAP_EVENT_LISTENER( tid, i ) );
+ }
+
+ /* If we found a regular listener, rc is now zero, and we
+ * can skip the data portion. But if it was a UDP listener
+ * then rc is still 1, and we want to handle the data.
+ */
+ if ( rc ) {
+ fd = SLAP_EVENT_FD( tid, i );
+
+ /* Handle wake events */
+ if ( fd == wake_sds[tid][0] ) {
+ char c[BUFSIZ];
+ waking = 0;
+ (void)!tcp_read( SLAP_FD2SOCK(wake_sds[tid][0]), c, sizeof(c) );
+ continue;
+ }
+
+#ifdef HAVE_KQUEUE
+ if ( SLAP_EVENT_IS_WRITE( tid, i ) )
+#else
+ if ( SLAP_EVENT_IS_WRITE( i ) )
+#endif /* HAVE_KQUEUE */
+ {
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: write active on %d\n",
+ fd );
+
+ SLAP_EVENT_CLR_WRITE( i );
+ w = 1;
+
+ /*
+ * NOTE: it is possible that the connection was closed
+ * and that the stream is now inactive.
+ * connection_write() must valid the stream is still
+ * active.
+ */
+ if ( connection_write( fd ) < 0 ) {
+ continue;
+ }
+ }
+ /* If event is a read */
+#ifdef HAVE_KQUEUE
+ if ( SLAP_EVENT_IS_READ( tid, i ))
+#else
+ if ( SLAP_EVENT_IS_READ( i ))
+#endif /* HAVE_KQUEUE */
+ {
+ r = 1;
+ Debug( LDAP_DEBUG_CONNS,
+ "daemon: read active on %d\n",
+ fd );
+
+ SLAP_EVENT_CLR_READ( i );
+ connection_read_activate( fd );
+ } else if ( !w ) {
+#ifdef HAVE_EPOLL
+ /* Don't keep reporting the hangup
+ */
+ if ( SLAP_SOCK_IS_ACTIVE( tid, fd )) {
+ SLAP_EPOLL_SOCK_SET( tid, fd, EPOLLET );
+ }
+#endif
+ }
+ }
+ }
+#endif /* SLAP_EVENTS_ARE_INDEXED */
+
+ /* Was number of listener threads decreased? */
+ if ( ldap_pvt_thread_pool_pausecheck_native( &connection_pool )) {
+ /* decreased, let this thread finish */
+ if ( tid >= slapd_daemon_threads )
+ break;
+ }
+
+#ifndef HAVE_YIELDING_SELECT
+ ldap_pvt_thread_yield();
+#endif /* ! HAVE_YIELDING_SELECT */
+ }
+
+ /* Only thread 0 handles shutdown */
+ if ( tid )
+ return NULL;
+
+ if ( slapd_shutdown == 1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: shutdown requested and initiated.\n" );
+
+ } else if ( slapd_shutdown == 2 ) {
+#ifdef HAVE_NT_SERVICE_MANAGER
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: shutdown initiated by Service Manager.\n" );
+#else /* !HAVE_NT_SERVICE_MANAGER */
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: abnormal condition, shutdown initiated.\n" );
+#endif /* !HAVE_NT_SERVICE_MANAGER */
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: no active streams, shutdown initiated.\n" );
+ }
+
+ close_listeners( 1 );
+
+ if ( !slapd_gentle_shutdown ) {
+ slapd_abrupt_shutdown = 1;
+ connections_shutdown();
+ }
+
+#ifdef HAVE_KQUEUE
+ close( slap_daemon[tid].sd_kq );
+#endif
+
+ if ( LogTest( LDAP_DEBUG_ANY )) {
+ int t = ldap_pvt_thread_pool_backload( &connection_pool );
+ Debug( LDAP_DEBUG_ANY,
+ "slapd shutdown: waiting for %d operations/tasks to finish\n",
+ t );
+ }
+ ldap_pvt_thread_pool_close( &connection_pool, 1 );
+
+ return NULL;
+}
+
+typedef struct slap_tid_waiter {
+ int num_tids;
+ ldap_pvt_thread_t tids[0];
+} slap_tid_waiter;
+
+static void *
+slapd_daemon_tid_cleanup(
+ void *ctx,
+ void *ptr )
+{
+ slap_tid_waiter *tids = ptr;
+ int i;
+
+ for ( i=0; i<tids->num_tids; i++ )
+ ldap_pvt_thread_join( tids->tids[i], (void *)NULL );
+ ch_free( ptr );
+ return NULL;
+}
+
+int
+slapd_daemon_resize( int newnum )
+{
+ int i, rc;
+
+ if ( newnum == slapd_daemon_threads )
+ return 0;
+
+ /* wake up all current listener threads */
+ for ( i=0; i<slapd_daemon_threads; i++ )
+ WAKE_LISTENER(i,1);
+
+ /* mutexes may not survive realloc, so destroy & recreate later */
+ for ( i=0; i<slapd_daemon_threads; i++ )
+ ldap_pvt_thread_mutex_destroy( &slap_daemon[i].sd_mutex );
+
+ if ( newnum > slapd_daemon_threads ) {
+ wake_sds = ch_realloc( wake_sds, newnum * sizeof( sdpair ));
+ slap_daemon = ch_realloc( slap_daemon, newnum * sizeof( slap_daemon_st ));
+
+ for ( i=slapd_daemon_threads; i<newnum; i++ )
+ {
+ memset( &slap_daemon[i], 0, sizeof( slap_daemon_st ));
+ if( (rc = lutil_pair( wake_sds[i] )) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: lutil_pair() failed rc=%d\n", rc );
+ return rc;
+ }
+ ber_pvt_socket_set_nonblock( wake_sds[i][1], 1 );
+
+ SLAP_SOCK_INIT(i);
+ }
+
+ for ( i=0; i<newnum; i++ )
+ ldap_pvt_thread_mutex_init( &slap_daemon[i].sd_mutex );
+
+ slapd_socket_realloc( newnum );
+
+ for ( i=slapd_daemon_threads; i<newnum; i++ )
+ {
+ /* listener as a separate THREAD */
+ rc = ldap_pvt_thread_create( &slap_daemon[i].sd_tid,
+ 0, slapd_daemon_task, &slap_daemon[i] );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "listener ldap_pvt_thread_create failed (%d)\n", rc );
+ return rc;
+ }
+ }
+ } else {
+ int j;
+ slap_tid_waiter *tids = ch_malloc( sizeof(slap_tid_waiter) +
+ ((slapd_daemon_threads - newnum) * sizeof(ldap_pvt_thread_t )));
+ slapd_socket_realloc( newnum );
+ tids->num_tids = slapd_daemon_threads - newnum;
+ for ( i=newnum, j=0; i<slapd_daemon_threads; i++, j++ ) {
+ tids->tids[j] = slap_daemon[i].sd_tid;
+#ifdef HAVE_WINSOCK
+ if ( wake_sds[i][1] != INVALID_SOCKET &&
+ SLAP_FD2SOCK( wake_sds[i][1] ) != SLAP_FD2SOCK( wake_sds[i][0] ))
+#endif /* HAVE_WINSOCK */
+ tcp_close( SLAP_FD2SOCK(wake_sds[i][1]) );
+#ifdef HAVE_WINSOCK
+ if ( wake_sds[i][0] != INVALID_SOCKET )
+#endif /* HAVE_WINSOCK */
+ tcp_close( SLAP_FD2SOCK(wake_sds[i][0]) );
+
+ SLAP_SOCK_DESTROY( i );
+ }
+
+ wake_sds = ch_realloc( wake_sds, newnum * sizeof( sdpair ));
+ slap_daemon = ch_realloc( slap_daemon, newnum * sizeof( slap_daemon_st ));
+ for ( i=0; i<newnum; i++ )
+ ldap_pvt_thread_mutex_init( &slap_daemon[i].sd_mutex );
+ ldap_pvt_thread_pool_submit( &connection_pool,
+ slapd_daemon_tid_cleanup, (void *) tids );
+ }
+ slapd_daemon_threads = newnum;
+ slapd_daemon_mask = newnum - 1;
+ return 0;
+}
+
+#ifdef LDAP_CONNECTIONLESS
+static int
+connectionless_init( void )
+{
+ int l;
+
+ for ( l = 0; slap_listeners[l] != NULL; l++ ) {
+ Listener *lr = slap_listeners[l];
+ Connection *c;
+
+ if ( !lr->sl_is_udp ) {
+ continue;
+ }
+
+ c = connection_init( lr->sl_sd, lr, "", "",
+ CONN_IS_UDP, (slap_ssf_t) 0, NULL
+ LDAP_PF_LOCAL_SENDMSG_ARG(NULL));
+
+ if ( !c ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "connectionless_init: failed on %s (%d)\n",
+ lr->sl_url.bv_val, lr->sl_sd );
+ return -1;
+ }
+ lr->sl_is_udp++;
+ }
+
+ return 0;
+}
+#endif /* LDAP_CONNECTIONLESS */
+
+int
+slapd_daemon( void )
+{
+ int i, rc;
+
+#ifdef LDAP_CONNECTIONLESS
+ connectionless_init();
+#endif /* LDAP_CONNECTIONLESS */
+
+ SLAP_SOCK_INIT2();
+
+ /* daemon_init only inits element 0 */
+ for ( i=1; i<slapd_daemon_threads; i++ )
+ {
+ ldap_pvt_thread_mutex_init( &slap_daemon[i].sd_mutex );
+
+ if( (rc = lutil_pair( wake_sds[i] )) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "daemon: lutil_pair() failed rc=%d\n", rc );
+ return rc;
+ }
+ ber_pvt_socket_set_nonblock( wake_sds[i][1], 1 );
+
+ SLAP_SOCK_INIT(i);
+ }
+
+ for ( i=0; i<slapd_daemon_threads; i++ )
+ {
+ /* listener as a separate THREAD */
+ rc = ldap_pvt_thread_create( &slap_daemon[i].sd_tid,
+ 0, slapd_daemon_task, &slap_daemon[i] );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "listener ldap_pvt_thread_create failed (%d)\n", rc );
+ return rc;
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_init_mutex );
+ while ( !slapd_ready && !slapd_shutdown ) {
+ ldap_pvt_thread_cond_wait( &slapd_init_cond, &slapd_init_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_init_mutex );
+
+ if ( slapd_shutdown ) {
+ Debug( LDAP_DEBUG_ANY,
+ "listener initialization failed\n" );
+ return 1;
+ }
+
+#ifdef HAVE_SYSTEMD
+ rc = sd_notify( 1, "READY=1" );
+ if ( rc < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "systemd sd_notify failed (%d)\n", rc );
+ }
+#endif /* HAVE_SYSTEMD */
+
+ /* wait for the listener threads to complete */
+ for ( i=0; i<slapd_daemon_threads; i++ )
+ ldap_pvt_thread_join( slap_daemon[i].sd_tid, (void *)NULL );
+
+ destroy_listeners();
+
+ return 0;
+}
+
+static int
+sockinit( void )
+{
+#if defined( HAVE_WINSOCK2 )
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD( 2, 0 );
+
+ err = WSAStartup( wVersionRequested, &wsaData );
+ if ( err != 0 ) {
+ /* Tell the user that we couldn't find a usable */
+ /* WinSock DLL. */
+ return -1;
+ }
+
+ /* Confirm that the WinSock DLL supports 2.0.*/
+ /* Note that if the DLL supports versions greater */
+ /* than 2.0 in addition to 2.0, it will still return */
+ /* 2.0 in wVersion since that is the version we */
+ /* requested. */
+
+ if ( LOBYTE( wsaData.wVersion ) != 2 ||
+ HIBYTE( wsaData.wVersion ) != 0 )
+ {
+ /* Tell the user that we couldn't find a usable */
+ /* WinSock DLL. */
+ WSACleanup();
+ return -1;
+ }
+
+ /* The WinSock DLL is acceptable. Proceed. */
+#elif defined( HAVE_WINSOCK )
+ WSADATA wsaData;
+ if ( WSAStartup( 0x0101, &wsaData ) != 0 ) return -1;
+#endif /* ! HAVE_WINSOCK2 && ! HAVE_WINSOCK */
+
+ return 0;
+}
+
+static int
+sockdestroy( void )
+{
+#if defined( HAVE_WINSOCK2 ) || defined( HAVE_WINSOCK )
+ WSACleanup();
+#endif /* HAVE_WINSOCK2 || HAVE_WINSOCK */
+
+ return 0;
+}
+
+RETSIGTYPE
+slap_sig_shutdown( int sig )
+{
+ int save_errno = errno;
+ int i;
+
+#if 0
+ Debug(LDAP_DEBUG_TRACE, "slap_sig_shutdown: signal %d\n", sig);
+#endif
+
+ /*
+ * If the NT Service Manager is controlling the server, we don't
+ * want SIGBREAK to kill the server. For some strange reason,
+ * SIGBREAK is generated when a user logs out.
+ */
+
+#if defined(HAVE_NT_SERVICE_MANAGER) && defined(SIGBREAK)
+ if (is_NT_Service && sig == SIGBREAK) {
+ /* empty */;
+ } else
+#endif /* HAVE_NT_SERVICE_MANAGER && SIGBREAK */
+#ifdef SIGHUP
+ if (sig == SIGHUP && global_gentlehup && slapd_gentle_shutdown == 0) {
+ slapd_gentle_shutdown = 1;
+ } else
+#endif /* SIGHUP */
+ {
+ slapd_shutdown = 1;
+ }
+
+ for (i=0; i<slapd_daemon_threads; i++) {
+ WAKE_LISTENER(i,1);
+ }
+
+ /* reinstall self */
+ (void) SIGNAL_REINSTALL( sig, slap_sig_shutdown );
+
+ errno = save_errno;
+}
+
+RETSIGTYPE
+slap_sig_wake( int sig )
+{
+ int save_errno = errno;
+
+ WAKE_LISTENER(0,1);
+
+ /* reinstall self */
+ (void) SIGNAL_REINSTALL( sig, slap_sig_wake );
+
+ errno = save_errno;
+}
+
+int
+slap_pause_server( void )
+{
+ BackendInfo *bi;
+ int rc = LDAP_SUCCESS;
+
+ rc = ldap_pvt_thread_pool_pause( &connection_pool );
+
+ LDAP_STAILQ_FOREACH(bi, &backendInfo, bi_next) {
+ if ( bi->bi_pause ) {
+ rc = bi->bi_pause( bi );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slap_pause_server: "
+ "bi_pause failed for backend %s\n",
+ bi->bi_type );
+ return rc;
+ }
+ }
+ }
+
+ return rc;
+}
+
+int
+slap_unpause_server( void )
+{
+ BackendInfo *bi;
+ int rc = LDAP_SUCCESS;
+
+ LDAP_STAILQ_FOREACH(bi, &backendInfo, bi_next) {
+ if ( bi->bi_unpause ) {
+ rc = bi->bi_unpause( bi );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "slap_unpause_server: "
+ "bi_unpause failed for backend %s\n",
+ bi->bi_type );
+ return rc;
+ }
+ }
+ }
+
+ rc = ldap_pvt_thread_pool_resume( &connection_pool );
+ return rc;
+}
+
+
+void
+slapd_add_internal( ber_socket_t s, int isactive )
+{
+ if (!isactive) {
+ SET_CLOSE(s);
+ }
+ slapd_add( s, isactive, NULL, -1 );
+}
+
+Listener **
+slapd_get_listeners( void )
+{
+ /* Could return array with no listeners if !listening, but current
+ * callers mostly look at the URLs. E.g. syncrepl uses this to
+ * identify the server, which means it wants the startup arguments.
+ */
+ return slap_listeners;
+}
+
+/* Reject all incoming requests */
+void
+slap_suspend_listeners( void )
+{
+ int i;
+ for (i=0; slap_listeners[i]; i++) {
+ slap_listeners[i]->sl_mute = 1;
+ listen( slap_listeners[i]->sl_sd, 0 );
+ }
+}
+
+/* Resume after a suspend */
+void
+slap_resume_listeners( void )
+{
+ int i;
+ for (i=0; slap_listeners[i]; i++) {
+ slap_listeners[i]->sl_mute = 0;
+ listen( slap_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG );
+ }
+}
+
+void
+slap_wake_listener()
+{
+ WAKE_LISTENER(0,1);
+}
+
+/* return 0 on timeout, 1 on writer ready
+ * -1 on general error
+ */
+int
+slapd_wait_writer( ber_socket_t sd )
+{
+#ifdef HAVE_WINSOCK
+ fd_set writefds;
+ struct timeval tv, *tvp;
+
+ FD_ZERO( &writefds );
+ FD_SET( slapd_ws_sockets[sd], &writefds );
+ if ( global_writetimeout ) {
+ tv.tv_sec = global_writetimeout;
+ tv.tv_usec = 0;
+ tvp = &tv;
+ } else {
+ tvp = NULL;
+ }
+ return select( 0, NULL, &writefds, NULL, tvp );
+#else
+ struct pollfd fds;
+ int timeout = global_writetimeout ? global_writetimeout * 1000 : -1;
+
+ fds.fd = sd;
+ fds.events = POLLOUT;
+
+ return poll( &fds, 1, timeout );
+#endif
+}
diff --git a/servers/slapd/delete.c b/servers/slapd/delete.c
new file mode 100644
index 0000000..0329154
--- /dev/null
+++ b/servers/slapd/delete.c
@@ -0,0 +1,244 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#include "lutil.h"
+
+int
+do_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval dn = BER_BVNULL;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_delete\n",
+ op->o_log_prefix );
+ /*
+ * Parse the delete request. It looks like this:
+ *
+ * DelRequest := DistinguishedName
+ */
+
+ if ( ber_scanf( op->o_ber, "m", &dn ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_delete: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_delete: get_ctrls failed\n",
+ op->o_log_prefix );
+ goto cleanup;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_delete: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto cleanup;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s DEL dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ if( op->o_req_ndn.bv_len == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_delete: root dse!\n",
+ op->o_log_prefix );
+ /* protocolError would likely be a more appropriate error */
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "cannot delete the root DSE" );
+ goto cleanup;
+
+ } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_delete: subschema subentry!\n",
+ op->o_log_prefix );
+ /* protocolError would likely be a more appropriate error */
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "cannot delete the root DSE" );
+ goto cleanup;
+ }
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_delete( op, rs );
+ if ( rs->sr_err == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+ if( rs->sr_err == LDAP_TXN_SPECIFY_OKAY ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+cleanup:;
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ return rs->sr_err;
+}
+
+int
+fe_op_delete( Operation *op, SlapReply *rs )
+{
+ struct berval pdn = BER_BVNULL;
+ BackendDB *op_be, *bd = op->o_bd;
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd;
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+
+ if (!rs->sr_ref) rs->sr_ref = default_referral;
+ if ( rs->sr_ref != NULL ) {
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref );
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "no global superior knowledge" );
+ }
+ goto cleanup;
+ }
+
+ /* If we've got a glued backend, check the real backend */
+ op_be = op->o_bd;
+ if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check for referrals */
+ if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ /*
+ * do the delete if 1 && (2 || 3)
+ * 1) there is a delete function implemented in this backend;
+ * 2) this backend is the provider for what it holds;
+ * 3) it's a replica and the dn supplied is the update_ndn.
+ */
+ if ( op->o_bd->be_delete ) {
+ /* do the update here */
+ int repl_user = be_isupdate( op );
+ if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) {
+ struct berval org_req_dn = BER_BVNULL;
+ struct berval org_req_ndn = BER_BVNULL;
+ struct berval org_dn = BER_BVNULL;
+ struct berval org_ndn = BER_BVNULL;
+ int org_managedsait;
+
+ if ( op->o_txnSpec ) {
+ txn_preop( op, rs );
+ goto cleanup;
+ }
+
+ op->o_bd = op_be;
+ op->o_bd->be_delete( op, rs );
+
+ org_req_dn = op->o_req_dn;
+ org_req_ndn = op->o_req_ndn;
+ org_dn = op->o_dn;
+ org_ndn = op->o_ndn;
+ org_managedsait = get_manageDSAit( op );
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+
+ while ( rs->sr_err == LDAP_SUCCESS &&
+ op->o_delete_glue_parent )
+ {
+ op->o_delete_glue_parent = 0;
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn )) {
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ cb.sc_response = slap_null_cb;
+ dnParent( &op->o_req_ndn, &pdn );
+ op->o_req_dn = pdn;
+ op->o_req_ndn = pdn;
+ op->o_callback = &cb;
+ op->o_bd->be_delete( op, rs );
+ } else {
+ break;
+ }
+ }
+
+ op->o_managedsait = org_managedsait;
+ op->o_dn = org_dn;
+ op->o_ndn = org_ndn;
+ op->o_req_dn = org_req_dn;
+ op->o_req_ndn = org_req_ndn;
+ op->o_delete_glue_parent = 0;
+
+ } else {
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if ( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( defref,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ if (!rs->sr_ref) rs->sr_ref = defref;
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref );
+
+ } else {
+ send_ldap_error( op, rs,
+ LDAP_UNWILLING_TO_PERFORM,
+ "shadow context; no update referral" );
+ }
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within namingContext" );
+ }
+
+cleanup:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
diff --git a/servers/slapd/dn.c b/servers/slapd/dn.c
new file mode 100644
index 0000000..9b82f55
--- /dev/null
+++ b/servers/slapd/dn.c
@@ -0,0 +1,1333 @@
+/* dn.c - routines for dealing with distinguished names */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+/*
+ * The DN syntax-related functions take advantage of the dn representation
+ * handling functions ldap_str2dn/ldap_dn2str. The latter are not schema-
+ * aware, so the attributes and their values need be validated (and possibly
+ * normalized). In the current implementation the required validation/nor-
+ * malization/"pretty"ing are done on newly created DN structural represen-
+ * tations; however the idea is to move towards DN handling in structural
+ * representation instead of the current string representation. To this
+ * purpose, we need to do only the required operations and keep track of
+ * what has been done to minimize their impact on performances.
+ *
+ * Developers are strongly encouraged to use this feature, to speed-up
+ * its stabilization.
+ */
+
+#define AVA_PRIVATE( ava ) ( ( AttributeDescription * )(ava)->la_private )
+
+int slap_DN_strict = SLAP_AD_NOINSERT;
+
+static int
+LDAPRDN_validate( LDAPRDN rdn )
+{
+ int iAVA;
+ int rc;
+
+ assert( rdn != NULL );
+
+ for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
+ LDAPAVA *ava = rdn[ iAVA ];
+ AttributeDescription *ad;
+ slap_syntax_validate_func *validate = NULL;
+
+ assert( ava != NULL );
+
+ if ( ( ad = AVA_PRIVATE( ava ) ) == NULL ) {
+ const char *text = NULL;
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = slap_bv2undef_ad( &ava->la_attr,
+ &ad, &text,
+ SLAP_AD_PROXIED|slap_DN_strict );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ ava->la_private = ( void * )ad;
+ }
+
+ /*
+ * Do not allow X-ORDERED 'VALUES' naming attributes
+ */
+ if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /*
+ * Replace attr oid/name with the canonical name
+ */
+ ava->la_attr = ad->ad_cname;
+
+ validate = ad->ad_type->sat_syntax->ssyn_validate;
+
+ if ( validate ) {
+ /*
+ * validate value by validate function
+ */
+ rc = ( *validate )( ad->ad_type->sat_syntax,
+ &ava->la_value );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * In-place, schema-aware validation of the
+ * structural representation of a distinguished name.
+ */
+static int
+LDAPDN_validate( LDAPDN dn )
+{
+ int iRDN;
+ int rc;
+
+ assert( dn != NULL );
+
+ for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) {
+ rc = LDAPRDN_validate( dn[ iRDN ] );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * dn validate routine
+ */
+int
+dnValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ LDAPDN dn = NULL;
+
+ assert( in != NULL );
+
+ if ( in->bv_len == 0 ) {
+ return LDAP_SUCCESS;
+
+ } else if ( in->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = ldap_bv2dn( in, &dn, LDAP_DN_FORMAT_LDAP );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( in->bv_val ) == in->bv_len );
+
+ /*
+ * Schema-aware validate
+ */
+ rc = LDAPDN_validate( dn );
+ ldap_dnfree( dn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+rdnValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ LDAPRDN rdn;
+ char* p;
+
+ assert( in != NULL );
+ if ( in->bv_len == 0 ) {
+ return LDAP_SUCCESS;
+
+ } else if ( in->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = ldap_bv2rdn_x( in , &rdn, (char **) &p,
+ LDAP_DN_FORMAT_LDAP, NULL);
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( in->bv_val ) == in->bv_len );
+
+ /*
+ * Schema-aware validate
+ */
+ rc = LDAPRDN_validate( rdn );
+ ldap_rdnfree( rdn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+
+/*
+ * AVA sorting inside a RDN
+ *
+ * Rule: sort attributeTypes in alphabetical order.
+ *
+ * Note: the sorting can be slightly improved by sorting first
+ * by attribute type length, then by alphabetical order.
+ *
+ * uses an insertion sort; should be fine since the number of AVAs in
+ * a RDN should be limited.
+ */
+static int
+AVA_Sort( LDAPRDN rdn, int nAVAs )
+{
+ LDAPAVA *ava_i;
+ int i;
+ int rc = LDAP_SUCCESS;
+
+ assert( rdn != NULL );
+
+ for ( i = 1; i < nAVAs; i++ ) {
+ LDAPAVA *ava_j;
+ int j;
+
+ ava_i = rdn[ i ];
+ for ( j = i-1; j >=0; j-- ) {
+ int a;
+
+ ava_j = rdn[ j ];
+ a = strcmp( ava_i->la_attr.bv_val, ava_j->la_attr.bv_val );
+
+ /* RFC4512 does not allow multiple AVAs
+ * with the same attribute type in RDN (ITS#5968) */
+ if ( a == 0 )
+ rc = LDAP_INVALID_DN_SYNTAX;
+
+ if ( a > 0 )
+ break;
+
+ rdn[ j+1 ] = rdn[ j ];
+ }
+ rdn[ j+1 ] = ava_i;
+ }
+ return rc;
+}
+
+static int
+LDAPRDN_rewrite( LDAPRDN rdn, unsigned flags, void *ctx )
+{
+
+ int rc, iAVA, do_sort = 0;
+
+ for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
+ LDAPAVA *ava = rdn[ iAVA ];
+ AttributeDescription *ad;
+ slap_syntax_validate_func *validf = NULL;
+ slap_mr_normalize_func *normf = NULL;
+ slap_syntax_transform_func *transf = NULL;
+ MatchingRule *mr = NULL;
+ struct berval bv = BER_BVNULL;
+
+ assert( ava != NULL );
+
+ if ( ( ad = AVA_PRIVATE( ava ) ) == NULL ) {
+ const char *text = NULL;
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = slap_bv2undef_ad( &ava->la_attr,
+ &ad, &text,
+ SLAP_AD_PROXIED|slap_DN_strict );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ ava->la_private = ( void * )ad;
+ do_sort = 1;
+ }
+
+ /*
+ * Replace attr oid/name with the canonical name
+ */
+ ava->la_attr = ad->ad_cname;
+
+ if( ava->la_flags & LDAP_AVA_BINARY ) {
+ /* AVA is binary encoded, not supported */
+ return LDAP_INVALID_SYNTAX;
+
+ /* Do not allow X-ORDERED 'VALUES' naming attributes */
+ } else if( ad->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) {
+ return LDAP_INVALID_SYNTAX;
+
+ } else if( flags & SLAP_LDAPDN_PRETTY ) {
+ transf = ad->ad_type->sat_syntax->ssyn_pretty;
+ if( !transf ) {
+ validf = ad->ad_type->sat_syntax->ssyn_validate;
+ }
+ } else { /* normalization */
+ validf = ad->ad_type->sat_syntax->ssyn_validate;
+ mr = ad->ad_type->sat_equality;
+ if( mr && (!( mr->smr_usage & SLAP_MR_MUTATION_NORMALIZER ))) {
+ normf = mr->smr_normalize;
+ }
+ }
+
+ if ( validf ) {
+ /* validate value before normalization */
+ rc = ( *validf )( ad->ad_type->sat_syntax,
+ ava->la_value.bv_len
+ ? &ava->la_value
+ : (struct berval *) &slap_empty_bv );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( transf ) {
+ /*
+ * transform value by pretty function
+ * if value is empty, use empty_bv
+ */
+ rc = ( *transf )( ad->ad_type->sat_syntax,
+ ava->la_value.bv_len
+ ? &ava->la_value
+ : (struct berval *) &slap_empty_bv,
+ &bv, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( normf ) {
+ /*
+ * normalize value
+ * if value is empty, use empty_bv
+ */
+ rc = ( *normf )(
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ ad->ad_type->sat_syntax,
+ mr,
+ ava->la_value.bv_len
+ ? &ava->la_value
+ : (struct berval *) &slap_empty_bv,
+ &bv, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+
+ if( bv.bv_val ) {
+ if ( ava->la_flags & LDAP_AVA_FREE_VALUE )
+ ber_memfree_x( ava->la_value.bv_val, ctx );
+ ava->la_value = bv;
+ ava->la_flags |= LDAP_AVA_FREE_VALUE;
+ }
+ /* reject empty values */
+ if (!ava->la_value.bv_len) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ rc = LDAP_SUCCESS;
+
+ if ( do_sort ) {
+ rc = AVA_Sort( rdn, iAVA );
+ }
+
+ return rc;
+}
+
+/*
+ * In-place, schema-aware normalization / "pretty"ing of the
+ * structural representation of a distinguished name.
+ */
+static int
+LDAPDN_rewrite( LDAPDN dn, unsigned flags, void *ctx )
+{
+ int iRDN;
+ int rc;
+
+ assert( dn != NULL );
+
+ for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) {
+ rc = LDAPRDN_rewrite( dn[ iRDN ], flags, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+dnNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx)
+{
+ assert( val != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> dnNormalize: <%s>\n", val->bv_val ? val->bv_val : "" );
+
+ if ( val->bv_len != 0 ) {
+ LDAPDN dn = NULL;
+ int rc;
+
+ /*
+ * Go to structural representation
+ */
+ rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPDN_rewrite( dn, 0, ctx ) != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /*
+ * Back to string representation
+ */
+ rc = ldap_dn2bv_x( dn, out,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ ldap_dnfree_x( dn, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ } else {
+ ber_dupbv_x( out, val, ctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dnNormalize: <%s>\n", out->bv_val ? out->bv_val : "" );
+
+ return LDAP_SUCCESS;
+}
+
+int
+rdnNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx)
+{
+ assert( val != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> dnNormalize: <%s>\n", val->bv_val ? val->bv_val : "" );
+ if ( val->bv_len != 0 ) {
+ LDAPRDN rdn = NULL;
+ int rc;
+ char* p;
+
+ /*
+ * Go to structural representation
+ */
+ rc = ldap_bv2rdn_x( val , &rdn, (char **) &p,
+ LDAP_DN_FORMAT_LDAP, ctx);
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPRDN_rewrite( rdn, 0, ctx ) != LDAP_SUCCESS ) {
+ ldap_rdnfree_x( rdn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /*
+ * Back to string representation
+ */
+ rc = ldap_rdn2bv_x( rdn, out,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ ldap_rdnfree_x( rdn, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ } else {
+ ber_dupbv_x( out, val, ctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dnNormalize: <%s>\n", out->bv_val ? out->bv_val : "" );
+
+ return LDAP_SUCCESS;
+}
+
+int
+dnPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx)
+{
+ assert( val != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> dnPretty: <%s>\n", val->bv_val ? val->bv_val : "" );
+
+ if ( val->bv_len == 0 ) {
+ ber_dupbv_x( out, val, ctx );
+
+ } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+
+ } else {
+ LDAPDN dn = NULL;
+ int rc;
+
+ /* FIXME: should be liberal in what we accept */
+ rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPDN_rewrite( dn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* FIXME: not sure why the default isn't pretty */
+ /* RE: the default is the form that is used as
+ * an internal representation; the pretty form
+ * is a variant */
+ rc = ldap_dn2bv_x( dn, out,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ ldap_dnfree_x( dn, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dnPretty: <%s>\n", out->bv_val ? out->bv_val : "" );
+
+ return LDAP_SUCCESS;
+}
+
+int
+rdnPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx)
+{
+ assert( val != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> rdnPretty: <%s>\n", val->bv_val ? val->bv_val : "" );
+
+ if ( val->bv_len == 0 ) {
+ ber_dupbv_x( out, val, ctx );
+
+ } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+
+ } else {
+ LDAPRDN rdn = NULL;
+ int rc;
+ char* p;
+
+ /* FIXME: should be liberal in what we accept */
+ rc = ldap_bv2rdn_x( val , &rdn, (char **) &p,
+ LDAP_DN_FORMAT_LDAP, ctx);
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPRDN_rewrite( rdn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) {
+ ldap_rdnfree_x( rdn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* FIXME: not sure why the default isn't pretty */
+ /* RE: the default is the form that is used as
+ * an internal representation; the pretty form
+ * is a variant */
+ rc = ldap_rdn2bv_x( rdn, out,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ ldap_rdnfree_x( rdn, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dnPretty: <%s>\n", out->bv_val ? out->bv_val : "" );
+
+ return LDAP_SUCCESS;
+}
+
+
+int
+dnPrettyNormalDN(
+ Syntax *syntax,
+ struct berval *val,
+ LDAPDN *dn,
+ int flags,
+ void *ctx )
+{
+ assert( val != NULL );
+ assert( dn != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> dn%sDN: <%s>\n",
+ flags == SLAP_LDAPDN_PRETTY ? "Pretty" : "Normal",
+ val->bv_val ? val->bv_val : "" );
+
+ if ( val->bv_len == 0 ) {
+ return LDAP_SUCCESS;
+
+ } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+
+ } else {
+ int rc;
+
+ /* FIXME: should be liberal in what we accept */
+ rc = ldap_bv2dn_x( val, dn, LDAP_DN_FORMAT_LDAP, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPDN_rewrite( *dn, flags, ctx ) != LDAP_SUCCESS ) {
+ ldap_dnfree_x( *dn, ctx );
+ *dn = NULL;
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dn%sDN\n",
+ flags == SLAP_LDAPDN_PRETTY ? "Pretty" : "Normal" );
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Combination of both dnPretty and dnNormalize
+ */
+int
+dnPrettyNormal(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *pretty,
+ struct berval *normal,
+ void *ctx)
+{
+ assert( val != NULL );
+ assert( pretty != NULL );
+ assert( normal != NULL );
+ Debug( LDAP_DEBUG_TRACE, ">>> dnPrettyNormal: <%s>\n", val->bv_val ? val->bv_val : "" );
+
+ if ( val->bv_len == 0 ) {
+ ber_dupbv_x( pretty, val, ctx );
+ ber_dupbv_x( normal, val, ctx );
+
+ } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ /* too big */
+ return LDAP_INVALID_SYNTAX;
+
+ } else {
+ LDAPDN dn = NULL;
+ int rc;
+
+ pretty->bv_val = NULL;
+ normal->bv_val = NULL;
+ pretty->bv_len = 0;
+ normal->bv_len = 0;
+
+ /* FIXME: should be liberal in what we accept */
+ rc = ldap_bv2dn_x( val, &dn, LDAP_DN_FORMAT_LDAP, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ assert( strlen( val->bv_val ) == val->bv_len );
+
+ /*
+ * Schema-aware rewrite
+ */
+ if ( LDAPDN_rewrite( dn, SLAP_LDAPDN_PRETTY, ctx ) != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = ldap_dn2bv_x( dn, pretty,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( LDAPDN_rewrite( dn, 0, ctx ) != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, ctx );
+ ber_memfree_x( pretty->bv_val, ctx );
+ pretty->bv_val = NULL;
+ pretty->bv_len = 0;
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = ldap_dn2bv_x( dn, normal,
+ LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY, ctx );
+
+ ldap_dnfree_x( dn, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ ber_memfree_x( pretty->bv_val, ctx );
+ pretty->bv_val = NULL;
+ pretty->bv_len = 0;
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< dnPrettyNormal: <%s>, <%s>\n",
+ pretty->bv_val ? pretty->bv_val : "",
+ normal->bv_val ? normal->bv_val : "" );
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * dnMatch routine
+ */
+int
+dnMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match;
+ struct berval *asserted = (struct berval *) assertedValue;
+
+ assert( matchp != NULL );
+ assert( value != NULL );
+ assert( assertedValue != NULL );
+ assert( !BER_BVISNULL( value ) );
+ assert( !BER_BVISNULL( asserted ) );
+
+ match = value->bv_len - asserted->bv_len;
+
+ if ( match == 0 ) {
+ match = memcmp( value->bv_val, asserted->bv_val,
+ value->bv_len );
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "dnMatch %d\n\t\"%s\"\n\t\"%s\"\n",
+ match, value->bv_val, asserted->bv_val );
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/*
+ * dnRelativeMatch routine
+ */
+int
+dnRelativeMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match;
+ struct berval *asserted = (struct berval *) assertedValue;
+
+ assert( matchp != NULL );
+ assert( value != NULL );
+ assert( assertedValue != NULL );
+ assert( !BER_BVISNULL( value ) );
+ assert( !BER_BVISNULL( asserted ) );
+
+ if( mr == slap_schema.si_mr_dnSubtreeMatch ) {
+ if( asserted->bv_len > value->bv_len ) {
+ match = -1;
+ } else if ( asserted->bv_len == value->bv_len ) {
+ match = memcmp( value->bv_val, asserted->bv_val,
+ value->bv_len );
+ } else {
+ if ( BER_BVISEMPTY( asserted ) ) {
+ match = 0;
+ } else if ( DN_SEPARATOR(
+ value->bv_val[value->bv_len - asserted->bv_len - 1] ))
+ {
+ match = memcmp(
+ &value->bv_val[value->bv_len - asserted->bv_len],
+ asserted->bv_val,
+ asserted->bv_len );
+ } else {
+ match = 1;
+ }
+ }
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+ }
+
+ if( mr == slap_schema.si_mr_dnSuperiorMatch ) {
+ asserted = value;
+ value = (struct berval *) assertedValue;
+ mr = slap_schema.si_mr_dnSubordinateMatch;
+ }
+
+ if( mr == slap_schema.si_mr_dnSubordinateMatch ) {
+ if( asserted->bv_len >= value->bv_len ) {
+ match = -1;
+ } else {
+ if ( BER_BVISEMPTY( asserted ) ) {
+ match = 0;
+ } else if ( DN_SEPARATOR(
+ value->bv_val[value->bv_len - asserted->bv_len - 1] ))
+ {
+ match = memcmp(
+ &value->bv_val[value->bv_len - asserted->bv_len],
+ asserted->bv_val,
+ asserted->bv_len );
+ } else {
+ match = 1;
+ }
+ }
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+ }
+
+ if( mr == slap_schema.si_mr_dnOneLevelMatch ) {
+ if( asserted->bv_len >= value->bv_len ) {
+ match = -1;
+ } else {
+ if( DN_SEPARATOR(
+ value->bv_val[value->bv_len - asserted->bv_len - 1] ))
+ {
+ match = memcmp(
+ &value->bv_val[value->bv_len - asserted->bv_len],
+ asserted->bv_val,
+ asserted->bv_len );
+
+ if( !match ) {
+ struct berval rdn;
+ rdn.bv_val = value->bv_val;
+ rdn.bv_len = value->bv_len - asserted->bv_len - 1;
+ match = dnIsOneLevelRDN( &rdn ) ? 0 : 1;
+ }
+ } else {
+ match = 1;
+ }
+ }
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+ }
+
+ /* should not be reachable */
+ assert( 0 );
+ return LDAP_OTHER;
+}
+
+int
+rdnMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match;
+ struct berval *asserted = (struct berval *) assertedValue;
+
+ assert( matchp != NULL );
+ assert( value != NULL );
+ assert( assertedValue != NULL );
+
+ match = value->bv_len - asserted->bv_len;
+
+ if ( match == 0 ) {
+ match = memcmp( value->bv_val, asserted->bv_val,
+ value->bv_len );
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "rdnMatch %d\n\t\"%s\"\n\t\"%s\"\n",
+ match, value->bv_val, asserted->bv_val );
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+
+/*
+ * dnParent - dn's parent, in-place
+ * note: the incoming dn is assumed to be normalized/prettyfied,
+ * so that escaped rdn/ava separators are in '\'+hexpair form
+ *
+ * note: "dn" and "pdn" can point to the same berval;
+ * beware that, in this case, the pointer to the original buffer
+ * will get lost.
+ */
+void
+dnParent(
+ struct berval *dn,
+ struct berval *pdn )
+{
+ char *p;
+
+ p = ber_bvchr( dn, ',' );
+
+ /* one-level dn */
+ if ( p == NULL ) {
+ pdn->bv_val = dn->bv_val + dn->bv_len;
+ pdn->bv_len = 0;
+ return;
+ }
+
+ assert( DN_SEPARATOR( p[ 0 ] ) );
+ p++;
+
+ assert( ATTR_LEADCHAR( p[ 0 ] ) );
+ pdn->bv_len = dn->bv_len - (p - dn->bv_val);
+ pdn->bv_val = p;
+
+ return;
+}
+
+/*
+ * dnRdn - dn's rdn, in-place
+ * note: the incoming dn is assumed to be normalized/prettyfied,
+ * so that escaped rdn/ava separators are in '\'+hexpair form
+ */
+void
+dnRdn(
+ struct berval *dn,
+ struct berval *rdn )
+{
+ char *p;
+
+ *rdn = *dn;
+ p = ber_bvchr( dn, ',' );
+
+ /* one-level dn */
+ if ( p == NULL ) {
+ return;
+ }
+
+ assert( DN_SEPARATOR( p[ 0 ] ) );
+ assert( ATTR_LEADCHAR( p[ 1 ] ) );
+ rdn->bv_len = p - dn->bv_val;
+
+ return;
+}
+
+int
+dnExtractRdn(
+ struct berval *dn,
+ struct berval *rdn,
+ void *ctx )
+{
+ LDAPRDN tmpRDN;
+ const char *p;
+ int rc;
+
+ assert( dn != NULL );
+ assert( rdn != NULL );
+
+ if( dn->bv_len == 0 ) {
+ return LDAP_OTHER;
+ }
+
+ rc = ldap_bv2rdn_x( dn, &tmpRDN, (char **)&p, LDAP_DN_FORMAT_LDAP, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ rc = ldap_rdn2bv_x( tmpRDN, rdn, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY,
+ ctx );
+
+ ldap_rdnfree_x( tmpRDN, ctx );
+ return rc;
+}
+
+/*
+ * We can assume the input is a prettied or normalized DN
+ */
+ber_len_t
+dn_rdnlen(
+ Backend *be,
+ struct berval *dn_in )
+{
+ const char *p;
+
+ assert( dn_in != NULL );
+
+ if ( dn_in == NULL ) {
+ return 0;
+ }
+
+ if ( !dn_in->bv_len ) {
+ return 0;
+ }
+
+ if ( be != NULL && be_issuffix( be, dn_in ) ) {
+ return 0;
+ }
+
+ p = ber_bvchr( dn_in, ',' );
+
+ return p ? (ber_len_t) (p - dn_in->bv_val) : dn_in->bv_len;
+}
+
+
+/* rdnValidate:
+ *
+ * LDAP_SUCCESS if rdn is a legal rdn;
+ * LDAP_INVALID_SYNTAX otherwise (including a sequence of rdns)
+ */
+int
+rdn_validate( struct berval *rdn )
+{
+#if 1
+ /* Major cheat!
+ * input is a pretty or normalized DN
+ * hence, we can just search for ','
+ */
+ if( rdn == NULL || rdn->bv_len == 0 ||
+ rdn->bv_len > SLAP_LDAPDN_MAXLEN )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+ return ber_bvchr( rdn, ',' ) == NULL
+ ? LDAP_SUCCESS : LDAP_INVALID_SYNTAX;
+
+#else
+ LDAPRDN *RDN, **DN[ 2 ] = { &RDN, NULL };
+ const char *p;
+ int rc;
+
+ /*
+ * must be non-empty
+ */
+ if ( rdn == NULL || rdn == '\0' ) {
+ return 0;
+ }
+
+ /*
+ * must be parsable
+ */
+ rc = ldap_bv2rdn( rdn, &RDN, (char **)&p, LDAP_DN_FORMAT_LDAP );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ /*
+ * Must be one-level
+ */
+ if ( p[ 0 ] != '\0' ) {
+ return 0;
+ }
+
+ /*
+ * Schema-aware validate
+ */
+ if ( rc == LDAP_SUCCESS ) {
+ rc = LDAPDN_validate( DN );
+ }
+ ldap_rdnfree( RDN );
+
+ /*
+ * Must validate (there's a repeated parsing ...)
+ */
+ return ( rc == LDAP_SUCCESS );
+#endif
+}
+
+
+/* build_new_dn:
+ *
+ * Used to create the new dn of entries being renamed.
+ *
+ * new_dn = parent (p_dn) + separator + rdn (newrdn) + null.
+ */
+
+void
+build_new_dn( struct berval * new_dn,
+ struct berval * parent_dn,
+ struct berval * newrdn,
+ void *memctx )
+{
+ char *ptr;
+
+ if ( parent_dn == NULL || parent_dn->bv_len == 0 ) {
+ ber_dupbv_x( new_dn, newrdn, memctx );
+ return;
+ }
+
+ new_dn->bv_len = parent_dn->bv_len + newrdn->bv_len + 1;
+ new_dn->bv_val = (char *) slap_sl_malloc( new_dn->bv_len + 1, memctx );
+
+ ptr = lutil_strncopy( new_dn->bv_val, newrdn->bv_val, newrdn->bv_len );
+ *ptr++ = ',';
+ strcpy( ptr, parent_dn->bv_val );
+}
+
+
+/*
+ * dnIsSuffix - tells whether suffix is a suffix of dn.
+ * Both dn and suffix must be normalized.
+ */
+int
+dnIsSuffix(
+ const struct berval *dn,
+ const struct berval *suffix )
+{
+ int d;
+
+ assert( dn != NULL );
+ assert( suffix != NULL );
+
+ d = dn->bv_len - suffix->bv_len;
+
+ /* empty suffix matches any dn */
+ if ( suffix->bv_len == 0 ) {
+ return 1;
+ }
+
+ /* suffix longer than dn */
+ if ( d < 0 ) {
+ return 0;
+ }
+
+ /* no rdn separator or escaped rdn separator */
+ if ( d > 1 && !DN_SEPARATOR( dn->bv_val[ d - 1 ] ) ) {
+ return 0;
+ }
+
+ /* no possible match or malformed dn */
+ if ( d == 1 ) {
+ return 0;
+ }
+
+ /* compare */
+ return( strncmp( dn->bv_val + d, suffix->bv_val, suffix->bv_len ) == 0 );
+}
+
+/*
+ * In place; assumes:
+ * - ndn is normalized
+ * - nbase is normalized
+ * - dnIsSuffix( ndn, nbase ) == TRUE
+ * - LDAP_SCOPE_DEFAULT == LDAP_SCOPE_SUBTREE
+ */
+int
+dnIsWithinScope( struct berval *ndn, struct berval *nbase, int scope )
+{
+ assert( ndn != NULL );
+ assert( nbase != NULL );
+ assert( !BER_BVISNULL( ndn ) );
+ assert( !BER_BVISNULL( nbase ) );
+
+ switch ( scope ) {
+ case LDAP_SCOPE_DEFAULT:
+ case LDAP_SCOPE_SUBTREE:
+ break;
+
+ case LDAP_SCOPE_BASE:
+ if ( ndn->bv_len != nbase->bv_len ) {
+ return 0;
+ }
+ break;
+
+ case LDAP_SCOPE_ONELEVEL: {
+ struct berval pndn;
+ dnParent( ndn, &pndn );
+ if ( pndn.bv_len != nbase->bv_len ) {
+ return 0;
+ }
+ } break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( ndn->bv_len == nbase->bv_len ) {
+ return 0;
+ }
+ break;
+
+ /* unknown scope */
+ default:
+ return -1;
+ }
+
+ return 1;
+}
+
+/*
+ * In place; assumes:
+ * - ndn is normalized
+ * - nbase is normalized
+ * - LDAP_SCOPE_DEFAULT == LDAP_SCOPE_SUBTREE
+ */
+int
+dnIsSuffixScope( struct berval *ndn, struct berval *nbase, int scope )
+{
+ if ( !dnIsSuffix( ndn, nbase ) ) {
+ return 0;
+ }
+
+ return dnIsWithinScope( ndn, nbase, scope );
+}
+
+int
+dnIsOneLevelRDN( struct berval *rdn )
+{
+ ber_len_t len = rdn->bv_len;
+ for ( ; len--; ) {
+ if ( DN_SEPARATOR( rdn->bv_val[ len ] ) ) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+#ifdef HAVE_TLS
+static SLAP_CERT_MAP_FN *DNX509PeerNormalizeCertMap = NULL;
+#endif
+
+int register_certificate_map_function(SLAP_CERT_MAP_FN *fn)
+{
+#ifdef HAVE_TLS
+ if ( DNX509PeerNormalizeCertMap == NULL ) {
+ DNX509PeerNormalizeCertMap = fn;
+ return 0;
+ }
+#endif
+
+ return -1;
+}
+
+/*
+ * Convert an X.509 DN into a normalized LDAP DN
+ */
+int
+dnX509normalize( void *x509_name, struct berval *out )
+{
+ /* Invoke the LDAP library's converter with our schema-rewriter */
+ int rc = ldap_X509dn2bv( x509_name, out, LDAPDN_rewrite, 0 );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "dnX509Normalize: <%s> (%d)\n",
+ BER_BVISNULL( out ) ? "(null)" : out->bv_val, rc );
+
+ return rc;
+}
+
+#ifdef HAVE_TLS
+/*
+ * Get the TLS session's peer's DN into a normalized LDAP DN
+ */
+int
+dnX509peerNormalize( void *ssl, struct berval *dn )
+{
+ int rc = LDAP_INVALID_CREDENTIALS;
+
+ if ( DNX509PeerNormalizeCertMap != NULL )
+ rc = (*DNX509PeerNormalizeCertMap)( ssl, dn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ rc = ldap_pvt_tls_get_peer_dn( ssl, dn,
+ (LDAPDN_rewrite_dummy *)LDAPDN_rewrite, 0 );
+ }
+
+ return rc;
+}
+#endif
diff --git a/servers/slapd/entry.c b/servers/slapd/entry.c
new file mode 100644
index 0000000..8e1463c
--- /dev/null
+++ b/servers/slapd/entry.c
@@ -0,0 +1,1025 @@
+/* entry.c - routines for dealing with entries */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "ldif.h"
+
+static char *ebuf; /* buf returned by entry2str */
+static char *ecur; /* pointer to end of currently used ebuf */
+static int emaxsize;/* max size of ebuf */
+
+/*
+ * Empty root entry
+ */
+const Entry slap_entry_root = {
+ NOID, { 0, "" }, { 0, "" }, NULL, 0, { 0, "" }, NULL
+};
+
+/*
+ * these mutexes must be used when calling the entry2str()
+ * routine since it returns a pointer to static data.
+ */
+ldap_pvt_thread_mutex_t entry2str_mutex;
+
+static const struct berval dn_bv = BER_BVC("dn");
+
+/*
+ * Entry free list
+ *
+ * Allocate in chunks, minimum of 1000 at a time.
+ */
+#define CHUNK_SIZE 1000
+typedef struct slap_list {
+ struct slap_list *next;
+} slap_list;
+static slap_list *entry_chunks;
+static Entry *entry_list;
+static ldap_pvt_thread_mutex_t entry_mutex;
+
+int entry_destroy(void)
+{
+ slap_list *e;
+ if ( ebuf ) free( ebuf );
+ ebuf = NULL;
+ ecur = NULL;
+ emaxsize = 0;
+
+ for ( e=entry_chunks; e; e=entry_chunks ) {
+ entry_chunks = e->next;
+ free( e );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &entry_mutex );
+ ldap_pvt_thread_mutex_destroy( &entry2str_mutex );
+ return attr_destroy();
+}
+
+int
+entry_init(void)
+{
+ ldap_pvt_thread_mutex_init( &entry2str_mutex );
+ ldap_pvt_thread_mutex_init( &entry_mutex );
+ return attr_init();
+}
+
+Entry *
+str2entry( char *s )
+{
+ return str2entry2( s, 1 );
+}
+
+#define bvcasematch(bv1, bv2) (ber_bvstrcasecmp(bv1, bv2) == 0)
+
+Entry *
+str2entry2( char *s, int checkvals )
+{
+ int rc;
+ Entry *e;
+ struct berval *type, *vals, *nvals;
+ char *freeval;
+ AttributeDescription *ad, *ad_prev;
+ const char *text;
+ char *next;
+ int attr_cnt;
+ int i, lines;
+ Attribute ahead, *atail;
+
+ /*
+ * LDIF is used as the string format.
+ * An entry looks like this:
+ *
+ * dn: <dn>\n
+ * [<attr>:[:] <value>\n]
+ * [<tab><continuedvalue>\n]*
+ * ...
+ *
+ * If a double colon is used after a type, it means the
+ * following value is encoded as a base 64 string. This
+ * happens if the value contains a non-printing character
+ * or newline.
+ */
+
+ Debug( LDAP_DEBUG_TRACE, "=> str2entry: \"%s\"\n",
+ s ? s : "NULL" );
+
+ e = entry_alloc();
+
+ if( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= str2entry NULL (entry allocation failed)\n" );
+ return( NULL );
+ }
+
+ /* initialize entry */
+ e->e_id = NOID;
+
+ /* dn + attributes */
+ atail = &ahead;
+ ahead.a_next = NULL;
+ ad = NULL;
+ ad_prev = NULL;
+ attr_cnt = 0;
+ next = s;
+
+ lines = ldif_countlines( s );
+ type = ch_calloc( 1, (lines+1)*3*sizeof(struct berval)+lines );
+ vals = type+lines+1;
+ nvals = vals+lines+1;
+ freeval = (char *)(nvals+lines+1);
+ i = -1;
+
+ /* parse into individual values, record DN */
+ while ( (s = ldif_getline( &next )) != NULL ) {
+ int freev;
+ if ( *s == '\n' || *s == '\0' ) {
+ break;
+ }
+ i++;
+ if (i >= lines) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= str2entry ran past end of entry\n" );
+ goto fail;
+ }
+
+ rc = ldif_parse_line2( s, type+i, vals+i, &freev );
+ freeval[i] = freev;
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= str2entry NULL (parse_line)\n" );
+ continue;
+ }
+
+ if ( bvcasematch( &type[i], &dn_bv ) ) {
+ if ( e->e_dn != NULL ) {
+ Debug( LDAP_DEBUG_ANY, "str2entry: "
+ "entry %ld has multiple DNs \"%s\" and \"%s\"\n",
+ (long) e->e_id, e->e_dn, vals[i].bv_val );
+ goto fail;
+ }
+
+ rc = dnPrettyNormal( NULL, &vals[i], &e->e_name, &e->e_nname, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "str2entry: "
+ "entry %ld has invalid DN \"%s\"\n",
+ (long) e->e_id, vals[i].bv_val );
+ goto fail;
+ }
+ if ( freeval[i] ) free( vals[i].bv_val );
+ vals[i].bv_val = NULL;
+ i--;
+ continue;
+ }
+ }
+ lines = i+1;
+
+ /* check to make sure there was a dn: line */
+ if ( BER_BVISNULL( &e->e_name )) {
+ Debug( LDAP_DEBUG_ANY, "str2entry: entry %ld has no dn\n",
+ (long) e->e_id );
+ goto fail;
+ }
+
+ /* Make sure all attributes with multiple values are contiguous */
+ if ( checkvals ) {
+ int j, k;
+ struct berval bv;
+ int fv;
+
+ for (i=0; i<lines; i++) {
+ for ( j=i+1; j<lines; j++ ) {
+ if ( bvcasematch( type+i, type+j )) {
+ /* out of order, move intervening attributes down */
+ if ( j != i+1 ) {
+ bv = vals[j];
+ fv = freeval[j];
+ for ( k=j; k>i; k-- ) {
+ type[k] = type[k-1];
+ vals[k] = vals[k-1];
+ freeval[k] = freeval[k-1];
+ }
+ k++;
+ type[k] = type[i];
+ vals[k] = bv;
+ freeval[k] = fv;
+ }
+ i++;
+ }
+ }
+ }
+ }
+
+ if ( lines > 0 ) {
+ for ( i=0; i<=lines; i++ ) {
+ ad_prev = ad;
+ if ( !ad || ( i<lines && !bvcasematch( type+i, &ad->ad_cname ))) {
+ ad = NULL;
+ rc = slap_bv2ad( type+i, &ad, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ int wtool = ( slapMode & (SLAP_TOOL_MODE|SLAP_TOOL_READONLY|SLAP_TOOL_NO_SCHEMA_CHECK) ) == SLAP_TOOL_MODE;
+ Debug( wtool ? LDAP_DEBUG_ANY : LDAP_DEBUG_TRACE,
+ "<= str2entry: str2ad(%s): %s\n", type[i].bv_val, text );
+ if( wtool ) {
+ goto fail;
+ }
+
+ rc = slap_bv2undef_ad( type+i, &ad, &text, 0 );
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= str2entry: slap_str2undef_ad(%s): %s\n",
+ type[i].bv_val, text );
+ goto fail;
+ }
+ }
+
+ /* require ';binary' when appropriate (ITS#5071) */
+ if ( slap_syntax_is_binary( ad->ad_type->sat_syntax ) && !slap_ad_is_binary( ad ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "str2entry: attributeType %s #%d: "
+ "needs ';binary' transfer as per syntax %s\n",
+ ad->ad_cname.bv_val, 0,
+ ad->ad_type->sat_syntax->ssyn_oid );
+ goto fail;
+ }
+ }
+
+ if (( ad_prev && ad != ad_prev ) || ( i == lines )) {
+ int j, k;
+ atail->a_next = attr_alloc( NULL );
+ atail = atail->a_next;
+ atail->a_flags = 0;
+ atail->a_numvals = attr_cnt;
+ atail->a_desc = ad_prev;
+ atail->a_vals = ch_malloc( (attr_cnt + 1) * sizeof(struct berval));
+ if( ad_prev->ad_type->sat_equality &&
+ ad_prev->ad_type->sat_equality->smr_normalize )
+ atail->a_nvals = ch_malloc( (attr_cnt + 1) * sizeof(struct berval));
+ else
+ atail->a_nvals = NULL;
+ k = i - attr_cnt;
+ for ( j=0; j<attr_cnt; j++ ) {
+ if ( freeval[k] )
+ atail->a_vals[j] = vals[k];
+ else
+ ber_dupbv( atail->a_vals+j, &vals[k] );
+ vals[k].bv_val = NULL;
+ if ( atail->a_nvals ) {
+ atail->a_nvals[j] = nvals[k];
+ nvals[k].bv_val = NULL;
+ }
+ k++;
+ }
+ BER_BVZERO( &atail->a_vals[j] );
+ if ( atail->a_nvals ) {
+ BER_BVZERO( &atail->a_nvals[j] );
+ } else {
+ atail->a_nvals = atail->a_vals;
+ }
+ attr_cnt = 0;
+ /* FIXME: we only need this when migrating from an unsorted DB */
+ if ( atail->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ rc = slap_sort_vals( (Modifications *)atail, &text, &j, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ atail->a_flags |= SLAP_ATTR_SORTED_VALS;
+ } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "str2entry: attributeType %s value #%d provided more than once\n",
+ atail->a_desc->ad_cname.bv_val, j );
+ goto fail;
+ }
+ }
+ if ( i == lines ) break;
+ }
+
+ if ( BER_BVISNULL( &vals[i] ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "str2entry: attributeType %s #%d: "
+ "no value\n",
+ ad->ad_cname.bv_val, attr_cnt );
+ goto fail;
+ }
+
+ if ( ad->ad_type->sat_equality &&
+ ad->ad_type->sat_equality->smr_normalize )
+ {
+ rc = ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ ad,
+ ad->ad_type->sat_equality,
+ &vals[i], &nvals[i], NULL );
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= str2entry NULL (smr_normalize %s %d)\n", ad->ad_cname.bv_val, rc );
+ goto fail;
+ }
+ }
+
+ attr_cnt++;
+ }
+ }
+
+ free( type );
+ atail->a_next = NULL;
+ e->e_attrs = ahead.a_next;
+
+ Debug(LDAP_DEBUG_TRACE, "<= str2entry(%s) -> 0x%lx\n",
+ e->e_dn, (unsigned long) e );
+ return( e );
+
+fail:
+ for ( i=0; i<lines; i++ ) {
+ if ( freeval[i] ) free( vals[i].bv_val );
+ free( nvals[i].bv_val );
+ }
+ free( type );
+ entry_free( e );
+ return NULL;
+}
+
+
+#define GRABSIZE BUFSIZ
+
+#define MAKE_SPACE( n ) { \
+ while ( ecur + (n) > ebuf + emaxsize ) { \
+ ptrdiff_t offset; \
+ offset = (int) (ecur - ebuf); \
+ ebuf = ch_realloc( ebuf, \
+ emaxsize + GRABSIZE ); \
+ emaxsize += GRABSIZE; \
+ ecur = ebuf + offset; \
+ } \
+ }
+
+/* NOTE: only preserved for binary compatibility */
+char *
+entry2str(
+ Entry *e,
+ int *len )
+{
+ return entry2str_wrap( e, len, LDIF_LINE_WIDTH );
+}
+
+char *
+entry2str_wrap(
+ Entry *e,
+ int *len,
+ ber_len_t wrap )
+{
+ Attribute *a;
+ struct berval *bv;
+ int i;
+ ber_len_t tmplen;
+
+ assert( e != NULL );
+
+ /*
+ * In string format, an entry looks like this:
+ * dn: <dn>\n
+ * [<attr>: <value>\n]*
+ */
+
+ ecur = ebuf;
+
+ /* put the dn */
+ if ( e->e_dn != NULL ) {
+ /* put "dn: <dn>" */
+ tmplen = e->e_name.bv_len;
+ MAKE_SPACE( LDIF_SIZE_NEEDED( 2, tmplen ));
+ ldif_sput_wrap( &ecur, LDIF_PUT_VALUE, "dn", e->e_dn, tmplen, wrap );
+ }
+
+ /* put the attributes */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ /* put "<type>:[:] <value>" line for each value */
+ for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) {
+ bv = &a->a_vals[i];
+ tmplen = a->a_desc->ad_cname.bv_len;
+ MAKE_SPACE( LDIF_SIZE_NEEDED( tmplen, bv->bv_len ));
+ ldif_sput_wrap( &ecur, LDIF_PUT_VALUE,
+ a->a_desc->ad_cname.bv_val,
+ bv->bv_val, bv->bv_len, wrap );
+ }
+ }
+ MAKE_SPACE( 1 );
+ *ecur = '\0';
+ *len = ecur - ebuf;
+
+ return( ebuf );
+}
+
+void
+entry_clean( Entry *e )
+{
+ /* free an entry structure */
+ assert( e != NULL );
+
+ /* e_private must be freed by the caller */
+ assert( e->e_private == NULL );
+
+ e->e_id = 0;
+
+ /* free DNs */
+ if ( !BER_BVISNULL( &e->e_name ) ) {
+ free( e->e_name.bv_val );
+ BER_BVZERO( &e->e_name );
+ }
+ if ( !BER_BVISNULL( &e->e_nname ) ) {
+ free( e->e_nname.bv_val );
+ BER_BVZERO( &e->e_nname );
+ }
+
+ if ( !BER_BVISNULL( &e->e_bv ) ) {
+ free( e->e_bv.bv_val );
+ BER_BVZERO( &e->e_bv );
+ }
+
+ /* free attributes */
+ if ( e->e_attrs ) {
+ attrs_free( e->e_attrs );
+ e->e_attrs = NULL;
+ }
+
+ e->e_ocflags = 0;
+}
+
+void
+entry_free( Entry *e )
+{
+ entry_clean( e );
+
+ ldap_pvt_thread_mutex_lock( &entry_mutex );
+ e->e_private = entry_list;
+ entry_list = e;
+ ldap_pvt_thread_mutex_unlock( &entry_mutex );
+}
+
+/* These parameters work well on AMD64 */
+#if 0
+#define STRIDE 8
+#define STRIPE 5
+#else
+#define STRIDE 1
+#define STRIPE 1
+#endif
+#define STRIDE_FACTOR (STRIDE*STRIPE)
+
+int
+entry_prealloc( int num )
+{
+ Entry *e, **prev, *tmp;
+ slap_list *s;
+ int i, j;
+
+ if (!num) return 0;
+
+#if STRIDE_FACTOR > 1
+ /* Round up to our stride factor */
+ num += STRIDE_FACTOR-1;
+ num /= STRIDE_FACTOR;
+ num *= STRIDE_FACTOR;
+#endif
+
+ s = ch_calloc( 1, sizeof(slap_list) + num * sizeof(Entry));
+ s->next = entry_chunks;
+ entry_chunks = s;
+
+ prev = &tmp;
+ for (i=0; i<STRIPE; i++) {
+ e = (Entry *)(s+1);
+ e += i;
+ for (j=i; j<num; j+= STRIDE) {
+ *prev = e;
+ prev = (Entry **)&e->e_private;
+ e += STRIDE;
+ }
+ }
+ *prev = entry_list;
+ entry_list = (Entry *)(s+1);
+
+ return 0;
+}
+
+Entry *
+entry_alloc( void )
+{
+ Entry *e;
+
+ ldap_pvt_thread_mutex_lock( &entry_mutex );
+ if ( !entry_list )
+ entry_prealloc( CHUNK_SIZE );
+ e = entry_list;
+ entry_list = e->e_private;
+ e->e_private = NULL;
+ ldap_pvt_thread_mutex_unlock( &entry_mutex );
+
+ return e;
+}
+
+
+/*
+ * These routines are used only by Backend.
+ *
+ * the Entry has three entry points (ways to find things):
+ *
+ * by entry e.g., if you already have an entry from the cache
+ * and want to delete it. (really by entry ptr)
+ * by dn e.g., when looking for the base object of a search
+ * by id e.g., for search candidates
+ *
+ * these correspond to three different avl trees that are maintained.
+ */
+
+int
+entry_cmp( Entry *e1, Entry *e2 )
+{
+ return SLAP_PTRCMP( e1, e2 );
+}
+
+int
+entry_dn_cmp( const void *v_e1, const void *v_e2 )
+{
+ /* compare their normalized UPPERCASED dn's */
+ const Entry *e1 = v_e1, *e2 = v_e2;
+
+ return ber_bvcmp( &e1->e_nname, &e2->e_nname );
+}
+
+int
+entry_id_cmp( const void *v_e1, const void *v_e2 )
+{
+ const Entry *e1 = v_e1, *e2 = v_e2;
+ return( e1->e_id < e2->e_id ? -1 : (e1->e_id > e2->e_id ? 1 : 0) );
+}
+
+/* This is like a ber_len */
+#define entry_lenlen(l) (((l) < 0x80) ? 1 : ((l) < 0x100) ? 2 : \
+ ((l) < 0x10000) ? 3 : ((l) < 0x1000000) ? 4 : 5)
+
+static void
+entry_putlen(unsigned char **buf, ber_len_t len)
+{
+ ber_len_t lenlen = entry_lenlen(len);
+
+ if (lenlen == 1) {
+ **buf = (unsigned char) len;
+ } else {
+ int i;
+ **buf = 0x80 | ((unsigned char) lenlen - 1);
+ for (i=lenlen-1; i>0; i--) {
+ (*buf)[i] = (unsigned char) len;
+ len >>= 8;
+ }
+ }
+ *buf += lenlen;
+}
+
+static ber_len_t
+entry_getlen(unsigned char **buf)
+{
+ ber_len_t len;
+ int i;
+
+ len = *(*buf)++;
+ if (len <= 0x7f)
+ return len;
+ i = len & 0x7f;
+ len = 0;
+ for (;i > 0; i--) {
+ len <<= 8;
+ len |= *(*buf)++;
+ }
+ return len;
+}
+
+/* Count up the sizes of the components of an entry */
+void entry_partsize(Entry *e, ber_len_t *plen,
+ int *pnattrs, int *pnvals, int norm)
+{
+ ber_len_t len, dnlen, ndnlen;
+ int i, nat = 0, nval = 0;
+ Attribute *a;
+
+ dnlen = e->e_name.bv_len;
+ len = dnlen + 1; /* trailing NUL byte */
+ len += entry_lenlen(dnlen);
+ if (norm) {
+ ndnlen = e->e_nname.bv_len;
+ len += ndnlen + 1;
+ len += entry_lenlen(ndnlen);
+ }
+ for (a=e->e_attrs; a; a=a->a_next) {
+ /* For AttributeDesc, we only store the attr name */
+ nat++;
+ len += a->a_desc->ad_cname.bv_len+1;
+ len += entry_lenlen(a->a_desc->ad_cname.bv_len);
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ nval++;
+ len += a->a_vals[i].bv_len + 1;
+ len += entry_lenlen(a->a_vals[i].bv_len);
+ }
+ len += entry_lenlen(i);
+ nval++; /* empty berval at end */
+ if (norm && a->a_nvals != a->a_vals) {
+ for (i=0; a->a_nvals[i].bv_val; i++) {
+ nval++;
+ len += a->a_nvals[i].bv_len + 1;
+ len += entry_lenlen(a->a_nvals[i].bv_len);
+ }
+ len += entry_lenlen(i); /* i nvals */
+ nval++;
+ } else {
+ len += entry_lenlen(0); /* 0 nvals */
+ }
+ }
+ len += entry_lenlen(nat);
+ len += entry_lenlen(nval);
+ *plen = len;
+ *pnattrs = nat;
+ *pnvals = nval;
+}
+
+/* Add up the size of the entry for a flattened buffer */
+ber_len_t entry_flatsize(Entry *e, int norm)
+{
+ ber_len_t len;
+ int nattrs, nvals;
+
+ entry_partsize(e, &len, &nattrs, &nvals, norm);
+ len += sizeof(Entry) + (nattrs * sizeof(Attribute)) +
+ (nvals * sizeof(struct berval));
+ return len;
+}
+
+/* Flatten an Entry into a buffer. The buffer is filled with just the
+ * strings/bervals of all the entry components. Each field is preceded
+ * by its length, encoded the way ber_put_len works. Every field is NUL
+ * terminated. The entire buffer size is precomputed so that a single
+ * malloc can be performed. The entry size is also recorded,
+ * to aid in entry_decode.
+ */
+int entry_encode(Entry *e, struct berval *bv)
+{
+ ber_len_t len, dnlen, ndnlen, i;
+ int nattrs, nvals;
+ Attribute *a;
+ unsigned char *ptr;
+
+ Debug( LDAP_DEBUG_TRACE, "=> entry_encode(0x%08lx): %s\n",
+ (long) e->e_id, e->e_dn );
+
+ dnlen = e->e_name.bv_len;
+ ndnlen = e->e_nname.bv_len;
+
+ entry_partsize( e, &len, &nattrs, &nvals, 1 );
+
+ bv->bv_len = len;
+ bv->bv_val = ch_malloc(len);
+ ptr = (unsigned char *)bv->bv_val;
+ entry_putlen(&ptr, nattrs);
+ entry_putlen(&ptr, nvals);
+ entry_putlen(&ptr, dnlen);
+ AC_MEMCPY(ptr, e->e_dn, dnlen);
+ ptr += dnlen;
+ *ptr++ = '\0';
+ entry_putlen(&ptr, ndnlen);
+ AC_MEMCPY(ptr, e->e_ndn, ndnlen);
+ ptr += ndnlen;
+ *ptr++ = '\0';
+
+ for (a=e->e_attrs; a; a=a->a_next) {
+ entry_putlen(&ptr, a->a_desc->ad_cname.bv_len);
+ AC_MEMCPY(ptr, a->a_desc->ad_cname.bv_val,
+ a->a_desc->ad_cname.bv_len);
+ ptr += a->a_desc->ad_cname.bv_len;
+ *ptr++ = '\0';
+ if (a->a_vals) {
+ for (i=0; a->a_vals[i].bv_val; i++);
+ assert( i == a->a_numvals );
+ entry_putlen(&ptr, i);
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ entry_putlen(&ptr, a->a_vals[i].bv_len);
+ AC_MEMCPY(ptr, a->a_vals[i].bv_val,
+ a->a_vals[i].bv_len);
+ ptr += a->a_vals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ if (a->a_nvals != a->a_vals) {
+ entry_putlen(&ptr, i);
+ for (i=0; a->a_nvals[i].bv_val; i++) {
+ entry_putlen(&ptr, a->a_nvals[i].bv_len);
+ AC_MEMCPY(ptr, a->a_nvals[i].bv_val,
+ a->a_nvals[i].bv_len);
+ ptr += a->a_nvals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ } else {
+ entry_putlen(&ptr, 0);
+ }
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= entry_encode(0x%08lx): %s\n",
+ (long) e->e_id, e->e_dn );
+
+ return 0;
+}
+
+/* Retrieve an Entry that was stored using entry_encode above.
+ * First entry_header must be called to decode the size of the entry.
+ * Then a single block of memory must be malloc'd to accommodate the
+ * bervals and the bulk data. Next the bulk data is retrieved from
+ * the DB and parsed by entry_decode.
+ *
+ * Note: everything is stored in a single contiguous block, so
+ * you can not free individual attributes or names from this
+ * structure. Attempting to do so will likely corrupt memory.
+ */
+int entry_header(EntryHeader *eh)
+{
+ unsigned char *ptr = (unsigned char *)eh->bv.bv_val;
+
+ /* Some overlays can create empty entries
+ * so don't check for zeros here.
+ */
+ eh->nattrs = entry_getlen(&ptr);
+ eh->nvals = entry_getlen(&ptr);
+ eh->data = (char *)ptr;
+ return LDAP_SUCCESS;
+}
+
+int
+entry_decode_dn( EntryHeader *eh, struct berval *dn, struct berval *ndn )
+{
+ int i;
+ unsigned char *ptr = (unsigned char *)eh->bv.bv_val;
+
+ assert( dn != NULL || ndn != NULL );
+
+ ptr = (unsigned char *)eh->data;
+ i = entry_getlen(&ptr);
+ if ( dn != NULL ) {
+ dn->bv_val = (char *) ptr;
+ dn->bv_len = i;
+ }
+
+ if ( ndn != NULL ) {
+ ptr += i + 1;
+ i = entry_getlen(&ptr);
+ ndn->bv_val = (char *) ptr;
+ ndn->bv_len = i;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "entry_decode_dn: \"%s\"\n",
+ dn ? dn->bv_val : ndn->bv_val );
+
+ return 0;
+}
+
+#ifdef SLAP_ZONE_ALLOC
+int entry_decode(EntryHeader *eh, Entry **e, void *ctx)
+#else
+int entry_decode(EntryHeader *eh, Entry **e)
+#endif
+{
+ int i, j, nattrs, nvals;
+ int rc;
+ Attribute *a;
+ Entry *x;
+ const char *text;
+ AttributeDescription *ad;
+ unsigned char *ptr = (unsigned char *)eh->bv.bv_val;
+ BerVarray bptr;
+
+ nattrs = eh->nattrs;
+ nvals = eh->nvals;
+ x = entry_alloc();
+ x->e_attrs = attrs_alloc( nattrs );
+ ptr = (unsigned char *)eh->data;
+ i = entry_getlen(&ptr);
+ x->e_name.bv_val = (char *) ptr;
+ x->e_name.bv_len = i;
+ ptr += i+1;
+ i = entry_getlen(&ptr);
+ x->e_nname.bv_val = (char *) ptr;
+ x->e_nname.bv_len = i;
+ ptr += i+1;
+ Debug( LDAP_DEBUG_TRACE,
+ "entry_decode: \"%s\"\n",
+ x->e_dn );
+ x->e_bv = eh->bv;
+
+ a = x->e_attrs;
+ bptr = (BerVarray)eh->bv.bv_val;
+
+ while (((char *)ptr - eh->bv.bv_val < eh->bv.bv_len) &&
+ (i = entry_getlen(&ptr))) {
+ struct berval bv;
+ bv.bv_len = i;
+ bv.bv_val = (char *) ptr;
+ ad = NULL;
+ rc = slap_bv2ad( &bv, &ad, &text );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<= entry_decode: str2ad(%s): %s\n", ptr, text );
+ rc = slap_bv2undef_ad( &bv, &ad, &text, 0 );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= entry_decode: slap_str2undef_ad(%s): %s\n",
+ ptr, text );
+ return rc;
+ }
+ }
+ ptr += i + 1;
+ a->a_desc = ad;
+ a->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS;
+ j = entry_getlen(&ptr);
+ a->a_numvals = j;
+ a->a_vals = bptr;
+
+ while (j) {
+ i = entry_getlen(&ptr);
+ bptr->bv_len = i;
+ bptr->bv_val = (char *)ptr;
+ ptr += i+1;
+ bptr++;
+ j--;
+ }
+ bptr->bv_val = NULL;
+ bptr->bv_len = 0;
+ bptr++;
+
+ j = entry_getlen(&ptr);
+ if (j) {
+ a->a_nvals = bptr;
+ while (j) {
+ i = entry_getlen(&ptr);
+ bptr->bv_len = i;
+ bptr->bv_val = (char *)ptr;
+ ptr += i+1;
+ bptr++;
+ j--;
+ }
+ bptr->bv_val = NULL;
+ bptr->bv_len = 0;
+ bptr++;
+ } else {
+ a->a_nvals = a->a_vals;
+ }
+ /* FIXME: This is redundant once a sorted entry is saved into the DB */
+ if ( a->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ rc = slap_sort_vals( (Modifications *)a, &text, &j, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ a->a_flags |= SLAP_ATTR_SORTED_VALS;
+ } else if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ /* should never happen */
+ Debug( LDAP_DEBUG_ANY,
+ "entry_decode: attributeType %s value #%d provided more than once\n",
+ a->a_desc->ad_cname.bv_val, j );
+ return rc;
+ }
+ }
+ a = a->a_next;
+ nattrs--;
+ if ( !nattrs )
+ break;
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "<= entry_decode(%s)\n",
+ x->e_dn );
+ *e = x;
+ return 0;
+}
+
+Entry *
+entry_dup2( Entry *dest, Entry *source )
+{
+ assert( dest != NULL );
+ assert( source != NULL );
+
+ assert( dest->e_private == NULL );
+
+ dest->e_id = source->e_id;
+ ber_dupbv( &dest->e_name, &source->e_name );
+ ber_dupbv( &dest->e_nname, &source->e_nname );
+ dest->e_attrs = attrs_dup( source->e_attrs );
+ dest->e_ocflags = source->e_ocflags;
+
+ return dest;
+}
+
+Entry *
+entry_dup( Entry *e )
+{
+ return entry_dup2( entry_alloc(), e );
+}
+
+#if 1
+/* Duplicates an entry using a single malloc. Saves CPU time, increases
+ * heap usage because a single large malloc is harder to satisfy than
+ * lots of small ones, and the freed space isn't as easily reusable.
+ *
+ * Probably not worth using this function.
+ */
+Entry *entry_dup_bv( Entry *e )
+{
+ ber_len_t len;
+ int nattrs, nvals;
+ Entry *ret;
+ struct berval *bvl;
+ char *ptr;
+ Attribute *src, *dst;
+
+ ret = entry_alloc();
+
+ entry_partsize(e, &len, &nattrs, &nvals, 1);
+ ret->e_id = e->e_id;
+ ret->e_attrs = attrs_alloc( nattrs );
+ ret->e_ocflags = e->e_ocflags;
+ ret->e_bv.bv_len = len + nvals * sizeof(struct berval);
+ ret->e_bv.bv_val = ch_malloc( ret->e_bv.bv_len );
+
+ bvl = (struct berval *)ret->e_bv.bv_val;
+ ptr = (char *)(bvl + nvals);
+
+ ret->e_name.bv_len = e->e_name.bv_len;
+ ret->e_name.bv_val = ptr;
+ AC_MEMCPY( ptr, e->e_name.bv_val, e->e_name.bv_len );
+ ptr += e->e_name.bv_len;
+ *ptr++ = '\0';
+
+ ret->e_nname.bv_len = e->e_nname.bv_len;
+ ret->e_nname.bv_val = ptr;
+ AC_MEMCPY( ptr, e->e_nname.bv_val, e->e_nname.bv_len );
+ ptr += e->e_name.bv_len;
+ *ptr++ = '\0';
+
+ dst = ret->e_attrs;
+ for (src = e->e_attrs; src; src=src->a_next,dst=dst->a_next ) {
+ int i;
+ dst->a_desc = src->a_desc;
+ dst->a_flags = SLAP_ATTR_DONT_FREE_DATA | SLAP_ATTR_DONT_FREE_VALS;
+ dst->a_vals = bvl;
+ dst->a_numvals = src->a_numvals;
+ for ( i=0; src->a_vals[i].bv_val; i++ ) {
+ bvl->bv_len = src->a_vals[i].bv_len;
+ bvl->bv_val = ptr;
+ AC_MEMCPY( ptr, src->a_vals[i].bv_val, bvl->bv_len );
+ ptr += bvl->bv_len;
+ *ptr++ = '\0';
+ bvl++;
+ }
+ BER_BVZERO(bvl);
+ bvl++;
+ if ( src->a_vals != src->a_nvals ) {
+ dst->a_nvals = bvl;
+ for ( i=0; src->a_nvals[i].bv_val; i++ ) {
+ bvl->bv_len = src->a_nvals[i].bv_len;
+ bvl->bv_val = ptr;
+ AC_MEMCPY( ptr, src->a_nvals[i].bv_val, bvl->bv_len );
+ ptr += bvl->bv_len;
+ *ptr++ = '\0';
+ bvl++;
+ }
+ BER_BVZERO(bvl);
+ bvl++;
+ }
+ }
+ return ret;
+}
+#endif
diff --git a/servers/slapd/extended.c b/servers/slapd/extended.c
new file mode 100644
index 0000000..3f17889
--- /dev/null
+++ b/servers/slapd/extended.c
@@ -0,0 +1,463 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/*
+ * LDAPv3 Extended Operation Request
+ * ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
+ * requestName [0] LDAPOID,
+ * requestValue [1] OCTET STRING OPTIONAL
+ * }
+ *
+ * LDAPv3 Extended Operation Response
+ * ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
+ * COMPONENTS OF LDAPResult,
+ * responseName [10] LDAPOID OPTIONAL,
+ * response [11] OCTET STRING OPTIONAL
+ * }
+ *
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lber_pvt.h"
+
+static struct extop_list {
+ struct extop_list *next;
+ struct berval oid;
+ slap_mask_t flags;
+ SLAP_EXTOP_MAIN_FN *ext_main;
+} *supp_ext_list = NULL;
+
+static SLAP_EXTOP_MAIN_FN whoami_extop;
+
+/* This list of built-in extops is for extops that are not part
+ * of backends or in external modules. Essentially, this is
+ * just a way to get built-in extops onto the extop list without
+ * having a separate init routine for each built-in extop.
+ */
+static struct {
+ const struct berval *oid;
+ slap_mask_t flags;
+ SLAP_EXTOP_MAIN_FN *ext_main;
+} builtin_extops[] = {
+ { &slap_EXOP_TXN_START, 0, txn_start_extop },
+ { &slap_EXOP_TXN_END, 0, txn_end_extop },
+ { &slap_EXOP_CANCEL, 0, cancel_extop },
+ { &slap_EXOP_WHOAMI, 0, whoami_extop },
+ { &slap_EXOP_MODIFY_PASSWD, SLAP_EXOP_WRITES, passwd_extop },
+ { NULL, 0, NULL }
+};
+
+
+static struct extop_list *find_extop(
+ struct extop_list *list, struct berval *oid );
+
+struct berval *
+get_supported_extop (int index)
+{
+ struct extop_list *ext;
+
+ /* linear scan is slow, but this way doesn't force a
+ * big change on root_dse.c, where this routine is used.
+ */
+ for (ext = supp_ext_list; ext != NULL && --index >= 0; ext = ext->next) {
+ ; /* empty */
+ }
+
+ if (ext == NULL) return NULL;
+
+ return &ext->oid;
+}
+
+
+int exop_root_dse_info( Entry *e )
+{
+ AttributeDescription *ad_supportedExtension
+ = slap_schema.si_ad_supportedExtension;
+ struct berval vals[2];
+ struct extop_list *ext;
+
+ vals[1].bv_val = NULL;
+ vals[1].bv_len = 0;
+
+ for (ext = supp_ext_list; ext != NULL; ext = ext->next) {
+ if( ext->flags & SLAP_EXOP_HIDE ) continue;
+
+ vals[0] = ext->oid;
+
+ if( attr_merge( e, ad_supportedExtension, vals, NULL ) ) {
+ return LDAP_OTHER;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+do_extended(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ struct berval reqdata = {0, NULL};
+ ber_len_t len;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_extended\n",
+ op->o_log_prefix );
+
+ if( op->o_protocol < LDAP_VERSION3 ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_extended: protocol version (%d) too low\n",
+ op->o_log_prefix, op->o_protocol );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "requires LDAPv3" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto done;
+ }
+
+ if ( ber_scanf( op->o_ber, "{m" /*}*/, &op->ore_reqoid ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_extended: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto done;
+ }
+
+ if( ber_peek_tag( op->o_ber, &len ) == LDAP_TAG_EXOP_REQ_VALUE ) {
+ if( ber_scanf( op->o_ber, "m", &reqdata ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_extended: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto done;
+ }
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_extended: get_ctrls failed\n",
+ op->o_log_prefix );
+ return rs->sr_err;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s EXT oid=%s\n",
+ op->o_log_prefix, op->ore_reqoid.bv_val );
+
+ /* check for controls inappropriate for all extended operations */
+ if( get_manageDSAit( op ) == SLAP_CONTROL_CRITICAL ) {
+ send_ldap_error( op, rs,
+ LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "manageDSAit control inappropriate" );
+ goto done;
+ }
+
+ /* FIXME: temporary? */
+ if ( reqdata.bv_val ) {
+ op->ore_reqdata = &reqdata;
+ }
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_extended( op, rs );
+
+ if ( rs->sr_err == SLAPD_ASYNCOP ||
+ rs->sr_err == LDAP_TXN_SPECIFY_OKAY ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+ /* clean up in case some overlay set them? */
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ if ( !BER_BVISNULL( &op->o_req_dn )
+ && op->o_req_ndn.bv_val != op->o_req_dn.bv_val )
+ {
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_dn );
+ BER_BVZERO( &op->o_req_ndn );
+ }
+
+done:
+ return rs->sr_err;
+}
+
+int
+fe_extended( Operation *op, SlapReply *rs )
+{
+ struct extop_list *ext = NULL;
+ struct berval reqdata = BER_BVNULL;
+
+ if (op->ore_reqdata) {
+ reqdata = *op->ore_reqdata;
+ }
+
+ ext = find_extop(supp_ext_list, &op->ore_reqoid );
+ if ( ext == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_extended: unsupported operation \"%s\"\n",
+ op->o_log_prefix, op->ore_reqoid.bv_val );
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR,
+ "unsupported extended operation" );
+ goto done;
+ }
+
+ op->ore_flags = ext->flags;
+
+ Debug( LDAP_DEBUG_ARGS, "do_extended: oid=%s\n",
+ op->ore_reqoid.bv_val );
+
+ { /* start of OpenLDAP extended operation */
+ BackendDB *bd = op->o_bd;
+
+ rs->sr_err = (ext->ext_main)( op, rs );
+
+ if( rs->sr_err != SLAPD_ABANDON && rs->sr_err != SLAPD_ASYNCOP &&
+ rs->sr_err != SLAPD_NO_REPLY ) {
+ if ( rs->sr_err == LDAP_REFERRAL && rs->sr_ref == NULL ) {
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, NULL, LDAP_SCOPE_DEFAULT );
+ if ( !rs->sr_ref ) rs->sr_ref = default_referral;
+ if ( !rs->sr_ref ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "referral missing";
+ }
+ }
+
+ if ( op->o_bd == NULL )
+ op->o_bd = bd;
+ send_ldap_extended( op, rs );
+
+ if ( rs->sr_ref != default_referral ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+
+ if ( rs->sr_rspoid != NULL ) {
+ free( (char *)rs->sr_rspoid );
+ rs->sr_rspoid = NULL;
+ }
+
+ if ( rs->sr_rspdata != NULL ) {
+ ber_bvfree( rs->sr_rspdata );
+ rs->sr_rspdata = NULL;
+ }
+ } /* end of OpenLDAP extended operation */
+
+done:;
+ return rs->sr_err;
+}
+
+int
+load_extop2(
+ const struct berval *ext_oid,
+ slap_mask_t ext_flags,
+ SLAP_EXTOP_MAIN_FN *ext_main,
+ unsigned flags )
+{
+ struct berval oidm = BER_BVNULL;
+ struct extop_list *ext;
+ int insertme = 0;
+
+ if ( !ext_main ) {
+ return -1;
+ }
+
+ if ( ext_oid == NULL || BER_BVISNULL( ext_oid ) ||
+ BER_BVISEMPTY( ext_oid ) )
+ {
+ return -1;
+ }
+
+ if ( numericoidValidate( NULL, (struct berval *)ext_oid ) !=
+ LDAP_SUCCESS )
+ {
+ oidm.bv_val = oidm_find( ext_oid->bv_val );
+ if ( oidm.bv_val == NULL ) {
+ return -1;
+ }
+ oidm.bv_len = strlen( oidm.bv_val );
+ ext_oid = &oidm;
+ }
+
+ for ( ext = supp_ext_list; ext; ext = ext->next ) {
+ if ( bvmatch( ext_oid, &ext->oid ) ) {
+ if ( flags == 1 ) {
+ break;
+ }
+ return -1;
+ }
+ }
+
+ if ( flags == 0 || ext == NULL ) {
+ ext = ch_calloc( 1, sizeof(struct extop_list) + ext_oid->bv_len + 1 );
+ if ( ext == NULL ) {
+ return(-1);
+ }
+
+ ext->oid.bv_val = (char *)(ext + 1);
+ AC_MEMCPY( ext->oid.bv_val, ext_oid->bv_val, ext_oid->bv_len );
+ ext->oid.bv_len = ext_oid->bv_len;
+ ext->oid.bv_val[ext->oid.bv_len] = '\0';
+
+ insertme = 1;
+ }
+
+ ext->flags = ext_flags;
+ ext->ext_main = ext_main;
+
+ if ( insertme ) {
+ ext->next = supp_ext_list;
+ supp_ext_list = ext;
+ }
+
+ return(0);
+}
+
+int
+unload_extop(
+ const struct berval *ext_oid,
+ SLAP_EXTOP_MAIN_FN *ext_main,
+ unsigned flags )
+{
+ struct berval oidm = BER_BVNULL;
+ struct extop_list *ext, **extp;
+
+ /* oid must be given */
+ if ( ext_oid == NULL || BER_BVISNULL( ext_oid ) ||
+ BER_BVISEMPTY( ext_oid ) )
+ {
+ return -1;
+ }
+
+ /* if it's not an oid, check if it's a macto */
+ if ( numericoidValidate( NULL, (struct berval *)ext_oid ) !=
+ LDAP_SUCCESS )
+ {
+ oidm.bv_val = oidm_find( ext_oid->bv_val );
+ if ( oidm.bv_val == NULL ) {
+ return -1;
+ }
+ oidm.bv_len = strlen( oidm.bv_val );
+ ext_oid = &oidm;
+ }
+
+ /* lookup the oid */
+ for ( extp = &supp_ext_list; *extp; extp = &(*extp)->next ) {
+ if ( bvmatch( ext_oid, &(*extp)->oid ) ) {
+ /* if ext_main is given, only remove if it matches */
+ if ( ext_main != NULL && (*extp)->ext_main != ext_main ) {
+ return -1;
+ }
+ break;
+ }
+ }
+
+ if ( *extp == NULL ) {
+ return -1;
+ }
+
+ ext = *extp;
+ *extp = (*extp)->next;
+
+ ch_free( ext );
+
+ return 0;
+}
+
+int
+extops_init (void)
+{
+ int i;
+
+ for ( i = 0; builtin_extops[i].oid != NULL; i++ ) {
+ load_extop( (struct berval *)builtin_extops[i].oid,
+ builtin_extops[i].flags,
+ builtin_extops[i].ext_main );
+ }
+
+ return(0);
+}
+
+int
+extops_kill (void)
+{
+ struct extop_list *ext;
+
+ /* we allocated the memory, so we have to free it, too. */
+ while ((ext = supp_ext_list) != NULL) {
+ supp_ext_list = ext->next;
+ ch_free(ext);
+ }
+ return(0);
+}
+
+static struct extop_list *
+find_extop( struct extop_list *list, struct berval *oid )
+{
+ struct extop_list *ext;
+
+ for (ext = list; ext; ext = ext->next) {
+ if (bvmatch(&ext->oid, oid))
+ return(ext);
+ }
+ return(NULL);
+}
+
+
+const struct berval slap_EXOP_WHOAMI = BER_BVC(LDAP_EXOP_WHO_AM_I);
+
+static int
+whoami_extop (
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval *bv;
+
+ if ( op->ore_reqdata != NULL ) {
+ /* no request data should be provided */
+ rs->sr_text = "no request data expected";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s WHOAMI\n",
+ op->o_log_prefix );
+
+ op->o_bd = op->o_conn->c_authz_backend;
+ if( backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_WHOAMI ) != LDAP_SUCCESS )
+ {
+ return rs->sr_err;
+ }
+
+ bv = (struct berval *) ch_malloc( sizeof(struct berval) );
+ if( op->o_dn.bv_len ) {
+ bv->bv_len = op->o_dn.bv_len + STRLENOF( "dn:" );
+ bv->bv_val = ch_malloc( bv->bv_len + 1 );
+ AC_MEMCPY( bv->bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &bv->bv_val[STRLENOF( "dn:" )], op->o_dn.bv_val,
+ op->o_dn.bv_len );
+ bv->bv_val[bv->bv_len] = '\0';
+
+ } else {
+ bv->bv_len = 0;
+ bv->bv_val = NULL;
+ }
+
+ rs->sr_rspdata = bv;
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/filter.c b/servers/slapd/filter.c
new file mode 100644
index 0000000..9d000bc
--- /dev/null
+++ b/servers/slapd/filter.c
@@ -0,0 +1,1445 @@
+/* filter.c - routines for parsing and dealing with filters */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+const Filter *slap_filter_objectClass_pres;
+const struct berval *slap_filterstr_objectClass_pres;
+
+static int get_filter_list(
+ Operation *op,
+ BerElement *ber,
+ Filter **f,
+ const char **text,
+ int depth );
+
+static int get_ssa(
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ const char **text );
+
+static void simple_vrFilter2bv(
+ Operation *op,
+ ValuesReturnFilter *f,
+ struct berval *fstr );
+
+static int get_simple_vrFilter(
+ Operation *op,
+ BerElement *ber,
+ ValuesReturnFilter **f,
+ const char **text );
+
+int
+filter_init( void )
+{
+ static Filter filter_objectClass_pres = { LDAP_FILTER_PRESENT };
+ static struct berval filterstr_objectClass_pres = BER_BVC("(objectClass=*)");
+
+ filter_objectClass_pres.f_desc = slap_schema.si_ad_objectClass;
+
+ slap_filter_objectClass_pres = &filter_objectClass_pres;
+ slap_filterstr_objectClass_pres = &filterstr_objectClass_pres;
+
+ return 0;
+}
+
+void
+filter_destroy( void )
+{
+ return;
+}
+
+static int
+get_filter0(
+ Operation *op,
+ BerElement *ber,
+ Filter **filt,
+ const char **text,
+ int depth )
+{
+ ber_tag_t tag;
+ ber_len_t len;
+ int err;
+ Filter f;
+
+ Debug( LDAP_DEBUG_FILTER, "begin get_filter\n" );
+ /*
+ * A filter looks like this coming in:
+ * Filter ::= CHOICE {
+ * and [0] SET OF Filter,
+ * or [1] SET OF Filter,
+ * not [2] Filter,
+ * equalityMatch [3] AttributeValueAssertion,
+ * substrings [4] SubstringFilter,
+ * greaterOrEqual [5] AttributeValueAssertion,
+ * lessOrEqual [6] AttributeValueAssertion,
+ * present [7] AttributeType,
+ * approxMatch [8] AttributeValueAssertion,
+ * extensibleMatch [9] MatchingRuleAssertion
+ * }
+ *
+ * SubstringFilter ::= SEQUENCE {
+ * type AttributeType,
+ * SEQUENCE OF CHOICE {
+ * initial [0] IA5String,
+ * any [1] IA5String,
+ * final [2] IA5String
+ * }
+ * }
+ *
+ * MatchingRuleAssertion ::= SEQUENCE {
+ * matchingRule [1] MatchingRuleId OPTIONAL,
+ * type [2] AttributeDescription OPTIONAL,
+ * matchValue [3] AssertionValue,
+ * dnAttributes [4] BOOLEAN DEFAULT FALSE
+ * }
+ *
+ */
+
+ if( depth > slap_max_filter_depth ) {
+ *text = "filter nested too deeply";
+ return SLAPD_DISCONNECT;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if( tag == LBER_ERROR ) {
+ *text = "error decoding filter";
+ return SLAPD_DISCONNECT;
+ }
+
+ err = LDAP_SUCCESS;
+
+ f.f_next = NULL;
+ f.f_choice = tag;
+
+ switch ( f.f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, "EQUALITY\n" );
+ err = get_ava( op, ber, &f, SLAP_MR_EQUALITY, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( f.f_ava != NULL );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, "SUBSTRINGS\n" );
+ err = get_ssa( op, ber, &f, text );
+ if( err != LDAP_SUCCESS ) {
+ break;
+ }
+ assert( f.f_sub != NULL );
+ break;
+
+ case LDAP_FILTER_GE:
+ Debug( LDAP_DEBUG_FILTER, "GE\n" );
+ err = get_ava( op, ber, &f, SLAP_MR_ORDERING, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ assert( f.f_ava != NULL );
+ break;
+
+ case LDAP_FILTER_LE:
+ Debug( LDAP_DEBUG_FILTER, "LE\n" );
+ err = get_ava( op, ber, &f, SLAP_MR_ORDERING, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ assert( f.f_ava != NULL );
+ break;
+
+ case LDAP_FILTER_PRESENT: {
+ struct berval type;
+
+ Debug( LDAP_DEBUG_FILTER, "PRESENT\n" );
+ if ( ber_scanf( ber, "m", &type ) == LBER_ERROR ) {
+ err = SLAPD_DISCONNECT;
+ *text = "error decoding filter";
+ break;
+ }
+
+ f.f_desc = NULL;
+ err = slap_bv2ad( &type, &f.f_desc, text );
+
+ if( err != LDAP_SUCCESS ) {
+ f.f_choice |= SLAPD_FILTER_UNDEFINED;
+ err = slap_bv2undef_ad( &type, &f.f_desc, text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+
+ if ( err != LDAP_SUCCESS ) {
+ /* unrecognized attribute description or other error */
+ Debug( LDAP_DEBUG_ANY,
+ "get_filter: conn %lu unknown attribute "
+ "type=%s (%d)\n",
+ op->o_connid, type.bv_val, err );
+
+ err = LDAP_SUCCESS;
+ f.f_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx );
+ }
+ *text = NULL;
+ }
+
+ assert( f.f_desc != NULL );
+ } break;
+
+ case LDAP_FILTER_APPROX:
+ Debug( LDAP_DEBUG_FILTER, "APPROX\n" );
+ err = get_ava( op, ber, &f, SLAP_MR_EQUALITY_APPROX, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ assert( f.f_ava != NULL );
+ break;
+
+ case LDAP_FILTER_AND:
+ Debug( LDAP_DEBUG_FILTER, "AND\n" );
+ err = get_filter_list( op, ber, &f.f_and, text, depth+1 );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( f.f_and == NULL ) {
+ f.f_choice = SLAPD_FILTER_COMPUTED;
+ f.f_result = LDAP_COMPARE_TRUE;
+ }
+ /* no assert - list could be empty */
+ break;
+
+ case LDAP_FILTER_OR:
+ Debug( LDAP_DEBUG_FILTER, "OR\n" );
+ err = get_filter_list( op, ber, &f.f_or, text, depth+1 );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( f.f_or == NULL ) {
+ f.f_choice = SLAPD_FILTER_COMPUTED;
+ f.f_result = LDAP_COMPARE_FALSE;
+ }
+ /* no assert - list could be empty */
+ break;
+
+ case LDAP_FILTER_NOT:
+ Debug( LDAP_DEBUG_FILTER, "NOT\n" );
+ (void) ber_skip_tag( ber, &len );
+ err = get_filter0( op, ber, &f.f_not, text, depth+1 );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( f.f_not != NULL );
+ if ( f.f_not->f_choice == SLAPD_FILTER_COMPUTED ) {
+ int fresult = f.f_not->f_result;
+ f.f_choice = SLAPD_FILTER_COMPUTED;
+ op->o_tmpfree( f.f_not, op->o_tmpmemctx );
+ f.f_not = NULL;
+
+ switch( fresult ) {
+ case LDAP_COMPARE_TRUE:
+ f.f_result = LDAP_COMPARE_FALSE;
+ break;
+ case LDAP_COMPARE_FALSE:
+ f.f_result = LDAP_COMPARE_TRUE;
+ break;
+ default: ;
+ /* (!Undefined) is Undefined */
+ }
+ }
+ break;
+
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, "EXTENSIBLE\n" );
+
+ err = get_mra( op, ber, &f, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( f.f_mra != NULL );
+ break;
+
+ default:
+ (void) ber_scanf( ber, "x" ); /* skip the element */
+ Debug( LDAP_DEBUG_ANY, "get_filter: unknown filter type=%lu\n",
+ f.f_choice );
+ f.f_choice = SLAPD_FILTER_COMPUTED;
+ f.f_result = SLAPD_COMPARE_UNDEFINED;
+ break;
+ }
+
+ if( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) {
+ /* ignore error */
+ *text = NULL;
+ f.f_choice = SLAPD_FILTER_COMPUTED;
+ f.f_result = SLAPD_COMPARE_UNDEFINED;
+ err = LDAP_SUCCESS;
+ }
+
+ if ( err == LDAP_SUCCESS ) {
+ *filt = op->o_tmpalloc( sizeof(f), op->o_tmpmemctx );
+ **filt = f;
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "end get_filter %d\n", err );
+
+ return( err );
+}
+
+int
+get_filter(
+ Operation *op,
+ BerElement *ber,
+ Filter **filt,
+ const char **text )
+{
+ return get_filter0( op, ber, filt, text, 0 );
+}
+
+
+static int
+get_filter_list( Operation *op, BerElement *ber,
+ Filter **f,
+ const char **text,
+ int depth )
+{
+ Filter **new;
+ int err;
+ ber_tag_t tag;
+ ber_len_t len;
+ char *last;
+
+ Debug( LDAP_DEBUG_FILTER, "begin get_filter_list\n" );
+ new = f;
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ err = get_filter0( op, ber, new, text, depth );
+ if ( err != LDAP_SUCCESS )
+ return( err );
+ new = &(*new)->f_next;
+ }
+ *new = NULL;
+
+ Debug( LDAP_DEBUG_FILTER, "end get_filter_list\n" );
+ return( LDAP_SUCCESS );
+}
+
+static int
+get_ssa(
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ const char **text )
+{
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc;
+ struct berval desc, value, nvalue;
+ char *last;
+ SubstringsAssertion ssa;
+
+ *text = "error decoding filter";
+
+ Debug( LDAP_DEBUG_FILTER, "begin get_ssa\n" );
+ if ( ber_scanf( ber, "{m" /*}*/, &desc ) == LBER_ERROR ) {
+ return SLAPD_DISCONNECT;
+ }
+
+ *text = NULL;
+
+ ssa.sa_desc = NULL;
+ ssa.sa_initial.bv_val = NULL;
+ ssa.sa_any = NULL;
+ ssa.sa_final.bv_val = NULL;
+
+ rc = slap_bv2ad( &desc, &ssa.sa_desc, text );
+
+ if( rc != LDAP_SUCCESS ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ rc = slap_bv2undef_ad( &desc, &ssa.sa_desc, text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "get_ssa: conn %lu unknown attribute type=%s (%ld)\n",
+ op->o_connid, desc.bv_val, (long) rc );
+
+ ssa.sa_desc = slap_bv2tmp_ad( &desc, op->o_tmpmemctx );
+ }
+ }
+
+ rc = LDAP_PROTOCOL_ERROR;
+
+ /* If there is no substring matching rule, there's nothing
+ * we can do with this filter. But we continue to parse it
+ * for logging purposes.
+ */
+ if ( ssa.sa_desc->ad_type->sat_substr == NULL ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ Debug( LDAP_DEBUG_FILTER,
+ "get_ssa: no substring matching rule for attributeType %s\n",
+ desc.bv_val );
+ }
+
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ unsigned usage;
+
+ if ( ber_scanf( ber, "m", &value ) == LBER_ERROR ) {
+ rc = SLAPD_DISCONNECT;
+ goto return_error;
+ }
+
+ if ( value.bv_val == NULL || value.bv_len == 0 ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto return_error;
+ }
+
+ switch ( tag ) {
+ case LDAP_SUBSTRING_INITIAL:
+ if ( ssa.sa_initial.bv_val != NULL
+ || ssa.sa_any != NULL
+ || ssa.sa_final.bv_val != NULL )
+ {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto return_error;
+ }
+ usage = SLAP_MR_SUBSTR_INITIAL;
+ break;
+
+ case LDAP_SUBSTRING_ANY:
+ if ( ssa.sa_final.bv_val != NULL ) {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto return_error;
+ }
+ usage = SLAP_MR_SUBSTR_ANY;
+ break;
+
+ case LDAP_SUBSTRING_FINAL:
+ if ( ssa.sa_final.bv_val != NULL ) {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto return_error;
+ }
+
+ usage = SLAP_MR_SUBSTR_FINAL;
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_FILTER,
+ " unknown substring choice=%ld\n",
+ (long) tag );
+
+ rc = LDAP_PROTOCOL_ERROR;
+ goto return_error;
+ }
+
+ /* validate/normalize using equality matching rule validator! */
+ rc = asserted_value_validate_normalize(
+ ssa.sa_desc, ssa.sa_desc->ad_type->sat_equality,
+ usage, &value, &nvalue, text, op->o_tmpmemctx );
+ if( rc != LDAP_SUCCESS ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ Debug( LDAP_DEBUG_FILTER,
+ "get_ssa: illegal value for attributeType %s (%d) %s\n",
+ desc.bv_val, rc, *text );
+ ber_dupbv_x( &nvalue, &value, op->o_tmpmemctx );
+ }
+
+ switch ( tag ) {
+ case LDAP_SUBSTRING_INITIAL:
+ Debug( LDAP_DEBUG_FILTER, " INITIAL\n" );
+ ssa.sa_initial = nvalue;
+ break;
+
+ case LDAP_SUBSTRING_ANY:
+ Debug( LDAP_DEBUG_FILTER, " ANY\n" );
+ ber_bvarray_add_x( &ssa.sa_any, &nvalue, op->o_tmpmemctx );
+ break;
+
+ case LDAP_SUBSTRING_FINAL:
+ Debug( LDAP_DEBUG_FILTER, " FINAL\n" );
+ ssa.sa_final = nvalue;
+ break;
+
+ default:
+ assert( 0 );
+ slap_sl_free( nvalue.bv_val, op->o_tmpmemctx );
+ rc = LDAP_PROTOCOL_ERROR;
+
+return_error:
+ Debug( LDAP_DEBUG_FILTER, " error=%ld\n",
+ (long) rc );
+ slap_sl_free( ssa.sa_initial.bv_val, op->o_tmpmemctx );
+ ber_bvarray_free_x( ssa.sa_any, op->o_tmpmemctx );
+ if ( ssa.sa_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ op->o_tmpfree( ssa.sa_desc, op->o_tmpmemctx );
+ slap_sl_free( ssa.sa_final.bv_val, op->o_tmpmemctx );
+ return rc;
+ }
+
+ *text = NULL;
+ rc = LDAP_SUCCESS;
+ }
+
+ if( rc == LDAP_SUCCESS ) {
+ f->f_sub = op->o_tmpalloc( sizeof( ssa ), op->o_tmpmemctx );
+ *f->f_sub = ssa;
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "end get_ssa\n" );
+ return rc /* LDAP_SUCCESS */ ;
+}
+
+void
+filter_free_x( Operation *op, Filter *f, int freeme )
+{
+ Filter *p, *next;
+
+ if ( f == NULL ) {
+ return;
+ }
+
+ f->f_choice &= SLAPD_FILTER_MASK;
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_PRESENT:
+ if ( f->f_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ op->o_tmpfree( f->f_desc, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ ava_free( op, f->f_ava, 1 );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ if ( f->f_sub_initial.bv_val != NULL ) {
+ op->o_tmpfree( f->f_sub_initial.bv_val, op->o_tmpmemctx );
+ }
+ ber_bvarray_free_x( f->f_sub_any, op->o_tmpmemctx );
+ if ( f->f_sub_final.bv_val != NULL ) {
+ op->o_tmpfree( f->f_sub_final.bv_val, op->o_tmpmemctx );
+ }
+ if ( f->f_sub->sa_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ op->o_tmpfree( f->f_sub->sa_desc, op->o_tmpmemctx );
+ op->o_tmpfree( f->f_sub, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ for ( p = f->f_list; p != NULL; p = next ) {
+ next = p->f_next;
+ filter_free_x( op, p, 1 );
+ }
+ break;
+
+ case LDAP_FILTER_EXT:
+ mra_free( op, f->f_mra, 1 );
+ break;
+
+ case SLAPD_FILTER_COMPUTED:
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "filter_free: unknown filter type=%lu\n",
+ f->f_choice );
+ break;
+ }
+
+ if ( freeme ) {
+ op->o_tmpfree( f, op->o_tmpmemctx );
+ }
+}
+
+void
+filter_free( Filter *f )
+{
+ Operation op;
+ Opheader ohdr;
+
+ op.o_hdr = &ohdr;
+ op.o_tmpmemctx = slap_sl_context( f );
+ op.o_tmpmfuncs = &slap_sl_mfuncs;
+ filter_free_x( &op, f, 1 );
+}
+
+void
+filter2bv_x( Operation *op, Filter *f, struct berval *fstr )
+{
+ filter2bv_undef_x( op, f, 0, fstr );
+}
+
+void
+filter2bv_undef_x( Operation *op, Filter *f, int noundef, struct berval *fstr )
+{
+ int i;
+ Filter *p;
+ struct berval tmp, value;
+ static struct berval
+ ber_bvfalse = BER_BVC( "(?=false)" ),
+ ber_bvtrue = BER_BVC( "(?=true)" ),
+ ber_bvundefined = BER_BVC( "(?=undefined)" ),
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" ),
+ ber_bvF = BER_BVC( "(|)" ),
+ ber_bvT = BER_BVC( "(&)" );
+ ber_len_t len;
+ ber_tag_t choice;
+ int undef, undef2;
+ char *sign;
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx );
+ return;
+ }
+
+ undef = f->f_choice & SLAPD_FILTER_UNDEFINED;
+ undef2 = (undef && !noundef);
+ choice = f->f_choice & SLAPD_FILTER_MASK;
+
+ switch ( choice ) {
+ case LDAP_FILTER_EQUALITY:
+ fstr->bv_len = STRLENOF("(=)");
+ sign = "=";
+ goto simple;
+ case LDAP_FILTER_GE:
+ fstr->bv_len = STRLENOF("(>=)");
+ sign = ">=";
+ goto simple;
+ case LDAP_FILTER_LE:
+ fstr->bv_len = STRLENOF("(<=)");
+ sign = "<=";
+ goto simple;
+ case LDAP_FILTER_APPROX:
+ fstr->bv_len = STRLENOF("(~=)");
+ sign = "~=";
+
+simple:
+ value = f->f_av_value;
+ if ( f->f_av_desc->ad_type->sat_equality &&
+ !undef &&
+ ( f->f_av_desc->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ))
+ {
+ f->f_av_desc->ad_type->sat_equality->smr_normalize(
+ (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX),
+ NULL, NULL, &f->f_av_value, &value, op->o_tmpmemctx );
+ }
+
+ filter_escape_value_x( &value, &tmp, op->o_tmpmemctx );
+ /* NOTE: tmp can legitimately be NULL (meaning empty)
+ * since in a Filter values in AVAs are supposed
+ * to have been normalized, meaning that an empty value
+ * is legal for that attribute's syntax */
+
+ fstr->bv_len += f->f_av_desc->ad_cname.bv_len + tmp.bv_len;
+ if ( undef2 )
+ fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s)",
+ undef2 ? "?" : "",
+ f->f_av_desc->ad_cname.bv_val, sign,
+ tmp.bv_len ? tmp.bv_val : "" );
+
+ if ( value.bv_val != f->f_av_value.bv_val ) {
+ ber_memfree_x( value.bv_val, op->o_tmpmemctx );
+ }
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ fstr->bv_len = f->f_sub_desc->ad_cname.bv_len +
+ STRLENOF("(=*)");
+ if ( undef2 )
+ fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s=*)",
+ undef2 ? "?" : "",
+ f->f_sub_desc->ad_cname.bv_val );
+
+ if ( f->f_sub_initial.bv_val != NULL ) {
+ ber_len_t tmplen;
+
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &tmp, op->o_tmpmemctx );
+ tmplen = tmp.bv_len;
+
+ fstr->bv_len += tmplen;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val,
+ fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 2],
+ tmplen + STRLENOF( /*(*/ "*)" ) + 1,
+ /* "(attr=" */ "%s*)",
+ tmp.bv_len ? tmp.bv_val : "");
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ ) {
+ ber_len_t tmplen;
+
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i],
+ &tmp, op->o_tmpmemctx );
+ tmplen = tmp.bv_len;
+
+ fstr->bv_len += tmplen + STRLENOF( /*(*/ ")" );
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val,
+ fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1],
+ tmplen + STRLENOF( /*(*/ "*)" ) + 1,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ tmp.bv_len ? tmp.bv_val : "");
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( f->f_sub_final.bv_val != NULL ) {
+ ber_len_t tmplen;
+
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &tmp, op->o_tmpmemctx );
+ tmplen = tmp.bv_len;
+
+ fstr->bv_len += tmplen;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val,
+ fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1],
+ tmplen + STRLENOF( /*(*/ ")" ) + 1,
+ /* "(attr=[init*][any*]" */ "%s)",
+ tmp.bv_len ? tmp.bv_val : "");
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ fstr->bv_len = f->f_desc->ad_cname.bv_len +
+ STRLENOF("(=*)");
+ if ( undef2 )
+ fstr->bv_len++;
+
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s=*)",
+ undef2 ? "?" : "",
+ f->f_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF("(%)");
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ len = fstr->bv_len;
+
+ filter2bv_undef_x( op, p, noundef, &tmp );
+
+ fstr->bv_len += tmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1],
+ tmp.bv_len + STRLENOF( /*(*/ ")" ) + 1,
+ /*"("*/ "%s)", tmp.bv_val );
+
+ op->o_tmpfree( tmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT: {
+ struct berval ad;
+
+ filter_escape_value_x( &f->f_mr_value, &tmp, op->o_tmpmemctx );
+ /* NOTE: tmp can legitimately be NULL (meaning empty)
+ * since in a Filter values in MRAs are supposed
+ * to have been normalized, meaning that an empty value
+ * is legal for that attribute's syntax */
+
+ if ( f->f_mr_desc ) {
+ ad = f->f_mr_desc->ad_cname;
+ } else {
+ ad.bv_len = 0;
+ ad.bv_val = "";
+ }
+
+ fstr->bv_len = ad.bv_len +
+ ( undef2 ? 1 : 0 ) +
+ ( f->f_mr_dnattrs ? STRLENOF(":dn") : 0 ) +
+ ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + STRLENOF(":") : 0 ) +
+ tmp.bv_len + STRLENOF("(:=)");
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s%s:=%s)",
+ undef2 ? "?" : "",
+ ad.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ f->f_mr_rule_text.bv_len ? ":" : "",
+ f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_val : "",
+ tmp.bv_len ? tmp.bv_val : "" );
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ } break;
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ case LDAP_COMPARE_FALSE:
+ tmp = ( noundef ? ber_bvF : ber_bvfalse );
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ tmp = ( noundef ? ber_bvT : ber_bvtrue );
+ break;
+
+ case SLAPD_COMPARE_UNDEFINED:
+ tmp = ber_bvundefined;
+ break;
+
+ default:
+ tmp = ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, &tmp, op->o_tmpmemctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx );
+ break;
+ }
+}
+
+void
+filter2bv( Filter *f, struct berval *fstr )
+{
+ filter2bv_undef( f, 0, fstr );
+}
+
+void
+filter2bv_undef( Filter *f, int noundef, struct berval *fstr )
+{
+ Operation op;
+ Opheader ohdr;
+
+ op.o_hdr = &ohdr;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ filter2bv_undef_x( &op, f, noundef, fstr );
+}
+
+Filter *
+filter_dup( Filter *f, void *memctx )
+{
+ BerMemoryFunctions *mf = &slap_sl_mfuncs;
+ Filter *n;
+
+ if ( !f )
+ return NULL;
+
+ n = mf->bmf_malloc( sizeof(Filter), memctx );
+ n->f_choice = f->f_choice;
+ n->f_next = NULL;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case SLAPD_FILTER_COMPUTED:
+ n->f_result = f->f_result;
+ break;
+ case LDAP_FILTER_PRESENT:
+ if ( f->f_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ n->f_desc = slap_bv2tmp_ad( &f->f_desc->ad_cname, memctx );
+ else
+ n->f_desc = f->f_desc;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ n->f_ava = ava_dup( f->f_ava, memctx );
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ n->f_sub = mf->bmf_calloc( 1, sizeof(SubstringsAssertion), memctx );
+ if ( f->f_sub_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ n->f_sub_desc = slap_bv2tmp_ad( &f->f_sub_desc->ad_cname, memctx );
+ else
+ n->f_sub_desc = f->f_sub_desc;
+ if ( !BER_BVISNULL( &f->f_sub_initial ))
+ ber_dupbv_x( &n->f_sub_initial, &f->f_sub_initial, memctx );
+ if ( f->f_sub_any ) {
+ int i;
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ );
+ n->f_sub_any = mf->bmf_malloc(( i+1 )*sizeof( struct berval ),
+ memctx );
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ ber_dupbv_x( &n->f_sub_any[i], &f->f_sub_any[i], memctx );
+ }
+ BER_BVZERO( &n->f_sub_any[i] );
+ }
+ if ( !BER_BVISNULL( &f->f_sub_final ))
+ ber_dupbv_x( &n->f_sub_final, &f->f_sub_final, memctx );
+ break;
+ case LDAP_FILTER_EXT: {
+ /* Should this be mra_dup() ? */
+ ber_len_t length;
+ length = sizeof(MatchingRuleAssertion);
+ if ( !BER_BVISNULL( &f->f_mr_rule_text ))
+ length += f->f_mr_rule_text.bv_len + 1;
+ n->f_mra = mf->bmf_calloc( 1, length, memctx );
+ *n->f_mra = *f->f_mra;
+ if ( f->f_mr_desc && ( f->f_sub_desc->ad_flags & SLAP_DESC_TEMPORARY ))
+ n->f_mr_desc = slap_bv2tmp_ad( &f->f_mr_desc->ad_cname, memctx );
+ ber_dupbv_x( &n->f_mr_value, &f->f_mr_value, memctx );
+ if ( !BER_BVISNULL( &f->f_mr_rule_text )) {
+ n->f_mr_rule_text.bv_val = (char *)(n->f_mra+1);
+ AC_MEMCPY(n->f_mr_rule_text.bv_val,
+ f->f_mr_rule_text.bv_val, f->f_mr_rule_text.bv_len );
+ }
+ } break;
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ Filter **p;
+ for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) {
+ *p = filter_dup( f, memctx );
+ p = &(*p)->f_next;
+ }
+ } break;
+ }
+ return n;
+}
+
+static int
+get_simple_vrFilter(
+ Operation *op,
+ BerElement *ber,
+ ValuesReturnFilter **filt,
+ const char **text )
+{
+ ber_tag_t tag;
+ ber_len_t len;
+ int err;
+ ValuesReturnFilter vrf;
+
+ Debug( LDAP_DEBUG_FILTER, "begin get_simple_vrFilter\n" );
+
+ tag = ber_peek_tag( ber, &len );
+
+ if( tag == LBER_ERROR ) {
+ *text = "error decoding filter";
+ return SLAPD_DISCONNECT;
+ }
+
+ vrf.vrf_next = NULL;
+
+ err = LDAP_SUCCESS;
+ vrf.vrf_choice = tag;
+
+ switch ( vrf.vrf_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, "EQUALITY\n" );
+ err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_EQUALITY, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( vrf.vrf_ava != NULL );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, "SUBSTRINGS\n" );
+ err = get_ssa( op, ber, (Filter *)&vrf, text );
+ break;
+
+ case LDAP_FILTER_GE:
+ Debug( LDAP_DEBUG_FILTER, "GE\n" );
+ err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_ORDERING, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ break;
+
+ case LDAP_FILTER_LE:
+ Debug( LDAP_DEBUG_FILTER, "LE\n" );
+ err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_ORDERING, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ break;
+
+ case LDAP_FILTER_PRESENT: {
+ struct berval type;
+
+ Debug( LDAP_DEBUG_FILTER, "PRESENT\n" );
+ if ( ber_scanf( ber, "m", &type ) == LBER_ERROR ) {
+ err = SLAPD_DISCONNECT;
+ *text = "error decoding filter";
+ break;
+ }
+
+ vrf.vrf_desc = NULL;
+ err = slap_bv2ad( &type, &vrf.vrf_desc, text );
+
+ if( err != LDAP_SUCCESS ) {
+ vrf.vrf_choice |= SLAPD_FILTER_UNDEFINED;
+ err = slap_bv2undef_ad( &type, &vrf.vrf_desc, text,
+ SLAP_AD_PROXIED);
+
+ if( err != LDAP_SUCCESS ) {
+ /* unrecognized attribute description or other error */
+ Debug( LDAP_DEBUG_ANY,
+ "get_simple_vrFilter: conn %lu unknown "
+ "attribute type=%s (%d)\n",
+ op->o_connid, type.bv_val, err );
+
+ vrf.vrf_choice = SLAPD_FILTER_COMPUTED;
+ vrf.vrf_result = LDAP_COMPARE_FALSE;
+ err = LDAP_SUCCESS;
+ break;
+ }
+ }
+ } break;
+
+ case LDAP_FILTER_APPROX:
+ Debug( LDAP_DEBUG_FILTER, "APPROX\n" );
+ err = get_ava( op, ber, (Filter *)&vrf, SLAP_MR_EQUALITY_APPROX, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+ break;
+
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, "EXTENSIBLE\n" );
+
+ err = get_mra( op, ber, (Filter *)&vrf, text );
+ if ( err != LDAP_SUCCESS ) {
+ break;
+ }
+
+ assert( vrf.vrf_mra != NULL );
+ break;
+
+ default:
+ (void) ber_scanf( ber, "x" ); /* skip the element */
+ Debug( LDAP_DEBUG_ANY, "get_simple_vrFilter: unknown filter type=%lu\n",
+ vrf.vrf_choice );
+ vrf.vrf_choice = SLAPD_FILTER_COMPUTED;
+ vrf.vrf_result = SLAPD_COMPARE_UNDEFINED;
+ break;
+ }
+
+ if ( err != LDAP_SUCCESS && err != SLAPD_DISCONNECT ) {
+ /* ignore error */
+ vrf.vrf_choice = SLAPD_FILTER_COMPUTED;
+ vrf.vrf_result = SLAPD_COMPARE_UNDEFINED;
+ err = LDAP_SUCCESS;
+ }
+
+ if ( err == LDAP_SUCCESS ) {
+ *filt = op->o_tmpalloc( sizeof vrf, op->o_tmpmemctx );
+ **filt = vrf;
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "end get_simple_vrFilter %d\n", err );
+
+ return err;
+}
+
+int
+get_vrFilter( Operation *op, BerElement *ber,
+ ValuesReturnFilter **vrf,
+ const char **text )
+{
+ /*
+ * A ValuesReturnFilter looks like this:
+ *
+ * ValuesReturnFilter ::= SEQUENCE OF SimpleFilterItem
+ * SimpleFilterItem ::= CHOICE {
+ * equalityMatch [3] AttributeValueAssertion,
+ * substrings [4] SubstringFilter,
+ * greaterOrEqual [5] AttributeValueAssertion,
+ * lessOrEqual [6] AttributeValueAssertion,
+ * present [7] AttributeType,
+ * approxMatch [8] AttributeValueAssertion,
+ * extensibleMatch [9] SimpleMatchingAssertion -- LDAPv3
+ * }
+ *
+ * SubstringFilter ::= SEQUENCE {
+ * type AttributeType,
+ * SEQUENCE OF CHOICE {
+ * initial [0] IA5String,
+ * any [1] IA5String,
+ * final [2] IA5String
+ * }
+ * }
+ *
+ * SimpleMatchingAssertion ::= SEQUENCE { -- LDAPv3
+ * matchingRule [1] MatchingRuleId OPTIONAL,
+ * type [2] AttributeDescription OPTIONAL,
+ * matchValue [3] AssertionValue }
+ */
+
+ ValuesReturnFilter **n;
+ ber_tag_t tag;
+ ber_len_t len;
+ char *last;
+
+ Debug( LDAP_DEBUG_FILTER, "begin get_vrFilter\n" );
+
+ tag = ber_peek_tag( ber, &len );
+
+ if( tag == LBER_ERROR ) {
+ *text = "error decoding vrFilter";
+ return SLAPD_DISCONNECT;
+ }
+
+ if( tag != LBER_SEQUENCE ) {
+ *text = "error decoding vrFilter, expect SEQUENCE tag";
+ return SLAPD_DISCONNECT;
+ }
+
+ n = vrf;
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ int err = get_simple_vrFilter( op, ber, n, text );
+
+ if ( err != LDAP_SUCCESS ) return( err );
+
+ n = &(*n)->vrf_next;
+ }
+ *n = NULL;
+
+ Debug( LDAP_DEBUG_FILTER, "end get_vrFilter\n" );
+ return( LDAP_SUCCESS );
+}
+
+void
+vrFilter_free( Operation *op, ValuesReturnFilter *vrf )
+{
+ ValuesReturnFilter *next;
+
+ for ( ; vrf != NULL; vrf = next ) {
+ next = vrf->vrf_next;
+
+ switch ( vrf->vrf_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_PRESENT:
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ ava_free( op, vrf->vrf_ava, 1 );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ if ( vrf->vrf_sub_initial.bv_val != NULL ) {
+ op->o_tmpfree( vrf->vrf_sub_initial.bv_val, op->o_tmpmemctx );
+ }
+ ber_bvarray_free_x( vrf->vrf_sub_any, op->o_tmpmemctx );
+ if ( vrf->vrf_sub_final.bv_val != NULL ) {
+ op->o_tmpfree( vrf->vrf_sub_final.bv_val, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( vrf->vrf_sub, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_EXT:
+ mra_free( op, vrf->vrf_mra, 1 );
+ break;
+
+ case SLAPD_FILTER_COMPUTED:
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "filter_free: unknown filter type=%lu\n",
+ vrf->vrf_choice );
+ break;
+ }
+
+ op->o_tmpfree( vrf, op->o_tmpmemctx );
+ }
+}
+
+void
+vrFilter2bv( Operation *op, ValuesReturnFilter *vrf, struct berval *fstr )
+{
+ ValuesReturnFilter *p;
+ struct berval tmp;
+ ber_len_t len;
+
+ if ( vrf == NULL ) {
+ ber_str2bv_x( "No filter!", STRLENOF("No filter!"),
+ 1, fstr, op->o_tmpmemctx );
+ return;
+ }
+
+ fstr->bv_len = STRLENOF("()");
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "()");
+
+ for ( p = vrf; p != NULL; p = p->vrf_next ) {
+ len = fstr->bv_len;
+
+ simple_vrFilter2bv( op, p, &tmp );
+
+ fstr->bv_len += tmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1], tmp.bv_len + 2,
+ /*"("*/ "%s)", tmp.bv_val );
+
+ op->o_tmpfree( tmp.bv_val, op->o_tmpmemctx );
+ }
+}
+
+static void
+simple_vrFilter2bv( Operation *op, ValuesReturnFilter *vrf, struct berval *fstr )
+{
+ struct berval tmp;
+ ber_len_t len;
+ int undef;
+
+ if ( vrf == NULL ) {
+ ber_str2bv_x( "No filter!", STRLENOF("No filter!"), 1, fstr,
+ op->o_tmpmemctx );
+ return;
+ }
+ undef = vrf->vrf_choice & SLAPD_FILTER_UNDEFINED;
+
+ switch ( vrf->vrf_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len +
+ tmp.bv_len + STRLENOF("(=)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ vrf->vrf_av_desc->ad_cname.bv_val,
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len +
+ tmp.bv_len + STRLENOF("(>=)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ vrf->vrf_av_desc->ad_cname.bv_val,
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len +
+ tmp.bv_len + STRLENOF("(<=)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ vrf->vrf_av_desc->ad_cname.bv_val,
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ filter_escape_value_x( &vrf->vrf_av_value, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len = vrf->vrf_av_desc->ad_cname.bv_len +
+ tmp.bv_len + STRLENOF("(~=)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ vrf->vrf_av_desc->ad_cname.bv_val,
+ tmp.bv_val );
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ fstr->bv_len = vrf->vrf_sub_desc->ad_cname.bv_len +
+ STRLENOF("(=*)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ vrf->vrf_sub_desc->ad_cname.bv_val );
+
+ if ( vrf->vrf_sub_initial.bv_val != NULL ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &vrf->vrf_sub_initial, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len += tmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-2], tmp.bv_len+3,
+ /* "(attr=" */ "%s*)",
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( vrf->vrf_sub_any != NULL ) {
+ int i;
+ for ( i = 0; vrf->vrf_sub_any[i].bv_val != NULL; i++ ) {
+ len = fstr->bv_len;
+ filter_escape_value_x( &vrf->vrf_sub_any[i], &tmp,
+ op->o_tmpmemctx );
+
+ fstr->bv_len += tmp.bv_len + 1;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val,
+ fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1], tmp.bv_len+3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ tmp.bv_val );
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( vrf->vrf_sub_final.bv_val != NULL ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &vrf->vrf_sub_final, &tmp, op->o_tmpmemctx );
+
+ fstr->bv_len += tmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1], tmp.bv_len+3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ fstr->bv_len = vrf->vrf_desc->ad_cname.bv_len +
+ STRLENOF("(=*)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ vrf->vrf_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_FILTER_EXT: {
+ struct berval ad;
+ filter_escape_value_x( &vrf->vrf_mr_value, &tmp, op->o_tmpmemctx );
+
+ if ( vrf->vrf_mr_desc ) {
+ ad = vrf->vrf_mr_desc->ad_cname;
+ } else {
+ ad.bv_len = 0;
+ ad.bv_val = "";
+ }
+
+ fstr->bv_len = ad.bv_len +
+ ( vrf->vrf_mr_dnattrs ? STRLENOF(":dn") : 0 ) +
+ ( vrf->vrf_mr_rule_text.bv_len
+ ? vrf->vrf_mr_rule_text.bv_len+1 : 0 ) +
+ tmp.bv_len + STRLENOF("(:=)");
+ if ( undef ) fstr->bv_len++;
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ ad.bv_val,
+ vrf->vrf_mr_dnattrs ? ":dn" : "",
+ vrf->vrf_mr_rule_text.bv_len ? ":" : "",
+ vrf->vrf_mr_rule_text.bv_len ? vrf->vrf_mr_rule_text.bv_val : "",
+ tmp.bv_val );
+
+ ber_memfree_x( tmp.bv_val, op->o_tmpmemctx );
+ } break;
+
+ case SLAPD_FILTER_COMPUTED:
+ ber_str2bv_x(
+ vrf->vrf_result == LDAP_COMPARE_FALSE ? "(?=false)" :
+ vrf->vrf_result == LDAP_COMPARE_TRUE ? "(?=true)" :
+ vrf->vrf_result == SLAPD_COMPARE_UNDEFINED
+ ? "(?=undefined)" : "(?=error)",
+ vrf->vrf_result == LDAP_COMPARE_FALSE ? STRLENOF("(?=false)") :
+ vrf->vrf_result == LDAP_COMPARE_TRUE ? STRLENOF("(?=true)") :
+ vrf->vrf_result == SLAPD_COMPARE_UNDEFINED
+ ? STRLENOF("(?=undefined)") : STRLENOF("(?=error)"),
+ 1, fstr, op->o_tmpmemctx );
+ break;
+
+ default:
+ ber_str2bv_x( "(?=unknown)", STRLENOF("(?=unknown)"),
+ 1, fstr, op->o_tmpmemctx );
+ break;
+ }
+}
diff --git a/servers/slapd/filterentry.c b/servers/slapd/filterentry.c
new file mode 100644
index 0000000..61b64ff
--- /dev/null
+++ b/servers/slapd/filterentry.c
@@ -0,0 +1,986 @@
+/* filterentry.c - apply a filter to an entry */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+
+#ifdef LDAP_COMP_MATCH
+#include "component.h"
+#endif
+
+static int test_filter_and( Operation *op, Entry *e, Filter *flist );
+static int test_filter_or( Operation *op, Entry *e, Filter *flist );
+static int test_substrings_filter( Operation *op, Entry *e, Filter *f);
+static int test_ava_filter( Operation *op,
+ Entry *e, AttributeAssertion *ava, int type );
+static int test_mra_filter( Operation *op,
+ Entry *e, MatchingRuleAssertion *mra );
+static int test_presence_filter( Operation *op,
+ Entry *e, AttributeDescription *desc );
+
+
+/*
+ * test_filter - test a filter against a single entry.
+ * returns:
+ * LDAP_COMPARE_TRUE filter matched
+ * LDAP_COMPARE_FALSE filter did not match
+ * SLAPD_COMPARE_UNDEFINED filter is undefined
+ * or an ldap result code indicating error
+ */
+
+int
+test_filter(
+ Operation *op,
+ Entry *e,
+ Filter *f )
+{
+ int rc;
+ Debug( LDAP_DEBUG_FILTER, "=> test_filter\n" );
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ Debug( LDAP_DEBUG_FILTER, " UNDEFINED\n" );
+ rc = SLAPD_COMPARE_UNDEFINED;
+ goto out;
+ }
+
+ switch ( f->f_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ Debug( LDAP_DEBUG_FILTER, " COMPUTED %s (%d)\n",
+ f->f_result == LDAP_COMPARE_FALSE ? "false" :
+ f->f_result == LDAP_COMPARE_TRUE ? "true" :
+ f->f_result == SLAPD_COMPARE_UNDEFINED ? "undefined" : "error",
+ f->f_result );
+
+ rc = f->f_result;
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, " EQUALITY\n" );
+ rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_EQUALITY );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, " SUBSTRINGS\n" );
+ rc = test_substrings_filter( op, e, f );
+ break;
+
+ case LDAP_FILTER_GE:
+ Debug( LDAP_DEBUG_FILTER, " GE\n" );
+ rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_GE );
+ break;
+
+ case LDAP_FILTER_LE:
+ Debug( LDAP_DEBUG_FILTER, " LE\n" );
+ rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_LE );
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ Debug( LDAP_DEBUG_FILTER, " PRESENT\n" );
+ rc = test_presence_filter( op, e, f->f_desc );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ Debug( LDAP_DEBUG_FILTER, " APPROX\n" );
+ rc = test_ava_filter( op, e, f->f_ava, LDAP_FILTER_APPROX );
+ break;
+
+ case LDAP_FILTER_AND:
+ Debug( LDAP_DEBUG_FILTER, " AND\n" );
+ rc = test_filter_and( op, e, f->f_and );
+ break;
+
+ case LDAP_FILTER_OR:
+ Debug( LDAP_DEBUG_FILTER, " OR\n" );
+ rc = test_filter_or( op, e, f->f_or );
+ break;
+
+ case LDAP_FILTER_NOT:
+ Debug( LDAP_DEBUG_FILTER, " NOT\n" );
+ rc = test_filter( op, e, f->f_not );
+
+ /* Flip true to false and false to true
+ * but leave Undefined alone.
+ */
+ switch( rc ) {
+ case LDAP_COMPARE_TRUE:
+ rc = LDAP_COMPARE_FALSE;
+ break;
+ case LDAP_COMPARE_FALSE:
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+ break;
+
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, " EXT\n" );
+ rc = test_mra_filter( op, e, f->f_mra );
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, " unknown filter type %lu\n",
+ f->f_choice );
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+out:
+ Debug( LDAP_DEBUG_FILTER, "<= test_filter %d\n", rc );
+ return( rc );
+}
+
+static int test_mra_filter(
+ Operation *op,
+ Entry *e,
+ MatchingRuleAssertion *mra )
+{
+ Attribute *a;
+ void *memctx;
+ BER_MEMFREE_FN *memfree;
+#ifdef LDAP_COMP_MATCH
+ int i, num_attr_vals = 0;
+#endif
+
+ if ( op == NULL ) {
+ memctx = NULL;
+ memfree = slap_sl_mfuncs.bmf_free;
+ } else {
+ memctx = op->o_tmpmemctx;
+ memfree = op->o_tmpfree;
+ }
+
+ if ( mra->ma_desc ) {
+ /*
+ * if ma_desc is available, then we're filtering for
+ * one attribute, and SEARCH permissions can be checked
+ * directly.
+ */
+ if ( !access_allowed( op, e,
+ mra->ma_desc, &mra->ma_value, ACL_SEARCH, NULL ) )
+ {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ if ( mra->ma_desc == slap_schema.si_ad_entryDN ) {
+ int ret, rc;
+ const char *text;
+
+ rc = value_match( &ret, slap_schema.si_ad_entryDN, mra->ma_rule,
+ SLAP_MR_EXT, &e->e_nname, &mra->ma_value, &text );
+
+
+ if( rc != LDAP_SUCCESS ) return rc;
+ if ( ret == 0 ) return LDAP_COMPARE_TRUE;
+ return LDAP_COMPARE_FALSE;
+ }
+
+ for ( a = attrs_find( e->e_attrs, mra->ma_desc );
+ a != NULL;
+ a = attrs_find( a->a_next, mra->ma_desc ) )
+ {
+ struct berval *bv;
+ int normalize_attribute = 0;
+
+#ifdef LDAP_COMP_MATCH
+ /* Component Matching */
+ if ( mra->ma_cf && mra->ma_rule->smr_usage & SLAP_MR_COMPONENT ) {
+ num_attr_vals = 0;
+ if ( !a->a_comp_data ) {
+ num_attr_vals = a->a_numvals;
+ if ( num_attr_vals <= 0 ) {
+ /* no attribute value */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ num_attr_vals++;
+
+ /* following malloced will be freed by comp_tree_free () */
+ a->a_comp_data = SLAP_MALLOC( sizeof( ComponentData ) +
+ sizeof( ComponentSyntaxInfo* )*num_attr_vals );
+
+ if ( !a->a_comp_data ) return LDAP_NO_MEMORY;
+ a->a_comp_data->cd_tree = (ComponentSyntaxInfo**)
+ ((char*)a->a_comp_data + sizeof(ComponentData));
+ a->a_comp_data->cd_tree[num_attr_vals - 1] =
+ (ComponentSyntaxInfo*) NULL;
+ a->a_comp_data->cd_mem_op =
+ nibble_mem_allocator( 1024*16, 1024 );
+ }
+ }
+#endif
+
+ /* If ma_rule is not the same as the attribute's
+ * normal rule, then we can't use the a_nvals.
+ */
+ if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) {
+ bv = a->a_nvals;
+
+ } else {
+ bv = a->a_vals;
+ normalize_attribute = 1;
+ }
+#ifdef LDAP_COMP_MATCH
+ i = 0;
+#endif
+ for ( ; !BER_BVISNULL( bv ); bv++ ) {
+ int ret;
+ int rc;
+ const char *text;
+
+#ifdef LDAP_COMP_MATCH
+ if ( mra->ma_cf &&
+ mra->ma_rule->smr_usage & SLAP_MR_COMPONENT )
+ {
+ /* Check if decoded component trees are already linked */
+ if ( num_attr_vals ) {
+ a->a_comp_data->cd_tree[i] = attr_converter(
+ a, a->a_desc->ad_type->sat_syntax, bv );
+ }
+ /* decoding error */
+ if ( !a->a_comp_data->cd_tree[i] ) {
+ return LDAP_OPERATIONS_ERROR;
+ }
+ rc = value_match( &ret, a->a_desc, mra->ma_rule,
+ SLAP_MR_COMPONENT,
+ (struct berval*)a->a_comp_data->cd_tree[i++],
+ (void*)mra, &text );
+ } else
+#endif
+ {
+ struct berval nbv = BER_BVNULL;
+
+ if ( normalize_attribute && mra->ma_rule->smr_normalize ) {
+ /*
+
+ Document: RFC 4511
+
+ 4.5.1. Search Request
+ ...
+ If the type field is present and the matchingRule is present,
+ the matchValue is compared against entry attributes of the
+ specified type. In this case, the matchingRule MUST be one
+ suitable for use with the specified type (see [RFC4517]),
+ otherwise the filter item is Undefined.
+
+
+ In this case, since the matchingRule requires the assertion
+ value to be normalized, we normalize the attribute value
+ according to the syntax of the matchingRule.
+
+ This should likely be done inside value_match(), by passing
+ the appropriate flags, but this is not done at present.
+ See ITS#3406.
+ */
+ if ( mra->ma_rule->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mra->ma_rule->smr_syntax,
+ mra->ma_rule,
+ bv, &nbv, memctx ) != LDAP_SUCCESS )
+ {
+ /* FIXME: stop processing? */
+ continue;
+ }
+
+ } else {
+ nbv = *bv;
+ }
+
+ rc = value_match( &ret, a->a_desc, mra->ma_rule,
+ SLAP_MR_EXT, &nbv, &mra->ma_value, &text );
+
+ if ( nbv.bv_val != bv->bv_val ) {
+ memfree( nbv.bv_val, memctx );
+ }
+ }
+
+ if ( rc != LDAP_SUCCESS ) return rc;
+ if ( ret == 0 ) return LDAP_COMPARE_TRUE;
+ }
+ }
+
+ } else {
+ /*
+ * No attribute description: test all
+ */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ struct berval *bv, value;
+ const char *text = NULL;
+ int rc;
+ int normalize_attribute = 0;
+
+ /* check if matching is appropriate */
+ if ( !mr_usable_with_at( mra->ma_rule, a->a_desc->ad_type ) ) {
+ continue;
+ }
+
+ /* normalize for equality */
+ rc = asserted_value_validate_normalize( a->a_desc, mra->ma_rule,
+ SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &mra->ma_value, &value, &text, memctx );
+ if ( rc != LDAP_SUCCESS ) continue;
+
+ /* check search access */
+ if ( !access_allowed( op, e,
+ a->a_desc, &value, ACL_SEARCH, NULL ) )
+ {
+ memfree( value.bv_val, memctx );
+ continue;
+ }
+#ifdef LDAP_COMP_MATCH
+ /* Component Matching */
+ if ( mra->ma_cf &&
+ mra->ma_rule->smr_usage & SLAP_MR_COMPONENT )
+ {
+ int ret;
+
+ rc = value_match( &ret, a->a_desc, mra->ma_rule,
+ SLAP_MR_COMPONENT,
+ (struct berval*)a, (void*)mra, &text );
+ if ( rc != LDAP_SUCCESS ) break;
+
+ if ( ret == 0 ) {
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+
+ }
+#endif
+
+ /* check match */
+ if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) {
+ bv = a->a_nvals;
+
+ } else {
+ bv = a->a_vals;
+ normalize_attribute = 1;
+ }
+
+ for ( ; !BER_BVISNULL( bv ); bv++ ) {
+ int ret;
+ struct berval nbv = BER_BVNULL;
+
+ if ( normalize_attribute && mra->ma_rule->smr_normalize ) {
+ /* see comment above */
+ if ( mra->ma_rule->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mra->ma_rule->smr_syntax,
+ mra->ma_rule,
+ bv, &nbv, memctx ) != LDAP_SUCCESS )
+ {
+ /* FIXME: stop processing? */
+ continue;
+ }
+
+ } else {
+ nbv = *bv;
+ }
+
+ rc = value_match( &ret, a->a_desc, mra->ma_rule,
+ SLAP_MR_EXT, &nbv, &value, &text );
+
+ if ( nbv.bv_val != bv->bv_val ) {
+ memfree( nbv.bv_val, memctx );
+ }
+
+ if ( rc != LDAP_SUCCESS ) break;
+
+ if ( ret == 0 ) {
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+ memfree( value.bv_val, memctx );
+ if ( rc != LDAP_SUCCESS ) return rc;
+ }
+ }
+
+ /* check attrs in DN AVAs if required */
+ if ( mra->ma_dnattrs && !BER_BVISEMPTY( &e->e_nname ) ) {
+ LDAPDN dn = NULL;
+ int iRDN, iAVA;
+ int rc;
+
+ /* parse and pretty the dn */
+ rc = dnPrettyDN( NULL, &e->e_name, &dn, memctx );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* for each AVA of each RDN ... */
+ for ( iRDN = 0; dn[ iRDN ]; iRDN++ ) {
+ LDAPRDN rdn = dn[ iRDN ];
+
+ for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
+ LDAPAVA *ava = rdn[ iAVA ];
+ struct berval *bv = &ava->la_value,
+ value = BER_BVNULL,
+ nbv = BER_BVNULL;
+ AttributeDescription *ad =
+ (AttributeDescription *)ava->la_private;
+ int ret;
+ const char *text;
+
+ assert( ad != NULL );
+
+ if ( mra->ma_desc ) {
+ /* have a mra type? check for subtype */
+ if ( !is_ad_subtype( ad, mra->ma_desc ) ) {
+ continue;
+ }
+ value = mra->ma_value;
+
+ } else {
+ const char *text = NULL;
+
+ /* check if matching is appropriate */
+ if ( !mr_usable_with_at( mra->ma_rule, ad->ad_type ) ) {
+ continue;
+ }
+
+ /* normalize for equality */
+ rc = asserted_value_validate_normalize( ad,
+ mra->ma_rule,
+ SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &mra->ma_value, &value, &text, memctx );
+ if ( rc != LDAP_SUCCESS ) continue;
+
+ /* check search access */
+ if ( !access_allowed( op, e,
+ ad, &value, ACL_SEARCH, NULL ) )
+ {
+ memfree( value.bv_val, memctx );
+ continue;
+ }
+ }
+
+ if ( mra->ma_rule->smr_normalize ) {
+ /* see comment above */
+ if ( mra->ma_rule->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mra->ma_rule->smr_syntax,
+ mra->ma_rule,
+ bv, &nbv, memctx ) != LDAP_SUCCESS )
+ {
+ /* FIXME: stop processing? */
+ rc = LDAP_SUCCESS;
+ ret = -1;
+ goto cleanup;
+ }
+
+ } else {
+ nbv = *bv;
+ }
+
+ /* check match */
+ rc = value_match( &ret, ad, mra->ma_rule, SLAP_MR_EXT,
+ &nbv, &value, &text );
+
+cleanup:;
+ if ( !BER_BVISNULL( &value ) && value.bv_val != mra->ma_value.bv_val ) {
+ memfree( value.bv_val, memctx );
+ }
+
+ if ( !BER_BVISNULL( &nbv ) && nbv.bv_val != bv->bv_val ) {
+ memfree( nbv.bv_val, memctx );
+ }
+
+ if ( rc == LDAP_SUCCESS && ret == 0 ) rc = LDAP_COMPARE_TRUE;
+
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_dnfree_x( dn, memctx );
+ return rc;
+ }
+ }
+ }
+ ldap_dnfree_x( dn, memctx );
+ }
+
+ return LDAP_COMPARE_FALSE;
+}
+
+static int
+test_ava_filter(
+ Operation *op,
+ Entry *e,
+ AttributeAssertion *ava,
+ int type )
+{
+ int rc;
+ Attribute *a;
+#ifdef LDAP_COMP_MATCH
+ int i, num_attr_vals = 0;
+ AttributeAliasing *a_alias = NULL;
+#endif
+
+ if ( !access_allowed( op, e,
+ ava->aa_desc, &ava->aa_value, ACL_SEARCH, NULL ) )
+ {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ if ( ava->aa_desc == slap_schema.si_ad_hasSubordinates
+ && op && op->o_bd && op->o_bd->be_has_subordinates )
+ {
+ int hasSubordinates = 0;
+ struct berval hs;
+
+ if( type != LDAP_FILTER_EQUALITY &&
+ type != LDAP_FILTER_APPROX )
+ {
+ /* No other match is allowed */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if ( op->o_bd->be_has_subordinates( op, e, &hasSubordinates ) !=
+ LDAP_SUCCESS )
+ {
+ return LDAP_OTHER;
+ }
+
+ if ( hasSubordinates == LDAP_COMPARE_TRUE ) {
+ hs = slap_true_bv;
+
+ } else if ( hasSubordinates == LDAP_COMPARE_FALSE ) {
+ hs = slap_false_bv;
+
+ } else {
+ return LDAP_OTHER;
+ }
+
+ if ( bvmatch( &ava->aa_value, &hs ) ) return LDAP_COMPARE_TRUE;
+ return LDAP_COMPARE_FALSE;
+ }
+
+ if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
+ MatchingRule *mr;
+ int match;
+ const char *text;
+
+ if( type != LDAP_FILTER_EQUALITY &&
+ type != LDAP_FILTER_APPROX )
+ {
+ /* No other match is allowed */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ mr = slap_schema.si_ad_entryDN->ad_type->sat_equality;
+ assert( mr != NULL );
+
+ rc = value_match( &match, slap_schema.si_ad_entryDN, mr,
+ SLAP_MR_EXT, &e->e_nname, &ava->aa_value, &text );
+
+ if( rc != LDAP_SUCCESS ) return rc;
+ if( match == 0 ) return LDAP_COMPARE_TRUE;
+ return LDAP_COMPARE_FALSE;
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+
+#ifdef LDAP_COMP_MATCH
+ if ( is_aliased_attribute && ava->aa_cf )
+ {
+ a_alias = is_aliased_attribute ( ava->aa_desc );
+ if ( a_alias )
+ ava->aa_desc = a_alias->aa_aliased_ad;
+ else
+ ava->aa_cf = NULL;
+ }
+#endif
+
+ for(a = attrs_find( e->e_attrs, ava->aa_desc );
+ a != NULL;
+ a = attrs_find( a->a_next, ava->aa_desc ) )
+ {
+ int use;
+ MatchingRule *mr;
+ struct berval *bv;
+
+ if (( ava->aa_desc != a->a_desc ) && !access_allowed( op,
+ e, a->a_desc, &ava->aa_value, ACL_SEARCH, NULL ))
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ continue;
+ }
+
+ use = SLAP_MR_EQUALITY;
+
+ switch ( type ) {
+ case LDAP_FILTER_APPROX:
+ use = SLAP_MR_EQUALITY_APPROX;
+ mr = a->a_desc->ad_type->sat_approx;
+ if( mr != NULL ) break;
+
+ /* fallthru: use EQUALITY matching rule if no APPROX rule */
+
+ case LDAP_FILTER_EQUALITY:
+ /* use variable set above so fall thru use is not clobbered */
+ mr = a->a_desc->ad_type->sat_equality;
+ break;
+
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ use = SLAP_MR_ORDERING;
+ mr = a->a_desc->ad_type->sat_ordering;
+ break;
+
+ default:
+ mr = NULL;
+ }
+
+ if( mr == NULL ) {
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ continue;
+ }
+
+ /* We have no Sort optimization for Approx matches */
+ if (( a->a_flags & SLAP_ATTR_SORTED_VALS ) && type != LDAP_FILTER_APPROX ) {
+ unsigned slot;
+ int ret;
+
+ /* For Ordering matches, we just need to do one comparison with
+ * either the first (least) or last (greatest) value.
+ */
+ if ( use == SLAP_MR_ORDERING ) {
+ const char *text;
+ int match, which;
+ which = (type == LDAP_FILTER_LE) ? 0 : a->a_numvals-1;
+ ret = value_match( &match, a->a_desc, mr, use,
+ &a->a_nvals[which], &ava->aa_value, &text );
+ if ( ret != LDAP_SUCCESS ) return ret;
+ if (( type == LDAP_FILTER_LE && match <= 0 ) ||
+ ( type == LDAP_FILTER_GE && match >= 0 ))
+ return LDAP_COMPARE_TRUE;
+ continue;
+ }
+ /* Only Equality will get here */
+ ret = attr_valfind( a, use | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &ava->aa_value, &slot, NULL );
+ if ( ret == LDAP_SUCCESS )
+ return LDAP_COMPARE_TRUE;
+ else if ( ret != LDAP_NO_SUCH_ATTRIBUTE )
+ return ret;
+#if 0
+ /* The following is useful if we want to know which values
+ * matched an ordering test. But here we don't care, we just
+ * want to know if any value did, and that is checked above.
+ */
+ if ( ret == LDAP_NO_SUCH_ATTRIBUTE ) {
+ /* If insertion point is not the end of the list, there was
+ * at least one value greater than the assertion.
+ */
+ if ( type == LDAP_FILTER_GE && slot < a->a_numvals )
+ return LDAP_COMPARE_TRUE;
+ /* Likewise, if insertion point is not the head of the list,
+ * there was at least one value less than the assertion.
+ */
+ if ( type == LDAP_FILTER_LE && slot > 0 )
+ return LDAP_COMPARE_TRUE;
+ return LDAP_COMPARE_FALSE;
+ }
+#endif
+ continue;
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( nibble_mem_allocator && ava->aa_cf && !a->a_comp_data ) {
+ /* Component Matching */
+ for ( num_attr_vals = 0; a->a_vals[num_attr_vals].bv_val != NULL; num_attr_vals++ );
+ if ( num_attr_vals <= 0 )/* no attribute value */
+ return LDAP_INAPPROPRIATE_MATCHING;
+ num_attr_vals++;/* for NULL termination */
+
+ /* following malloced will be freed by comp_tree_free () */
+ a->a_comp_data = SLAP_MALLOC( sizeof( ComponentData ) + sizeof( ComponentSyntaxInfo* )*num_attr_vals );
+
+ if ( !a->a_comp_data ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ a->a_comp_data->cd_tree = (ComponentSyntaxInfo**)((char*)a->a_comp_data + sizeof(ComponentData));
+ i = num_attr_vals;
+ for ( ; i ; i-- ) {
+ a->a_comp_data->cd_tree[ i-1 ] = (ComponentSyntaxInfo*)NULL;
+ }
+
+ a->a_comp_data->cd_mem_op = nibble_mem_allocator ( 1024*10*(num_attr_vals-1), 1024 );
+ if ( a->a_comp_data->cd_mem_op == NULL ) {
+ free ( a->a_comp_data );
+ a->a_comp_data = NULL;
+ return LDAP_OPERATIONS_ERROR;
+ }
+ }
+
+ i = 0;
+#endif
+
+ for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) {
+ int ret, match;
+ const char *text;
+
+#ifdef LDAP_COMP_MATCH
+ if( attr_converter && ava->aa_cf && a->a_comp_data ) {
+ /* Check if decoded component trees are already linked */
+ struct berval cf_bv = { 20, "componentFilterMatch" };
+ MatchingRule* cf_mr = mr_bvfind( &cf_bv );
+ MatchingRuleAssertion mra;
+ mra.ma_cf = ava->aa_cf;
+
+ if ( a->a_comp_data->cd_tree[i] == NULL )
+ a->a_comp_data->cd_tree[i] = attr_converter (a, a->a_desc->ad_type->sat_syntax, (a->a_vals + i));
+ /* decoding error */
+ if ( !a->a_comp_data->cd_tree[i] ) {
+ free_ComponentData ( a );
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ ret = value_match( &match, a->a_desc, cf_mr,
+ SLAP_MR_COMPONENT,
+ (struct berval*)a->a_comp_data->cd_tree[i++],
+ (void*)&mra, &text );
+ if ( ret == LDAP_INAPPROPRIATE_MATCHING ) {
+ /* cached component tree is broken, just remove it */
+ free_ComponentData ( a );
+ return ret;
+ }
+ if ( a_alias )
+ ava->aa_desc = a_alias->aa_aliasing_ad;
+
+ } else
+#endif
+ {
+ ret = ordered_value_match( &match, a->a_desc, mr, use,
+ bv, &ava->aa_value, &text );
+ }
+
+ if( ret != LDAP_SUCCESS ) {
+ rc = ret;
+ break;
+ }
+
+ switch ( type ) {
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_APPROX:
+ if ( match == 0 ) return LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_FILTER_GE:
+ if ( match >= 0 ) return LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_FILTER_LE:
+ if ( match <= 0 ) return LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+ }
+
+#ifdef LDAP_COMP_MATCH
+ if ( a_alias )
+ ava->aa_desc = a_alias->aa_aliasing_ad;
+#endif
+
+ return rc;
+}
+
+
+static int
+test_presence_filter(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc )
+{
+ Attribute *a;
+ int rc;
+
+ if ( !access_allowed( op, e, desc, NULL, ACL_SEARCH, NULL ) ) {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ if ( desc == slap_schema.si_ad_hasSubordinates ) {
+ /*
+ * XXX: fairly optimistic: if the function is defined,
+ * then PRESENCE must succeed, because hasSubordinate
+ * is boolean-valued; I think we may live with this
+ * simplification by now.
+ */
+ if ( op && op->o_bd && op->o_bd->be_has_subordinates ) {
+ return LDAP_COMPARE_TRUE;
+ }
+
+ return LDAP_COMPARE_FALSE;
+ }
+
+ if ( desc == slap_schema.si_ad_entryDN ||
+ desc == slap_schema.si_ad_subschemaSubentry )
+ {
+ /* entryDN and subschemaSubentry are always present */
+ return LDAP_COMPARE_TRUE;
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+
+ for(a = attrs_find( e->e_attrs, desc );
+ a != NULL;
+ a = attrs_find( a->a_next, desc ) )
+ {
+ if (( desc != a->a_desc ) && !access_allowed( op,
+ e, a->a_desc, NULL, ACL_SEARCH, NULL ))
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ continue;
+ }
+
+ rc = LDAP_COMPARE_TRUE;
+ break;
+ }
+
+ return rc;
+}
+
+
+static int
+test_filter_and(
+ Operation *op,
+ Entry *e,
+ Filter *flist )
+{
+ Filter *f;
+ int rtn = LDAP_COMPARE_TRUE; /* True if empty */
+
+ Debug( LDAP_DEBUG_FILTER, "=> test_filter_and\n" );
+
+ for ( f = flist; f != NULL; f = f->f_next ) {
+ int rc = test_filter( op, e, f );
+
+ if ( rc == LDAP_COMPARE_FALSE ) {
+ /* filter is False */
+ rtn = rc;
+ break;
+ }
+
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ /* filter is Undefined unless later elements are False */
+ rtn = rc;
+ }
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "<= test_filter_and %d\n", rtn );
+
+ return rtn;
+}
+
+static int
+test_filter_or(
+ Operation *op,
+ Entry *e,
+ Filter *flist )
+{
+ Filter *f;
+ int rtn = LDAP_COMPARE_FALSE; /* False if empty */
+
+ Debug( LDAP_DEBUG_FILTER, "=> test_filter_or\n" );
+
+ for ( f = flist; f != NULL; f = f->f_next ) {
+ int rc = test_filter( op, e, f );
+
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ /* filter is True */
+ rtn = rc;
+ break;
+ }
+
+ if ( rc != LDAP_COMPARE_FALSE ) {
+ /* filter is Undefined unless later elements are True */
+ rtn = rc;
+ }
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "<= test_filter_or %d\n", rtn );
+ return rtn;
+}
+
+
+static int
+test_substrings_filter(
+ Operation *op,
+ Entry *e,
+ Filter *f )
+{
+ Attribute *a;
+ int rc;
+
+ Debug( LDAP_DEBUG_FILTER, "begin test_substrings_filter\n" );
+
+ if ( !access_allowed( op, e,
+ f->f_sub_desc, NULL, ACL_SEARCH, NULL ) )
+ {
+ return LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+
+ for(a = attrs_find( e->e_attrs, f->f_sub_desc );
+ a != NULL;
+ a = attrs_find( a->a_next, f->f_sub_desc ) )
+ {
+ MatchingRule *mr;
+ struct berval *bv;
+
+ if (( f->f_sub_desc != a->a_desc ) && !access_allowed( op,
+ e, a->a_desc, NULL, ACL_SEARCH, NULL ))
+ {
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ continue;
+ }
+
+ mr = a->a_desc->ad_type->sat_substr;
+ if( mr == NULL ) {
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ continue;
+ }
+
+ for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) {
+ int ret, match;
+ const char *text;
+
+ ret = value_match( &match, a->a_desc, mr, SLAP_MR_SUBSTR,
+ bv, f->f_sub, &text );
+
+ if( ret != LDAP_SUCCESS ) {
+ rc = ret;
+ break;
+ }
+ if ( match == 0 ) return LDAP_COMPARE_TRUE;
+ }
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "end test_substrings_filter %d\n",
+ rc );
+ return rc;
+}
diff --git a/servers/slapd/frontend.c b/servers/slapd/frontend.c
new file mode 100644
index 0000000..d0ca419
--- /dev/null
+++ b/servers/slapd/frontend.c
@@ -0,0 +1,174 @@
+/* frontend.c - routines for dealing with frontend */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <sys/stat.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "lber_pvt.h"
+
+#include "ldap_rq.h"
+
+static BackendInfo slap_frontendInfo;
+static BackendDB slap_frontendDB;
+BackendDB *frontendDB;
+
+static int
+fe_entry_get_rw(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **e )
+{
+ BackendDB *bd;
+ int rc = LDAP_NO_SUCH_OBJECT;
+
+ bd = op->o_bd;
+ op->o_bd = select_backend( ndn, 0 );
+ if ( op->o_bd != NULL ) {
+ if ( op->o_bd->be_fetch ) {
+ rc = op->o_bd->be_fetch( op, ndn, oc, at, rw, e );
+ }
+ }
+ op->o_bd = bd;
+
+ return rc;
+}
+
+static int
+fe_entry_release_rw(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ BackendDB *bd;
+ int rc = LDAP_NO_SUCH_OBJECT;
+
+ bd = op->o_bd;
+ op->o_bd = select_backend( &e->e_nname, 0 );
+ if ( op->o_bd != NULL ) {
+ if ( op->o_bd->be_release ) {
+ rc = op->o_bd->be_release( op, e, rw );
+ }
+ }
+ op->o_bd = bd;
+
+ return rc;
+}
+
+int
+frontend_init( void )
+{
+ /* data */
+ frontendDB = &slap_frontendDB;
+ frontendDB->bd_self = frontendDB;
+
+ /* ACLs */
+ frontendDB->be_dfltaccess = ACL_READ;
+
+ /* limits */
+ frontendDB->be_def_limit.lms_t_soft = SLAPD_DEFAULT_TIMELIMIT; /* backward compatible limits */
+ frontendDB->be_def_limit.lms_t_hard = 0;
+ frontendDB->be_def_limit.lms_s_soft = SLAPD_DEFAULT_SIZELIMIT; /* backward compatible limits */
+ frontendDB->be_def_limit.lms_s_hard = 0;
+ frontendDB->be_def_limit.lms_s_unchecked = -1; /* no limit on unchecked size */
+ frontendDB->be_def_limit.lms_s_pr = 0; /* page limit */
+ frontendDB->be_def_limit.lms_s_pr_hide = 0; /* don't hide number of entries left */
+ frontendDB->be_def_limit.lms_s_pr_total = 0; /* number of total entries returned by pagedResults equal to hard limit */
+
+ ldap_pvt_thread_mutex_init( &frontendDB->be_pcsn_st.be_pcsn_mutex );
+
+ /* suffix */
+ frontendDB->be_suffix = ch_calloc( 2, sizeof( struct berval ) );
+ ber_str2bv( "", 0, 1, &frontendDB->be_suffix[0] );
+ BER_BVZERO( &frontendDB->be_suffix[1] );
+ frontendDB->be_nsuffix = ch_calloc( 2, sizeof( struct berval ) );
+ ber_str2bv( "", 0, 1, &frontendDB->be_nsuffix[0] );
+ BER_BVZERO( &frontendDB->be_nsuffix[1] );
+
+ /* info */
+ frontendDB->bd_info = &slap_frontendInfo;
+
+ SLAP_BFLAGS(frontendDB) |= SLAP_BFLAG_FRONTEND;
+
+ /* name */
+ frontendDB->bd_info->bi_type = "frontend";
+
+ /* known controls */
+ {
+ int i;
+
+ frontendDB->bd_info->bi_controls = slap_known_controls;
+
+ for ( i = 0; slap_known_controls[ i ]; i++ ) {
+ int cid;
+
+ if ( slap_find_control_id( slap_known_controls[ i ], &cid )
+ == LDAP_CONTROL_NOT_FOUND )
+ {
+ assert( 0 );
+ return -1;
+ }
+
+ frontendDB->bd_info->bi_ctrls[ cid ] = 1;
+ frontendDB->be_ctrls[ cid ] = 1;
+ }
+ }
+
+ /* calls */
+ frontendDB->bd_info->bi_op_abandon = fe_op_abandon;
+ frontendDB->bd_info->bi_op_add = fe_op_add;
+ frontendDB->bd_info->bi_op_bind = fe_op_bind;
+ frontendDB->bd_info->bi_op_compare = fe_op_compare;
+ frontendDB->bd_info->bi_op_delete = fe_op_delete;
+ frontendDB->bd_info->bi_op_modify = fe_op_modify;
+ frontendDB->bd_info->bi_op_modrdn = fe_op_modrdn;
+ frontendDB->bd_info->bi_op_search = fe_op_search;
+ frontendDB->bd_info->bi_extended = fe_extended;
+ frontendDB->bd_info->bi_operational = fe_aux_operational;
+ frontendDB->bd_info->bi_entry_get_rw = fe_entry_get_rw;
+ frontendDB->bd_info->bi_entry_release_rw = fe_entry_release_rw;
+ frontendDB->bd_info->bi_access_allowed = fe_access_allowed;
+ frontendDB->bd_info->bi_acl_group = fe_acl_group;
+ frontendDB->bd_info->bi_acl_attribute = fe_acl_attribute;
+
+#if 0
+ /* FIXME: is this too early? */
+ return backend_startup_one( frontendDB );
+#endif
+
+ return 0;
+}
+
diff --git a/servers/slapd/globals.c b/servers/slapd/globals.c
new file mode 100644
index 0000000..c785165
--- /dev/null
+++ b/servers/slapd/globals.c
@@ -0,0 +1,38 @@
+/* globals.c - various global variables */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include "lber_pvt.h"
+
+#include "slap.h"
+
+
+/*
+ * Global variables, in general, should be declared in the file
+ * primarily responsible for its management. Configurable globals
+ * belong in config.c. Variables declared here have no other
+ * sensible home.
+ */
+
+const struct berval slap_empty_bv = BER_BVC("");
+const struct berval slap_unknown_bv = BER_BVC("unknown");
+
+/* normalized boolean values */
+const struct berval slap_true_bv = BER_BVC("TRUE");
+const struct berval slap_false_bv = BER_BVC("FALSE");
+
diff --git a/servers/slapd/index.c b/servers/slapd/index.c
new file mode 100644
index 0000000..303cb9a
--- /dev/null
+++ b/servers/slapd/index.c
@@ -0,0 +1,91 @@
+/* index.c - index utilities */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <lutil.h>
+
+#include "slap.h"
+
+static slap_verbmasks idxstr[] = {
+ { BER_BVC("pres"), SLAP_INDEX_PRESENT },
+ { BER_BVC("eq"), SLAP_INDEX_EQUALITY },
+ { BER_BVC("approx"), SLAP_INDEX_APPROX },
+ { BER_BVC("subinitial"), SLAP_INDEX_SUBSTR_INITIAL },
+ { BER_BVC("subany"), SLAP_INDEX_SUBSTR_ANY },
+ { BER_BVC("subfinal"), SLAP_INDEX_SUBSTR_FINAL },
+ { BER_BVC("sub"), SLAP_INDEX_SUBSTR_DEFAULT },
+ { BER_BVC("substr"), 0 },
+ { BER_BVC("notags"), SLAP_INDEX_NOTAGS },
+ { BER_BVC("nolang"), 0 }, /* backwards compat */
+ { BER_BVC("nosubtypes"), SLAP_INDEX_NOSUBTYPES },
+ { BER_BVNULL, 0 }
+};
+
+
+int slap_str2index( const char *str, slap_mask_t *idx )
+{
+ int i;
+
+ i = verb_to_mask( str, idxstr );
+ if ( BER_BVISNULL(&idxstr[i].word) ) return LDAP_OTHER;
+ while ( !idxstr[i].mask ) i--;
+ *idx = idxstr[i].mask;
+
+
+ return LDAP_SUCCESS;
+}
+
+void slap_index2bvlen( slap_mask_t idx, struct berval *bv )
+{
+ int i;
+
+ bv->bv_len = 0;
+
+ for ( i=0; !BER_BVISNULL( &idxstr[i].word ); i++ ) {
+ if ( !idxstr[i].mask ) continue;
+ if ( IS_SLAP_INDEX( idx, idxstr[i].mask )) {
+ if ( (idxstr[i].mask & SLAP_INDEX_SUBSTR) &&
+ ((idx & SLAP_INDEX_SUBSTR_DEFAULT) != idxstr[i].mask))
+ continue;
+ if ( bv->bv_len ) bv->bv_len++;
+ bv->bv_len += idxstr[i].word.bv_len;
+ }
+ }
+}
+
+/* caller must provide buffer space, after calling index2bvlen */
+void slap_index2bv( slap_mask_t idx, struct berval *bv )
+{
+ int i;
+ char *ptr;
+
+ if ( !bv->bv_len ) return;
+
+ ptr = bv->bv_val;
+ for ( i=0; !BER_BVISNULL( &idxstr[i].word ); i++ ) {
+ if ( !idxstr[i].mask ) continue;
+ if ( IS_SLAP_INDEX( idx, idxstr[i].mask )) {
+ if ( (idxstr[i].mask & SLAP_INDEX_SUBSTR) &&
+ ((idx & SLAP_INDEX_SUBSTR_DEFAULT) != idxstr[i].mask))
+ continue;
+ if ( ptr != bv->bv_val ) *ptr++ = ',';
+ ptr = lutil_strcopy( ptr, idxstr[i].word.bv_val );
+ }
+ }
+}
diff --git a/servers/slapd/init.c b/servers/slapd/init.c
new file mode 100644
index 0000000..591276b
--- /dev/null
+++ b/servers/slapd/init.c
@@ -0,0 +1,330 @@
+/* init.c - initialize various things */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lber_pvt.h"
+
+#include "ldap_rq.h"
+
+/*
+ * read-only global variables or variables only written by the listener
+ * thread (after they are initialized) - no need to protect them with a mutex.
+ */
+int slap_debug = 0;
+
+#ifdef LDAP_DEBUG
+int ldap_syslog = LDAP_DEBUG_STATS;
+#else
+int ldap_syslog;
+#endif
+
+#ifdef LOG_DEBUG
+int ldap_syslog_level = LOG_DEBUG;
+#endif
+
+BerVarray default_referral = NULL;
+
+/*
+ * global variables that need mutex protection
+ */
+ldap_pvt_thread_pool_t connection_pool;
+int connection_pool_max = SLAP_MAX_WORKER_THREADS;
+int connection_pool_queues = 1;
+int slap_tool_thread_max = 1;
+
+slap_counters_t slap_counters, *slap_counters_list;
+
+static const char* slap_name = NULL;
+int slapMode = SLAP_UNDEFINED_MODE;
+
+int
+slap_init( int mode, const char *name )
+{
+ int rc;
+
+ assert( mode );
+
+ if ( slapMode != SLAP_UNDEFINED_MODE ) {
+ /* Make sure we write something to stderr */
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s init: init called twice (old=%d, new=%d)\n",
+ name, slapMode, mode );
+
+ return 1;
+ }
+
+ slapMode = mode;
+
+ slap_op_init();
+
+ ldap_pvt_thread_mutex_init( &slapd_init_mutex );
+ ldap_pvt_thread_cond_init( &slapd_init_cond );
+
+#ifdef SLAPD_MODULES
+ if ( module_init() != 0 ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: module_init failed\n",
+ name );
+ return 1;
+ }
+#endif
+
+ if ( slap_schema_init( ) != 0 ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: slap_schema_init failed\n",
+ name );
+ return 1;
+ }
+
+ if ( filter_init() != 0 ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: filter_init failed\n",
+ name );
+ return 1;
+ }
+
+ if ( entry_init() != 0 ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: entry_init failed\n",
+ name );
+ return 1;
+ }
+
+ switch ( slapMode & SLAP_MODE ) {
+ case SLAP_SERVER_MODE:
+ root_dse_init();
+
+ /* FALLTHRU */
+ case SLAP_TOOL_MODE:
+ Debug( LDAP_DEBUG_TRACE,
+ "%s init: initiated %s.\n", name,
+ (mode & SLAP_MODE) == SLAP_TOOL_MODE ? "tool" : "server" );
+
+ slap_name = name;
+
+ ldap_pvt_thread_pool_init_q( &connection_pool,
+ connection_pool_max, 0, connection_pool_queues);
+
+ slap_counters_init( &slap_counters );
+
+ ldap_pvt_thread_mutex_init( &slapd_rq.rq_mutex );
+ LDAP_STAILQ_INIT( &slapd_rq.task_list );
+ LDAP_STAILQ_INIT( &slapd_rq.run_list );
+
+ slap_passwd_init();
+
+ rc = slap_sasl_init();
+
+ if( rc == 0 ) {
+ rc = backend_init( );
+ }
+ if ( rc )
+ return rc;
+
+ break;
+
+ default:
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s init: undefined mode (%d).\n", name, mode );
+
+ rc = 1;
+ break;
+ }
+
+ if ( slap_controls_init( ) != 0 ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: slap_controls_init failed\n",
+ name );
+ return 1;
+ }
+
+ if ( frontend_init() ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: frontend_init failed\n",
+ name );
+ return 1;
+ }
+
+ if ( overlay_init() ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: overlay_init failed\n",
+ name );
+ return 1;
+ }
+
+ if ( glue_sub_init() ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: glue/subordinate init failed\n",
+ name );
+
+ return 1;
+ }
+
+ if ( acl_init() ) {
+ slap_debug |= LDAP_DEBUG_NONE;
+ Debug( LDAP_DEBUG_ANY,
+ "%s: acl_init failed\n",
+ name );
+ return 1;
+ }
+
+ return rc;
+}
+
+int slap_startup( Backend *be )
+{
+ int rc;
+ Debug( LDAP_DEBUG_TRACE,
+ "%s startup: initiated.\n",
+ slap_name );
+
+ rc = backend_startup( be );
+ if ( !rc && ( slapMode & SLAP_SERVER_MODE ))
+ slapMode |= SLAP_SERVER_RUNNING;
+ return rc;
+}
+
+int slap_shutdown( Backend *be )
+{
+ Debug( LDAP_DEBUG_TRACE,
+ "%s shutdown: initiated\n",
+ slap_name );
+
+ /* Make sure the pool stops now even if we did not start up fully */
+ ldap_pvt_thread_pool_close( &connection_pool, 1 );
+
+ /* let backends do whatever cleanup they need to do */
+ return backend_shutdown( be );
+}
+
+int slap_destroy(void)
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s destroy: freeing system resources.\n",
+ slap_name );
+
+ if ( default_referral ) {
+ ber_bvarray_free( default_referral );
+ }
+
+ ldap_pvt_thread_pool_free( &connection_pool );
+
+ /* clear out any thread-keys for the main thread */
+ ldap_pvt_thread_pool_context_reset( ldap_pvt_thread_pool_context());
+
+ rc = backend_destroy();
+
+ slap_sasl_destroy();
+
+ /* rootdse destroy goes before entry_destroy()
+ * because it may use entry_free() */
+ root_dse_destroy();
+ entry_destroy();
+
+ switch ( slapMode & SLAP_MODE ) {
+ case SLAP_SERVER_MODE:
+ case SLAP_TOOL_MODE:
+ slap_counters_destroy( &slap_counters );
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "slap_destroy(): undefined mode (%d).\n", slapMode );
+
+ rc = 1;
+ break;
+
+ }
+
+ ldap_pvt_thread_mutex_destroy( &slapd_init_mutex );
+ ldap_pvt_thread_cond_destroy( &slapd_init_cond );
+
+ slap_op_destroy();
+
+ ldap_pvt_thread_destroy();
+
+ /* should destroy the above mutex */
+ return rc;
+}
+
+void slap_counters_init( slap_counters_t *sc )
+{
+ int i;
+
+ ldap_pvt_thread_mutex_init( &sc->sc_mutex );
+ ldap_pvt_mp_init( sc->sc_bytes );
+ ldap_pvt_mp_init( sc->sc_pdu );
+ ldap_pvt_mp_init( sc->sc_entries );
+ ldap_pvt_mp_init( sc->sc_refs );
+
+ ldap_pvt_mp_init( sc->sc_ops_initiated );
+ ldap_pvt_mp_init( sc->sc_ops_completed );
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ ldap_pvt_mp_init( sc->sc_ops_initiated_[ i ] );
+ ldap_pvt_mp_init( sc->sc_ops_completed_[ i ] );
+ }
+}
+
+void slap_counters_destroy( slap_counters_t *sc )
+{
+ int i;
+
+ ldap_pvt_thread_mutex_destroy( &sc->sc_mutex );
+ ldap_pvt_mp_clear( sc->sc_bytes );
+ ldap_pvt_mp_clear( sc->sc_pdu );
+ ldap_pvt_mp_clear( sc->sc_entries );
+ ldap_pvt_mp_clear( sc->sc_refs );
+
+ ldap_pvt_mp_clear( sc->sc_ops_initiated );
+ ldap_pvt_mp_clear( sc->sc_ops_completed );
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ ldap_pvt_mp_clear( sc->sc_ops_initiated_[ i ] );
+ ldap_pvt_mp_clear( sc->sc_ops_completed_[ i ] );
+ }
+}
+
diff --git a/servers/slapd/ldapsync.c b/servers/slapd/ldapsync.c
new file mode 100644
index 0000000..f922cd5
--- /dev/null
+++ b/servers/slapd/ldapsync.c
@@ -0,0 +1,525 @@
+/* ldapsync.c -- LDAP Content Sync Routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../../libraries/liblber/lber-int.h" /* get ber_strndup() */
+#include "lutil_ldap.h"
+
+struct slap_sync_cookie_s slap_sync_cookie =
+ LDAP_STAILQ_HEAD_INITIALIZER( slap_sync_cookie );
+
+void
+slap_compose_sync_cookie(
+ Operation *op,
+ struct berval *cookie,
+ BerVarray csn,
+ int rid,
+ int sid,
+ struct berval *delcsn )
+{
+ int len, numcsn = 0;
+
+ if ( csn ) {
+ for (; !BER_BVISNULL( &csn[numcsn] ); numcsn++);
+ }
+
+ if ( numcsn == 0 || rid == -1 ) {
+ char cookiestr[ LDAP_PVT_CSNSTR_BUFSIZE + 20 ];
+ if ( rid == -1 ) {
+ cookiestr[0] = '\0';
+ len = 0;
+ } else {
+ len = snprintf( cookiestr, sizeof( cookiestr ),
+ "rid=%03d", rid );
+ if ( sid >= 0 ) {
+ len += sprintf( cookiestr+len, ",sid=%03x", sid );
+ }
+ }
+ ber_str2bv_x( cookiestr, len, 1, cookie,
+ op ? op->o_tmpmemctx : NULL );
+ } else {
+ char *ptr;
+ int i;
+
+ len = 0;
+ for ( i=0; i<numcsn; i++)
+ len += csn[i].bv_len + 1;
+ if ( delcsn && !BER_BVISEMPTY(delcsn) )
+ len += STRLENOF(",delcsn=") + delcsn->bv_len;
+
+ len += STRLENOF("rid=123,csn=");
+ if ( sid >= 0 )
+ len += STRLENOF("sid=xxx,");
+
+ cookie->bv_val = slap_sl_malloc( len, op ? op->o_tmpmemctx : NULL );
+
+ len = sprintf( cookie->bv_val, "rid=%03d,", rid );
+ ptr = cookie->bv_val + len;
+ if ( sid >= 0 ) {
+ ptr += sprintf( ptr, "sid=%03x,", sid );
+ }
+ ptr = lutil_strcopy( ptr, "csn=" );
+ for ( i=0; i<numcsn; i++) {
+ ptr = lutil_strncopy( ptr, csn[i].bv_val, csn[i].bv_len );
+ *ptr++ = ';';
+ }
+ ptr--;
+ if ( delcsn && !BER_BVISEMPTY(delcsn) ) {
+ ptr = lutil_strcopy( ptr, ",delcsn=" );
+ ptr = lutil_strncopy( ptr, delcsn->bv_val, delcsn->bv_len );
+ }
+ *ptr = '\0';
+ cookie->bv_len = ptr - cookie->bv_val;
+ }
+}
+
+void
+slap_sync_cookie_free(
+ struct sync_cookie *cookie,
+ int free_cookie
+)
+{
+ if ( cookie == NULL )
+ return;
+
+ if ( cookie->sids ) {
+ ch_free( cookie->sids );
+ cookie->sids = NULL;
+ }
+
+ if ( cookie->ctxcsn ) {
+ ber_bvarray_free( cookie->ctxcsn );
+ cookie->ctxcsn = NULL;
+ }
+ cookie->numcsns = 0;
+ if ( !BER_BVISNULL( &cookie->octet_str )) {
+ ch_free( cookie->octet_str.bv_val );
+ BER_BVZERO( &cookie->octet_str );
+ }
+
+ if ( !BER_BVISNULL( &cookie->delcsn )) {
+ ch_free( cookie->delcsn.bv_val );
+ BER_BVZERO( &cookie->delcsn );
+ }
+
+ if ( free_cookie ) {
+ ch_free( cookie );
+ }
+
+ return;
+}
+
+int
+slap_parse_csn_sid( struct berval *csnp )
+{
+ char *p, *q;
+ struct berval csn = *csnp;
+ int i;
+
+ p = ber_bvchr( &csn, '#' );
+ if ( !p )
+ return -1;
+ p++;
+ csn.bv_len -= p - csn.bv_val;
+ csn.bv_val = p;
+
+ p = ber_bvchr( &csn, '#' );
+ if ( !p )
+ return -1;
+ p++;
+ csn.bv_len -= p - csn.bv_val;
+ csn.bv_val = p;
+
+ q = ber_bvchr( &csn, '#' );
+ if ( !q )
+ return -1;
+
+ csn.bv_len = q - p;
+
+ i = strtol( p, &q, 16 );
+ if ( p == q || q != p + csn.bv_len || i < 0 || i > SLAP_SYNC_SID_MAX ) {
+ i = -1;
+ }
+
+ return i;
+}
+
+int *
+slap_parse_csn_sids( BerVarray csns, int numcsns, void *memctx )
+{
+ int i, *ret;
+
+ ret = slap_sl_malloc( numcsns * sizeof(int), memctx );
+ for ( i=0; i<numcsns; i++ ) {
+ ret[i] = slap_parse_csn_sid( &csns[i] );
+ }
+ return ret;
+}
+
+static slap_mr_match_func sidsort_cmp;
+
+static const MatchingRule sidsort_mr = {
+ { 0 },
+ NULL,
+ { 0 },
+ { 0 },
+ 0,
+ NULL, NULL, NULL, sidsort_cmp
+};
+static const AttributeType sidsort_at = {
+ { 0 },
+ { 0 },
+ NULL, NULL, (MatchingRule *)&sidsort_mr,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, SLAP_AT_SORTED_VAL
+};
+static const AttributeDescription sidsort_ad = {
+ NULL,
+ (AttributeType *)&sidsort_at
+};
+
+static int
+sidsort_cmp(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *b1,
+ void *v2 )
+{
+ struct berval *b2 = v2;
+ *matchp = b1->bv_len - b2->bv_len;
+ return LDAP_SUCCESS;
+}
+
+/* sort CSNs by SID. Use a fake Attribute with our own
+ * syntax and matching rule, which sorts the nvals by
+ * bv_len order. Stuff our sids into the bv_len.
+ */
+int
+slap_sort_csn_sids( BerVarray csns, int *sids, int numcsns, void *memctx )
+{
+ Attribute a;
+ const char *text;
+ int i, rc;
+
+ a.a_desc = (AttributeDescription *)&sidsort_ad;
+ a.a_nvals = slap_sl_malloc( numcsns * sizeof(struct berval), memctx );
+ for ( i=0; i<numcsns; i++ ) {
+ a.a_nvals[i].bv_len = sids[i];
+ a.a_nvals[i].bv_val = NULL;
+ }
+ a.a_vals = csns;
+ a.a_numvals = numcsns;
+ a.a_flags = 0;
+ rc = slap_sort_vals( (Modifications *)&a, &text, &i, memctx );
+ for ( i=0; i<numcsns; i++ )
+ sids[i] = a.a_nvals[i].bv_len;
+ slap_sl_free( a.a_nvals, memctx );
+ return rc;
+}
+
+void
+slap_insert_csn_sids(
+ struct sync_cookie *ck,
+ int pos,
+ int sid,
+ struct berval *csn
+)
+{
+ int i;
+ ck->numcsns++;
+ ck->ctxcsn = ch_realloc( ck->ctxcsn,
+ (ck->numcsns+1) * sizeof(struct berval));
+ BER_BVZERO( &ck->ctxcsn[ck->numcsns] );
+ ck->sids = ch_realloc( ck->sids, ck->numcsns * sizeof(int));
+ for ( i = ck->numcsns-1; i > pos; i-- ) {
+ ck->ctxcsn[i] = ck->ctxcsn[i-1];
+ ck->sids[i] = ck->sids[i-1];
+ }
+ ck->sids[i] = sid;
+ ber_dupbv( &ck->ctxcsn[i], csn );
+}
+
+int
+slap_parse_sync_cookie(
+ struct sync_cookie *cookie,
+ void *memctx
+)
+{
+ char *csn_ptr;
+ char *csn_str;
+ char *cval;
+ char *next, *end;
+ AttributeDescription *ad = slap_schema.si_ad_entryCSN;
+
+ if ( cookie == NULL )
+ return -1;
+
+ if ( cookie->octet_str.bv_len <= STRLENOF( "rid=" ) )
+ return -1;
+
+ cookie->rid = -1;
+ cookie->sid = -1;
+ cookie->ctxcsn = NULL;
+ cookie->sids = NULL;
+ cookie->numcsns = 0;
+ BER_BVZERO( &cookie->delcsn );
+
+ end = cookie->octet_str.bv_val + cookie->octet_str.bv_len;
+
+ for ( next=cookie->octet_str.bv_val; next < end; ) {
+ if ( !strncmp( next, "rid=", STRLENOF("rid=") )) {
+ char *rid_ptr = next;
+ cookie->rid = strtol( &rid_ptr[ STRLENOF( "rid=" ) ], &next, 10 );
+ if ( next == rid_ptr ||
+ next > end ||
+ ( *next && *next != ',' ) ||
+ cookie->rid < 0 ||
+ cookie->rid > SLAP_SYNC_RID_MAX )
+ {
+ return -1;
+ }
+ if ( *next == ',' ) {
+ next++;
+ }
+ if ( !ad ) {
+ break;
+ }
+ continue;
+ }
+ if ( !strncmp( next, "sid=", STRLENOF("sid=") )) {
+ char *sid_ptr = next;
+ sid_ptr = next;
+ cookie->sid = strtol( &sid_ptr[ STRLENOF( "sid=" ) ], &next, 16 );
+ if ( next == sid_ptr ||
+ next > end ||
+ ( *next && *next != ',' ) ||
+ cookie->sid < 0 ||
+ cookie->sid > SLAP_SYNC_SID_MAX )
+ {
+ return -1;
+ }
+ if ( *next == ',' ) {
+ next++;
+ }
+ continue;
+ }
+ if ( !strncmp( next, "csn=", STRLENOF("csn=") )) {
+ struct berval stamp;
+
+ next += STRLENOF("csn=");
+ while ( next < end ) {
+ csn_str = next;
+ csn_ptr = strchr( csn_str, '#' );
+ if ( !csn_ptr || csn_ptr > end )
+ break;
+ /* ad will be NULL when called from main. we just
+ * want to parse the rid then. But we still iterate
+ * through the string to find the end.
+ */
+ cval = strchr( csn_ptr, ';' );
+ if ( !cval )
+ cval = strchr(csn_ptr, ',' );
+ if ( cval )
+ stamp.bv_len = cval - csn_str;
+ else
+ stamp.bv_len = end - csn_str;
+ if ( ad ) {
+ struct berval bv;
+ stamp.bv_val = csn_str;
+ if ( ad->ad_type->sat_syntax->ssyn_validate(
+ ad->ad_type->sat_syntax, &stamp ) != LDAP_SUCCESS )
+ break;
+ if ( ad->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_equality,
+ &stamp, &bv, memctx ) != LDAP_SUCCESS )
+ break;
+ ber_bvarray_add_x( &cookie->ctxcsn, &bv, memctx );
+ cookie->numcsns++;
+ }
+ if ( cval ) {
+ next = cval + 1;
+ if ( *cval != ';' )
+ break;
+ } else {
+ next = end;
+ break;
+ }
+ }
+ continue;
+ }
+ if ( !strncmp( next, "delcsn=", STRLENOF("delcsn=") )) {
+ struct berval stamp;
+
+ next += STRLENOF("delcsn=");
+ while ( next < end ) {
+ csn_str = next;
+ csn_ptr = strchr( csn_str, '#' );
+ if ( !csn_ptr || csn_ptr > end )
+ break;
+ /* ad will be NULL when called from main. we just
+ * want to parse the rid then. But we still iterate
+ * through the string to find the end.
+ */
+ cval = strchr( csn_ptr, ';' );
+ if ( !cval )
+ cval = strchr(csn_ptr, ',' );
+ if ( cval )
+ stamp.bv_len = cval - csn_str;
+ else
+ stamp.bv_len = end - csn_str;
+ if ( ad ) {
+ struct berval bv;
+ stamp.bv_val = csn_str;
+ if ( ad->ad_type->sat_syntax->ssyn_validate(
+ ad->ad_type->sat_syntax, &stamp ) != LDAP_SUCCESS )
+ break;
+ if ( ad->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ ad->ad_type->sat_syntax,
+ ad->ad_type->sat_equality,
+ &stamp, &bv, memctx ) != LDAP_SUCCESS )
+ break;
+ cookie->delcsn = bv;
+ }
+ if ( cval ) {
+ next = cval + 1;
+ } else {
+ next = end;
+ }
+ break;
+ }
+ continue;
+ }
+ next++;
+ }
+ if ( cookie->numcsns ) {
+ cookie->sids = slap_parse_csn_sids( cookie->ctxcsn, cookie->numcsns,
+ memctx );
+ if ( cookie->numcsns > 1 )
+ slap_sort_csn_sids( cookie->ctxcsn, cookie->sids, cookie->numcsns, memctx );
+ }
+ return 0;
+}
+
+/* count the numcsns and regenerate the list of SIDs in a recomposed cookie */
+void
+slap_reparse_sync_cookie(
+ struct sync_cookie *cookie,
+ void *memctx )
+{
+ if ( cookie->ctxcsn ) {
+ for (; !BER_BVISNULL( &cookie->ctxcsn[cookie->numcsns] ); cookie->numcsns++);
+ }
+ if ( cookie->numcsns ) {
+ cookie->sids = slap_parse_csn_sids( cookie->ctxcsn, cookie->numcsns, NULL );
+ if ( cookie->numcsns > 1 )
+ slap_sort_csn_sids( cookie->ctxcsn, cookie->sids, cookie->numcsns, memctx );
+ }
+}
+
+int
+slap_init_sync_cookie_ctxcsn(
+ struct sync_cookie *cookie
+)
+{
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE + 4 ];
+ struct berval octet_str = BER_BVNULL;
+ struct berval ctxcsn = BER_BVNULL;
+
+ if ( cookie == NULL )
+ return -1;
+
+ octet_str.bv_len = snprintf( csnbuf, LDAP_PVT_CSNSTR_BUFSIZE + 4,
+ "csn=%4d%02d%02d%02d%02d%02dZ#%06x#%02x#%06x",
+ 1900, 1, 1, 0, 0, 0, 0, 0, 0 );
+ octet_str.bv_val = csnbuf;
+ ch_free( cookie->octet_str.bv_val );
+ ber_dupbv( &cookie->octet_str, &octet_str );
+
+ ctxcsn.bv_val = octet_str.bv_val + 4;
+ ctxcsn.bv_len = octet_str.bv_len - 4;
+ cookie->ctxcsn = NULL;
+ value_add_one( &cookie->ctxcsn, &ctxcsn );
+ cookie->numcsns = 1;
+ cookie->sid = -1;
+ BER_BVZERO( &cookie->delcsn );
+
+ return 0;
+}
+
+struct sync_cookie *
+slap_dup_sync_cookie(
+ struct sync_cookie *dst,
+ struct sync_cookie *src
+)
+{
+ struct sync_cookie *new;
+ int i;
+
+ if ( src == NULL )
+ return NULL;
+
+ if ( dst ) {
+ ber_bvarray_free( dst->ctxcsn );
+ dst->ctxcsn = NULL;
+ dst->sids = NULL;
+ ch_free( dst->octet_str.bv_val );
+ BER_BVZERO( &dst->octet_str );
+ new = dst;
+ } else {
+ new = ( struct sync_cookie * )
+ ch_calloc( 1, sizeof( struct sync_cookie ));
+ }
+
+ new->rid = src->rid;
+ new->sid = src->sid;
+ new->numcsns = src->numcsns;
+
+ if ( src->numcsns ) {
+ if ( ber_bvarray_dup_x( &new->ctxcsn, src->ctxcsn, NULL )) {
+ if ( !dst ) {
+ ch_free( new );
+ }
+ return NULL;
+ }
+ new->sids = ch_malloc( src->numcsns * sizeof(int) );
+ for (i=0; i<src->numcsns; i++)
+ new->sids[i] = src->sids[i];
+ }
+
+ if ( !BER_BVISNULL( &src->delcsn )) {
+ ber_dupbv( &new->delcsn, &src->delcsn );
+ }
+
+ if ( !BER_BVISNULL( &src->octet_str )) {
+ ber_dupbv( &new->octet_str, &src->octet_str );
+ }
+
+ return new;
+}
+
diff --git a/servers/slapd/limits.c b/servers/slapd/limits.c
new file mode 100644
index 0000000..2e4d051
--- /dev/null
+++ b/servers/slapd/limits.c
@@ -0,0 +1,1355 @@
+/* limits.c - routines to handle regex-based size and time limits */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/regex.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+/* define to get an error if requesting limit higher than hard */
+#undef ABOVE_HARD_LIMIT_IS_ERROR
+
+static const struct berval lmpats[] = {
+ BER_BVC( "base" ),
+ BER_BVC( "base" ),
+ BER_BVC( "onelevel" ),
+ BER_BVC( "subtree" ),
+ BER_BVC( "children" ),
+ BER_BVC( "regex" ),
+ BER_BVC( "anonymous" ),
+ BER_BVC( "users" ),
+ BER_BVC( "*" )
+};
+
+#ifdef LDAP_DEBUG
+static const char *const dn_source[2] = { "DN", "DN.THIS" };
+static const char *const lmpats_out[] = {
+ "UNDEFINED",
+ "EXACT",
+ "ONELEVEL",
+ "SUBTREE",
+ "CHILDREN",
+ "REGEX",
+ "ANONYMOUS",
+ "USERS",
+ "ANY"
+};
+
+static const char *
+limits2str( unsigned i )
+{
+ return i < (sizeof( lmpats_out ) / sizeof( lmpats_out[0] ))
+ ? lmpats_out[i] : "UNKNOWN";
+}
+#endif /* LDAP_DEBUG */
+
+static int
+limits_get(
+ Operation *op,
+ struct slap_limits_set **limit
+)
+{
+ static struct berval empty_dn = BER_BVC( "" );
+ struct slap_limits **lm;
+ struct berval *ndns[2];
+
+ assert( op != NULL );
+ assert( limit != NULL );
+
+ ndns[0] = &op->o_ndn;
+ ndns[1] = &op->o_req_ndn;
+
+ Debug( LDAP_DEBUG_TRACE, "==> limits_get: %s self=\"%s\" this=\"%s\"\n",
+ op->o_log_prefix,
+ BER_BVISNULL( ndns[0] ) ? "[anonymous]" : ndns[0]->bv_val,
+ BER_BVISNULL( ndns[1] ) ? "" : ndns[1]->bv_val );
+ /*
+ * default values
+ */
+ *limit = &op->o_bd->be_def_limit;
+
+ if ( op->o_bd->be_limits == NULL ) {
+ return( 0 );
+ }
+
+ for ( lm = op->o_bd->be_limits; lm[0] != NULL; lm++ ) {
+ unsigned style = lm[0]->lm_flags & SLAP_LIMITS_MASK;
+ unsigned type = lm[0]->lm_flags & SLAP_LIMITS_TYPE_MASK;
+ unsigned isthis = type == SLAP_LIMITS_TYPE_THIS;
+ struct berval *ndn = ndns[isthis];
+
+ if ( style == SLAP_LIMITS_ANY )
+ goto found_any;
+
+ if ( BER_BVISEMPTY( ndn ) ) {
+ if ( style == SLAP_LIMITS_ANONYMOUS )
+ goto found_nodn;
+ if ( !isthis )
+ continue;
+ ndn = &empty_dn;
+ }
+
+ switch ( style ) {
+ case SLAP_LIMITS_EXACT:
+ if ( type == SLAP_LIMITS_TYPE_GROUP ) {
+ int rc = backend_group( op, NULL,
+ &lm[0]->lm_pat, ndn,
+ lm[0]->lm_group_oc,
+ lm[0]->lm_group_ad );
+ if ( rc == 0 ) {
+ goto found_group;
+ }
+ } else {
+ if ( dn_match( &lm[0]->lm_pat, ndn ) ) {
+ goto found_dn;
+ }
+ }
+ break;
+
+ case SLAP_LIMITS_ONE:
+ case SLAP_LIMITS_SUBTREE:
+ case SLAP_LIMITS_CHILDREN: {
+ ber_len_t d;
+
+ /* ndn shorter than lm_pat */
+ if ( ndn->bv_len < lm[0]->lm_pat.bv_len ) {
+ break;
+ }
+ d = ndn->bv_len - lm[0]->lm_pat.bv_len;
+
+ if ( d == 0 ) {
+ /* allow exact match for SUBTREE only */
+ if ( style != SLAP_LIMITS_SUBTREE ) {
+ break;
+ }
+ } else {
+ /* check for unescaped rdn separator */
+ if ( !DN_SEPARATOR( ndn->bv_val[d - 1] ) ) {
+ break;
+ }
+ }
+
+ /* check that ndn ends with lm_pat */
+ if ( strcmp( lm[0]->lm_pat.bv_val, &ndn->bv_val[d] ) != 0 ) {
+ break;
+ }
+
+ /* in case of ONE, require exactly one rdn below lm_pat */
+ if ( style == SLAP_LIMITS_ONE ) {
+ if ( dn_rdnlen( NULL, ndn ) != d - 1 ) {
+ break;
+ }
+ }
+
+ goto found_dn;
+ }
+
+ case SLAP_LIMITS_REGEX:
+ if ( regexec( &lm[0]->lm_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
+ goto found_dn;
+ }
+ break;
+
+ case SLAP_LIMITS_ANONYMOUS:
+ break;
+
+ case SLAP_LIMITS_USERS:
+ found_nodn:
+ Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=%s match=%s\n",
+ dn_source[isthis], limits2str( style ) );
+ found_any:
+ *limit = &lm[0]->lm_limits;
+ return( 0 );
+
+ found_dn:
+ Debug( LDAP_DEBUG_TRACE,
+ "<== limits_get: type=%s match=%s dn=\"%s\"\n",
+ dn_source[isthis], limits2str( style ), lm[0]->lm_pat.bv_val );
+ *limit = &lm[0]->lm_limits;
+ return( 0 );
+
+ found_group:
+ Debug( LDAP_DEBUG_TRACE, "<== limits_get: type=GROUP match=EXACT "
+ "dn=\"%s\" oc=\"%s\" ad=\"%s\"\n",
+ lm[0]->lm_pat.bv_val,
+ lm[0]->lm_group_oc->soc_cname.bv_val,
+ lm[0]->lm_group_ad->ad_cname.bv_val );
+ *limit = &lm[0]->lm_limits;
+ return( 0 );
+
+ default:
+ assert( 0 ); /* unreachable */
+ return( -1 );
+ }
+ }
+
+ return( 0 );
+}
+
+static int
+limits_add(
+ Backend *be,
+ unsigned flags,
+ const char *pattern,
+ ObjectClass *group_oc,
+ AttributeDescription *group_ad,
+ struct slap_limits_set *limit
+)
+{
+ int i;
+ struct slap_limits *lm;
+ unsigned type, style;
+
+ assert( be != NULL );
+ assert( limit != NULL );
+
+ type = flags & SLAP_LIMITS_TYPE_MASK;
+ style = flags & SLAP_LIMITS_MASK;
+
+ switch ( style ) {
+ case SLAP_LIMITS_ANONYMOUS:
+ case SLAP_LIMITS_USERS:
+ case SLAP_LIMITS_ANY:
+ /* For these styles, type == 0 (SLAP_LIMITS_TYPE_SELF). */
+ for ( i = 0; be->be_limits && be->be_limits[ i ]; i++ ) {
+ if ( be->be_limits[ i ]->lm_flags == style ) {
+ return( -1 );
+ }
+ }
+ break;
+ }
+
+
+ lm = ( struct slap_limits * )ch_calloc( sizeof( struct slap_limits ), 1 );
+
+ switch ( style ) {
+ case SLAP_LIMITS_UNDEFINED:
+ style = SLAP_LIMITS_EXACT;
+ /* continue to next cases */
+ case SLAP_LIMITS_EXACT:
+ case SLAP_LIMITS_ONE:
+ case SLAP_LIMITS_SUBTREE:
+ case SLAP_LIMITS_CHILDREN:
+ {
+ int rc;
+ struct berval bv;
+
+ ber_str2bv( pattern, 0, 0, &bv );
+
+ rc = dnNormalize( 0, NULL, NULL, &bv, &lm->lm_pat, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ ch_free( lm );
+ return( -1 );
+ }
+ }
+ break;
+
+ case SLAP_LIMITS_REGEX:
+ ber_str2bv( pattern, 0, 1, &lm->lm_pat );
+ if ( regcomp( &lm->lm_regex, lm->lm_pat.bv_val,
+ REG_EXTENDED | REG_ICASE ) ) {
+ free( lm->lm_pat.bv_val );
+ ch_free( lm );
+ return( -1 );
+ }
+ break;
+
+ case SLAP_LIMITS_ANONYMOUS:
+ case SLAP_LIMITS_USERS:
+ case SLAP_LIMITS_ANY:
+ BER_BVZERO( &lm->lm_pat );
+ break;
+ }
+
+ switch ( type ) {
+ case SLAP_LIMITS_TYPE_GROUP:
+ assert( group_oc != NULL );
+ assert( group_ad != NULL );
+ lm->lm_group_oc = group_oc;
+ lm->lm_group_ad = group_ad;
+ break;
+ }
+
+ lm->lm_flags = style | type;
+ lm->lm_limits = *limit;
+
+ i = 0;
+ if ( be->be_limits != NULL ) {
+ for ( ; be->be_limits[i]; i++ );
+ }
+
+ be->be_limits = ( struct slap_limits ** )ch_realloc( be->be_limits,
+ sizeof( struct slap_limits * ) * ( i + 2 ) );
+ be->be_limits[i] = lm;
+ be->be_limits[i+1] = NULL;
+
+ return( 0 );
+}
+
+#define STRSTART( s, m ) (strncasecmp( s, m, STRLENOF( "" m "" )) == 0)
+
+int
+limits_parse(
+ Backend *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ int flags = SLAP_LIMITS_UNDEFINED;
+ char *pattern;
+ struct slap_limits_set limit;
+ int i, rc = 0;
+ ObjectClass *group_oc = NULL;
+ AttributeDescription *group_ad = NULL;
+
+ assert( be != NULL );
+
+ if ( argc < 3 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: missing arg(s) in "
+ "\"limits <pattern> <limits>\" line.\n",
+ fname, lineno );
+ return( -1 );
+ }
+
+ limit = be->be_def_limit;
+
+ /*
+ * syntax:
+ *
+ * "limits" <pattern> <limit> [ ... ]
+ *
+ *
+ * <pattern>:
+ *
+ * "anonymous"
+ * "users"
+ * [ "dn" [ "." { "this" | "self" } ] [ "." { "exact" | "base" |
+ * "onelevel" | "subtree" | "children" | "regex" | "anonymous" } ]
+ * "=" ] <dn pattern>
+ *
+ * Note:
+ * "this" is the baseobject, "self" (the default) is the bound DN
+ * "exact" and "base" are the same (exact match);
+ * "onelevel" means exactly one rdn below, NOT including pattern
+ * "subtree" means any rdn below, including pattern
+ * "children" means any rdn below, NOT including pattern
+ *
+ * "anonymous" may be deprecated in favour
+ * of the pattern = "anonymous" form
+ *
+ * "group[/objectClass[/attributeType]]" "=" "<dn pattern>"
+ *
+ * <limit>:
+ *
+ * "time" [ "." { "soft" | "hard" } ] "=" <integer>
+ *
+ * "size" [ "." { "soft" | "hard" | "unchecked" } ] "=" <integer>
+ */
+
+ pattern = argv[1];
+ if ( strcmp( pattern, "*" ) == 0) {
+ flags = SLAP_LIMITS_ANY;
+
+ } else if ( strcasecmp( pattern, "anonymous" ) == 0 ) {
+ flags = SLAP_LIMITS_ANONYMOUS;
+
+ } else if ( strcasecmp( pattern, "users" ) == 0 ) {
+ flags = SLAP_LIMITS_USERS;
+
+ } else if ( STRSTART( pattern, "dn" ) ) {
+ pattern += STRLENOF( "dn" );
+ flags = SLAP_LIMITS_TYPE_SELF;
+ if ( pattern[0] == '.' ) {
+ pattern++;
+ if ( STRSTART( pattern, "this" ) ) {
+ flags = SLAP_LIMITS_TYPE_THIS;
+ pattern += STRLENOF( "this" );
+ } else if ( STRSTART( pattern, "self" ) ) {
+ pattern += STRLENOF( "self" );
+ } else {
+ goto got_dn_dot;
+ }
+ }
+ if ( pattern[0] == '.' ) {
+ pattern++;
+ got_dn_dot:
+ if ( STRSTART( pattern, "exact" ) ) {
+ flags |= SLAP_LIMITS_EXACT;
+ pattern += STRLENOF( "exact" );
+
+ } else if ( STRSTART( pattern, "base" ) ) {
+ flags |= SLAP_LIMITS_BASE;
+ pattern += STRLENOF( "base" );
+
+ } else if ( STRSTART( pattern, "one" ) ) {
+ flags |= SLAP_LIMITS_ONE;
+ pattern += STRLENOF( "one" );
+ if ( STRSTART( pattern, "level" ) ) {
+ pattern += STRLENOF( "level" );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: deprecated \"one\" style "
+ "\"limits <pattern> <limits>\" line; "
+ "use \"onelevel\" instead.\n", fname, lineno );
+ }
+
+ } else if ( STRSTART( pattern, "sub" ) ) {
+ flags |= SLAP_LIMITS_SUBTREE;
+ pattern += STRLENOF( "sub" );
+ if ( STRSTART( pattern, "tree" ) ) {
+ pattern += STRLENOF( "tree" );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: deprecated \"sub\" style "
+ "\"limits <pattern> <limits>\" line; "
+ "use \"subtree\" instead.\n", fname, lineno );
+ }
+
+ } else if ( STRSTART( pattern, "children" ) ) {
+ flags |= SLAP_LIMITS_CHILDREN;
+ pattern += STRLENOF( "children" );
+
+ } else if ( STRSTART( pattern, "regex" ) ) {
+ flags |= SLAP_LIMITS_REGEX;
+ pattern += STRLENOF( "regex" );
+
+ /*
+ * this could be deprecated in favour
+ * of the pattern = "anonymous" form
+ */
+ } else if ( STRSTART( pattern, "anonymous" )
+ && flags == SLAP_LIMITS_TYPE_SELF )
+ {
+ flags = SLAP_LIMITS_ANONYMOUS;
+ pattern = NULL;
+
+ } else {
+ /* force error below */
+ if ( *pattern == '=' )
+ --pattern;
+ }
+ }
+
+ /* pre-check the data */
+ if ( pattern != NULL ) {
+ if ( pattern[0] != '=' ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: %s in "
+ "\"dn[.{this|self}][.{exact|base"
+ "|onelevel|subtree|children|regex"
+ "|anonymous}]=<pattern>\" in "
+ "\"limits <pattern> <limits>\" line.\n",
+ fname, lineno,
+ isalnum( (unsigned char)pattern[0] )
+ ? "unknown DN modifier" : "missing '='" );
+ return( -1 );
+ }
+
+ /* skip '=' (required) */
+ pattern++;
+
+ /* trim obvious cases */
+ if ( strcmp( pattern, "*" ) == 0 ) {
+ flags = SLAP_LIMITS_ANY;
+ pattern = NULL;
+
+ } else if ( (flags & SLAP_LIMITS_MASK) == SLAP_LIMITS_REGEX
+ && strcmp( pattern, ".*" ) == 0 ) {
+ flags = SLAP_LIMITS_ANY;
+ pattern = NULL;
+ }
+ }
+
+ } else if (STRSTART( pattern, "group" ) ) {
+ pattern += STRLENOF( "group" );
+
+ if ( pattern[0] == '/' ) {
+ struct berval oc, ad;
+
+ oc.bv_val = pattern + 1;
+ pattern = strchr( pattern, '=' );
+ if ( pattern == NULL ) {
+ return -1;
+ }
+
+ ad.bv_val = strchr( oc.bv_val, '/' );
+ if ( ad.bv_val != NULL ) {
+ const char *text = NULL;
+
+ oc.bv_len = ad.bv_val - oc.bv_val;
+
+ ad.bv_val++;
+ ad.bv_len = pattern - ad.bv_val;
+ rc = slap_bv2ad( &ad, &group_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ goto no_ad;
+ }
+
+ } else {
+ oc.bv_len = pattern - oc.bv_val;
+ }
+
+ group_oc = oc_bvfind( &oc );
+ if ( group_oc == NULL ) {
+ goto no_oc;
+ }
+ }
+
+ if ( group_oc == NULL ) {
+ group_oc = oc_find( SLAPD_GROUP_CLASS );
+ if ( group_oc == NULL ) {
+no_oc:;
+ return( -1 );
+ }
+ }
+
+ if ( group_ad == NULL ) {
+ const char *text = NULL;
+
+ rc = slap_str2ad( SLAPD_GROUP_ATTR, &group_ad, &text );
+
+ if ( rc != LDAP_SUCCESS ) {
+no_ad:;
+ return( -1 );
+ }
+ }
+
+ flags = SLAP_LIMITS_TYPE_GROUP | SLAP_LIMITS_EXACT;
+
+ if ( pattern[0] != '=' ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: missing '=' in "
+ "\"group[/objectClass[/attributeType]]"
+ "=<pattern>\" in "
+ "\"limits <pattern> <limits>\" line.\n",
+ fname, lineno );
+ return( -1 );
+ }
+
+ /* skip '=' (required) */
+ pattern++;
+ }
+
+ /* get the limits */
+ for ( i = 2; i < argc; i++ ) {
+ if ( limits_parse_one( argv[i], &limit ) ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: unknown limit values \"%s\" in "
+ "\"limits <pattern> <limits>\" line.\n",
+ fname, lineno, argv[i] );
+
+ return( 1 );
+ }
+ }
+
+ /*
+ * sanity checks ...
+ *
+ * FIXME: add warnings?
+ */
+ if ( limit.lms_t_hard > 0 &&
+ ( limit.lms_t_hard < limit.lms_t_soft
+ || limit.lms_t_soft == -1 ) ) {
+ limit.lms_t_hard = limit.lms_t_soft;
+ }
+
+ if ( limit.lms_s_hard > 0 &&
+ ( limit.lms_s_hard < limit.lms_s_soft
+ || limit.lms_s_soft == -1 ) ) {
+ limit.lms_s_hard = limit.lms_s_soft;
+ }
+
+ /*
+ * defaults ...
+ *
+ * lms_t_hard:
+ * -1 => no limits
+ * 0 => same as soft
+ * > 0 => limit (in seconds)
+ *
+ * lms_s_hard:
+ * -1 => no limits
+ * 0 0> same as soft
+ * > 0 => limit (in entries)
+ *
+ * lms_s_pr_total:
+ * -2 => disable the control
+ * -1 => no limits
+ * 0 => same as soft
+ * > 0 => limit (in entries)
+ *
+ * lms_s_pr:
+ * -1 => no limits
+ * 0 => no limits?
+ * > 0 => limit size (in entries)
+ */
+ if ( limit.lms_s_pr_total > 0 &&
+ limit.lms_s_pr > limit.lms_s_pr_total ) {
+ limit.lms_s_pr = limit.lms_s_pr_total;
+ }
+
+ rc = limits_add( be, flags, pattern, group_oc, group_ad, &limit );
+ if ( rc ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s : line %d: unable to add limit in "
+ "\"limits <pattern> <limits>\" line.\n",
+ fname, lineno );
+ }
+
+ return( rc );
+}
+
+int
+limits_parse_one(
+ const char *arg,
+ struct slap_limits_set *limit
+)
+{
+ assert( arg != NULL );
+ assert( limit != NULL );
+
+ if ( STRSTART( arg, "time" ) ) {
+ arg += STRLENOF( "time" );
+
+ if ( arg[0] == '.' ) {
+ arg++;
+ if ( STRSTART( arg, "soft=" ) ) {
+ arg += STRLENOF( "soft=" );
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_t_soft = -1;
+
+ } else {
+ int soft;
+
+ if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) {
+ return( 1 );
+ }
+
+ if ( soft == -1 ) {
+ /* FIXME: use "unlimited" instead; issue warning? */
+ }
+
+ limit->lms_t_soft = soft;
+ }
+
+ } else if ( STRSTART( arg, "hard=" ) ) {
+ arg += STRLENOF( "hard=" );
+ if ( strcasecmp( arg, "soft" ) == 0 ) {
+ limit->lms_t_hard = 0;
+
+ } else if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_t_hard = -1;
+
+ } else {
+ int hard;
+
+ if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) {
+ return( 1 );
+ }
+
+ if ( hard == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ if ( hard == 0 ) {
+ /* FIXME: use "soft" instead */
+ }
+
+ limit->lms_t_hard = hard;
+ }
+
+ } else {
+ return( 1 );
+ }
+
+ } else if ( arg[0] == '=' ) {
+ arg++;
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_t_soft = -1;
+
+ } else {
+ if ( lutil_atoi( &limit->lms_t_soft, arg ) != 0
+ || limit->lms_t_soft < -1 )
+ {
+ return( 1 );
+ }
+ }
+ limit->lms_t_hard = 0;
+
+ } else {
+ return( 1 );
+ }
+
+ } else if ( STRSTART( arg, "size" ) ) {
+ arg += STRLENOF( "size" );
+
+ if ( arg[0] == '.' ) {
+ arg++;
+ if ( STRSTART( arg, "soft=" ) ) {
+ arg += STRLENOF( "soft=" );
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_soft = -1;
+
+ } else {
+ int soft;
+
+ if ( lutil_atoi( &soft, arg ) != 0 || soft < -1 ) {
+ return( 1 );
+ }
+
+ if ( soft == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ limit->lms_s_soft = soft;
+ }
+
+ } else if ( STRSTART( arg, "hard=" ) ) {
+ arg += STRLENOF( "hard=" );
+ if ( strcasecmp( arg, "soft" ) == 0 ) {
+ limit->lms_s_hard = 0;
+
+ } else if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_hard = -1;
+
+ } else {
+ int hard;
+
+ if ( lutil_atoi( &hard, arg ) != 0 || hard < -1 ) {
+ return( 1 );
+ }
+
+ if ( hard == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ if ( hard == 0 ) {
+ /* FIXME: use "soft" instead */
+ }
+
+ limit->lms_s_hard = hard;
+ }
+
+ } else if ( STRSTART( arg, "unchecked=" ) ) {
+ arg += STRLENOF( "unchecked=" );
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_unchecked = -1;
+
+ } else if ( strcasecmp( arg, "disabled" ) == 0 ) {
+ limit->lms_s_unchecked = 0;
+
+ } else {
+ int unchecked;
+
+ if ( lutil_atoi( &unchecked, arg ) != 0 || unchecked < -1 ) {
+ return( 1 );
+ }
+
+ if ( unchecked == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ limit->lms_s_unchecked = unchecked;
+ }
+
+ } else if ( STRSTART( arg, "pr=" ) ) {
+ arg += STRLENOF( "pr=" );
+ if ( strcasecmp( arg, "noEstimate" ) == 0 ) {
+ limit->lms_s_pr_hide = 1;
+
+ } else if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_pr = -1;
+
+ } else {
+ int pr;
+
+ if ( lutil_atoi( &pr, arg ) != 0 || pr < -1 ) {
+ return( 1 );
+ }
+
+ if ( pr == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ limit->lms_s_pr = pr;
+ }
+
+ } else if ( STRSTART( arg, "prtotal=" ) ) {
+ arg += STRLENOF( "prtotal=" );
+
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_pr_total = -1;
+
+ } else if ( strcasecmp( arg, "disabled" ) == 0 ) {
+ limit->lms_s_pr_total = -2;
+
+ } else if ( strcasecmp( arg, "hard" ) == 0 ) {
+ limit->lms_s_pr_total = 0;
+
+ } else {
+ int total;
+
+ if ( lutil_atoi( &total, arg ) != 0 || total < -1 ) {
+ return( 1 );
+ }
+
+ if ( total == -1 ) {
+ /* FIXME: use "unlimited" instead */
+ }
+
+ if ( total == 0 ) {
+ /* FIXME: use "pr=disable" instead */
+ }
+
+ limit->lms_s_pr_total = total;
+ }
+
+ } else {
+ return( 1 );
+ }
+
+ } else if ( arg[0] == '=' ) {
+ arg++;
+ if ( strcasecmp( arg, "unlimited" ) == 0
+ || strcasecmp( arg, "none" ) == 0 )
+ {
+ limit->lms_s_soft = -1;
+
+ } else {
+ if ( lutil_atoi( &limit->lms_s_soft, arg ) != 0
+ || limit->lms_s_soft < -1 )
+ {
+ return( 1 );
+ }
+ }
+ limit->lms_s_hard = 0;
+
+ } else {
+ return( 1 );
+ }
+ }
+
+ return 0;
+}
+
+/* Helper macros for limits_unparse() and limits_unparse_one():
+ * Write to ptr, but not past bufEnd. Move ptr past the new text.
+ * Return (success && enough room ? 0 : -1).
+ */
+#define ptr_APPEND_BV(bv) /* Append a \0-terminated berval */ \
+ (WHATSLEFT <= (bv).bv_len ? -1 : \
+ ((void) (ptr = lutil_strcopy( ptr, (bv).bv_val )), 0))
+#define ptr_APPEND_LIT(str) /* Append a string literal */ \
+ (WHATSLEFT <= STRLENOF( "" str "" ) ? -1 : \
+ ((void) (ptr = lutil_strcopy( ptr, str )), 0))
+#define ptr_APPEND_FMT(args) /* Append formatted text */ \
+ (WHATSLEFT <= (tmpLen = snprintf args) ? -1 : ((void) (ptr += tmpLen), 0))
+#define ptr_APPEND_FMT1(fmt, arg) ptr_APPEND_FMT(( ptr, WHATSLEFT, fmt, arg ))
+#define WHATSLEFT ((ber_len_t) (bufEnd - ptr))
+
+/* Caller must provide an adequately sized buffer in bv */
+int
+limits_unparse( struct slap_limits *lim, struct berval *bv, ber_len_t buflen )
+{
+ struct berval btmp;
+ char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */
+ ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */
+ unsigned type, style;
+ int rc = 0;
+
+ if ( !bv || !bv->bv_val ) return -1;
+
+ ptr = bv->bv_val;
+ bufEnd = ptr + buflen;
+ type = lim->lm_flags & SLAP_LIMITS_TYPE_MASK;
+
+ if ( type == SLAP_LIMITS_TYPE_GROUP ) {
+ rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "group/%s/%s=\"%s\"",
+ lim->lm_group_oc->soc_cname.bv_val,
+ lim->lm_group_ad->ad_cname.bv_val,
+ lim->lm_pat.bv_val ));
+ } else {
+ style = lim->lm_flags & SLAP_LIMITS_MASK;
+ switch( style ) {
+ case SLAP_LIMITS_ANONYMOUS:
+ case SLAP_LIMITS_USERS:
+ case SLAP_LIMITS_ANY:
+ rc = ptr_APPEND_BV( lmpats[style] );
+ break;
+ case SLAP_LIMITS_UNDEFINED:
+ case SLAP_LIMITS_EXACT:
+ case SLAP_LIMITS_ONE:
+ case SLAP_LIMITS_SUBTREE:
+ case SLAP_LIMITS_CHILDREN:
+ case SLAP_LIMITS_REGEX:
+ rc = ptr_APPEND_FMT(( ptr, WHATSLEFT, "dn.%s%s=\"%s\"",
+ type == SLAP_LIMITS_TYPE_SELF ? "" : "this.",
+ lmpats[style].bv_val, lim->lm_pat.bv_val ));
+ break;
+ }
+ }
+ if ( rc == 0 ) {
+ bv->bv_len = ptr - bv->bv_val;
+ btmp.bv_val = ptr;
+ btmp.bv_len = 0;
+ rc = limits_unparse_one( &lim->lm_limits,
+ SLAP_LIMIT_SIZE | SLAP_LIMIT_TIME,
+ &btmp, WHATSLEFT );
+ if ( rc == 0 )
+ bv->bv_len += btmp.bv_len;
+ }
+ return rc;
+}
+
+/* Caller must provide an adequately sized buffer in bv */
+int
+limits_unparse_one(
+ struct slap_limits_set *lim,
+ int which,
+ struct berval *bv,
+ ber_len_t buflen )
+{
+ char *ptr, *bufEnd; /* Updated/used by ptr_APPEND_*()/WHATSLEFT */
+ ber_len_t tmpLen; /* Used by ptr_APPEND_FMT*() */
+
+ if ( !bv || !bv->bv_val ) return -1;
+
+ ptr = bv->bv_val;
+ bufEnd = ptr + buflen;
+
+ if ( which & SLAP_LIMIT_SIZE ) {
+ if ( lim->lms_s_soft != SLAPD_DEFAULT_SIZELIMIT ) {
+
+ /* If same as global limit, drop it */
+ if ( lim != &frontendDB->be_def_limit &&
+ lim->lms_s_soft == frontendDB->be_def_limit.lms_s_soft )
+ {
+ goto s_hard;
+ /* If there's also a hard limit, fully qualify this one */
+ } else if ( lim->lms_s_hard ) {
+ if ( ptr_APPEND_LIT( " size.soft=" ) ) return -1;
+
+ /* If doing both size & time, qualify this */
+ } else if ( which & SLAP_LIMIT_TIME ) {
+ if ( ptr_APPEND_LIT( " size=" ) ) return -1;
+ }
+
+ if ( lim->lms_s_soft == -1
+ ? ptr_APPEND_LIT( "unlimited " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_s_soft ) )
+ return -1;
+ }
+s_hard:
+ if ( lim->lms_s_hard ) {
+ if ( ptr_APPEND_LIT( " size.hard=" ) ) return -1;
+ if ( lim->lms_s_hard == -1
+ ? ptr_APPEND_LIT( "unlimited " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_s_hard ) )
+ return -1;
+ }
+ if ( lim->lms_s_unchecked != -1 ) {
+ if ( ptr_APPEND_LIT( " size.unchecked=" ) ) return -1;
+ if ( lim->lms_s_unchecked == 0
+ ? ptr_APPEND_LIT( "disabled " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_s_unchecked ) )
+ return -1;
+ }
+ if ( lim->lms_s_pr_hide ) {
+ if ( ptr_APPEND_LIT( " size.pr=noEstimate " ) ) return -1;
+ }
+ if ( lim->lms_s_pr ) {
+ if ( ptr_APPEND_LIT( " size.pr=" ) ) return -1;
+ if ( lim->lms_s_pr == -1
+ ? ptr_APPEND_LIT( "unlimited " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr ) )
+ return -1;
+ }
+ if ( lim->lms_s_pr_total ) {
+ if ( ptr_APPEND_LIT( " size.prtotal=" ) ) return -1;
+ if ( lim->lms_s_pr_total == -1 ? ptr_APPEND_LIT( "unlimited " )
+ : lim->lms_s_pr_total == -2 ? ptr_APPEND_LIT( "disabled " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_s_pr_total ) )
+ return -1;
+ }
+ }
+
+ if ( which & SLAP_LIMIT_TIME ) {
+ if ( lim->lms_t_soft != SLAPD_DEFAULT_TIMELIMIT ) {
+
+ /* If same as global limit, drop it */
+ if ( lim != &frontendDB->be_def_limit &&
+ lim->lms_t_soft == frontendDB->be_def_limit.lms_t_soft )
+ {
+ goto t_hard;
+
+ /* If there's also a hard limit, fully qualify this one */
+ } else if ( lim->lms_t_hard ) {
+ if ( ptr_APPEND_LIT( " time.soft=" ) ) return -1;
+
+ /* If doing both size & time, qualify this */
+ } else if ( which & SLAP_LIMIT_SIZE ) {
+ if ( ptr_APPEND_LIT( " time=" ) ) return -1;
+ }
+
+ if ( lim->lms_t_soft == -1
+ ? ptr_APPEND_LIT( "unlimited " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_t_soft ) )
+ return -1;
+ }
+t_hard:
+ if ( lim->lms_t_hard ) {
+ if ( ptr_APPEND_LIT( " time.hard=" ) ) return -1;
+ if ( lim->lms_t_hard == -1
+ ? ptr_APPEND_LIT( "unlimited " )
+ : ptr_APPEND_FMT1( "%d ", lim->lms_t_hard ) )
+ return -1;
+ }
+ }
+ if ( ptr != bv->bv_val ) {
+ ptr--;
+ *ptr = '\0';
+ bv->bv_len = ptr - bv->bv_val;
+ }
+
+ return 0;
+}
+
+int
+limits_check( Operation *op, SlapReply *rs )
+{
+ assert( op != NULL );
+ assert( rs != NULL );
+ /* FIXME: should this be always true? */
+ assert( op->o_tag == LDAP_REQ_SEARCH);
+
+ /* protocol only allows 0..maxInt;
+ *
+ * internal searches:
+ * - may use SLAP_NO_LIMIT ( = -1 ) to indicate no limits;
+ * - should use slimit = N and tlimit = SLAP_NO_LIMIT to
+ * indicate searches that should return exactly N matches,
+ * and handle errors thru a callback (see for instance
+ * slap_sasl_match() and slap_sasl2dn())
+ */
+ if ( op->ors_tlimit == SLAP_NO_LIMIT && op->ors_slimit == SLAP_NO_LIMIT ) {
+ return 0;
+ }
+
+ /* allow root to set no limit */
+ if ( be_isroot( op ) ) {
+ op->ors_limit = NULL;
+
+ if ( op->ors_tlimit == 0 ) {
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ }
+
+ if ( op->ors_slimit == 0 ) {
+ op->ors_slimit = SLAP_NO_LIMIT;
+ }
+
+ /* if paged results and slimit are requested */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED &&
+ op->ors_slimit != SLAP_NO_LIMIT ) {
+ PagedResultsState *ps = op->o_pagedresults_state;
+ int total = op->ors_slimit - ps->ps_count;
+ if ( total > 0 ) {
+ op->ors_slimit = total;
+ } else {
+ op->ors_slimit = 0;
+ }
+ }
+
+ /* if not root, get appropriate limits */
+ } else {
+ ( void ) limits_get( op, &op->ors_limit );
+
+ assert( op->ors_limit != NULL );
+
+ /* if no limit is required, use soft limit */
+ if ( op->ors_tlimit == 0 ) {
+ op->ors_tlimit = op->ors_limit->lms_t_soft;
+
+ /* limit required: check if legal */
+ } else {
+ if ( op->ors_limit->lms_t_hard == 0 ) {
+ if ( op->ors_limit->lms_t_soft > 0
+ && ( op->ors_tlimit > op->ors_limit->lms_t_soft ) ) {
+ op->ors_tlimit = op->ors_limit->lms_t_soft;
+ }
+
+ } else if ( op->ors_limit->lms_t_hard > 0 ) {
+#ifdef ABOVE_HARD_LIMIT_IS_ERROR
+ if ( op->ors_tlimit == SLAP_MAX_LIMIT ) {
+ op->ors_tlimit = op->ors_limit->lms_t_hard;
+
+ } else if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) {
+ /* error if exceeding hard limit */
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ return -1;
+ }
+#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ if ( op->ors_tlimit > op->ors_limit->lms_t_hard ) {
+ op->ors_tlimit = op->ors_limit->lms_t_hard;
+ }
+#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ }
+ }
+
+ /* else leave as is */
+
+ /* don't even get to backend if candidate check is disabled */
+ if ( op->ors_limit->lms_s_unchecked == 0 ) {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ return -1;
+ }
+
+ /* if paged results is requested */
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
+ int slimit = -2;
+ int pr_total;
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* paged results is not allowed */
+ if ( op->ors_limit->lms_s_pr_total == -2 ) {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ rs->sr_text = "pagedResults control not allowed";
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ return -1;
+ }
+
+ if ( op->ors_limit->lms_s_pr > 0
+ && ps->ps_size > op->ors_limit->lms_s_pr )
+ {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ rs->sr_text = "illegal pagedResults page size";
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+ return -1;
+ }
+
+ if ( op->ors_limit->lms_s_pr_total == 0 ) {
+ if ( op->ors_limit->lms_s_hard == 0 ) {
+ pr_total = op->ors_limit->lms_s_soft;
+ } else {
+ pr_total = op->ors_limit->lms_s_hard;
+ }
+ } else {
+ pr_total = op->ors_limit->lms_s_pr_total;
+ }
+
+ if ( pr_total == -1 ) {
+ if ( op->ors_slimit == 0 || op->ors_slimit == SLAP_MAX_LIMIT ) {
+ slimit = -1;
+
+ } else {
+ slimit = op->ors_slimit - ps->ps_count;
+ }
+
+#ifdef ABOVE_HARD_LIMIT_IS_ERROR
+ } else if ( pr_total > 0 && op->ors_slimit != SLAP_MAX_LIMIT
+ && ( op->ors_slimit == SLAP_NO_LIMIT
+ || op->ors_slimit > pr_total ) )
+ {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ return -1;
+#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+
+ } else {
+ /* if no limit is required, use soft limit */
+ int total;
+ int slimit2;
+
+ /* first round of pagedResults:
+ * set count to any appropriate limit */
+
+ /* if the limit is set, check that it does
+ * not violate any server-side limit */
+#ifdef ABOVE_HARD_LIMIT_IS_ERROR
+ if ( op->ors_slimit == SLAP_MAX_LIMIT )
+#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ if ( op->ors_slimit == SLAP_MAX_LIMIT
+ || op->ors_slimit > pr_total )
+#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ {
+ slimit2 = op->ors_slimit = pr_total;
+
+ } else if ( op->ors_slimit == 0 ) {
+ slimit2 = pr_total;
+
+ } else {
+ slimit2 = op->ors_slimit;
+ }
+
+ total = slimit2 - ps->ps_count;
+
+ if ( total >= 0 ) {
+ if ( op->ors_limit->lms_s_pr > 0 ) {
+ /* use the smallest limit set by total/per page */
+ if ( total < op->ors_limit->lms_s_pr ) {
+ slimit = total;
+
+ } else {
+ /* use the perpage limit if any
+ * NOTE: + 1 because given value must be legal */
+ slimit = op->ors_limit->lms_s_pr + 1;
+ }
+
+ } else {
+ /* use the total limit if any */
+ slimit = total;
+ }
+
+ } else if ( op->ors_limit->lms_s_pr > 0 ) {
+ /* use the perpage limit if any
+ * NOTE: + 1 because the given value must be legal */
+ slimit = op->ors_limit->lms_s_pr + 1;
+
+ } else {
+ /* use the standard hard/soft limit if any */
+ slimit = op->ors_limit->lms_s_hard;
+ }
+ }
+
+ /* if got any limit, use it */
+ if ( slimit != -2 ) {
+ if ( op->ors_slimit == 0 ) {
+ op->ors_slimit = slimit;
+
+ } else if ( slimit > 0 ) {
+ if ( op->ors_slimit - ps->ps_count > slimit ) {
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ return -1;
+ }
+ op->ors_slimit = slimit;
+
+ } else if ( slimit == 0 ) {
+ op->ors_slimit = 0;
+ }
+
+ } else {
+ /* use the standard hard/soft limit if any */
+ op->ors_slimit = pr_total;
+ }
+
+ /* no limit requested: use soft, whatever it is */
+ } else if ( op->ors_slimit == 0 ) {
+ op->ors_slimit = op->ors_limit->lms_s_soft;
+
+ /* limit requested: check if legal */
+ } else {
+ /* hard limit as soft (traditional behavior) */
+ if ( op->ors_limit->lms_s_hard == 0 ) {
+ if ( op->ors_limit->lms_s_soft > 0
+ && op->ors_slimit > op->ors_limit->lms_s_soft ) {
+ op->ors_slimit = op->ors_limit->lms_s_soft;
+ }
+
+ /* explicit hard limit: error if violated */
+ } else if ( op->ors_limit->lms_s_hard > 0 ) {
+#ifdef ABOVE_HARD_LIMIT_IS_ERROR
+ if ( op->ors_slimit == SLAP_MAX_LIMIT ) {
+ op->ors_slimit = op->ors_limit->lms_s_hard;
+
+ } else if ( op->ors_slimit > op->ors_limit->lms_s_hard ) {
+ /* if limit exceeds hard, error */
+ rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
+ send_ldap_result( op, rs );
+ rs->sr_err = LDAP_SUCCESS;
+ return -1;
+ }
+#else /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ if ( op->ors_slimit > op->ors_limit->lms_s_hard ) {
+ op->ors_slimit = op->ors_limit->lms_s_hard;
+ }
+#endif /* ! ABOVE_HARD_LIMIT_IS_ERROR */
+ }
+ }
+
+ /* else leave as is */
+ }
+
+ return 0;
+}
+
+void
+limits_free_one(
+ struct slap_limits *lm )
+{
+ if ( ( lm->lm_flags & SLAP_LIMITS_MASK ) == SLAP_LIMITS_REGEX )
+ regfree( &lm->lm_regex );
+
+ if ( !BER_BVISNULL( &lm->lm_pat ) )
+ ch_free( lm->lm_pat.bv_val );
+
+ ch_free( lm );
+}
+
+void
+limits_destroy(
+ struct slap_limits **lm )
+{
+ int i;
+
+ if ( lm == NULL ) {
+ return;
+ }
+
+ for ( i = 0; lm[ i ]; i++ ) {
+ limits_free_one( lm[ i ] );
+ }
+
+ ch_free( lm );
+}
diff --git a/servers/slapd/lock.c b/servers/slapd/lock.c
new file mode 100644
index 0000000..91a4e34
--- /dev/null
+++ b/servers/slapd/lock.c
@@ -0,0 +1,83 @@
+/* lock.c - routines to open and apply an advisory lock to a file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+
+#include "slap.h"
+#include <lutil.h>
+
+FILE *
+lock_fopen( const char *fname, const char *type, FILE **lfp )
+{
+ FILE *fp;
+ char buf[MAXPATHLEN];
+
+ /* open the lock file */
+ snprintf( buf, sizeof buf, "%s.lock", fname );
+
+ if ( (*lfp = fopen( buf, "w" )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "could not open \"%s\"\n", buf );
+
+ return( NULL );
+ }
+
+ /* acquire the lock */
+ ldap_lockf( fileno(*lfp) );
+
+ /* open the log file */
+ if ( (fp = fopen( fname, type )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "could not open \"%s\"\n", fname );
+
+ ldap_unlockf( fileno(*lfp) );
+ fclose( *lfp );
+ *lfp = NULL;
+ return( NULL );
+ }
+
+ return( fp );
+}
+
+int
+lock_fclose( FILE *fp, FILE *lfp )
+{
+ int rc = fclose( fp );
+ /* unlock */
+ ldap_unlockf( fileno(lfp) );
+ fclose( lfp );
+
+ return( rc );
+}
diff --git a/servers/slapd/logging.c b/servers/slapd/logging.c
new file mode 100644
index 0000000..95f7ff2
--- /dev/null
+++ b/servers/slapd/logging.c
@@ -0,0 +1,836 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2021-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/param.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/ctype.h>
+
+#include <sys/stat.h>
+#ifndef _WIN32
+#include <sys/uio.h>
+#endif
+#include <fcntl.h>
+
+#include "slap.h"
+#include "ldif.h"
+
+#include "slap-config.h"
+#include "slap-cfglog.h"
+
+static int config_syslog, active_syslog;
+
+static char logfile_suffix[sizeof(".xx.gz")];
+static char logfile_path[MAXPATHLEN - sizeof(logfile_suffix) -1];
+static long logfile_fslimit;
+static int logfile_age, logfile_only, logfile_max;
+static char *syslog_prefix;
+static int splen;
+
+typedef enum { LFMT_DEFAULT, LFMT_DEBUG, LFMT_SYSLOG_UTC, LFMT_SYSLOG_LOCAL } LogFormat;
+static LogFormat logfile_format;
+
+static slap_verbmasks logformat_key[] = {
+ { BER_BVC("default"), LFMT_DEFAULT },
+ { BER_BVC("debug"), LFMT_DEBUG },
+ { BER_BVC("syslog-utc"), LFMT_SYSLOG_UTC },
+ { BER_BVC("syslog-localtime"), LFMT_SYSLOG_LOCAL },
+ { BER_BVNULL, 0 }
+};
+
+char *serverName;
+int slap_debug_orig;
+
+ldap_pvt_thread_mutex_t logfile_mutex;
+
+static off_t logfile_fsize;
+static time_t logfile_fcreated;
+static int logfile_fd = -1;
+static char logpaths[2][MAXPATHLEN];
+static int logpathlen;
+
+#define SYSLOG_STAMP "Mmm dd hh:mm:ss"
+
+void
+slap_debug_print( const char *data )
+{
+#ifdef _WIN32
+ char msgbuf[4096];
+ int prefixlen, poffset = 0, datalen;
+#else
+ char prefix[sizeof("ssssssssssssssss.ffffffff 0xtttttttttttttttt ")];
+ struct iovec iov[2];
+#endif
+ int rotate = 0;
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec tv;
+#define TS "%08x"
+#define Tfrac tv.tv_nsec
+#define gettime(tv) clock_gettime( CLOCK_REALTIME, tv )
+#else
+ struct timeval tv;
+#define TS "%05x"
+#define Tfrac tv.tv_usec
+#define gettime(tv) gettimeofday( tv, NULL )
+#endif
+ char *ptr;
+ int len;
+
+
+ gettime( &tv );
+#ifdef _WIN32
+ ptr = msgbuf;
+ prefixlen = sprintf( ptr, "%lx." TS " %p ",
+ (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() );
+ if ( prefixlen < splen ) {
+ poffset = splen - prefixlen;
+ AC_MEMCPY( ptr+poffset, ptr, prefixlen );
+ }
+
+ ptr = lutil_strncopy( ptr+poffset+prefixlen, data, sizeof(msgbuf) - prefixlen);
+ len = ptr - msgbuf - poffset;
+ datalen = len - prefixlen;
+ if ( !logfile_only )
+ (void)!write( 2, msgbuf+poffset, len );
+ ptr = msgbuf;
+#else
+ iov[0].iov_base = prefix;
+ iov[0].iov_len = sprintf( prefix, "%lx." TS " %p ",
+ (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() );
+ iov[1].iov_base = (void *)data;
+ iov[1].iov_len = strlen( data );
+ len = iov[0].iov_len + iov[1].iov_len;
+ if ( !logfile_only )
+ (void)!writev( 2, iov, 2 );
+#endif
+ if ( logfile_fd >= 0 ) {
+ if ( logfile_fslimit || logfile_age ) {
+ ldap_pvt_thread_mutex_lock( &logfile_mutex );
+ if ( logfile_fslimit && logfile_fsize + len > logfile_fslimit )
+ rotate = 1;
+ if ( logfile_age && tv.tv_sec - logfile_fcreated >= logfile_age )
+ rotate |= 2;
+ if ( rotate ) {
+ close( logfile_fd );
+ logfile_fd = -1;
+ strcpy( logpaths[0]+logpathlen, ".tmp" );
+ rename( logfile_path, logpaths[0] );
+ logfile_open( logfile_path );
+ }
+ }
+
+ if ( logfile_format > LFMT_DEBUG ) {
+ struct tm tm;
+ if ( logfile_format == LFMT_SYSLOG_UTC )
+ ldap_pvt_gmtime( &tv.tv_sec, &tm );
+ else
+ ldap_pvt_localtime( &tv.tv_sec, &tm );
+#ifdef _WIN32
+ if ( splen < prefixlen )
+ ptr += prefixlen - splen;
+ memcpy( ptr, syslog_prefix, splen );
+#else
+ ptr = syslog_prefix;
+#endif
+ strftime( ptr, sizeof( SYSLOG_STAMP ),
+ "%b %d %H:%M:%S", &tm );
+ ptr[ sizeof( SYSLOG_STAMP )-1 ] = ' ';
+#ifdef _WIN32
+ len = datalen + splen;
+#else
+ iov[0].iov_base = syslog_prefix;
+ iov[0].iov_len = splen;
+#endif
+ }
+
+#ifdef _WIN32
+ if ( logfile_format <= LFMT_DEBUG )
+ ptr += poffset; /* only nonzero if logfile-format was explicitly set */
+ len = write( logfile_fd, ptr, len );
+#else
+ len = writev( logfile_fd, iov, 2 );
+#endif
+ if ( len > 0 )
+ logfile_fsize += len;
+ if ( logfile_fslimit || logfile_age )
+ ldap_pvt_thread_mutex_unlock( &logfile_mutex );
+ }
+ if ( rotate ) {
+ int i;
+ for (i=logfile_max; i > 1; i--) {
+ sprintf( logpaths[0]+logpathlen, ".%02d", i );
+ sprintf( logpaths[1]+logpathlen, ".%02d", i-1 );
+ rename( logpaths[1], logpaths[0] );
+ }
+ sprintf( logpaths[0]+logpathlen, ".tmp" );
+ rename( logpaths[0], logpaths[1] );
+ }
+}
+
+void
+logfile_close()
+{
+ if ( logfile_fd >= 0 ) {
+ close( logfile_fd );
+ logfile_fd = -1;
+ }
+ logfile_path[0] = '\0';
+}
+
+int
+logfile_open( const char *path )
+{
+ struct stat st;
+ int fd, saved_errno;
+
+ /* the logfile is for slapd only, not tools */
+ if ( !( slapMode & SLAP_SERVER_MODE ))
+ return 0;
+
+ fd = open( path, O_CREAT|O_WRONLY, 0640 );
+ if ( fd < 0 ) {
+ saved_errno = errno;
+fail:
+ logfile_only = 0; /* make sure something gets output */
+ return saved_errno;
+ }
+
+ if ( fstat( fd, &st ) ) {
+ saved_errno = errno;
+ close( fd );
+ goto fail;
+ }
+
+ if ( !logfile_path[0] ) {
+ logpathlen = strlen( path );
+ if ( logpathlen >= sizeof(logfile_path) ) {
+ saved_errno = ENAMETOOLONG;
+ goto fail;
+ }
+ strcpy( logfile_path, path );
+ strcpy( logpaths[0], path );
+ strcpy( logpaths[1], path );
+ }
+
+ logfile_fsize = st.st_size;
+ logfile_fcreated = st.st_ctime; /* not strictly true but close enough */
+ logfile_fd = fd;
+ lseek( fd, 0, SEEK_END );
+
+ return 0;
+}
+
+const char *
+logfile_name()
+{
+ return logfile_path[0] ? logfile_path : NULL;
+}
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+#ifdef LOG_LOCAL4
+int
+slap_parse_syslog_user( const char *arg, int *syslogUser )
+{
+ static slap_verbmasks syslogUsers[] = {
+ { BER_BVC( "LOCAL0" ), LOG_LOCAL0 },
+ { BER_BVC( "LOCAL1" ), LOG_LOCAL1 },
+ { BER_BVC( "LOCAL2" ), LOG_LOCAL2 },
+ { BER_BVC( "LOCAL3" ), LOG_LOCAL3 },
+ { BER_BVC( "LOCAL4" ), LOG_LOCAL4 },
+ { BER_BVC( "LOCAL5" ), LOG_LOCAL5 },
+ { BER_BVC( "LOCAL6" ), LOG_LOCAL6 },
+ { BER_BVC( "LOCAL7" ), LOG_LOCAL7 },
+#ifdef LOG_USER
+ { BER_BVC( "USER" ), LOG_USER },
+#endif /* LOG_USER */
+#ifdef LOG_DAEMON
+ { BER_BVC( "DAEMON" ), LOG_DAEMON },
+#endif /* LOG_DAEMON */
+ { BER_BVNULL, 0 }
+ };
+ int i = verb_to_mask( arg, syslogUsers );
+
+ if ( BER_BVISNULL( &syslogUsers[ i ].word ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unrecognized syslog user \"%s\".\n",
+ arg );
+ return 1;
+ }
+
+ *syslogUser = syslogUsers[ i ].mask;
+
+ return 0;
+}
+#endif /* LOG_LOCAL4 */
+
+int
+slap_parse_syslog_level( const char *arg, int *levelp )
+{
+ static slap_verbmasks str2syslog_level[] = {
+ { BER_BVC( "EMERG" ), LOG_EMERG },
+ { BER_BVC( "ALERT" ), LOG_ALERT },
+ { BER_BVC( "CRIT" ), LOG_CRIT },
+ { BER_BVC( "ERR" ), LOG_ERR },
+ { BER_BVC( "WARNING" ), LOG_WARNING },
+ { BER_BVC( "NOTICE" ), LOG_NOTICE },
+ { BER_BVC( "INFO" ), LOG_INFO },
+ { BER_BVC( "DEBUG" ), LOG_DEBUG },
+ { BER_BVNULL, 0 }
+ };
+ int i = verb_to_mask( arg, str2syslog_level );
+ if ( BER_BVISNULL( &str2syslog_level[ i ].word ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "unknown syslog level \"%s\".\n",
+ arg );
+ return 1;
+ }
+
+ *levelp = str2syslog_level[ i ].mask;
+
+ return 0;
+}
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+static char **debug_unknowns;
+static char **syslog_unknowns;
+
+static int
+parse_debug_unknowns( char **unknowns, int *levelp )
+{
+ int i, level, rc = 0;
+
+ for ( i = 0; unknowns[ i ] != NULL; i++ ) {
+ level = 0;
+ if ( str2loglevel( unknowns[ i ], &level )) {
+ fprintf( stderr,
+ "unrecognized log level \"%s\"\n", unknowns[ i ] );
+ rc = 1;
+ } else {
+ *levelp |= level;
+ }
+ }
+ return rc;
+}
+
+int
+slap_parse_debug_level( const char *arg, int *levelp, int which )
+{
+ int level;
+
+ if ( arg && arg[ 0 ] != '-' && !isdigit( (unsigned char) arg[ 0 ] ) )
+ {
+ int i;
+ char **levels;
+ char ***unknowns = which ? &syslog_unknowns : &debug_unknowns;
+
+ levels = ldap_str2charray( arg, "," );
+
+ for ( i = 0; levels[ i ] != NULL; i++ ) {
+ level = 0;
+
+ if ( str2loglevel( levels[ i ], &level ) ) {
+ /* remember this for later */
+ ldap_charray_add( unknowns, levels[ i ] );
+ fprintf( stderr,
+ "unrecognized log level \"%s\" (deferred)\n",
+ levels[ i ] );
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ ldap_charray_free( levels );
+
+ } else {
+ int rc;
+
+ if ( arg[0] == '-' ) {
+ rc = lutil_atoix( &level, arg, 0 );
+ } else {
+ unsigned ulevel;
+
+ rc = lutil_atoux( &ulevel, arg, 0 );
+ level = (int)ulevel;
+ }
+
+ if ( rc ) {
+ fprintf( stderr,
+ "unrecognized log level "
+ "\"%s\"\n", arg );
+ return 1;
+ }
+
+ if ( level == 0 ) {
+ *levelp = 0;
+
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ return 0;
+}
+
+int
+slap_parse_debug_unknowns() {
+ int rc = 0;
+ if ( debug_unknowns ) {
+ rc = parse_debug_unknowns( debug_unknowns, &slap_debug );
+ ldap_charray_free( debug_unknowns );
+ debug_unknowns = NULL;
+ if ( rc )
+ goto leave;
+ ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug );
+ ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug );
+ }
+ if ( syslog_unknowns ) {
+ rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog );
+ ldap_charray_free( syslog_unknowns );
+ syslog_unknowns = NULL;
+ }
+leave:
+ return rc;
+}
+
+void slap_check_unknown_level( char *levelstr, int level )
+{
+ int i;
+
+ if ( debug_unknowns ) {
+ for ( i = 0; debug_unknowns[ i ]; i++ ) {
+ if ( !strcasecmp( debug_unknowns[ i ], levelstr )) {
+ slap_debug |= level;
+ break;
+ }
+ }
+ }
+
+ if ( syslog_unknowns ) {
+ for ( i = 0; syslog_unknowns[ i ]; i++ ) {
+ if ( !strcasecmp( syslog_unknowns[ i ], levelstr )) {
+ ldap_syslog |= level;
+ break;
+ }
+ }
+ }
+}
+
+static slap_verbmasks *loglevel_ops;
+
+static int
+loglevel_init( void )
+{
+ slap_verbmasks lo[] = {
+ { BER_BVC("Any"), (slap_mask_t) LDAP_DEBUG_ANY },
+ { BER_BVC("Trace"), LDAP_DEBUG_TRACE },
+ { BER_BVC("Packets"), LDAP_DEBUG_PACKETS },
+ { BER_BVC("Args"), LDAP_DEBUG_ARGS },
+ { BER_BVC("Conns"), LDAP_DEBUG_CONNS },
+ { BER_BVC("BER"), LDAP_DEBUG_BER },
+ { BER_BVC("Filter"), LDAP_DEBUG_FILTER },
+ { BER_BVC("Config"), LDAP_DEBUG_CONFIG },
+ { BER_BVC("ACL"), LDAP_DEBUG_ACL },
+ { BER_BVC("Stats"), LDAP_DEBUG_STATS },
+ { BER_BVC("Stats2"), LDAP_DEBUG_STATS2 },
+ { BER_BVC("Shell"), LDAP_DEBUG_SHELL },
+ { BER_BVC("Parse"), LDAP_DEBUG_PARSE },
+#if 0 /* no longer used (nor supported) */
+ { BER_BVC("Cache"), LDAP_DEBUG_CACHE },
+ { BER_BVC("Index"), LDAP_DEBUG_INDEX },
+#endif
+ { BER_BVC("Sync"), LDAP_DEBUG_SYNC },
+ { BER_BVC("None"), LDAP_DEBUG_NONE },
+ { BER_BVNULL, 0 }
+ };
+
+ return slap_verbmasks_init( &loglevel_ops, lo );
+}
+
+void
+slap_loglevel_destroy( void )
+{
+ if ( loglevel_ops ) {
+ (void)slap_verbmasks_destroy( loglevel_ops );
+ }
+ loglevel_ops = NULL;
+}
+
+static slap_mask_t loglevel_ignore[] = { -1, 0 };
+
+int
+slap_loglevel_get( struct berval *s, int *l )
+{
+ int rc;
+ slap_mask_t m, i;
+
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ for ( m = 0, i = 1; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) {
+ m |= loglevel_ops[ i ].mask;
+ }
+
+ for ( i = 1; m & i; i <<= 1 )
+ ;
+
+ if ( i == 0 ) {
+ return -1;
+ }
+
+ rc = slap_verbmasks_append( &loglevel_ops, i, s, loglevel_ignore );
+
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "slap_loglevel_get(%lu, \"%s\") failed\n",
+ i, s->bv_val );
+
+ } else {
+ *l = i;
+ slap_check_unknown_level( s->bv_val, i );
+ }
+
+ return rc;
+}
+
+int
+slap_syslog_get()
+{
+ return active_syslog;
+}
+
+void
+slap_syslog_set( int l )
+{
+ active_syslog = l;
+ if ( logfile_only ) {
+ slap_debug |= active_syslog;
+ ldap_syslog = 0;
+ } else {
+ ldap_syslog = active_syslog;
+ }
+}
+
+int
+slap_debug_get()
+{
+ return slap_debug_orig;
+}
+
+void
+slap_debug_set( int l )
+{
+ slap_debug_orig = l;
+ if ( logfile_only )
+ slap_debug = slap_debug_orig | active_syslog;
+ else
+ slap_debug = slap_debug_orig;
+ ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug);
+ ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug);
+ ldif_debug = slap_debug;
+}
+
+int
+str2loglevel( const char *s, int *l )
+{
+ int i;
+
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ i = verb_to_mask( s, loglevel_ops );
+
+ if ( BER_BVISNULL( &loglevel_ops[ i ].word ) ) {
+ return -1;
+ }
+
+ *l = loglevel_ops[ i ].mask;
+
+ return 0;
+}
+
+const char *
+loglevel2str( int l )
+{
+ struct berval bv = BER_BVNULL;
+
+ loglevel2bv( l, &bv );
+
+ return bv.bv_val;
+}
+
+int
+loglevel2bv( int l, struct berval *bv )
+{
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ BER_BVZERO( bv );
+
+ return enum_to_verb( loglevel_ops, l, bv ) == -1;
+}
+
+int
+loglevel2bvarray( int l, BerVarray *bva )
+{
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ if ( l == 0 ) {
+ struct berval bv = BER_BVC("0");
+ return value_add_one( bva, &bv );
+ }
+
+ return mask_to_verbs( loglevel_ops, l, bva );
+}
+
+int
+loglevel_print( FILE *out )
+{
+ int i;
+
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ fprintf( out, "Installed log subsystems:\n\n" );
+ for ( i = 0; !BER_BVISNULL( &loglevel_ops[ i ].word ); i++ ) {
+ unsigned mask = loglevel_ops[ i ].mask & 0xffffffffUL;
+ fprintf( out,
+ (mask == ((slap_mask_t) -1 & 0xffffffffUL)
+ ? "\t%-30s (-1, 0xffffffff)\n" : "\t%-30s (%u, 0x%x)\n"),
+ loglevel_ops[ i ].word.bv_val, mask, mask );
+ }
+
+ fprintf( out, "\nNOTE: custom log subsystems may be later installed "
+ "by specific code\n\n" );
+
+ return 0;
+}
+
+int
+config_logging(ConfigArgs *c) {
+ int i, rc = 0;
+
+ if ( loglevel_ops == NULL ) {
+ loglevel_init();
+ }
+
+ if (c->op == SLAP_CONFIG_EMIT) {
+ switch(c->type) {
+ case CFG_LOGLEVEL:
+ /* Get default or commandline slapd setting */
+ if ( ldap_syslog && !config_syslog )
+ config_syslog = ldap_syslog;
+ rc = loglevel2bvarray( config_syslog, &c->rvalue_vals );
+ break;
+
+ case CFG_LOGFILE: {
+ const char *logfileName = logfile_name();
+ if ( logfileName && *logfileName )
+ c->value_string = ch_strdup( logfileName );
+ else
+ rc = 1;
+ }
+ break;
+ case CFG_LOGFILE_FORMAT:
+ if ( logfile_format ) {
+ value_add_one( &c->rvalue_vals, &logformat_key[logfile_format].word );
+ } else {
+ rc = 1;
+ }
+ break;
+ case CFG_LOGFILE_ONLY:
+ c->value_int = logfile_only;
+ break;
+ case CFG_LOGFILE_ROTATE:
+ rc = 1;
+ if ( logfile_max ) {
+ char buf[64];
+ struct berval bv;
+ bv.bv_len = snprintf( buf, sizeof(buf), "%d %ld %ld", logfile_max,
+ (long) logfile_fslimit / 1048576, (long) logfile_age / 3600 );
+ if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) {
+ bv.bv_val = buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ }
+ break;
+ default:
+ rc = 1;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch(c->type) {
+ case CFG_LOGLEVEL:
+ if ( !c->line ) {
+ config_syslog = 0;
+ } else {
+ i = verb_to_mask( c->line, loglevel_ops );
+ config_syslog &= ~loglevel_ops[i].mask;
+ }
+ goto reset;
+
+ case CFG_LOGFILE:
+ logfile_close();
+ break;
+
+ case CFG_LOGFILE_FORMAT:
+ logfile_format = 0;
+ ch_free( syslog_prefix );
+ syslog_prefix = NULL;
+ break;
+
+ case CFG_LOGFILE_ONLY:
+ /* remove loglevel from debuglevel */
+ slap_debug = slap_debug_orig;
+ ldap_syslog = config_syslog;
+ break;
+
+ case CFG_LOGFILE_ROTATE:
+ logfile_max = logfile_fslimit = logfile_age = 0;
+ break;
+ default:
+ rc = 1;
+ }
+ return rc;
+ }
+
+ switch(c->type) {
+ case CFG_LOGLEVEL:
+ for( i=1; i < c->argc; i++ ) {
+ int level;
+
+ if ( isdigit((unsigned char)c->argv[i][0]) || c->argv[i][0] == '-' ) {
+ if( lutil_atoix( &level, c->argv[i], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to parse level", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return( 1 );
+ }
+ } else {
+ if ( str2loglevel( c->argv[i], &level ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown level", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[i]);
+ return( 1 );
+ }
+ }
+ /* Explicitly setting a zero clears all the levels */
+ if ( level )
+ config_syslog |= level;
+ else
+ config_syslog = 0;
+ }
+
+reset:
+ slap_debug = slap_debug_orig;
+ active_syslog = config_syslog;
+ if ( slapMode & SLAP_SERVER_MODE ) {
+ if ( logfile_only ) {
+ slap_debug |= config_syslog;
+ ldap_syslog = 0;
+ } else {
+ ldap_syslog = config_syslog;
+ }
+ }
+ rc = 0;
+ break;
+
+ case CFG_LOGFILE:
+ rc = logfile_open( c->value_string );
+ ch_free( c->value_string );
+ break;
+
+ case CFG_LOGFILE_FORMAT: {
+ int len;
+ i = verb_to_mask( c->argv[1], logformat_key );
+
+ if ( BER_BVISNULL( &logformat_key[ i ].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown format", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->argv[1]);
+ return( 1 );
+ }
+ if ( syslog_prefix )
+ ch_free( syslog_prefix );
+ len = strlen( global_host ) + 1 + strlen( serverName ) + 1 + sizeof("[123456789]:") +
+ sizeof( SYSLOG_STAMP );
+ syslog_prefix = ch_malloc( len );
+ splen = sprintf( syslog_prefix, SYSLOG_STAMP " %s %s[%d]: ", global_host, serverName, getpid() );
+ logfile_format = logformat_key[i].mask;
+ }
+ break;
+
+ case CFG_LOGFILE_ONLY:
+ logfile_only = c->value_int;
+ goto reset;
+
+ case CFG_LOGFILE_ROTATE: {
+ unsigned lf_max, lf_mbyte, lf_hour;
+ if ( lutil_atoux( &lf_max, c->argv[1], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+ "invalid max value \"%s\"", c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ if ( !lf_max || lf_max > 99 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+ "invalid max value \"%s\" must be 1-99", c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ if ( lutil_atoux( &lf_mbyte, c->argv[2], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+ "invalid Mbyte value \"%s\"", c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ if ( lutil_atoux( &lf_hour, c->argv[3], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+ "invalid hours value \"%s\"", c->argv[0], c->argv[3] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ if ( !lf_mbyte && !lf_hour ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+ "Mbyte and hours cannot both be zero", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+ logfile_max = lf_max;
+ logfile_fslimit = lf_mbyte * 1048576; /* Megabytes to bytes */
+ logfile_age = lf_hour * 3600; /* hours to seconds */
+ }
+ break;
+ default:
+ rc = 1;
+ }
+ return rc;
+}
diff --git a/servers/slapd/main.c b/servers/slapd/main.c
new file mode 100644
index 0000000..a02d663
--- /dev/null
+++ b/servers/slapd/main.c
@@ -0,0 +1,989 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+#include <ac/errno.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldif.h"
+
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+
+#ifdef LDAP_SIGCHLD
+static RETSIGTYPE wait4child( int sig );
+#endif
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+#define MAIN_RETURN(x) return
+static struct sockaddr_in bind_addr;
+
+#define SERVICE_EXIT( e, n ) do { \
+ if ( is_NT_Service ) { \
+ lutil_ServiceStatus.dwWin32ExitCode = (e); \
+ lutil_ServiceStatus.dwServiceSpecificExitCode = (n); \
+ } \
+} while ( 0 )
+
+#else
+#define SERVICE_EXIT( e, n )
+#define MAIN_RETURN(x) return(x)
+#endif
+
+typedef int (MainFunc) LDAP_P(( int argc, char *argv[] ));
+extern MainFunc slapadd, slapcat, slapdn, slapindex, slappasswd,
+ slaptest, slapauth, slapacl, slapschema, slapmodify;
+
+static struct {
+ char *name;
+ MainFunc *func;
+} tools[] = {
+ {"slapadd", slapadd},
+ {"slapcat", slapcat},
+ {"slapdn", slapdn},
+ {"slapindex", slapindex},
+ {"slapmodify", slapmodify},
+ {"slappasswd", slappasswd},
+ {"slapschema", slapschema},
+ {"slaptest", slaptest},
+ {"slapauth", slapauth},
+ {"slapacl", slapacl},
+ /* NOTE: new tools must be added in chronological order,
+ * not in alphabetical order, because for backwards
+ * compatibility name[4] is used to identify the
+ * tools; so name[4]=='a' must refer to "slapadd" and
+ * not to "slapauth". Alphabetical order can be used
+ * for tools whose name[4] is not used yet */
+ {NULL, NULL}
+};
+
+/*
+ * when more than one slapd is running on one machine, each one might have
+ * it's own LOCAL for syslogging and must have its own pid/args files
+ */
+
+#ifndef HAVE_MKVERSION
+const char Versionstr[] =
+ OPENLDAP_PACKAGE " " OPENLDAP_VERSION " Standalone LDAP Server (slapd)";
+#endif
+
+extern OverlayInit slap_oinfo[];
+extern BackendInfo slap_binfo[];
+
+#define CHECK_NONE 0x00
+#define CHECK_CONFIG 0x01
+#define CHECK_LOGLEVEL 0x02
+static int check = CHECK_NONE;
+static int version = 0;
+
+void *slap_tls_ctx;
+LDAP *slap_tls_ld;
+
+static int
+slapd_opt_slp( const char *val, void *arg )
+{
+#ifdef HAVE_SLP
+ /* NULL is default */
+ if ( val == NULL || *val == '(' || strcasecmp( val, "on" ) == 0 ) {
+ slapd_register_slp = 1;
+ slapd_slp_attrs = (val != NULL && *val == '(') ? val : NULL;
+
+ } else if ( strcasecmp( val, "off" ) == 0 ) {
+ slapd_register_slp = 0;
+
+ /* NOTE: add support for URL specification? */
+
+ } else {
+ fprintf(stderr, "unrecognized value \"%s\" for SLP option\n", val );
+ return -1;
+ }
+
+ return 0;
+
+#else
+ fputs( "slapd: SLP support is not available\n", stderr );
+ return 0;
+#endif
+}
+
+/*
+ * Option helper structure:
+ *
+ * oh_nam is left-hand part of <option>[=<value>]
+ * oh_fnc is handler function
+ * oh_arg is an optional arg to oh_fnc
+ * oh_usage is the one-line usage string related to the option,
+ * which is assumed to start with <option>[=<value>]
+ *
+ * please leave valid options in the structure, and optionally #ifdef
+ * their processing inside the helper, so that reasonable and helpful
+ * error messages can be generated if a disabled option is requested.
+ */
+struct option_helper {
+ struct berval oh_name;
+ int (*oh_fnc)(const char *val, void *arg);
+ void *oh_arg;
+ const char *oh_usage;
+} option_helpers[] = {
+ { BER_BVC("slp"), slapd_opt_slp, NULL, "slp[={on|off|(attrs)}] enable/disable SLP using (attrs)" },
+ { BER_BVNULL, 0, NULL, NULL }
+};
+
+static void
+usage( char *name )
+{
+ fprintf( stderr,
+ "usage: %s options\n", name );
+ fprintf( stderr,
+ "\t-4\t\tIPv4 only\n"
+#ifdef LDAP_PF_INET6
+ "\t-6\t\tIPv6 only\n"
+#endif
+ "\t-T {acl|add|auth|cat|dn|index|modify|passwd|test}\n"
+ "\t\t\tRun in Tool mode\n"
+ "\t-c cookie\tSync cookie of consumer\n"
+ "\t-d level\tDebug level" "\n"
+ "\t-f filename\tConfiguration file\n"
+ "\t-F dir\tConfiguration directory\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-g group\tGroup (id or name) to run as\n"
+#endif
+ "\t-h URLs\t\tList of URLs to serve\n"
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ "\t-l facility\tSyslog facility (default: LOCAL4)\n"
+#endif
+ "\t-n serverName\tService name\n"
+ "\t-o <opt>[=val] generic means to specify options" );
+ if ( !BER_BVISNULL( &option_helpers[0].oh_name ) ) {
+ int i;
+
+ fprintf( stderr, "; supported options:\n" );
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++) {
+ fprintf( stderr, "\t\t%s\n", option_helpers[i].oh_usage );
+ }
+ } else {
+ fprintf( stderr, "\n" );
+ }
+ fprintf( stderr,
+#ifdef HAVE_CHROOT
+ "\t-r directory\tSandbox directory to chroot to\n"
+#endif
+ "\t-s level\tSyslog level\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-u user\t\tUser (id or name) to run as\n"
+#endif
+ "\t-V\t\tprint version info (-VV exit afterwards, -VVV print\n"
+ "\t\t\tinfo about static overlays and backends)\n"
+ );
+}
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+void WINAPI ServiceMain( DWORD argc, LPTSTR *argv )
+#else
+int main( int argc, char **argv )
+#endif
+{
+ int i, no_detach = 0;
+ int rc = 1;
+ char *urls = NULL;
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ char *username = NULL;
+ char *groupname = NULL;
+#endif
+#if defined(HAVE_CHROOT)
+ char *sandbox = NULL;
+#endif
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ int syslogUser = SLAP_DEFAULT_SYSLOG_USER;
+#endif
+
+#ifndef HAVE_WINSOCK
+ int pid, waitfds[2];
+#endif
+ int g_argc = argc;
+ char **g_argv = argv;
+
+ char *configfile = NULL;
+ char *configdir = NULL;
+ int serverMode = SLAP_SERVER_MODE;
+
+ struct sync_cookie *scp = NULL;
+ struct sync_cookie *scp_entry = NULL;
+
+ char *serverNamePrefix = "";
+ size_t l;
+
+ int slapd_pid_file_unlink = 0, slapd_args_file_unlink = 0;
+ int firstopt = 1;
+
+#ifdef CSRIMALLOC
+ FILE *leakfile;
+ if( ( leakfile = fopen( "slapd.leak", "w" )) == NULL ) {
+ leakfile = stderr;
+ }
+#endif
+
+ slap_sl_mem_init();
+
+ (void) ldap_pvt_thread_initialize();
+ ldap_pvt_thread_mutex_init( &logfile_mutex );
+
+#ifdef HAVE_TLS
+ rc = ldap_create( &slap_tls_ld );
+ if ( rc ) {
+ MAIN_RETURN( rc );
+ }
+ /* Library defaults to full certificate checking. This is correct when
+ * a client is verifying a server because all servers should have a
+ * valid cert. But few clients have valid certs, so we want our default
+ * to be no checking. The config file can override this as usual.
+ */
+ rc = LDAP_OPT_X_TLS_NEVER;
+ (void) ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &rc );
+#endif
+
+ global_host = ldap_pvt_get_fqdn( NULL );
+ ber_str2bv( global_host, 0, 0, &global_host_bv );
+
+ serverName = lutil_progname( "slapd", argc, argv );
+
+ if ( strcmp( serverName, "slapd" ) ) {
+#ifdef DEBUG_CLOSE
+ extern void slapd_debug_close();
+ slapd_debug_close();
+#endif
+ for (i=0; tools[i].name; i++) {
+ if ( !strcmp( serverName, tools[i].name ) ) {
+ rc = tools[i].func(argc, argv);
+ MAIN_RETURN(rc);
+ }
+ }
+ }
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ {
+ int *ip;
+ char *newConfigFile;
+ char *newConfigDir;
+ char *newUrls;
+ char *regService = NULL;
+
+ if ( is_NT_Service ) {
+ lutil_CommenceStartupProcessing( serverName, slap_sig_shutdown );
+ if ( strcmp(serverName, SERVICE_NAME) )
+ regService = serverName;
+ }
+
+ ip = (int*)lutil_getRegParam( regService, "DebugLevel" );
+ if ( ip != NULL ) {
+ slap_debug = *ip;
+ Debug( LDAP_DEBUG_ANY,
+ "new debug level from registry is: %d\n", slap_debug );
+ }
+
+ newUrls = (char *) lutil_getRegParam(regService, "Urls");
+ if (newUrls) {
+ urls = ch_strdup(newUrls);
+ Debug(LDAP_DEBUG_ANY, "new urls from registry: %s\n",
+ urls );
+ }
+
+ newConfigFile = (char*)lutil_getRegParam( regService, "ConfigFile" );
+ if ( newConfigFile != NULL ) {
+ configfile = ch_strdup(newConfigFile);
+ Debug ( LDAP_DEBUG_ANY, "new config file from registry is: %s\n", configfile );
+ }
+
+ newConfigDir = (char*)lutil_getRegParam( regService, "ConfigDir" );
+ if ( newConfigDir != NULL ) {
+ configdir = ch_strdup(newConfigDir);
+ Debug ( LDAP_DEBUG_ANY, "new config dir from registry is: %s\n", configdir );
+ }
+ }
+#endif
+
+ while ( (i = getopt( argc, argv,
+ "c:d:f:F:h:n:o:s:tT:V"
+#ifdef LDAP_PF_INET6
+ "46"
+#endif
+#ifdef HAVE_CHROOT
+ "r:"
+#endif
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ "S:"
+#ifdef LOG_LOCAL4
+ "l:"
+#endif
+#endif
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "u:g:"
+#endif
+ )) != EOF ) {
+ switch ( i ) {
+ case '4':
+ slap_inet4or6 = AF_INET;
+ break;
+#ifdef LDAP_PF_INET6
+ case '6':
+ slap_inet4or6 = AF_INET6;
+ break;
+#endif
+
+ case 'h': /* listen URLs */
+ urls = optarg;
+ break;
+
+ case 'c': /* provide sync cookie, override if exist in consumer */
+ scp = (struct sync_cookie *) ch_calloc( 1,
+ sizeof( struct sync_cookie ));
+ ber_str2bv( optarg, 0, 1, &scp->octet_str );
+
+ /* This only parses out the rid at this point */
+ slap_parse_sync_cookie( scp, NULL );
+
+ if ( scp->rid == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "main: invalid cookie \"%s\"\n",
+ optarg );
+ slap_sync_cookie_free( scp, 1 );
+ goto destroy;
+ }
+
+ LDAP_STAILQ_FOREACH( scp_entry, &slap_sync_cookie, sc_next ) {
+ if ( scp->rid == scp_entry->rid ) {
+ Debug( LDAP_DEBUG_ANY,
+ "main: duplicated replica id in cookies\n" );
+ slap_sync_cookie_free( scp, 1 );
+ goto destroy;
+ }
+ }
+ LDAP_STAILQ_INSERT_TAIL( &slap_sync_cookie, scp, sc_next );
+ break;
+
+ case 'd': { /* set debug level and 'do not detach' flag */
+ int level = 0;
+
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ no_detach = 1;
+ if ( slap_parse_debug_level( optarg, &level, 0 ) ) {
+ goto destroy;
+ }
+#ifdef LDAP_DEBUG
+ slap_debug |= level;
+#else
+ if ( level != 0 )
+ fputs( "must compile with LDAP_DEBUG for debugging\n",
+ stderr );
+#endif
+ } break;
+
+ case 'f': /* read config file */
+ configfile = optarg;
+ break;
+
+ case 'F': /* use config dir */
+ configdir = optarg;
+ break;
+
+ case 'o': {
+ char *val = strchr( optarg, '=' );
+ struct berval opt;
+
+ opt.bv_val = optarg;
+
+ if ( val ) {
+ opt.bv_len = ( val - optarg );
+ val++;
+
+ } else {
+ opt.bv_len = strlen( optarg );
+ }
+
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++ ) {
+ if ( ber_bvstrcasecmp( &option_helpers[i].oh_name, &opt ) == 0 ) {
+ assert( option_helpers[i].oh_fnc != NULL );
+ if ( (*option_helpers[i].oh_fnc)( val, option_helpers[i].oh_arg ) == -1 ) {
+ /* we assume the option parsing helper
+ * issues appropriate and self-explanatory
+ * error messages... */
+ goto stop;
+ }
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &option_helpers[i].oh_name ) ) {
+ goto unhandled_option;
+ }
+ break;
+ }
+
+ case 's': /* set syslog level */
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ if ( slap_parse_debug_level( optarg, &ldap_syslog, 1 ) ) {
+ goto destroy;
+ }
+ break;
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ case 'S':
+ if ( slap_parse_syslog_level( optarg, &ldap_syslog_level ) ) {
+ goto destroy;
+ }
+ break;
+
+#ifdef LOG_LOCAL4
+ case 'l': /* set syslog local user */
+ if ( slap_parse_syslog_user( optarg, &syslogUser ) ) {
+ goto destroy;
+ }
+ break;
+#endif
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+#ifdef HAVE_CHROOT
+ case 'r':
+ sandbox = optarg;
+ break;
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ case 'u': /* user name */
+ username = optarg;
+ break;
+
+ case 'g': /* group name */
+ groupname = optarg;
+ break;
+#endif /* SETUID && GETUID */
+
+ case 'n': /* NT service name */
+ serverName = optarg;
+ break;
+
+ case 't':
+ /* deprecated; use slaptest instead */
+ fprintf( stderr, "option -t deprecated; "
+ "use slaptest command instead\n" );
+ check |= CHECK_CONFIG;
+ break;
+
+ case 'V':
+ version++;
+ break;
+
+ case 'T':
+ if ( firstopt == 0 ) {
+ fprintf( stderr, "warning: \"-T %s\" "
+ "should be the first option.\n",
+ optarg );
+ }
+
+#ifdef DEBUG_CLOSE
+ extern void slapd_debug_close();
+ slapd_debug_close();
+#endif
+ /* try full option string first */
+ for ( i = 0; tools[i].name; i++ ) {
+ if ( strcmp( optarg, &tools[i].name[4] ) == 0 ) {
+ rc = tools[i].func( argc, argv );
+ MAIN_RETURN( rc );
+ }
+ }
+
+ /* try bits of option string (backward compatibility for single char) */
+ l = strlen( optarg );
+ for ( i = 0; tools[i].name; i++ ) {
+ if ( strncmp( optarg, &tools[i].name[4], l ) == 0 ) {
+ rc = tools[i].func( argc, argv );
+ MAIN_RETURN( rc );
+ }
+ }
+
+ /* issue error */
+ serverName = optarg;
+ serverNamePrefix = "slap";
+ fprintf( stderr, "program name \"%s%s\" unrecognized; "
+ "aborting...\n", serverNamePrefix, serverName );
+ /* FALLTHRU */
+ default:
+unhandled_option:;
+ usage( argv[0] );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 15 );
+ goto stop;
+ }
+
+ if ( firstopt ) {
+ firstopt = 0;
+ }
+ }
+
+ if ( optind != argc )
+ goto unhandled_option;
+
+ ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, slap_debug_print);
+ ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug);
+ ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug);
+ ldif_debug = slap_debug;
+ slap_debug_orig = slap_debug;
+
+ if ( version ) {
+ fprintf( stderr, "%s\n", Versionstr );
+ if ( version > 2 ) {
+ if ( slap_oinfo[0].ov_type ) {
+ fprintf( stderr, "Included static overlays:\n");
+ for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) {
+ fprintf( stderr, " %s\n", slap_oinfo[i].ov_type );
+ }
+ }
+ if ( slap_binfo[0].bi_type ) {
+ fprintf( stderr, "Included static backends:\n");
+ for ( i= 0 ; slap_binfo[i].bi_type; i++ ) {
+ fprintf( stderr, " %s\n", slap_binfo[i].bi_type );
+ }
+ }
+ }
+
+ if ( version > 1 ) goto stop;
+ }
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ {
+ char *logName;
+#ifdef HAVE_EBCDIC
+ logName = ch_strdup( serverName );
+ __atoe( logName );
+#else
+ logName = serverName;
+#endif
+
+#ifdef LOG_LOCAL4
+ openlog( logName, OPENLOG_OPTIONS, syslogUser );
+#elif defined LOG_DEBUG
+ openlog( logName, OPENLOG_OPTIONS );
+#endif
+#ifdef HAVE_EBCDIC
+ free( logName );
+#endif
+ }
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+ Debug( LDAP_DEBUG_ANY, "%s", Versionstr );
+
+ if( check == CHECK_NONE && slapd_daemon_init( urls ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 16 );
+ goto stop;
+ }
+
+#if defined(HAVE_CHROOT)
+ if ( sandbox ) {
+ if ( chdir( sandbox ) ) {
+ perror("chdir");
+ rc = 1;
+ goto stop;
+ }
+ if ( chroot( sandbox ) ) {
+ perror("chroot");
+ rc = 1;
+ goto stop;
+ }
+ if ( chdir( "/" ) ) {
+ perror("chdir");
+ rc = 1;
+ goto stop;
+ }
+ }
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ if ( username != NULL || groupname != NULL ) {
+ slap_init_user( username, groupname );
+ }
+#endif
+
+ extops_init();
+ lutil_passwd_init();
+
+ rc = slap_init( serverMode, serverName );
+ if ( rc ) {
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 18 );
+ goto destroy;
+ }
+
+ if ( read_config( configfile, configdir ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 19 );
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check failed\n" );
+ }
+
+ goto destroy;
+ }
+
+ rc = slap_parse_debug_unknowns();
+ if ( rc )
+ goto destroy;
+
+ if ( check & CHECK_LOGLEVEL ) {
+ rc = 0;
+ goto destroy;
+ }
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check succeeded\n" );
+
+ check &= ~CHECK_CONFIG;
+ if ( check == CHECK_NONE ) {
+ rc = 0;
+ goto destroy;
+ }
+ }
+
+ if ( glue_sub_attach( 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "subordinate config error\n" );
+
+ goto destroy;
+ }
+
+ if ( slap_schema_check( ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "schema prep error\n" );
+
+ goto destroy;
+ }
+
+#ifdef HAVE_TLS
+ rc = ldap_pvt_tls_init( 1 );
+ if( rc != 0) {
+ Debug( LDAP_DEBUG_ANY,
+ "main: TLS init failed: %d\n",
+ rc );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+
+ {
+ int opt = 1;
+
+ /* Force new ctx to be created */
+ rc = ldap_pvt_tls_set_option( slap_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt );
+ if( rc == 0 ) {
+ /* The ctx's refcount is bumped up here */
+ ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CTX, &slap_tls_ctx );
+ load_extop( &slap_EXOP_START_TLS, 0, starttls_extop );
+ } else if ( rc != LDAP_NOT_SUPPORTED ) {
+ char *errmsg = NULL;
+ ldap_get_option( slap_tls_ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &errmsg );
+ Debug( LDAP_DEBUG_ANY,
+ "main: TLS init def ctx failed: %d %s\n",
+ rc, errmsg ? errmsg : "" );
+ ldap_memfree( errmsg );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+ }
+#endif
+
+#ifdef HAVE_CYRUS_SASL
+ if( sasl_host == NULL ) {
+ sasl_host = ch_strdup( global_host );
+ }
+#endif
+
+ (void) SIGNAL( LDAP_SIGUSR1, slap_sig_wake );
+ (void) SIGNAL( LDAP_SIGUSR2, slap_sig_shutdown );
+
+#ifdef SIGPIPE
+ (void) SIGNAL( SIGPIPE, SIG_IGN );
+#endif
+#ifdef SIGHUP
+ (void) SIGNAL( SIGHUP, slap_sig_shutdown );
+#endif
+ (void) SIGNAL( SIGINT, slap_sig_shutdown );
+ (void) SIGNAL( SIGTERM, slap_sig_shutdown );
+#ifdef SIGTRAP
+ (void) SIGNAL( SIGTRAP, slap_sig_shutdown );
+#endif
+#ifdef LDAP_SIGCHLD
+ (void) SIGNAL( LDAP_SIGCHLD, wait4child );
+#endif
+#ifdef SIGBREAK
+ /* SIGBREAK is generated when Ctrl-Break is pressed. */
+ (void) SIGNAL( SIGBREAK, slap_sig_shutdown );
+#endif
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ if ( lutil_pair( waitfds ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "main: lutil_pair failed: %d\n",
+ 0 );
+ rc = 1;
+ goto destroy;
+ }
+ pid = lutil_detach( no_detach, 0 );
+ if ( pid ) {
+ char buf[4];
+ rc = EXIT_SUCCESS;
+ close( waitfds[1] );
+ if ( read( waitfds[0], buf, 1 ) != 1 )
+ rc = EXIT_FAILURE;
+ _exit( rc );
+ } else {
+ close( waitfds[0] );
+ }
+ }
+#endif /* HAVE_WINSOCK */
+
+#ifdef CSRIMALLOC
+ mal_leaktrace(1);
+#endif
+
+ if ( slapd_pid_file != NULL ) {
+ FILE *fp = fopen( slapd_pid_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open pid file "
+ "\"%s\": %d (%s)\n",
+ slapd_pid_file,
+ save_errno, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_pid_file );
+ slapd_pid_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+ fprintf( fp, "%d\n", (int) getpid() );
+ fclose( fp );
+ slapd_pid_file_unlink = 1;
+ }
+
+ if ( slapd_args_file != NULL ) {
+ FILE *fp = fopen( slapd_args_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open args file "
+ "\"%s\": %d (%s)\n",
+ slapd_args_file,
+ save_errno, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_args_file );
+ slapd_args_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+
+ for ( i = 0; i < g_argc; i++ ) {
+ fprintf( fp, "%s ", g_argv[i] );
+ }
+ fprintf( fp, "\n" );
+ fclose( fp );
+ slapd_args_file_unlink = 1;
+ }
+
+ /*
+ * FIXME: moved here from slapd_daemon_task()
+ * because back-monitor db_open() needs it
+ */
+ time( &starttime );
+
+ connections_init();
+
+ if ( slap_startup( NULL ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 );
+ goto shutdown;
+ }
+
+ Debug( LDAP_DEBUG_ANY, "slapd starting\n" );
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ write( waitfds[1], "1", 1 );
+ close( waitfds[1] );
+ }
+#endif
+
+#ifdef HAVE_NT_EVENT_LOG
+ if (is_NT_Service)
+ lutil_LogStartedEvent( serverName, slap_debug, configfile ?
+ configfile : SLAPD_DEFAULT_CONFIGFILE , urls );
+#endif
+
+ rc = slapd_daemon();
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ /* Throw away the event that we used during the startup process. */
+ if ( is_NT_Service )
+ ldap_pvt_thread_cond_destroy( &started_event );
+#endif
+
+shutdown:
+ /* remember an error during shutdown */
+ rc |= slap_shutdown( NULL );
+
+destroy:
+ if ( check & CHECK_LOGLEVEL ) {
+ (void)loglevel_print( stdout );
+ }
+ /* remember an error during destroy */
+ rc |= slap_destroy();
+
+ while ( !LDAP_STAILQ_EMPTY( &slap_sync_cookie )) {
+ scp = LDAP_STAILQ_FIRST( &slap_sync_cookie );
+ LDAP_STAILQ_REMOVE_HEAD( &slap_sync_cookie, sc_next );
+ ch_free( scp );
+ }
+
+#ifdef SLAPD_MODULES
+ module_kill();
+#endif
+
+ extops_kill();
+
+ supported_feature_destroy();
+ entry_info_destroy();
+
+stop:
+#ifdef HAVE_NT_EVENT_LOG
+ if (is_NT_Service)
+ lutil_LogStoppedEvent( serverName );
+#endif
+
+ Debug( LDAP_DEBUG_ANY, "slapd stopped.\n" );
+
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ lutil_ReportShutdownComplete();
+#endif
+
+#ifdef LOG_DEBUG
+ closelog();
+#endif
+ slapd_daemon_destroy();
+
+ controls_destroy();
+
+ filter_destroy();
+
+ schema_destroy();
+
+ lutil_passwd_destroy();
+
+#ifdef HAVE_TLS
+ if ( slap_tls_ld ) {
+ ldap_pvt_tls_ctx_free( slap_tls_ctx );
+ ldap_unbind_ext( slap_tls_ld, NULL, NULL );
+ }
+ ldap_pvt_tls_destroy();
+#endif
+
+ slap_sasl_regexp_destroy();
+
+ if ( slapd_pid_file_unlink ) {
+ unlink( slapd_pid_file );
+ }
+ if ( slapd_args_file_unlink ) {
+ unlink( slapd_args_file );
+ }
+
+ config_destroy();
+
+ if ( global_host )
+ ch_free( global_host );
+
+ /* kludge, get symbols referenced */
+ ldap_tavl_free( NULL, NULL );
+
+#ifdef CSRIMALLOC
+ mal_dumpleaktrace( leakfile );
+#endif
+
+ ldap_pvt_thread_mutex_destroy( &logfile_mutex );
+ MAIN_RETURN(rc);
+}
+
+
+#ifdef LDAP_SIGCHLD
+
+/*
+ * Catch and discard terminated child processes, to avoid zombies.
+ */
+
+static RETSIGTYPE
+wait4child( int sig )
+{
+ int save_errno = errno;
+
+#ifdef WNOHANG
+ do
+ errno = 0;
+#ifdef HAVE_WAITPID
+ while ( waitpid( (pid_t)-1, NULL, WNOHANG ) > 0 || errno == EINTR );
+#else
+ while ( wait3( NULL, WNOHANG, NULL ) > 0 || errno == EINTR );
+#endif
+#else
+ (void) wait( NULL );
+#endif
+ (void) SIGNAL_REINSTALL( sig, wait4child );
+ errno = save_errno;
+}
+
+#endif /* LDAP_SIGCHLD */
diff --git a/servers/slapd/matchedValues.c b/servers/slapd/matchedValues.c
new file mode 100644
index 0000000..574e085
--- /dev/null
+++ b/servers/slapd/matchedValues.c
@@ -0,0 +1,348 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+static int
+test_mra_vrFilter(
+ Operation *op,
+ Attribute *a,
+ MatchingRuleAssertion *mra,
+ char ***e_flags
+);
+
+static int
+test_substrings_vrFilter(
+ Operation *op,
+ Attribute *a,
+ ValuesReturnFilter *f,
+ char ***e_flags
+);
+
+static int
+test_presence_vrFilter(
+ Operation *op,
+ Attribute *a,
+ AttributeDescription *desc,
+ char ***e_flags
+);
+
+static int
+test_ava_vrFilter(
+ Operation *op,
+ Attribute *a,
+ AttributeAssertion *ava,
+ int type,
+ char ***e_flags
+);
+
+
+int
+filter_matched_values(
+ Operation *op,
+ Attribute *a,
+ char ***e_flags )
+{
+ ValuesReturnFilter *vrf;
+ int rc = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_FILTER, "=> filter_matched_values\n" );
+
+ for ( vrf = op->o_vrFilter; vrf != NULL; vrf = vrf->vrf_next ) {
+ switch ( vrf->vrf_choice ) {
+ case SLAPD_FILTER_COMPUTED:
+ Debug( LDAP_DEBUG_FILTER, " COMPUTED %s (%d)\n",
+ vrf->vrf_result == LDAP_COMPARE_FALSE ? "false"
+ : vrf->vrf_result == LDAP_COMPARE_TRUE ? "true"
+ : vrf->vrf_result == SLAPD_COMPARE_UNDEFINED ? "undefined"
+ : "error",
+ vrf->vrf_result );
+ /*This type of filter does not affect the result */
+ rc = LDAP_SUCCESS;
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ Debug( LDAP_DEBUG_FILTER, " EQUALITY\n" );
+ rc = test_ava_vrFilter( op, a, vrf->vrf_ava,
+ LDAP_FILTER_EQUALITY, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ Debug( LDAP_DEBUG_FILTER, " SUBSTRINGS\n" );
+ rc = test_substrings_vrFilter( op, a,
+ vrf, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ Debug( LDAP_DEBUG_FILTER, " PRESENT\n" );
+ rc = test_presence_vrFilter( op, a,
+ vrf->vrf_desc, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ case LDAP_FILTER_GE:
+ rc = test_ava_vrFilter( op, a, vrf->vrf_ava,
+ LDAP_FILTER_GE, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ case LDAP_FILTER_LE:
+ rc = test_ava_vrFilter( op, a, vrf->vrf_ava,
+ LDAP_FILTER_LE, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ case LDAP_FILTER_EXT:
+ Debug( LDAP_DEBUG_FILTER, " EXT\n" );
+ rc = test_mra_vrFilter( op, a,
+ vrf->vrf_mra, e_flags );
+ if( rc == -1 ) return rc;
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, " unknown filter type %lu\n",
+ vrf->vrf_choice );
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+ }
+
+ Debug( LDAP_DEBUG_FILTER, "<= filter_matched_values %d\n", rc );
+ return( rc );
+}
+
+static int
+test_ava_vrFilter(
+ Operation *op,
+ Attribute *a,
+ AttributeAssertion *ava,
+ int type,
+ char ***e_flags )
+{
+ int i, j;
+
+ for ( i=0; a != NULL; a = a->a_next, i++ ) {
+ MatchingRule *mr;
+ struct berval *bv;
+
+ if ( !is_ad_subtype( a->a_desc, ava->aa_desc ) ) {
+ continue;
+ }
+
+ switch ( type ) {
+ case LDAP_FILTER_APPROX:
+ mr = a->a_desc->ad_type->sat_approx;
+ if( mr != NULL ) break;
+ /* use EQUALITY matching rule if no APPROX rule */
+
+ case LDAP_FILTER_EQUALITY:
+ mr = a->a_desc->ad_type->sat_equality;
+ break;
+
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ mr = a->a_desc->ad_type->sat_ordering;
+ break;
+
+ default:
+ mr = NULL;
+ }
+
+ if( mr == NULL ) continue;
+
+ bv = a->a_nvals;
+ for ( j=0; !BER_BVISNULL( bv ); bv++, j++ ) {
+ int rc, match;
+ const char *text;
+
+ rc = value_match( &match, a->a_desc, mr, 0,
+ bv, &ava->aa_value, &text );
+ if( rc != LDAP_SUCCESS ) return rc;
+
+ switch ( type ) {
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_APPROX:
+ if ( match == 0 ) {
+ (*e_flags)[i][j] = 1;
+ }
+ break;
+
+ case LDAP_FILTER_GE:
+ if ( match >= 0 ) {
+ (*e_flags)[i][j] = 1;
+ }
+ break;
+
+ case LDAP_FILTER_LE:
+ if ( match <= 0 ) {
+ (*e_flags)[i][j] = 1;
+ }
+ break;
+ }
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+test_presence_vrFilter(
+ Operation *op,
+ Attribute *a,
+ AttributeDescription *desc,
+ char ***e_flags )
+{
+ int i, j;
+
+ for ( i=0; a != NULL; a = a->a_next, i++ ) {
+ struct berval *bv;
+
+ if ( !is_ad_subtype( a->a_desc, desc ) ) continue;
+
+ for ( bv = a->a_vals, j = 0; !BER_BVISNULL( bv ); bv++, j++ );
+ memset( (*e_flags)[i], 1, j);
+ }
+
+ return( LDAP_SUCCESS );
+}
+
+static int
+test_substrings_vrFilter(
+ Operation *op,
+ Attribute *a,
+ ValuesReturnFilter *vrf,
+ char ***e_flags )
+{
+ int i, j;
+
+ for ( i=0; a != NULL; a = a->a_next, i++ ) {
+ MatchingRule *mr = a->a_desc->ad_type->sat_substr;
+ struct berval *bv;
+
+ if ( !is_ad_subtype( a->a_desc, vrf->vrf_sub_desc ) ) {
+ continue;
+ }
+
+ if( mr == NULL ) continue;
+
+ bv = a->a_nvals;
+ for ( j = 0; !BER_BVISNULL( bv ); bv++, j++ ) {
+ int rc, match;
+ const char *text;
+
+ rc = value_match( &match, a->a_desc, mr, 0,
+ bv, vrf->vrf_sub, &text );
+
+ if( rc != LDAP_SUCCESS ) return rc;
+
+ if ( match == 0 ) {
+ (*e_flags)[i][j] = 1;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+test_mra_vrFilter(
+ Operation *op,
+ Attribute *a,
+ MatchingRuleAssertion *mra,
+ char ***e_flags )
+{
+ int i, j;
+
+ for ( i = 0; a != NULL; a = a->a_next, i++ ) {
+ struct berval *bv, assertedValue;
+ int normalize_attribute = 0;
+
+ if ( mra->ma_desc ) {
+ if ( !is_ad_subtype( a->a_desc, mra->ma_desc ) ) {
+ continue;
+ }
+ assertedValue = mra->ma_value;
+
+ } else {
+ int rc;
+ const char *text = NULL;
+
+ /* check if matching is appropriate */
+ if ( !mr_usable_with_at( mra->ma_rule, a->a_desc->ad_type ) ) {
+ continue;
+ }
+
+ rc = asserted_value_validate_normalize( a->a_desc, mra->ma_rule,
+ SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &mra->ma_value, &assertedValue, &text, op->o_tmpmemctx );
+
+ if ( rc != LDAP_SUCCESS ) continue;
+ }
+
+ /* check match */
+ if ( mra->ma_rule == a->a_desc->ad_type->sat_equality ) {
+ bv = a->a_nvals;
+
+ } else {
+ bv = a->a_vals;
+ normalize_attribute = 1;
+ }
+
+ for ( j = 0; !BER_BVISNULL( bv ); bv++, j++ ) {
+ int rc, match;
+ const char *text;
+ struct berval nbv = BER_BVNULL;
+
+ if ( normalize_attribute && mra->ma_rule->smr_normalize ) {
+ /* see comment in filterentry.c */
+ if ( mra->ma_rule->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mra->ma_rule->smr_syntax,
+ mra->ma_rule,
+ bv, &nbv, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ {
+ /* FIXME: stop processing? */
+ continue;
+ }
+
+ } else {
+ nbv = *bv;
+ }
+
+ rc = value_match( &match, a->a_desc, mra->ma_rule, 0,
+ &nbv, &assertedValue, &text );
+
+ if ( nbv.bv_val != bv->bv_val ) {
+ op->o_tmpfree( nbv.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( rc != LDAP_SUCCESS ) return rc;
+
+ if ( match == 0 ) {
+ (*e_flags)[i][j] = 1;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/modify.c b/servers/slapd/modify.c
new file mode 100644
index 0000000..0da8953
--- /dev/null
+++ b/servers/slapd/modify.c
@@ -0,0 +1,1099 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+
+int
+do_modify(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval dn = BER_BVNULL;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+#ifdef LDAP_DEBUG
+ Modifications *tmp;
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_modify\n",
+ op->o_log_prefix );
+ /*
+ * Parse the modify request. It looks like this:
+ *
+ * ModifyRequest := [APPLICATION 6] SEQUENCE {
+ * name DistinguishedName,
+ * mods SEQUENCE OF SEQUENCE {
+ * operation ENUMERATED {
+ * add (0),
+ * delete (1),
+ * replace (2)
+ * },
+ * modification SEQUENCE {
+ * type AttributeType,
+ * values SET OF AttributeValue
+ * }
+ * }
+ * }
+ */
+
+ if ( ber_scanf( op->o_ber, "{m" /*}*/, &dn ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "%s do_modify: dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+
+ rs->sr_err = slap_parse_modlist( op, rs, op->o_ber, &op->oq_modify );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: slap_parse_modlist failed err=%d msg=%s\n",
+ op->o_log_prefix, rs->sr_err, rs->sr_text );
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: get_ctrls failed\n",
+ op->o_log_prefix );
+ /* get_ctrls has sent results. Now clean up. */
+ goto cleanup;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn,
+ op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto cleanup;
+ }
+
+ op->orm_no_opattrs = 0;
+
+#ifdef LDAP_DEBUG
+ Debug( LDAP_DEBUG_ARGS, "%s modifications:\n",
+ op->o_log_prefix );
+
+ for ( tmp = op->orm_modlist; tmp != NULL; tmp = tmp->sml_next ) {
+ Debug( LDAP_DEBUG_ARGS, "\t%s: %s\n",
+ tmp->sml_op == LDAP_MOD_ADD ? "add" :
+ (tmp->sml_op == LDAP_MOD_INCREMENT ? "increment" :
+ (tmp->sml_op == LDAP_MOD_DELETE ? "delete" :
+ "replace")), tmp->sml_type.bv_val );
+
+ if ( tmp->sml_values == NULL ) {
+ Debug( LDAP_DEBUG_ARGS, "\t\tno values\n" );
+ } else if ( BER_BVISNULL( &tmp->sml_values[ 0 ] ) ) {
+ Debug( LDAP_DEBUG_ARGS, "\t\tzero values\n" );
+ } else if ( BER_BVISNULL( &tmp->sml_values[ 1 ] ) ) {
+ Debug( LDAP_DEBUG_ARGS, "\t\tone value, length %ld\n",
+ (long) tmp->sml_values[0].bv_len );
+ } else {
+ Debug( LDAP_DEBUG_ARGS, "\t\tmultiple values\n" );
+ }
+ }
+
+ if (LogTest( LDAP_DEBUG_STATS ) ) {
+ char abuf[BUFSIZ/2], *ptr = abuf;
+ int len = 0;
+
+ Debug( LDAP_DEBUG_STATS, "%s MOD dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ for ( tmp = op->orm_modlist; tmp != NULL; tmp = tmp->sml_next ) {
+ if (len + 1 + tmp->sml_type.bv_len > sizeof(abuf)) {
+ Debug( LDAP_DEBUG_STATS, "%s MOD attr=%s\n",
+ op->o_log_prefix, abuf );
+
+ len = 0;
+ ptr = abuf;
+
+ if( 1 + tmp->sml_type.bv_len > sizeof(abuf)) {
+ Debug( LDAP_DEBUG_STATS, "%s MOD attr=%s\n",
+ op->o_log_prefix, tmp->sml_type.bv_val );
+ continue;
+ }
+ }
+ if (len) {
+ *ptr++ = ' ';
+ len++;
+ }
+ ptr = lutil_strcopy(ptr, tmp->sml_type.bv_val);
+ len += tmp->sml_type.bv_len;
+ }
+ if (len) {
+ Debug( LDAP_DEBUG_STATS, "%s MOD attr=%s\n",
+ op->o_log_prefix, abuf );
+ }
+ }
+#endif /* LDAP_DEBUG */
+
+ rs->sr_err = slap_mods_check( op, op->orm_modlist,
+ &rs->sr_text, textbuf, textlen, NULL );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_modify( op, rs );
+ if ( rs->sr_err == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+ if( rs->sr_err == LDAP_TXN_SPECIFY_OKAY ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+cleanup:
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ if ( op->orm_modlist != NULL ) slap_mods_free( op->orm_modlist, 1 );
+
+ return rs->sr_err;
+}
+
+int
+fe_op_modify( Operation *op, SlapReply *rs )
+{
+ BackendDB *op_be, *bd = op->o_bd;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+
+ if ( BER_BVISEMPTY( &op->o_req_ndn ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: root dse!\n",
+ op->o_log_prefix );
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "modify upon the root DSE not supported" );
+ goto cleanup;
+
+ } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modify: subschema subentry!\n",
+ op->o_log_prefix );
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "modification of subschema subentry not supported" );
+ goto cleanup;
+ }
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd;
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ if ( !rs->sr_ref ) {
+ rs->sr_ref = default_referral;
+ }
+
+ if ( rs->sr_ref != NULL ) {
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_ref != default_referral ) {
+ ber_bvarray_free( rs->sr_ref );
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "no global superior knowledge" );
+ }
+ goto cleanup;
+ }
+
+ /* If we've got a glued backend, check the real backend */
+ op_be = op->o_bd;
+ if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ }
+
+ /* check restrictions */
+ if ( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check for referrals */
+ if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ rs->sr_err = slap_mods_obsolete_check( op, op->orm_modlist,
+ &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check for modify/increment support */
+ if ( op->orm_increment && !SLAP_INCREMENT( op->o_bd ) ) {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "modify/increment not supported in context" );
+ goto cleanup;
+ }
+
+ /*
+ * do the modify if 1 && (2 || 3)
+ * 1) there is a modify function implemented in this backend;
+ * 2) this backend is the provider for what it holds;
+ * 3) it's a replica and the dn supplied is the update_ndn.
+ */
+ if ( op->o_bd->be_modify ) {
+ /* do the update here */
+ int repl_user = be_isupdate( op );
+
+ /*
+ * Multimaster slapd does not have to check for replicator dn
+ * because it accepts each modify request
+ */
+ if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user ) {
+ int update = !BER_BVISEMPTY( &op->o_bd->be_update_ndn );
+
+ if ( !update ) {
+ rs->sr_err = slap_mods_no_user_mod_check( op, op->orm_modlist,
+ &rs->sr_text, textbuf, textlen );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+ }
+ if ( op->o_txnSpec ) {
+ txn_preop( op, rs );
+ goto cleanup;
+ }
+ op->o_bd = op_be;
+ op->o_bd->be_modify( op, rs );
+
+ } else { /* send a referral */
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+ if ( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( defref,
+ NULL, &op->o_req_dn,
+ LDAP_SCOPE_DEFAULT );
+ if ( rs->sr_ref == NULL ) {
+ /* FIXME: must duplicate, because
+ * overlays may muck with it */
+ rs->sr_ref = defref;
+ }
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+ if ( rs->sr_ref != defref ) {
+ ber_bvarray_free( rs->sr_ref );
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "shadow context; no update referral" );
+ }
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within namingContext" );
+ }
+
+cleanup:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
+
+/*
+ * Obsolete constraint checking.
+ */
+int
+slap_mods_obsolete_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ if( get_relax( op ) ) return LDAP_SUCCESS;
+
+ for ( ; ml != NULL; ml = ml->sml_next ) {
+ if ( is_at_obsolete( ml->sml_desc->ad_type ) &&
+ (( ml->sml_op != LDAP_MOD_REPLACE &&
+ ml->sml_op != LDAP_MOD_DELETE ) ||
+ ml->sml_values != NULL ))
+ {
+ /*
+ * attribute is obsolete,
+ * only allow replace/delete with no values
+ */
+ snprintf( textbuf, textlen,
+ "%s: attribute is obsolete",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * No-user-modification constraint checking.
+ */
+int
+slap_mods_no_user_mod_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ for ( ; ml != NULL; ml = ml->sml_next ) {
+ if ( !is_at_no_user_mod( ml->sml_desc->ad_type ) ) {
+ continue;
+ }
+
+ if ( ml->sml_flags & SLAP_MOD_INTERNAL ) {
+ continue;
+ }
+
+ if ( get_relax( op ) ) {
+ if ( ml->sml_desc->ad_type->sat_flags & SLAP_AT_MANAGEABLE ) {
+ ml->sml_flags |= SLAP_MOD_MANAGING;
+ continue;
+ }
+
+ /* attribute not manageable */
+ snprintf( textbuf, textlen,
+ "%s: no-user-modification attribute not manageable",
+ ml->sml_type.bv_val );
+
+ } else {
+ /* user modification disallowed */
+ snprintf( textbuf, textlen,
+ "%s: no user modification allowed",
+ ml->sml_type.bv_val );
+ }
+
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+slap_mods_no_repl_user_mod_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ Modifications *mods;
+ Modifications *modp;
+
+ for ( mods = ml; mods != NULL; mods = mods->sml_next ) {
+ assert( mods->sml_op == LDAP_MOD_ADD );
+
+ /* check doesn't already appear */
+ for ( modp = ml; modp != NULL; modp = modp->sml_next ) {
+ if ( mods->sml_desc == modp->sml_desc && mods != modp ) {
+ snprintf( textbuf, textlen,
+ "attribute '%s' provided more than once",
+ mods->sml_desc->ad_cname.bv_val );
+ *text = textbuf;
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Do basic attribute type checking and syntax validation.
+ */
+int slap_mods_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf,
+ size_t textlen,
+ void *ctx )
+{
+ int rc;
+
+ for( ; ml != NULL; ml = ml->sml_next ) {
+ AttributeDescription *ad = NULL;
+
+ /* convert to attribute description */
+ if ( ml->sml_desc == NULL ) {
+ rc = slap_bv2ad( &ml->sml_type, &ml->sml_desc, text );
+ if( rc != LDAP_SUCCESS ) {
+ if ( get_no_schema_check( op )) {
+ rc = slap_bv2undef_ad( &ml->sml_type, &ml->sml_desc,
+ text, 0 );
+ }
+ }
+ if( rc != LDAP_SUCCESS ) {
+ snprintf( textbuf, textlen, "%s: %s",
+ ml->sml_type.bv_val, *text );
+ *text = textbuf;
+ return rc;
+ }
+ }
+
+ ad = ml->sml_desc;
+
+ if( slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && !slap_ad_is_binary( ad ))
+ {
+ /* attribute requires binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: requires ;binary transfer",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ if( !slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && slap_ad_is_binary( ad ))
+ {
+ /* attribute does not require binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: disallows ;binary transfer",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+ if( slap_ad_is_tag_range( ad )) {
+ /* attribute requires binary transfer */
+ snprintf( textbuf, textlen,
+ "%s: inappropriate use of tag range option",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_UNDEFINED_TYPE;
+ }
+
+#if 0
+ if ( is_at_obsolete( ad->ad_type ) &&
+ (( ml->sml_op != LDAP_MOD_REPLACE &&
+ ml->sml_op != LDAP_MOD_DELETE ) ||
+ ml->sml_values != NULL ))
+ {
+ /*
+ * attribute is obsolete,
+ * only allow replace/delete with no values
+ */
+ snprintf( textbuf, textlen,
+ "%s: attribute is obsolete",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+#endif
+
+ if ( ml->sml_op == LDAP_MOD_INCREMENT &&
+#ifdef SLAPD_REAL_SYNTAX
+ !is_at_syntax( ad->ad_type, SLAPD_REAL_SYNTAX ) &&
+#endif
+ !is_at_syntax( ad->ad_type, SLAPD_INTEGER_SYNTAX ) )
+ {
+ /*
+ * attribute values must be INTEGER or REAL
+ */
+ snprintf( textbuf, textlen,
+ "%s: attribute syntax inappropriate for increment",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * check values
+ */
+ if( ml->sml_values != NULL ) {
+ ber_len_t nvals;
+ slap_syntax_validate_func *validate =
+ ad->ad_type->sat_syntax->ssyn_validate;
+ slap_syntax_transform_func *pretty =
+ ad->ad_type->sat_syntax->ssyn_pretty;
+
+ if( !pretty && !validate ) {
+ *text = "no validator for syntax";
+ snprintf( textbuf, textlen,
+ "%s: no validator for syntax %s",
+ ml->sml_type.bv_val,
+ ad->ad_type->sat_syntax->ssyn_oid );
+ *text = textbuf;
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /*
+ * check that each value is valid per syntax
+ * and pretty if appropriate
+ */
+ for ( nvals = 0; !BER_BVISNULL( &ml->sml_values[nvals] ); nvals++ ) {
+ struct berval pval;
+
+ if ( pretty ) {
+ rc = ordered_value_pretty( ad,
+ &ml->sml_values[nvals], &pval, ctx );
+ } else {
+ rc = ordered_value_validate( ad,
+ &ml->sml_values[nvals], ml->sml_op );
+ }
+
+ if( rc != 0 ) {
+ snprintf( textbuf, textlen,
+ "%s: value #%ld invalid per syntax",
+ ml->sml_type.bv_val, (long) nvals );
+ *text = textbuf;
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( pretty ) {
+ ber_memfree_x( ml->sml_values[nvals].bv_val, ctx );
+ ml->sml_values[nvals] = pval;
+ }
+ }
+ ml->sml_values[nvals].bv_len = 0;
+ ml->sml_numvals = nvals;
+
+ /*
+ * a rough single value check... an additional check is needed
+ * to catch add of single value to existing single valued attribute
+ */
+ if ((ml->sml_op == LDAP_MOD_ADD || ml->sml_op == LDAP_MOD_REPLACE)
+ && nvals > 1 && is_at_single_value( ad->ad_type ))
+ {
+ snprintf( textbuf, textlen,
+ "%s: multiple values provided",
+ ml->sml_type.bv_val );
+ *text = textbuf;
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ /* if the type has a normalizer, generate the
+ * normalized values. otherwise leave them NULL.
+ *
+ * this is different from the rule for attributes
+ * in an entry - in an attribute list, the normalized
+ * value is set equal to the non-normalized value
+ * when there is no normalizer.
+ */
+ if( nvals && ad->ad_type->sat_equality &&
+ ad->ad_type->sat_equality->smr_normalize )
+ {
+ ml->sml_nvalues = slap_sl_malloc(
+ (nvals+1)*sizeof(struct berval), ctx );
+
+ for ( nvals = 0; !BER_BVISNULL( &ml->sml_values[nvals] ); nvals++ ) {
+ rc = ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ ad,
+ ad->ad_type->sat_equality,
+ &ml->sml_values[nvals], &ml->sml_nvalues[nvals], ctx );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "<= str2entry NULL (ssyn_normalize %d)\n",
+ rc );
+ snprintf( textbuf, textlen,
+ "%s: value #%ld normalization failed",
+ ml->sml_type.bv_val, (long) nvals );
+ *text = textbuf;
+ BER_BVZERO( &ml->sml_nvalues[nvals] );
+ return rc;
+ }
+ }
+
+ BER_BVZERO( &ml->sml_nvalues[nvals] );
+ }
+
+ /* check for duplicates, but ignore Deletes.
+ */
+ if( nvals > 1 && ml->sml_op != LDAP_MOD_DELETE ) {
+ int i;
+ rc = slap_sort_vals( ml, text, &i, ctx );
+ if ( rc == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ /* value exists already */
+ snprintf( textbuf, textlen,
+ "%s: value #%d provided more than once",
+ ml->sml_desc->ad_cname.bv_val, i );
+ *text = textbuf;
+ }
+ if ( rc )
+ return rc;
+ }
+ } else {
+ ml->sml_numvals = 0;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Sort a set of values. An (Attribute *) may be used interchangeably here
+ * instead of a (Modifications *) structure.
+ *
+ * Uses Quicksort + Insertion sort for small arrays
+ */
+
+int
+slap_sort_vals(
+ Modifications *ml,
+ const char **text,
+ int *dup,
+ void *ctx )
+{
+ AttributeDescription *ad;
+ MatchingRule *mr;
+ int istack[sizeof(int)*16];
+ int i, j, k, l, ir, jstack, match, *ix, itmp, nvals, rc = LDAP_SUCCESS;
+ int is_norm;
+ struct berval a, *cv;
+
+#define SMALL 8
+#define SWAP(a,b,tmp) tmp=(a);(a)=(b);(b)=tmp
+#define COMP(a,b) match=0; rc = ordered_value_match( &match, \
+ ad, mr, SLAP_MR_EQUALITY \
+ | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX \
+ | SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH \
+ | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, \
+ &(a), &(b), text );
+
+#define IX(x) ix[x]
+#define EXCH(x,y) SWAP(ix[x],ix[y],itmp)
+#define SETA(x) itmp = ix[x]; a = cv[itmp]
+#define GETA(x) ix[x] = itmp;
+#define SET(x,y) ix[x] = ix[y]
+
+ ad = ml->sml_desc;
+ nvals = ml->sml_numvals;
+ if ( nvals <= 1 )
+ goto ret;
+
+ /* For Modifications, sml_nvalues is NULL if normalization wasn't needed.
+ * For Attributes, sml_nvalues == sml_values when normalization isn't needed.
+ */
+ if ( ml->sml_nvalues && ml->sml_nvalues != ml->sml_values ) {
+ cv = ml->sml_nvalues;
+ is_norm = 1;
+ } else {
+ cv = ml->sml_values;
+ is_norm = 0;
+ }
+
+ if ( ad == slap_schema.si_ad_objectClass )
+ mr = NULL; /* shortcut matching */
+ else
+ mr = ad->ad_type->sat_equality;
+
+ /* record indices to preserve input ordering */
+ ix = slap_sl_malloc( nvals * sizeof(int), ctx );
+ for (i=0; i<nvals; i++) ix[i] = i;
+
+ ir = nvals-1;
+ l = 0;
+ jstack = 0;
+
+ for(;;) {
+ if (ir - l < SMALL) { /* Insertion sort */
+ match=1;
+ for (j=l+1;j<=ir;j++) {
+ SETA(j);
+ for (i=j-1;i>=0;i--) {
+ COMP(cv[IX(i)], a);
+ if ( match <= 0 )
+ break;
+ SET(i+1,i);
+ }
+ GETA(i+1);
+ if ( match == 0 ) goto done;
+ }
+ if ( jstack == 0 ) break;
+ ir = istack[jstack--];
+ l = istack[jstack--];
+ } else {
+ k = (l + ir) >> 1; /* Choose median of left, center, right */
+ EXCH(k, l+1);
+ COMP( cv[IX(l)], cv[IX(ir)] );
+ if ( match > 0 ) {
+ EXCH(l, ir);
+ } else if ( match == 0 ) {
+ i = ir;
+ break;
+ }
+ COMP( cv[IX(l+1)], cv[IX(ir)] );
+ if ( match > 0 ) {
+ EXCH(l+1, ir);
+ } else if ( match == 0 ) {
+ i = ir;
+ break;
+ }
+ COMP( cv[IX(l)], cv[IX(l+1)] );
+ if ( match > 0 ) {
+ EXCH(l, l+1);
+ } else if ( match == 0 ) {
+ i = l;
+ break;
+ }
+ i = l+1;
+ j = ir;
+ a = cv[IX(i)];
+ for(;;) {
+ do {
+ i++;
+ COMP( cv[IX(i)], a );
+ } while( match < 0 );
+ while( match > 0 ) {
+ j--;
+ COMP( cv[IX(j)], a );
+ }
+ if (j < i) {
+ match = 1;
+ break;
+ }
+ if ( match == 0 ) {
+ i = l+1;
+ break;
+ }
+ EXCH(i,j);
+ }
+ if ( match == 0 )
+ break;
+ EXCH(l+1,j);
+ jstack += 2;
+ if (ir-i+1 > j-l) {
+ istack[jstack] = ir;
+ istack[jstack-1] = i;
+ ir = j;
+ } else {
+ istack[jstack] = j;
+ istack[jstack-1] = l;
+ l = i;
+ }
+ }
+ }
+ done:
+ if ( match == 0 && i >= 0 )
+ *dup = ix[i];
+
+ /* For sorted attributes, put the values in index order */
+ if ( rc == LDAP_SUCCESS && match &&
+ ( ad->ad_type->sat_flags & SLAP_AT_SORTED_VAL )) {
+ BerVarray tmpv = slap_sl_malloc( sizeof( struct berval ) * nvals, ctx );
+ for ( i = 0; i<nvals; i++ )
+ tmpv[i] = cv[ix[i]];
+ for ( i = 0; i<nvals; i++ )
+ cv[i] = tmpv[i];
+ /* Check if the non-normalized array needs to move too */
+ if ( is_norm ) {
+ cv = ml->sml_values;
+ for ( i = 0; i<nvals; i++ )
+ tmpv[i] = cv[ix[i]];
+ for ( i = 0; i<nvals; i++ )
+ cv[i] = tmpv[i];
+ }
+ slap_sl_free( tmpv, ctx );
+ }
+
+ slap_sl_free( ix, ctx );
+
+ if ( rc == LDAP_SUCCESS && match == 0 ) {
+ /* value exists already */
+ assert( i >= 0 );
+ assert( i < nvals );
+ rc = LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ ret:
+ return rc;
+}
+
+/* Enter with bv->bv_len = sizeof buffer, returns with
+ * actual length of string
+ */
+void slap_timestamp( time_t *tm, struct berval *bv )
+{
+ struct tm ltm;
+
+ ldap_pvt_gmtime( tm, &ltm );
+
+ bv->bv_len = lutil_gentime( bv->bv_val, bv->bv_len, &ltm );
+}
+
+/* Called for all modify and modrdn ops. If the current op was replicated
+ * from elsewhere, all of the attrs should already be present.
+ */
+void slap_mods_opattrs(
+ Operation *op,
+ Modifications **modsp,
+ int manage_ctxcsn )
+{
+ struct berval name, timestamp, csn = BER_BVNULL;
+ struct berval nname;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ Modifications *mod, **modtail, *modlast;
+ int gotcsn = 0, gotmname = 0, gotmtime = 0;
+
+ if ( SLAP_LASTMOD( op->o_bd ) && !op->orm_no_opattrs ) {
+ char *ptr;
+ timestamp.bv_val = timebuf;
+ for ( modtail = modsp; *modtail; modtail = &(*modtail)->sml_next ) {
+ if ( (*modtail)->sml_op != LDAP_MOD_ADD &&
+ (*modtail)->sml_op != SLAP_MOD_SOFTADD &&
+ (*modtail)->sml_op != SLAP_MOD_ADD_IF_NOT_PRESENT &&
+ (*modtail)->sml_op != LDAP_MOD_REPLACE )
+ {
+ continue;
+ }
+
+ if ( (*modtail)->sml_desc == slap_schema.si_ad_entryCSN )
+ {
+ csn = (*modtail)->sml_values[0];
+ gotcsn = 1;
+
+ } else if ( (*modtail)->sml_desc == slap_schema.si_ad_modifiersName )
+ {
+ gotmname = 1;
+
+ } else if ( (*modtail)->sml_desc == slap_schema.si_ad_modifyTimestamp )
+ {
+ gotmtime = 1;
+ }
+ }
+
+ if ( BER_BVISEMPTY( &op->o_csn )) {
+ if ( !gotcsn ) {
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof( csnbuf );
+ slap_get_csn( op, &csn, manage_ctxcsn );
+
+ } else {
+ if ( manage_ctxcsn ) {
+ slap_queue_csn( op, &csn );
+ }
+ }
+
+ } else {
+ csn = op->o_csn;
+ }
+
+ ptr = ber_bvchr( &csn, '#' );
+ if ( ptr ) {
+ timestamp.bv_len = STRLENOF("YYYYMMDDHHMMSSZ");
+ AC_MEMCPY( timebuf, csn.bv_val, timestamp.bv_len );
+ timebuf[timestamp.bv_len-1] = 'Z';
+ timebuf[timestamp.bv_len] = '\0';
+
+ } else {
+ time_t now = slap_get_time();
+
+ timestamp.bv_len = sizeof(timebuf);
+
+ slap_timestamp( &now, &timestamp );
+ }
+
+ if ( BER_BVISEMPTY( &op->o_dn ) ) {
+ BER_BVSTR( &name, SLAPD_ANONYMOUS );
+ nname = name;
+
+ } else {
+ name = op->o_dn;
+ nname = op->o_ndn;
+ }
+
+ if ( !gotcsn ) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = SLAP_MOD_INTERNAL;
+ mod->sml_next = NULL;
+ BER_BVZERO( &mod->sml_type );
+ mod->sml_desc = slap_schema.si_ad_entryCSN;
+ mod->sml_numvals = 1;
+ mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_values[0], &csn );
+ BER_BVZERO( &mod->sml_values[1] );
+ assert( !BER_BVISNULL( &mod->sml_values[0] ) );
+ mod->sml_nvalues = NULL;
+ *modtail = mod;
+ modlast = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( !gotmname ) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = SLAP_MOD_INTERNAL;
+ mod->sml_next = NULL;
+ BER_BVZERO( &mod->sml_type );
+ mod->sml_desc = slap_schema.si_ad_modifiersName;
+ mod->sml_numvals = 1;
+ mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_values[0], &name );
+ BER_BVZERO( &mod->sml_values[1] );
+ assert( !BER_BVISNULL( &mod->sml_values[0] ) );
+ mod->sml_nvalues =
+ (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_nvalues[0], &nname );
+ BER_BVZERO( &mod->sml_nvalues[1] );
+ assert( !BER_BVISNULL( &mod->sml_nvalues[0] ) );
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( !gotmtime ) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = SLAP_MOD_INTERNAL;
+ mod->sml_next = NULL;
+ BER_BVZERO( &mod->sml_type );
+ mod->sml_desc = slap_schema.si_ad_modifyTimestamp;
+ mod->sml_numvals = 1;
+ mod->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod->sml_values[0], &timestamp );
+ BER_BVZERO( &mod->sml_values[1] );
+ assert( !BER_BVISNULL( &mod->sml_values[0] ) );
+ mod->sml_nvalues = NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ }
+}
+
+int
+slap_parse_modlist(
+ Operation *op,
+ SlapReply *rs,
+ BerElement *ber,
+ req_modify_s *ms )
+{
+ ber_tag_t tag;
+ ber_len_t len;
+ char *last;
+ Modifications **modtail = &ms->rs_mods.rs_modlist;
+
+ ms->rs_mods.rs_modlist = NULL;
+ ms->rs_increment = 0;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* collect modifications & save for later */
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ ber_int_t mop;
+ Modifications tmp, *mod;
+
+ tmp.sml_nvalues = NULL;
+
+ if ( ber_scanf( ber, "{e{m[W]}}", &mop,
+ &tmp.sml_type, &tmp.sml_values ) == LBER_ERROR )
+ {
+ rs->sr_text = "decoding modlist error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ mod = (Modifications *) ch_malloc( sizeof(Modifications) );
+ mod->sml_op = mop;
+ mod->sml_flags = 0;
+ mod->sml_type = tmp.sml_type;
+ mod->sml_values = tmp.sml_values;
+ mod->sml_nvalues = NULL;
+ mod->sml_desc = NULL;
+ mod->sml_next = NULL;
+ *modtail = mod;
+
+ switch( mop ) {
+ case LDAP_MOD_ADD:
+ if ( mod->sml_values == NULL ) {
+ rs->sr_text = "modify/add operation requires values";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ /* fall through */
+
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ if( op->o_protocol >= LDAP_VERSION3 ) {
+ ms->rs_increment++;
+ if ( mod->sml_values == NULL ) {
+ rs->sr_text = "modify/increment operation requires value";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ if ( !BER_BVISNULL( &mod->sml_values[ 1 ] ) ) {
+ rs->sr_text = "modify/increment operation requires single value";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ break;
+ }
+ /* fall thru */
+
+ default:
+ rs->sr_text = "unrecognized modify operation";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ modtail = &mod->sml_next;
+ }
+ *modtail = NULL;
+
+done:
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ slap_mods_free( ms->rs_mods.rs_modlist, 1 );
+ ms->rs_mods.rs_modlist = NULL;
+ ms->rs_increment = 0;
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/modrdn.c b/servers/slapd/modrdn.c
new file mode 100644
index 0000000..77a20d1
--- /dev/null
+++ b/servers/slapd/modrdn.c
@@ -0,0 +1,550 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright 1999, Juan C. Gomez, All rights reserved.
+ * This software is not subject to any license of Silicon Graphics
+ * Inc. or Purdue University.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * without restriction or fee of any kind as long as this notice
+ * is preserved.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+
+int
+do_modrdn(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ struct berval dn = BER_BVNULL;
+ struct berval newrdn = BER_BVNULL;
+ struct berval newSuperior = BER_BVNULL;
+ ber_int_t deloldrdn;
+
+ struct berval pnewSuperior = BER_BVNULL;
+
+ struct berval nnewSuperior = BER_BVNULL;
+ struct berval dest_pdn, dest_pndn;
+
+ ber_len_t length;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_modrdn\n",
+ op->o_log_prefix );
+ /*
+ * Parse the modrdn request. It looks like this:
+ *
+ * ModifyRDNRequest := SEQUENCE {
+ * entry DistinguishedName,
+ * newrdn RelativeDistinguishedName
+ * deleteoldrdn BOOLEAN,
+ * newSuperior [0] LDAPDN OPTIONAL (v3 Only!)
+ * }
+ */
+
+ if ( ber_scanf( op->o_ber, "{mmb", &dn, &newrdn, &deloldrdn )
+ == LBER_ERROR )
+ {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ return SLAPD_DISCONNECT;
+ }
+
+ /* Check for newSuperior parameter, if present scan it */
+
+ if ( ber_peek_tag( op->o_ber, &length ) == LDAP_TAG_NEWSUPERIOR ) {
+ if ( op->o_protocol < LDAP_VERSION3 ) {
+ /* Connection record indicates v2 but field
+ * newSuperior is present: report error.
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "%s do_modrdn: newSuperior requires LDAPv3\n",
+ op->o_log_prefix );
+
+ send_ldap_discon( op, rs,
+ LDAP_PROTOCOL_ERROR, "newSuperior requires LDAPv3" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+
+ if ( ber_scanf( op->o_ber, "m", &newSuperior )
+ == LBER_ERROR ) {
+
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf(\"m\") failed\n",
+ op->o_log_prefix );
+
+ send_ldap_discon( op, rs,
+ LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+ op->orr_newSup = &pnewSuperior;
+ op->orr_nnewSup = &nnewSuperior;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ "do_modrdn: dn (%s) newrdn (%s) newsuperior (%s)\n",
+ dn.bv_val, newrdn.bv_val,
+ newSuperior.bv_len ? newSuperior.bv_val : "" );
+
+ if ( ber_scanf( op->o_ber, /*{*/ "}") == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: ber_scanf failed\n",
+ op->o_log_prefix );
+ send_ldap_discon( op, rs,
+ LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto cleanup;
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: get_ctrls failed\n",
+ op->o_log_prefix );
+ /* get_ctrls has sent results. Now clean up. */
+ goto cleanup;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &dn, &op->o_req_dn, &op->o_req_ndn, op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid dn (%s)\n",
+ op->o_log_prefix, dn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto cleanup;
+ }
+
+ /* FIXME: should have/use rdnPretty / rdnNormalize routines */
+
+ rs->sr_err = dnPrettyNormal( NULL, &newrdn, &op->orr_newrdn, &op->orr_nnewrdn, op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid newrdn (%s)\n",
+ op->o_log_prefix, newrdn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid new RDN" );
+ goto cleanup;
+ }
+
+ if( rdn_validate( &op->orr_newrdn ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: invalid rdn (%s)\n",
+ op->o_log_prefix, op->orr_newrdn.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid new RDN" );
+ goto cleanup;
+ }
+
+ if( op->orr_newSup ) {
+ rs->sr_err = dnPrettyNormal( NULL, &newSuperior, &pnewSuperior,
+ &nnewSuperior, op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s do_modrdn: invalid newSuperior (%s)\n",
+ op->o_log_prefix, newSuperior.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid newSuperior" );
+ goto cleanup;
+ }
+
+ dest_pdn = pnewSuperior;
+ dest_pndn = nnewSuperior;
+ } else {
+ dnParent( &op->o_req_dn, &dest_pdn );
+ dnParent( &op->o_req_ndn, &dest_pndn );
+ }
+ build_new_dn( &op->orr_newDN, &dest_pdn, &op->orr_newrdn, op->o_tmpmemctx );
+ build_new_dn( &op->orr_nnewDN, &dest_pndn, &op->orr_nnewrdn, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_STATS, "%s MODRDN dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ op->orr_deleteoldrdn = deloldrdn;
+ op->orr_modlist = NULL;
+
+ /* prepare modlist of modifications from old/new RDN */
+ rs->sr_err = slap_modrdn2mods( op, rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_modrdn( op, rs );
+
+ if ( rs->sr_err == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+ if( rs->sr_err == LDAP_TXN_SPECIFY_OKAY ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+cleanup:
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+
+ op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx );
+
+ op->o_tmpfree( op->orr_newDN.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewDN.bv_val, op->o_tmpmemctx );
+
+ if ( op->orr_modlist != NULL )
+ slap_mods_free( op->orr_modlist, 1 );
+
+ if ( !BER_BVISNULL( &pnewSuperior ) ) {
+ op->o_tmpfree( pnewSuperior.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &nnewSuperior ) ) {
+ op->o_tmpfree( nnewSuperior.bv_val, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+int
+fe_op_modrdn( Operation *op, SlapReply *rs )
+{
+ struct berval pdn = BER_BVNULL;
+ BackendDB *op_be, *bd = op->o_bd;
+ ber_slen_t diff;
+
+ if( op->o_req_ndn.bv_len == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: root dse!\n",
+ op->o_log_prefix );
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "cannot rename the root DSE" );
+ goto cleanup;
+
+ } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_modrdn: subschema subentry: %s (%ld)\n",
+ op->o_log_prefix, frontendDB->be_schemandn.bv_val, (long)frontendDB->be_schemandn.bv_len );
+
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "cannot rename subschema subentry" );
+ goto cleanup;
+ }
+
+ diff = (ber_slen_t) op->orr_nnewDN.bv_len - (ber_slen_t) op->o_req_ndn.bv_len;
+ if ( diff > 0 ? dnIsSuffix( &op->orr_nnewDN, &op->o_req_ndn )
+ : diff < 0 && dnIsSuffix( &op->o_req_ndn, &op->orr_nnewDN ) )
+ {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ diff > 0 ? "cannot place an entry below itself"
+ : "cannot place an entry above itself" );
+ goto cleanup;
+ }
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd;
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ if (!rs->sr_ref) rs->sr_ref = default_referral;
+
+ if ( rs->sr_ref != NULL ) {
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != default_referral) ber_bvarray_free( rs->sr_ref );
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "no global superior knowledge" );
+ }
+ goto cleanup;
+ }
+
+ /* If we've got a glued backend, check the real backend */
+ op_be = op->o_bd;
+ if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto cleanup;
+ }
+
+ /* check for referrals */
+ if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ /* check that destination DN is in the same backend as source DN */
+ if ( select_backend( &op->orr_nnewDN, 0 ) != op->o_bd ) {
+ send_ldap_error( op, rs, LDAP_AFFECTS_MULTIPLE_DSAS,
+ "cannot rename between DSAs" );
+ goto cleanup;
+ }
+
+ /*
+ * do the modrdn if 1 && (2 || 3)
+ * 1) there is a modrdn function implemented in this backend;
+ * 2) this backend is the provider for what it holds;
+ * 3) it's a replica and the dn supplied is the update_ndn.
+ */
+ if ( op->o_bd->be_modrdn ) {
+ /* do the update here */
+ int repl_user = be_isupdate( op );
+ if ( !SLAP_SINGLE_SHADOW(op->o_bd) || repl_user )
+ {
+ if ( op->o_txnSpec ) {
+ txn_preop( op, rs );
+ goto cleanup;
+ }
+
+ op->o_bd = op_be;
+ op->o_bd->be_modrdn( op, rs );
+
+ if ( op->o_bd->be_delete ) {
+ struct berval org_req_dn = BER_BVNULL;
+ struct berval org_req_ndn = BER_BVNULL;
+ struct berval org_dn = BER_BVNULL;
+ struct berval org_ndn = BER_BVNULL;
+ int org_managedsait;
+
+ org_req_dn = op->o_req_dn;
+ org_req_ndn = op->o_req_ndn;
+ org_dn = op->o_dn;
+ org_ndn = op->o_ndn;
+ org_managedsait = get_manageDSAit( op );
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+
+ while ( rs->sr_err == LDAP_SUCCESS &&
+ op->o_delete_glue_parent ) {
+ op->o_delete_glue_parent = 0;
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn )) {
+ slap_callback cb = { NULL };
+ cb.sc_response = slap_null_cb;
+ dnParent( &op->o_req_ndn, &pdn );
+ op->o_req_dn = pdn;
+ op->o_req_ndn = pdn;
+ op->o_callback = &cb;
+ op->o_bd->be_delete( op, rs );
+ } else {
+ break;
+ }
+ }
+ op->o_managedsait = org_managedsait;
+ op->o_dn = org_dn;
+ op->o_ndn = org_ndn;
+ op->o_req_dn = org_req_dn;
+ op->o_req_ndn = org_req_ndn;
+ op->o_delete_glue_parent = 0;
+ }
+
+ } else {
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if ( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( defref,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+ if (!rs->sr_ref) rs->sr_ref = defref;
+
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref );
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "shadow context; no update referral" );
+ }
+ }
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within namingContext" );
+ }
+
+cleanup:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
+
+/* extracted from slap_modrdn2mods() */
+static int
+mod_op_add_val(
+ Operation *op,
+ AttributeDescription * const desc,
+ struct berval * const val,
+ short const sm_op )
+{
+ int rv = LDAP_SUCCESS;
+ Modifications *mod_tmp;
+ mod_tmp = ( Modifications * )ch_malloc( sizeof( Modifications ) );
+ mod_tmp->sml_desc = desc;
+ BER_BVZERO( &mod_tmp->sml_type );
+ mod_tmp->sml_numvals = 1;
+ mod_tmp->sml_values = ( BerVarray )ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mod_tmp->sml_values[0], val );
+ mod_tmp->sml_values[1].bv_val = NULL;
+ if( desc->ad_type->sat_equality && desc->ad_type->sat_equality->smr_normalize) {
+ mod_tmp->sml_nvalues = ( BerVarray )ch_malloc( 2 * sizeof( struct berval ) );
+ rv = desc->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_EQUALITY|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ &mod_tmp->sml_values[0],
+ &mod_tmp->sml_nvalues[0], NULL );
+ if (rv != LDAP_SUCCESS) {
+ ch_free(mod_tmp->sml_nvalues);
+ ch_free(mod_tmp->sml_values[0].bv_val);
+ ch_free(mod_tmp->sml_values);
+ ch_free(mod_tmp);
+ goto done;
+ }
+ mod_tmp->sml_nvalues[1].bv_val = NULL;
+ } else {
+ mod_tmp->sml_nvalues = NULL;
+ }
+ mod_tmp->sml_op = sm_op;
+ mod_tmp->sml_flags = 0;
+ mod_tmp->sml_next = op->orr_modlist;
+ op->orr_modlist = mod_tmp;
+done:
+ return rv;
+}
+
+int
+slap_modrdn2mods(
+ Operation *op,
+ SlapReply *rs )
+{
+ int a_cnt, d_cnt;
+ LDAPRDN old_rdn = NULL;
+ LDAPRDN new_rdn = NULL;
+
+ assert( !BER_BVISEMPTY( &op->oq_modrdn.rs_newrdn ) );
+
+ /* if requestDN is empty, silently reset deleteOldRDN */
+ if ( BER_BVISEMPTY( &op->o_req_dn ) ) op->orr_deleteoldrdn = 0;
+
+ if ( ldap_bv2rdn_x( &op->oq_modrdn.rs_newrdn, &new_rdn,
+ (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s slap_modrdn2mods: can't figure out "
+ "type(s)/value(s) of newrdn\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_INVALID_DN_SYNTAX;
+ rs->sr_text = "unknown type(s)/value(s) used in RDN";
+ goto done;
+ }
+
+ if ( op->oq_modrdn.rs_deleteoldrdn ) {
+ if ( ldap_bv2rdn_x( &op->o_req_dn, &old_rdn,
+ (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s slap_modrdn2mods: can't figure out "
+ "type(s)/value(s) of oldrdn\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "cannot parse RDN from old DN";
+ goto done;
+ }
+ }
+ rs->sr_text = NULL;
+
+ /* Add new attribute values to the entry */
+ for ( a_cnt = 0; new_rdn[a_cnt]; a_cnt++ ) {
+ AttributeDescription *desc = NULL;
+
+ rs->sr_err = slap_bv2ad( &new_rdn[a_cnt]->la_attr, &desc, &rs->sr_text );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s slap_modrdn2mods: %s: %s (new)\n",
+ op->o_log_prefix,
+ rs->sr_text,
+ new_rdn[ a_cnt ]->la_attr.bv_val );
+ goto done;
+ }
+
+ if ( !desc->ad_type->sat_equality ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s slap_modrdn2mods: %s: %s (new)\n",
+ op->o_log_prefix,
+ rs->sr_text,
+ new_rdn[ a_cnt ]->la_attr.bv_val );
+ rs->sr_text = "naming attribute has no equality matching rule";
+ rs->sr_err = LDAP_NAMING_VIOLATION;
+ goto done;
+ }
+
+ /* Apply modification */
+ rs->sr_err = mod_op_add_val( op, desc, &new_rdn[a_cnt]->la_value, SLAP_MOD_SOFTADD );
+ if (rs->sr_err != LDAP_SUCCESS)
+ goto done;
+ }
+
+ /* Remove old rdn value if required */
+ if ( op->orr_deleteoldrdn ) {
+ for ( d_cnt = 0; old_rdn[d_cnt]; d_cnt++ ) {
+ AttributeDescription *desc = NULL;
+
+ rs->sr_err = slap_bv2ad( &old_rdn[d_cnt]->la_attr, &desc, &rs->sr_text );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s slap_modrdn2mods: %s: %s (old)\n",
+ op->o_log_prefix,
+ rs->sr_text,
+ old_rdn[d_cnt]->la_attr.bv_val );
+ goto done;
+ }
+
+ /* Apply modification */
+ rs->sr_err = mod_op_add_val( op, desc, &old_rdn[d_cnt]->la_value, LDAP_MOD_DELETE );
+ if (rs->sr_err != LDAP_SUCCESS)
+ goto done;
+ }
+ }
+
+done:
+
+ /* LDAP v2 supporting correct attribute handling. */
+ if ( rs->sr_err != LDAP_SUCCESS && op->orr_modlist != NULL ) {
+ slap_mods_free( op->orr_modlist, 1 );
+ op->orr_modlist = NULL;
+ }
+
+ if ( new_rdn != NULL ) {
+ ldap_rdnfree_x( new_rdn, op->o_tmpmemctx );
+ }
+ if ( old_rdn != NULL ) {
+ ldap_rdnfree_x( old_rdn, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/mods.c b/servers/slapd/mods.c
new file mode 100644
index 0000000..989274f
--- /dev/null
+++ b/servers/slapd/mods.c
@@ -0,0 +1,487 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+int
+modify_add_values(
+ Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ int rc;
+ const char *op;
+ Attribute *a;
+ Modification pmod = *mod;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ op = "add";
+ break;
+ case LDAP_MOD_REPLACE:
+ op = "replace";
+ break;
+ default:
+ op = "?";
+ assert( 0 );
+ }
+
+ /* FIXME: Catch old code that doesn't set sm_numvals.
+ */
+ if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) {
+ unsigned i;
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ );
+ assert( mod->sm_numvals == i );
+ }
+
+ /* check if values to add exist in attribute */
+ a = attr_find( e->e_attrs, mod->sm_desc );
+ if ( a != NULL ) {
+ MatchingRule *mr;
+ struct berval *cvals;
+ int rc;
+ unsigned i, p, flags;
+
+ mr = mod->sm_desc->ad_type->sat_equality;
+ if( mr == NULL || !mr->smr_match ) {
+ /* do not allow add of additional attribute
+ if no equality rule exists */
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/%s: %s: no equality matching rule",
+ op, mod->sm_desc->ad_cname.bv_val );
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if ( permissive ) {
+ i = mod->sm_numvals;
+ pmod.sm_values = (BerVarray)ch_malloc(
+ (i + 1) * sizeof( struct berval ));
+ if ( pmod.sm_nvalues != NULL ) {
+ pmod.sm_nvalues = (BerVarray)ch_malloc(
+ (i + 1) * sizeof( struct berval ));
+ }
+ }
+
+ /* no normalization is done in this routine nor
+ * in the matching routines called by this routine.
+ * values are now normalized once on input to the
+ * server (whether from LDAP or from the underlying
+ * database).
+ */
+ if ( a->a_desc == slap_schema.si_ad_objectClass ) {
+ /* Needed by ITS#5517 */
+ flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX;
+
+ } else {
+ flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX;
+ }
+ if ( mod->sm_nvalues ) {
+ flags |= SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH;
+ cvals = mod->sm_nvalues;
+ } else {
+ cvals = mod->sm_values;
+ }
+ for ( p = i = 0; i < mod->sm_numvals; i++ ) {
+ unsigned slot;
+
+ rc = attr_valfind( a, flags, &cvals[i], &slot, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( !permissive ) {
+ /* value already exists */
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/%s: %s: value #%u already exists",
+ op, mod->sm_desc->ad_cname.bv_val, i );
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+ } else if ( rc != LDAP_NO_SUCH_ATTRIBUTE ) {
+ return rc;
+ }
+
+ if ( permissive && rc ) {
+ if ( pmod.sm_nvalues ) {
+ pmod.sm_nvalues[p] = mod->sm_nvalues[i];
+ }
+ pmod.sm_values[p++] = mod->sm_values[i];
+ }
+ }
+
+ if ( permissive ) {
+ if ( p == 0 ) {
+ /* all new values match exist */
+ ch_free( pmod.sm_values );
+ if ( pmod.sm_nvalues ) ch_free( pmod.sm_nvalues );
+ return LDAP_SUCCESS;
+ }
+
+ BER_BVZERO( &pmod.sm_values[p] );
+ if ( pmod.sm_nvalues ) {
+ BER_BVZERO( &pmod.sm_nvalues[p] );
+ }
+ }
+ }
+
+ /* no - add them */
+ if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) {
+ rc = ordered_value_add( e, mod->sm_desc, a,
+ pmod.sm_values, pmod.sm_nvalues );
+ } else {
+ rc = attr_merge( e, mod->sm_desc, pmod.sm_values, pmod.sm_nvalues );
+ }
+
+ if ( a != NULL && permissive ) {
+ ch_free( pmod.sm_values );
+ if ( pmod.sm_nvalues ) ch_free( pmod.sm_nvalues );
+ }
+
+ if ( rc != 0 ) {
+ /* this should return result of attr_merge */
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/%s: %s: merge error (%d)",
+ op, mod->sm_desc->ad_cname.bv_val, rc );
+ return LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+modify_delete_values(
+ Entry *e,
+ Modification *m,
+ int perm,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ return modify_delete_vindex( e, m, perm, text, textbuf, textlen, NULL );
+}
+
+int
+modify_delete_vindex(
+ Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text,
+ char *textbuf, size_t textlen,
+ int *idx )
+{
+ Attribute *a;
+ MatchingRule *mr = mod->sm_desc->ad_type->sat_equality;
+ struct berval *cvals;
+ int *id2 = NULL;
+ int rc = 0;
+ unsigned i, j, flags;
+ char dummy = '\0';
+
+ /*
+ * If permissive is set, then the non-existence of an
+ * attribute is not treated as an error.
+ */
+
+ /* delete the entire attribute */
+ if ( mod->sm_values == NULL ) {
+ rc = attr_delete( &e->e_attrs, mod->sm_desc );
+
+ if( permissive ) {
+ rc = LDAP_SUCCESS;
+ } else if( rc != LDAP_SUCCESS ) {
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: no such attribute",
+ mod->sm_desc->ad_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ return rc;
+ }
+
+ /* FIXME: Catch old code that doesn't set sm_numvals.
+ */
+ if ( !BER_BVISNULL( &mod->sm_values[mod->sm_numvals] )) {
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ );
+ assert( mod->sm_numvals == i );
+ }
+ if ( !idx ) {
+ id2 = ch_malloc( mod->sm_numvals * sizeof( int ));
+ idx = id2;
+ }
+
+ if( mr == NULL || !mr->smr_match ) {
+ /* disallow specific attributes from being deleted if
+ no equality rule */
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: no equality matching rule",
+ mod->sm_desc->ad_cname.bv_val );
+ rc = LDAP_INAPPROPRIATE_MATCHING;
+ goto return_result;
+ }
+
+ /* delete specific values - find the attribute first */
+ if ( (a = attr_find( e->e_attrs, mod->sm_desc )) == NULL ) {
+ if( permissive ) {
+ rc = LDAP_SUCCESS;
+ goto return_result;
+ }
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: no such attribute",
+ mod->sm_desc->ad_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_result;
+ }
+
+ if ( a->a_desc == slap_schema.si_ad_objectClass ) {
+ /* Needed by ITS#5517,ITS#5963 */
+ flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX;
+
+ } else {
+ flags = SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX;
+ }
+ if ( mod->sm_nvalues ) {
+ flags |= SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
+ | SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH;
+ cvals = mod->sm_nvalues;
+ } else {
+ cvals = mod->sm_values;
+ }
+
+ /* Locate values to delete */
+ for ( i = 0; !BER_BVISNULL( &mod->sm_values[i] ); i++ ) {
+ unsigned sort;
+ rc = attr_valfind( a, flags, &cvals[i], &sort, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ idx[i] = sort;
+ } else if ( rc == LDAP_NO_SUCH_ATTRIBUTE ) {
+ if ( permissive ) {
+ idx[i] = -1;
+ continue;
+ }
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: no such value",
+ mod->sm_desc->ad_cname.bv_val );
+ goto return_result;
+ } else {
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: matching rule failed",
+ mod->sm_desc->ad_cname.bv_val );
+ goto return_result;
+ }
+ }
+
+ /* Delete the values */
+ for ( i = 0; i < mod->sm_numvals; i++ ) {
+ /* Skip permissive values that weren't found */
+ if ( idx[i] < 0 )
+ continue;
+ /* Skip duplicate delete specs */
+ if ( a->a_vals[idx[i]].bv_val == &dummy )
+ continue;
+ /* delete value and mark it as gone */
+ free( a->a_vals[idx[i]].bv_val );
+ a->a_vals[idx[i]].bv_val = &dummy;
+ if( a->a_nvals != a->a_vals ) {
+ free( a->a_nvals[idx[i]].bv_val );
+ a->a_nvals[idx[i]].bv_val = &dummy;
+ }
+ a->a_numvals--;
+ }
+
+ /* compact array skipping dummies */
+ for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) {
+ /* skip dummies */
+ if( a->a_vals[i].bv_val == &dummy ) {
+ assert( a->a_nvals[i].bv_val == &dummy );
+ continue;
+ }
+ if ( j != i ) {
+ a->a_vals[ j ] = a->a_vals[ i ];
+ if (a->a_nvals != a->a_vals) {
+ a->a_nvals[ j ] = a->a_nvals[ i ];
+ }
+ }
+ j++;
+ }
+
+ BER_BVZERO( &a->a_vals[j] );
+ if (a->a_nvals != a->a_vals) {
+ BER_BVZERO( &a->a_nvals[j] );
+ }
+
+ /* if no values remain, delete the entire attribute */
+ if ( !a->a_numvals ) {
+ if ( attr_delete( &e->e_attrs, mod->sm_desc ) ) {
+ /* Can never happen */
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/delete: %s: no such attribute",
+ mod->sm_desc->ad_cname.bv_val );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ } else if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED_VAL ) {
+ /* For an ordered attribute, renumber the value indices */
+ ordered_value_sort( a, 1 );
+ }
+return_result:
+ if ( id2 )
+ ch_free( id2 );
+ return rc;
+}
+
+int
+modify_replace_values(
+ Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ (void) attr_delete( &e->e_attrs, mod->sm_desc );
+
+ if ( mod->sm_values ) {
+ return modify_add_values( e, mod, permissive, text, textbuf, textlen );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+modify_increment_values(
+ Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text,
+ char *textbuf, size_t textlen )
+{
+ Attribute *a;
+ const char *syn_oid;
+
+ a = attr_find( e->e_attrs, mod->sm_desc );
+ if( a == NULL ) {
+ if ( permissive ) {
+ Modification modReplace = *mod;
+
+ modReplace.sm_op = LDAP_MOD_REPLACE;
+
+ return modify_add_values(e, &modReplace, permissive, text, textbuf, textlen);
+ } else {
+ *text = textbuf;
+ snprintf( textbuf, textlen,
+ "modify/increment: %s: no such attribute",
+ mod->sm_desc->ad_cname.bv_val );
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ syn_oid = at_syntax( a->a_desc->ad_type );
+ if ( syn_oid && !strcmp( syn_oid, SLAPD_INTEGER_SYNTAX )) {
+ int i;
+ char str[sizeof(long)*3 + 2]; /* overly long */
+ long incr;
+
+ if ( lutil_atol( &incr, mod->sm_values[0].bv_val ) != 0 ) {
+ *text = "modify/increment: invalid syntax of increment";
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* treat zero and errors as a no-op */
+ if( incr == 0 ) {
+ return LDAP_SUCCESS;
+ }
+
+ for( i = 0; !BER_BVISNULL( &a->a_nvals[i] ); i++ ) {
+ char *tmp;
+ long value;
+ size_t strln;
+ if ( lutil_atol( &value, a->a_nvals[i].bv_val ) != 0 ) {
+ *text = "modify/increment: invalid syntax of original value";
+ return LDAP_INVALID_SYNTAX;
+ }
+ strln = snprintf( str, sizeof(str), "%ld", value+incr );
+
+ tmp = SLAP_REALLOC( a->a_nvals[i].bv_val, strln+1 );
+ if( tmp == NULL ) {
+ *text = "modify/increment: reallocation error";
+ return LDAP_OTHER;
+ }
+ a->a_nvals[i].bv_val = tmp;
+ a->a_nvals[i].bv_len = strln;
+
+ AC_MEMCPY( a->a_nvals[i].bv_val, str, strln+1 );
+ }
+
+ } else {
+ snprintf( textbuf, textlen,
+ "modify/increment: %s: increment not supported for value syntax %s",
+ mod->sm_desc->ad_cname.bv_val,
+ syn_oid ? syn_oid : "(NULL)" );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+void
+slap_mod_free(
+ Modification *mod,
+ int freeit )
+{
+ if ( mod->sm_values != NULL ) ber_bvarray_free( mod->sm_values );
+ mod->sm_values = NULL;
+
+ if ( mod->sm_nvalues != NULL ) ber_bvarray_free( mod->sm_nvalues );
+ mod->sm_nvalues = NULL;
+
+ if( freeit ) free( mod );
+}
+
+void
+slap_mods_free(
+ Modifications *ml,
+ int freevals )
+{
+ Modifications *next;
+
+ for ( ; ml != NULL; ml = next ) {
+ next = ml->sml_next;
+
+ if ( freevals )
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+}
+
diff --git a/servers/slapd/module.c b/servers/slapd/module.c
new file mode 100644
index 0000000..048ce8d
--- /dev/null
+++ b/servers/slapd/module.c
@@ -0,0 +1,367 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#include <stdio.h>
+#include "slap.h"
+
+#ifdef SLAPD_MODULES
+
+#include <ltdl.h>
+
+typedef int (*MODULE_INIT_FN)(
+ int argc,
+ char *argv[]);
+typedef int (*MODULE_LOAD_FN)(
+ const void *module,
+ const char *filename);
+typedef int (*MODULE_TERM_FN)(void);
+
+
+struct module_regtable_t {
+ char *type;
+ MODULE_LOAD_FN proc;
+} module_regtable[] = {
+ { "null", load_null_module },
+#ifdef SLAPD_EXTERNAL_EXTENSIONS
+ { "extension", load_extop_module },
+#endif
+ { NULL, NULL }
+};
+
+typedef struct module_loaded_t {
+ struct module_loaded_t *next;
+ lt_dlhandle lib;
+ char name[1];
+} module_loaded_t;
+
+module_loaded_t *module_list = NULL;
+
+static int module_int_unload (module_loaded_t *module);
+
+#ifdef HAVE_EBCDIC
+static char ebuf[BUFSIZ];
+#endif
+
+int module_init (void)
+{
+ if (lt_dlinit()) {
+ const char *error = lt_dlerror();
+#ifdef HAVE_EBCDIC
+ strcpy( ebuf, error );
+ __etoa( ebuf );
+ error = ebuf;
+#endif
+ Debug(LDAP_DEBUG_ANY, "lt_dlinit failed: %s\n", error );
+
+ return -1;
+ }
+
+ return module_path( LDAP_MODULEDIR );
+}
+
+int module_kill (void)
+{
+ /* unload all modules before shutdown */
+ while (module_list != NULL) {
+ module_int_unload(module_list);
+ }
+
+ if (lt_dlexit()) {
+ const char *error = lt_dlerror();
+#ifdef HAVE_EBCDIC
+ strcpy( ebuf, error );
+ __etoa( ebuf );
+ error = ebuf;
+#endif
+ Debug(LDAP_DEBUG_ANY, "lt_dlexit failed: %s\n", error );
+
+ return -1;
+ }
+ return 0;
+}
+
+void * module_handle( const char *file_name )
+{
+ module_loaded_t *module;
+
+ for ( module = module_list; module; module= module->next ) {
+ if ( !strcmp( module->name, file_name )) {
+ return module;
+ }
+ }
+ return NULL;
+}
+
+int module_unload( const char *file_name )
+{
+ module_loaded_t *module;
+
+ module = module_handle( file_name );
+ if ( module ) {
+ module_int_unload( module );
+ return 0;
+ }
+ return -1; /* not found */
+}
+
+int module_load(const char* file_name, int argc, char *argv[])
+{
+ module_loaded_t *module;
+ const char *error;
+ int rc;
+ MODULE_INIT_FN initialize;
+#ifdef HAVE_EBCDIC
+#define file ebuf
+#else
+#define file file_name
+#endif
+
+ module = module_handle( file_name );
+ if ( module ) {
+ Debug( LDAP_DEBUG_ANY, "module_load: (%s) already loaded\n",
+ file_name );
+ return -1;
+ }
+
+ /* If loading a backend, see if we already have it */
+ if ( !strncasecmp( file_name, "back_", 5 )) {
+ char *name = (char *)file_name + 5;
+ char *dot = strchr( name, '.');
+ if (dot) *dot = '\0';
+ rc = backend_info( name ) != NULL;
+ if (dot) *dot = '.';
+ if ( rc ) {
+ Debug( LDAP_DEBUG_CONFIG, "module_load: (%s) already present (static)\n",
+ file_name );
+ return 0;
+ }
+ } else {
+ /* check for overlays too */
+ char *dot = strchr( file_name, '.' );
+ if ( dot ) *dot = '\0';
+ rc = overlay_find( file_name ) != NULL;
+ if ( dot ) *dot = '.';
+ if ( rc ) {
+ Debug( LDAP_DEBUG_CONFIG, "module_load: (%s) already present (static)\n",
+ file_name );
+ return 0;
+ }
+ }
+
+ module = (module_loaded_t *)ch_calloc(1, sizeof(module_loaded_t) +
+ strlen(file_name));
+ if (module == NULL) {
+ Debug(LDAP_DEBUG_ANY, "module_load failed: (%s) out of memory\n", file_name );
+
+ return -1;
+ }
+ strcpy( module->name, file_name );
+
+#ifdef HAVE_EBCDIC
+ strcpy( file, file_name );
+ __atoe( file );
+#endif
+ /*
+ * The result of lt_dlerror(), when called, must be cached prior
+ * to calling Debug. This is because Debug is a macro that expands
+ * into multiple function calls.
+ */
+ if ((module->lib = lt_dlopenext(file)) == NULL) {
+ error = lt_dlerror();
+#ifdef HAVE_EBCDIC
+ strcpy( ebuf, error );
+ __etoa( ebuf );
+ error = ebuf;
+#endif
+ Debug(LDAP_DEBUG_ANY, "lt_dlopenext failed: (%s) %s\n", file_name,
+ error );
+
+ ch_free(module);
+ return -1;
+ }
+
+ Debug(LDAP_DEBUG_CONFIG, "loaded module %s\n", file_name );
+
+
+#ifdef HAVE_EBCDIC
+#pragma convlit(suspend)
+#endif
+ if ((initialize = lt_dlsym(module->lib, "init_module")) == NULL) {
+#ifdef HAVE_EBCDIC
+#pragma convlit(resume)
+#endif
+ Debug(LDAP_DEBUG_CONFIG, "module %s: no init_module() function found\n",
+ file_name );
+
+ lt_dlclose(module->lib);
+ ch_free(module);
+ return -1;
+ }
+
+ /* The imported init_module() routine passes back the type of
+ * module (i.e., which part of slapd it should be hooked into)
+ * or -1 for error. If it passes back 0, then you get the
+ * old behavior (i.e., the library is loaded and not hooked
+ * into anything).
+ *
+ * It might be better if the conf file could specify the type
+ * of module. That way, a single module could support multiple
+ * type of hooks. This could be done by using something like:
+ *
+ * moduleload extension /usr/local/openldap/whatever.so
+ *
+ * then we'd search through module_regtable for a matching
+ * module type, and hook in there.
+ */
+ rc = initialize(argc, argv);
+ if (rc == -1) {
+ Debug(LDAP_DEBUG_CONFIG, "module %s: init_module() failed\n",
+ file_name );
+
+ lt_dlclose(module->lib);
+ ch_free(module);
+ return rc;
+ }
+
+ if (rc >= (int)(sizeof(module_regtable) / sizeof(struct module_regtable_t))
+ || module_regtable[rc].proc == NULL)
+ {
+ Debug(LDAP_DEBUG_CONFIG, "module %s: unknown registration type (%d)\n",
+ file_name, rc );
+
+ module_int_unload(module);
+ return -1;
+ }
+
+ rc = (module_regtable[rc].proc)(module, file_name);
+ if (rc != 0) {
+ Debug(LDAP_DEBUG_CONFIG, "module %s: %s module could not be registered\n",
+ file_name, module_regtable[rc].type );
+
+ module_int_unload(module);
+ return rc;
+ }
+
+ module->next = module_list;
+ module_list = module;
+
+ Debug(LDAP_DEBUG_CONFIG, "module %s: %s module registered\n",
+ file_name, module_regtable[rc].type );
+
+ return 0;
+}
+
+int module_path(const char *path)
+{
+#ifdef HAVE_EBCDIC
+ strcpy(ebuf, path);
+ __atoe(ebuf);
+ path = ebuf;
+#endif
+ return lt_dlsetsearchpath( path );
+}
+
+void *module_resolve (const void *module, const char *name)
+{
+#ifdef HAVE_EBCDIC
+ strcpy(ebuf, name);
+ __atoe(ebuf);
+ name = ebuf;
+#endif
+ if (module == NULL || name == NULL)
+ return(NULL);
+ return(lt_dlsym(((module_loaded_t *)module)->lib, name));
+}
+
+static int module_int_unload (module_loaded_t *module)
+{
+ module_loaded_t *mod;
+ MODULE_TERM_FN terminate;
+
+ if (module != NULL) {
+ /* remove module from tracking list */
+ if (module_list == module) {
+ module_list = module->next;
+ } else {
+ for (mod = module_list; mod; mod = mod->next) {
+ if (mod->next == module) {
+ mod->next = module->next;
+ break;
+ }
+ }
+ }
+
+ /* call module's terminate routine, if present */
+#ifdef HAVE_EBCDIC
+#pragma convlit(suspend)
+#endif
+ if ((terminate = lt_dlsym(module->lib, "term_module"))) {
+#ifdef HAVE_EBCDIC
+#pragma convlit(resume)
+#endif
+ terminate();
+ }
+
+ /* close the library and free the memory */
+ lt_dlclose(module->lib);
+ ch_free(module);
+ }
+ return 0;
+}
+
+int load_null_module (const void *module, const char *file_name)
+{
+ return 0;
+}
+
+#ifdef SLAPD_EXTERNAL_EXTENSIONS
+int
+load_extop_module (
+ const void *module,
+ const char *file_name
+)
+{
+ SLAP_EXTOP_MAIN_FN *ext_main;
+ SLAP_EXTOP_GETOID_FN *ext_getoid;
+ struct berval oid;
+ int rc;
+
+ ext_main = (SLAP_EXTOP_MAIN_FN *)module_resolve(module, "ext_main");
+ if (ext_main == NULL) {
+ return(-1);
+ }
+
+ ext_getoid = module_resolve(module, "ext_getoid");
+ if (ext_getoid == NULL) {
+ return(-1);
+ }
+
+ rc = (ext_getoid)(0, &oid, 256);
+ if (rc != 0) {
+ return(rc);
+ }
+ if (oid.bv_val == NULL || oid.bv_len == 0) {
+ return(-1);
+ }
+
+ /* FIXME: this is broken, and no longer needed,
+ * as a module can call load_extop() itself... */
+ rc = load_extop( &oid, ext_main );
+ return rc;
+}
+#endif /* SLAPD_EXTERNAL_EXTENSIONS */
+#endif /* SLAPD_MODULES */
+
diff --git a/servers/slapd/mr.c b/servers/slapd/mr.c
new file mode 100644
index 0000000..8170345
--- /dev/null
+++ b/servers/slapd/mr.c
@@ -0,0 +1,549 @@
+/* mr.c - routines to manage matching rule definitions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+struct mindexrec {
+ struct berval mir_name;
+ MatchingRule *mir_mr;
+};
+
+static Avlnode *mr_index = NULL;
+static LDAP_SLIST_HEAD(MRList, MatchingRule) mr_list
+ = LDAP_SLIST_HEAD_INITIALIZER(&mr_list);
+static LDAP_SLIST_HEAD(MRUList, MatchingRuleUse) mru_list
+ = LDAP_SLIST_HEAD_INITIALIZER(&mru_list);
+
+static int
+mr_index_cmp(
+ const void *v_mir1,
+ const void *v_mir2
+)
+{
+ const struct mindexrec *mir1 = v_mir1;
+ const struct mindexrec *mir2 = v_mir2;
+ int i = mir1->mir_name.bv_len - mir2->mir_name.bv_len;
+ if (i) return i;
+ return (strcasecmp( mir1->mir_name.bv_val, mir2->mir_name.bv_val ));
+}
+
+static int
+mr_index_name_cmp(
+ const void *v_name,
+ const void *v_mir
+)
+{
+ const struct berval *name = v_name;
+ const struct mindexrec *mir = v_mir;
+ int i = name->bv_len - mir->mir_name.bv_len;
+ if (i) return i;
+ return (strncasecmp( name->bv_val, mir->mir_name.bv_val, name->bv_len ));
+}
+
+MatchingRule *
+mr_find( const char *mrname )
+{
+ struct berval bv;
+
+ bv.bv_val = (char *)mrname;
+ bv.bv_len = strlen( mrname );
+ return mr_bvfind( &bv );
+}
+
+MatchingRule *
+mr_bvfind( struct berval *mrname )
+{
+ struct mindexrec *mir = NULL;
+
+ if ( (mir = ldap_avl_find( mr_index, mrname, mr_index_name_cmp )) != NULL ) {
+ return( mir->mir_mr );
+ }
+ return( NULL );
+}
+
+void
+mr_destroy( void )
+{
+ MatchingRule *m;
+
+ ldap_avl_free(mr_index, ldap_memfree);
+ while( !LDAP_SLIST_EMPTY(&mr_list) ) {
+ m = LDAP_SLIST_FIRST(&mr_list);
+ LDAP_SLIST_REMOVE_HEAD(&mr_list, smr_next);
+ ch_free( m->smr_str.bv_val );
+ ch_free( m->smr_compat_syntaxes );
+ ldap_matchingrule_free((LDAPMatchingRule *)m);
+ }
+}
+
+static int
+mr_insert(
+ MatchingRule *smr,
+ const char **err
+)
+{
+ struct mindexrec *mir;
+ char **names;
+
+ LDAP_SLIST_NEXT( smr, smr_next ) = NULL;
+ LDAP_SLIST_INSERT_HEAD(&mr_list, smr, smr_next);
+
+ if ( smr->smr_oid ) {
+ mir = (struct mindexrec *)
+ ch_calloc( 1, sizeof(struct mindexrec) );
+ mir->mir_name.bv_val = smr->smr_oid;
+ mir->mir_name.bv_len = strlen( smr->smr_oid );
+ mir->mir_mr = smr;
+ if ( ldap_avl_insert( &mr_index, (caddr_t) mir,
+ mr_index_cmp, ldap_avl_dup_error ) ) {
+ *err = smr->smr_oid;
+ ldap_memfree(mir);
+ return SLAP_SCHERR_MR_DUP;
+ }
+ /* FIX: temporal consistency check */
+ mr_bvfind(&mir->mir_name);
+ }
+ if ( (names = smr->smr_names) ) {
+ while ( *names ) {
+ mir = (struct mindexrec *)
+ ch_calloc( 1, sizeof(struct mindexrec) );
+ mir->mir_name.bv_val = *names;
+ mir->mir_name.bv_len = strlen( *names );
+ mir->mir_mr = smr;
+ if ( ldap_avl_insert( &mr_index, (caddr_t) mir,
+ mr_index_cmp, ldap_avl_dup_error ) ) {
+ *err = *names;
+ ldap_memfree(mir);
+ return SLAP_SCHERR_MR_DUP;
+ }
+ /* FIX: temporal consistency check */
+ mr_bvfind(&mir->mir_name);
+ names++;
+ }
+ }
+ return 0;
+}
+
+int
+mr_make_syntax_compat_with_mr(
+ Syntax *syn,
+ MatchingRule *mr )
+{
+ int n = 0;
+
+ assert( syn != NULL );
+ assert( mr != NULL );
+
+ if ( mr->smr_compat_syntaxes ) {
+ /* count existing */
+ for ( n = 0;
+ mr->smr_compat_syntaxes[ n ];
+ n++ )
+ {
+ if ( mr->smr_compat_syntaxes[ n ] == syn ) {
+ /* already compatible; mmmmh... */
+ return 1;
+ }
+ }
+ }
+
+ mr->smr_compat_syntaxes = ch_realloc(
+ mr->smr_compat_syntaxes,
+ sizeof( Syntax * )*(n + 2) );
+ mr->smr_compat_syntaxes[ n ] = syn;
+ mr->smr_compat_syntaxes[ n + 1 ] = NULL;
+
+ return 0;
+}
+
+int
+mr_make_syntax_compat_with_mrs(
+ const char *syntax,
+ char *const *mrs )
+{
+ int r, rc = 0;
+ Syntax *syn;
+
+ assert( syntax != NULL );
+ assert( mrs != NULL );
+
+ syn = syn_find( syntax );
+ if ( syn == NULL ) {
+ return -1;
+ }
+
+ for ( r = 0; mrs[ r ] != NULL; r++ ) {
+ MatchingRule *mr = mr_find( mrs[ r ] );
+ if ( mr == NULL ) {
+ /* matchingRule not found -- ignore by now */
+ continue;
+ }
+
+ rc += mr_make_syntax_compat_with_mr( syn, mr );
+ }
+
+ return rc;
+}
+
+int
+mr_add(
+ LDAPMatchingRule *mr,
+ slap_mrule_defs_rec *def,
+ MatchingRule *amr,
+ const char **err
+)
+{
+ MatchingRule *smr;
+ Syntax *syn;
+ Syntax **compat_syn = NULL;
+ int code;
+
+ if( def->mrd_compat_syntaxes ) {
+ int i;
+ for( i=0; def->mrd_compat_syntaxes[i]; i++ ) {
+ /* just count em */
+ }
+
+ compat_syn = ch_malloc( sizeof(Syntax *) * (i+1) );
+
+ for( i=0; def->mrd_compat_syntaxes[i]; i++ ) {
+ compat_syn[i] = syn_find( def->mrd_compat_syntaxes[i] );
+ if( compat_syn[i] == NULL ) {
+ ch_free( compat_syn );
+ return SLAP_SCHERR_SYN_NOT_FOUND;
+ }
+ }
+
+ compat_syn[i] = NULL;
+ }
+
+ smr = (MatchingRule *) ch_calloc( 1, sizeof(MatchingRule) );
+ AC_MEMCPY( &smr->smr_mrule, mr, sizeof(LDAPMatchingRule));
+
+ /*
+ * note: smr_bvoid uses the same memory of smr_mrule.mr_oid;
+ * smr_oidlen is #defined as smr_bvoid.bv_len
+ */
+ smr->smr_bvoid.bv_val = smr->smr_mrule.mr_oid;
+ smr->smr_oidlen = strlen( mr->mr_oid );
+ smr->smr_usage = def->mrd_usage;
+ smr->smr_compat_syntaxes = compat_syn;
+ smr->smr_normalize = def->mrd_normalize;
+ smr->smr_match = def->mrd_match;
+ smr->smr_indexer = def->mrd_indexer;
+ smr->smr_filter = def->mrd_filter;
+ smr->smr_associated = amr;
+
+ if ( smr->smr_syntax_oid ) {
+ if ( (syn = syn_find(smr->smr_syntax_oid)) ) {
+ smr->smr_syntax = syn;
+ } else {
+ *err = smr->smr_syntax_oid;
+ ch_free( smr );
+ return SLAP_SCHERR_SYN_NOT_FOUND;
+ }
+ } else {
+ *err = "";
+ ch_free( smr );
+ return SLAP_SCHERR_MR_INCOMPLETE;
+ }
+ code = mr_insert(smr,err);
+ return code;
+}
+
+int
+register_matching_rule(
+ slap_mrule_defs_rec *def )
+{
+ LDAPMatchingRule *mr;
+ MatchingRule *amr = NULL;
+ int code;
+ const char *err;
+
+ if( def->mrd_usage == SLAP_MR_NONE && def->mrd_compat_syntaxes == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "register_matching_rule: not usable %s\n",
+ def->mrd_desc );
+
+ return -1;
+ }
+
+ if( def->mrd_associated != NULL ) {
+ amr = mr_find( def->mrd_associated );
+ if( amr == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "register_matching_rule: "
+ "could not locate associated matching rule %s for %s\n",
+ def->mrd_associated, def->mrd_desc );
+
+ return -1;
+ }
+
+ if (( def->mrd_usage & SLAP_MR_EQUALITY ) &&
+ (( def->mrd_usage & SLAP_MR_SUBTYPE_MASK ) == SLAP_MR_NONE ))
+ {
+ if (( def->mrd_usage & SLAP_MR_EQUALITY ) &&
+ (( def->mrd_usage & SLAP_MR_SUBTYPE_MASK ) != SLAP_MR_NONE ))
+ {
+ Debug( LDAP_DEBUG_ANY, "register_matching_rule: "
+ "inappropriate (approx) association %s for %s\n",
+ def->mrd_associated, def->mrd_desc );
+ return -1;
+ }
+
+ } else if (!( amr->smr_usage & SLAP_MR_EQUALITY )) {
+ Debug( LDAP_DEBUG_ANY, "register_matching_rule: "
+ "inappropriate (equalilty) association %s for %s\n",
+ def->mrd_associated, def->mrd_desc );
+ return -1;
+ }
+ }
+
+ mr = ldap_str2matchingrule( def->mrd_desc, &code, &err,
+ LDAP_SCHEMA_ALLOW_ALL );
+ if ( !mr ) {
+ Debug( LDAP_DEBUG_ANY,
+ "Error in register_matching_rule: %s before %s in %s\n",
+ ldap_scherr2str(code), err, def->mrd_desc );
+
+ return -1;
+ }
+
+
+ code = mr_add( mr, def, amr, &err );
+
+ ldap_memfree( mr );
+
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "Error in register_matching_rule: %s for %s in %s\n",
+ scherr2str(code), err, def->mrd_desc );
+
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+mru_destroy( void )
+{
+ MatchingRuleUse *m;
+
+ while( !LDAP_SLIST_EMPTY(&mru_list) ) {
+ m = LDAP_SLIST_FIRST(&mru_list);
+ LDAP_SLIST_REMOVE_HEAD(&mru_list, smru_next);
+
+ if ( m->smru_str.bv_val ) {
+ ch_free( m->smru_str.bv_val );
+ m->smru_str.bv_val = NULL;
+ }
+ /* memory borrowed from m->smru_mr */
+ m->smru_oid = NULL;
+ m->smru_names = NULL;
+ m->smru_desc = NULL;
+
+ /* free what's left (basically smru_mruleuse.mru_applies_oids) */
+ ldap_matchingruleuse_free((LDAPMatchingRuleUse *)m);
+ }
+}
+
+int
+matching_rule_use_init( void )
+{
+ MatchingRule *mr;
+ MatchingRuleUse **mru_ptr = &LDAP_SLIST_FIRST(&mru_list);
+
+ Debug( LDAP_DEBUG_TRACE, "matching_rule_use_init\n" );
+
+ LDAP_SLIST_FOREACH( mr, &mr_list, smr_next ) {
+ AttributeType *at;
+ MatchingRuleUse mru_storage = {{ 0 }},
+ *mru = &mru_storage;
+
+ char **applies_oids = NULL;
+
+ mr->smr_mru = NULL;
+
+ /* hide rules marked as HIDE */
+ if ( mr->smr_usage & SLAP_MR_HIDE ) {
+ continue;
+ }
+
+ /* hide rules not marked as designed for extensibility */
+ /* MR_EXT means can be used any attribute type whose
+ * syntax is same as the assertion syntax.
+ * Another mechanism is needed where rule can be used
+ * with attribute of other syntaxes.
+ * Framework doesn't support this (yet).
+ */
+
+ if (!( ( mr->smr_usage & SLAP_MR_EXT )
+ || mr->smr_compat_syntaxes ) )
+ {
+ continue;
+ }
+
+ /*
+ * Note: we're using the same values of the corresponding
+ * MatchingRule structure; maybe we'd copy them ...
+ */
+ mru->smru_mr = mr;
+ mru->smru_obsolete = mr->smr_obsolete;
+ mru->smru_applies_oids = NULL;
+ LDAP_SLIST_NEXT(mru, smru_next) = NULL;
+ mru->smru_oid = mr->smr_oid;
+ mru->smru_names = mr->smr_names;
+ mru->smru_desc = mr->smr_desc;
+
+ Debug( LDAP_DEBUG_TRACE, " %s (%s):\n",
+ mru->smru_oid,
+ mru->smru_names ? mru->smru_names[ 0 ] : "" );
+
+ at = NULL;
+ for ( at_start( &at ); at; at_next( &at ) ) {
+ if( at->sat_flags & SLAP_AT_HIDE ) continue;
+
+ if( mr_usable_with_at( mr, at )) {
+ ldap_charray_add( &applies_oids, at->sat_cname.bv_val );
+ }
+ }
+
+ /*
+ * Note: the matchingRules that are not used
+ * by any attributeType are not listed as
+ * matchingRuleUse
+ */
+ if ( applies_oids != NULL ) {
+ mru->smru_applies_oids = applies_oids;
+ {
+ char *str = ldap_matchingruleuse2str( &mru->smru_mruleuse );
+ Debug( LDAP_DEBUG_TRACE, " matchingRuleUse: %s\n", str );
+ ldap_memfree( str );
+ }
+
+ mru = (MatchingRuleUse *)ber_memalloc( sizeof( MatchingRuleUse ) );
+ /* call-forward from MatchingRule to MatchingRuleUse */
+ mr->smr_mru = mru;
+ /* copy static data to newly allocated struct */
+ *mru = mru_storage;
+ /* append the struct pointer to the end of the list */
+ *mru_ptr = mru;
+ /* update the list head pointer */
+ mru_ptr = &LDAP_SLIST_NEXT(mru,smru_next);
+ }
+ }
+
+ return( 0 );
+}
+
+int
+mr_usable_with_at(
+ MatchingRule *mr,
+ AttributeType *at )
+{
+ if ( ( mr->smr_usage & SLAP_MR_EXT ) && (
+ mr->smr_syntax == at->sat_syntax ||
+ mr == at->sat_equality ||
+ mr == at->sat_approx ||
+ syn_is_sup( at->sat_syntax, mr->smr_syntax ) ) )
+ {
+ return 1;
+ }
+
+ if ( mr->smr_compat_syntaxes ) {
+ int i;
+ for( i=0; mr->smr_compat_syntaxes[i]; i++ ) {
+ if( at->sat_syntax == mr->smr_compat_syntaxes[i] ) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+int mr_schema_info( Entry *e )
+{
+ AttributeDescription *ad_matchingRules = slap_schema.si_ad_matchingRules;
+ MatchingRule *mr;
+ struct berval nval;
+
+ LDAP_SLIST_FOREACH(mr, &mr_list, smr_next ) {
+ if ( mr->smr_usage & SLAP_MR_HIDE ) {
+ /* skip hidden rules */
+ continue;
+ }
+
+ if ( ! mr->smr_match ) {
+ /* skip rules without matching functions */
+ continue;
+ }
+
+ if ( mr->smr_str.bv_val == NULL ) {
+ if ( ldap_matchingrule2bv( &mr->smr_mrule, &mr->smr_str ) == NULL ) {
+ return -1;
+ }
+ }
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "Merging mr [%lu] %s\n",
+ mr->smr_str.bv_len, mr->smr_str.bv_val );
+#endif
+
+ nval.bv_val = mr->smr_oid;
+ nval.bv_len = strlen(mr->smr_oid);
+ if( attr_merge_one( e, ad_matchingRules, &mr->smr_str, &nval ) ) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int mru_schema_info( Entry *e )
+{
+ AttributeDescription *ad_matchingRuleUse
+ = slap_schema.si_ad_matchingRuleUse;
+ MatchingRuleUse *mru;
+ struct berval nval;
+
+ LDAP_SLIST_FOREACH( mru, &mru_list, smru_next ) {
+ assert( !( mru->smru_usage & SLAP_MR_HIDE ) );
+
+ if ( mru->smru_str.bv_val == NULL ) {
+ if ( ldap_matchingruleuse2bv( &mru->smru_mruleuse, &mru->smru_str )
+ == NULL ) {
+ return -1;
+ }
+ }
+
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "Merging mru [%lu] %s\n",
+ mru->smru_str.bv_len, mru->smru_str.bv_val );
+#endif
+
+ nval.bv_val = mru->smru_oid;
+ nval.bv_len = strlen(mru->smru_oid);
+ if( attr_merge_one( e, ad_matchingRuleUse, &mru->smru_str, &nval ) ) {
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/servers/slapd/mra.c b/servers/slapd/mra.c
new file mode 100644
index 0000000..10cf117
--- /dev/null
+++ b/servers/slapd/mra.c
@@ -0,0 +1,231 @@
+/* mra.c - routines for dealing with extensible matching rule assertions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#ifdef LDAP_COMP_MATCH
+#include "component.h"
+#endif
+
+void
+mra_free(
+ Operation *op,
+ MatchingRuleAssertion *mra,
+ int freeit )
+{
+#ifdef LDAP_COMP_MATCH
+ /* free component assertion */
+ if ( mra->ma_rule->smr_usage & SLAP_MR_COMPONENT && mra->ma_cf ) {
+ component_free( mra->ma_cf );
+ }
+#endif
+ /* op->o_tmpfree( mra->ma_value.bv_val, op->o_tmpmemctx ); */
+ ch_free( mra->ma_value.bv_val );
+ if ( mra->ma_desc && mra->ma_desc->ad_flags & SLAP_DESC_TEMPORARY )
+ op->o_tmpfree( mra->ma_desc, op->o_tmpmemctx );
+ if ( freeit ) op->o_tmpfree( (char *) mra, op->o_tmpmemctx );
+}
+
+int
+get_mra(
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ const char **text )
+{
+ int rc;
+ ber_tag_t tag, rtag;
+ ber_len_t length;
+ struct berval type = BER_BVNULL;
+ struct berval value = BER_BVNULL;
+ struct berval rule_text = BER_BVNULL;
+ MatchingRuleAssertion ma = { 0 };
+#ifdef LDAP_COMP_MATCH
+ AttributeAliasing* aa = NULL;
+#endif
+
+ rtag = ber_scanf( ber, "{t" /*"}"*/, &tag );
+
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n" );
+
+ *text = "Error parsing matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ if ( tag == LDAP_FILTER_EXT_OID ) {
+ rtag = ber_scanf( ber, "m", &rule_text );
+ if ( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf for mr\n" );
+
+ *text = "Error parsing matching rule in matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ rtag = ber_scanf( ber, "t", &tag );
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n" );
+
+ *text = "Error parsing matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+ }
+
+ if ( tag == LDAP_FILTER_EXT_TYPE ) {
+ rtag = ber_scanf( ber, "m", &type );
+ if ( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf for ad\n" );
+
+ *text = "Error parsing attribute description in matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ rtag = ber_scanf( ber, "t", &tag );
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n" );
+
+ *text = "Error parsing matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+ }
+
+ if ( tag != LDAP_FILTER_EXT_VALUE ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf missing value\n" );
+
+ *text = "Missing value in matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ rtag = ber_scanf( ber, "m", &value );
+
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n" );
+
+ *text = "Error decoding value in matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ tag = ber_peek_tag( ber, &length );
+
+ if ( tag == LDAP_FILTER_EXT_DNATTRS ) {
+ rtag = ber_scanf( ber, /*"{"*/ "b}", &ma.ma_dnattrs );
+ } else {
+ rtag = ber_scanf( ber, /*"{"*/ "}" );
+ }
+
+ if( rtag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, " get_mra ber_scanf\n" );
+
+ *text = "Error decoding dnattrs matching rule assertion";
+ return SLAPD_DISCONNECT;
+ }
+
+ if( type.bv_val != NULL ) {
+ rc = slap_bv2ad( &type, &ma.ma_desc, text );
+ if( rc != LDAP_SUCCESS ) {
+ f->f_choice |= SLAPD_FILTER_UNDEFINED;
+ rc = slap_bv2undef_ad( &type, &ma.ma_desc, text,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT );
+
+ if( rc != LDAP_SUCCESS ) {
+ ma.ma_desc = slap_bv2tmp_ad( &type, op->o_tmpmemctx );
+ rc = LDAP_SUCCESS;
+ }
+ }
+ }
+
+ if( rule_text.bv_val != NULL ) {
+ ma.ma_rule = mr_bvfind( &rule_text );
+ if( ma.ma_rule == NULL ) {
+ *text = "matching rule not recognized";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ }
+
+ if ( ma.ma_rule == NULL ) {
+ /*
+ * Need either type or rule ...
+ */
+ if ( ma.ma_desc == NULL ) {
+ *text = "no matching rule or type";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if ( ma.ma_desc->ad_type->sat_equality != NULL &&
+ ma.ma_desc->ad_type->sat_equality->smr_usage & SLAP_MR_EXT )
+ {
+ /* no matching rule was provided, use the attribute's
+ equality rule if it supports extensible matching. */
+ ma.ma_rule = ma.ma_desc->ad_type->sat_equality;
+
+ } else {
+ *text = "no appropriate rule to use for type";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ }
+
+ if ( ma.ma_desc != NULL ) {
+ if( !mr_usable_with_at( ma.ma_rule, ma.ma_desc->ad_type ) ) {
+ *text = "matching rule use with this attribute not appropriate";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ }
+
+ /*
+ * Normalize per matching rule
+ */
+ rc = asserted_value_validate_normalize( ma.ma_desc,
+ ma.ma_rule,
+ SLAP_MR_EXT|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &value, &ma.ma_value, text, op->o_tmpmemctx );
+
+ if( rc != LDAP_SUCCESS ) return rc;
+
+#ifdef LDAP_COMP_MATCH
+ /* Check If this attribute is aliased */
+ if ( is_aliased_attribute && ma.ma_desc && ( aa = is_aliased_attribute ( ma.ma_desc ) ) ) {
+ rc = get_aliased_filter ( op, &ma, aa, text );
+ if ( rc != LDAP_SUCCESS ) return rc;
+ }
+ else if ( ma.ma_rule && ma.ma_rule->smr_usage & SLAP_MR_COMPONENT ) {
+ /* Matching Rule for Component Matching */
+ rc = get_comp_filter( op, &ma.ma_value, &ma.ma_cf, text );
+ if ( rc != LDAP_SUCCESS ) return rc;
+ }
+#endif
+
+ length = sizeof(ma);
+ /* Append rule_text to end of struct */
+ if (rule_text.bv_val) length += rule_text.bv_len + 1;
+ f->f_mra = op->o_tmpalloc( length, op->o_tmpmemctx );
+ *f->f_mra = ma;
+ if (rule_text.bv_val) {
+ f->f_mra->ma_rule_text.bv_len = rule_text.bv_len;
+ f->f_mra->ma_rule_text.bv_val = (char *)(f->f_mra+1);
+ AC_MEMCPY(f->f_mra->ma_rule_text.bv_val, rule_text.bv_val,
+ rule_text.bv_len+1);
+ }
+
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/nt_svc.c b/servers/slapd/nt_svc.c
new file mode 100644
index 0000000..1d2c1c3
--- /dev/null
+++ b/servers/slapd/nt_svc.c
@@ -0,0 +1,110 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#include <stdio.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "lutil.h"
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+
+/* in main.c */
+void WINAPI ServiceMain( DWORD argc, LPTSTR *argv );
+
+/* in ntservice.c */
+int main( int argc, LPTSTR *argv )
+{
+ int length;
+ char filename[MAX_PATH], *fname_start;
+
+ /*
+ * Because the service was registered as SERVICE_WIN32_OWN_PROCESS,
+ * the lpServiceName element of the SERVICE_TABLE_ENTRY will be
+ * ignored.
+ */
+
+ SERVICE_TABLE_ENTRY DispatchTable[] = {
+ { "", (LPSERVICE_MAIN_FUNCTION) ServiceMain },
+ { NULL, NULL }
+ };
+
+ /*
+ * set the service's current directory to the installation directory
+ * for the service. this way we don't have to write absolute paths
+ * in the configuration files
+ */
+ GetModuleFileName( NULL, filename, sizeof( filename ) );
+ fname_start = strrchr( filename, *LDAP_DIRSEP );
+
+ if ( argc > 1 ) {
+ if ( _stricmp( "install", argv[1] ) == 0 )
+ {
+ char *svcName = SERVICE_NAME;
+ char *displayName = "OpenLDAP Directory Service";
+ BOOL auto_start = FALSE;
+
+ if ( (argc > 2) && (argv[2] != NULL) )
+ svcName = argv[2];
+
+ if ( argc > 3 && argv[3])
+ displayName = argv[3];
+
+ if ( argc > 4 && stricmp(argv[4], "auto") == 0)
+ auto_start = TRUE;
+
+ strcat(filename, " service");
+ if ( !lutil_srv_install(svcName, displayName, filename, auto_start) )
+ {
+ fputs( "service failed installation ...\n", stderr );
+ return EXIT_FAILURE;
+ }
+ fputs( "service has been installed ...\n", stderr );
+ return EXIT_SUCCESS;
+ }
+
+ if ( _stricmp( "remove", argv[1] ) == 0 )
+ {
+ char *svcName = SERVICE_NAME;
+ if ( (argc > 2) && (argv[2] != NULL) )
+ svcName = argv[2];
+ if ( !lutil_srv_remove(svcName, filename) )
+ {
+ fputs( "failed to remove the service ...\n", stderr );
+ return EXIT_FAILURE;
+ }
+ fputs( "service has been removed ...\n", stderr );
+ return EXIT_SUCCESS;
+ }
+ if ( _stricmp( "service", argv[1] ) == 0 )
+ {
+ is_NT_Service = 1;
+ *fname_start = '\0';
+ SetCurrentDirectory( filename );
+ }
+ }
+
+ if (is_NT_Service)
+ {
+ StartServiceCtrlDispatcher(DispatchTable);
+ } else
+ {
+ ServiceMain( argc, argv );
+ }
+
+ return EXIT_SUCCESS;
+}
+
+#endif
diff --git a/servers/slapd/oc.c b/servers/slapd/oc.c
new file mode 100644
index 0000000..d88c88c
--- /dev/null
+++ b/servers/slapd/oc.c
@@ -0,0 +1,940 @@
+/* oc.c - object class routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+int is_object_subclass(
+ ObjectClass *sup,
+ ObjectClass *sub )
+{
+ int i;
+
+ if( sub == NULL || sup == NULL ) return 0;
+
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "is_object_subclass(%s,%s) %d\n",
+ sup->soc_oid, sub->soc_oid, sup == sub );
+#endif
+
+ if ( sup == sub ) {
+ return 1;
+ }
+
+ if ( sub->soc_sups == NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; sub->soc_sups[i] != NULL; i++ ) {
+ if ( is_object_subclass( sup, sub->soc_sups[i] ) ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int is_entry_objectclass(
+ Entry* e,
+ ObjectClass *oc,
+ unsigned flags )
+{
+ /*
+ * set_flags should only be true if oc is one of operational
+ * object classes which we support objectClass flags for
+ * (e.g., referral, alias, ...). See <slap.h>.
+ */
+
+ Attribute *attr;
+ struct berval *bv;
+
+ assert( !( e == NULL || oc == NULL ) );
+ assert( ( flags & SLAP_OCF_MASK ) != SLAP_OCF_MASK );
+
+ if ( e == NULL || oc == NULL ) {
+ return 0;
+ }
+
+ if ( flags == SLAP_OCF_SET_FLAGS && ( e->e_ocflags & SLAP_OC__END ) )
+ {
+ /* flags are set, use them */
+ return (e->e_ocflags & oc->soc_flags & SLAP_OC__MASK) != 0;
+ }
+
+ /*
+ * find objectClass attribute
+ */
+ attr = attr_find( e->e_attrs, slap_schema.si_ad_objectClass );
+ if ( attr == NULL ) {
+ /* no objectClass attribute */
+ Debug( LDAP_DEBUG_ANY, "is_entry_objectclass(\"%s\", \"%s\") "
+ "no objectClass attribute\n",
+ e->e_dn == NULL ? "" : e->e_dn,
+ oc->soc_oclass.oc_oid );
+
+ /* mark flags as set */
+ e->e_ocflags |= SLAP_OC__END;
+
+ return 0;
+ }
+
+ for ( bv = attr->a_vals; bv->bv_val; bv++ ) {
+ ObjectClass *objectClass = oc_bvfind( bv );
+
+ if ( objectClass == NULL ) {
+ /* FIXME: is this acceptable? */
+ continue;
+ }
+
+ if ( !( flags & SLAP_OCF_SET_FLAGS ) ) {
+ if ( objectClass == oc ) {
+ return 1;
+ }
+
+ if ( ( flags & SLAP_OCF_CHECK_SUP )
+ && is_object_subclass( oc, objectClass ) )
+ {
+ return 1;
+ }
+ }
+
+ e->e_ocflags |= objectClass->soc_flags;
+ }
+
+ /* mark flags as set */
+ e->e_ocflags |= SLAP_OC__END;
+
+ return ( e->e_ocflags & oc->soc_flags & SLAP_OC__MASK ) != 0;
+}
+
+
+struct oindexrec {
+ struct berval oir_name;
+ ObjectClass *oir_oc;
+};
+
+static Avlnode *oc_index = NULL;
+static Avlnode *oc_cache = NULL;
+static LDAP_STAILQ_HEAD(OCList, ObjectClass) oc_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(oc_list);
+
+ObjectClass *oc_sys_tail;
+
+static int
+oc_index_cmp(
+ const void *v_oir1,
+ const void *v_oir2 )
+{
+ const struct oindexrec *oir1 = v_oir1, *oir2 = v_oir2;
+ int i = oir1->oir_name.bv_len - oir2->oir_name.bv_len;
+ if (i) return i;
+ return strcasecmp( oir1->oir_name.bv_val, oir2->oir_name.bv_val );
+}
+
+static int
+oc_index_name_cmp(
+ const void *v_name,
+ const void *v_oir )
+{
+ const struct berval *name = v_name;
+ const struct oindexrec *oir = v_oir;
+ int i = name->bv_len - oir->oir_name.bv_len;
+ if (i) return i;
+ return strncasecmp( name->bv_val, oir->oir_name.bv_val, name->bv_len );
+}
+
+ObjectClass *
+oc_find( const char *ocname )
+{
+ struct berval bv;
+
+ bv.bv_val = (char *)ocname;
+ bv.bv_len = strlen( ocname );
+
+ return( oc_bvfind( &bv ) );
+}
+
+ObjectClass *
+oc_bvfind( struct berval *ocname )
+{
+ struct oindexrec *oir;
+
+ if ( oc_cache ) {
+ oir = ldap_avl_find( oc_cache, ocname, oc_index_name_cmp );
+ if ( oir ) return oir->oir_oc;
+ }
+ oir = ldap_avl_find( oc_index, ocname, oc_index_name_cmp );
+
+ if ( oir != NULL ) {
+ if ( at_oc_cache ) {
+ ldap_avl_insert( &oc_cache, (caddr_t) oir,
+ oc_index_cmp, ldap_avl_dup_error );
+ }
+ return( oir->oir_oc );
+ }
+
+ return( NULL );
+}
+
+static LDAP_STAILQ_HEAD(OCUList, ObjectClass) oc_undef_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(oc_undef_list);
+
+ObjectClass *
+oc_bvfind_undef( struct berval *ocname )
+{
+ ObjectClass *oc = oc_bvfind( ocname );
+
+ if ( oc ) {
+ return oc;
+ }
+
+ LDAP_STAILQ_FOREACH( oc, &oc_undef_list, soc_next ) {
+ int d = oc->soc_cname.bv_len - ocname->bv_len;
+
+ if ( d ) {
+ continue;
+ }
+
+ if ( strcasecmp( oc->soc_cname.bv_val, ocname->bv_val ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( oc ) {
+ return oc;
+ }
+
+ oc = ch_malloc( sizeof( ObjectClass ) + ocname->bv_len + 1 );
+ memset( oc, 0, sizeof( ObjectClass ) );
+
+ oc->soc_cname.bv_len = ocname->bv_len;
+ oc->soc_cname.bv_val = (char *)&oc[ 1 ];
+ AC_MEMCPY( oc->soc_cname.bv_val, ocname->bv_val, ocname->bv_len );
+ oc->soc_cname.bv_val[ oc->soc_cname.bv_len ] = '\0';
+
+ /* canonical to upper case */
+ ldap_pvt_str2upper( oc->soc_cname.bv_val );
+
+ LDAP_STAILQ_NEXT( oc, soc_next ) = NULL;
+ ldap_pvt_thread_mutex_lock( &oc_undef_mutex );
+ LDAP_STAILQ_INSERT_HEAD( &oc_undef_list, oc, soc_next );
+ ldap_pvt_thread_mutex_unlock( &oc_undef_mutex );
+
+ return oc;
+}
+
+static int
+oc_create_required(
+ ObjectClass *soc,
+ char **attrs,
+ int *op,
+ const char **err )
+{
+ char **attrs1;
+ AttributeType *sat;
+ AttributeType **satp;
+ int i;
+
+ if ( attrs ) {
+ attrs1 = attrs;
+ while ( *attrs1 ) {
+ sat = at_find(*attrs1);
+ if ( !sat ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_ATTR_NOT_FOUND;
+ }
+
+ if( is_at_operational( sat )) (*op)++;
+
+ if ( at_find_in_list(sat, soc->soc_required) < 0) {
+ if ( at_append_to_list(sat, &soc->soc_required) ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+ }
+ attrs1++;
+ }
+ /* Now delete duplicates from the allowed list */
+ for ( satp = soc->soc_required; *satp; satp++ ) {
+ i = at_find_in_list(*satp, soc->soc_allowed);
+ if ( i >= 0 ) {
+ at_delete_from_list(i, &soc->soc_allowed);
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+oc_create_allowed(
+ ObjectClass *soc,
+ char **attrs,
+ int *op,
+ const char **err )
+{
+ char **attrs1;
+ AttributeType *sat;
+
+ if ( attrs ) {
+ attrs1 = attrs;
+ while ( *attrs1 ) {
+ sat = at_find(*attrs1);
+ if ( !sat ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_ATTR_NOT_FOUND;
+ }
+
+ if( is_at_operational( sat )) (*op)++;
+
+ if ( at_find_in_list(sat, soc->soc_required) < 0 &&
+ at_find_in_list(sat, soc->soc_allowed) < 0 ) {
+ if ( at_append_to_list(sat, &soc->soc_allowed) ) {
+ *err = *attrs1;
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+ }
+ attrs1++;
+ }
+ }
+ return 0;
+}
+
+static int
+oc_add_sups(
+ ObjectClass *soc,
+ char **sups,
+ int *op,
+ const char **err )
+{
+ int code;
+ ObjectClass *soc1;
+ int nsups;
+ char **sups1;
+ int add_sups = 0;
+
+ if ( sups ) {
+ if ( !soc->soc_sups ) {
+ /* We are at the first recursive level */
+ add_sups = 1;
+ nsups = 1;
+ sups1 = sups;
+ while ( *sups1 ) {
+ nsups++;
+ sups1++;
+ }
+ soc->soc_sups = (ObjectClass **)ch_calloc(nsups,
+ sizeof(ObjectClass *));
+ }
+
+ nsups = 0;
+ sups1 = sups;
+ while ( *sups1 ) {
+ soc1 = oc_find(*sups1);
+ if ( !soc1 ) {
+ *err = *sups1;
+ return SLAP_SCHERR_CLASS_NOT_FOUND;
+ }
+
+ /* check object class usage
+ * abstract classes can only sup abstract classes
+ * structural classes can not sup auxiliary classes
+ * auxiliary classes can not sup structural classes
+ */
+ if( soc->soc_kind != soc1->soc_kind
+ && soc1->soc_kind != LDAP_SCHEMA_ABSTRACT )
+ {
+ *err = *sups1;
+ return SLAP_SCHERR_CLASS_BAD_SUP;
+ }
+
+ if( soc1->soc_obsolete && !soc->soc_obsolete ) {
+ *err = *sups1;
+ return SLAP_SCHERR_CLASS_BAD_SUP;
+ }
+
+ if( soc->soc_flags & SLAP_OC_OPERATIONAL ) (*op)++;
+
+ if ( add_sups ) {
+ soc->soc_sups[nsups] = soc1;
+ }
+
+ code = oc_add_sups( soc, soc1->soc_sup_oids, op, err );
+ if ( code ) return code;
+
+ code = oc_create_required( soc, soc1->soc_at_oids_must, op, err );
+ if ( code ) return code;
+
+ code = oc_create_allowed( soc, soc1->soc_at_oids_may, op, err );
+ if ( code ) return code;
+
+ nsups++;
+ sups1++;
+ }
+ }
+
+ return 0;
+}
+
+static void
+oc_delete_names( ObjectClass *oc )
+{
+ char **names = oc->soc_names;
+
+ if (!names) return;
+
+ while (*names) {
+ struct oindexrec tmpoir, *oir;
+
+ ber_str2bv( *names, 0, 0, &tmpoir.oir_name );
+ tmpoir.oir_oc = oc;
+ oir = (struct oindexrec *)ldap_avl_delete( &oc_index,
+ (caddr_t)&tmpoir, oc_index_cmp );
+ assert( oir != NULL );
+ ldap_memfree( oir );
+ names++;
+ }
+}
+
+/* Mark the ObjectClass as deleted, remove from list, and remove all its
+ * names from the AVL tree. Leave the OID in the tree.
+ */
+void
+oc_delete( ObjectClass *oc )
+{
+ oc->soc_flags |= SLAP_OC_DELETED;
+
+ LDAP_STAILQ_REMOVE(&oc_list, oc, ObjectClass, soc_next);
+
+ oc_delete_names( oc );
+}
+
+static void
+oc_clean( ObjectClass *o )
+{
+ if (o->soc_sups) {
+ ldap_memfree(o->soc_sups);
+ o->soc_sups = NULL;
+ }
+ if (o->soc_required) {
+ ldap_memfree(o->soc_required);
+ o->soc_required = NULL;
+ }
+ if (o->soc_allowed) {
+ ldap_memfree(o->soc_allowed);
+ o->soc_allowed = NULL;
+ }
+ if (o->soc_oidmacro) {
+ ldap_memfree(o->soc_oidmacro);
+ o->soc_oidmacro = NULL;
+ }
+}
+
+static void
+oc_destroy_one( void *v )
+{
+ struct oindexrec *oir = v;
+ ObjectClass *o = oir->oir_oc;
+
+ oc_clean( o );
+ ldap_objectclass_free((LDAPObjectClass *)o);
+ ldap_memfree(oir);
+}
+
+void
+oc_destroy( void )
+{
+ ObjectClass *o;
+
+ while( !LDAP_STAILQ_EMPTY(&oc_list) ) {
+ o = LDAP_STAILQ_FIRST(&oc_list);
+ LDAP_STAILQ_REMOVE_HEAD(&oc_list, soc_next);
+
+ oc_delete_names( o );
+ }
+
+ ldap_avl_free( oc_index, oc_destroy_one );
+
+ while( !LDAP_STAILQ_EMPTY(&oc_undef_list) ) {
+ o = LDAP_STAILQ_FIRST(&oc_undef_list);
+ LDAP_STAILQ_REMOVE_HEAD(&oc_undef_list, soc_next);
+
+ ch_free( (ObjectClass *)o );
+ }
+}
+
+int
+oc_start( ObjectClass **oc )
+{
+ assert( oc != NULL );
+
+ *oc = LDAP_STAILQ_FIRST(&oc_list);
+
+ return (*oc != NULL);
+}
+
+int
+oc_next( ObjectClass **oc )
+{
+ assert( oc != NULL );
+
+#if 0 /* pedantic check: breaks when deleting an oc, don't use it. */
+ {
+ ObjectClass *tmp = NULL;
+
+ LDAP_STAILQ_FOREACH(tmp,&oc_list,soc_next) {
+ if ( tmp == *oc ) {
+ break;
+ }
+ }
+
+ assert( tmp != NULL );
+ }
+#endif
+
+ if ( *oc == NULL ) {
+ return 0;
+ }
+
+ *oc = LDAP_STAILQ_NEXT(*oc,soc_next);
+
+ return (*oc != NULL);
+}
+
+/*
+ * check whether the two ObjectClasses actually __are__ identical,
+ * or rather inconsistent
+ */
+static int
+oc_check_dup(
+ ObjectClass *soc,
+ ObjectClass *new_soc )
+{
+ if ( new_soc->soc_oid != NULL ) {
+ if ( soc->soc_oid == NULL ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+
+ if ( strcmp( soc->soc_oid, new_soc->soc_oid ) != 0 ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+
+ } else {
+ if ( soc->soc_oid != NULL ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+ }
+
+ if ( new_soc->soc_names ) {
+ int i;
+
+ if ( soc->soc_names == NULL ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+
+ for ( i = 0; new_soc->soc_names[ i ]; i++ ) {
+ if ( soc->soc_names[ i ] == NULL ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+
+ if ( strcasecmp( soc->soc_names[ i ],
+ new_soc->soc_names[ i ] ) != 0 )
+ {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+ }
+ } else {
+ if ( soc->soc_names != NULL ) {
+ return SLAP_SCHERR_CLASS_INCONSISTENT;
+ }
+ }
+
+ return SLAP_SCHERR_CLASS_DUP;
+}
+
+static struct oindexrec *oir_old;
+
+static int
+oc_dup_error( void *left, void *right )
+{
+ oir_old = left;
+ return -1;
+}
+
+static int
+oc_insert(
+ ObjectClass **roc,
+ ObjectClass *prev,
+ const char **err )
+{
+ struct oindexrec *oir;
+ char **names;
+ ObjectClass *soc = *roc;
+
+ if ( soc->soc_oid ) {
+ oir = (struct oindexrec *)
+ ch_calloc( 1, sizeof(struct oindexrec) );
+ ber_str2bv( soc->soc_oid, 0, 0, &oir->oir_name );
+ oir->oir_oc = soc;
+ oir_old = NULL;
+
+ if ( ldap_avl_insert( &oc_index, (caddr_t) oir,
+ oc_index_cmp, oc_dup_error ) )
+ {
+ ObjectClass *old_soc;
+ int rc;
+
+ *err = soc->soc_oid;
+
+ assert( oir_old != NULL );
+ old_soc = oir_old->oir_oc;
+
+ /* replacing a deleted definition? */
+ if ( old_soc->soc_flags & SLAP_OC_DELETED ) {
+ ObjectClass tmp;
+
+ /* Keep old oid, free new oid;
+ * Keep new everything else, free old
+ */
+ tmp = *old_soc;
+ *old_soc = *soc;
+ old_soc->soc_oid = tmp.soc_oid;
+ tmp.soc_oid = soc->soc_oid;
+ *soc = tmp;
+
+ oc_clean( soc );
+ oc_destroy_one( oir );
+
+ oir = oir_old;
+ soc = old_soc;
+ *roc = soc;
+ } else {
+ rc = oc_check_dup( old_soc, soc );
+
+ ldap_memfree( oir );
+ return rc;
+ }
+ }
+
+ /* FIX: temporal consistency check */
+ assert( oc_bvfind( &oir->oir_name ) != NULL );
+ }
+
+ assert( soc != NULL );
+
+ if ( (names = soc->soc_names) ) {
+ while ( *names ) {
+ oir = (struct oindexrec *)
+ ch_calloc( 1, sizeof(struct oindexrec) );
+ oir->oir_name.bv_val = *names;
+ oir->oir_name.bv_len = strlen( *names );
+ oir->oir_oc = soc;
+
+ if ( ldap_avl_insert( &oc_index, (caddr_t) oir,
+ oc_index_cmp, ldap_avl_dup_error ) )
+ {
+ ObjectClass *old_soc;
+ int rc;
+
+ *err = *names;
+
+ old_soc = oc_bvfind( &oir->oir_name );
+ assert( old_soc != NULL );
+ rc = oc_check_dup( old_soc, soc );
+
+ ldap_memfree( oir );
+
+ while ( names > soc->soc_names ) {
+ struct oindexrec tmpoir;
+
+ names--;
+ ber_str2bv( *names, 0, 0, &tmpoir.oir_name );
+ tmpoir.oir_oc = soc;
+ oir = (struct oindexrec *)ldap_avl_delete( &oc_index,
+ (caddr_t)&tmpoir, oc_index_cmp );
+ assert( oir != NULL );
+ ldap_memfree( oir );
+ }
+
+ if ( soc->soc_oid ) {
+ struct oindexrec tmpoir;
+
+ ber_str2bv( soc->soc_oid, 0, 0, &tmpoir.oir_name );
+ tmpoir.oir_oc = soc;
+ oir = (struct oindexrec *)ldap_avl_delete( &oc_index,
+ (caddr_t)&tmpoir, oc_index_cmp );
+ assert( oir != NULL );
+ ldap_memfree( oir );
+ }
+
+ return rc;
+ }
+
+ /* FIX: temporal consistency check */
+ assert( oc_bvfind(&oir->oir_name) != NULL );
+
+ names++;
+ }
+ }
+ if ( soc->soc_flags & SLAP_OC_HARDCODE ) {
+ prev = oc_sys_tail;
+ oc_sys_tail = soc;
+ }
+ if ( prev ) {
+ LDAP_STAILQ_INSERT_AFTER( &oc_list, prev, soc, soc_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &oc_list, soc, soc_next );
+ }
+
+ return 0;
+}
+
+int
+oc_add(
+ LDAPObjectClass *oc,
+ int user,
+ ObjectClass **rsoc,
+ ObjectClass *prev,
+ const char **err )
+{
+ ObjectClass *soc;
+ int code;
+ int op = 0;
+ char *oidm = NULL;
+
+ if ( oc->oc_names != NULL ) {
+ int i;
+
+ for( i=0; oc->oc_names[i]; i++ ) {
+ if( !slap_valid_descr( oc->oc_names[i] ) ) {
+ return SLAP_SCHERR_BAD_DESCR;
+ }
+ }
+ }
+
+ if ( !OID_LEADCHAR( oc->oc_oid[0] )) {
+ /* Expand OID macros */
+ char *oid = oidm_find( oc->oc_oid );
+ if ( !oid ) {
+ *err = oc->oc_oid;
+ return SLAP_SCHERR_OIDM;
+ }
+ if ( oid != oc->oc_oid ) {
+ oidm = oc->oc_oid;
+ oc->oc_oid = oid;
+ }
+ }
+
+ soc = (ObjectClass *) ch_calloc( 1, sizeof(ObjectClass) );
+ AC_MEMCPY( &soc->soc_oclass, oc, sizeof(LDAPObjectClass) );
+
+ soc->soc_oidmacro = oidm;
+ if( oc->oc_names != NULL ) {
+ soc->soc_cname.bv_val = soc->soc_names[0];
+ } else {
+ soc->soc_cname.bv_val = soc->soc_oid;
+ }
+ soc->soc_cname.bv_len = strlen( soc->soc_cname.bv_val );
+
+ if( soc->soc_sup_oids == NULL &&
+ soc->soc_kind == LDAP_SCHEMA_STRUCTURAL )
+ {
+ /* structural object classes implicitly inherit from 'top' */
+ static char *top_oids[] = { SLAPD_TOP_OID, NULL };
+ code = oc_add_sups( soc, top_oids, &op, err );
+ } else {
+ code = oc_add_sups( soc, soc->soc_sup_oids, &op, err );
+ }
+
+ if ( code != 0 ) {
+ goto done;
+ }
+
+ if ( user && op ) {
+ code = SLAP_SCHERR_CLASS_BAD_SUP;
+ goto done;
+ }
+
+ code = oc_create_required( soc, soc->soc_at_oids_must, &op, err );
+ if ( code != 0 ) {
+ goto done;
+ }
+
+ code = oc_create_allowed( soc, soc->soc_at_oids_may, &op, err );
+ if ( code != 0 ) {
+ goto done;
+ }
+
+ if ( user && op ) {
+ code = SLAP_SCHERR_CLASS_BAD_USAGE;
+ goto done;
+ }
+
+ if ( !user ) {
+ soc->soc_flags |= SLAP_OC_HARDCODE;
+ }
+
+ code = oc_insert(&soc,prev,err);
+done:;
+ if ( code != 0 ) {
+ if ( soc->soc_sups ) {
+ ch_free( soc->soc_sups );
+ }
+
+ if ( soc->soc_required ) {
+ ch_free( soc->soc_required );
+ }
+
+ if ( soc->soc_allowed ) {
+ ch_free( soc->soc_allowed );
+ }
+
+ if ( soc->soc_oidmacro ) {
+ ch_free( soc->soc_oidmacro );
+ }
+
+ ch_free( soc );
+
+ } else if ( rsoc ) {
+ *rsoc = soc;
+ }
+ return code;
+}
+
+void
+oc_unparse( BerVarray *res, ObjectClass *start, ObjectClass *end, int sys )
+{
+ ObjectClass *oc;
+ int i, num;
+ struct berval bv, *bva = NULL, idx;
+ char ibuf[32];
+
+ if ( !start )
+ start = LDAP_STAILQ_FIRST( &oc_list );
+
+ /* count the result size */
+ i = 0;
+ for ( oc=start; oc; oc=LDAP_STAILQ_NEXT(oc, soc_next)) {
+ if ( sys && !(oc->soc_flags & SLAP_OC_HARDCODE)) break;
+ i++;
+ if ( oc == end ) break;
+ }
+ if (!i) return;
+
+ num = i;
+ bva = ch_malloc( (num+1) * sizeof(struct berval) );
+ BER_BVZERO( bva );
+ idx.bv_val = ibuf;
+ if ( sys ) {
+ idx.bv_len = 0;
+ ibuf[0] = '\0';
+ }
+ i = 0;
+ for ( oc=start; oc; oc=LDAP_STAILQ_NEXT(oc, soc_next)) {
+ LDAPObjectClass loc, *locp;
+ if ( sys && !(oc->soc_flags & SLAP_OC_HARDCODE)) break;
+ if ( oc->soc_oidmacro ) {
+ loc = oc->soc_oclass;
+ loc.oc_oid = oc->soc_oidmacro;
+ locp = &loc;
+ } else {
+ locp = &oc->soc_oclass;
+ }
+ if ( ldap_objectclass2bv( locp, &bv ) == NULL ) {
+ ber_bvarray_free( bva );
+ }
+ if ( !sys ) {
+ idx.bv_len = sprintf(idx.bv_val, "{%d}", i);
+ }
+ bva[i].bv_len = idx.bv_len + bv.bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ strcpy( bva[i].bv_val, ibuf );
+ strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val );
+ i++;
+ bva[i].bv_val = NULL;
+ ldap_memfree( bv.bv_val );
+ if ( oc == end ) break;
+ }
+ *res = bva;
+}
+
+int
+oc_schema_info( Entry *e )
+{
+ AttributeDescription *ad_objectClasses = slap_schema.si_ad_objectClasses;
+ ObjectClass *oc;
+ struct berval val;
+ struct berval nval;
+
+ LDAP_STAILQ_FOREACH( oc, &oc_list, soc_next ) {
+ if( oc->soc_flags & SLAP_OC_HIDE ) continue;
+
+ if ( ldap_objectclass2bv( &oc->soc_oclass, &val ) == NULL ) {
+ return -1;
+ }
+
+ nval = oc->soc_cname;
+
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "Merging oc [%ld] %s (%s)\n",
+ (long) val.bv_len, val.bv_val, nval.bv_val );
+#endif
+
+ if( attr_merge_one( e, ad_objectClasses, &val, &nval ) ) {
+ return -1;
+ }
+ ldap_memfree( val.bv_val );
+ }
+ return 0;
+}
+
+int
+register_oc( const char *def, ObjectClass **soc, int dupok )
+{
+ LDAPObjectClass *oc;
+ int code;
+ const char *err;
+
+ oc = ldap_str2objectclass( def, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !oc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "register_oc: objectclass \"%s\": %s, %s\n",
+ def, ldap_scherr2str(code), err );
+ return code;
+ }
+ code = oc_add(oc,0,NULL,NULL,&err);
+ if ( code && ( code != SLAP_SCHERR_CLASS_DUP || !dupok )) {
+ Debug( LDAP_DEBUG_ANY,
+ "register_oc: objectclass \"%s\": %s, %s\n",
+ def, scherr2str(code), err );
+ ldap_objectclass_free(oc);
+ return code;
+ }
+ if ( soc )
+ *soc = oc_find(oc->oc_names[0]);
+ if ( code ) {
+ ldap_objectclass_free(oc);
+ } else {
+ ldap_memfree(oc);
+ }
+ return 0;
+}
diff --git a/servers/slapd/oidm.c b/servers/slapd/oidm.c
new file mode 100644
index 0000000..19725ac
--- /dev/null
+++ b/servers/slapd/oidm.c
@@ -0,0 +1,217 @@
+/* oidm.c - object identifier macro routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+static LDAP_STAILQ_HEAD(OidMacroList, OidMacro) om_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(om_list);
+
+OidMacro *om_sys_tail;
+
+/* Replace an OID Macro invocation with its full numeric OID.
+ * If the macro is used with "macroname:suffix" append ".suffix"
+ * to the expansion.
+ */
+char *
+oidm_find(char *oid)
+{
+ OidMacro *om;
+
+ /* OID macros must start alpha */
+ if ( OID_LEADCHAR( *oid ) ) {
+ return oid;
+ }
+
+ LDAP_STAILQ_FOREACH( om, &om_list, som_next ) {
+ BerVarray names = om->som_names;
+
+ if( names == NULL ) {
+ continue;
+ }
+
+ for( ; !BER_BVISNULL( names ) ; names++ ) {
+ int pos = dscompare(names->bv_val, oid, ':');
+
+ if( pos ) {
+ int suflen = strlen(oid + pos);
+ char *tmp = SLAP_MALLOC( om->som_oid.bv_len
+ + suflen + 1);
+ if( tmp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "oidm_find: SLAP_MALLOC failed" );
+ return NULL;
+ }
+ strcpy(tmp, om->som_oid.bv_val);
+ if( suflen ) {
+ suflen = om->som_oid.bv_len;
+ tmp[suflen++] = '.';
+ strcpy(tmp+suflen, oid+pos+1);
+ }
+ return tmp;
+ }
+ }
+ }
+ return NULL;
+}
+
+void
+oidm_destroy()
+{
+ OidMacro *om;
+ while( !LDAP_STAILQ_EMPTY( &om_list )) {
+ om = LDAP_STAILQ_FIRST( &om_list );
+ LDAP_STAILQ_REMOVE_HEAD( &om_list, som_next );
+
+ ber_bvarray_free(om->som_names);
+ ber_bvarray_free(om->som_subs);
+ free(om->som_oid.bv_val);
+ free(om);
+
+ }
+}
+
+int
+parse_oidm(
+ struct config_args_s *c,
+ int user,
+ OidMacro **rom)
+{
+ char *oid, *oidv;
+ OidMacro *om = NULL, *prev = NULL;
+ struct berval bv;
+
+ oidv = oidm_find( c->argv[2] );
+ if( !oidv ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: OID %s not recognized",
+ c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ oid = oidm_find( c->argv[1] );
+ if( oid != NULL ) {
+ int rc;
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: \"%s\" previously defined \"%s\"",
+ c->argv[0], c->argv[1], oid );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ /* Allow duplicate if the definition is identical */
+ rc = strcmp( oid, oidv ) != 0;
+ SLAP_FREE( oid );
+ if ( oidv != c->argv[2] )
+ SLAP_FREE( oidv );
+ return rc;
+ }
+
+ om = (OidMacro *) SLAP_CALLOC( sizeof(OidMacro), 1 );
+ if( om == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: SLAP_CALLOC failed", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY,
+ "%s %s\n", c->log, c->cr_msg );
+ if ( oidv != c->argv[2] )
+ SLAP_FREE( oidv );
+ return 1;
+ }
+
+ om->som_names = NULL;
+ om->som_subs = NULL;
+ ber_str2bv( c->argv[1], 0, 1, &bv );
+ ber_bvarray_add( &om->som_names, &bv );
+ ber_str2bv( c->argv[2], 0, 1, &bv );
+ ber_bvarray_add( &om->som_subs, &bv );
+ om->som_oid.bv_val = oidv;
+
+ if (om->som_oid.bv_val == c->argv[2]) {
+ om->som_oid.bv_val = ch_strdup( c->argv[2] );
+ }
+
+ om->som_oid.bv_len = strlen( om->som_oid.bv_val );
+ if ( !user ) {
+ om->som_flags |= SLAP_OM_HARDCODE;
+ prev = om_sys_tail;
+ om_sys_tail = om;
+ }
+
+ if ( prev ) {
+ LDAP_STAILQ_INSERT_AFTER( &om_list, prev, om, som_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &om_list, om, som_next );
+ }
+ if ( rom ) *rom = om;
+ return 0;
+}
+
+void oidm_unparse( BerVarray *res, OidMacro *start, OidMacro *end, int sys )
+{
+ OidMacro *om;
+ int i, j, num;
+ struct berval *bva = NULL, idx;
+ char ibuf[32], *ptr;
+
+ if ( !start )
+ start = LDAP_STAILQ_FIRST( &om_list );
+
+ /* count the result size */
+ i = 0;
+ for ( om=start; om; om=LDAP_STAILQ_NEXT(om, som_next)) {
+ if ( sys && !(om->som_flags & SLAP_OM_HARDCODE)) break;
+ for ( j=0; !BER_BVISNULL(&om->som_names[j]); j++ );
+ i += j;
+ if ( om == end ) break;
+ }
+ num = i;
+ if (!i) return;
+
+ bva = ch_malloc( (num+1) * sizeof(struct berval) );
+ BER_BVZERO( bva+num );
+ idx.bv_val = ibuf;
+ if ( sys ) {
+ idx.bv_len = 0;
+ ibuf[0] = '\0';
+ }
+ for ( i=0,om=start; om; om=LDAP_STAILQ_NEXT(om, som_next)) {
+ if ( sys && !(om->som_flags & SLAP_OM_HARDCODE)) break;
+ for ( j=0; !BER_BVISNULL(&om->som_names[j]); i++,j++ ) {
+ if ( !sys ) {
+ idx.bv_len = sprintf(idx.bv_val, "{%d}", i );
+ }
+ bva[i].bv_len = idx.bv_len + om->som_names[j].bv_len +
+ om->som_subs[j].bv_len + 1;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ ptr = lutil_strcopy( bva[i].bv_val, ibuf );
+ ptr = lutil_strcopy( ptr, om->som_names[j].bv_val );
+ *ptr++ = ' ';
+ strcpy( ptr, om->som_subs[j].bv_val );
+ }
+ if ( i>=num ) break;
+ if ( om == end ) break;
+ }
+ *res = bva;
+}
diff --git a/servers/slapd/operation.c b/servers/slapd/operation.c
new file mode 100644
index 0000000..b4b4830
--- /dev/null
+++ b/servers/slapd/operation.c
@@ -0,0 +1,252 @@
+/* operation.c - routines to deal with pending ldap operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+
+static ldap_pvt_thread_mutex_t slap_op_mutex;
+static time_t last_time;
+static int last_incr;
+
+void slap_op_init(void)
+{
+ struct timeval tv;
+ ldap_pvt_thread_mutex_init( &slap_op_mutex );
+ gettimeofday( &tv, NULL );
+ last_time = tv.tv_sec;
+ last_incr = tv.tv_usec;
+}
+
+void slap_op_destroy(void)
+{
+ ldap_pvt_thread_mutex_destroy( &slap_op_mutex );
+}
+
+static void
+slap_op_q_destroy( void *key, void *data )
+{
+ Operation *op, *op2;
+ for ( op = data; op; op = op2 ) {
+ op2 = LDAP_STAILQ_NEXT( op, o_next );
+ ber_memfree_x( op, NULL );
+ }
+}
+
+void
+slap_op_groups_free( Operation *op )
+{
+ GroupAssertion *g, *n;
+ for ( g = op->o_groups; g; g = n ) {
+ n = g->ga_next;
+ slap_sl_free( g, op->o_tmpmemctx );
+ }
+ op->o_groups = NULL;
+}
+
+void
+slap_op_free( Operation *op, void *ctx )
+{
+ OperationBuffer *opbuf;
+
+ assert( LDAP_STAILQ_NEXT(op, o_next) == NULL );
+
+ /* paranoia */
+ op->o_abandon = 1;
+
+ if ( op->o_ber != NULL ) {
+ ber_free( op->o_ber, 1 );
+ }
+ if ( !BER_BVISNULL( &op->o_dn ) ) {
+ ch_free( op->o_dn.bv_val );
+ }
+ if ( !BER_BVISNULL( &op->o_ndn ) ) {
+ ch_free( op->o_ndn.bv_val );
+ }
+ if ( !BER_BVISNULL( &op->o_authmech ) ) {
+ ch_free( op->o_authmech.bv_val );
+ }
+ if ( op->o_ctrls != NULL ) {
+ slap_free_ctrls( op, op->o_ctrls );
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if ( op->o_res_ber != NULL ) {
+ ber_free( op->o_res_ber, 1 );
+ }
+#endif
+
+ if ( op->o_groups ) {
+ slap_op_groups_free( op );
+ }
+
+#if defined( LDAP_SLAPI )
+ if ( slapi_plugins_used ) {
+ slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, op );
+ }
+#endif /* defined( LDAP_SLAPI ) */
+
+ if ( !BER_BVISNULL( &op->o_csn ) ) {
+ op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( op->o_pagedresults_state != NULL ) {
+ op->o_tmpfree( op->o_pagedresults_state, op->o_tmpmemctx );
+ }
+
+ /* Selectively zero out the struct. Ignore fields that will
+ * get explicitly initialized later anyway. Keep o_abandon intact.
+ */
+ opbuf = (OperationBuffer *) op;
+ op->o_bd = NULL;
+ BER_BVZERO( &op->o_req_dn );
+ BER_BVZERO( &op->o_req_ndn );
+ memset( op->o_hdr, 0, sizeof( *op->o_hdr ));
+ memset( &op->o_request, 0, sizeof( op->o_request ));
+ memset( &op->o_do_not_cache, 0, sizeof( Operation ) - offsetof( Operation, o_do_not_cache ));
+ memset( opbuf->ob_controls, 0, sizeof( opbuf->ob_controls ));
+ op->o_controls = opbuf->ob_controls;
+
+ if ( ctx ) {
+ Operation *op2 = NULL;
+ ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free,
+ op, slap_op_q_destroy, (void **)&op2, NULL );
+ LDAP_STAILQ_NEXT( op, o_next ) = op2;
+ if ( op2 ) {
+ op->o_tincr = op2->o_tincr + 1;
+ /* No more than 10 ops on per-thread free list */
+ if ( op->o_tincr > 10 ) {
+ ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free,
+ op2, slap_op_q_destroy, NULL, NULL );
+ ber_memfree_x( op, NULL );
+ }
+ } else {
+ op->o_tincr = 1;
+ }
+ } else {
+ ber_memfree_x( op, NULL );
+ }
+}
+
+void
+slap_op_time(time_t *t, int *nop)
+{
+ struct timeval tv;
+ ldap_pvt_thread_mutex_lock( &slap_op_mutex );
+ gettimeofday( &tv, NULL );
+ *t = tv.tv_sec;
+ if ( *t == last_time ) {
+ *nop = ++last_incr;
+ } else {
+ last_time = *t;
+ last_incr = 0;
+ *nop = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &slap_op_mutex );
+ nop[1] = tv.tv_usec;
+}
+
+Operation *
+slap_op_alloc(
+ BerElement *ber,
+ ber_int_t msgid,
+ ber_tag_t tag,
+ ber_int_t id,
+ void *ctx )
+{
+ Operation *op = NULL;
+
+ if ( ctx ) {
+ void *otmp = NULL;
+ ldap_pvt_thread_pool_getkey( ctx, (void *)slap_op_free, &otmp, NULL );
+ if ( otmp ) {
+ op = otmp;
+ otmp = LDAP_STAILQ_NEXT( op, o_next );
+ ldap_pvt_thread_pool_setkey( ctx, (void *)slap_op_free,
+ otmp, slap_op_q_destroy, NULL, NULL );
+ op->o_abandon = 0;
+ op->o_cancel = 0;
+ }
+ }
+ if (!op) {
+ op = (Operation *) ch_calloc( 1, sizeof(OperationBuffer) );
+ op->o_hdr = &((OperationBuffer *) op)->ob_hdr;
+ op->o_controls = ((OperationBuffer *) op)->ob_controls;
+ }
+
+ op->o_ber = ber;
+ op->o_msgid = msgid;
+ op->o_tag = tag;
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+ op->o_opid = id;
+
+#if defined( LDAP_SLAPI )
+ if ( slapi_plugins_used ) {
+ slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op );
+ }
+#endif /* defined( LDAP_SLAPI ) */
+
+ return( op );
+}
+
+slap_op_t
+slap_req2op( ber_tag_t tag )
+{
+ switch ( tag ) {
+ case LDAP_REQ_BIND:
+ return SLAP_OP_BIND;
+ case LDAP_REQ_UNBIND:
+ return SLAP_OP_UNBIND;
+ case LDAP_REQ_ADD:
+ return SLAP_OP_ADD;
+ case LDAP_REQ_DELETE:
+ return SLAP_OP_DELETE;
+ case LDAP_REQ_MODRDN:
+ return SLAP_OP_MODRDN;
+ case LDAP_REQ_MODIFY:
+ return SLAP_OP_MODIFY;
+ case LDAP_REQ_COMPARE:
+ return SLAP_OP_COMPARE;
+ case LDAP_REQ_SEARCH:
+ return SLAP_OP_SEARCH;
+ case LDAP_REQ_ABANDON:
+ return SLAP_OP_ABANDON;
+ case LDAP_REQ_EXTENDED:
+ return SLAP_OP_EXTENDED;
+ }
+
+ return SLAP_OP_LAST;
+}
diff --git a/servers/slapd/operational.c b/servers/slapd/operational.c
new file mode 100644
index 0000000..77dce46
--- /dev/null
+++ b/servers/slapd/operational.c
@@ -0,0 +1,90 @@
+/* operational.c - routines to deal with on-the-fly operational attrs */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "slap.h"
+
+/*
+ * helpers for on-the-fly operational attribute generation
+ */
+
+Attribute *
+slap_operational_subschemaSubentry( Backend *be )
+{
+ Attribute *a;
+
+ /* The backend wants to take care of it */
+ if ( be && !SLAP_FRONTEND(be) && be->be_schemadn.bv_val ) return NULL;
+
+ a = attr_alloc( slap_schema.si_ad_subschemaSubentry );
+
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( a->a_vals, &frontendDB->be_schemadn );
+ a->a_vals[1].bv_len = 0;
+ a->a_vals[1].bv_val = NULL;
+
+ a->a_nvals = ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( a->a_nvals, &frontendDB->be_schemandn );
+ a->a_nvals[1].bv_len = 0;
+ a->a_nvals[1].bv_val = NULL;
+
+ return a;
+}
+
+Attribute *
+slap_operational_entryDN( Entry *e )
+{
+ Attribute *a;
+
+ assert( e != NULL );
+ assert( !BER_BVISNULL( &e->e_name ) );
+ assert( !BER_BVISNULL( &e->e_nname ) );
+
+ a = attr_alloc( slap_schema.si_ad_entryDN );
+
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &a->a_vals[ 0 ], &e->e_name );
+ BER_BVZERO( &a->a_vals[ 1 ] );
+
+ a->a_nvals = ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &a->a_nvals[ 0 ], &e->e_nname );
+ BER_BVZERO( &a->a_nvals[ 1 ] );
+
+ return a;
+}
+
+Attribute *
+slap_operational_hasSubordinate( int hs )
+{
+ Attribute *a;
+ struct berval val;
+
+ val = hs ? slap_true_bv : slap_false_bv;
+
+ a = attr_alloc( slap_schema.si_ad_hasSubordinates );
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc( 2 * sizeof( struct berval ) );
+
+ ber_dupbv( &a->a_vals[0], &val );
+ a->a_vals[1].bv_val = NULL;
+
+ a->a_nvals = a->a_vals;
+
+ return a;
+}
+
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
new file mode 100644
index 0000000..e6711fe
--- /dev/null
+++ b/servers/slapd/overlays/Makefile.in
@@ -0,0 +1,172 @@
+# Makefile.in for overlays
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2003-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = overlays.c \
+ accesslog.c \
+ auditlog.c \
+ autoca.c \
+ constraint.c \
+ dds.c \
+ deref.c \
+ dyngroup.c \
+ dynlist.c \
+ homedir.c \
+ memberof.c \
+ otp.c \
+ pcache.c \
+ collect.c \
+ ppolicy.c \
+ refint.c \
+ remoteauth.c \
+ retcode.c \
+ rwm.c rwmconf.c rwmdn.c rwmmap.c \
+ seqmod.c \
+ sssvlv.c \
+ syncprov.c \
+ translucent.c \
+ unique.c \
+ valsort.c
+OBJS = statover.o \
+ @SLAPD_STATIC_OVERLAYS@ \
+ overlays.o
+
+# Add here the objs that are needed by overlays, but do not make it
+# into SLAPD_STATIC_OVERLAYS...
+OBJDEP=rwm.o rwmconf.o rwmdn.o rwmmap.o
+
+LTONLY_MOD = $(LTONLY_mod)
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+MOD_DEFS = -DSLAPD_IMPORT
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBRARY = ../liboverlays.a
+PROGRAMS = @SLAPD_DYNAMIC_OVERLAYS@
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+static: $(LIBRARY)
+
+dynamic: $(PROGRAMS)
+
+accesslog.la : accesslog.lo
+ $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS)
+
+auditlog.la : auditlog.lo
+ $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS)
+
+autoca.la : autoca.lo
+ $(LTLINK_MOD) -module -o $@ autoca.lo version.lo $(LINK_LIBS)
+
+constraint.la : constraint.lo
+ $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS)
+
+dds.la : dds.lo
+ $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS)
+
+deref.la : deref.lo
+ $(LTLINK_MOD) -module -o $@ deref.lo version.lo $(LINK_LIBS)
+
+dyngroup.la : dyngroup.lo
+ $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS)
+
+dynlist.la : dynlist.lo
+ $(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS)
+
+homedir.la : homedir.lo
+ $(LTLINK_MOD) -module -o $@ homedir.lo version.lo $(LINK_LIBS)
+
+memberof.la : memberof.lo
+ $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
+
+otp.la : otp.lo
+ $(LTLINK_MOD) -module -o $@ otp.lo version.lo $(LINK_LIBS)
+
+pcache.la : pcache.lo
+ $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS)
+
+collect.la : collect.lo
+ $(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS)
+
+ppolicy.la : ppolicy.lo
+ $(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS)
+
+refint.la : refint.lo
+ $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS)
+
+remoteauth.la : remoteauth.lo
+ $(LTLINK_MOD) -module -o $@ remoteauth.lo version.lo $(LINK_LIBS)
+
+retcode.la : retcode.lo
+ $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS)
+
+rwm_x.o: rwm.o rwmconf.o rwmdn.o rwmmap.o
+ $(LD) -r -o $@ rwm.o rwmconf.o rwmdn.o rwmmap.o
+
+rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo
+ $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS)
+
+seqmod.la : seqmod.lo
+ $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS)
+
+sssvlv.la : sssvlv.lo
+ $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS)
+
+syncprov.la : syncprov.lo
+ $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS)
+
+translucent.la : translucent.lo
+ $(LTLINK_MOD) -module -o $@ translucent.lo version.lo $(LINK_LIBS)
+
+unique.la : unique.lo
+ $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS)
+
+valsort.la : valsort.lo
+ $(LTLINK_MOD) -module -o $@ valsort.lo version.lo $(LINK_LIBS)
+
+install-local: $(PROGRAMS)
+ @if test -n "$?" ; then \
+ $(MKDIR) $(DESTDIR)$(moduledir); \
+ $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\
+ fi
+
+MKDEPFLAG = -l
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+ $(LTCOMPILE_MOD) $<
+
+statover.o: statover.c $(srcdir)/../slap.h
+
+$(LIBRARY): $(OBJS) version.lo
+ $(AR) rs $@ $(OBJS)
+
+# Must fixup depends for non-libtool objects
+depend-local: depend-common
+ @if test -n "$(OBJS)"; then \
+ OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \
+ SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \
+ mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \
+ $(RM) Makefile.bak; fi
+
+veryclean-local:
+ $(RM) statover.c
+
diff --git a/servers/slapd/overlays/README b/servers/slapd/overlays/README
new file mode 100644
index 0000000..e426e4b
--- /dev/null
+++ b/servers/slapd/overlays/README
@@ -0,0 +1,5 @@
+This directory contains a number of SLAPD overlays, some
+project-maintained, some not. Some are generally usable,
+others are purely experimental. Additional overlays can
+be found in the contrib/slapd-modules directory.
+
diff --git a/servers/slapd/overlays/accesslog.c b/servers/slapd/overlays/accesslog.c
new file mode 100644
index 0000000..182be57
--- /dev/null
+++ b/servers/slapd/overlays/accesslog.c
@@ -0,0 +1,2874 @@
+/* accesslog.c - log operations for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions copyright 2004-2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_ACCESSLOG
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include <assert.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+
+#define LOG_OP_ADD 0x001
+#define LOG_OP_DELETE 0x002
+#define LOG_OP_MODIFY 0x004
+#define LOG_OP_MODRDN 0x008
+#define LOG_OP_COMPARE 0x010
+#define LOG_OP_SEARCH 0x020
+#define LOG_OP_BIND 0x040
+#define LOG_OP_UNBIND 0x080
+#define LOG_OP_ABANDON 0x100
+#define LOG_OP_EXTENDED 0x200
+#define LOG_OP_UNKNOWN 0x400
+
+#define LOG_OP_WRITES (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN)
+#define LOG_OP_READS (LOG_OP_COMPARE|LOG_OP_SEARCH)
+#define LOG_OP_SESSION (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON)
+#define LOG_OP_ALL (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \
+ LOG_OP_EXTENDED|LOG_OP_UNKNOWN)
+
+typedef struct log_attr {
+ struct log_attr *next;
+ AttributeDescription *attr;
+} log_attr;
+
+typedef struct log_base {
+ struct log_base *lb_next;
+ slap_mask_t lb_ops;
+ struct berval lb_base;
+ struct berval lb_line;
+} log_base;
+
+typedef struct log_info {
+ BackendDB *li_db;
+ struct berval li_db_suffix;
+ int li_open;
+
+ slap_mask_t li_ops;
+ int li_age;
+ int li_cycle;
+ struct re_s *li_task;
+ Filter *li_oldf;
+ Entry *li_old;
+ log_attr *li_oldattrs;
+ struct berval li_uuid;
+ int li_success;
+ log_base *li_bases;
+ BerVarray li_mincsn;
+ int *li_sids, li_numcsns;
+
+ /*
+ * Allow partial concurrency, main operation processing serialised with
+ * li_op_rmutex (there might be multiple such in progress by the same
+ * thread at a time, think overlays), the actual logging and mincsn
+ * management are serialised with li_log_mutex.
+ *
+ * ITS#9538:
+ * Any CSN assignment should happen while li_op_rmutex is held and
+ * li_log_mutex should be acquired before the former has been released.
+ */
+ ldap_pvt_thread_mutex_t li_op_rmutex;
+ ldap_pvt_thread_mutex_t li_log_mutex;
+} log_info;
+
+static ConfigDriver log_cf_gen;
+
+enum {
+ LOG_DB = 1,
+ LOG_OPS,
+ LOG_PURGE,
+ LOG_SUCCESS,
+ LOG_OLD,
+ LOG_OLDATTR,
+ LOG_BASE
+};
+
+static ConfigTable log_cfats[] = {
+ { "logdb", "suffix", 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|LOG_DB,
+ log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' "
+ "DESC 'Suffix of database for log content' "
+ "EQUALITY distinguishedNameMatch "
+ "SUP distinguishedName SINGLE-VALUE )", NULL, NULL },
+ { "logops", "op|writes|reads|session|all", 2, 0, 0,
+ ARG_MAGIC|LOG_OPS,
+ log_cf_gen, "( OLcfgOvAt:4.2 NAME 'olcAccessLogOps' "
+ "DESC 'Operation types to log' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "logpurge", "age> <interval", 3, 3, 0, ARG_MAGIC|LOG_PURGE,
+ log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' "
+ "DESC 'Log cleanup parameters' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logsuccess", NULL, 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|LOG_SUCCESS,
+ log_cf_gen, "( OLcfgOvAt:4.4 NAME 'olcAccessLogSuccess' "
+ "DESC 'Log successful ops only' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "logold", "filter", 2, 2, 0, ARG_MAGIC|LOG_OLD,
+ log_cf_gen, "( OLcfgOvAt:4.5 NAME 'olcAccessLogOld' "
+ "DESC 'Log old values when modifying entries matching the filter' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logoldattr", "attrs", 2, 0, 0, ARG_MAGIC|LOG_OLDATTR,
+ log_cf_gen, "( OLcfgOvAt:4.6 NAME 'olcAccessLogOldAttr' "
+ "DESC 'Log old values of these attributes even if unmodified' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "logbase", "op|writes|reads|session|all< <baseDN", 3, 3, 0,
+ ARG_MAGIC|LOG_BASE,
+ log_cf_gen, "( OLcfgOvAt:4.7 NAME 'olcAccessLogBase' "
+ "DESC 'Operation types to log under a specific branch' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs log_cfocs[] = {
+ { "( OLcfgOvOc:4.1 "
+ "NAME 'olcAccessLogConfig' "
+ "DESC 'Access log configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcAccessLogDB "
+ "MAY ( olcAccessLogOps $ olcAccessLogPurge $ olcAccessLogSuccess $ "
+ "olcAccessLogOld $ olcAccessLogOldAttr $ olcAccessLogBase ) )",
+ Cft_Overlay, log_cfats },
+ { NULL }
+};
+
+static slap_verbmasks logops[] = {
+ { BER_BVC("all"), LOG_OP_ALL },
+ { BER_BVC("writes"), LOG_OP_WRITES },
+ { BER_BVC("session"), LOG_OP_SESSION },
+ { BER_BVC("reads"), LOG_OP_READS },
+ { BER_BVC("add"), LOG_OP_ADD },
+ { BER_BVC("delete"), LOG_OP_DELETE },
+ { BER_BVC("modify"), LOG_OP_MODIFY },
+ { BER_BVC("modrdn"), LOG_OP_MODRDN },
+ { BER_BVC("compare"), LOG_OP_COMPARE },
+ { BER_BVC("search"), LOG_OP_SEARCH },
+ { BER_BVC("bind"), LOG_OP_BIND },
+ { BER_BVC("unbind"), LOG_OP_UNBIND },
+ { BER_BVC("abandon"), LOG_OP_ABANDON },
+ { BER_BVC("extended"), LOG_OP_EXTENDED },
+ { BER_BVC("unknown"), LOG_OP_UNKNOWN },
+ { BER_BVNULL, 0 }
+};
+
+/* Start with "add" in logops */
+#define EN_OFFSET 4
+
+enum {
+ LOG_EN_ADD = 0,
+ LOG_EN_DELETE,
+ LOG_EN_MODIFY,
+ LOG_EN_MODRDN,
+ LOG_EN_COMPARE,
+ LOG_EN_SEARCH,
+ LOG_EN_BIND,
+ LOG_EN_UNBIND,
+ LOG_EN_ABANDON,
+ LOG_EN_EXTENDED,
+ LOG_EN_UNKNOWN,
+ LOG_EN__COUNT
+};
+
+static ObjectClass *log_ocs[LOG_EN__COUNT], *log_container,
+ *log_oc_read, *log_oc_write;
+
+#define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5"
+
+#define LOG_SCHEMA_AT LOG_SCHEMA_ROOT ".1"
+#define LOG_SCHEMA_OC LOG_SCHEMA_ROOT ".2"
+#define LOG_SCHEMA_SYN LOG_SCHEMA_ROOT ".3"
+
+static AttributeDescription *ad_reqDN, *ad_reqStart, *ad_reqEnd, *ad_reqType,
+ *ad_reqSession, *ad_reqResult, *ad_reqAuthzID, *ad_reqControls,
+ *ad_reqRespControls, *ad_reqMethod, *ad_reqAssertion, *ad_reqNewRDN,
+ *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod,
+ *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries,
+ *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData,
+ *ad_reqId, *ad_reqMessage, *ad_reqVersion, *ad_reqDerefAliases,
+ *ad_reqReferral, *ad_reqOld, *ad_auditContext, *ad_reqEntryUUID,
+ *ad_minCSN, *ad_reqNewDN;
+
+static int
+logSchemaControlValidate(
+ Syntax *syntax,
+ struct berval *val );
+
+char *mrControl[] = {
+ "objectIdentifierFirstComponentMatch",
+ NULL
+};
+
+static struct {
+ char *oid;
+ slap_syntax_defs_rec syn;
+ char **mrs;
+} lsyntaxes[] = {
+ { LOG_SCHEMA_SYN ".1" ,
+ { "( " LOG_SCHEMA_SYN ".1 DESC 'Control' )",
+ SLAP_SYNTAX_HIDE,
+ NULL,
+ logSchemaControlValidate,
+ NULL },
+ mrControl },
+ { NULL }
+};
+
+static struct {
+ char *at;
+ AttributeDescription **ad;
+} lattrs[] = {
+ { "( " LOG_SCHEMA_AT ".1 NAME 'reqDN' "
+ "DESC 'Target DN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqDN },
+ { "( " LOG_SCHEMA_AT ".2 NAME 'reqStart' "
+ "DESC 'Start time of request' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE )", &ad_reqStart },
+ { "( " LOG_SCHEMA_AT ".3 NAME 'reqEnd' "
+ "DESC 'End time of request' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE )", &ad_reqEnd },
+ { "( " LOG_SCHEMA_AT ".4 NAME 'reqType' "
+ "DESC 'Type of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqType },
+ { "( " LOG_SCHEMA_AT ".5 NAME 'reqSession' "
+ "DESC 'Session ID of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqSession },
+ { "( " LOG_SCHEMA_AT ".6 NAME 'reqAuthzID' "
+ "DESC 'Authorization ID of requestor' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqAuthzID },
+ { "( " LOG_SCHEMA_AT ".7 NAME 'reqResult' "
+ "DESC 'Result code of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqResult },
+ { "( " LOG_SCHEMA_AT ".8 NAME 'reqMessage' "
+ "DESC 'Error text of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqMessage },
+ { "( " LOG_SCHEMA_AT ".9 NAME 'reqReferral' "
+ "DESC 'Referrals returned for request' "
+ "SUP labeledURI )", &ad_reqReferral },
+ { "( " LOG_SCHEMA_AT ".10 NAME 'reqControls' "
+ "DESC 'Request controls' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX " LOG_SCHEMA_SYN ".1 "
+ "X-ORDERED 'VALUES' )", &ad_reqControls },
+ { "( " LOG_SCHEMA_AT ".11 NAME 'reqRespControls' "
+ "DESC 'Response controls of request' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX " LOG_SCHEMA_SYN ".1 "
+ "X-ORDERED 'VALUES' )", &ad_reqRespControls },
+ { "( " LOG_SCHEMA_AT ".12 NAME 'reqId' "
+ "DESC 'ID of Request to Abandon' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqId },
+ { "( " LOG_SCHEMA_AT ".13 NAME 'reqVersion' "
+ "DESC 'Protocol version of Bind request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqVersion },
+ { "( " LOG_SCHEMA_AT ".14 NAME 'reqMethod' "
+ "DESC 'Bind method of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqMethod },
+ { "( " LOG_SCHEMA_AT ".15 NAME 'reqAssertion' "
+ "DESC 'Compare Assertion of request' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqAssertion },
+ { "( " LOG_SCHEMA_AT ".16 NAME 'reqMod' "
+ "DESC 'Modifications of request' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString )", &ad_reqMod },
+ { "( " LOG_SCHEMA_AT ".17 NAME 'reqOld' "
+ "DESC 'Old values of entry before request completed' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString )", &ad_reqOld },
+ { "( " LOG_SCHEMA_AT ".18 NAME 'reqNewRDN' "
+ "DESC 'New RDN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewRDN },
+ { "( " LOG_SCHEMA_AT ".19 NAME 'reqDeleteOldRDN' "
+ "DESC 'Delete old RDN' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", &ad_reqDeleteOldRDN },
+ { "( " LOG_SCHEMA_AT ".20 NAME 'reqNewSuperior' "
+ "DESC 'New superior DN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewSuperior },
+ { "( " LOG_SCHEMA_AT ".21 NAME 'reqScope' "
+ "DESC 'Scope of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqScope },
+ { "( " LOG_SCHEMA_AT ".22 NAME 'reqDerefAliases' "
+ "DESC 'Disposition of Aliases in request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqDerefAliases },
+ { "( " LOG_SCHEMA_AT ".23 NAME 'reqAttrsOnly' "
+ "DESC 'Attributes and values of request' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", &ad_reqAttrsOnly },
+ { "( " LOG_SCHEMA_AT ".24 NAME 'reqFilter' "
+ "DESC 'Filter of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqFilter },
+ { "( " LOG_SCHEMA_AT ".25 NAME 'reqAttr' "
+ "DESC 'Attributes of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", &ad_reqAttr },
+ { "( " LOG_SCHEMA_AT ".26 NAME 'reqSizeLimit' "
+ "DESC 'Size limit of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqSizeLimit },
+ { "( " LOG_SCHEMA_AT ".27 NAME 'reqTimeLimit' "
+ "DESC 'Time limit of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqTimeLimit },
+ { "( " LOG_SCHEMA_AT ".28 NAME 'reqEntries' "
+ "DESC 'Number of entries returned' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqEntries },
+ { "( " LOG_SCHEMA_AT ".29 NAME 'reqData' "
+ "DESC 'Data of extended request' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString "
+ "SINGLE-VALUE )", &ad_reqData },
+
+ /*
+ * from <draft-chu-ldap-logschema-01.txt>:
+ *
+
+ ( LOG_SCHEMA_AT .30 NAME 'auditContext'
+ DESC 'DN of auditContainer'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )
+
+ * - removed EQUALITY matchingRule
+ * - changed directoryOperation in dSAOperation
+ */
+ { "( " LOG_SCHEMA_AT ".30 NAME 'auditContext' "
+ "DESC 'DN of auditContainer' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", &ad_auditContext },
+
+ /*
+ * ITS#6656
+ */
+ { "( " LOG_SCHEMA_AT ".31 NAME 'reqEntryUUID' "
+ "DESC 'UUID of entry' "
+ "EQUALITY UUIDMatch "
+ "ORDERING UUIDOrderingMatch "
+ "SYNTAX 1.3.6.1.1.16.1 "
+ "SINGLE-VALUE )", &ad_reqEntryUUID },
+
+ /*
+ * ITS#8486
+ */
+ { "( " LOG_SCHEMA_AT ".32 NAME 'minCSN' "
+ "DESC 'CSN set that the logs are recorded from' "
+ "EQUALITY CSNMatch "
+ "ORDERING CSNOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", &ad_minCSN },
+
+ /*
+ * ITS#9552
+ */
+ { "( " LOG_SCHEMA_AT ".33 NAME 'reqNewDN' "
+ "DESC 'New DN after rename' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewDN },
+ { NULL, NULL }
+};
+
+static struct {
+ char *ot;
+ ObjectClass **oc;
+} locs[] = {
+ { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' "
+ "DESC 'AuditLog container' "
+ "SUP top STRUCTURAL "
+ "MAY ( cn $ reqStart $ reqEnd ) )", &log_container },
+ { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' "
+ "DESC 'OpenLDAP request auditing' "
+ "SUP top STRUCTURAL "
+ "MUST ( reqStart $ reqType $ reqSession ) "
+ "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ "
+ "reqResult $ reqMessage $ reqReferral $ reqEntryUUID ) )",
+ &log_ocs[LOG_EN_UNBIND] },
+ { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' "
+ "DESC 'OpenLDAP read request record' "
+ "SUP auditObject STRUCTURAL )", &log_oc_read },
+ { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' "
+ "DESC 'OpenLDAP write request record' "
+ "SUP auditObject STRUCTURAL )", &log_oc_write },
+ { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' "
+ "DESC 'Abandon operation' "
+ "SUP auditObject STRUCTURAL "
+ "MUST reqId )", &log_ocs[LOG_EN_ABANDON] },
+ { "( " LOG_SCHEMA_OC ".5 NAME 'auditAdd' "
+ "DESC 'Add operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MUST reqMod )", &log_ocs[LOG_EN_ADD] },
+ { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' "
+ "DESC 'Bind operation' "
+ "SUP auditObject STRUCTURAL "
+ "MUST ( reqVersion $ reqMethod ) )", &log_ocs[LOG_EN_BIND] },
+ { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' "
+ "DESC 'Compare operation' "
+ "SUP auditReadObject STRUCTURAL "
+ "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] },
+ { "( " LOG_SCHEMA_OC ".8 NAME 'auditDelete' "
+ "DESC 'Delete operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MAY reqOld )", &log_ocs[LOG_EN_DELETE] },
+ { "( " LOG_SCHEMA_OC ".9 NAME 'auditModify' "
+ "DESC 'Modify operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MAY ( reqOld $ reqMod ) )", &log_ocs[LOG_EN_MODIFY] },
+ { "( " LOG_SCHEMA_OC ".10 NAME 'auditModRDN' "
+ "DESC 'ModRDN operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MUST ( reqNewRDN $ reqDeleteOldRDN ) "
+ "MAY ( reqNewSuperior $ reqMod $ reqOld $ reqNewDN ) )",
+ &log_ocs[LOG_EN_MODRDN] },
+ { "( " LOG_SCHEMA_OC ".11 NAME 'auditSearch' "
+ "DESC 'Search operation' "
+ "SUP auditReadObject STRUCTURAL "
+ "MUST ( reqScope $ reqDerefAliases $ reqAttrsonly ) "
+ "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ "
+ "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] },
+ { "( " LOG_SCHEMA_OC ".12 NAME 'auditExtended' "
+ "DESC 'Extended operation' "
+ "SUP auditObject STRUCTURAL "
+ "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] },
+ { NULL, NULL }
+};
+
+#define RDNEQ "reqStart="
+
+/* Our time intervals are of the form [ddd+]hh:mm[:ss]
+ * If a field is present, it must be two digits. (Except for
+ * days, which can be arbitrary width.)
+ */
+static int
+log_age_parse(char *agestr)
+{
+ int t1, t2;
+ int gotdays = 0;
+ char *endptr;
+
+ t1 = strtol( agestr, &endptr, 10 );
+ /* Is there a days delimiter? */
+ if ( *endptr == '+' ) {
+ /* 32 bit time only covers about 68 years */
+ if ( t1 < 0 || t1 > 25000 )
+ return -1;
+ t1 *= 24;
+ gotdays = 1;
+ agestr = endptr + 1;
+ } else {
+ if ( agestr[2] != ':' ) {
+ /* No valid delimiter found, fail */
+ return -1;
+ }
+ t1 *= 60;
+ agestr += 3;
+ }
+
+ t2 = atoi( agestr );
+ t1 += t2;
+
+ if ( agestr[2] ) {
+ /* if there's a delimiter, it can only be a colon */
+ if ( agestr[2] != ':' )
+ return -1;
+ } else {
+ /* If we're at the end of the string, and we started with days,
+ * fail because we expected to find minutes too.
+ */
+ return gotdays ? -1 : t1 * 60;
+ }
+
+ agestr += 3;
+ t2 = atoi( agestr );
+
+ /* last field can only be seconds */
+ if ( agestr[2] && ( agestr[2] != ':' || !gotdays ))
+ return -1;
+ t1 *= 60;
+ t1 += t2;
+
+ if ( agestr[2] ) {
+ agestr += 3;
+ if ( agestr[2] )
+ return -1;
+ t1 *= 60;
+ t1 += atoi( agestr );
+ } else if ( gotdays ) {
+ /* only got days+hh:mm */
+ t1 *= 60;
+ }
+ return t1;
+}
+
+static void
+log_age_unparse( int age, struct berval *agebv, size_t size )
+{
+ int dd, hh, mm, ss, len;
+ char *ptr;
+
+ assert( size > 0 );
+
+ ss = age % 60;
+ age /= 60;
+ mm = age % 60;
+ age /= 60;
+ hh = age % 24;
+ age /= 24;
+ dd = age;
+
+ ptr = agebv->bv_val;
+
+ if ( dd ) {
+ len = snprintf( ptr, size, "%d+", dd );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ }
+ len = snprintf( ptr, size, "%02d:%02d", hh, mm );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ if ( ss ) {
+ len = snprintf( ptr, size, ":%02d", ss );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ }
+
+ agebv->bv_len = ptr - agebv->bv_val;
+}
+
+static slap_callback nullsc;
+
+#define PURGE_INCREMENT 100
+
+typedef struct purge_data {
+ struct log_info *li;
+ int slots;
+ int used;
+ int mincsn_updated;
+ BerVarray dn;
+ BerVarray ndn;
+} purge_data;
+
+static int
+log_old_lookup( Operation *op, SlapReply *rs )
+{
+ purge_data *pd = op->o_callback->sc_private;
+ struct log_info *li = pd->li;
+ Attribute *a;
+
+ if ( rs->sr_type != REP_SEARCH) return 0;
+
+ if ( slapd_shutdown ) return 0;
+
+ /* Update minCSN */
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ ber_len_t len = a->a_nvals[0].bv_len;
+ int i, sid;
+
+ /* Find the correct sid */
+ sid = slap_parse_csn_sid( &a->a_nvals[0] );
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ for ( i=0; i < li->li_numcsns; i++ ) {
+ if ( sid <= li->li_sids[i] ) break;
+ }
+ if ( i >= li->li_numcsns || sid != li->li_sids[i] ) {
+ Debug( LDAP_DEBUG_ANY, "log_old_lookup: "
+ "csn=%s with sid not in minCSN set!\n",
+ a->a_nvals[0].bv_val );
+ slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i,
+ sid, &a->a_nvals[0] );
+ } else {
+ /* Paranoid len check, normalized CSNs are always the same length */
+ if ( len > li->li_mincsn[i].bv_len )
+ len = li->li_mincsn[i].bv_len;
+ if ( ber_bvcmp( &li->li_mincsn[i], &a->a_nvals[0] ) < 0 ) {
+ pd->mincsn_updated = 1;
+ AC_MEMCPY( li->li_mincsn[i].bv_val, a->a_nvals[0].bv_val, len );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ }
+ if ( pd->used >= pd->slots ) {
+ pd->slots += PURGE_INCREMENT;
+ pd->dn = ch_realloc( pd->dn, pd->slots * sizeof( struct berval ));
+ pd->ndn = ch_realloc( pd->ndn, pd->slots * sizeof( struct berval ));
+ }
+ ber_dupbv( &pd->dn[pd->used], &rs->sr_entry->e_name );
+ ber_dupbv( &pd->ndn[pd->used], &rs->sr_entry->e_nname );
+ pd->used++;
+ return 0;
+}
+
+/* Periodically search for old entries in the log database and delete them */
+static void *
+accesslog_purge( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ struct log_info *li = rtask->arg;
+
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ SlapReply rs = {REP_RESULT};
+ slap_callback cb = { NULL, log_old_lookup, NULL, NULL, NULL };
+ Filter f;
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ purge_data pd = { .li = li };
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ time_t old = slap_get_time();
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ f.f_choice = LDAP_FILTER_LE;
+ f.f_ava = &ava;
+ f.f_next = NULL;
+
+ ava.aa_desc = ad_reqStart;
+ ava.aa_value.bv_val = timebuf;
+ ava.aa_value.bv_len = sizeof(timebuf);
+
+ old -= li->li_age;
+ slap_timestamp( &old, &ava.aa_value );
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_bd = li->li_db;
+ op->o_dn = li->li_db->be_rootdn;
+ op->o_ndn = li->li_db->be_rootndn;
+ op->o_req_dn = li->li_db->be_suffix[0];
+ op->o_req_ndn = li->li_db->be_nsuffix[0];
+ op->o_callback = &cb;
+ op->ors_scope = LDAP_SCOPE_ONELEVEL;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_filter = &f;
+ filter2bv_x( op, &f, &op->ors_filterstr );
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->ors_attrsonly = 1;
+
+ cb.sc_private = &pd;
+
+ op->o_bd->be_search( op, &rs );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+
+ if ( pd.used ) {
+ int i;
+
+ op->o_callback = &nullsc;
+ op->o_dont_replicate = 1;
+ op->o_csn = slap_empty_bv;
+
+ if ( pd.mincsn_updated ) {
+ Modifications mod;
+ /* update context's minCSN to reflect oldest CSN */
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ mod.sml_numvals = li->li_numcsns;
+ mod.sml_values = li->li_mincsn;
+ mod.sml_nvalues = li->li_mincsn;
+ mod.sml_desc = ad_minCSN;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = li->li_db->be_suffix[0];
+ op->o_req_ndn = li->li_db->be_nsuffix[0];
+ op->o_no_schema_check = 1;
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+ if ( !slapd_shutdown ) {
+ Debug( LDAP_DEBUG_SYNC, "accesslog_purge: "
+ "updating minCSN with %d values\n",
+ li->li_numcsns );
+ op->o_bd->be_modify( op, &rs );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ }
+
+ /* delete the expired entries */
+ op->o_tag = LDAP_REQ_DELETE;
+ for (i=0; i<pd.used; i++) {
+ op->o_req_dn = pd.dn[i];
+ op->o_req_ndn = pd.ndn[i];
+ if ( !slapd_shutdown ) {
+ rs_reinit( &rs, REP_RESULT );
+ op->o_bd->be_delete( op, &rs );
+ }
+ ch_free( pd.ndn[i].bv_val );
+ ch_free( pd.dn[i].bv_val );
+ ldap_pvt_thread_pool_pausewait( &connection_pool );
+ }
+ ch_free( pd.ndn );
+ ch_free( pd.dn );
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+static int
+log_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ struct log_info *li = on->on_bi.bi_private;
+ int rc = 0;
+ slap_mask_t tmask = 0;
+ char agebuf[2*STRLENOF("ddddd+hh:mm:ss ")];
+ struct berval agebv, cyclebv;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch( c->type ) {
+ case LOG_DB:
+ if ( !BER_BVISEMPTY( &li->li_db_suffix )) {
+ value_add_one( &c->rvalue_vals, &li->li_db_suffix );
+ value_add_one( &c->rvalue_nvals, &li->li_db_suffix );
+ } else if ( li->li_db ) {
+ value_add_one( &c->rvalue_vals, li->li_db->be_suffix );
+ value_add_one( &c->rvalue_nvals, li->li_db->be_nsuffix );
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "accesslog: \"logdb <suffix>\" must be specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ break;
+ }
+ break;
+ case LOG_OPS:
+ rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals );
+ break;
+ case LOG_PURGE:
+ if ( !li->li_age ) {
+ rc = 1;
+ break;
+ }
+ agebv.bv_val = agebuf;
+ log_age_unparse( li->li_age, &agebv, sizeof( agebuf ) );
+ agebv.bv_val[agebv.bv_len] = ' ';
+ agebv.bv_len++;
+ cyclebv.bv_val = agebv.bv_val + agebv.bv_len;
+ log_age_unparse( li->li_cycle, &cyclebv, sizeof( agebuf ) - agebv.bv_len );
+ agebv.bv_len += cyclebv.bv_len;
+ value_add_one( &c->rvalue_vals, &agebv );
+ break;
+ case LOG_SUCCESS:
+ if ( li->li_success )
+ c->value_int = li->li_success;
+ else
+ rc = 1;
+ break;
+ case LOG_OLD:
+ if ( li->li_oldf ) {
+ filter2bv( li->li_oldf, &agebv );
+ ber_bvarray_add( &c->rvalue_vals, &agebv );
+ }
+ else
+ rc = 1;
+ break;
+ case LOG_OLDATTR:
+ if ( li->li_oldattrs ) {
+ log_attr *la;
+
+ for ( la = li->li_oldattrs; la; la=la->next )
+ value_add_one( &c->rvalue_vals, &la->attr->ad_cname );
+ }
+ else
+ rc = 1;
+ break;
+ case LOG_BASE:
+ if ( li->li_bases ) {
+ log_base *lb;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ value_add_one( &c->rvalue_vals, &lb->lb_line );
+ }
+ else
+ rc = 1;
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch( c->type ) {
+ case LOG_DB:
+ /* noop. this should always be a valid backend. */
+ break;
+ case LOG_OPS:
+ if ( c->valx < 0 ) {
+ li->li_ops = 0;
+ } else {
+ rc = verbs_to_mask( 1, &c->line, logops, &tmask );
+ if ( rc == 0 )
+ li->li_ops &= ~tmask;
+ }
+ break;
+ case LOG_PURGE:
+ if ( li->li_task ) {
+ struct re_s *re = li->li_task;
+ li->li_task = NULL;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ))
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ li->li_age = 0;
+ li->li_cycle = 0;
+ break;
+ case LOG_SUCCESS:
+ li->li_success = 0;
+ break;
+ case LOG_OLD:
+ if ( li->li_oldf ) {
+ filter_free( li->li_oldf );
+ li->li_oldf = NULL;
+ }
+ break;
+ case LOG_OLDATTR:
+ if ( c->valx < 0 ) {
+ log_attr *la, *ln;
+
+ for ( la = li->li_oldattrs; la; la = ln ) {
+ ln = la->next;
+ ch_free( la );
+ }
+ } else {
+ log_attr *la = li->li_oldattrs, **lp = &li->li_oldattrs;
+ int i;
+
+ for ( i=0; i < c->valx; i++ ) {
+ la = *lp;
+ lp = &la->next;
+ }
+ *lp = la->next;
+ ch_free( la );
+ }
+ break;
+ case LOG_BASE:
+ if ( c->valx < 0 ) {
+ log_base *lb, *ln;
+
+ for ( lb = li->li_bases; lb; lb = ln ) {
+ ln = lb->lb_next;
+ ch_free( lb );
+ }
+ } else {
+ log_base *lb = li->li_bases, **lp = &li->li_bases;
+ int i;
+
+ for ( i=0; i < c->valx; i++ ) {
+ lb = *lp;
+ lp = &lb->lb_next;
+ }
+ *lp = lb->lb_next;
+ ch_free( lb );
+ }
+ break;
+ }
+ break;
+ default:
+ switch( c->type ) {
+ case LOG_DB:
+ if ( CONFIG_ONLINE_ADD( c )) {
+ li->li_db = select_backend( &c->value_ndn, 0 );
+ if ( !li->li_db ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> no matching backend found for suffix",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ }
+ if ( !rc && ( li->li_db->bd_self == c->be->bd_self )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid suffix, points to itself",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ }
+ ch_free( c->value_ndn.bv_val );
+ } else {
+ li->li_db_suffix = c->value_ndn;
+ }
+ ch_free( c->value_dn.bv_val );
+ break;
+ case LOG_OPS:
+ rc = verbs_to_mask( c->argc, c->argv, logops, &tmask );
+ if ( rc == 0 )
+ li->li_ops |= tmask;
+ break;
+ case LOG_PURGE:
+ li->li_age = log_age_parse( c->argv[1] );
+ if ( li->li_age < 1 ) {
+ rc = 1;
+ } else {
+ li->li_cycle = log_age_parse( c->argv[2] );
+ if ( li->li_cycle < 1 ) {
+ rc = 1;
+ } else if ( slapMode & SLAP_SERVER_MODE ) {
+ struct re_s *re = li->li_task;
+ if ( re )
+ re->interval.tv_sec = li->li_cycle;
+ else if ( li->li_open ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ li->li_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ li->li_cycle, accesslog_purge, li,
+ "accesslog_purge", li->li_db ?
+ li->li_db->be_suffix[0].bv_val :
+ c->be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ }
+ }
+ break;
+ case LOG_SUCCESS:
+ li->li_success = c->value_int;
+ break;
+ case LOG_OLD:
+ li->li_oldf = str2filter( c->argv[1] );
+ if ( !li->li_oldf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "bad filter!" );
+ rc = 1;
+ }
+ break;
+ case LOG_OLDATTR: {
+ int i;
+ AttributeDescription *ad;
+ const char *text;
+ log_attr **lp = &li->li_oldattrs;
+
+ for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ )
+ lp = &(*lp)->next;
+
+ for ( i=1; i< c->argc; i++ ) {
+ ad = NULL;
+ if ( slap_str2ad( c->argv[i], &ad, &text ) == LDAP_SUCCESS ) {
+ log_attr *la = ch_malloc( sizeof( log_attr ));
+ la->attr = ad;
+ if ( *lp ) {
+ la->next = (*lp)->next;
+ } else {
+ la->next = NULL;
+ }
+ *lp = la;
+ lp = &la->next;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s <%s>: %s",
+ c->argv[0], c->argv[i], text );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ }
+ }
+ break;
+ case LOG_BASE: {
+ int i;
+ slap_mask_t m = 0;
+ log_base **lp = &li->li_bases;
+
+ for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ )
+ lp = &(*lp)->lb_next;
+
+ rc = verbstring_to_mask( logops, c->argv[1], '|', &m );
+ if ( rc == 0 ) {
+ struct berval dn, ndn;
+ ber_str2bv( c->argv[2], 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+ if ( rc == 0 ) {
+ log_base *lb;
+ struct berval mbv;
+ char *ptr;
+ mask_to_verbstring( logops, m, '|', &mbv );
+ lb = ch_malloc( sizeof( log_base ) + mbv.bv_len + ndn.bv_len + 3 + 1 );
+ lb->lb_line.bv_val = (char *)(lb + 1);
+ lb->lb_line.bv_len = mbv.bv_len + ndn.bv_len + 3;
+ ptr = lutil_strcopy( lb->lb_line.bv_val, mbv.bv_val );
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ lb->lb_base.bv_val = ptr;
+ lb->lb_base.bv_len = ndn.bv_len;
+ ptr = lutil_strcopy( ptr, ndn.bv_val );
+ *ptr++ = '"';
+ lb->lb_ops = m;
+ lb->lb_next = li->li_bases;
+ if ( *lp ) {
+ lb->lb_next = (*lp)->lb_next;
+ } else {
+ lb->lb_next = NULL;
+ }
+ *lp = lb;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: %s",
+ c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid ops: %s",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return rc;
+}
+
+static int
+logSchemaControlValidate(
+ Syntax *syntax,
+ struct berval *valp )
+{
+ struct berval val, bv;
+ ber_len_t i;
+ int rc = LDAP_SUCCESS;
+
+ assert( valp != NULL );
+
+ val = *valp;
+
+ /* check minimal size */
+ if ( val.bv_len < STRLENOF( "{*}" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ val.bv_len--;
+
+ /* check SEQUENCE boundaries */
+ if ( val.bv_val[ 0 ] != '{' /*}*/ ||
+ val.bv_val[ val.bv_len ] != /*{*/ '}' )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* extract and check OID */
+ for ( i = 1; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ bv.bv_val = &val.bv_val[ i ];
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( ASCII_SPACE( val.bv_val[ i ] ) )
+ {
+ break;
+ }
+ }
+
+ bv.bv_len = &val.bv_val[ i ] - bv.bv_val;
+
+ rc = numericoidValidate( NULL, &bv );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( val.bv_val[ i ] != ' ' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* extract and check criticality */
+ if ( strncasecmp( &val.bv_val[ i ], "criticality ", STRLENOF( "criticality " ) ) == 0 )
+ {
+ i += STRLENOF( "criticality " );
+ for ( ; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_val = &val.bv_val[ i ];
+
+ for ( ; i < val.bv_len; i++ ) {
+ if ( ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ bv.bv_len = &val.bv_val[ i ] - bv.bv_val;
+
+ if ( !bvmatch( &bv, &slap_true_bv ) && !bvmatch( &bv, &slap_false_bv ) )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( val.bv_val[ i ] != ' ' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* extract and check controlValue */
+ if ( strncasecmp( &val.bv_val[ i ], "controlValue ", STRLENOF( "controlValue " ) ) == 0 )
+ {
+ ber_len_t valueStart, valueLen;
+
+ i += STRLENOF( "controlValue " );
+ for ( ; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( val.bv_val[ i ] != '"' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ i++;
+ valueStart = i;
+
+ for ( ; i < val.bv_len; i++ ) {
+ if ( val.bv_val[ i ] == '"' ) {
+ break;
+ }
+
+ if ( !ASCII_HEX( val.bv_val[ i ] ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( val.bv_val[ i ] != '"' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ valueLen = i - valueStart;
+ if ( (valueLen/2)*2 != valueLen ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+accesslog_ctrls(
+ LDAPControl **ctrls,
+ BerVarray *valsp,
+ BerVarray *nvalsp,
+ void *memctx )
+{
+ long i, rc = 0;
+
+ assert( valsp != NULL );
+ assert( ctrls != NULL );
+
+ *valsp = NULL;
+ *nvalsp = NULL;
+
+ for ( i = 0; ctrls[ i ] != NULL; i++ ) {
+ struct berval idx,
+ oid,
+ noid,
+ bv;
+ char *ptr,
+ buf[ 32 ];
+
+ if ( ctrls[ i ]->ldctl_oid == NULL ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ idx.bv_len = snprintf( buf, sizeof( buf ), "{%ld}", i );
+ idx.bv_val = buf;
+
+ ber_str2bv( ctrls[ i ]->ldctl_oid, 0, 0, &oid );
+ noid.bv_len = idx.bv_len + oid.bv_len;
+ ptr = noid.bv_val = ber_memalloc_x( noid.bv_len + 1, memctx );
+ ptr = lutil_strcopy( ptr, idx.bv_val );
+ ptr = lutil_strcopy( ptr, oid.bv_val );
+
+ bv.bv_len = idx.bv_len + STRLENOF( "{}" ) + oid.bv_len;
+
+ if ( ctrls[ i ]->ldctl_iscritical ) {
+ bv.bv_len += STRLENOF( " criticality TRUE" );
+ }
+
+ if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) {
+ bv.bv_len += STRLENOF( " controlValue \"\"" )
+ + 2 * ctrls[ i ]->ldctl_value.bv_len;
+ }
+
+ ptr = bv.bv_val = ber_memalloc_x( bv.bv_len + 1, memctx );
+ if ( ptr == NULL ) {
+ ber_bvarray_free( *valsp );
+ *valsp = NULL;
+ ber_bvarray_free( *nvalsp );
+ *nvalsp = NULL;
+ return LDAP_OTHER;
+ }
+
+ ptr = lutil_strcopy( ptr, idx.bv_val );
+
+ *ptr++ = '{' /*}*/ ;
+ ptr = lutil_strcopy( ptr, oid.bv_val );
+
+ if ( ctrls[ i ]->ldctl_iscritical ) {
+ ptr = lutil_strcopy( ptr, " criticality TRUE" );
+ }
+
+ if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) {
+ ber_len_t j;
+
+ ptr = lutil_strcopy( ptr, " controlValue \"" );
+ for ( j = 0; j < ctrls[ i ]->ldctl_value.bv_len; j++ ) {
+ *ptr++ = SLAP_ESCAPE_HI(ctrls[ i ]->ldctl_value.bv_val[ j ]);
+ *ptr++ = SLAP_ESCAPE_LO(ctrls[ i ]->ldctl_value.bv_val[ j ]);
+ }
+
+ *ptr++ = '"';
+ }
+
+ *ptr++ = '}';
+ *ptr = '\0';
+
+ ber_bvarray_add_x( valsp, &bv, memctx );
+ ber_bvarray_add_x( nvalsp, &noid, memctx );
+ }
+
+ return rc;
+
+}
+
+static Entry *accesslog_entry( Operation *op, SlapReply *rs,
+ log_info *li, int logop, Operation *op2 ) {
+
+ char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
+ char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
+
+ struct berval rdn, nrdn, timestamp, ntimestamp, bv;
+ slap_verbmasks *lo = logops+logop+EN_OFFSET;
+
+ Entry *e = entry_alloc();
+
+ strcpy( rdnbuf, RDNEQ );
+ rdn.bv_val = rdnbuf;
+ strcpy( nrdnbuf, RDNEQ );
+ nrdn.bv_val = nrdnbuf;
+
+ timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ);
+ timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);
+ slap_timestamp( &op->o_time, &timestamp );
+ snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op->o_tincr );
+ timestamp.bv_len += STRLENOF(".123456");
+
+ rdn.bv_len = STRLENOF(RDNEQ)+timestamp.bv_len;
+ ad_reqStart->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, ad_reqStart->ad_type->sat_syntax,
+ ad_reqStart->ad_type->sat_equality, &timestamp, &ntimestamp,
+ op->o_tmpmemctx );
+
+ strcpy( nrdn.bv_val + STRLENOF(RDNEQ), ntimestamp.bv_val );
+ nrdn.bv_len = STRLENOF(RDNEQ)+ntimestamp.bv_len;
+ build_new_dn( &e->e_name, li->li_db->be_suffix, &rdn, NULL );
+ build_new_dn( &e->e_nname, li->li_db->be_nsuffix, &nrdn, NULL );
+
+ attr_merge_one( e, slap_schema.si_ad_objectClass,
+ &log_ocs[logop]->soc_cname, NULL );
+ attr_merge_one( e, slap_schema.si_ad_structuralObjectClass,
+ &log_ocs[logop]->soc_cname, NULL );
+ attr_merge_one( e, ad_reqStart, &timestamp, &ntimestamp );
+ op->o_tmpfree( ntimestamp.bv_val, op->o_tmpmemctx );
+
+ slap_op_time( &op2->o_time, &op2->o_tincr );
+
+ timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);
+ slap_timestamp( &op2->o_time, &timestamp );
+ snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op2->o_tincr );
+ timestamp.bv_len += STRLENOF(".123456");
+
+ attr_merge_normalize_one( e, ad_reqEnd, &timestamp, op->o_tmpmemctx );
+
+ /* Exops have OID appended */
+ if ( logop == LOG_EN_EXTENDED ) {
+ bv.bv_len = lo->word.bv_len + op->ore_reqoid.bv_len + 2;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ AC_MEMCPY( bv.bv_val, lo->word.bv_val, lo->word.bv_len );
+ bv.bv_val[lo->word.bv_len] = '{';
+ AC_MEMCPY( bv.bv_val+lo->word.bv_len+1, op->ore_reqoid.bv_val,
+ op->ore_reqoid.bv_len );
+ bv.bv_val[bv.bv_len-1] = '}';
+ bv.bv_val[bv.bv_len] = '\0';
+ attr_merge_one( e, ad_reqType, &bv, NULL );
+ } else {
+ attr_merge_one( e, ad_reqType, &lo->word, NULL );
+ }
+
+ rdn.bv_len = snprintf( rdn.bv_val, sizeof( rdnbuf ), "%lu", op->o_connid );
+ if ( rdn.bv_len < sizeof( rdnbuf ) ) {
+ attr_merge_one( e, ad_reqSession, &rdn, NULL );
+ } /* else? */
+
+ if ( BER_BVISNULL( &op->o_dn ) ) {
+ attr_merge_one( e, ad_reqAuthzID, (struct berval *)&slap_empty_bv,
+ (struct berval *)&slap_empty_bv );
+ } else {
+ attr_merge_one( e, ad_reqAuthzID, &op->o_dn, &op->o_ndn );
+ }
+
+ /* FIXME: need to add reqControls and reqRespControls */
+ if ( op->o_ctrls ) {
+ BerVarray vals = NULL,
+ nvals = NULL;
+
+ if ( accesslog_ctrls( op->o_ctrls, &vals, &nvals,
+ op->o_tmpmemctx ) == LDAP_SUCCESS && vals )
+ {
+ attr_merge( e, ad_reqControls, vals, nvals );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( nvals, op->o_tmpmemctx );
+ }
+ }
+
+ if ( rs->sr_ctrls ) {
+ BerVarray vals = NULL,
+ nvals = NULL;
+
+ if ( accesslog_ctrls( rs->sr_ctrls, &vals, &nvals,
+ op->o_tmpmemctx ) == LDAP_SUCCESS && vals )
+ {
+ attr_merge( e, ad_reqRespControls, vals, nvals );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( nvals, op->o_tmpmemctx );
+ }
+
+ }
+
+ return e;
+}
+
+static struct berval scopes[] = {
+ BER_BVC("base"),
+ BER_BVC("one"),
+ BER_BVC("sub"),
+ BER_BVC("subord")
+};
+
+static struct berval derefs[] = {
+ BER_BVC("never"),
+ BER_BVC("searching"),
+ BER_BVC("finding"),
+ BER_BVC("always")
+};
+
+static struct berval simple = BER_BVC("SIMPLE");
+
+static void accesslog_val2val(AttributeDescription *ad, struct berval *val,
+ char c_op, struct berval *dst) {
+ char *ptr;
+
+ dst->bv_len = ad->ad_cname.bv_len + val->bv_len + 2;
+ if ( c_op ) dst->bv_len++;
+
+ dst->bv_val = ch_malloc( dst->bv_len+1 );
+
+ ptr = lutil_strcopy( dst->bv_val, ad->ad_cname.bv_val );
+ *ptr++ = ':';
+ if ( c_op )
+ *ptr++ = c_op;
+ *ptr++ = ' ';
+ AC_MEMCPY( ptr, val->bv_val, val->bv_len );
+ dst->bv_val[dst->bv_len] = '\0';
+}
+
+static int
+accesslog_op2logop( Operation *op )
+{
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD: return LOG_EN_ADD;
+ case LDAP_REQ_DELETE: return LOG_EN_DELETE;
+ case LDAP_REQ_MODIFY: return LOG_EN_MODIFY;
+ case LDAP_REQ_MODRDN: return LOG_EN_MODRDN;
+ case LDAP_REQ_COMPARE: return LOG_EN_COMPARE;
+ case LDAP_REQ_SEARCH: return LOG_EN_SEARCH;
+ case LDAP_REQ_BIND: return LOG_EN_BIND;
+ case LDAP_REQ_EXTENDED: return LOG_EN_EXTENDED;
+ default: /* unknown operation type */
+ break;
+ } /* Unbind and Abandon never reach here */
+ return LOG_EN_UNKNOWN;
+}
+
+static int
+accesslog_response(Operation *op, SlapReply *rs)
+{
+ slap_callback *sc = op->o_callback;
+ slap_overinst *on = (slap_overinst *)sc->sc_private;
+ log_info *li = on->on_bi.bi_private;
+ Attribute *a, *last_attr;
+ Modifications *m;
+ struct berval *b, uuid = BER_BVNULL;
+ int i, success;
+ int logop;
+ slap_verbmasks *lo;
+ Entry *e = NULL, *old = NULL, *e_uuid = NULL;
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8];
+ struct berval bv;
+ char *ptr;
+ BerVarray vals;
+ Operation op2 = {0};
+ SlapReply rs2 = {REP_RESULT};
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+
+ /* ITS#9051 Make sure we only remove the callback on a final response */
+ if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED &&
+ rs->sr_type != REP_SASL )
+ return SLAP_CB_CONTINUE;
+
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ /* can't do anything if logDB isn't open */
+ if ( !SLAP_DBOPEN( li->li_db ) ) {
+ goto skip;
+ }
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ goto skip;
+
+ /*
+ * ITS#9051 Technically LDAP_REFERRAL and LDAP_SASL_BIND_IN_PROGRESS
+ * are not errors, but they aren't really success either
+ */
+ success = rs->sr_err == LDAP_SUCCESS ||
+ rs->sr_err == LDAP_COMPARE_TRUE ||
+ rs->sr_err == LDAP_COMPARE_FALSE;
+ if ( li->li_success && !success )
+ goto skip;
+
+ if ( !( li->li_ops & lo->mask ) ) {
+ log_base *lb;
+
+ i = 0;
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ goto skip;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ if ( !( lo->mask & LOG_OP_WRITES ) ) {
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+ }
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( !success || BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ if ( !( lo->mask & LOG_OP_WRITES ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_response: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ }
+ slap_queue_csn( &op2, &op->o_csn );
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ old = li->li_old;
+ uuid = li->li_uuid;
+ li->li_old = NULL;
+ BER_BVZERO( &li->li_uuid );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ e = accesslog_entry( op, rs, li, logop, &op2 );
+
+ if ( !BER_BVISNULL( &op->o_req_ndn ))
+ attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn );
+
+ if ( rs->sr_text ) {
+ ber_str2bv( rs->sr_text, 0, 0, &bv );
+ attr_merge_normalize_one( e, ad_reqMessage, &bv, op->o_tmpmemctx );
+ }
+ bv.bv_len = snprintf( timebuf, sizeof( timebuf ), "%d", rs->sr_err );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ bv.bv_val = timebuf;
+ attr_merge_one( e, ad_reqResult, &bv, NULL );
+ }
+
+ last_attr = attr_find( e->e_attrs, ad_reqResult );
+
+ e_uuid = old;
+ switch( logop ) {
+ case LOG_EN_ADD:
+ case LOG_EN_DELETE: {
+ char c_op;
+ Entry *e2;
+
+ if ( logop == LOG_EN_ADD ) {
+ e2 = op->ora_e;
+ e_uuid = op->ora_e;
+ c_op = '+';
+
+ } else {
+ if ( !old )
+ break;
+ e2 = old;
+ c_op = 0;
+ }
+ /* count all the vals */
+ i = 0;
+ for ( a=e2->e_attrs; a; a=a->a_next ) {
+ i += a->a_numvals;
+ }
+ vals = ch_malloc( (i+1) * sizeof( struct berval ));
+ i = 0;
+ for ( a=e2->e_attrs; a; a=a->a_next ) {
+ if ( a->a_vals ) {
+ for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {
+ accesslog_val2val( a->a_desc, b, c_op, &vals[i] );
+ }
+ }
+ }
+ vals[i].bv_val = NULL;
+ vals[i].bv_len = 0;
+ a = attr_alloc( logop == LOG_EN_ADD ? ad_reqMod : ad_reqOld );
+ a->a_numvals = i;
+ a->a_vals = vals;
+ a->a_nvals = vals;
+ last_attr->a_next = a;
+ break;
+ }
+
+ case LOG_EN_MODRDN:
+ case LOG_EN_MODIFY:
+ /* count all the mods + attributes (ITS#6545) */
+ i = 0;
+ for ( m = op->orm_modlist; m; m = m->sml_next ) {
+ if ( m->sml_values ) {
+ i += m->sml_numvals;
+ } else if ( m->sml_op == LDAP_MOD_DELETE ||
+ m->sml_op == SLAP_MOD_SOFTDEL ||
+ m->sml_op == LDAP_MOD_REPLACE )
+ {
+ i++;
+ }
+ if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc ) {
+ i++;
+ }
+ }
+ vals = ch_malloc( (i+1) * sizeof( struct berval ));
+ i = 0;
+
+ /* init flags on old entry */
+ if ( old ) {
+ for ( a = old->e_attrs; a; a = a->a_next ) {
+ log_attr *la;
+ a->a_flags = 0;
+
+ /* look for attrs that are always logged */
+ for ( la = li->li_oldattrs; la; la = la->next ) {
+ if ( a->a_desc == la->attr ) {
+ a->a_flags = 1;
+ }
+ }
+ }
+ }
+
+ for ( m = op->orm_modlist; m; m = m->sml_next ) {
+ /* Mark this attribute as modified */
+ if ( old ) {
+ a = attr_find( old->e_attrs, m->sml_desc );
+ if ( a ) {
+ a->a_flags = 1;
+ }
+ }
+
+ /* don't log the RDN mods; they're explicitly logged later */
+ if ( logop == LOG_EN_MODRDN &&
+ ( m->sml_op == SLAP_MOD_SOFTADD ||
+ m->sml_op == LDAP_MOD_DELETE ) )
+ {
+ continue;
+ }
+
+ if ( m->sml_values ) {
+ for ( b = m->sml_values; !BER_BVISNULL( b ); b++, i++ ) {
+ char c_op;
+
+ switch ( m->sml_op ) {
+ case LDAP_MOD_ADD: /* FALLTHRU */
+ case SLAP_MOD_SOFTADD: c_op = '+'; break;
+ case LDAP_MOD_DELETE: /* FALLTHRU */
+ case SLAP_MOD_SOFTDEL: c_op = '-'; break;
+ case LDAP_MOD_REPLACE: c_op = '='; break;
+ case LDAP_MOD_INCREMENT: c_op = '#'; break;
+
+ /* unknown op. there shouldn't be any of these. we
+ * don't know what to do with it, but we shouldn't just
+ * ignore it.
+ */
+ default: c_op = '?'; break;
+ }
+ accesslog_val2val( m->sml_desc, b, c_op, &vals[i] );
+ }
+ } else if ( m->sml_op == LDAP_MOD_DELETE ||
+ m->sml_op == SLAP_MOD_SOFTDEL ||
+ m->sml_op == LDAP_MOD_REPLACE )
+ {
+ vals[i].bv_len = m->sml_desc->ad_cname.bv_len + 2;
+ vals[i].bv_val = ch_malloc( vals[i].bv_len + 1 );
+ ptr = lutil_strcopy( vals[i].bv_val,
+ m->sml_desc->ad_cname.bv_val );
+ *ptr++ = ':';
+ if ( m->sml_op == LDAP_MOD_DELETE || m->sml_op == SLAP_MOD_SOFTDEL ) {
+ *ptr++ = '-';
+ } else {
+ *ptr++ = '=';
+ }
+ *ptr = '\0';
+ i++;
+ }
+ /* ITS#6545: when the same attribute is edited multiple times,
+ * record the transition */
+ if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc &&
+ m->sml_op == m->sml_next->sml_op ) {
+ ber_str2bv( ":", STRLENOF(":"), 1, &vals[i] );
+ i++;
+ }
+ }
+
+ if ( i > 0 ) {
+ BER_BVZERO( &vals[i] );
+ a = attr_alloc( ad_reqMod );
+ a->a_numvals = i;
+ a->a_vals = vals;
+ a->a_nvals = vals;
+ last_attr->a_next = a;
+ last_attr = a;
+
+ } else {
+ ch_free( vals );
+ }
+
+ if ( old ) {
+ /* count all the vals */
+ i = 0;
+ for ( a = old->e_attrs; a != NULL; a = a->a_next ) {
+ if ( a->a_vals && a->a_flags ) {
+ i += a->a_numvals;
+ }
+ }
+ if ( i ) {
+ vals = ch_malloc( (i + 1) * sizeof( struct berval ) );
+ i = 0;
+ for ( a=old->e_attrs; a; a=a->a_next ) {
+ if ( a->a_vals && a->a_flags ) {
+ for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {
+ accesslog_val2val( a->a_desc, b, 0, &vals[i] );
+ }
+ }
+ }
+ vals[i].bv_val = NULL;
+ vals[i].bv_len = 0;
+ a = attr_alloc( ad_reqOld );
+ a->a_numvals = i;
+ a->a_vals = vals;
+ a->a_nvals = vals;
+ last_attr->a_next = a;
+ }
+ }
+ if ( logop == LOG_EN_MODIFY ) {
+ break;
+ }
+
+ /* Now log the actual modRDN info */
+ attr_merge_one( e, ad_reqNewRDN, &op->orr_newrdn, &op->orr_nnewrdn );
+ attr_merge_one( e, ad_reqDeleteOldRDN, op->orr_deleteoldrdn ?
+ (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,
+ NULL );
+ if ( op->orr_newSup ) {
+ attr_merge_one( e, ad_reqNewSuperior, op->orr_newSup, op->orr_nnewSup );
+ }
+ attr_merge_one( e, ad_reqNewDN, &op->orr_newDN, &op->orr_nnewDN );
+ break;
+
+ case LOG_EN_COMPARE:
+ bv.bv_len = op->orc_ava->aa_desc->ad_cname.bv_len + 1 +
+ op->orc_ava->aa_value.bv_len;
+ bv.bv_val = op->o_tmpalloc( bv.bv_len+1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( bv.bv_val, op->orc_ava->aa_desc->ad_cname.bv_val );
+ *ptr++ = '=';
+ AC_MEMCPY( ptr, op->orc_ava->aa_value.bv_val, op->orc_ava->aa_value.bv_len );
+ bv.bv_val[bv.bv_len] = '\0';
+ attr_merge_one( e, ad_reqAssertion, &bv, NULL );
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LOG_EN_SEARCH:
+ attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL );
+ attr_merge_one( e, ad_reqDerefAliases, &derefs[op->ors_deref], NULL );
+ attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ?
+ (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,
+ NULL );
+ if ( !BER_BVISEMPTY( &op->ors_filterstr ))
+ attr_merge_normalize_one( e, ad_reqFilter, &op->ors_filterstr, op->o_tmpmemctx );
+ if ( op->ors_attrs ) {
+ int j;
+ /* count them */
+ for (i=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++)
+ ;
+ vals = op->o_tmpalloc( (i+1) * sizeof(struct berval),
+ op->o_tmpmemctx );
+ for (i=0, j=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) {
+ if (!BER_BVISEMPTY(&op->ors_attrs[i].an_name)) {
+ vals[j] = op->ors_attrs[i].an_name;
+ j++;
+ }
+ }
+ BER_BVZERO(&vals[j]);
+ attr_merge_normalize( e, ad_reqAttr, vals, op->o_tmpmemctx );
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ }
+ bv.bv_val = timebuf;
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", rs->sr_nentries );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqEntries, &bv, NULL );
+ } /* else? */
+
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_tlimit );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqTimeLimit, &bv, NULL );
+ } /* else? */
+
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_slimit );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqSizeLimit, &bv, NULL );
+ } /* else? */
+ break;
+
+ case LOG_EN_BIND:
+ bv.bv_val = timebuf;
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->o_protocol );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqVersion, &bv, NULL );
+ } /* else? */
+ if ( op->orb_method == LDAP_AUTH_SIMPLE ) {
+ attr_merge_normalize_one( e, ad_reqMethod, &simple, op->o_tmpmemctx );
+ } else {
+ bv.bv_len = STRLENOF("SASL()") + op->orb_mech.bv_len;
+ bv.bv_val = op->o_tmpalloc( bv.bv_len + 1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( bv.bv_val, "SASL(" );
+ ptr = lutil_strcopy( ptr, op->orb_mech.bv_val );
+ *ptr++ = ')';
+ *ptr = '\0';
+ attr_merge_normalize_one( e, ad_reqMethod, &bv, op->o_tmpmemctx );
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LOG_EN_EXTENDED:
+ if ( op->ore_reqdata ) {
+ attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL );
+ }
+ break;
+
+ case LOG_EN_UNKNOWN:
+ /* we don't know its parameters, don't add any */
+ break;
+ }
+
+ if ( e_uuid || !BER_BVISNULL( &uuid ) ) {
+ struct berval *pbv = NULL;
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ pbv = &uuid;
+
+ } else {
+ a = attr_find( e_uuid->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a ) {
+ pbv = &a->a_vals[0];
+ }
+ }
+
+ if ( pbv ) {
+ attr_merge_normalize_one( e, ad_reqEntryUUID, pbv, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ ber_memfree( uuid.bv_val );
+ BER_BVZERO( &uuid );
+ }
+ }
+
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ /* contextCSN updates may still reach here */
+ op2.o_dont_replicate = op->o_dont_replicate;
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_response: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ if ( e == op2.ora_e ) entry_free( e );
+ e = NULL;
+
+ if ( ( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn ) ) {
+ Modifications mod;
+ int i, sid = slap_parse_csn_sid( &op->o_csn );
+
+ for ( i=0; i < li->li_numcsns; i++ ) {
+ if ( sid <= li->li_sids[i] ) break;
+ }
+ if ( i >= li->li_numcsns || sid != li->li_sids[i] ) {
+ /* SID not in minCSN set, add */
+ struct berval bv[2];
+
+ Debug( LDAP_DEBUG_TRACE, "accesslog_response: "
+ "adding minCSN %s\n",
+ op->o_csn.bv_val );
+ slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i,
+ sid, &op->o_csn );
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_req_dn = li->li_db->be_suffix[0];
+ op2.o_req_ndn = li->li_db->be_nsuffix[0];
+
+ bv[0] = op->o_csn;
+ BER_BVZERO( &bv[1] );
+
+ mod.sml_numvals = 1;
+ mod.sml_values = bv;
+ mod.sml_nvalues = bv;
+ mod.sml_desc = ad_minCSN;
+ mod.sml_op = LDAP_MOD_ADD;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op2.orm_modlist = &mod;
+ op2.orm_no_opattrs = 1;
+
+ Debug( LDAP_DEBUG_SYNC, "accesslog_response: "
+ "adding a new csn=%s into minCSN\n",
+ bv[0].bv_val );
+ rs_reinit( &rs2, REP_RESULT );
+ op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "accesslog_response: "
+ "got result 0x%x adding minCSN %s\n",
+ rs2.sr_err, op->o_csn.bv_val );
+ }
+ } else if ( ber_bvcmp( &op->o_csn, &li->li_mincsn[i] ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "accesslog_response: "
+ "csn=%s older than existing minCSN csn=%s for this sid\n",
+ op->o_csn.bv_val, li->li_mincsn[i].bv_val );
+ }
+ }
+
+done:
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ if ( old ) entry_free( old );
+ return SLAP_CB_CONTINUE;
+
+skip:
+ if ( lo->mask & LOG_OP_WRITES ) {
+ /* We haven't transitioned to li_log_mutex yet */
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_op_misc( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc;
+ slap_verbmasks *lo;
+ int logop;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ /* ignore these internal reads */
+ if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ sc->sc_response = accesslog_response;
+ sc->sc_private = op->o_bd->bd_info;
+
+ if ( op->o_callback ) {
+ sc->sc_next = op->o_callback->sc_next;
+ op->o_callback->sc_next = sc;
+ } else {
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ slap_verbmasks *lo;
+ slap_callback *cb;
+ int logop;
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ /* can't do anything if logDB isn't open */
+ if ( !SLAP_DBOPEN( li->li_db ))
+ return SLAP_CB_CONTINUE;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ if ( !( li->li_ops & lo->mask )) {
+ log_base *lb;
+ int i = 0;
+
+ for ( lb = li->li_bases; lb; lb = lb->lb_next )
+ if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ cb = op->o_tmpcalloc( 1, sizeof( slap_callback ), op->o_tmpmemctx );
+ cb->sc_cleanup = accesslog_response;
+ cb->sc_response = accesslog_response;
+ cb->sc_private = on;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+
+ if ( li->li_oldf && ( op->o_tag == LDAP_REQ_DELETE ||
+ op->o_tag == LDAP_REQ_MODIFY ||
+ ( op->o_tag == LDAP_REQ_MODRDN && li->li_oldattrs )))
+ {
+ int rc;
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( e ) {
+ if ( test_filter( op, e, li->li_oldf ) == LDAP_COMPARE_TRUE )
+ li->li_old = entry_dup( e );
+ be_entry_release_rw( op, e, 0 );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ } else {
+ int rc;
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( e ) {
+ Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a ) {
+ ber_dupbv( &li->li_uuid, &a->a_vals[0] );
+ }
+ be_entry_release_rw( op, e, 0 );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* unbinds are broadcast to all backends; we only log it if this
+ * backend was used for the original bind.
+ */
+static int
+accesslog_unbind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ Operation op2 = {};
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ void *cids[SLAP_MAX_CIDS];
+ SlapReply rs2 = {REP_RESULT};
+ Entry *e;
+
+ if ( op->o_conn->c_authz_backend != on->on_info->oi_origdb )
+ return SLAP_CB_CONTINUE;
+
+ if ( !( li->li_ops & LOG_OP_UNBIND ) ) {
+ log_base *lb;
+ int i = 0;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & LOG_OP_UNBIND ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_unbind: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ op2.o_csn = op->o_csn;
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ e = accesslog_entry( op, rs, li, LOG_EN_UNBIND, &op2 );
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ op2.o_controls = cids;
+ memset(cids, 0, sizeof( cids ));
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_unbind: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+
+ if ( e == op2.ora_e )
+ entry_free( e );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_abandon( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ Operation op2 = {0};
+ void *cids[SLAP_MAX_CIDS];
+ SlapReply rs2 = {REP_RESULT};
+ Entry *e;
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ char buf[64];
+ struct berval bv;
+
+ if ( !op->o_time )
+ return SLAP_CB_CONTINUE;
+
+ if ( !( li->li_ops & LOG_OP_ABANDON )) {
+ log_base *lb;
+ int i = 0;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & LOG_OP_ABANDON ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_abandon: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ op2.o_csn = op->o_csn;
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ e = accesslog_entry( op, rs, li, LOG_EN_ABANDON, &op2 );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", op->orn_msgid );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ attr_merge_one( e, ad_reqId, &bv, NULL );
+ } /* else? */
+
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ op2.o_controls = cids;
+ memset(cids, 0, sizeof( cids ));
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_abandon: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ if ( e == op2.ora_e )
+ entry_free( e );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_operational( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+
+ if ( op->o_sync != SLAP_CONTROL_NONE )
+ return SLAP_CB_CONTINUE;
+
+ if ( rs->sr_entry != NULL
+ && dn_match( &op->o_bd->be_nsuffix[0], &rs->sr_entry->e_nname ) )
+ {
+ Attribute **ap;
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
+ /* just count */ ;
+
+ if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( ad_auditContext, rs->sr_attrs ) )
+ {
+ *ap = attr_alloc( ad_auditContext );
+ attr_valadd( *ap,
+ &li->li_db->be_suffix[0],
+ &li->li_db->be_nsuffix[0], 1 );
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst accesslog;
+
+static int
+accesslog_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = ch_calloc(1, sizeof(log_info));
+
+ on->on_bi.bi_private = li;
+ ldap_pvt_thread_mutex_recursive_init( &li->li_op_rmutex );
+ ldap_pvt_thread_mutex_init( &li->li_log_mutex );
+ return 0;
+}
+
+static int
+accesslog_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ log_attr *la;
+
+ if ( li->li_oldf )
+ filter_free( li->li_oldf );
+ for ( la=li->li_oldattrs; la; la=li->li_oldattrs ) {
+ li->li_oldattrs = la->next;
+ ch_free( la );
+ }
+ if ( li->li_sids )
+ ch_free( li->li_sids );
+ if ( li->li_mincsn )
+ ber_bvarray_free( li->li_mincsn );
+ if ( li->li_db_suffix.bv_val )
+ ch_free( li->li_db_suffix.bv_val );
+ ldap_pvt_thread_mutex_destroy( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_destroy( &li->li_op_rmutex );
+ free( li );
+ return LDAP_SUCCESS;
+}
+
+/* Create the logdb's root entry if it's missing, load mincsn */
+static void *
+accesslog_db_root(
+ void *ctx,
+ void *arg )
+{
+ struct re_s *rtask = arg;
+ slap_overinst *on = rtask->arg;
+ log_info *li = on->on_bi.bi_private;
+
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+
+ Entry *e;
+ int rc;
+
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+ op->o_bd = li->li_db;
+ op->o_dn = li->li_db->be_rootdn;
+ op->o_ndn = li->li_db->be_rootndn;
+ rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e );
+
+ if ( e ) {
+ Attribute *a = attr_find( e->e_attrs, ad_minCSN );
+ if ( !a ) {
+ /* TODO: find the lowest CSN we are safe to put in */
+ a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ SlapReply rs = {REP_RESULT};
+ Modifications mod;
+ BackendDB db = *li->li_db;
+
+ op->o_bd = &db;
+
+ mod.sml_numvals = a->a_numvals;
+ mod.sml_values = a->a_vals;
+ mod.sml_nvalues = a->a_nvals;
+ mod.sml_desc = ad_minCSN;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ op->o_callback = &nullsc;
+ SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD;
+
+ Debug( LDAP_DEBUG_SYNC, "accesslog_db_root: "
+ "setting up minCSN with %d values\n",
+ a->a_numvals );
+
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ rc = op->o_bd->be_modify( op, &rs );
+ }
+ }
+ if ( a ) {
+ ber_bvarray_dup_x( &li->li_mincsn, a->a_vals, NULL );
+ li->li_numcsns = a->a_numvals;
+ li->li_sids = slap_parse_csn_sids( li->li_mincsn, li->li_numcsns, NULL );
+ slap_sort_csn_sids( li->li_mincsn, li->li_sids, li->li_numcsns, NULL );
+ }
+ be_entry_release_rw( op, e, 0 );
+ } else {
+ SlapReply rs = {REP_RESULT};
+ struct berval rdn, nrdn, attr;
+ char *ptr;
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+ Entry *e_ctx;
+ BackendDB db;
+
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, li->li_db->be_suffix );
+ ber_dupbv( &e->e_nname, li->li_db->be_nsuffix );
+
+ attr_merge_one( e, slap_schema.si_ad_objectClass,
+ &log_container->soc_cname, NULL );
+
+ dnRdn( &e->e_name, &rdn );
+ dnRdn( &e->e_nname, &nrdn );
+ ptr = ber_bvchr( &rdn, '=' );
+
+ assert( ptr != NULL );
+
+ attr.bv_val = rdn.bv_val;
+ attr.bv_len = ptr - rdn.bv_val;
+
+ slap_bv2ad( &attr, &ad, &text );
+
+ rdn.bv_val = ptr+1;
+ rdn.bv_len -= attr.bv_len + 1;
+ ptr = ber_bvchr( &nrdn, '=' );
+ nrdn.bv_len -= ptr - nrdn.bv_val + 1;
+ nrdn.bv_val = ptr+1;
+ attr_merge_one( e, ad, &rdn, &nrdn );
+
+ /* Get contextCSN from main DB */
+ op->o_bd = on->on_info->oi_origdb;
+ rc = be_entry_get_rw( op, op->o_bd->be_nsuffix, NULL,
+ slap_schema.si_ad_contextCSN, 0, &e_ctx );
+
+ if ( e_ctx ) {
+ Attribute *a;
+
+ a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ /* FIXME: contextCSN could have multiple values!
+ * should select the one with the server's SID */
+ attr_merge_one( e, slap_schema.si_ad_entryCSN,
+ &a->a_vals[0], &a->a_nvals[0] );
+ attr_merge( e, a->a_desc, a->a_vals, a->a_nvals );
+ attr_merge( e, ad_minCSN, a->a_vals, a->a_nvals );
+ }
+ be_entry_release_rw( op, e_ctx, 0 );
+ }
+ db = *li->li_db;
+ op->o_bd = &db;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->ora_e = e;
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ op->o_callback = &nullsc;
+ SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD;
+ rc = op->o_bd->be_add( op, &rs );
+ if ( rs.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_db_root: "
+ "got result 0x%x adding log root entry %s\n",
+ op->o_log_prefix, rs.sr_err, op->o_req_dn.bv_val );
+ }
+ if ( e == op->ora_e )
+ entry_free( e );
+ }
+ li->li_open = 1;
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_runqueue_remove( &slapd_rq, rtask );
+
+ if ( li->li_age && li->li_cycle ) {
+ assert( li->li_task == NULL );
+ li->li_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ li->li_cycle, accesslog_purge, li,
+ "accesslog_purge", li->li_db->be_suffix[0].bv_val );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+static int
+accesslog_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+
+
+ if ( !BER_BVISEMPTY( &li->li_db_suffix )) {
+ li->li_db = select_backend( &li->li_db_suffix, 0 );
+ ch_free( li->li_db_suffix.bv_val );
+ BER_BVZERO( &li->li_db_suffix );
+ }
+ if ( li->li_db == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog: \"logdb <suffix>\" missing or invalid.\n" );
+ return 1;
+ }
+ if ( li->li_db->bd_self == be->bd_self ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog: \"logdb <suffix>\" is this database, cannot log to itself.\n" );
+ return 1;
+ }
+
+ if ( slapMode & SLAP_TOOL_MODE )
+ return 0;
+
+ if ( BER_BVISEMPTY( &li->li_db->be_rootndn )) {
+ ber_dupbv( &li->li_db->be_rootdn, li->li_db->be_suffix );
+ ber_dupbv( &li->li_db->be_rootndn, li->li_db->be_nsuffix );
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, 3600, accesslog_db_root, on,
+ "accesslog_db_root", li->li_db->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return 0;
+}
+
+static int
+accesslog_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ struct re_s *re = li->li_task;
+
+ li->li_open = 0;
+
+ if ( re ) {
+ li->li_task = NULL;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) )
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ return 0;
+}
+
+enum { start = 0 };
+
+static int
+check_rdntime_syntax (struct berval *val,
+ int *parts,
+ struct berval *fraction)
+{
+ /*
+ * GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM])
+ * GeneralizedTime supports leap seconds, UTCTime does not.
+ */
+ static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 };
+ static const int mdays[2][12] = {
+ /* non-leap years */
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ /* leap years */
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+ };
+ char *p, *e;
+ int part, c, c1, c2, tzoffset, leapyear = 0;
+
+ p = val->bv_val;
+ e = p + val->bv_len;
+
+ for (part = start; part < 7 && p < e; part++) {
+ c1 = *p;
+ if (!ASCII_DIGIT(c1)) {
+ break;
+ }
+ p++;
+ if (p == e) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ c = *p++;
+ if (!ASCII_DIGIT(c)) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ c += c1 * 10 - '0' * 11;
+ if ((part | 1) == 3) {
+ --c;
+ if (c < 0) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ if (c >= ceiling[part]) {
+ if (! (c == 60 && part == 6 && start == 0))
+ return LDAP_INVALID_SYNTAX;
+ }
+ parts[part] = c;
+ }
+ if (part < 5 + start) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ for (; part < 9; part++) {
+ parts[part] = 0;
+ }
+
+ /* leapyear check for the Gregorian calendar (year>1581) */
+ if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) {
+ leapyear = 1;
+ }
+
+ if (parts[3] >= mdays[leapyear][parts[2]]) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if (start == 0) {
+ fraction->bv_val = p;
+ fraction->bv_len = 0;
+ if (p < e && (*p == '.' || *p == ',')) {
+ char *end_num;
+ while (++p < e && ASCII_DIGIT(*p)) {
+ /* EMPTY */;
+ }
+ if (p - fraction->bv_val == 1) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+#if 0 /* don't truncate trailing zeros */
+ for (end_num = p; end_num[-1] == '0'; --end_num) {
+ /* EMPTY */;
+ }
+ c = end_num - fraction->bv_val;
+#else
+ c = p - fraction->bv_val;
+#endif
+ if (c != 1) fraction->bv_len = c;
+ }
+ }
+
+ if (p == e) {
+ /* no time zone */
+ return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS;
+ }
+
+ tzoffset = *p++;
+ switch (tzoffset) {
+ case 'Z':
+ /* UTC */
+ break;
+ default:
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS;
+}
+
+static int
+rdnTimestampValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int parts[9];
+ struct berval fraction;
+ return check_rdntime_syntax(in, parts, &fraction);
+}
+
+static int
+rdnTimestampNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ int parts[9], rc;
+ unsigned int len;
+ struct berval fraction;
+
+ rc = check_rdntime_syntax(val, parts, &fraction);
+ if (rc != LDAP_SUCCESS) {
+ return rc;
+ }
+
+ len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len;
+ normalized->bv_val = slap_sl_malloc( len + 1, ctx );
+ if ( BER_BVISNULL( normalized ) ) {
+ return LBER_ERROR_MEMORY;
+ }
+
+ sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d",
+ parts[0], parts[1], parts[2] + 1, parts[3] + 1,
+ parts[4], parts[5], parts[6] );
+ if ( !BER_BVISEMPTY( &fraction ) ) {
+ memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1,
+ fraction.bv_val, fraction.bv_len );
+ normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.';
+ }
+ strcpy( normalized->bv_val + len-1, "Z" );
+ normalized->bv_len = len;
+
+ return LDAP_SUCCESS;
+}
+
+
+int accesslog_initialize()
+{
+ int i, rc;
+ Syntax *rdnTimestampSyntax;
+ MatchingRule *rdnTimestampMatch, *rdnTimestampOrdering;
+
+ accesslog.on_bi.bi_type = "accesslog";
+ accesslog.on_bi.bi_db_init = accesslog_db_init;
+ accesslog.on_bi.bi_db_destroy = accesslog_db_destroy;
+ accesslog.on_bi.bi_db_open = accesslog_db_open;
+ accesslog.on_bi.bi_db_close = accesslog_db_close;
+
+ accesslog.on_bi.bi_op_add = accesslog_op_mod;
+ accesslog.on_bi.bi_op_bind = accesslog_op_misc;
+ accesslog.on_bi.bi_op_compare = accesslog_op_misc;
+ accesslog.on_bi.bi_op_delete = accesslog_op_mod;
+ accesslog.on_bi.bi_op_modify = accesslog_op_mod;
+ accesslog.on_bi.bi_op_modrdn = accesslog_op_mod;
+ accesslog.on_bi.bi_op_search = accesslog_op_misc;
+ accesslog.on_bi.bi_extended = accesslog_op_misc;
+ accesslog.on_bi.bi_op_unbind = accesslog_unbind;
+ accesslog.on_bi.bi_op_abandon = accesslog_abandon;
+ accesslog.on_bi.bi_operational = accesslog_operational;
+
+ accesslog.on_bi.bi_cf_ocs = log_cfocs;
+
+ nullsc.sc_response = slap_null_cb;
+
+ rc = config_register_schema( log_cfats, log_cfocs );
+ if ( rc ) return rc;
+
+ /* log schema integration */
+ for ( i=0; lsyntaxes[i].oid; i++ ) {
+ int code;
+
+ code = register_syntax( &lsyntaxes[ i ].syn );
+ if ( code != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_syntax failed\n" );
+ return code;
+ }
+
+ if ( lsyntaxes[i].mrs != NULL ) {
+ code = mr_make_syntax_compat_with_mrs(
+ lsyntaxes[i].oid, lsyntaxes[i].mrs );
+ if ( code < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: "
+ "mr_make_syntax_compat_with_mrs "
+ "failed\n" );
+ return code;
+ }
+ }
+ }
+
+ for ( i=0; lattrs[i].at; i++ ) {
+ int code;
+
+ code = register_at( lattrs[i].at, lattrs[i].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_at failed\n" );
+ return -1;
+ }
+ }
+
+ /* Inject custom normalizer for reqStart/reqEnd */
+ rdnTimestampMatch = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampSyntax = ch_malloc( sizeof( Syntax ));
+ *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality;
+ rdnTimestampMatch->smr_normalize = rdnTimestampNormalize;
+ *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering;
+ rdnTimestampOrdering->smr_normalize = rdnTimestampNormalize;
+ *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax;
+ rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate;
+ ad_reqStart->ad_type->sat_equality = rdnTimestampMatch;
+ ad_reqStart->ad_type->sat_ordering = rdnTimestampOrdering;
+ ad_reqStart->ad_type->sat_syntax = rdnTimestampSyntax;
+
+ rdnTimestampMatch = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampSyntax = ch_malloc( sizeof( Syntax ));
+ *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality;
+ *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering;
+ *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax;
+ ad_reqEnd->ad_type->sat_equality = rdnTimestampMatch;
+ ad_reqEnd->ad_type->sat_ordering = rdnTimestampOrdering;
+ ad_reqEnd->ad_type->sat_syntax = rdnTimestampSyntax;
+
+ for ( i=0; locs[i].ot; i++ ) {
+ int code;
+
+ code = register_oc( locs[i].ot, locs[i].oc, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_oc failed\n" );
+ return -1;
+ }
+ }
+
+ return overlay_register(&accesslog);
+}
+
+#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return accesslog_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_ACCESSLOG */
diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c
new file mode 100644
index 0000000..9292d4a
--- /dev/null
+++ b/servers/slapd/overlays/auditlog.c
@@ -0,0 +1,242 @@
+/* auditlog.c - log modifications for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions copyright 2004-2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_AUDITLOG
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "ldif.h"
+
+typedef struct auditlog_data {
+ ldap_pvt_thread_mutex_t ad_mutex;
+ char *ad_logfile;
+} auditlog_data;
+
+static ConfigTable auditlogcfg[] = {
+ { "auditlog", "filename", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(auditlog_data, ad_logfile),
+ "( OLcfgOvAt:15.1 NAME 'olcAuditlogFile' "
+ "DESC 'Filename for auditlogging' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs auditlogocs[] = {
+ { "( OLcfgOvOc:15.1 "
+ "NAME 'olcAuditlogConfig' "
+ "DESC 'Auditlog configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcAuditlogFile ) )",
+ Cft_Overlay, auditlogcfg },
+ { NULL, 0, NULL }
+};
+
+static int fprint_ldif(FILE *f, char *name, char *val, ber_len_t len) {
+ char *s;
+ if((s = ldif_put(LDIF_PUT_VALUE, name, val, len)) == NULL)
+ return(-1);
+ fputs(s, f);
+ ber_memfree(s);
+ return(0);
+}
+
+static int auditlog_response(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ auditlog_data *ad = on->on_bi.bi_private;
+ FILE *f;
+ Attribute *a;
+ Modifications *m;
+ struct berval *b, *who = NULL, peername;
+ char *what, *whatm, *suffix;
+ time_t stamp;
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+
+ if ( !ad->ad_logfile ) return SLAP_CB_CONTINUE;
+
+/*
+** add or modify: use modifiersName if present
+**
+*/
+ switch(op->o_tag) {
+ case LDAP_REQ_MODRDN: what = "modrdn"; break;
+ case LDAP_REQ_DELETE: what = "delete"; break;
+ case LDAP_REQ_ADD:
+ what = "add";
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ if( a->a_desc == slap_schema.si_ad_modifiersName ) {
+ who = &a->a_vals[0];
+ break;
+ }
+ break;
+ case LDAP_REQ_MODIFY:
+ what = "modify";
+ for(m = op->orm_modlist; m; m = m->sml_next)
+ if( m->sml_desc == slap_schema.si_ad_modifiersName &&
+ ( m->sml_op == LDAP_MOD_ADD ||
+ m->sml_op == LDAP_MOD_REPLACE )) {
+ who = &m->sml_values[0];
+ break;
+ }
+ break;
+ default:
+ return SLAP_CB_CONTINUE;
+ }
+
+ suffix = op->o_bd->be_suffix[0].bv_len ? op->o_bd->be_suffix[0].bv_val :
+ "global";
+
+/*
+** note: this means requestor's dn when modifiersName is null
+*/
+ if ( !who )
+ who = &op->o_dn;
+
+ peername = op->o_conn->c_peer_name;
+ ldap_pvt_thread_mutex_lock(&ad->ad_mutex);
+ if((f = fopen(ad->ad_logfile, "a")) == NULL) {
+ ldap_pvt_thread_mutex_unlock(&ad->ad_mutex);
+ return SLAP_CB_CONTINUE;
+ }
+
+ stamp = slap_get_time();
+ fprintf(f, "# %s %ld %s%s%s %s conn=%ld\n",
+ what, (long)stamp, suffix, who ? " " : "", who ? who->bv_val : "",
+ peername.bv_val ? peername.bv_val: "", op->o_conn->c_connid);
+
+ if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) &&
+ (!who || !dn_match( who, &op->o_conn->c_dn )))
+ fprintf(f, "# realdn: %s\n", op->o_conn->c_dn.bv_val );
+
+ fprintf(f, "dn: %s\nchangetype: %s\n",
+ op->o_req_dn.bv_val, what);
+
+ switch(op->o_tag) {
+ case LDAP_REQ_ADD:
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ if((b = a->a_vals) != NULL)
+ for(i = 0; b[i].bv_val; i++)
+ fprint_ldif(f, a->a_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len);
+ break;
+
+ case LDAP_REQ_MODIFY:
+ for(m = op->orm_modlist; m; m = m->sml_next) {
+ switch(m->sml_op & LDAP_MOD_OP) {
+ case LDAP_MOD_ADD: whatm = "add"; break;
+ case LDAP_MOD_REPLACE: whatm = "replace"; break;
+ case LDAP_MOD_DELETE: whatm = "delete"; break;
+ case LDAP_MOD_INCREMENT: whatm = "increment"; break;
+ default:
+ fprintf(f, "# MOD_TYPE_UNKNOWN:%02x\n", m->sml_op & LDAP_MOD_OP);
+ continue;
+ }
+ fprintf(f, "%s: %s\n", whatm, m->sml_desc->ad_cname.bv_val);
+ if((b = m->sml_values) != NULL)
+ for(i = 0; b[i].bv_val; i++)
+ fprint_ldif(f, m->sml_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len);
+ fprintf(f, "-\n");
+ }
+ break;
+
+ case LDAP_REQ_MODRDN:
+ fprintf(f, "newrdn: %s\ndeleteoldrdn: %s\n",
+ op->orr_newrdn.bv_val, op->orr_deleteoldrdn ? "1" : "0");
+ if(op->orr_newSup) fprintf(f, "newsuperior: %s\n", op->orr_newSup->bv_val);
+ break;
+
+ case LDAP_REQ_DELETE:
+ /* nothing else needed */
+ break;
+ }
+
+ fprintf(f, "# end %s %ld\n\n", what, (long)stamp);
+
+ fclose(f);
+ ldap_pvt_thread_mutex_unlock(&ad->ad_mutex);
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst auditlog;
+
+static int
+auditlog_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ auditlog_data *ad = ch_calloc(1, sizeof(auditlog_data));
+
+ on->on_bi.bi_private = ad;
+ ldap_pvt_thread_mutex_init( &ad->ad_mutex );
+ return 0;
+}
+
+static int
+auditlog_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ auditlog_data *ad = on->on_bi.bi_private;
+
+ ldap_pvt_thread_mutex_destroy( &ad->ad_mutex );
+ free( ad->ad_logfile );
+ free( ad );
+ return 0;
+}
+
+int auditlog_initialize() {
+ int rc;
+
+ auditlog.on_bi.bi_type = "auditlog";
+ auditlog.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ auditlog.on_bi.bi_db_init = auditlog_db_init;
+ auditlog.on_bi.bi_db_destroy = auditlog_db_destroy;
+ auditlog.on_response = auditlog_response;
+
+ auditlog.on_bi.bi_cf_ocs = auditlogocs;
+ rc = config_register_schema( auditlogcfg, auditlogocs );
+ if ( rc ) return rc;
+
+ return overlay_register(&auditlog);
+}
+
+#if SLAPD_OVER_AUDITLOG == SLAPD_MOD_DYNAMIC && defined(PIC)
+int
+init_module( int argc, char *argv[] )
+{
+ return auditlog_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_AUDITLOG */
diff --git a/servers/slapd/overlays/autoca.c b/servers/slapd/overlays/autoca.c
new file mode 100644
index 0000000..5fcd204
--- /dev/null
+++ b/servers/slapd/overlays/autoca.c
@@ -0,0 +1,1121 @@
+/* autoca.c - Automatic Certificate Authority */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 The OpenLDAP Foundation.
+ * Copyright 2009-2018 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_AUTOCA
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/evp.h>
+#include <openssl/bn.h>
+
+/* Starting with OpenSSL 1.1.0, rsa.h is no longer included in
+ * x509.h, so we need to explicitly include it for the
+ * call to EVP_PKEY_CTX_set_rsa_keygen_bits
+ */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+#include <openssl/rsa.h>
+#define X509_get_notBefore(x) X509_getm_notBefore(x)
+#define X509_get_notAfter(x) X509_getm_notAfter(x)
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+#define BN_pseudo_rand(bn, bits, top, bottom) BN_rand(bn, bits, top, bottom)
+#endif
+
+/* This overlay implements a certificate authority that can generate
+ * certificates automatically for any entry in the directory.
+ * On startup it generates a self-signed CA cert for the directory's
+ * suffix entry and uses this to sign all other certs that it generates.
+ * User and server certs are generated on demand, using a Search request.
+ */
+
+#define LBER_TAG_OID ((ber_tag_t) 0x06UL)
+#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL)
+
+#define KEYBITS 2048
+#define MIN_KEYBITS 512
+
+#define ACA_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.11"
+
+#define ACA_SCHEMA_AT ACA_SCHEMA_ROOT ".1"
+#define ACA_SCHEMA_OC ACA_SCHEMA_ROOT ".2"
+
+static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey;
+static AttributeDescription *ad_mail, *ad_ipaddr;
+static ObjectClass *oc_caObj, *oc_usrObj;
+
+static char *aca_attrs[] = {
+ "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' "
+ "DESC 'X.509 CA private key, use ;binary' "
+ "SUP pKCS8PrivateKey )",
+ "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' "
+ "DESC 'X.509 user private key, use ;binary' "
+ "SUP pKCS8PrivateKey )",
+ NULL
+};
+
+static struct {
+ char *at;
+ AttributeDescription **ad;
+} aca_attr2[] = {
+ { "cACertificate;binary", &ad_caCert },
+ { "cAPrivateKey;binary", &ad_caPkey },
+ { "userCertificate;binary", &ad_usrCert },
+ { "userPrivateKey;binary", &ad_usrPkey },
+ { "mail", &ad_mail },
+ { NULL }
+};
+
+static struct {
+ char *ot;
+ ObjectClass **oc;
+} aca_ocs[] = {
+ { "( " ACA_SCHEMA_OC ".1 NAME 'autoCA' "
+ "DESC 'Automated PKI certificate authority' "
+ "SUP pkiCA AUXILIARY "
+ "MAY cAPrivateKey )", &oc_caObj },
+ { "( " ACA_SCHEMA_OC ".2 NAME 'autoCAuser' "
+ "DESC 'Automated PKI CA user' "
+ "SUP pkiUser AUXILIARY "
+ "MAY userPrivateKey )", &oc_usrObj },
+ { NULL }
+};
+
+typedef struct autoca_info {
+ X509 *ai_cert;
+ EVP_PKEY *ai_pkey;
+ ObjectClass *ai_usrclass;
+ ObjectClass *ai_srvclass;
+ struct berval ai_localdn;
+ struct berval ai_localndn;
+ int ai_usrkeybits;
+ int ai_srvkeybits;
+ int ai_cakeybits;
+ int ai_usrdays;
+ int ai_srvdays;
+ int ai_cadays;
+} autoca_info;
+
+/* Rewrite an LDAP DN in DER form
+ * Input must be valid DN, therefore no error checking is done here.
+ */
+static int autoca_dnbv2der( Operation *op, struct berval *bv, struct berval *der )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPDN dn;
+ LDAPRDN rdn;
+ LDAPAVA *ava;
+ AttributeDescription *ad;
+ int irdn, iava;
+
+ ldap_bv2dn_x( bv, &dn, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* count RDNs, we need them in reverse order */
+ for (irdn = 0; dn[irdn]; irdn++);
+ irdn--;
+
+ /* DN is a SEQuence of RDNs */
+ ber_start_seq( ber, LBER_SEQUENCE );
+ for (; irdn >=0; irdn--)
+ {
+ /* RDN is a SET of AVAs */
+ ber_start_set( ber, LBER_SET );
+ rdn = dn[irdn];
+ for (iava = 0; rdn[iava]; iava++)
+ {
+ const char *text;
+ char oid[1024];
+ struct berval bvo = { sizeof(oid), oid };
+ struct berval bva;
+
+ /* AVA is a SEQuence of attr and value */
+ ber_start_seq( ber, LBER_SEQUENCE );
+ ava = rdn[iava];
+ ad = NULL;
+ slap_bv2ad( &ava->la_attr, &ad, &text );
+ ber_str2bv( ad->ad_type->sat_oid, 0, 0, &bva );
+ ber_encode_oid( &bva, &bvo );
+ ber_put_berval( ber, &bvo, LBER_TAG_OID );
+ ber_put_berval( ber, &ava->la_value, LBER_TAG_UTF8 );
+ ber_put_seq( ber );
+ }
+ ber_put_set( ber );
+ }
+ ber_put_seq( ber );
+ ber_flatten2( ber, der, 0 );
+ ldap_dnfree_x( dn, op->o_tmpmemctx );
+ return 0;
+}
+
+static int autoca_genpkey(int bits, EVP_PKEY **pkey)
+{
+ EVP_PKEY_CTX *kctx;
+ int rc;
+
+ kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (kctx == NULL)
+ return -1;
+ if (EVP_PKEY_keygen_init(kctx) <= 0)
+ {
+ EVP_PKEY_CTX_free(kctx);
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, bits) <= 0)
+ {
+ EVP_PKEY_CTX_free(kctx);
+ return -1;
+ }
+ rc = EVP_PKEY_keygen(kctx, pkey);
+ EVP_PKEY_CTX_free(kctx);
+ return rc;
+}
+
+static int autoca_signcert(X509 *cert, EVP_PKEY *pkey)
+{
+ EVP_MD_CTX *ctx = EVP_MD_CTX_create();
+ EVP_PKEY_CTX *pkctx = NULL;
+ int rc = -1;
+
+ if ( ctx == NULL )
+ return -1;
+ if (EVP_DigestSignInit(ctx, &pkctx, NULL, NULL, pkey))
+ {
+ rc = X509_sign_ctx(cert, ctx);
+ }
+ EVP_MD_CTX_destroy(ctx);
+ return rc;
+}
+
+#define SERIAL_BITS 64 /* should be less than 160 */
+
+typedef struct myext {
+ char *name;
+ char *value;
+} myext;
+
+static myext CAexts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "critical,CA:true" },
+ { "keyUsage", "digitalSignature,cRLSign,keyCertSign" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+static myext usrExts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "CA:false" },
+ { "keyUsage", "digitalSignature,nonRepudiation,keyEncipherment" },
+ { "extendedKeyUsage", "clientAuth,emailProtection,codeSigning" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+static myext srvExts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "CA:false" },
+ { "keyUsage", "digitalSignature,keyEncipherment" },
+ { "extendedKeyUsage", "serverAuth,clientAuth" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+typedef struct genargs {
+ X509 *issuer_cert;
+ EVP_PKEY *issuer_pkey;
+ struct berval *subjectDN;
+ myext *cert_exts;
+ myext *more_exts;
+ X509 *newcert;
+ EVP_PKEY *newpkey;
+ struct berval dercert;
+ struct berval derpkey;
+ int keybits;
+ int days;
+} genargs;
+
+static int autoca_gencert( Operation *op, genargs *args )
+{
+ X509_NAME *subj_name, *issuer_name;
+ X509 *subj_cert;
+ struct berval derdn;
+ unsigned char *pp;
+ EVP_PKEY *evpk = NULL;
+ int rc;
+
+ if ((subj_cert = X509_new()) == NULL)
+ return -1;
+
+ autoca_dnbv2der( op, args->subjectDN, &derdn );
+ pp = (unsigned char *)derdn.bv_val;
+ subj_name = d2i_X509_NAME( NULL, (const unsigned char **)&pp, derdn.bv_len );
+ op->o_tmpfree( derdn.bv_val, op->o_tmpmemctx );
+ if ( subj_name == NULL )
+ {
+fail1:
+ X509_free( subj_cert );
+ return -1;
+ }
+
+ rc = autoca_genpkey( args->keybits, &evpk );
+ if ( rc <= 0 )
+ {
+fail2:
+ if ( subj_name ) X509_NAME_free( subj_name );
+ goto fail1;
+ }
+ /* encode DER in PKCS#8 */
+ {
+ PKCS8_PRIV_KEY_INFO *p8inf;
+ if (( p8inf = EVP_PKEY2PKCS8( evpk )) == NULL )
+ goto fail2;
+ args->derpkey.bv_len = i2d_PKCS8_PRIV_KEY_INFO( p8inf, NULL );
+ args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx );
+ pp = (unsigned char *)args->derpkey.bv_val;
+ i2d_PKCS8_PRIV_KEY_INFO( p8inf, &pp );
+ PKCS8_PRIV_KEY_INFO_free( p8inf );
+ }
+ args->newpkey = evpk;
+
+ /* set random serial */
+ {
+ BIGNUM *bn = BN_new();
+ if ( bn == NULL )
+ {
+fail3:
+ EVP_PKEY_free( evpk );
+ goto fail2;
+ }
+ if (!BN_pseudo_rand(bn, SERIAL_BITS, 0, 0))
+ {
+ BN_free( bn );
+ goto fail3;
+ }
+ if (!BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(subj_cert)))
+ {
+ BN_free( bn );
+ goto fail3;
+ }
+ BN_free(bn);
+ }
+ if (args->issuer_cert) {
+ issuer_name = X509_get_subject_name(args->issuer_cert);
+ } else {
+ issuer_name = subj_name;
+ args->issuer_cert = subj_cert;
+ args->issuer_pkey = evpk;
+ }
+ if (!X509_set_version(subj_cert, 2) || /* set version to V3 */
+ !X509_set_issuer_name(subj_cert, issuer_name) ||
+ !X509_set_subject_name(subj_cert, subj_name) ||
+ !X509_gmtime_adj(X509_get_notBefore(subj_cert), 0) ||
+ !X509_time_adj_ex(X509_get_notAfter(subj_cert), args->days, 0, NULL) ||
+ !X509_set_pubkey(subj_cert, evpk))
+ {
+ goto fail3;
+ }
+ X509_NAME_free(subj_name);
+ subj_name = NULL;
+
+ /* set cert extensions */
+ {
+ X509V3_CTX ctx;
+ X509_EXTENSION *ext;
+ int i;
+
+ X509V3_set_ctx(&ctx, args->issuer_cert, subj_cert, NULL, NULL, 0);
+ for (i=0; args->cert_exts[i].name; i++) {
+ ext = X509V3_EXT_nconf(NULL, &ctx, args->cert_exts[i].name, args->cert_exts[i].value);
+ if ( ext == NULL )
+ goto fail3;
+ rc = X509_add_ext(subj_cert, ext, -1);
+ X509_EXTENSION_free(ext);
+ if ( !rc )
+ goto fail3;
+ }
+ if (args->more_exts) {
+ for (i=0; args->more_exts[i].name; i++) {
+ ext = X509V3_EXT_nconf(NULL, &ctx, args->more_exts[i].name, args->more_exts[i].value);
+ if ( ext == NULL )
+ goto fail3;
+ rc = X509_add_ext(subj_cert, ext, -1);
+ X509_EXTENSION_free(ext);
+ if ( !rc )
+ goto fail3;
+ }
+ }
+ }
+ rc = autoca_signcert( subj_cert, args->issuer_pkey );
+ if ( rc < 0 )
+ goto fail3;
+ args->dercert.bv_len = i2d_X509( subj_cert, NULL );
+ args->dercert.bv_val = op->o_tmpalloc( args->dercert.bv_len, op->o_tmpmemctx );
+ pp = (unsigned char *)args->dercert.bv_val;
+ i2d_X509( subj_cert, &pp );
+ args->newcert = subj_cert;
+ return 0;
+}
+
+typedef struct saveargs {
+ ObjectClass *oc;
+ struct berval *dercert;
+ struct berval *derpkey;
+ slap_overinst *on;
+ struct berval *dn;
+ struct berval *ndn;
+ int isca;
+} saveargs;
+
+static int autoca_savecert( Operation *op, saveargs *args )
+{
+ Modifications mod[3], *mp = mod;
+ struct berval bvs[6], *bp = bvs;
+ BackendInfo *bi;
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+
+ if ( args->oc ) {
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = slap_schema.si_ad_objectClass;
+ mp->sml_op = LDAP_MOD_ADD;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = args->oc->soc_cname;
+ BER_BVZERO( bp );
+ bp++;
+ mp->sml_next = mp+1;
+ mp++;
+ }
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = args->isca ? ad_caCert : ad_usrCert;
+ mp->sml_op = LDAP_MOD_REPLACE;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = *args->dercert;
+ BER_BVZERO( bp );
+ bp++;
+ mp->sml_next = mp+1;
+ mp++;
+
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = args->isca ? ad_caPkey : ad_usrPkey;
+ mp->sml_op = LDAP_MOD_ADD;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = *args->derpkey;
+ BER_BVZERO( bp );
+ mp->sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ bi = op->o_bd->bd_info;
+ op->o_bd->bd_info = args->on->on_info->oi_orig;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = *args->dn;
+ op->o_req_ndn = *args->ndn;
+ op->o_bd->be_modify( op, &rs );
+ op->o_bd->bd_info = bi;
+ return rs.sr_err;
+}
+
+static const struct berval configDN = BER_BVC("cn=config");
+
+/* must run as a pool thread to avoid cn=config deadlock */
+static void *
+autoca_setca_task( void *ctx, void *arg )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ struct berval *cacert = arg;
+ Modifications mod;
+ struct berval bvs[2];
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+ const char *text;
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ mod.sml_numvals = 1;
+ mod.sml_values = bvs;
+ mod.sml_nvalues = NULL;
+ mod.sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCACertificate;binary", &mod.sml_desc, &text ))
+ goto leave;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ bvs[0] = *cacert;
+ BER_BVZERO( &bvs[1] );
+ mod.sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ op->o_bd = select_backend( (struct berval *)&configDN, 0 );
+ if ( !op->o_bd )
+ goto leave;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = configDN;
+ op->o_req_ndn = configDN;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->be_modify( op, &rs );
+leave:
+ ch_free( arg );
+ return NULL;
+}
+
+static int
+autoca_setca( struct berval *cacert )
+{
+ struct berval *bv = ch_malloc( sizeof(struct berval) + cacert->bv_len );
+ bv->bv_len = cacert->bv_len;
+ bv->bv_val = (char *)(bv+1);
+ AC_MEMCPY( bv->bv_val, cacert->bv_val, bv->bv_len );
+ return ldap_pvt_thread_pool_submit( &connection_pool, autoca_setca_task, bv );
+}
+
+static int
+autoca_setlocal( Operation *op, struct berval *cert, struct berval *pkey )
+{
+ Modifications mod[2];
+ struct berval bvs[4];
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+ const char *text;
+
+ mod[0].sml_numvals = 1;
+ mod[0].sml_values = bvs;
+ mod[0].sml_nvalues = NULL;
+ mod[0].sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCertificate;binary", &mod[0].sml_desc, &text ))
+ return -1;
+ mod[0].sml_op = LDAP_MOD_REPLACE;
+ mod[0].sml_flags = SLAP_MOD_INTERNAL;
+ bvs[0] = *cert;
+ BER_BVZERO( &bvs[1] );
+ mod[0].sml_next = &mod[1];
+
+ mod[1].sml_numvals = 1;
+ mod[1].sml_values = &bvs[2];
+ mod[1].sml_nvalues = NULL;
+ mod[1].sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCertificateKey;binary", &mod[1].sml_desc, &text ))
+ return -1;
+ mod[1].sml_op = LDAP_MOD_REPLACE;
+ mod[1].sml_flags = SLAP_MOD_INTERNAL;
+ bvs[2] = *pkey;
+ BER_BVZERO( &bvs[3] );
+ mod[1].sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ op->o_bd = select_backend( (struct berval *)&configDN, 0 );
+ if ( !op->o_bd )
+ return -1;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = configDN;
+ op->o_req_ndn = configDN;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->be_modify( op, &rs );
+ return rs.sr_err;
+}
+
+enum {
+ ACA_USRCLASS = 1,
+ ACA_SRVCLASS,
+ ACA_USRKEYBITS,
+ ACA_SRVKEYBITS,
+ ACA_CAKEYBITS,
+ ACA_USRDAYS,
+ ACA_SRVDAYS,
+ ACA_CADAYS,
+ ACA_LOCALDN
+};
+
+static int autoca_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ autoca_info *ai = on->on_bi.bi_private;
+ int rc = 0;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ if ( ai->ai_usrclass ) {
+ c->value_string = ch_strdup( ai->ai_usrclass->soc_cname.bv_val );
+ } else {
+ rc = 1;
+ }
+ break;
+ case ACA_SRVCLASS:
+ if ( ai->ai_srvclass ) {
+ c->value_string = ch_strdup( ai->ai_srvclass->soc_cname.bv_val );
+ } else {
+ rc = 1;
+ }
+ break;
+ case ACA_USRKEYBITS:
+ c->value_int = ai->ai_usrkeybits;
+ break;
+ case ACA_SRVKEYBITS:
+ c->value_int = ai->ai_srvkeybits;
+ break;
+ case ACA_CAKEYBITS:
+ c->value_int = ai->ai_cakeybits;
+ break;
+ case ACA_USRDAYS:
+ c->value_int = ai->ai_usrdays;
+ break;
+ case ACA_SRVDAYS:
+ c->value_int = ai->ai_srvdays;
+ break;
+ case ACA_CADAYS:
+ c->value_int = ai->ai_cadays;
+ break;
+ case ACA_LOCALDN:
+ if ( !BER_BVISNULL( &ai->ai_localdn )) {
+ rc = value_add_one( &c->rvalue_vals, &ai->ai_localdn );
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ ai->ai_usrclass = NULL;
+ break;
+ case ACA_SRVCLASS:
+ ai->ai_srvclass = NULL;
+ break;
+ case ACA_LOCALDN:
+ if ( ai->ai_localdn.bv_val ) {
+ ch_free( ai->ai_localdn.bv_val );
+ ch_free( ai->ai_localndn.bv_val );
+ BER_BVZERO( &ai->ai_localdn );
+ BER_BVZERO( &ai->ai_localndn );
+ }
+ break;
+ /* single-valued attrs, all no-ops */
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ {
+ ObjectClass *oc = oc_find( c->value_string );
+ if ( oc )
+ ai->ai_usrclass = oc;
+ else
+ rc = 1;
+ }
+ break;
+ case ACA_SRVCLASS:
+ {
+ ObjectClass *oc = oc_find( c->value_string );
+ if ( oc )
+ ai->ai_srvclass = oc;
+ else
+ rc = 1;
+ }
+ case ACA_USRKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_usrkeybits = c->value_int;
+ break;
+ case ACA_SRVKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_srvkeybits = c->value_int;
+ break;
+ case ACA_CAKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_cakeybits = c->value_int;
+ break;
+ case ACA_USRDAYS:
+ ai->ai_usrdays = c->value_int;
+ break;
+ case ACA_SRVDAYS:
+ ai->ai_srvdays = c->value_int;
+ break;
+ case ACA_CADAYS:
+ ai->ai_cadays = c->value_int;
+ break;
+ case ACA_LOCALDN:
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !dnIsSuffix( &c->value_ndn, c->be->be_nsuffix )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DN is not a subordinate of backend" );
+ Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( ai->ai_localdn.bv_val ) {
+ ch_free( ai->ai_localdn.bv_val );
+ ch_free( ai->ai_localndn.bv_val );
+ }
+ ai->ai_localdn = c->value_dn;
+ ai->ai_localndn = c->value_ndn;
+ }
+ }
+ return rc;
+}
+
+static ConfigTable autoca_cfg[] = {
+ { "userClass", "objectclass", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|ACA_USRCLASS, autoca_cf,
+ "( OLcfgOvAt:22.1 NAME 'olcAutoCAuserClass' "
+ "DESC 'ObjectClass of user entries' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "serverClass", "objectclass", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf,
+ "( OLcfgOvAt:22.2 NAME 'olcAutoCAserverClass' "
+ "DESC 'ObjectClass of server entries' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "userKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_USRKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.3 NAME 'olcAutoCAuserKeybits' "
+ "DESC 'Size of PrivateKey for user entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "serverKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_SRVKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.4 NAME 'olcAutoCAserverKeybits' "
+ "DESC 'Size of PrivateKey for server entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "caKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_CAKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.5 NAME 'olcAutoCAKeybits' "
+ "DESC 'Size of PrivateKey for CA certificate' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "userDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_USRDAYS, autoca_cf,
+ "( OLcfgOvAt:22.6 NAME 'olcAutoCAuserDays' "
+ "DESC 'Lifetime of user certificates in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "serverDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_SRVDAYS, autoca_cf,
+ "( OLcfgOvAt:22.7 NAME 'olcAutoCAserverDays' "
+ "DESC 'Lifetime of server certificates in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "caDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_CADAYS, autoca_cf,
+ "( OLcfgOvAt:22.8 NAME 'olcAutoCADays' "
+ "DESC 'Lifetime of CA certificate in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "localdn", "dn", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|ACA_LOCALDN, autoca_cf,
+ "( OLcfgOvAt:22.9 NAME 'olcAutoCAlocalDN' "
+ "DESC 'DN of local server cert' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs autoca_ocs[] = {
+ { "( OLcfgOvOc:22.1 "
+ "NAME 'olcAutoCAConfig' "
+ "DESC 'AutoCA configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcAutoCAuserClass $ olcAutoCAserverClass $ "
+ "olcAutoCAuserKeybits $ olcAutoCAserverKeybits $ olcAutoCAKeyBits $ "
+ "olcAutoCAuserDays $ olcAutoCAserverDays $ olcAutoCADays $ "
+ "olcAutoCAlocalDN ) )",
+ Cft_Overlay, autoca_cfg },
+ { NULL, 0, NULL }
+};
+
+static int
+autoca_op_response(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = op->o_callback->sc_private;
+ autoca_info *ai = on->on_bi.bi_private;
+ Attribute *a;
+ int isusr = 0;
+
+ if (rs->sr_type != REP_SEARCH)
+ return SLAP_CB_CONTINUE;
+
+ /* If root or self */
+ if ( !be_isroot( op ) &&
+ !dn_match( &rs->sr_entry->e_nname, &op->o_ndn ))
+ return SLAP_CB_CONTINUE;
+
+ isusr = is_entry_objectclass( rs->sr_entry, ai->ai_usrclass, SLAP_OCF_CHECK_SUP );
+ if ( !isusr )
+ {
+ if (!is_entry_objectclass( rs->sr_entry, ai->ai_srvclass, SLAP_OCF_CHECK_SUP ))
+ return SLAP_CB_CONTINUE;
+ }
+ a = attr_find( rs->sr_entry->e_attrs, ad_usrPkey );
+ if ( !a )
+ {
+ Operation op2;
+ genargs args;
+ saveargs arg2;
+ myext extras[2];
+ int rc;
+
+ args.issuer_cert = ai->ai_cert;
+ args.issuer_pkey = ai->ai_pkey;
+ args.subjectDN = &rs->sr_entry->e_name;
+ args.more_exts = NULL;
+ if ( isusr )
+ {
+ args.cert_exts = usrExts;
+ args.keybits = ai->ai_usrkeybits;
+ args.days = ai->ai_usrdays;
+ a = attr_find( rs->sr_entry->e_attrs, ad_mail );
+ if ( a )
+ {
+ extras[0].name = "subjectAltName";
+ extras[1].name = NULL;
+ extras[0].value = op->o_tmpalloc( sizeof("email:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+ sprintf(extras[0].value, "email:%s", a->a_vals[0].bv_val);
+ args.more_exts = extras;
+ }
+ } else
+ {
+ args.cert_exts = srvExts;
+ args.keybits = ai->ai_srvkeybits;
+ args.days = ai->ai_srvdays;
+ if ( ad_ipaddr && (a = attr_find( rs->sr_entry->e_attrs, ad_ipaddr )))
+ {
+ extras[0].name = "subjectAltName";
+ extras[1].name = NULL;
+ extras[0].value = op->o_tmpalloc( sizeof("IP:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+ sprintf(extras[0].value, "IP:%s", a->a_vals[0].bv_val);
+ args.more_exts = extras;
+ }
+ }
+ rc = autoca_gencert( op, &args );
+ if ( rc )
+ return SLAP_CB_CONTINUE;
+ X509_free( args.newcert );
+ EVP_PKEY_free( args.newpkey );
+
+ if ( is_entry_objectclass( rs->sr_entry, oc_usrObj, 0 ))
+ arg2.oc = NULL;
+ else
+ arg2.oc = oc_usrObj;
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ))
+ {
+ Entry *e = entry_dup( rs->sr_entry );
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+ arg2.dercert = &args.dercert;
+ arg2.derpkey = &args.derpkey;
+ arg2.on = on;
+ arg2.dn = &rs->sr_entry->e_name;
+ arg2.ndn = &rs->sr_entry->e_nname;
+ arg2.isca = 0;
+ op2 = *op;
+ rc = autoca_savecert( &op2, &arg2 );
+ if ( !rc )
+ {
+ /* If this is our cert DN, configure it */
+ if ( dn_match( &rs->sr_entry->e_nname, &ai->ai_localndn ))
+ autoca_setlocal( &op2, &args.dercert, &args.derpkey );
+ attr_merge_one( rs->sr_entry, ad_usrCert, &args.dercert, NULL );
+ attr_merge_one( rs->sr_entry, ad_usrPkey, &args.derpkey, NULL );
+ }
+ op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_op_search(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ /* we only act on a search that returns just our cert/key attrs */
+ if ( op->ors_attrs && op->ors_attrs[0].an_desc == ad_usrCert &&
+ op->ors_attrs[1].an_desc == ad_usrPkey &&
+ op->ors_attrs[2].an_name.bv_val == NULL )
+ {
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ sc->sc_response = autoca_op_response;
+ sc->sc_private = on;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ autoca_info *ai;
+
+ ai = ch_calloc(1, sizeof(autoca_info));
+ on->on_bi.bi_private = ai;
+
+ /* set defaults */
+ ai->ai_usrclass = oc_find( "person" );
+ ai->ai_srvclass = oc_find( "ipHost" );
+ ai->ai_usrkeybits = KEYBITS;
+ ai->ai_srvkeybits = KEYBITS;
+ ai->ai_cakeybits = KEYBITS;
+ ai->ai_usrdays = 365; /* 1 year */
+ ai->ai_srvdays = 1826; /* 5 years */
+ ai->ai_cadays = 3652; /* 10 years */
+ return 0;
+}
+
+static int
+autoca_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ autoca_info *ai = on->on_bi.bi_private;
+
+ if ( ai->ai_cert )
+ X509_free( ai->ai_cert );
+ if ( ai->ai_pkey )
+ EVP_PKEY_free( ai->ai_pkey );
+ ch_free( ai );
+
+ return 0;
+}
+
+static int
+autoca_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ autoca_info *ai = on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc;
+
+ if (slapMode & SLAP_TOOL_MODE)
+ return 0;
+
+ if ( ! *aca_attr2[0].ad ) {
+ int i, code;
+ const char *text;
+
+ for ( i=0; aca_attr2[i].at; i++ ) {
+ code = slap_str2ad( aca_attr2[i].at, aca_attr2[i].ad, &text );
+ if ( code ) return code;
+ }
+
+ /* Schema may not be loaded, ignore if missing */
+ slap_str2ad( "ipHostNumber", &ad_ipaddr, &text );
+
+ for ( i=0; aca_ocs[i].ot; i++ ) {
+ code = register_oc( aca_ocs[i].ot, aca_ocs[i].oc, 0 );
+ if ( code ) return code;
+ }
+ }
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = be;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+ rc = overlay_entry_get_ov( op, be->be_nsuffix, NULL,
+ NULL, 0, &e, on );
+
+ if ( e ) {
+ int gotoc = 0, gotat = 0;
+ if ( is_entry_objectclass( e, oc_caObj, 0 )) {
+ gotoc = 1;
+ a = attr_find( e->e_attrs, ad_caPkey );
+ if ( a ) {
+ const unsigned char *pp;
+ pp = (unsigned char *)a->a_vals[0].bv_val;
+ ai->ai_pkey = d2i_AutoPrivateKey( NULL, &pp, a->a_vals[0].bv_len );
+ if ( ai->ai_pkey )
+ {
+ a = attr_find( e->e_attrs, ad_caCert );
+ if ( a )
+ {
+ pp = (unsigned char *)a->a_vals[0].bv_val;
+ ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len );
+ /* If TLS wasn't configured yet, set this as our CA */
+ if ( !slap_tls_ctx )
+ autoca_setca( a->a_vals );
+ }
+ }
+ gotat = 1;
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ /* generate attrs, store... */
+ if ( !gotat ) {
+ genargs args;
+ saveargs arg2;
+
+ args.issuer_cert = NULL;
+ args.issuer_pkey = NULL;
+ args.subjectDN = &be->be_suffix[0];
+ args.cert_exts = CAexts;
+ args.more_exts = NULL;
+ args.keybits = ai->ai_cakeybits;
+ args.days = ai->ai_cadays;
+
+ rc = autoca_gencert( op, &args );
+ if ( rc )
+ return -1;
+
+ ai->ai_cert = args.newcert;
+ ai->ai_pkey = args.newpkey;
+
+ arg2.dn = be->be_suffix;
+ arg2.ndn = be->be_nsuffix;
+ arg2.isca = 1;
+ if ( !gotoc )
+ arg2.oc = oc_caObj;
+ else
+ arg2.oc = NULL;
+ arg2.on = on;
+ arg2.dercert = &args.dercert;
+ arg2.derpkey = &args.derpkey;
+
+ autoca_savecert( op, &arg2 );
+
+ /* If TLS wasn't configured yet, set this as our CA */
+ if ( !slap_tls_ctx )
+ autoca_setca( &args.dercert );
+
+ op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ return 0;
+}
+
+static slap_overinst autoca;
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+int autoca_initialize() {
+ int i, code;
+
+ autoca.on_bi.bi_type = "autoca";
+ autoca.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ autoca.on_bi.bi_db_init = autoca_db_init;
+ autoca.on_bi.bi_db_destroy = autoca_db_destroy;
+ autoca.on_bi.bi_db_open = autoca_db_open;
+ autoca.on_bi.bi_op_search = autoca_op_search;
+
+ autoca.on_bi.bi_cf_ocs = autoca_ocs;
+ code = config_register_schema( autoca_cfg, autoca_ocs );
+ if ( code ) return code;
+
+ for ( i=0; aca_attrs[i]; i++ ) {
+ code = register_at( aca_attrs[i], NULL, 0 );
+ if ( code ) return code;
+ }
+
+ return overlay_register( &autoca );
+}
+
+#if SLAPD_OVER_AUTOCA == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return autoca_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_AUTOCA) */
diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c
new file mode 100644
index 0000000..bbc6219
--- /dev/null
+++ b/servers/slapd/overlays/collect.c
@@ -0,0 +1,440 @@
+/* collect.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_COLLECT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+
+#include "lutil.h"
+
+/* This is a cheap hack to implement a collective attribute.
+ *
+ * This demonstration overlay looks for a specified attribute in an
+ * ancestor of a given entry and adds that attribute to the given
+ * entry when it is returned in a search response. It takes no effect
+ * for any other operations. If the ancestor does not exist, there
+ * is no effect. If no attribute was configured, there is no effect.
+ */
+
+typedef struct collect_info {
+ struct collect_info *ci_next;
+ struct berval ci_dn;
+ int ci_ad_num;
+ AttributeDescription *ci_ad[1];
+} collect_info;
+
+static int collect_cf( ConfigArgs *c );
+
+static ConfigTable collectcfg[] = {
+ { "collectinfo", "dn> <attribute", 3, 3, 0,
+ ARG_MAGIC, collect_cf,
+ "( OLcfgOvAt:19.1 NAME 'olcCollectInfo' "
+ "DESC 'DN of entry and attribute to distribute' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs collectocs[] = {
+ { "( OLcfgOvOc:19.1 "
+ "NAME 'olcCollectConfig' "
+ "DESC 'Collective Attribute configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcCollectInfo )",
+ Cft_Overlay, collectcfg },
+ { NULL, 0, NULL }
+};
+
+/*
+ * inserts a collect_info into on->on_bi.bi_private taking into account
+ * order. this means longer dn's (i.e. more specific dn's) will be found
+ * first when searching, allowing some limited overlap of dn's
+ */
+static void
+insert_ordered( slap_overinst *on, collect_info *ci ) {
+ collect_info *find = on->on_bi.bi_private;
+ collect_info *prev = NULL;
+ int found = 0;
+
+ while (!found) {
+ if (find == NULL) {
+ if (prev == NULL) {
+ /* base case - empty list */
+ on->on_bi.bi_private = ci;
+ ci->ci_next = NULL;
+ } else {
+ /* final case - end of list */
+ prev->ci_next = ci;
+ ci->ci_next = NULL;
+ }
+ found = 1;
+ } else if (find->ci_dn.bv_len < ci->ci_dn.bv_len) {
+ /* insert into list here */
+ if (prev == NULL) {
+ /* entry is head of list */
+ ci->ci_next = on->on_bi.bi_private;
+ on->on_bi.bi_private = ci;
+ } else {
+ /* entry is not head of list */
+ prev->ci_next = ci;
+ ci->ci_next = find;
+ }
+ found = 1;
+ } else {
+ /* keep looking */
+ prev = find;
+ find = find->ci_next;
+ }
+ }
+}
+
+static int
+collect_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ int rc = 1, idx;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ {
+ collect_info *ci;
+ for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) {
+ struct berval bv;
+ char *ptr;
+ int len;
+
+ /* calculate the length & malloc memory */
+ bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" ");
+ for (idx=0; idx<ci->ci_ad_num; idx++) {
+ bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len;
+ if (idx<(ci->ci_ad_num-1)) {
+ bv.bv_len++;
+ }
+ }
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ /* copy the value and update len */
+ len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ",
+ ci->ci_dn.bv_val);
+ ptr = bv.bv_val + len;
+ for (idx=0; idx<ci->ci_ad_num; idx++) {
+ ptr = lutil_strncopy( ptr,
+ ci->ci_ad[idx]->ad_cname.bv_val,
+ ci->ci_ad[idx]->ad_cname.bv_len);
+ if (idx<(ci->ci_ad_num-1)) {
+ *ptr++ = ',';
+ }
+ }
+ *ptr = '\0';
+ bv.bv_len = ptr - bv.bv_val;
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx == -1 ) {
+ /* Delete entire attribute */
+ collect_info *ci;
+ while (( ci = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ } else {
+ /* Delete just one value */
+ collect_info **cip, *ci;
+ int i;
+ cip = (collect_info **)&on->on_bi.bi_private;
+ ci = *cip;
+ for ( i=0; i < c->valx; i++ ) {
+ cip = &ci->ci_next;
+ ci = *cip;
+ }
+ *cip = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ {
+ collect_info *ci;
+ struct berval bv, dn;
+ const char *text;
+ int idx, count=0;
+ char *arg;
+
+ /* count delimiters in attribute argument */
+ arg = strtok(c->argv[2], ",");
+ while (arg!=NULL) {
+ count++;
+ arg = strtok(NULL, ",");
+ }
+
+ /* validate and normalize dn */
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ /* check for duplicate DNs */
+ for ( ci = (collect_info *)on->on_bi.bi_private; ci;
+ ci = ci->ci_next ) {
+ /* If new DN is longest, there are no possible matches */
+ if ( dn.bv_len > ci->ci_dn.bv_len ) {
+ ci = NULL;
+ break;
+ }
+ if ( bvmatch( &dn, &ci->ci_dn )) {
+ break;
+ }
+ }
+ if ( ci ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s DN already configured: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ /* allocate config info with room for attribute array */
+ ci = ch_malloc( sizeof( collect_info ) +
+ sizeof( AttributeDescription * ) * count );
+
+ /* load attribute description for attribute list */
+ arg = c->argv[2];
+ for( idx=0; idx<count; idx++) {
+ ci->ci_ad[idx] = NULL;
+
+ if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s attribute description unknown: \"%s\"",
+ c->argv[0], arg);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ ch_free( ci );
+ return ARG_BAD_CONF;
+ }
+ while(*arg!='\0') {
+ arg++; /* skip to end of argument */
+ }
+ if (idx<count-1) {
+ arg++; /* skip inner delimiters */
+ }
+ }
+
+ /* The on->on_bi.bi_private pointer can be used for
+ * anything this instance of the overlay needs.
+ */
+ ci->ci_ad[count] = NULL;
+ ci->ci_ad_num = count;
+ ci->ci_dn = dn;
+
+ /* creates list of ci's ordered by dn length */
+ insert_ordered ( on, ci );
+
+ /* New ci wasn't simply appended to end, adjust its
+ * position in the config entry's a_vals
+ */
+ if ( c->ca_entry && ci->ci_next ) {
+ Attribute *a = attr_find( c->ca_entry->e_attrs,
+ collectcfg[0].ad );
+ if ( a ) {
+ struct berval bv, nbv;
+ collect_info *c2 = (collect_info *)on->on_bi.bi_private;
+ int i, j;
+ for ( i=0; c2 != ci; i++, c2 = c2->ci_next );
+ bv = a->a_vals[a->a_numvals-1];
+ nbv = a->a_nvals[a->a_numvals-1];
+ for ( j=a->a_numvals-1; j>i; j-- ) {
+ a->a_vals[j] = a->a_vals[j-1];
+ a->a_nvals[j] = a->a_nvals[j-1];
+ }
+ a->a_vals[j] = bv;
+ a->a_nvals[j] = nbv;
+ }
+ }
+
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static int
+collect_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ collect_info *ci;
+
+ while (( ci = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ return 0;
+}
+
+static int
+collect_modify( Operation *op, SlapReply *rs)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ collect_info *ci = on->on_bi.bi_private;
+ Modifications *ml;
+ char errMsg[100];
+ int idx;
+
+ for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) {
+ for (; ci; ci=ci->ci_next ) {
+ /* Is this entry an ancestor of this collectinfo ? */
+ if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) {
+ /* this collectinfo does not match */
+ continue;
+ }
+
+ /* Is this entry the same as the template DN ? */
+ if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) {
+ /* all changes in this ci are allowed */
+ continue;
+ }
+
+ /* check for collect attributes - disallow modify if present */
+ for(idx=0; idx<ci->ci_ad_num; idx++) {
+ if (ml->sml_desc == ci->ci_ad[idx]) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ snprintf( errMsg, sizeof( errMsg ),
+ "cannot change virtual attribute '%s'",
+ ci->ci_ad[idx]->ad_cname.bv_val);
+ rs->sr_text = errMsg;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+ }
+
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+collect_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ collect_info *ci = on->on_bi.bi_private;
+
+ /* If we've been configured and the current response is
+ * a search entry
+ */
+ if ( ci && rs->sr_type == REP_SEARCH ) {
+ int rc;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ for (; ci; ci=ci->ci_next ) {
+ int idx=0;
+
+ /* Is this entry an ancestor of this collectinfo ? */
+ if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+ /* collectinfo does not match */
+ continue;
+ }
+
+ /* Is this entry the same as the template DN ? */
+ if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+ /* dont apply change to parent */
+ continue;
+ }
+
+ /* The current entry may live in a cache, so
+ * don't modify it directly. Make a copy and
+ * work with that instead.
+ */
+ rs_entry2modifiable( op, rs, on );
+
+ /* Loop for each attribute in this collectinfo */
+ for(idx=0; idx<ci->ci_ad_num; idx++) {
+ BerVarray vals = NULL;
+
+ /* Extract the values of the desired attribute from
+ * the ancestor entry */
+ rc = backend_attribute( op, NULL, &ci->ci_dn,
+ ci->ci_ad[idx], &vals, ACL_READ );
+
+ /* If there are any values, merge them into the
+ * current search result
+ */
+ if ( vals ) {
+ attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx],
+ vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+ }
+ }
+
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst collect;
+
+int collect_initialize() {
+ int code;
+
+ collect.on_bi.bi_type = "collect";
+ collect.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ collect.on_bi.bi_db_destroy = collect_destroy;
+ collect.on_bi.bi_op_modify = collect_modify;
+ collect.on_response = collect_response;
+
+ collect.on_bi.bi_cf_ocs = collectocs;
+ code = config_register_schema( collectcfg, collectocs );
+ if ( code ) return code;
+
+ return overlay_register( &collect );
+}
+
+#if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return collect_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_COLLECT */
diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c
new file mode 100644
index 0000000..f939b37
--- /dev/null
+++ b/servers/slapd/overlays/constraint.c
@@ -0,0 +1,1236 @@
+/* $OpenLDAP$ */
+/* constraint.c - Overlay to constrain attributes to certain values */
+/*
+ * Copyright 2003-2004 Hewlett-Packard Company
+ * Copyright 2007 Emmanuel Dreyfus
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/*
+ * Authors: Neil Dunbar <neil.dunbar@hp.com>
+ * Emmanuel Dreyfus <manu@netbsd.org>
+ */
+#include "portable.h"
+
+#ifdef SLAPD_OVER_CONSTRAINT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/regex.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+/*
+ * This overlay limits the values which can be placed into an
+ * attribute, over and above the limits placed by the schema.
+ *
+ * It traps only LDAP adds and modify commands (and only seeks to
+ * control the add and modify value mods of a modify)
+ */
+
+#define REGEX_STR "regex"
+#define NEG_REGEX_STR "negregex"
+#define URI_STR "uri"
+#define SET_STR "set"
+#define SIZE_STR "size"
+#define COUNT_STR "count"
+
+/*
+ * Linked list of attribute constraints which we should enforce.
+ * This is probably a sub optimal structure - some form of sorted
+ * array would be better if the number of attributes constrained is
+ * likely to be much bigger than 4 or 5. We stick with a list for
+ * the moment.
+ */
+
+typedef struct constraint {
+ struct constraint *ap_next;
+ AttributeDescription **ap;
+
+ LDAPURLDesc *restrict_lud;
+ struct berval restrict_ndn;
+ Filter *restrict_filter;
+ struct berval restrict_val;
+
+ int type;
+ regex_t *re;
+ LDAPURLDesc *lud;
+ int set;
+ size_t size;
+ size_t count;
+ AttributeDescription **attrs;
+ struct berval val; /* constraint value */
+ struct berval dn;
+ struct berval filter;
+} constraint;
+
+enum {
+ CONSTRAINT_ATTRIBUTE = 1,
+ CONSTRAINT_COUNT,
+ CONSTRAINT_SIZE,
+ CONSTRAINT_REGEX,
+ CONSTRAINT_NEG_REGEX,
+ CONSTRAINT_SET,
+ CONSTRAINT_URI,
+};
+
+static ConfigDriver constraint_cf_gen;
+
+static ConfigTable constraintcfg[] = {
+ { "constraint_attribute", "attribute[list]> (regex|negregex|uri|set|size|count) <value> [<restrict URI>]",
+ 4, 0, 0, ARG_MAGIC | CONSTRAINT_ATTRIBUTE, constraint_cf_gen,
+ "( OLcfgOvAt:13.1 NAME 'olcConstraintAttribute' "
+ "DESC 'constraint for list of attributes' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs constraintocs[] = {
+ { "( OLcfgOvOc:13.1 "
+ "NAME 'olcConstraintConfig' "
+ "DESC 'Constraint overlay configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcConstraintAttribute ) )",
+ Cft_Overlay, constraintcfg },
+ { NULL, 0, NULL }
+};
+
+static void
+constraint_free( constraint *cp, int freeme )
+{
+ if (cp->restrict_lud)
+ ldap_free_urldesc(cp->restrict_lud);
+ if (!BER_BVISNULL(&cp->restrict_ndn))
+ ch_free(cp->restrict_ndn.bv_val);
+ if (cp->restrict_filter != NULL && cp->restrict_filter != slap_filter_objectClass_pres)
+ filter_free(cp->restrict_filter);
+ if (!BER_BVISNULL(&cp->restrict_val))
+ ch_free(cp->restrict_val.bv_val);
+ if (cp->re) {
+ regfree(cp->re);
+ ch_free(cp->re);
+ }
+ if (!BER_BVISNULL(&cp->val))
+ ch_free(cp->val.bv_val);
+ if (cp->lud)
+ ldap_free_urldesc(cp->lud);
+ if (cp->attrs)
+ ch_free(cp->attrs);
+ if (cp->ap)
+ ch_free(cp->ap);
+ if (freeme)
+ ch_free(cp);
+}
+
+static int
+constraint_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)(c->bi);
+ constraint *cn = on->on_bi.bi_private, *cp;
+ struct berval bv;
+ int i, rc = 0;
+ constraint ap = { NULL };
+ const char *text = NULL;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE:
+ for (cp=cn; cp; cp=cp->ap_next) {
+ char *s;
+ char *tstr = NULL;
+ int quotes = 0, numeric = 0;
+ int j;
+ size_t val;
+ char val_buf[SLAP_TEXT_BUFLEN] = { '\0' };
+
+ bv.bv_len = STRLENOF(" ");
+ for (j = 0; cp->ap[j]; j++) {
+ bv.bv_len += cp->ap[j]->ad_cname.bv_len;
+ }
+
+ /* room for commas */
+ bv.bv_len += j - 1;
+
+ switch (cp->type) {
+ case CONSTRAINT_COUNT:
+ tstr = COUNT_STR;
+ val = cp->count;
+ numeric = 1;
+ break;
+ case CONSTRAINT_SIZE:
+ tstr = SIZE_STR;
+ val = cp->size;
+ numeric = 1;
+ break;
+ case CONSTRAINT_REGEX:
+ tstr = REGEX_STR;
+ quotes = 1;
+ break;
+ case CONSTRAINT_NEG_REGEX:
+ tstr = NEG_REGEX_STR;
+ quotes = 1;
+ break;
+ case CONSTRAINT_SET:
+ tstr = SET_STR;
+ quotes = 1;
+ break;
+ case CONSTRAINT_URI:
+ tstr = URI_STR;
+ quotes = 1;
+ break;
+ default:
+ abort();
+ }
+
+ bv.bv_len += strlen(tstr);
+ bv.bv_len += cp->val.bv_len + 2*quotes;
+
+ if (cp->restrict_lud != NULL) {
+ bv.bv_len += cp->restrict_val.bv_len + STRLENOF(" restrict=\"\"");
+ }
+
+ if (numeric) {
+ int len = snprintf(val_buf, sizeof(val_buf), "%zu", val);
+ if (len <= 0) {
+ /* error */
+ return -1;
+ }
+ bv.bv_len += len;
+ }
+
+ s = bv.bv_val = ch_malloc(bv.bv_len + 1);
+
+ s = lutil_strncopy( s, cp->ap[0]->ad_cname.bv_val, cp->ap[0]->ad_cname.bv_len );
+ for (j = 1; cp->ap[j]; j++) {
+ *s++ = ',';
+ s = lutil_strncopy( s, cp->ap[j]->ad_cname.bv_val, cp->ap[j]->ad_cname.bv_len );
+ }
+ *s++ = ' ';
+ s = lutil_strcopy( s, tstr );
+ *s++ = ' ';
+ if (numeric) {
+ s = lutil_strcopy( s, val_buf );
+ } else {
+ if ( quotes ) *s++ = '"';
+ s = lutil_strncopy( s, cp->val.bv_val, cp->val.bv_len );
+ if ( quotes ) *s++ = '"';
+ }
+ if (cp->restrict_lud != NULL) {
+ s = lutil_strcopy( s, " restrict=\"" );
+ s = lutil_strncopy( s, cp->restrict_val.bv_val, cp->restrict_val.bv_len );
+ *s++ = '"';
+ }
+ *s = '\0';
+
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if (rc == LDAP_SUCCESS)
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ ch_free(bv.bv_val);
+ if (rc) return rc;
+ }
+ break;
+ default:
+ abort();
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE:
+ if (!cn) break; /* nothing to do */
+
+ if (c->valx < 0) {
+ /* zap all constraints */
+ while (cn) {
+ cp = cn->ap_next;
+ constraint_free( cn, 1 );
+ cn = cp;
+ }
+
+ on->on_bi.bi_private = NULL;
+ } else {
+ constraint **cpp;
+
+ /* zap constraint numbered 'valx' */
+ for(i=0, cp = cn, cpp = &cn;
+ (cp) && (i<c->valx);
+ i++, cpp = &cp->ap_next, cp = *cpp);
+
+ if (cp) {
+ /* zap cp, and join cpp to cp->ap_next */
+ *cpp = cp->ap_next;
+ constraint_free( cp, 1 );
+ }
+ on->on_bi.bi_private = cn;
+ }
+ break;
+
+ default:
+ abort();
+ break;
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE: {
+ int j;
+ char **attrs = ldap_str2charray( c->argv[1], "," );
+
+ for ( j = 0; attrs[j]; j++)
+ /* just count */ ;
+ ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 );
+ for ( j = 0; attrs[j]; j++) {
+ if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s\n", c->argv[0], attrs[j], text );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+
+ int is_regex = strcasecmp( c->argv[2], REGEX_STR ) == 0;
+ int is_neg_regex = strcasecmp( c->argv[2], NEG_REGEX_STR ) == 0;
+ if ( is_regex || is_neg_regex ) {
+ int err;
+
+ ap.type = is_regex ? CONSTRAINT_REGEX : CONSTRAINT_NEG_REGEX;
+ ap.re = ch_malloc( sizeof(regex_t) );
+ if ((err = regcomp( ap.re,
+ c->argv[3], REG_EXTENDED )) != 0) {
+ char errmsg[1024];
+
+ regerror( err, ap.re, errmsg, sizeof(errmsg) );
+ ch_free(ap.re);
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Illegal regular expression \"%s\": Error %s",
+ c->argv[0], c->argv[1], c->argv[3], errmsg);
+ ap.re = NULL;
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+ } else if ( strcasecmp( c->argv[2], SIZE_STR ) == 0 ) {
+ size_t size;
+ char *endptr;
+
+ ap.type = CONSTRAINT_SIZE;
+ ap.size = strtoull(c->argv[3], &endptr, 10);
+ if ( *endptr )
+ rc = ARG_BAD_CONF;
+ } else if ( strcasecmp( c->argv[2], COUNT_STR ) == 0 ) {
+ size_t count;
+ char *endptr;
+
+ ap.type = CONSTRAINT_COUNT;
+ ap.count = strtoull(c->argv[3], &endptr, 10);
+ if ( *endptr )
+ rc = ARG_BAD_CONF;
+ } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) {
+ int err;
+
+ ap.type = CONSTRAINT_URI;
+ err = ldap_url_parse(c->argv[3], &ap.lud);
+ if ( err != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Invalid URI \"%s\"",
+ c->argv[0], c->argv[1], c->argv[3]);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if (ap.lud->lud_host != NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unsupported hostname in URI \"%s\"",
+ c->argv[0], c->argv[1], c->argv[3]);
+ ldap_free_urldesc(ap.lud);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ for ( i=0; ap.lud->lud_attrs[i]; i++);
+ /* FIXME: This is worthless without at least one attr */
+ if ( i ) {
+ ap.attrs = ch_malloc( (i+1)*sizeof(AttributeDescription *));
+ for ( i=0; ap.lud->lud_attrs[i]; i++) {
+ ap.attrs[i] = NULL;
+ if ( slap_str2ad( ap.lud->lud_attrs[i], &ap.attrs[i], &text ) ) {
+ ch_free( ap.attrs );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s\n", c->argv[0], ap.lud->lud_attrs[i], text );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+ ap.attrs[i] = NULL;
+ }
+
+ if (ap.lud->lud_dn == NULL) {
+ ap.lud->lud_dn = ch_strdup("");
+ } else {
+ struct berval dn, ndn;
+
+ ber_str2bv( ap.lud->lud_dn, 0, 0, &dn );
+ if (dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) ) {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: URI %s DN normalization failed",
+ c->argv[0], c->argv[1], c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ldap_memfree( ap.lud->lud_dn );
+ ap.lud->lud_dn = ndn.bv_val;
+ }
+
+ if (ap.lud->lud_filter == NULL) {
+ ap.lud->lud_filter = ch_strdup("objectClass=*");
+ } else if ( ap.lud->lud_filter[0] == '(' ) {
+ ber_len_t len = strlen( ap.lud->lud_filter );
+ if ( ap.lud->lud_filter[len - 1] != ')' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: invalid URI filter: %s",
+ c->argv[0], c->argv[1], ap.lud->lud_filter );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ AC_MEMCPY( &ap.lud->lud_filter[0], &ap.lud->lud_filter[1], len - 2 );
+ ap.lud->lud_filter[len - 2] = '\0';
+ }
+
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+
+ } else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) {
+ ap.set = 1;
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+ ap.type = CONSTRAINT_SET;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Unknown constraint type: %s",
+ c->argv[0], c->argv[1], c->argv[2] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if ( c->argc > 4 ) {
+ int argidx;
+
+ for ( argidx = 4; argidx < c->argc; argidx++ ) {
+ if ( strncasecmp( c->argv[argidx], "restrict=", STRLENOF("restrict=") ) == 0 ) {
+ int err;
+ char *arg = c->argv[argidx] + STRLENOF("restrict=");
+
+ err = ldap_url_parse(arg, &ap.restrict_lud);
+ if ( err != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Invalid restrict URI \"%s\"",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if (ap.restrict_lud->lud_host != NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unsupported hostname in restrict URI \"%s\"",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if ( ap.restrict_lud->lud_attrs != NULL ) {
+ if ( ap.restrict_lud->lud_attrs[0] != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: attrs not allowed in restrict URI %s\n",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ldap_memvfree((void *)ap.restrict_lud->lud_attrs);
+ ap.restrict_lud->lud_attrs = NULL;
+ }
+
+ if (ap.restrict_lud->lud_dn != NULL) {
+ if (ap.restrict_lud->lud_dn[0] == '\0') {
+ ldap_memfree(ap.restrict_lud->lud_dn);
+ ap.restrict_lud->lud_dn = NULL;
+
+ } else {
+ struct berval dn, ndn;
+ int j;
+
+ ber_str2bv(ap.restrict_lud->lud_dn, 0, 0, &dn);
+ if (dnNormalize(0, NULL, NULL, &dn, &ndn, NULL)) {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI %s DN normalization failed",
+ c->argv[0], c->argv[1], arg );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ assert(c->be != NULL);
+ if (c->be->be_nsuffix == NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI requires suffix",
+ c->argv[0], c->argv[1] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ for ( j = 0; !BER_BVISNULL(&c->be->be_nsuffix[j]); j++) {
+ if (dnIsSuffix(&ndn, &c->be->be_nsuffix[j])) break;
+ }
+
+ if (BER_BVISNULL(&c->be->be_nsuffix[j])) {
+ /* error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI DN %s not within database naming context(s)",
+ c->argv[0], c->argv[1], dn.bv_val );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ ap.restrict_ndn = ndn;
+ }
+ }
+
+ if (ap.restrict_lud->lud_filter != NULL) {
+ ap.restrict_filter = str2filter(ap.restrict_lud->lud_filter);
+ if (ap.restrict_filter == NULL) {
+ /* error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI filter %s invalid",
+ c->argv[0], c->argv[1], ap.restrict_lud->lud_filter );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+
+ ber_str2bv(c->argv[argidx] + STRLENOF("restrict="), 0, 1, &ap.restrict_val);
+
+ } else {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unrecognized arg #%d (%s)",
+ c->argv[0], c->argv[1], argidx, c->argv[argidx] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+ }
+
+done:;
+ if ( rc == LDAP_SUCCESS ) {
+ constraint **app, *a2 = ch_calloc( sizeof(constraint), 1 );
+
+ a2->ap = ap.ap;
+ a2->type = ap.type;
+ a2->re = ap.re;
+ a2->val = ap.val;
+ a2->lud = ap.lud;
+ a2->set = ap.set;
+ a2->size = ap.size;
+ a2->count = ap.count;
+ if ( a2->lud ) {
+ ber_str2bv(a2->lud->lud_dn, 0, 0, &a2->dn);
+ ber_str2bv(a2->lud->lud_filter, 0, 0, &a2->filter);
+ }
+ a2->attrs = ap.attrs;
+ a2->restrict_lud = ap.restrict_lud;
+ a2->restrict_ndn = ap.restrict_ndn;
+ a2->restrict_filter = ap.restrict_filter;
+ a2->restrict_val = ap.restrict_val;
+
+ for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next )
+ /* Get to the end */ ;
+
+ a2->ap_next = *app;
+ *app = a2;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ constraint_free( &ap, 0 );
+ }
+
+ ldap_memvfree((void**)attrs);
+ } break;
+ default:
+ abort();
+ break;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+constraint_uri_cb( Operation *op, SlapReply *rs )
+{
+ if(rs->sr_type == REP_SEARCH) {
+ int *foundp = op->o_callback->sc_private;
+
+ *foundp = 1;
+
+ Debug(LDAP_DEBUG_TRACE, "==> constraint_uri_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN" );
+ }
+ return 0;
+}
+
+static int
+constraint_violation( constraint *c, struct berval *bv, Operation *op )
+{
+ if ((!c) || (!bv)) return LDAP_SUCCESS;
+
+ switch (c->type) {
+ case CONSTRAINT_SIZE:
+ if (bv->bv_len > c->size)
+ return LDAP_CONSTRAINT_VIOLATION; /* size violation */
+ break;
+ case CONSTRAINT_REGEX:
+ if (regexec(c->re, bv->bv_val, 0, NULL, 0) == REG_NOMATCH)
+ return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */
+ break;
+ case CONSTRAINT_NEG_REGEX:
+ if (regexec(c->re, bv->bv_val, 0, NULL, 0) != REG_NOMATCH)
+ return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */
+ break;
+ case CONSTRAINT_URI: {
+ Operation nop = *op;
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ slap_callback cb = { 0 };
+ int i;
+ int found = 0;
+ int rc;
+ size_t len;
+ struct berval filterstr;
+ char *ptr;
+
+ cb.sc_response = constraint_uri_cb;
+ cb.sc_private = &found;
+
+ nop.o_protocol = LDAP_VERSION3;
+ nop.o_tag = LDAP_REQ_SEARCH;
+ nop.o_time = slap_get_time();
+ if (c->lud->lud_dn) {
+ struct berval dn;
+
+ ber_str2bv(c->lud->lud_dn, 0, 0, &dn);
+ nop.o_req_dn = dn;
+ nop.o_req_ndn = dn;
+ nop.o_bd = select_backend(&nop.o_req_ndn, 1 );
+ if (!nop.o_bd) {
+ return LDAP_NO_SUCH_OBJECT; /* unexpected error */
+ }
+ if (!nop.o_bd->be_search) {
+ return LDAP_OTHER; /* unexpected error */
+ }
+ } else {
+ nop.o_req_dn = nop.o_bd->be_nsuffix[0];
+ nop.o_req_ndn = nop.o_bd->be_nsuffix[0];
+ nop.o_bd = on->on_info->oi_origdb;
+ }
+ nop.o_do_not_cache = 1;
+ nop.o_callback = &cb;
+
+ nop.ors_scope = c->lud->lud_scope;
+ nop.ors_deref = LDAP_DEREF_NEVER;
+ nop.ors_slimit = SLAP_NO_LIMIT;
+ nop.ors_tlimit = SLAP_NO_LIMIT;
+ nop.ors_limit = NULL;
+
+ nop.ors_attrsonly = 0;
+ nop.ors_attrs = slap_anlist_no_attrs;
+
+ len = STRLENOF("(&(") +
+ c->filter.bv_len +
+ STRLENOF(")(|");
+
+ for (i = 0; c->attrs[i]; i++) {
+ len += STRLENOF("(") +
+ c->attrs[i]->ad_cname.bv_len +
+ STRLENOF("=") +
+ bv->bv_len +
+ STRLENOF(")");
+ }
+
+ len += STRLENOF("))");
+ filterstr.bv_len = len;
+ filterstr.bv_val = op->o_tmpalloc(len + 1, op->o_tmpmemctx);
+
+ ptr = filterstr.bv_val +
+ snprintf(filterstr.bv_val, len, "(&(%s)(|", c->lud->lud_filter);
+ for (i = 0; c->attrs[i]; i++) {
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, c->attrs[i]->ad_cname.bv_val );
+ *ptr++ = '=';
+ ptr = lutil_strcopy( ptr, bv->bv_val );
+ *ptr++ = ')';
+ }
+ *ptr++ = ')';
+ *ptr++ = ')';
+ *ptr++ = '\0';
+
+ nop.ors_filterstr = filterstr;
+ nop.ors_filter = str2filter_x(&nop, filterstr.bv_val);
+ if ( nop.ors_filter == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s constraint_violation uri filter=\"%s\" invalid\n",
+ op->o_log_prefix, filterstr.bv_val );
+ rc = LDAP_OTHER;
+
+ } else {
+ SlapReply nrs = { REP_RESULT };
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri filter = %s\n",
+ filterstr.bv_val );
+
+ rc = nop.o_bd->be_search( &nop, &nrs );
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri rc = %d, found = %d\n",
+ rc, found );
+ }
+ op->o_tmpfree(filterstr.bv_val, op->o_tmpmemctx);
+
+ if ((rc != LDAP_SUCCESS) && (rc != LDAP_NO_SUCH_OBJECT)) {
+ return rc; /* unexpected error */
+ }
+
+ if (!found)
+ return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */
+ break;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static char *
+print_message( struct berval *errtext, AttributeDescription *a )
+{
+ char *ret;
+ int sz;
+
+ sz = errtext->bv_len + sizeof(" on ") + a->ad_cname.bv_len;
+ ret = ch_malloc(sz);
+ snprintf( ret, sz, "%s on %s", errtext->bv_val, a->ad_cname.bv_val );
+ return ret;
+}
+
+static unsigned
+constraint_count_attr(Entry *e, AttributeDescription *ad)
+{
+ struct Attribute *a;
+
+ if ((a = attr_find(e->e_attrs, ad)) != NULL)
+ return a->a_numvals;
+ return 0;
+}
+
+static int
+constraint_check_restrict( Operation *op, constraint *c, Entry *e )
+{
+ assert( c->restrict_lud != NULL );
+
+ if ( c->restrict_lud->lud_dn != NULL ) {
+ int diff = e->e_nname.bv_len - c->restrict_ndn.bv_len;
+
+ if ( diff < 0 ) {
+ return 0;
+ }
+
+ if ( c->restrict_lud->lud_scope == LDAP_SCOPE_BASE ) {
+ return bvmatch( &e->e_nname, &c->restrict_ndn );
+ }
+
+ if ( !dnIsSuffix( &e->e_nname, &c->restrict_ndn ) ) {
+ return 0;
+ }
+
+ if ( c->restrict_lud->lud_scope != LDAP_SCOPE_SUBTREE ) {
+ struct berval pdn;
+
+ if ( diff == 0 ) {
+ return 0;
+ }
+
+ dnParent( &e->e_nname, &pdn );
+
+ if ( c->restrict_lud->lud_scope == LDAP_SCOPE_ONELEVEL
+ && pdn.bv_len != c->restrict_ndn.bv_len )
+ {
+ return 0;
+ }
+ }
+ }
+
+ if ( c->restrict_filter != NULL ) {
+ int rc;
+ struct berval save_dn = op->o_dn, save_ndn = op->o_ndn;
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ rc = test_filter( op, e, c->restrict_filter );
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+constraint_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Attribute *a;
+ constraint *c = on->on_bi.bi_private, *cp;
+ BerVarray b = NULL;
+ int i;
+ struct berval rsv = BER_BVC("add breaks constraint");
+ int rc = 0;
+ char *msg = NULL;
+
+ if ( get_relax(op) || be_shadow_update( op ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ((a = op->ora_e->e_attrs) == NULL) {
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "constraint_add: no attrs");
+ return(rs->sr_err);
+ }
+
+ for(; a; a = a->a_next ) {
+ /* we don't constrain operational attributes */
+ if (is_at_operational(a->a_desc->ad_type)) continue;
+
+ for(cp = c; cp; cp = cp->ap_next) {
+ int j;
+ for (j = 0; cp->ap[j]; j++) {
+ if (cp->ap[j] == a->a_desc) break;
+ }
+ if (cp->ap[j] == NULL) continue;
+ if ((b = a->a_vals) == NULL) continue;
+
+ if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, op->ora_e) == 0) {
+ continue;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_add, "
+ "a->a_numvals = %u, cp->count = %lu\n",
+ a->a_numvals, (unsigned long) cp->count );
+
+ switch (cp->type) {
+ case CONSTRAINT_COUNT:
+ if (a->a_numvals > cp->count)
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ case CONSTRAINT_SET:
+ if (acl_match_set(&cp->val, op, op->ora_e, NULL) == 0)
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ default:
+ for ( i = 0; b[i].bv_val; i++ ) {
+ rc = constraint_violation( cp, &b[i], op );
+ if ( rc ) {
+ goto add_violation;
+ }
+ }
+ }
+ if ( rc )
+ goto add_violation;
+
+ }
+ }
+
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+
+add_violation:
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ if (rc == LDAP_CONSTRAINT_VIOLATION ) {
+ msg = print_message( &rsv, a->a_desc );
+ }
+ send_ldap_error(op, rs, rc, msg );
+ ch_free(msg);
+ return (rs->sr_err);
+}
+
+
+static int
+constraint_check_count_violation( Modifications *m, Entry *target_entry, constraint *cp )
+{
+ BerVarray b = NULL;
+ unsigned ce = 0;
+ unsigned ca;
+ int j;
+
+ for ( j = 0; cp->ap[j]; j++ ) {
+ /* Get this attribute count */
+ if ( target_entry )
+ ce = constraint_count_attr( target_entry, cp->ap[j] );
+
+ for( ; m; m = m->sml_next ) {
+ if ( cp->ap[j] == m->sml_desc ) {
+ ca = m->sml_numvals;
+ switch ( m->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL:
+ if ( !ca || ca > ce ) {
+ ce = 0;
+ } else {
+ /* No need to check for values' validity. Invalid values
+ * cause the whole transaction to die anyway. */
+ ce -= ca;
+ }
+ break;
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD:
+ ce += ca;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ ce = ca;
+ break;
+
+#if 0
+ /* TODO */
+ case handle SLAP_MOD_ADD_IF_NOT_PRESENT:
+#endif
+
+ default:
+ /* impossible! assert? */
+ return 1;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_check_count_violation ce = %u, "
+ "ca = %u, cp->count = %lu\n",
+ ce, ca, (unsigned long) cp->count);
+ }
+ }
+ }
+
+ return ( ce > cp->count );
+}
+
+static int
+constraint_update( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Backend *be = op->o_bd;
+ constraint *c = on->on_bi.bi_private, *cp;
+ Entry *target_entry = NULL, *target_entry_copy = NULL;
+ Modifications *modlist, *m;
+ BerVarray b = NULL;
+ int i;
+ struct berval rsv = BER_BVC("modify breaks constraint");
+ int rc;
+ char *msg = NULL;
+ int is_v;
+
+ if ( get_relax(op) || be_shadow_update( op ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_MODIFY:
+ modlist = op->orm_modlist;
+ break;
+
+ case LDAP_REQ_MODRDN:
+ modlist = op->orr_modlist;
+ break;
+
+ default:
+ /* impossible! assert? */
+ return LDAP_OTHER;
+ }
+
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_update()\n" );
+ if ((m = modlist) == NULL) {
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "constraint_update() got null modlist");
+ return(rs->sr_err);
+ }
+
+ op->o_bd = on->on_info->oi_origdb;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry );
+ op->o_bd = be;
+
+ /* let the backend send the error */
+ if ( target_entry == NULL )
+ return SLAP_CB_CONTINUE;
+
+ /* Do we need to count attributes? */
+ for(cp = c; cp; cp = cp->ap_next) {
+ if (cp->type == CONSTRAINT_COUNT) {
+ if (cp->restrict_lud && constraint_check_restrict(op, cp, target_entry) == 0) {
+ continue;
+ }
+
+ is_v = constraint_check_count_violation(m, target_entry, cp);
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_update is_v: %d\n", is_v );
+
+ if (is_v) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto mod_violation;
+ }
+ }
+ }
+
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ for(;m; m = m->sml_next) {
+ unsigned ce = 0;
+
+ if (is_at_operational( m->sml_desc->ad_type )) continue;
+
+ if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) &&
+ (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) &&
+ (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE))
+ continue;
+ /* we only care about ADD and REPLACE modifications */
+ /* and DELETE are used to track attribute count */
+ if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL))
+ continue;
+
+ for(cp = c; cp; cp = cp->ap_next) {
+ int j;
+ for (j = 0; cp->ap[j]; j++) {
+ if (cp->ap[j] == m->sml_desc) {
+ break;
+ }
+ }
+ if (cp->ap[j] == NULL) continue;
+
+ if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, target_entry) == 0) {
+ continue;
+ }
+
+ /* DELETE are to be ignored beyond this point */
+ if (( m->sml_op & LDAP_MOD_OP ) == LDAP_MOD_DELETE)
+ continue;
+
+ for ( i = 0; b[i].bv_val; i++ ) {
+ rc = constraint_violation( cp, &b[i], op );
+ if ( rc ) {
+ goto mod_violation;
+ }
+ }
+
+ if (cp->type == CONSTRAINT_SET && target_entry) {
+ if (target_entry_copy == NULL) {
+ Modifications *ml;
+
+ target_entry_copy = entry_dup(target_entry);
+
+ /* if rename, set the new entry's name */
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ ber_bvreplace( &target_entry_copy->e_name, &op->orr_newDN );
+ ber_bvreplace( &target_entry_copy->e_nname, &op->orr_nnewDN );
+ }
+
+ /* apply modifications, in an attempt
+ * to estimate what the entry would
+ * look like in case all modifications
+ * pass */
+ for ( ml = modlist; ml; ml = ml->sml_next ) {
+ Modification *mod = &ml->sml_mod;
+ const char *text;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+ int err;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_DELETE:
+ err = modify_delete_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ err = modify_replace_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ err = modify_increment_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_SOFTADD;
+ if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ err = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_SOFTDEL:
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_delete_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_SOFTDEL;
+ if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+ err = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( attr_find( target_entry_copy->e_attrs, mod->sm_desc ) ) {
+ err = LDAP_SUCCESS;
+ break;
+ }
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+ break;
+
+ default:
+ err = LDAP_OTHER;
+ break;
+ }
+
+ if ( err != LDAP_SUCCESS ) {
+ rc = err;
+ goto mod_violation;
+ }
+ }
+ }
+
+ if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto mod_violation;
+ }
+ }
+ }
+ }
+
+ if (target_entry) {
+ op->o_bd = on->on_info->oi_origdb;
+ be_entry_release_r(op, target_entry);
+ op->o_bd = be;
+ }
+
+ if (target_entry_copy) {
+ entry_free(target_entry_copy);
+ }
+
+ return SLAP_CB_CONTINUE;
+
+mod_violation:
+ /* violation */
+ if (target_entry) {
+ op->o_bd = on->on_info->oi_origdb;
+ be_entry_release_r(op, target_entry);
+ op->o_bd = be;
+ }
+
+ if (target_entry_copy) {
+ entry_free(target_entry_copy);
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ if ( rc == LDAP_CONSTRAINT_VIOLATION ) {
+ msg = print_message( &rsv, m->sml_desc );
+ }
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, msg );
+ ch_free(msg);
+ return (rs->sr_err);
+}
+
+static int
+constraint_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ constraint *ap, *a2;
+
+ for ( ap = on->on_bi.bi_private; ap; ap = a2 ) {
+ a2 = ap->ap_next;
+ constraint_free( ap, 1 );
+ }
+
+ return 0;
+}
+
+static slap_overinst constraint_ovl;
+
+#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC
+static
+#endif
+int
+constraint_initialize( void ) {
+ int rc;
+
+ constraint_ovl.on_bi.bi_type = "constraint";
+ constraint_ovl.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ constraint_ovl.on_bi.bi_db_destroy = constraint_destroy;
+ constraint_ovl.on_bi.bi_op_add = constraint_add;
+ constraint_ovl.on_bi.bi_op_modify = constraint_update;
+ constraint_ovl.on_bi.bi_op_modrdn = constraint_update;
+
+ constraint_ovl.on_bi.bi_private = NULL;
+
+ constraint_ovl.on_bi.bi_cf_ocs = constraintocs;
+ rc = config_register_schema( constraintcfg, constraintocs );
+ if (rc) return rc;
+
+ return overlay_register( &constraint_ovl );
+}
+
+#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return constraint_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_CONSTRAINT) */
+
diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c
new file mode 100644
index 0000000..c19f042
--- /dev/null
+++ b/servers/slapd/overlays/dds.c
@@ -0,0 +1,2056 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2005-2006 SysNet s.n.c.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.n.c.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DDS
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+
+#include "slap-config.h"
+
+#define DDS_RF2589_MAX_TTL (31557600) /* 1 year + 6 hours */
+#define DDS_RF2589_DEFAULT_TTL (86400) /* 1 day */
+#define DDS_DEFAULT_INTERVAL (3600) /* 1 hour */
+
+typedef struct dds_info_t {
+ unsigned di_flags;
+#define DDS_FOFF (0x1U) /* is this really needed? */
+#define DDS_SET(di, f) ( (di)->di_flags & (f) )
+
+#define DDS_OFF(di) DDS_SET( (di), DDS_FOFF )
+
+ time_t di_max_ttl;
+ time_t di_min_ttl;
+ time_t di_default_ttl;
+#define DDS_DEFAULT_TTL(di) \
+ ( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl )
+
+ time_t di_tolerance;
+
+ /* expire check interval and task */
+ time_t di_interval;
+#define DDS_INTERVAL(di) \
+ ( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL )
+ struct re_s *di_expire_task;
+
+ /* allows to limit the maximum number of dynamic objects */
+ ldap_pvt_thread_mutex_t di_mutex;
+ int di_num_dynamicObjects;
+ int di_max_dynamicObjects;
+
+ /* used to advertise the dynamicSubtrees in the root DSE,
+ * and to select the database in the expiration task */
+ BerVarray di_suffix;
+ BerVarray di_nsuffix;
+} dds_info_t;
+
+static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH );
+static AttributeDescription *ad_entryExpireTimestamp;
+
+/* list of expired DNs */
+typedef struct dds_expire_t {
+ struct berval de_ndn;
+ struct dds_expire_t *de_next;
+} dds_expire_t;
+
+typedef struct dds_cb_t {
+ dds_expire_t *dc_ndnlist;
+} dds_cb_t;
+
+static int
+dds_expire_cb( Operation *op, SlapReply *rs )
+{
+ dds_cb_t *dc = (dds_cb_t *)op->o_callback->sc_private;
+ dds_expire_t *de;
+ int rc;
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ /* alloc list and buffer for berval all in one */
+ de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1,
+ op->o_tmpmemctx );
+
+ de->de_next = dc->dc_ndnlist;
+ dc->dc_ndnlist = de;
+
+ de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len;
+ de->de_ndn.bv_val = (char *)&de[ 1 ];
+ AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val,
+ rs->sr_entry->e_nname.bv_len + 1 );
+ rc = 0;
+ break;
+
+ case REP_SEARCHREF:
+ case REP_RESULT:
+ rc = rs->sr_err;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ return rc;
+}
+
+static int
+dds_expire( void *ctx, dds_info_t *di )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback sc = { 0 };
+ dds_cb_t dc = { 0 };
+ dds_expire_t *de = NULL, **dep;
+ SlapReply rs = { REP_RESULT };
+
+ time_t expire;
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval ts;
+
+ int ndeletes, ntotdeletes;
+
+ int rc;
+ char *extra = "";
+
+ connection_fake_init2( &conn, &opbuf, ctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+ op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0 );
+
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_tlimit = DDS_INTERVAL( di )/2 + 1;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->o_do_not_cache = 1;
+
+ expire = slap_get_time() - di->di_tolerance;
+ ts.bv_val = tsbuf;
+ ts.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &ts );
+
+ op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" )
+ + slap_schema.si_oc_dynamicObject->soc_cname.bv_len
+ + ad_entryExpireTimestamp->ad_cname.bv_len
+ + ts.bv_len;
+ op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+ snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+ "(&(objectClass=%s)(%s<=%s))",
+ slap_schema.si_oc_dynamicObject->soc_cname.bv_val,
+ ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val );
+
+ op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+ if ( op->ors_filter == NULL ) {
+ rs.sr_err = LDAP_OTHER;
+ goto done_search;
+ }
+
+ op->o_callback = &sc;
+ sc.sc_response = dds_expire_cb;
+ sc.sc_private = &dc;
+
+ (void)op->o_bd->bd_info->bi_op_search( op, &rs );
+
+done_search:;
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+
+ rc = rs.sr_err;
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* (ITS#5267) database not created yet? */
+ rs.sr_err = LDAP_SUCCESS;
+ extra = " (ignored)";
+ /* fallthru */
+
+ default:
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS expired objects lookup failed err=%d%s\n",
+ rc, extra );
+ goto done;
+ }
+
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_callback = &sc;
+ sc.sc_response = slap_null_cb;
+ sc.sc_private = NULL;
+
+ for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL && ndeletes > 0; ) {
+ ndeletes = 0;
+
+ for ( dep = &dc.dc_ndnlist; *dep != NULL; ) {
+ de = *dep;
+
+ op->o_req_dn = de->de_ndn;
+ op->o_req_ndn = de->de_ndn;
+ (void)op->o_bd->bd_info->bi_op_delete( op, &rs );
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS dn=\"%s\" expired.\n",
+ de->de_ndn.bv_val );
+ ndeletes++;
+ break;
+
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "DDS dn=\"%s\" is non-leaf; "
+ "deferring.\n",
+ de->de_ndn.bv_val );
+ dep = &de->de_next;
+ de = NULL;
+ break;
+
+ default:
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "DDS dn=\"%s\" err=%d; "
+ "deferring.\n",
+ de->de_ndn.bv_val, rs.sr_err );
+ break;
+ }
+
+ if ( de != NULL ) {
+ *dep = de->de_next;
+ op->o_tmpfree( de, op->o_tmpmemctx );
+ }
+ }
+
+ ntotdeletes += ndeletes;
+ }
+
+ rs.sr_err = LDAP_SUCCESS;
+
+ Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS expired=%d\n", ntotdeletes );
+
+done:;
+ return rs.sr_err;
+}
+
+static void *
+dds_expire_fn( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ dds_info_t *di = rtask->arg;
+
+ assert( di->di_expire_task == rtask );
+
+ (void)dds_expire( ctx, di );
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+/* frees the callback */
+static int
+dds_freeit_cb( Operation *op, SlapReply *rs )
+{
+ op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* updates counter - installed on add/delete only if required */
+static int
+dds_counter_cb( Operation *op, SlapReply *rs )
+{
+ assert( rs->sr_type == REP_RESULT );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ dds_info_t *di = op->o_callback->sc_private;
+
+ ldap_pvt_thread_mutex_lock( &di->di_mutex );
+ switch ( op->o_tag ) {
+ case LDAP_REQ_DELETE:
+ assert( di->di_num_dynamicObjects > 0 );
+ di->di_num_dynamicObjects--;
+ break;
+
+ case LDAP_REQ_ADD:
+ assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects );
+ di->di_num_dynamicObjects++;
+ break;
+
+ default:
+ assert( 0 );
+ }
+ ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+ }
+
+ return dds_freeit_cb( op, rs );
+}
+
+static int
+dds_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int is_dynamicObject;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ is_dynamicObject = is_entry_dynamicObject( op->ora_e );
+
+ /* FIXME: do not allow this right now, pending clarification */
+ if ( is_dynamicObject ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ if ( is_entry_referral( op->ora_e ) ) {
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "a referral cannot be a dynamicObject";
+
+ } else if ( is_entry_alias( op->ora_e ) ) {
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "an alias cannot be a dynamicObject";
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+
+ /* we don't allow dynamicObjects to have static subordinates */
+ if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) {
+ struct berval p_ndn;
+ Entry *e = NULL;
+ int rc;
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ dnParent( &op->o_req_ndn, &p_ndn );
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &p_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ if ( !is_dynamicObject ) {
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+
+ } else {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" );
+ }
+ }
+
+ be_entry_release_r( op, e );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+ op->o_bd->bd_info = bi;
+ }
+
+ /* handle dynamic object operational attr(s) */
+ if ( is_dynamicObject ) {
+ time_t ttl, expire;
+ char ttlbuf[STRLENOF("31557600") + 1];
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval bv;
+
+ if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) {
+ ldap_pvt_thread_mutex_lock( &di->di_mutex );
+ rs->sr_err = ( di->di_max_dynamicObjects &&
+ di->di_num_dynamicObjects >= di->di_max_dynamicObjects );
+ ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+ if ( rs->sr_err ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "too many dynamicObjects in context" );
+ return rs->sr_err;
+ }
+ }
+
+ ttl = DDS_DEFAULT_TTL( di );
+
+ /* assert because should be checked at configure */
+ assert( ttl <= DDS_RF2589_MAX_TTL );
+
+ bv.bv_val = ttlbuf;
+ bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl );
+ assert( bv.bv_len < sizeof( ttlbuf ) );
+
+ /* FIXME: apparently, values in op->ora_e are malloc'ed
+ * on the thread's slab; works fine by chance,
+ * only because the attribute doesn't exist yet. */
+ assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL );
+ attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv );
+
+ expire = slap_get_time() + ttl;
+ bv.bv_val = tsbuf;
+ bv.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &bv );
+ assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL );
+ attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv );
+
+ /* if required, install counter callback */
+ if ( di->di_max_dynamicObjects > 0) {
+ slap_callback *sc;
+
+ sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+ sc->sc_cleanup = dds_freeit_cb;
+ sc->sc_response = dds_counter_cb;
+ sc->sc_private = di;
+ sc->sc_next = op->o_callback;
+ sc->sc_writewait = 0;
+
+ op->o_callback = sc;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ /* if required, install counter callback */
+ if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) {
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+
+ /* FIXME: couldn't the entry be added before deletion? */
+ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+ slap_callback *sc;
+
+ be_entry_release_r( op, e );
+ e = NULL;
+
+ sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+ sc->sc_cleanup = dds_freeit_cb;
+ sc->sc_response = dds_counter_cb;
+ sc->sc_private = di;
+ sc->sc_writewait = 0;
+ sc->sc_next = op->o_callback;
+
+ op->o_callback = sc;
+ }
+ op->o_bd->bd_info = bi;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = (dds_info_t *)on->on_bi.bi_private;
+ Modifications *mod;
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int was_dynamicObject = 0,
+ is_dynamicObject = 0;
+ struct berval bv_entryTtl = BER_BVNULL;
+ time_t entryTtl = 0;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* bv_entryTtl stores the string representation of the entryTtl
+ * across modifies for consistency checks of the final value;
+ * the bv_val points to a static buffer; the bv_len is zero when
+ * the attribute is deleted.
+ * entryTtl stores the integer representation of the entryTtl;
+ * its value is -1 when the attribute is deleted; it is 0 only
+ * if no modifications of the entryTtl occurred, as an entryTtl
+ * of 0 is invalid. */
+ bv_entryTtl.bv_val = textbuf;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e );
+ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+ Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl );
+
+ /* the value of the entryTtl is saved for later checks */
+ if ( a != NULL ) {
+ unsigned long ttl;
+ int rc;
+
+ bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len;
+ AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len );
+ bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+ rc = lutil_atoul( &ttl, bv_entryTtl.bv_val );
+ assert( rc == 0 );
+ entryTtl = (time_t)ttl;
+ }
+
+ be_entry_release_r( op, e );
+ e = NULL;
+ was_dynamicObject = is_dynamicObject = 1;
+ }
+ op->o_bd->bd_info = bi;
+
+ rs->sr_err = LDAP_SUCCESS;
+ for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
+ if ( mod->sml_desc == slap_schema.si_ad_objectClass ) {
+ int i;
+ ObjectClass *oc;
+
+ switch ( mod->sml_op ) {
+ case LDAP_MOD_DELETE:
+ if ( mod->sml_values == NULL ) {
+ is_dynamicObject = 0;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+ oc = oc_bvfind( &mod->sml_values[ i ] );
+ if ( oc == slap_schema.si_oc_dynamicObject ) {
+ is_dynamicObject = 0;
+ break;
+ }
+ }
+
+ break;
+
+ case LDAP_MOD_REPLACE:
+ if ( mod->sml_values == NULL ) {
+ is_dynamicObject = 0;
+ break;
+ }
+ /* fallthru */
+
+ case LDAP_MOD_ADD:
+ for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+ oc = oc_bvfind( &mod->sml_values[ i ] );
+ if ( oc == slap_schema.si_oc_dynamicObject ) {
+ is_dynamicObject = 1;
+ break;
+ }
+ }
+ break;
+ }
+
+ } else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) {
+ unsigned long uttl;
+ time_t ttl;
+ int rc;
+
+ switch ( mod->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* FIXME? */
+ if ( mod->sml_values != NULL ) {
+ if ( BER_BVISEMPTY( &bv_entryTtl )
+ || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) )
+ {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ goto done;
+ }
+ }
+ bv_entryTtl.bv_len = 0;
+ entryTtl = -1;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ bv_entryTtl.bv_len = 0;
+ entryTtl = -1;
+ /* fallthru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* FIXME? */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */
+ assert( mod->sml_values != NULL );
+ assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) );
+
+ if ( !BER_BVISEMPTY( &bv_entryTtl ) ) {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_text = "attribute 'entryTtl' cannot have multiple values";
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ }
+ goto done;
+ }
+
+ rc = lutil_atoul( &uttl, mod->sml_values[ 0 ].bv_val );
+ ttl = (time_t)uttl;
+ assert( rc == 0 );
+ if ( ttl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+ goto done;
+ }
+
+ if ( ttl <= 0 || ttl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+ goto done;
+ }
+
+ entryTtl = ttl;
+ bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len;
+ AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len );
+ bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ if ( BER_BVISEMPTY( &bv_entryTtl ) ) {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ rs->sr_text = "modify/increment: entryTtl: no such attribute";
+ }
+ goto done;
+ }
+
+ entryTtl++;
+ if ( entryTtl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+
+ } else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_text = NULL;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ }
+ goto done;
+ }
+
+ bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl );
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ } else if ( mod->sml_desc == ad_entryExpireTimestamp ) {
+ /* should have been trapped earlier */
+ assert( mod->sml_flags & SLAP_MOD_INTERNAL );
+ }
+ }
+
+done:;
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int rc;
+
+ /* FIXME: this could be allowed when the Relax control is used...
+ * in that case:
+ *
+ * TODO
+ *
+ * static => dynamic:
+ * entryTtl must be provided; add
+ * entryExpireTimestamp accordingly
+ *
+ * dynamic => static:
+ * entryTtl must be removed; remove
+ * entryTimestamp accordingly
+ *
+ * ... but we need to make sure that there are no subordinate
+ * issues...
+ */
+ rc = is_dynamicObject - was_dynamicObject;
+ if ( rc ) {
+#if 0 /* fix subordinate issues first */
+ if ( get_relax( op ) ) {
+ switch ( rc ) {
+ case -1:
+ /* need to delete entryTtl to have a consistent entry */
+ if ( entryTtl != -1 ) {
+ rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion";
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ case 1:
+ /* need to add entryTtl to have a consistent entry */
+ if ( entryTtl <= 0 ) {
+ rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition";
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+ }
+
+ } else
+#endif
+ {
+ switch ( rc ) {
+ case -1:
+ rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry";
+ break;
+
+ case 1:
+ rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject";
+ break;
+ }
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_text = NULL;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+ }
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) {
+ Modifications *tmpmod = NULL, **modp;
+
+ for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next )
+ ;
+
+ tmpmod = ch_calloc( 1, sizeof( Modifications ) );
+ tmpmod->sml_flags = SLAP_MOD_INTERNAL;
+ tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname;
+ tmpmod->sml_desc = ad_entryExpireTimestamp;
+
+ *modp = tmpmod;
+
+ if ( entryTtl == -1 ) {
+ /* delete entryExpireTimestamp */
+ tmpmod->sml_op = LDAP_MOD_DELETE;
+
+ } else {
+ time_t expire;
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval bv;
+
+ /* keep entryExpireTimestamp consistent
+ * with entryTtl */
+ expire = slap_get_time() + entryTtl;
+ bv.bv_val = tsbuf;
+ bv.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &bv );
+
+ tmpmod->sml_op = LDAP_MOD_REPLACE;
+ value_add_one( &tmpmod->sml_values, &bv );
+ value_add_one( &tmpmod->sml_nvalues, &bv );
+ tmpmod->sml_numvals = 1;
+ }
+ }
+
+ if ( rs->sr_err ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_rename( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* we don't allow dynamicObjects to have static subordinates */
+ if ( op->orr_nnewSup != NULL ) {
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int is_dynamicObject = 0,
+ rc;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ be_entry_release_r( op, e );
+ e = NULL;
+ is_dynamicObject = 1;
+ }
+
+ rc = be_entry_get_rw( op, op->orr_nnewSup,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ if ( !is_dynamicObject ) {
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+
+ } else {
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "static entry cannot have dynamicObject as newSuperior" );
+ }
+ }
+ be_entry_release_r( op, e );
+ }
+ op->o_bd->bd_info = bi;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* entryTtl update for client */
+static int
+dds_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc;
+
+ if ( !DDS_OFF( di )
+ && rs->sr_type == REP_SEARCH
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) )
+ {
+ BerVarray vals = NULL;
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+ char ttlbuf[STRLENOF("31557600") + 1];
+ struct berval ttlvalue;
+ time_t ttl;
+ int len;
+
+ /* User already has access to entryTtl, skip ACL checks on
+ * entryExpireTimestamp */
+ rc = backend_attribute( op, NULL, &rs->sr_entry->e_nname,
+ ad_entryExpireTimestamp, &vals, ACL_NONE );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ assert( vals[0].bv_val[vals[0].bv_len] == '\0' );
+ if ( lutil_parsetime( vals[0].bv_val, &tm ) ) {
+ goto done;
+ }
+
+ lutil_tm2time( &tm, &tt );
+ ttl = tt.tt_sec - op->o_time;
+ ttl = (ttl < 0) ? 0 : ttl;
+ assert( ttl <= DDS_RF2589_MAX_TTL );
+
+ len = snprintf( ttlbuf, sizeof(ttlbuf), "%ld", ttl );
+ if ( len < 0 )
+ {
+ goto done;
+ }
+ ttlvalue.bv_val = ttlbuf;
+ ttlvalue.bv_len = len;
+
+ rs_entry2modifiable( op, rs, on );
+
+ if ( attr_delete( &rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryTtl ) )
+ {
+ goto done;
+ }
+ if ( attr_merge_normalize_one( rs->sr_entry,
+ slap_schema.si_ad_entryTtl,
+ &ttlvalue, op->o_tmpmemctx ) )
+ {
+ goto done;
+ }
+
+done:;
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+slap_parse_refresh(
+ struct berval *in,
+ struct berval *ndn,
+ time_t *ttl,
+ const char **text,
+ void *ctx )
+{
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval reqdata = BER_BVNULL;
+ int tmp;
+
+ *text = NULL;
+
+ if ( ndn ) {
+ BER_BVZERO( ndn );
+ }
+
+ if ( in == NULL || in->bv_len == 0 ) {
+ *text = "empty request data field in refresh exop";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_dupbv_x( &reqdata, in, ctx );
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, &reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+
+ if ( tag == LBER_ERROR ) {
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) {
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ if ( ndn ) {
+ struct berval dn;
+
+ tag = ber_scanf( ber, "m", &dn );
+ if ( tag == LBER_ERROR ) {
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: DN parse failed.\n" );
+ goto decoding_error;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ *text = "invalid DN in refresh exop request data";
+ goto done;
+ }
+
+ } else {
+ tag = ber_scanf( ber, "x" /* "m" */ );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) {
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ tag = ber_scanf( ber, "i", &tmp );
+ if ( tag == LBER_ERROR ) {
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: TTL parse failed.\n" );
+ goto decoding_error;
+ }
+
+ if ( ttl ) {
+ *ttl = tmp;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag != LBER_DEFAULT || len != 0 ) {
+decoding_error:;
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error, len=%ld\n",
+ (long)len );
+ rc = LDAP_PROTOCOL_ERROR;
+ *text = "data decoding error";
+
+done:;
+ if ( ndn && !BER_BVISNULL( ndn ) ) {
+ slap_sl_free( ndn->bv_val, ctx );
+ BER_BVZERO( ndn );
+ }
+ }
+
+ if ( !BER_BVISNULL( &reqdata ) ) {
+ ber_memfree_x( reqdata.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+static int
+dds_op_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) {
+ Entry *e = NULL;
+ time_t ttl;
+ BackendDB db = *op->o_bd;
+ SlapReply rs2 = { REP_RESULT };
+ Operation op2 = *op;
+ slap_callback sc = { 0 };
+ Modifications ttlmod = { { 0 } };
+ struct berval ttlvalues[ 2 ];
+ char ttlbuf[STRLENOF("31557600") + 1];
+
+ rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl,
+ &rs->sr_text, NULL );
+ assert( rs->sr_err == LDAP_SUCCESS );
+
+ if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+ return rs->sr_err;
+ }
+
+ if ( ttl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds limit";
+ return rs->sr_err;
+ }
+
+ if ( di->di_min_ttl && ttl < di->di_min_ttl ) {
+ ttl = di->di_min_ttl;
+ }
+
+ /* This does not apply to multi-provider case */
+ if ( !( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ) ) ) {
+ /* we SHOULD return a referral in this case */
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if ( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs,
+ NULL, NULL, LDAP_SCOPE_DEFAULT );
+ if ( rs->sr_ref ) {
+ rs->sr_flags |= REP_REF_MUSTBEFREED;
+ } else {
+ rs->sr_ref = defref;
+ }
+ rs->sr_err = LDAP_REFERRAL;
+
+ } else {
+ rs->sr_text = "shadow context; no update referral";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return rs->sr_err;
+ }
+
+ assert( !BER_BVISNULL( &op->o_req_ndn ) );
+
+
+
+ /* check if exists but not dynamicObject */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ NULL, NULL, 0, &e );
+ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "refresh operation only applies to dynamic objects";
+ }
+ be_entry_release_r( op, e );
+
+ } else {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ return rs->sr_err;
+
+ } else if ( e != NULL ) {
+ be_entry_release_r( op, e );
+ }
+
+ /* we require manage privileges on the entryTtl,
+ * and fake a Relax control */
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_bd = &db;
+ db.bd_info = (BackendInfo *)on->on_info;
+ op2.o_callback = &sc;
+ sc.sc_response = slap_null_cb;
+ op2.o_relax = SLAP_CONTROL_CRITICAL;
+ op2.orm_modlist = &ttlmod;
+
+ ttlmod.sml_op = LDAP_MOD_REPLACE;
+ ttlmod.sml_flags = SLAP_MOD_MANAGING;
+ ttlmod.sml_desc = slap_schema.si_ad_entryTtl;
+ ttlmod.sml_values = ttlvalues;
+ ttlmod.sml_numvals = 1;
+ ttlvalues[ 0 ].bv_val = ttlbuf;
+ ttlvalues[ 0 ].bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl );
+ BER_BVZERO( &ttlvalues[ 1 ] );
+
+ /* the entryExpireTimestamp is added by modify */
+ rs->sr_err = op2.o_bd->be_modify( &op2, &rs2 );
+
+ if ( ttlmod.sml_next != NULL ) {
+ slap_mods_free( ttlmod.sml_next, 1 );
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int rc;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ ber_init_w_nullc( ber, LBER_USE_DER );
+
+ rc = ber_printf( ber, "{tiN}", LDAP_TAG_EXOP_REFRESH_RES_TTL, (int)ttl );
+
+ if ( rc < 0 ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "internal error";
+
+ } else {
+ (void)ber_flatten( ber, &rs->sr_rspdata );
+ rs->sr_rspoid = ch_strdup( slap_EXOP_REFRESH.bv_val );
+
+ Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO,
+ "%s REFRESH dn=\"%s\" TTL=%ld\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val, ttl );
+ }
+
+ ber_free_buf( ber );
+ }
+
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+enum {
+ DDS_STATE = 1,
+ DDS_MAXTTL,
+ DDS_MINTTL,
+ DDS_DEFAULTTTL,
+ DDS_INTERVAL,
+ DDS_TOLERANCE,
+ DDS_MAXDYNAMICOBJS,
+
+ DDS_LAST
+};
+
+static ConfigDriver dds_cfgen;
+#if 0
+static ConfigLDAPadd dds_ldadd;
+static ConfigCfAdd dds_cfadd;
+#endif
+
+static ConfigTable dds_cfg[] = {
+ { "dds-state", "on|off",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen,
+ "( OLcfgOvAt:9.1 NAME 'olcDDSstate' "
+ "DESC 'RFC2589 Dynamic directory services state' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-max-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen,
+ "( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' "
+ "DESC 'RFC2589 Dynamic directory services max TTL' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-min-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen,
+ "( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' "
+ "DESC 'RFC2589 Dynamic directory services min TTL' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-default-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen,
+ "( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' "
+ "DESC 'RFC2589 Dynamic directory services default TTL' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-interval", "interval",
+ 2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen,
+ "( OLcfgOvAt:9.5 NAME 'olcDDSinterval' "
+ "DESC 'RFC2589 Dynamic directory services expiration "
+ "task run interval' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-tolerance", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen,
+ "( OLcfgOvAt:9.6 NAME 'olcDDStolerance' "
+ "DESC 'RFC2589 Dynamic directory services additional "
+ "TTL in expiration scheduling' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-max-dynamicObjects", "num",
+ 2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen,
+ "( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' "
+ "DESC 'RFC2589 Dynamic directory services max number of dynamic objects' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dds_ocs[] = {
+ { "( OLcfgOvOc:9.1 "
+ "NAME 'olcDDSConfig' "
+ "DESC 'RFC2589 Dynamic directory services configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcDDSstate "
+ "$ olcDDSmaxTtl "
+ "$ olcDDSminTtl "
+ "$ olcDDSdefaultTtl "
+ "$ olcDDSinterval "
+ "$ olcDDStolerance "
+ "$ olcDDSmaxDynamicObjects "
+ " ) "
+ ")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ },
+ { NULL, 0, NULL }
+};
+
+#if 0
+static int
+dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ return 0;
+}
+#endif
+
+static int
+dds_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc = 0;
+ unsigned long t;
+
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+
+ switch( c->type ) {
+ case DDS_STATE:
+ c->value_int = !DDS_OFF( di );
+ break;
+
+ case DDS_MAXTTL:
+ lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case DDS_MINTTL:
+ if ( di->di_min_ttl ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_DEFAULTTTL:
+ if ( di->di_default_ttl ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_INTERVAL:
+ if ( di->di_interval ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_interval );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_TOLERANCE:
+ if ( di->di_tolerance ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ if ( di->di_max_dynamicObjects > 0 ) {
+ c->value_int = di->di_max_dynamicObjects;
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case DDS_STATE:
+ di->di_flags &= ~DDS_FOFF;
+ break;
+
+ case DDS_MAXTTL:
+ di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+ break;
+
+ case DDS_MINTTL:
+ di->di_min_ttl = 0;
+ break;
+
+ case DDS_DEFAULTTTL:
+ di->di_default_ttl = 0;
+ break;
+
+ case DDS_INTERVAL:
+ di->di_interval = 0;
+ break;
+
+ case DDS_TOLERANCE:
+ di->di_tolerance = 0;
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ di->di_max_dynamicObjects = 0;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ switch ( c->type ) {
+ case DDS_STATE:
+ if ( c->value_int ) {
+ di->di_flags &= ~DDS_FOFF;
+
+ } else {
+ di->di_flags |= DDS_FOFF;
+ }
+ break;
+
+ case DDS_MAXTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-max-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-max-ttl=%lu; must be between %d and %d",
+ t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ di->di_max_ttl = (time_t)t;
+ break;
+
+ case DDS_MINTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-min-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-min-ttl=%lu",
+ t );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t == 0 ) {
+ di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ } else {
+ di->di_min_ttl = (time_t)t;
+ }
+ break;
+
+ case DDS_DEFAULTTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-default-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-default-ttl=%lu",
+ t );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t == 0 ) {
+ di->di_default_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ } else {
+ di->di_default_ttl = (time_t)t;
+ }
+ break;
+
+ case DDS_INTERVAL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-interval \"%s\"",
+ c->argv[ 1 ] );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-interval=%lu",
+ t );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t < 60 ) {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "%s: dds-interval=%lu may be too small.\n",
+ c->log, t );
+ }
+
+ di->di_interval = (time_t)t;
+ if ( di->di_expire_task ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+ }
+ di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di );
+ ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ break;
+
+ case DDS_TOLERANCE:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-tolerance \"%s\"",
+ c->argv[ 1 ] );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-tolerance=%lu",
+ t );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ di->di_tolerance = (time_t)t;
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ if ( c->value_int < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-max-dynamicObjects=%d", c->value_int );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ di->di_max_dynamicObjects = c->value_int;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+dds_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di;
+ BackendInfo *bi = on->on_info->oi_orig;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS cannot be used as global overlay.\n" );
+ return 1;
+ }
+
+ /* check support for required functions */
+ /* FIXME: some could be provided by other overlays in between */
+ if ( bi->bi_op_add == NULL /* object creation */
+ || bi->bi_op_delete == NULL /* object deletion */
+ || bi->bi_op_modify == NULL /* object refresh */
+ || bi->bi_op_search == NULL /* object expiration */
+ || bi->bi_entry_get_rw == NULL ) /* object type/existence checking */
+ {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS backend \"%s\" does not provide "
+ "required functionality.\n",
+ bi->bi_type );
+ return 1;
+ }
+
+ di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) );
+ on->on_bi.bi_private = di;
+
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ ldap_pvt_thread_mutex_init( &di->di_mutex );
+
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC;
+
+ return 0;
+}
+
+/* adds dynamicSubtrees to root DSE */
+static int
+dds_entry_info( void *arg, Entry *e )
+{
+ dds_info_t *di = (dds_info_t *)arg;
+
+ attr_merge( e, slap_schema.si_ad_dynamicSubtrees,
+ di->di_suffix, di->di_nsuffix );
+
+ return 0;
+}
+
+/* callback that counts the returned entries, since the search
+ * does not get to the point in slap_send_search_entries where
+ * the actual count occurs */
+static int
+dds_count_cb( Operation *op, SlapReply *rs )
+{
+ int *nump = (int *)op->o_callback->sc_private;
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ (*nump)++;
+ break;
+
+ case REP_SEARCHREF:
+ case REP_RESULT:
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ return 0;
+}
+
+/* count dynamic objects existing in the database at startup */
+static int
+dds_count( void *ctx, BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = (dds_info_t *)on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback sc = { 0 };
+ SlapReply rs = { REP_RESULT };
+
+ int rc;
+ char *extra = "";
+
+ connection_fake_init2( &conn, &opbuf, ctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+ op->o_bd = be;
+
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->o_do_not_cache = 1;
+
+ op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" )
+ + slap_schema.si_oc_dynamicObject->soc_cname.bv_len;
+ op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+ snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+ "(objectClass=%s)",
+ slap_schema.si_oc_dynamicObject->soc_cname.bv_val );
+
+ op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+ if ( op->ors_filter == NULL ) {
+ rs.sr_err = LDAP_OTHER;
+ goto done_search;
+ }
+
+ op->o_callback = &sc;
+ sc.sc_response = dds_count_cb;
+ sc.sc_private = &di->di_num_dynamicObjects;
+ di->di_num_dynamicObjects = 0;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ (void)op->o_bd->bd_info->bi_op_search( op, &rs );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+done_search:;
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+
+ rc = rs.sr_err;
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS non-expired=%d\n",
+ di->di_num_dynamicObjects );
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* (ITS#5267) database not created yet? */
+ rs.sr_err = LDAP_SUCCESS;
+ extra = " (ignored)";
+ /* fallthru */
+
+ default:
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS non-expired objects lookup failed err=%d%s\n",
+ rc, extra );
+ break;
+ }
+
+ return rs.sr_err;
+}
+
+static int
+dds_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc = 0;
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ if ( slapMode & SLAP_TOOL_MODE )
+ return 0;
+
+ if ( DDS_OFF( di ) ) {
+ goto done;
+ }
+
+ if ( SLAP_SINGLE_SHADOW( be ) ) {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS incompatible with shadow database \"%s\".\n",
+ be->be_suffix[ 0 ].bv_val );
+ return 1;
+ }
+
+ if ( di->di_max_ttl == 0 ) {
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ }
+
+ if ( di->di_min_ttl == 0 ) {
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ }
+
+ di->di_suffix = be->be_suffix;
+ di->di_nsuffix = be->be_nsuffix;
+
+ /* count the dynamic objects first */
+ rc = dds_count( thrctx, be );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = 1;
+ goto done;
+ }
+
+ /* start expire task */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ DDS_INTERVAL( di ),
+ dds_expire_fn, di, "dds_expire_fn",
+ be->be_suffix[ 0 ].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ /* register dinamicSubtrees root DSE info support */
+ rc = entry_info_register( dds_entry_info, (void *)di );
+
+done:;
+
+ return rc;
+}
+
+static int
+dds_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ /* stop expire task */
+ if ( di && di->di_expire_task ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ di->di_expire_task = NULL;
+ }
+
+ (void)entry_info_unregister( dds_entry_info, (void *)di );
+
+ return 0;
+}
+
+static int
+dds_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( di != NULL ) {
+ ldap_pvt_thread_mutex_destroy( &di->di_mutex );
+
+ free( di );
+ }
+
+ return 0;
+}
+
+static int
+slap_exop_refresh(
+ Operation *op,
+ SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL,
+ &rs->sr_text, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "%s REFRESH dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val );
+ op->o_req_dn = op->o_req_ndn;
+
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL ) {
+ send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT,
+ "no global superior knowledge" );
+ goto done;
+ }
+
+ if ( !SLAP_DYNAMIC( op->o_bd ) ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support dynamic directory services" );
+ goto done;
+ }
+
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_REFRESH );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if ( op->o_bd->be_extended == NULL ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support extended operations" );
+ goto done;
+ }
+
+ op->o_bd->be_extended( op, rs );
+
+done:;
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ BER_BVZERO( &op->o_req_dn );
+ }
+ op->o_bd = bd;
+
+ return rs->sr_err;
+}
+
+static slap_overinst dds;
+
+static int do_not_load_exop;
+static int do_not_replace_exop;
+static int do_not_load_schema;
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+int
+dds_initialize()
+{
+ int rc = 0;
+ int i, code;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( DDS_LAST );
+
+ if ( !do_not_load_schema ) {
+ static struct {
+ char *desc;
+ slap_mask_t flags;
+ AttributeDescription **ad;
+ } s_at[] = {
+ { "( 1.3.6.1.4.1.4203.666.1.57 "
+ "NAME ( 'entryExpireTimestamp' ) "
+ "DESC 'RFC2589 OpenLDAP extension: expire time of a dynamic object, "
+ "computed as now + entryTtl' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ SLAP_AT_HIDE,
+ &ad_entryExpireTimestamp },
+ { NULL }
+ };
+
+ for ( i = 0; s_at[ i ].desc != NULL; i++ ) {
+ code = register_at( s_at[ i ].desc, s_at[ i ].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dds_initialize: register_at failed\n" );
+ return code;
+ }
+ (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ if ( !do_not_load_exop ) {
+ rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH,
+ SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh,
+ !do_not_replace_exop );
+ if ( rc != LDAP_SUCCESS ) {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS unable to register refresh exop: %d.\n",
+ rc );
+ return rc;
+ }
+ }
+
+ dds.on_bi.bi_type = "dds";
+
+ dds.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ dds.on_bi.bi_db_init = dds_db_init;
+ dds.on_bi.bi_db_open = dds_db_open;
+ dds.on_bi.bi_db_close = dds_db_close;
+ dds.on_bi.bi_db_destroy = dds_db_destroy;
+
+ dds.on_bi.bi_op_add = dds_op_add;
+ dds.on_bi.bi_op_delete = dds_op_delete;
+ dds.on_bi.bi_op_modify = dds_op_modify;
+ dds.on_bi.bi_op_modrdn = dds_op_rename;
+ dds.on_bi.bi_extended = dds_op_extended;
+ dds.on_response = dds_response;
+
+ dds.on_bi.bi_cf_ocs = dds_ocs;
+
+ rc = config_register_schema( dds_cfg, dds_ocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &dds );
+}
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ int i;
+
+ for ( i = 0; i < argc; i++ ) {
+ char *arg = argv[ i ];
+ int no = 0;
+
+ if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) {
+ arg += STRLENOF( "no-" );
+ no = 1;
+ }
+
+ if ( strcasecmp( arg, "exop" ) == 0 ) {
+ do_not_load_exop = no;
+
+ } else if ( strcasecmp( arg, "replace" ) == 0 ) {
+ do_not_replace_exop = no;
+
+ } else if ( strcasecmp( arg, "schema" ) == 0 ) {
+ do_not_load_schema = no;
+
+ } else {
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS unknown module arg[#%d]=\"%s\".\n",
+ i, argv[ i ] );
+ return 1;
+ }
+ }
+
+ return dds_initialize();
+}
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_DDS) */
diff --git a/servers/slapd/overlays/deref.c b/servers/slapd/overlays/deref.c
new file mode 100644
index 0000000..93b7f69
--- /dev/null
+++ b/servers/slapd/overlays/deref.c
@@ -0,0 +1,586 @@
+/* deref.c - dereference overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2008 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati
+ * for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DEREF
+
+#include <stdio.h>
+
+#include "ac/string.h"
+#include "ac/socket.h"
+
+#include "slap.h"
+#include "slap-config.h"
+
+#include "lutil.h"
+
+/*
+ * 1. Specification
+ *
+ * 1.1. Request
+ *
+ * controlValue ::= SEQUENCE OF derefSpec DerefSpec
+ *
+ * DerefSpec ::= SEQUENCE {
+ * derefAttr attributeDescription, ; DN-valued
+ * attributes AttributeList }
+ *
+ * AttributeList ::= SEQUENCE OF attr AttributeDescription
+ *
+ * derefAttr MUST be unique within controlValue
+ *
+ *
+ * 1.2. Response
+ *
+ * controlValue ::= SEQUENCE OF DerefRes
+ *
+ * From RFC 4511:
+ * PartialAttribute ::= SEQUENCE {
+ * type AttributeDescription,
+ * vals SET OF value AttributeValue }
+ *
+ * PartialAttributeList ::= SEQUENCE OF
+ * partialAttribute PartialAttribute
+ *
+ * DerefRes ::= SEQUENCE {
+ * derefAttr AttributeDescription,
+ * derefVal LDAPDN,
+ * attrVals [0] PartialAttributeList OPTIONAL }
+ *
+ * If vals is empty, partialAttribute is omitted.
+ * If all vals in attrVals are empty, attrVals is omitted.
+ *
+ * 2. Examples
+ *
+ * 2.1. Example
+ *
+ * 2.1.1. Request
+ *
+ * { { member, { GUID, SID } }, { memberOf, { GUID, SID } } }
+ *
+ * 2.1.2. Response
+ *
+ * { { memberOf, "cn=abartlet,cn=users,dc=abartlet,dc=net",
+ * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fa" ] },
+ * { SID, [ "S-1-2-3-2345" ] } } },
+ * { memberOf, "cn=ando,cn=users,dc=sys-net,dc=it",
+ * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fb" ] },
+ * { SID, [ "S-1-2-3-2346" ] } } } }
+ *
+ * 2.2. Example
+ *
+ * 2.2.1. Request
+ *
+ * { { member, { cn, uid, drink } } }
+ *
+ * 2.2.2. Response
+ *
+ * { { member, "cn=ando,cn=users,dc=sys-net,dc=it",
+ * { { cn, [ "ando", "Pierangelo Masarati" ] },
+ * { uid, [ "ando" ] } } },
+ * { member, "dc=sys-net,dc=it" } }
+ *
+ *
+ * 3. Security considerations
+ *
+ * The control result must not disclose information the client's
+ * identity could not have accessed directly by performing the related
+ * search operations. The presence of a derefVal in the control
+ * response does not imply neither the existence of nor any access
+ * privilege to the corresponding entry. It is merely a consequence
+ * of the read access the client's identity has on the corresponding
+ * attribute's value.
+ */
+
+#define o_deref o_ctrlflag[deref_cid]
+#define o_ctrlderef o_controls[deref_cid]
+
+typedef struct DerefSpec {
+ AttributeDescription *ds_derefAttr;
+ AttributeDescription **ds_attributes;
+ int ds_nattrs;
+ struct DerefSpec *ds_next;
+} DerefSpec;
+
+typedef struct DerefVal {
+ struct berval dv_derefSpecVal;
+ BerVarray *dv_attrVals;
+} DerefVal;
+
+typedef struct DerefRes {
+ DerefSpec dr_spec;
+ DerefVal *dr_vals;
+ struct DerefRes *dr_next;
+} DerefRes;
+
+typedef struct deref_cb_t {
+ slap_overinst *dc_on;
+ DerefSpec *dc_ds;
+} deref_cb_t;
+
+static int deref_cid;
+static slap_overinst deref;
+static int ov_count;
+
+static int
+deref_parseCtrl (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_len_t len;
+ char *last;
+ DerefSpec *dshead = NULL, **dsp = &dshead;
+ BerVarray attributes = NULL;
+
+ if ( op->o_deref != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Dereference control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Dereference control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Dereference control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ struct berval derefAttr;
+ DerefSpec *ds, *dstmp;
+ const char *text;
+ int rc;
+ ber_len_t cnt = sizeof(struct berval);
+ ber_len_t off = 0;
+
+ if ( ber_scanf( ber, "{m{M}}", &derefAttr, &attributes, &cnt, off ) == LBER_ERROR
+ || !cnt )
+ {
+ rs->sr_text = "Dereference control: derefSpec decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ ds = (DerefSpec *)op->o_tmpcalloc( 1,
+ sizeof(DerefSpec) + sizeof(AttributeDescription *)*(cnt + 1),
+ op->o_tmpmemctx );
+ ds->ds_attributes = (AttributeDescription **)&ds[ 1 ];
+ ds->ds_nattrs = cnt;
+
+ rc = slap_bv2ad( &derefAttr, &ds->ds_derefAttr, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = "Dereference control: derefAttr decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ for ( dstmp = dshead; dstmp && dstmp != ds; dstmp = dstmp->ds_next ) {
+ if ( dstmp->ds_derefAttr == ds->ds_derefAttr ) {
+ rs->sr_text = "Dereference control: derefAttr must be unique within control";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ }
+
+ if ( !( ds->ds_derefAttr->ad_type->sat_syntax->ssyn_flags & SLAP_SYNTAX_DN )) {
+ if ( ctrl->ldctl_iscritical ) {
+ rs->sr_text = "Dereference control: derefAttr syntax not distinguishedName";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ goto justcleanup;
+ }
+
+ for ( cnt = 0; !BER_BVISNULL( &attributes[ cnt ] ); cnt++ ) {
+ rc = slap_bv2ad( &attributes[ cnt ], &ds->ds_attributes[ cnt ], &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = "Dereference control: attribute decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ }
+
+ ber_memfree_x( attributes, op->o_tmpmemctx );
+ attributes = NULL;
+
+ *dsp = ds;
+ dsp = &ds->ds_next;
+ }
+
+ op->o_ctrlderef = (void *)dshead;
+
+ op->o_deref = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+justcleanup:;
+ for ( ; dshead; ) {
+ DerefSpec *dsnext = dshead->ds_next;
+ op->o_tmpfree( dshead, op->o_tmpmemctx );
+ dshead = dsnext;
+ }
+ }
+
+ if ( attributes != NULL ) {
+ ber_memfree_x( attributes, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+deref_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) {
+ op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+ op->o_callback = NULL;
+
+ op->o_tmpfree( op->o_ctrlderef, op->o_tmpmemctx );
+ op->o_ctrlderef = NULL;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+deref_response( Operation *op, SlapReply *rs )
+{
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ deref_cb_t *dc = (deref_cb_t *)op->o_callback->sc_private;
+ DerefSpec *ds;
+ DerefRes *dr, *drhead = NULL, **drp = &drhead;
+ struct berval bv = BER_BVNULL;
+ int nDerefRes = 0, nDerefVals = 0, nAttrs = 0, nVals = 0;
+ struct berval ctrlval;
+ LDAPControl *ctrl, *ctrlsp[2];
+ AccessControlState acl_state = ACL_STATE_INIT;
+ static char dummy = '\0';
+ Entry *ebase;
+ int i;
+
+ rc = overlay_entry_get_ov( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &ebase, dc->dc_on );
+ if ( rc != LDAP_SUCCESS || ebase == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( ds = dc->dc_ds; ds; ds = ds->ds_next ) {
+ Attribute *a = attr_find( ebase->e_attrs, ds->ds_derefAttr );
+
+ if ( a != NULL ) {
+ DerefVal *dv;
+ BerVarray *bva;
+
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc,
+ NULL, ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+
+ dr = op->o_tmpcalloc( 1,
+ sizeof( DerefRes ) + ( sizeof( DerefVal ) + sizeof( BerVarray * ) * ds->ds_nattrs ) * ( a->a_numvals + 1 ),
+ op->o_tmpmemctx );
+ dr->dr_spec = *ds;
+ dv = dr->dr_vals = (DerefVal *)&dr[ 1 ];
+ bva = (BerVarray *)&dv[ a->a_numvals + 1 ];
+
+ bv.bv_len += ds->ds_derefAttr->ad_cname.bv_len;
+ nAttrs++;
+ nDerefRes++;
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e = NULL;
+
+ dv[ i ].dv_attrVals = bva;
+ bva += ds->ds_nattrs;
+
+
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc,
+ &a->a_nvals[ i ], ACL_READ, &acl_state ) )
+ {
+ dv[ i ].dv_derefSpecVal.bv_val = &dummy;
+ continue;
+ }
+
+ ber_dupbv_x( &dv[ i ].dv_derefSpecVal, &a->a_vals[ i ], op->o_tmpmemctx );
+ bv.bv_len += dv[ i ].dv_derefSpecVal.bv_len;
+ nVals++;
+ nDerefVals++;
+
+ rc = overlay_entry_get_ov( op, &a->a_nvals[ i ], NULL, NULL, 0, &e, dc->dc_on );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ int j;
+
+ if ( access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ for ( j = 0; j < ds->ds_nattrs; j++ ) {
+ Attribute *aa;
+
+ if ( !access_allowed( op, e, ds->ds_attributes[ j ], NULL,
+ ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+
+ aa = attr_find( e->e_attrs, ds->ds_attributes[ j ] );
+ if ( aa != NULL ) {
+ unsigned k, h, last = aa->a_numvals;
+
+ ber_bvarray_dup_x( &dv[ i ].dv_attrVals[ j ],
+ aa->a_vals, op->o_tmpmemctx );
+
+ bv.bv_len += ds->ds_attributes[ j ]->ad_cname.bv_len;
+
+ for ( k = 0, h = 0; k < aa->a_numvals; k++ ) {
+ if ( !access_allowed( op, e,
+ aa->a_desc,
+ &aa->a_nvals[ k ],
+ ACL_READ, &acl_state ) )
+ {
+ op->o_tmpfree( dv[ i ].dv_attrVals[ j ][ h ].bv_val,
+ op->o_tmpmemctx );
+ dv[ i ].dv_attrVals[ j ][ h ] = dv[ i ].dv_attrVals[ j ][ --last ];
+ BER_BVZERO( &dv[ i ].dv_attrVals[ j ][ last ] );
+ continue;
+ }
+ bv.bv_len += dv[ i ].dv_attrVals[ j ][ h ].bv_len;
+ nVals++;
+ h++;
+ }
+ nAttrs++;
+ }
+ }
+ }
+
+ overlay_entry_release_ov( op, e, 0, dc->dc_on );
+ }
+ }
+
+ *drp = dr;
+ drp = &dr->dr_next;
+ }
+ }
+ overlay_entry_release_ov( op, ebase, 0, dc->dc_on );
+
+ if ( drhead == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* cook the control value */
+ bv.bv_len += nVals * sizeof(struct berval)
+ + nAttrs * sizeof(struct berval)
+ + nDerefVals * sizeof(DerefVal)
+ + nDerefRes * sizeof(DerefRes);
+ bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx );
+
+ ber_init2( ber, &bv, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ rc = ber_printf( ber, "{" /*}*/ );
+ for ( dr = drhead; dr != NULL; dr = dr->dr_next ) {
+ for ( i = 0; !BER_BVISNULL( &dr->dr_vals[ i ].dv_derefSpecVal ); i++ ) {
+ int j, first = 1;
+
+ if ( dr->dr_vals[ i ].dv_derefSpecVal.bv_val == &dummy ) {
+ continue;
+ }
+
+ rc = ber_printf( ber, "{OO" /*}*/,
+ &dr->dr_spec.ds_derefAttr->ad_cname,
+ &dr->dr_vals[ i ].dv_derefSpecVal );
+ op->o_tmpfree( dr->dr_vals[ i ].dv_derefSpecVal.bv_val, op->o_tmpmemctx );
+ for ( j = 0; j < dr->dr_spec.ds_nattrs; j++ ) {
+ if ( dr->dr_vals[ i ].dv_attrVals[ j ] != NULL ) {
+ if ( first ) {
+ rc = ber_printf( ber, "t{" /*}*/,
+ (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) );
+ first = 0;
+ }
+ rc = ber_printf( ber, "{O[W]}",
+ &dr->dr_spec.ds_attributes[ j ]->ad_cname,
+ dr->dr_vals[ i ].dv_attrVals[ j ] );
+ ber_bvarray_free_x( dr->dr_vals[ i ].dv_attrVals[ j ],
+ op->o_tmpmemctx );
+ }
+ }
+ if ( !first ) {
+ rc = ber_printf( ber, /*{{*/ "}N}" );
+ } else {
+ rc = ber_printf( ber, /*{*/ "}" );
+ }
+ }
+ }
+ rc = ber_printf( ber, /*{*/ "}" );
+ if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) {
+ if ( op->o_deref == SLAP_CONTROL_CRITICAL ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ goto cleanup;
+ }
+
+ ctrl = op->o_tmpcalloc( 1,
+ sizeof( LDAPControl ) + ctrlval.bv_len + 1,
+ op->o_tmpmemctx );
+ ctrl->ldctl_value.bv_val = (char *)&ctrl[ 1 ];
+ ctrl->ldctl_oid = LDAP_CONTROL_X_DEREF;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_len = ctrlval.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len );
+ ctrl->ldctl_value.bv_val[ ctrl->ldctl_value.bv_len ] = '\0';
+
+ ber_free_buf( ber );
+
+ ctrlsp[0] = ctrl;
+ ctrlsp[1] = NULL;
+ slap_add_ctrls( op, rs, ctrlsp );
+
+ rc = SLAP_CB_CONTINUE;
+
+cleanup:;
+ /* release all */
+ for ( ; drhead != NULL; ) {
+ DerefRes *drnext = drhead->dr_next;
+ op->o_tmpfree( drhead, op->o_tmpmemctx );
+ drhead = drnext;
+ }
+
+ } else if ( rs->sr_type == REP_RESULT ) {
+ rc = deref_cleanup( op, rs );
+ }
+
+ return rc;
+}
+
+static int
+deref_op_search( Operation *op, SlapReply *rs )
+{
+ if ( op->o_deref ) {
+ slap_callback *sc;
+ deref_cb_t *dc;
+
+ sc = op->o_tmpcalloc( 1, sizeof( slap_callback ) + sizeof( deref_cb_t ), op->o_tmpmemctx );
+
+ dc = (deref_cb_t *)&sc[ 1 ];
+ dc->dc_on = (slap_overinst *)op->o_bd->bd_info;
+ dc->dc_ds = (DerefSpec *)op->o_ctrlderef;
+
+ sc->sc_response = deref_response;
+ sc->sc_cleanup = deref_cleanup;
+ sc->sc_private = (void *)dc;
+
+ sc->sc_next = op->o_callback->sc_next;
+ op->o_callback->sc_next = sc;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+deref_db_init( BackendDB *be, ConfigReply *cr)
+{
+ if ( ov_count == 0 ) {
+ int rc;
+
+ rc = register_supported_control2( LDAP_CONTROL_X_DEREF,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ deref_parseCtrl,
+ 1, /* replace */
+ &deref_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "deref_init: Failed to register control (%d)\n",
+ rc );
+ return rc;
+ }
+ }
+ ov_count++;
+ return LDAP_SUCCESS;
+}
+
+static int
+deref_db_open( BackendDB *be, ConfigReply *cr)
+{
+ return overlay_register_control( be, LDAP_CONTROL_X_DEREF );
+}
+
+#ifdef SLAP_CONFIG_DELETE
+static int
+deref_db_destroy( BackendDB *be, ConfigReply *cr)
+{
+ ov_count--;
+ overlay_unregister_control( be, LDAP_CONTROL_X_DEREF );
+ if ( ov_count == 0 ) {
+ unregister_supported_control( LDAP_CONTROL_X_DEREF );
+ }
+ return 0;
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+int
+deref_initialize(void)
+{
+ deref.on_bi.bi_type = "deref";
+ deref.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ deref.on_bi.bi_db_init = deref_db_init;
+ deref.on_bi.bi_db_open = deref_db_open;
+#ifdef SLAP_CONFIG_DELETE
+ deref.on_bi.bi_db_destroy = deref_db_destroy;
+#endif /* SLAP_CONFIG_DELETE */
+ deref.on_bi.bi_op_search = deref_op_search;
+
+ return overlay_register( &deref );
+}
+
+#if SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return deref_initialize();
+}
+#endif /* SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_DEREF */
diff --git a/servers/slapd/overlays/dyngroup.c b/servers/slapd/overlays/dyngroup.c
new file mode 100644
index 0000000..5d890d6
--- /dev/null
+++ b/servers/slapd/overlays/dyngroup.c
@@ -0,0 +1,234 @@
+/* dyngroup.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Copyright 2003 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DYNGROUP
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+/* This overlay extends the Compare operation to detect members of a
+ * dynamic group. It has no effect on any other operations. It must
+ * be configured with a pair of attributes to trigger on, e.g.
+ * attrpair member memberURL
+ * will cause compares on "member" to trigger a compare on "memberURL".
+ */
+
+typedef struct adpair {
+ struct adpair *ap_next;
+ AttributeDescription *ap_mem;
+ AttributeDescription *ap_uri;
+} adpair;
+
+static int dgroup_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ int rc = 1;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ {
+ adpair *ap;
+ for ( ap = on->on_bi.bi_private; ap; ap = ap->ap_next ) {
+ struct berval bv;
+ char *ptr;
+ bv.bv_len = ap->ap_mem->ad_cname.bv_len + 1 +
+ ap->ap_uri->ad_cname.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( bv.bv_val, ap->ap_mem->ad_cname.bv_val );
+ *ptr++ = ' ';
+ strcpy( ptr, ap->ap_uri->ad_cname.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx == -1 ) {
+ adpair *ap;
+ while (( ap = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ap->ap_next;
+ ch_free( ap );
+ }
+ } else {
+ adpair **app, *ap;
+ int i;
+ app = (adpair **)&on->on_bi.bi_private;
+ for (i=0; i<=c->valx; i++, app = &ap->ap_next) {
+ ap = *app;
+ }
+ *app = ap->ap_next;
+ ch_free( ap );
+ }
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ {
+ adpair ap = { NULL, NULL, NULL }, **app, *a2;
+ const char *text;
+ if ( slap_str2ad( c->argv[1], &ap.ap_mem, &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( slap_str2ad( c->argv[2], &ap.ap_uri, &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"",
+ c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ /* The on->on_bi.bi_private pointer can be used for
+ * anything this instance of the overlay needs.
+ */
+ a2 = ch_malloc( sizeof(adpair) );
+
+ for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next )
+ /* Get to the end */ ;
+
+ a2->ap_mem = ap.ap_mem;
+ a2->ap_uri = ap.ap_uri;
+ a2->ap_next = *app;
+ *app = a2;
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static ConfigTable dgroupcfg[] = {
+ { "attrpair", "member-attribute> <URL-attribute", 3, 3, 0,
+ ARG_MAGIC, dgroup_cf,
+ "( OLcfgOvAt:17.1 NAME ( 'olcDynGroupAttrPair' 'olcDGAttrPair' ) "
+ "EQUALITY caseIgnoreMatch "
+ "DESC 'Member and MemberURL attribute pair' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dgroupocs[] = {
+ { "( OLcfgOvOc:17.1 "
+ "NAME ( 'olcDynGroupConfig' 'olcDGConfig' ) "
+ "DESC 'Dynamic Group configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcDynGroupAttrPair)",
+ Cft_Overlay, dgroupcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+dyngroup_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ adpair *ap = on->on_bi.bi_private;
+
+ /* If we've been configured and the current response is
+ * what we're looking for...
+ */
+ if ( ap && op->o_tag == LDAP_REQ_COMPARE &&
+ rs->sr_err == LDAP_NO_SUCH_ATTRIBUTE ) {
+
+ for (;ap;ap=ap->ap_next) {
+ if ( op->oq_compare.rs_ava->aa_desc == ap->ap_mem ) {
+ /* This compare is for one of the attributes we're
+ * interested in. We'll use slapd's existing dyngroup
+ * evaluator to get the answer we want.
+ */
+ int cache = op->o_do_not_cache;
+
+ op->o_do_not_cache = 1;
+ rs->sr_err = backend_group( op, NULL, &op->o_req_ndn,
+ &op->oq_compare.rs_ava->aa_value, NULL, ap->ap_uri );
+ op->o_do_not_cache = cache;
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ break;
+ }
+ break;
+ }
+ }
+ }
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dyngroup_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ adpair *ap, *a2;
+
+ for ( ap = on->on_bi.bi_private; ap; ap = a2 ) {
+ a2 = ap->ap_next;
+ ch_free( ap );
+ }
+ return 0;
+}
+
+static slap_overinst dyngroup;
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+int dyngroup_initialize() {
+ int code;
+
+ dyngroup.on_bi.bi_type = "dyngroup";
+ dyngroup.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ dyngroup.on_bi.bi_db_destroy = dyngroup_destroy;
+ dyngroup.on_response = dyngroup_response;
+
+ dyngroup.on_bi.bi_cf_ocs = dgroupocs;
+ code = config_register_schema( dgroupcfg, dgroupocs );
+ if ( code ) return code;
+
+ return overlay_register( &dyngroup );
+}
+
+#if SLAPD_OVER_DYNGROUP == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return dyngroup_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_DYNGROUP) */
diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c
new file mode 100644
index 0000000..5c38b64
--- /dev/null
+++ b/servers/slapd/overlays/dynlist.c
@@ -0,0 +1,2968 @@
+/* dynlist.c - dynamic list overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004-2005 Pierangelo Masarati.
+ * Portions Copyright 2008 Emmanuel Dreyfus.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati
+ * for SysNet s.n.c., for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DYNLIST
+
+#if SLAPD_OVER_DYNGROUP != SLAPD_MOD_STATIC
+#define TAKEOVER_DYNGROUP
+#endif
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+
+static AttributeDescription *ad_dgIdentity, *ad_dgAuthz;
+static AttributeDescription *ad_memberOf;
+
+typedef struct dynlist_map_t {
+ AttributeDescription *dlm_member_ad;
+ AttributeDescription *dlm_mapped_ad;
+ AttributeDescription *dlm_memberOf_ad;
+ ObjectClass *dlm_static_oc;
+ int dlm_memberOf_nested;
+ int dlm_member_oper;
+ int dlm_memberOf_oper;
+ struct dynlist_map_t *dlm_next;
+} dynlist_map_t;
+
+typedef struct dynlist_info_t {
+ ObjectClass *dli_oc;
+ AttributeDescription *dli_ad;
+ struct dynlist_map_t *dli_dlm;
+ struct berval dli_uri;
+ LDAPURLDesc *dli_lud;
+ struct berval dli_uri_nbase;
+ Filter *dli_uri_filter;
+ struct berval dli_default_filter;
+ struct dynlist_info_t *dli_next;
+} dynlist_info_t;
+
+typedef struct dynlist_gen_t {
+ dynlist_info_t *dlg_dli;
+ int dlg_memberOf;
+ int dlg_simple;
+} dynlist_gen_t;
+
+#define DYNLIST_USAGE \
+ "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad>[+<memberOf-ad>[@<static-oc>[*]] ...]\": "
+
+static int
+ad_infilter( AttributeDescription *ad, Filter *f )
+{
+ if ( !f )
+ return 0;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case SLAPD_FILTER_COMPUTED:
+ return 0;
+ case LDAP_FILTER_PRESENT:
+ return f->f_desc == ad;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ return f->f_av_desc == ad;
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ for ( f = f->f_list; f; f = f->f_next )
+ if ( ad_infilter( ad, f ))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static Filter *
+transform_filter( Operation *op, dynlist_info_t *dli, int not, Filter *orig )
+{
+ Filter *f;
+ dynlist_map_t *dlm;
+
+ /* Tilt the filter towards TRUE if it could match through this dli */
+ int result = not ? LDAP_COMPARE_FALSE : LDAP_COMPARE_TRUE;
+
+ if ( orig ) {
+ f = orig;
+ } else {
+ f = orig = filter_dup( op->ors_filter, op->o_tmpmemctx );
+ }
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( f->f_av_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_PRESENT:
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( f->f_sub_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( f->f_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_EXT:
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( f->f_mr_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ for ( f = f->f_list; f; f = f->f_next )
+ transform_filter( op, dli, not, f );
+ break;
+ case LDAP_FILTER_NOT:
+ transform_filter( op, dli, !not, f->f_list );
+ case SLAPD_FILTER_COMPUTED:
+ break;
+ }
+
+ return orig;
+}
+
+typedef struct dynlist_filterinst_t {
+ AttributeAssertion *df_a;
+ Entry *df_e;
+} dynlist_filterinst_t;
+
+/* Record occurrences of ad in filter. Ignore in negated filters. */
+static void
+dynlist_filter_instances( Operation *op, AttributeDescription *ad, Filter *f, int not, int *dfn, dynlist_filterinst_t **dfp )
+{
+ if ( !f )
+ return;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ if ( !not && f->f_av_desc == ad ) {
+ dynlist_filterinst_t *df = *dfp;
+ int n = *dfn;
+ df = op->o_tmprealloc( df, (n + 1) * sizeof(dynlist_filterinst_t), op->o_tmpmemctx );
+ df[n].df_a = f->f_ava;
+ df[n++].df_e = NULL;
+ *dfp = df;
+ *dfn = n;
+ }
+ break;
+ case SLAPD_FILTER_COMPUTED:
+ case LDAP_FILTER_PRESENT:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ break;
+ case LDAP_FILTER_NOT: not ^= 1;
+ /* FALLTHRU */
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ for ( f = f->f_list; f; f = f->f_next )
+ dynlist_filter_instances( op, ad, f, not, dfn, dfp );
+ }
+}
+
+static int
+dynlist_make_filter( Operation *op, Entry *e, dynlist_info_t *dli, const char *url, struct berval *oldf, struct berval *newf )
+{
+ char *ptr;
+ int needBrackets = 0;
+
+ assert( oldf != NULL );
+ assert( newf != NULL );
+ assert( !BER_BVISNULL( oldf ) );
+ assert( !BER_BVISEMPTY( oldf ) );
+
+ if ( oldf->bv_val[0] != '(' ) {
+ Debug( LDAP_DEBUG_ANY, "%s: dynlist, DN=\"%s\": missing parentheses in URI=\"%s\" filter\n",
+ op->o_log_prefix, e->e_name.bv_val, url );
+ needBrackets = 2;
+ }
+
+ newf->bv_len = STRLENOF( "(&(!(objectClass=" "))" ")" )
+ + dli->dli_oc->soc_cname.bv_len + oldf->bv_len + needBrackets;
+ newf->bv_val = op->o_tmpalloc( newf->bv_len + 1, op->o_tmpmemctx );
+ if ( newf->bv_val == NULL ) {
+ return -1;
+ }
+ ptr = lutil_strcopy( newf->bv_val, "(&(!(objectClass=" );
+ ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "))" );
+ if ( needBrackets ) *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, oldf->bv_val );
+ if ( needBrackets ) *ptr++ = ')';
+ ptr = lutil_strcopy( ptr, ")" );
+ newf->bv_len = ptr - newf->bv_val;
+
+ return 0;
+}
+
+/* dynlist_sc_update() callback info set by dynlist_prepare_entry() */
+typedef struct dynlist_sc_t {
+ dynlist_info_t *dlc_dli;
+ Entry *dlc_e;
+ char **dlc_attrs;
+} dynlist_sc_t;
+
+static int
+dynlist_sc_update( Operation *op, SlapReply *rs )
+{
+ Entry *e;
+ Attribute *a;
+ int opattrs,
+ userattrs;
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ dynlist_sc_t *dlc;
+ dynlist_map_t *dlm;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return 0;
+ }
+
+ dlc = (dynlist_sc_t *)op->o_callback->sc_private;
+ e = dlc->dlc_e;
+
+ assert( e != NULL );
+ assert( rs->sr_entry != NULL );
+
+ /* test access to entry */
+ if ( !access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ goto done;
+ }
+
+ /* if there is only one member_ad, and it's not mapped,
+ * consider it as old-style member listing */
+ dlm = dlc->dlc_dli->dli_dlm;
+ if ( dlm && dlm->dlm_mapped_ad == NULL && dlm->dlm_next == NULL && dlc->dlc_attrs == NULL ) {
+ /* if access allowed, try to add values, emulating permissive
+ * control to silently ignore duplicates */
+ if ( access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ Modification mod;
+ const char *text = NULL;
+ char textbuf[1024];
+ struct berval vals[ 2 ], nvals[ 2 ];
+
+ vals[ 0 ] = rs->sr_entry->e_name;
+ BER_BVZERO( &vals[ 1 ] );
+ nvals[ 0 ] = rs->sr_entry->e_nname;
+ BER_BVZERO( &nvals[ 1 ] );
+
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = dlm->dlm_member_ad;
+ mod.sm_type = dlm->dlm_member_ad->ad_cname;
+ mod.sm_values = vals;
+ mod.sm_nvalues = nvals;
+ mod.sm_numvals = 1;
+
+ (void)modify_add_values( e, &mod, /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+ }
+
+ goto done;
+ }
+
+ opattrs = SLAP_OPATTRS( rs->sr_attr_flags );
+ userattrs = SLAP_USERATTRS( rs->sr_attr_flags );
+
+ for ( a = rs->sr_entry->e_attrs; a != NULL; a = a->a_next ) {
+ BerVarray vals, nvals = NULL;
+ int i, j,
+ is_oc = a->a_desc == slap_schema.si_ad_objectClass;
+
+ /* if attribute is not requested, skip it */
+ if ( rs->sr_attrs == NULL ) {
+ if ( is_at_operational( a->a_desc->ad_type ) ) {
+ continue;
+ }
+
+ } else {
+ if ( is_at_operational( a->a_desc->ad_type ) ) {
+ if ( !opattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) )
+ {
+ continue;
+ }
+
+ } else {
+ if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) )
+ {
+ continue;
+ }
+ }
+ }
+
+ /* test access to attribute */
+ if ( op->ors_attrsonly ) {
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc, NULL,
+ ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+ }
+
+ /* single-value check: keep first only */
+ if ( is_at_single_value( a->a_desc->ad_type ) ) {
+ if ( attr_find( e->e_attrs, a->a_desc ) != NULL ) {
+ continue;
+ }
+ }
+
+ /* test access to attribute */
+ i = a->a_numvals;
+
+ vals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ if ( a->a_nvals != a->a_vals ) {
+ nvals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ }
+
+ for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) {
+ if ( is_oc ) {
+ ObjectClass *soc = oc_bvfind( &a->a_vals[i] );
+
+ if ( soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
+ continue;
+ }
+ }
+
+ if ( access_allowed( op, rs->sr_entry, a->a_desc,
+ &a->a_nvals[i], ACL_READ, &acl_state ) )
+ {
+ vals[j] = a->a_vals[i];
+ if ( nvals ) {
+ nvals[j] = a->a_nvals[i];
+ }
+ j++;
+ }
+ }
+
+ /* if access allowed, try to add values, emulating permissive
+ * control to silently ignore duplicates */
+ if ( j != 0 ) {
+ Modification mod;
+ const char *text = NULL;
+ char textbuf[1024];
+ dynlist_map_t *dlm;
+ AttributeDescription *ad;
+
+ BER_BVZERO( &vals[j] );
+ if ( nvals ) {
+ BER_BVZERO( &nvals[j] );
+ }
+
+ ad = a->a_desc;
+ for ( dlm = dlc->dlc_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == a->a_desc ) {
+ if ( dlm->dlm_mapped_ad ) {
+ ad = dlm->dlm_mapped_ad;
+ }
+ break;
+ }
+ }
+
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = ad;
+ mod.sm_type = ad->ad_cname;
+ mod.sm_values = vals;
+ mod.sm_nvalues = nvals;
+ mod.sm_numvals = j;
+
+ (void)modify_add_values( e, &mod, /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+ }
+
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ if ( nvals ) {
+ op->o_tmpfree( nvals, op->o_tmpmemctx );
+ }
+ }
+
+done:;
+ if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) {
+ entry_free( rs->sr_entry );
+ rs->sr_entry = NULL;
+ rs->sr_flags &= ~REP_ENTRY_MASK;
+ }
+
+ return 0;
+}
+
+typedef struct dynlist_name_t {
+ struct berval dy_nname;
+ struct berval dy_name;
+ dynlist_info_t *dy_dli;
+ dynlist_map_t *dy_dlm;
+ AttributeDescription *dy_staticmember;
+ int dy_seen;
+ int dy_numuris;
+ TAvlnode *dy_subs;
+ TAvlnode *dy_sups;
+ LDAPURLDesc *dy_uris[];
+} dynlist_name_t;
+
+static void
+dynlist_urlmembers( Operation *op, dynlist_name_t *dyn, slap_callback *sc )
+{
+ Operation o = *op;
+ LDAPURLDesc *ludp;
+ int i;
+
+ o.ors_deref = LDAP_DEREF_NEVER;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+ o.ors_attrs = NULL;
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+ o.o_callback = sc;
+ o.o_do_not_cache = 1;
+
+ for (i=0; i<dyn->dy_numuris; i++) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_attrs )
+ continue;
+ o.o_req_dn.bv_val = ludp->lud_dn;
+ o.o_req_dn.bv_len = ludp->lud_port;
+ o.o_req_ndn = o.o_req_dn;
+ o.ors_scope = ludp->lud_scope;
+ o.ors_filter = (Filter *)ludp->lud_filter;
+ filter2bv_x( op, o.ors_filter, &o.ors_filterstr );
+ o.o_bd = select_backend( &o.o_req_ndn, 1 );
+ if ( o.o_bd && o.o_bd->be_search ) {
+ SlapReply r = { REP_SEARCH };
+ r.sr_attr_flags = slap_attr_flags( o.ors_attrs );
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ (void)o.o_bd->be_search( &o, &r );
+ }
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+}
+
+static void
+dynlist_nested_memberOf( Entry *e, AttributeDescription *ad, TAvlnode *sups )
+{
+ TAvlnode *ptr;
+ dynlist_name_t *dyn;
+ Attribute *a;
+
+ a = attr_find( e->e_attrs, ad );
+ for ( ptr = ldap_tavl_end( sups, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ if ( a ) {
+ unsigned slot;
+ if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &dyn->dy_nname, &slot, NULL ) == LDAP_SUCCESS )
+ continue;
+ }
+ attr_merge_one( e, ad, &dyn->dy_name, &dyn->dy_nname );
+ if ( !a )
+ a = attr_find( e->e_attrs, ad );
+ if ( dyn->dy_sups )
+ dynlist_nested_memberOf( e, ad, dyn->dy_sups );
+ }
+}
+
+typedef struct dynlist_member_t {
+ Entry *dm_e;
+ AttributeDescription *dm_ad;
+ Modification dm_mod;
+ TAvlnode *dm_groups;
+ struct berval dm_bv[2];
+ struct berval dm_nbv[2];
+ const char *dm_text;
+ char dm_textbuf[1024];
+} dynlist_member_t;
+
+static int
+dynlist_ptr_cmp( const void *c1, const void *c2 )
+{
+ return ( c1 < c2 ) ? -1 : c1 > c2;
+}
+
+static int
+dynlist_nested_member_dg( Operation *op, SlapReply *rs )
+{
+ dynlist_member_t *dm = op->o_callback->sc_private;
+
+ if ( rs->sr_type != REP_SEARCH )
+ return LDAP_SUCCESS;
+
+ dm->dm_bv[0] = rs->sr_entry->e_name;
+ dm->dm_nbv[0] = rs->sr_entry->e_nname;
+ modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1,
+ &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf ));
+
+ return LDAP_SUCCESS;
+}
+
+static void
+dynlist_nested_member( Operation *op, slap_overinst *on, dynlist_member_t *dm, TAvlnode *subs )
+{
+ TAvlnode *ptr;
+ dynlist_name_t *dyn;
+ Entry *ne;
+ Attribute *a, *b;
+
+ a = attr_find( dm->dm_e->e_attrs, dm->dm_ad );
+ if ( !a )
+ return;
+
+ for ( ptr = ldap_tavl_end( subs, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ if ( ldap_tavl_insert( &dm->dm_groups, dyn, dynlist_ptr_cmp, ldap_avl_dup_error ))
+ continue;
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &ne, on ) != LDAP_SUCCESS || ne == NULL )
+ continue;
+ b = attr_find( ne->e_attrs, dm->dm_ad );
+ if ( b ) {
+ dm->dm_mod.sm_values = b->a_vals;
+ dm->dm_mod.sm_nvalues = b->a_nvals;
+ dm->dm_mod.sm_numvals = b->a_numvals;
+ modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1,
+ &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf ));
+ }
+ overlay_entry_release_ov( op, ne, 0, on );
+ if ( dyn->dy_numuris ) {
+ slap_callback cb = { 0 };
+ cb.sc_private = dm;
+ BER_BVZERO( &dm->dm_bv[1] );
+ BER_BVZERO( &dm->dm_nbv[1] );
+ dm->dm_mod.sm_values = dm->dm_bv;
+ dm->dm_mod.sm_nvalues = dm->dm_nbv;
+ dm->dm_mod.sm_numvals = 1;
+ cb.sc_response = dynlist_nested_member_dg;
+ dynlist_urlmembers( op, dyn, &cb );
+ }
+ if ( dyn->dy_subs )
+ dynlist_nested_member( op, on, dm, dyn->dy_subs );
+ }
+}
+
+static int
+dynlist_prepare_entry( Operation *op, SlapReply *rs, slap_overinst *on, dynlist_info_t *dli, dynlist_name_t *dyn )
+{
+ Attribute *a, *id = NULL;
+ slap_callback cb = { 0 };
+ Operation o = *op;
+ struct berval *url;
+ Entry *e;
+ int opattrs,
+ userattrs;
+ dynlist_sc_t dlc = { 0 };
+ dynlist_map_t *dlm;
+
+ e = rs->sr_entry;
+ a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad );
+ if ( a == NULL ) {
+ /* FIXME: error? */
+ goto checkdyn;
+ }
+
+ opattrs = SLAP_OPATTRS( rs->sr_attr_flags );
+ userattrs = SLAP_USERATTRS( rs->sr_attr_flags );
+
+ /* Don't generate member list if it wasn't requested */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( userattrs || ad_inlist( ad, rs->sr_attrs )
+ || ad_infilter( ad, op->ors_filter ))
+ break;
+ }
+
+ /* If nothing matched and this was a search, skip over to nesting check.
+ * If this was a compare, keep on going.
+ */
+ if ( dli->dli_dlm && !dlm && o.o_acl_priv != ACL_COMPARE )
+ goto checkdyn;
+
+ if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) {
+ Attribute *authz = NULL;
+
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op )
+ && ( authz = attrs_find( rs->sr_entry->e_attrs, ad_dgAuthz ) ) )
+ {
+ if ( slap_sasl_matches( op, authz->a_nvals,
+ &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS )
+ {
+ goto checkdyn;
+ }
+ }
+
+ o.o_dn = id->a_vals[0];
+ o.o_ndn = id->a_nvals[0];
+ o.o_groups = NULL;
+ }
+
+ /* ensure e is modifiable, but do not replace
+ * sr_entry yet since we have pointers into it */
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ e = entry_dup( rs->sr_entry );
+ }
+
+ dlc.dlc_e = e;
+ dlc.dlc_dli = dli;
+ cb.sc_private = &dlc;
+ cb.sc_response = dynlist_sc_update;
+
+ o.o_callback = &cb;
+ o.ors_deref = LDAP_DEREF_NEVER;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+ o.o_do_not_cache = 1;
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+
+ for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) {
+ LDAPURLDesc *lud = NULL;
+ int i, j;
+ struct berval dn;
+ int rc;
+
+ BER_BVZERO( &o.o_req_dn );
+ BER_BVZERO( &o.o_req_ndn );
+ o.ors_filter = NULL;
+ o.ors_attrs = NULL;
+ BER_BVZERO( &o.ors_filterstr );
+
+ if ( ldap_url_parse( url->bv_val, &lud ) != LDAP_URL_SUCCESS ) {
+ /* FIXME: error? */
+ continue;
+ }
+
+ if ( lud->lud_host != NULL ) {
+ /* FIXME: host not allowed; reject as illegal? */
+ Debug( LDAP_DEBUG_ANY, "dynlist_prepare_entry(\"%s\"): "
+ "illegal URI \"%s\"\n",
+ e->e_name.bv_val, url->bv_val );
+ goto cleanup;
+ }
+
+ if ( lud->lud_dn == NULL ) {
+ /* note that an empty base is not honored in terms
+ * of defaultSearchBase, because select_backend()
+ * is not aware of the defaultSearchBase option;
+ * this can be useful in case of a database serving
+ * the empty suffix */
+ BER_BVSTR( &dn, "" );
+
+ } else {
+ ber_str2bv( lud->lud_dn, 0, 0, &dn );
+ }
+ rc = dnPrettyNormal( NULL, &dn, &o.o_req_dn, &o.o_req_ndn, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ /* FIXME: error? */
+ goto cleanup;
+ }
+ o.ors_scope = lud->lud_scope;
+
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_mapped_ad != NULL ) {
+ break;
+ }
+ }
+
+ if ( dli->dli_dlm && !dlm ) {
+ /* if ( lud->lud_attrs != NULL ),
+ * the URL should be ignored */
+ o.ors_attrs = slap_anlist_no_attrs;
+
+ } else if ( lud->lud_attrs == NULL ) {
+ o.ors_attrs = rs->sr_attrs;
+
+ } else {
+ for ( i = 0; lud->lud_attrs[i]; i++)
+ /* just count */ ;
+
+ o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx );
+ for ( i = 0, j = 0; lud->lud_attrs[i]; i++) {
+ const char *text = NULL;
+
+ ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name );
+ o.ors_attrs[j].an_desc = NULL;
+ (void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text );
+ /* FIXME: ignore errors... */
+
+ if ( ad_infilter( o.ors_attrs[j].an_desc, op->ors_filter )) {
+ /* if referenced in filter, must retrieve */
+ } else if ( rs->sr_attrs == NULL ) {
+ if ( o.ors_attrs[j].an_desc != NULL &&
+ is_at_operational( o.ors_attrs[j].an_desc->ad_type ) )
+ {
+ continue;
+ }
+
+ } else {
+ if ( o.ors_attrs[j].an_desc != NULL &&
+ is_at_operational( o.ors_attrs[j].an_desc->ad_type ) )
+ {
+ if ( !opattrs ) {
+ continue;
+ }
+
+ if ( !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) {
+ /* lookup if mapped -- linear search,
+ * not very efficient unless list
+ * is very short */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) {
+ break;
+ }
+ }
+
+ if ( dlm == NULL ) {
+ continue;
+ }
+ }
+
+ } else {
+ if ( !userattrs &&
+ o.ors_attrs[j].an_desc != NULL &&
+ !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) )
+ {
+ /* lookup if mapped -- linear search,
+ * not very efficient unless list
+ * is very short */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) {
+ break;
+ }
+ }
+
+ if ( dlm == NULL ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ j++;
+ }
+
+ if ( j == 0 ) {
+ goto cleanup;
+ }
+
+ BER_BVZERO( &o.ors_attrs[j].an_name );
+ }
+ dlc.dlc_attrs = lud->lud_attrs;
+
+ if ( lud->lud_filter == NULL ) {
+ ber_dupbv_x( &o.ors_filterstr,
+ &dli->dli_default_filter, op->o_tmpmemctx );
+
+ } else {
+ /* don't allow recursion in lists */
+ if ( lud->lud_attrs ) {
+ struct berval flt;
+ ber_str2bv( lud->lud_filter, 0, 0, &flt );
+ if ( dynlist_make_filter( op, rs->sr_entry, dli, url->bv_val, &flt, &o.ors_filterstr ) ) {
+ /* error */
+ goto cleanup;
+ }
+ } else {
+ ber_str2bv( lud->lud_filter, 0, 0, &o.ors_filterstr );
+ }
+ }
+ o.ors_filter = str2filter_x( op, o.ors_filterstr.bv_val );
+ if ( o.ors_filter == NULL ) {
+ goto cleanup;
+ }
+
+ o.o_bd = select_backend( &o.o_req_ndn, 1 );
+ if ( o.o_bd && o.o_bd->be_search ) {
+ SlapReply r = { REP_SEARCH };
+ r.sr_attr_flags = slap_attr_flags( o.ors_attrs );
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ (void)o.o_bd->be_search( &o, &r );
+ }
+
+cleanup:;
+ if ( id ) {
+ slap_op_groups_free( &o );
+ }
+ if ( o.ors_filter ) {
+ filter_free_x( &o, o.ors_filter, 1 );
+ }
+ if ( o.ors_attrs && o.ors_attrs != rs->sr_attrs
+ && o.ors_attrs != slap_anlist_no_attrs )
+ {
+ op->o_tmpfree( o.ors_attrs, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &o.o_req_dn ) ) {
+ op->o_tmpfree( o.o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &o.o_req_ndn ) ) {
+ op->o_tmpfree( o.o_req_ndn.bv_val, op->o_tmpmemctx );
+ }
+ if ( lud->lud_attrs ) {
+ assert( BER_BVISNULL( &o.ors_filterstr )
+ || o.ors_filterstr.bv_val != lud->lud_filter );
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ } else {
+ if ( o.ors_filterstr.bv_val != lud->lud_filter )
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ ldap_free_urldesc( lud );
+ }
+
+checkdyn:
+ /* handle nested groups */
+ if ( dyn && ( dyn->dy_sups || dyn->dy_subs )) {
+ /* ensure e is modifiable */
+ if ( e == rs->sr_entry && !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ e = entry_dup( rs->sr_entry );
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+ if ( dyn->dy_subs ) {
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad ) {
+ dynlist_member_t dm;
+ dm.dm_groups = NULL;
+ dm.dm_mod.sm_op = LDAP_MOD_ADD;
+ dm.dm_mod.sm_desc = dlm->dlm_member_ad;
+ dm.dm_mod.sm_type = dlm->dlm_member_ad->ad_cname;
+ dm.dm_e = e;
+ dm.dm_ad = dlm->dlm_member_ad;
+ dynlist_nested_member( op, on, &dm, dyn->dy_subs );
+ if ( dm.dm_groups )
+ ldap_tavl_free( dm.dm_groups, NULL );
+ }
+ }
+ }
+ if ( dyn->dy_sups ) {
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups );
+ }
+ }
+ }
+ }
+
+ if ( e != rs->sr_entry ) {
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dynlist_check_scope( Operation *op, Entry *e, dynlist_info_t *dli )
+{
+ if ( dli->dli_lud ) {
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) &&
+ !dnIsSuffixScope( &e->e_nname,
+ &dli->dli_uri_nbase,
+ dli->dli_lud->lud_scope ))
+ return 0;
+ if ( dli->dli_uri_filter && test_filter( op, e,
+ dli->dli_uri_filter ) != LDAP_COMPARE_TRUE )
+ return 0;
+ }
+ return 1;
+}
+
+static int
+dynlist_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+ Operation o = *op;
+ Entry *e = NULL;
+ dynlist_map_t *dlm;
+ BackendDB *be;
+ int ret = SLAP_CB_CONTINUE;
+
+ if ( get_manageDSAit( op ) )
+ return SLAP_CB_CONTINUE;
+
+ for ( ; dli != NULL; dli = dli->dli_next ) {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ /* builtin dyngroup evaluator only works for DNs */
+ if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName )
+ continue;
+ if ( op->oq_compare.rs_ava->aa_desc == ad )
+ break;
+ }
+
+ if ( dlm ) {
+ /* This compare is for one of the attributes we're
+ * interested in. We'll use slapd's existing dyngroup
+ * evaluator to get the answer we want.
+ */
+ BerVarray id = NULL, authz = NULL;
+
+ if ( e == NULL && ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL ))
+ {
+ return SLAP_CB_CONTINUE;
+ }
+ if ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) ||
+ !dynlist_check_scope( op, e, dli )) {
+ continue;
+ }
+
+ o.o_do_not_cache = 1;
+
+ if ( ad_dgIdentity && backend_attribute( &o, e, &o.o_req_ndn,
+ ad_dgIdentity, &id, ACL_READ ) == LDAP_SUCCESS )
+ {
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( id ) && !be_isroot( op )
+ && backend_attribute( &o, e, &o.o_req_ndn,
+ ad_dgAuthz, &authz, ACL_READ ) == LDAP_SUCCESS )
+ {
+
+ rs->sr_err = slap_sasl_matches( op, authz,
+ &o.o_ndn, &o.o_ndn );
+ ber_bvarray_free_x( authz, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+ o.o_dn = *id;
+ o.o_ndn = *id;
+ o.o_groups = NULL; /* authz changed, invalidate cached groups */
+ }
+
+ rs->sr_err = backend_group( &o, e, &o.o_req_ndn,
+ &o.oq_compare.rs_ava->aa_value, dli->dli_oc, dli->dli_ad );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* NOTE: backend_group() returns noSuchObject
+ * if op_ndn does not exist; however, since
+ * dynamic list expansion means that the
+ * member attribute is virtually present, the
+ * non-existence of the asserted value implies
+ * the assertion is FALSE rather than
+ * UNDEFINED */
+ rs->sr_err = LDAP_COMPARE_FALSE;
+
+ /* If also using static groups, fallback to
+ * vanilla compare
+ */
+ if ( dlm->dlm_static_oc )
+ return SLAP_CB_CONTINUE;
+
+ break;
+ }
+
+done:;
+ if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx );
+ overlay_entry_release_ov( &o, e, 0, on );
+
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+
+ be = select_backend( &o.o_req_ndn, 1 );
+ if ( !be || !be->be_search ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( e == NULL && ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL ))
+ {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* check for dynlist objectClass; done if not found */
+ dli = (dynlist_info_t *)dlg->dlg_dli;
+ while ( dli != NULL && ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) ||
+ !dynlist_check_scope( op, e, dli ))) {
+ dli = dli->dli_next;
+ }
+ if ( dli == NULL ) {
+ goto release;
+ }
+
+ if ( ad_dgIdentity ) {
+ Attribute *id = attrs_find( e->e_attrs, ad_dgIdentity );
+ if ( id ) {
+ Attribute *authz;
+
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op )
+ && ( authz = attrs_find( e->e_attrs, ad_dgAuthz ) ) )
+ {
+ if ( slap_sasl_matches( op, authz->a_nvals,
+ &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS )
+ {
+ goto release;
+ }
+ }
+
+ o.o_dn = id->a_vals[0];
+ o.o_ndn = id->a_nvals[0];
+ o.o_groups = NULL;
+ }
+ }
+
+ /* generate dynamic list with dynlist_response() and compare */
+ {
+ SlapReply r = { REP_SEARCH };
+ Attribute *a;
+ AttributeName an[2];
+
+ o.o_tag = LDAP_REQ_SEARCH;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+
+ o.ors_filterstr = *slap_filterstr_objectClass_pres;
+ o.ors_filter = (Filter *) slap_filter_objectClass_pres;
+
+ o.ors_scope = LDAP_SCOPE_BASE;
+ o.ors_deref = LDAP_DEREF_NEVER;
+ an[0].an_name = op->orc_ava->aa_desc->ad_cname;
+ an[0].an_desc = op->orc_ava->aa_desc;
+ BER_BVZERO( &an[1].an_name );
+ o.ors_attrs = an;
+ o.ors_attrsonly = 0;
+ r.sr_entry = e;
+ r.sr_attrs = an;
+
+ o.o_acl_priv = ACL_COMPARE;
+ dynlist_prepare_entry( &o, &r, on, dli, NULL );
+ a = attrs_find( r.sr_entry->e_attrs, op->orc_ava->aa_desc );
+
+ ret = LDAP_NO_SUCH_ATTRIBUTE;
+ for ( ; a ; a = attrs_find( a->a_next, op->orc_ava->aa_desc )) {
+ ret = LDAP_COMPARE_FALSE;
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->orc_ava->aa_value, NULL, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+ ret = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+ rs->sr_err = ret;
+
+ if ( r.sr_entry != e )
+ entry_free( r.sr_entry );
+ send_ldap_result( op, rs );
+ }
+
+release:;
+ if ( e != NULL ) {
+ overlay_entry_release_ov( &o, e, 0, on );
+ }
+
+ return ret;
+}
+
+#define WANT_MEMBEROF 1
+#define WANT_MEMBER 2
+
+typedef struct dynlist_search_t {
+ slap_overinst *ds_on;
+ TAvlnode *ds_names;
+ TAvlnode *ds_fnodes;
+ dynlist_info_t *ds_dli;
+ dynlist_map_t *ds_dlm;
+ Filter *ds_origfilter;
+ struct berval ds_origfilterbv;
+ int ds_want;
+ int ds_found;
+} dynlist_search_t;
+
+static int
+dynlist_avl_cmp( const void *c1, const void *c2 )
+{
+ const dynlist_name_t *n1, *n2;
+ int rc;
+ n1 = c1; n2 = c2;
+
+ rc = n1->dy_nname.bv_len - n2->dy_nname.bv_len;
+ if ( rc ) return rc;
+ return ber_bvcmp( &n1->dy_nname, &n2->dy_nname );
+}
+
+/* build a list of dynamic entries */
+static int
+dynlist_search1resp( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ Attribute *a, *b = NULL;
+
+ if ( ds->ds_dlm && ds->ds_dlm->dlm_static_oc && is_entry_objectclass( rs->sr_entry, ds->ds_dlm->dlm_static_oc, 0 ))
+ b = attr_find( rs->sr_entry->e_attrs, ds->ds_dlm->dlm_member_ad );
+ a = attr_find( rs->sr_entry->e_attrs, ds->ds_dli->dli_ad );
+
+ /* enforce scope of dynamic entries */
+ if ( a && !dynlist_check_scope( op, rs->sr_entry, ds->ds_dli ))
+ a = NULL;
+
+ if ( a || b ) {
+ unsigned len;
+ dynlist_name_t *dyn;
+ struct berval bv, nbase;
+ LDAPURLDesc *ludp;
+ int i, j = 0;
+
+ if ( a )
+ len = a->a_numvals * sizeof(LDAPURLDesc *);
+ else
+ len = 0;
+
+ dyn = ch_calloc(1, sizeof(dynlist_name_t)+rs->sr_entry->e_nname.bv_len + 1 +
+ rs->sr_entry->e_name.bv_len + 1 + len);
+ dyn->dy_name.bv_val = ((char *)(dyn+1)) + len;
+ dyn->dy_name.bv_len = rs->sr_entry->e_name.bv_len;
+ dyn->dy_nname.bv_val = dyn->dy_name.bv_val + dyn->dy_name.bv_len + 1;
+ dyn->dy_nname.bv_len = rs->sr_entry->e_nname.bv_len;
+ dyn->dy_dli = ds->ds_dli;
+ dyn->dy_dlm = ds->ds_dlm;
+ if ( a ) {
+ Filter *f;
+ /* parse and validate the URIs */
+ for (i=0; i<a->a_numvals; i++) {
+ if (ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS )
+ continue;
+ if (( ludp->lud_host && *ludp->lud_host)
+ || ludp->lud_exts ) {
+ skipit:
+ ldap_free_urldesc( ludp );
+ continue;
+ }
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ goto skipit;
+ ldap_memfree( ludp->lud_dn );
+ ludp->lud_dn = ldap_strdup( nbase.bv_val );
+ op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx );
+ /* cheat here, reuse fields */
+ ludp->lud_port = nbase.bv_len;
+ if ( ludp->lud_filter && *ludp->lud_filter ) {
+ f = str2filter( ludp->lud_filter );
+ if ( f == NULL )
+ goto skipit;
+ ldap_memfree( ludp->lud_filter );
+ } else {
+ f = ch_malloc( sizeof( Filter ));
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = LDAP_COMPARE_TRUE;
+ f->f_next = NULL;
+ }
+ ludp->lud_filter = (char *)f;
+ dyn->dy_uris[j] = ludp;
+ j++;
+ }
+ }
+ dyn->dy_numuris = j;
+ memcpy(dyn->dy_name.bv_val, rs->sr_entry->e_name.bv_val, rs->sr_entry->e_name.bv_len );
+ memcpy(dyn->dy_nname.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len );
+ if ( b )
+ dyn->dy_staticmember = ds->ds_dlm->dlm_member_ad;
+
+ if ( ldap_tavl_insert( &ds->ds_names, dyn, dynlist_avl_cmp, ldap_avl_dup_error )) {
+ for (i=dyn->dy_numuris-1; i>=0; i--) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_filter ) {
+ filter_free( (Filter *)ludp->lud_filter );
+ ludp->lud_filter = NULL;
+ }
+ ldap_free_urldesc( ludp );
+ }
+ ch_free( dyn );
+ } else {
+ ds->ds_found++;
+ }
+ }
+ }
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion
+ * of its dynamic members
+ * using (&(entryDN=<groupURIbase>)<groupURIfilter>)
+ */
+static int
+dynlist_filter_dyngroup( Operation *op, Filter *n, Attribute *a )
+{
+ Filter *andf = NULL, *dnf, *urif, *orf = NULL;
+ LDAPURLDesc *ludp;
+ struct berval bv, nbase;
+ int i;
+
+ for (i=0; i<a->a_numvals; i++) {
+ if ( ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS )
+ continue;
+ if (( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs
+ || ludp->lud_exts ) {
+ skip:
+ ldap_free_urldesc( ludp );
+ continue;
+ }
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ goto skip;
+ if ( ludp->lud_filter && *ludp->lud_filter ) {
+ urif = str2filter_x( op, ludp->lud_filter );
+ if ( urif == NULL ) {
+ op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx );
+ goto skip;
+ }
+ } else {
+ urif = NULL;
+ }
+ if ( !andf && n->f_choice == SLAPD_FILTER_COMPUTED ) {
+ andf = n;
+ andf->f_next = NULL;
+ } else {
+ orf = n;
+ if ( n->f_choice != LDAP_FILTER_OR ) {
+ andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ *andf = *n;
+ orf->f_choice = LDAP_FILTER_OR;
+ orf->f_next = NULL;
+ orf->f_list = andf;
+ }
+ andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ andf->f_next = orf->f_list;
+ orf->f_list = andf;
+ }
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ andf->f_choice = LDAP_FILTER_AND;
+ andf->f_list = dnf;
+ dnf->f_next = urif;
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ dnf->f_choice = LDAP_FILTER_EQUALITY;
+ dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ dnf->f_av_desc = slap_schema.si_ad_entryDN;
+ dnf->f_av_value = nbase;
+ } else {
+ dnf->f_choice = LDAP_FILTER_EXT;
+ dnf->f_mra = op->o_tmpcalloc( 1, sizeof(MatchingRuleAssertion), op->o_tmpmemctx );
+ dnf->f_mr_desc = slap_schema.si_ad_entryDN;
+ dnf->f_mr_value = nbase;
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_ONELEVEL:
+ dnf->f_mr_rule = slap_schema.si_mr_dnOneLevelMatch;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ dnf->f_mr_rule = slap_schema.si_mr_dnSubtreeMatch;
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ dnf->f_mr_rule = slap_schema.si_mr_dnSubordinateMatch;
+ break;
+ }
+ ber_str2bv( dnf->f_mr_rule->smr_names[0], 0, 0, &dnf->f_mr_rule_text );
+ }
+ ldap_free_urldesc( ludp );
+ }
+ if ( !andf )
+ return -1;
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion
+ * of its static members
+ * using (|(entryDN=<memberN>)[...])
+ */
+static int
+dynlist_filter_stgroup( Operation *op, Filter *n, Attribute *a )
+{
+ Filter *dnf, *orf = NULL;
+ int i;
+
+ if ( a->a_numvals == 1 && n->f_choice == SLAPD_FILTER_COMPUTED ) {
+ dnf = n;
+ } else {
+ orf = n;
+ if ( n->f_choice != LDAP_FILTER_OR ) {
+ orf->f_choice = LDAP_FILTER_OR;
+ orf->f_list = NULL;
+ }
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ dnf->f_next = orf->f_list;
+ orf->f_list = dnf;
+ }
+
+ for (i=0; i<a->a_numvals; i++) {
+ if ( i ) {
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ dnf->f_next = orf->f_list;
+ orf->f_list = dnf;
+ }
+ dnf->f_choice = LDAP_FILTER_EQUALITY;
+ dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ dnf->f_av_desc = slap_schema.si_ad_entryDN;
+ ber_dupbv_x( &dnf->f_av_value, &a->a_nvals[i], op->o_tmpmemctx );
+ }
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion of
+ * its members.
+ */
+static int
+dynlist_filter_group( Operation *op, dynlist_name_t *dyn, Filter *n, dynlist_search_t *ds )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ Entry *e;
+ Attribute *a;
+ int rc = -1;
+
+ if ( ldap_tavl_insert( &ds->ds_fnodes, dyn, dynlist_ptr_cmp, ldap_avl_dup_error ))
+ return 0;
+
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL ) {
+ return -1;
+ }
+ if ( ds->ds_dlm->dlm_static_oc && is_entry_objectclass( e, ds->ds_dlm->dlm_static_oc, 0 )) {
+ a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad );
+ if ( a ) {
+ rc = dynlist_filter_stgroup( op, n, a );
+ }
+ } else {
+ a = attr_find( e->e_attrs, ds->ds_dli->dli_ad );
+ if ( a ) {
+ rc = dynlist_filter_dyngroup( op, n, a );
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ if ( dyn->dy_subs && !rc ) {
+ TAvlnode *ptr;
+ for ( ptr = ldap_tavl_end( dyn->dy_subs, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ rc = dynlist_filter_group( op, dyn, n, ds );
+ if ( rc )
+ break;
+ }
+ }
+ return rc;
+}
+
+/* Dup the filter, replacing any references to given ad with group evaluation */
+static Filter *
+dynlist_filter_dup( Operation *op, Filter *f, AttributeDescription *ad, dynlist_search_t *ds )
+{
+ Filter *n;
+
+ if ( !f )
+ return NULL;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_next = NULL;
+ if ( f->f_av_desc == ad ) {
+ dynlist_name_t *dyn = ldap_tavl_find( ds->ds_names, &f->f_av_value, dynlist_avl_cmp );
+ n->f_choice = SLAPD_FILTER_COMPUTED;
+ if ( dyn && !dynlist_filter_group( op, dyn, n, ds ))
+ break;
+ }
+ n->f_choice = LDAP_FILTER_EQUALITY;
+ n->f_ava = ava_dup( f->f_ava, op->o_tmpmemctx );
+ break;
+ case SLAPD_FILTER_COMPUTED:
+ case LDAP_FILTER_PRESENT:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ n = filter_dup( f, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_NOT:
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR: {
+ Filter **p;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_next = NULL;
+ n->f_choice = f->f_choice;
+
+ for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) {
+ *p = dynlist_filter_dup( op, f, ad, ds );
+ if ( !*p )
+ continue;
+ p = &(*p)->f_next;
+ }
+ }
+ break;
+ }
+ return n;
+}
+
+static void
+dynlist_search_free( void *ptr )
+{
+ dynlist_name_t *dyn = (dynlist_name_t *)ptr;
+ LDAPURLDesc *ludp;
+ int i;
+
+ for (i=dyn->dy_numuris-1; i>=0; i--) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_filter ) {
+ filter_free( (Filter *)ludp->lud_filter );
+ ludp->lud_filter = NULL;
+ }
+ ldap_free_urldesc( ludp );
+ }
+ if ( dyn->dy_subs )
+ ldap_tavl_free( dyn->dy_subs, NULL );
+ if ( dyn->dy_sups )
+ ldap_tavl_free( dyn->dy_sups, NULL );
+ ch_free( ptr );
+}
+
+static int
+dynlist_search_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || op->o_abandon ||
+ rs->sr_err == SLAPD_ABANDON ) {
+ slap_callback *sc = op->o_callback;
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ ldap_tavl_free( ds->ds_names, dynlist_search_free );
+ if ( ds->ds_fnodes )
+ ldap_tavl_free( ds->ds_fnodes, NULL );
+ if ( ds->ds_origfilter ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+ op->ors_filter = ds->ds_origfilter;
+ op->ors_filterstr = ds->ds_origfilterbv;
+ }
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ }
+ return 0;
+}
+
+static int
+dynlist_test_dynmember(Operation *op, dynlist_name_t *dyn, Entry *e)
+{
+ LDAPURLDesc *ludp;
+ struct berval nbase, bv;
+ int i, rc = LDAP_COMPARE_FALSE;
+
+ for (i=0; i<dyn->dy_numuris; i++) {
+ ludp = dyn->dy_uris[i];
+ nbase.bv_val = ludp->lud_dn;
+ nbase.bv_len = ludp->lud_port;
+ if ( ludp->lud_attrs )
+ continue;
+ switch( ludp->lud_scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( !dn_match( &nbase, &e->e_nname ))
+ continue;
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ dnParent( &e->e_nname, &bv );
+ if ( !dn_match( &nbase, &bv ))
+ continue;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if ( !dnIsSuffix( &e->e_nname, &nbase ))
+ continue;
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( dn_match( &nbase, &e->e_nname ) ||
+ !dnIsSuffix( &e->e_nname, &nbase ))
+ continue;
+ break;
+ }
+ if ( !ludp->lud_filter ) /* there really should always be a filter */
+ rc = LDAP_COMPARE_TRUE;
+ else
+ rc = test_filter( op, e, (Filter *)ludp->lud_filter );
+ if ( rc == LDAP_COMPARE_TRUE )
+ break;
+ }
+ return rc;
+}
+
+static int
+dynlist_test_membership(Operation *op, slap_overinst *on, dynlist_name_t *dyn, Entry *e)
+{
+ if ( dyn->dy_staticmember ) {
+ Entry *grp;
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &grp, on ) == LDAP_SUCCESS && grp ) {
+ Attribute *a = attr_find( grp->e_attrs, dyn->dy_staticmember );
+ int rc;
+ if ( a ) {
+ rc = value_find_ex( dyn->dy_staticmember, SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, a->a_nvals, &e->e_nname, op->o_tmpmemctx );
+ rc = ( rc == LDAP_SUCCESS ) ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ } else {
+ rc = LDAP_COMPARE_FALSE;
+ }
+ overlay_entry_release_ov( op, grp, 0, on );
+ return rc;
+ }
+ }
+ return dynlist_test_dynmember( op, dyn, e );
+}
+
+static void
+dynlist_add_memberOf(Operation *op, SlapReply *rs, dynlist_search_t *ds)
+{
+ TAvlnode *ptr;
+ Entry *e = rs->sr_entry;
+ dynlist_name_t *dyn;
+ Attribute *a;
+
+ /* See if there are any memberOf values to attach to this entry */
+ for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dynlist_map_t *dlm;
+ dyn = ptr->avl_data;
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ if ( dynlist_test_membership( op, ds->ds_on, dyn, e ) == LDAP_COMPARE_TRUE ) {
+ /* ensure e is modifiable, but do not replace
+ * sr_entry yet since we have pointers into it */
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) && e == rs->sr_entry ) {
+ e = entry_dup( rs->sr_entry );
+ }
+ a = attr_find( e->e_attrs, dlm->dlm_memberOf_ad );
+ if ( a ) {
+ unsigned slot;
+ if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &dyn->dy_nname, &slot, NULL ) != LDAP_SUCCESS )
+ a = NULL;
+ }
+ if ( !a )
+ attr_merge_one( e, dlm->dlm_memberOf_ad, &dyn->dy_name, &dyn->dy_nname );
+ if ( dyn->dy_sups ) {
+ dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups );
+ }
+ break;
+ }
+ }
+ }
+ }
+ if ( e != rs->sr_entry ) {
+ rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+}
+
+/* See if a DN-valued filter attribute belongs to a dyngroup */
+static int
+dynmember( dynlist_name_t *dyn, Filter *f, int ndf, dynlist_filterinst_t *df )
+{
+ int i;
+ int ret = 1; /* default to accepting everything */
+
+ for ( i = 0; i < ndf; i++ ) {
+ if ( df[i].df_e ) {
+ ret = dynlist_test_dynmember( NULL, dyn, df[i].df_e ) == LDAP_COMPARE_TRUE;
+ if ( ret )
+ break;
+ }
+ }
+ return ret;
+}
+
+/* process the search responses */
+static int
+dynlist_search2resp( Operation *op, SlapReply *rs )
+{
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ dynlist_name_t *dyn;
+ int rc;
+
+ if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+ rc = SLAP_CB_CONTINUE;
+ /* See if this is one of our dynamic groups */
+ dyn = NULL;
+ if ( ds->ds_names ) {
+ dyn = ldap_tavl_find( ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp );
+ if ( dyn ) {
+ dyn->dy_seen = 1;
+ rc = dynlist_prepare_entry( op, rs, ds->ds_on, dyn->dy_dli, dyn );
+ } else if ( ds->ds_want )
+ dynlist_add_memberOf( op, rs, ds );
+ }
+ /* Then check for dynamic lists */
+ if ( dyn == NULL ) {
+ dynlist_info_t *dli;
+ Attribute *a = attr_find ( rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass );
+ if ( a ) {
+ for ( dli = ds->ds_dli; dli; dli = dli->dli_next ) {
+ if ( is_entry_objectclass_or_sub( rs->sr_entry, dli->dli_oc ) &&
+ dynlist_check_scope( op, rs->sr_entry, dli ))
+ rc = dynlist_prepare_entry( op, rs, ds->ds_on, dli, NULL );
+ }
+ }
+ }
+ if ( ds->ds_origfilter && test_filter( op, rs->sr_entry, ds->ds_origfilter ) != LDAP_COMPARE_TRUE ) {
+ rs_flush_entry( op, rs, NULL );
+ return LDAP_SUCCESS;
+ }
+ return rc;
+ } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) {
+ slap_overinst *on = ds->ds_on;
+ TAvlnode *ptr, *skip = NULL;
+ SlapReply r = *rs;
+ dynlist_map_t *dlm = NULL;
+ Filter *f = ds->ds_origfilter ? ds->ds_origfilter : op->ors_filter;
+ dynlist_filterinst_t *df = NULL;
+ int ndf = 0;
+
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED )
+ return SLAP_CB_CONTINUE;
+
+ /* Check for any unexpanded dynamic group entries that weren't picked up
+ * by the original search filter.
+ */
+ ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT );
+ while ( ptr ) {
+ dyn = ptr->avl_data;
+ if ( dyn->dy_seen )
+ goto next;
+ dyn->dy_seen = 1;
+ if ( !dnIsSuffixScope( &dyn->dy_nname, &op->o_req_ndn, op->ors_scope ))
+ goto next;
+ /* can only pre-check if this is a dyngroup, otherwise just build the entry */
+ if ( dyn->dy_dli->dli_dlm && !dyn->dy_dli->dli_dlm->dlm_next &&
+ dyn->dy_dlm && !dyn->dy_dlm->dlm_mapped_ad ) {
+ if ( !dlm ) {
+ AttributeDescription *ad;
+ int i;
+ dlm = dyn->dy_dlm;
+ ad = dlm->dlm_member_ad;
+ /* can only pre-check DN-valued attrs */
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ /* find any instances of this ad in the filter */
+ dynlist_filter_instances( op, ad, f, 0, &ndf, &df );
+ for ( i = 0; i < ndf; i++ ) {
+ overlay_entry_get_ov( op, &df[i].df_a->aa_value, NULL, NULL, 0, &df[i].df_e, on );
+ }
+ }
+ } else if ( dlm != dyn->dy_dlm ) { /* if a different map, do it later */
+ if ( !skip )
+ skip = ptr;
+ dyn->dy_seen = 0; /* we'll want to process it next time thru */
+ goto next;
+ }
+ /* only pre-check for non-nested */
+ if ( !dyn->dy_sups && !dyn->dy_subs && ndf && !dynmember( dyn, f, ndf, df ))
+ goto next;
+ }
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &r.sr_entry, on ) != LDAP_SUCCESS ||
+ r.sr_entry == NULL )
+ goto next;
+ r.sr_flags = REP_ENTRY_MUSTRELEASE;
+ dynlist_prepare_entry( op, &r, on, dyn->dy_dli, dyn );
+ if ( test_filter( op, r.sr_entry, f ) == LDAP_COMPARE_TRUE ) {
+ r.sr_attrs = op->ors_attrs;
+ rs->sr_err = send_search_entry( op, &r );
+ if ( rs->sr_err != LDAP_SUCCESS )
+ break;
+ r.sr_entry = NULL;
+ }
+ if ( r.sr_entry )
+ rs_flush_entry( op, &r, NULL );
+next:
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT );
+ if ( !ptr ) {
+ int i;
+ for ( i = 0; i<ndf; i++ ) {
+ if ( df[i].df_e )
+ overlay_entry_release_ov( op, df[i].df_e, 0, on );
+ }
+ op->o_tmpfree( df, op->o_tmpmemctx );
+ ndf = 0;
+ if ( skip ) { /* go back for dyns we skipped */
+ ptr = skip;
+ skip = NULL;
+ dlm = NULL;
+ df = NULL;
+ }
+ }
+ }
+ if ( ndf ) {
+ int i;
+ for ( i = 0; i<ndf; i++ ) {
+ if ( df[i].df_e )
+ overlay_entry_release_ov( op, df[i].df_e, 0, on );
+ }
+ op->o_tmpfree( df, op->o_tmpmemctx );
+ }
+ rs->sr_nentries = r.sr_nentries;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+dynlist_fix_filter( Operation *op, AttributeDescription *ad, dynlist_search_t *ds )
+{
+ Filter *f;
+ f = dynlist_filter_dup( op, op->ors_filter, ad, ds );
+ if ( ds->ds_origfilter ) {
+ filter_free_x( op, op->ors_filter, 1 );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ } else {
+ ds->ds_origfilter = op->ors_filter;
+ ds->ds_origfilterbv = op->ors_filterstr;
+ }
+ op->ors_filter = f;
+ filter2bv_x( op, f, &op->ors_filterstr );
+}
+
+typedef struct dynlist_link_t {
+ dynlist_search_t *dl_ds;
+ dynlist_name_t *dl_sup;
+} dynlist_link_t;
+
+static int
+dynlist_nestlink_dg( Operation *op, SlapReply *rs )
+{
+ dynlist_link_t *dll = op->o_callback->sc_private;
+ dynlist_search_t *ds = dll->dl_ds;
+ dynlist_name_t *di = dll->dl_sup, *dj;
+
+ if ( rs->sr_type != REP_SEARCH )
+ return LDAP_SUCCESS;
+
+ dj = ldap_tavl_find( dll->dl_ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp );
+ if ( dj ) {
+ if ( ds->ds_want & WANT_MEMBEROF ) {
+ ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ if ( ds->ds_want & WANT_MEMBER ) {
+ ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+/* Connect all nested groups to their parents/children */
+static void
+dynlist_nestlink( Operation *op, dynlist_search_t *ds )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_name_t *di, *dj;
+ TAvlnode *ptr;
+ Entry *e;
+ Attribute *a;
+ int i;
+
+ for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ di = ptr->avl_data;
+ if ( ds->ds_dlm ) {
+ if ( overlay_entry_get_ov( op, &di->dy_nname, NULL, NULL, 0, &e, on ) != LDAP_SUCCESS || e == NULL )
+ continue;
+ a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad );
+ if ( a ) {
+ for ( i=0; i < a->a_numvals; i++ ) {
+ dj = ldap_tavl_find( ds->ds_names, &a->a_nvals[i], dynlist_avl_cmp );
+ if ( dj ) {
+ if ( ds->ds_want & WANT_MEMBEROF ) {
+ ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ if ( ds->ds_want & WANT_MEMBER ) {
+ ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ }
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ if ( di->dy_numuris ) {
+ slap_callback cb = { 0 };
+ dynlist_link_t dll;
+ dll.dl_ds = ds;
+ dll.dl_sup = di;
+ cb.sc_private = &dll;
+ cb.sc_response = dynlist_nestlink_dg;
+ dynlist_urlmembers( op, di, &cb );
+ }
+ }
+}
+
+static int
+dynlist_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli;
+ Operation o = *op;
+ dynlist_map_t *dlm;
+ Filter f[4];
+ AttributeAssertion ava[2];
+ AttributeName an[2] = {0};
+
+ slap_callback *sc;
+ dynlist_search_t *ds;
+ ObjectClass *static_oc;
+ int nested, found, tmpwant;
+ int opattrs, userattrs;
+
+ if ( get_manageDSAit( op ) )
+ return SLAP_CB_CONTINUE;
+
+ sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(dynlist_search_t), op->o_tmpmemctx );
+ sc->sc_private = (void *)(sc+1);
+ ds = sc->sc_private;
+
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ o.o_do_not_cache = 1;
+
+ /* Are we using memberOf, and does it affect this request? */
+ if ( dlg->dlg_memberOf ) {
+ int attrflags = slap_attr_flags( op->ors_attrs );
+ opattrs = SLAP_OPATTRS( attrflags );
+ userattrs = SLAP_USERATTRS( attrflags );
+ }
+
+ if (dlg->dlg_simple)
+ goto simple;
+ /* Find all groups in scope. For group expansion
+ * we only need the groups within the search scope, but
+ * for memberOf populating, we need all dyngroups.
+ */
+ for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) {
+ static_oc = NULL;
+ nested = 0;
+ tmpwant = 0;
+
+ if ( !dli->dli_dlm ) {
+ /* A dynamic list returning arbitrary attrs:
+ * we don't know what attrs it might return,
+ * so we can't check if any of its attrs are
+ * in the filter. So assume none of them are.
+ *
+ * If filtering is desired, the filterable attrs
+ * must be explicitly mapped (even to
+ * themselves if nothing else).
+ */
+ continue;
+ } else {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ int want = 0;
+
+ /* is attribute in filter? */
+ if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) {
+ want |= WANT_MEMBEROF;
+ /* with nesting, filter attributes also require nestlink */
+ if ( dlm->dlm_memberOf_nested ) {
+ /* WANT_ flags have inverted meaning here:
+ * to satisfy (memberOf=) filter, we need to also
+ * find all subordinate groups. No special
+ * treatment is needed for (member=) since we
+ * already search all group entries.
+ */
+ want |= WANT_MEMBER;
+ }
+ }
+
+ /* if attribute is not requested, skip it */
+ if ( op->ors_attrs == NULL ) {
+ if ( !dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ } else {
+ if ( ad_inlist( dlm->dlm_memberOf_ad, op->ors_attrs )) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && ad_inlist( dlm->dlm_member_ad, op->ors_attrs ))
+ want |= WANT_MEMBER;
+ } else {
+ if ( opattrs ) {
+ if ( dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ }
+ if ( userattrs ) {
+ if ( !dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ }
+ }
+ }
+ if ( want ) {
+ nested = dlm->dlm_memberOf_nested;
+ ds->ds_want = tmpwant = want;
+ if ( dlm->dlm_static_oc ) {
+ static_oc = dlm->dlm_static_oc;
+ ds->ds_dlm = dlm;
+ }
+ }
+ }
+ {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( ad_infilter( ad, op->ors_filter )) {
+ tmpwant |= WANT_MEMBER;
+ ds->ds_want = tmpwant;
+ ds->ds_dlm = dlm;
+ }
+ }
+ }
+ }
+
+ if ( tmpwant ) {
+ Filter *f_new = NULL;
+
+ if ( tmpwant == WANT_MEMBER ) {
+ /*
+ * If we only need to list groups, not their members, keep the
+ * filter, assuming any references to mapped attributes make it
+ * succeed.
+ *
+ * A nested groups search will indicate that it needs both.
+ */
+ f_new = transform_filter( op, dli, 0, NULL );
+ }
+
+ if ( static_oc ) {
+ f[0].f_choice = LDAP_FILTER_AND;
+ f[0].f_list = &f[1];
+ f[0].f_next = NULL;
+ f[1].f_choice = LDAP_FILTER_OR;
+ f[1].f_list = &f[2];
+ f[1].f_next = f_new;
+ f[2].f_choice = LDAP_FILTER_EQUALITY;
+ f[2].f_next = &f[3];
+ f[2].f_ava = &ava[0];
+ f[2].f_av_desc = slap_schema.si_ad_objectClass;
+ f[2].f_av_value = dli->dli_oc->soc_cname;
+ f[3].f_choice = LDAP_FILTER_EQUALITY;
+ f[3].f_ava = &ava[1];
+ f[3].f_av_desc = slap_schema.si_ad_objectClass;
+ f[3].f_av_value = static_oc->soc_cname;
+ f[3].f_next = NULL;
+ } else {
+ f[0].f_choice = LDAP_FILTER_AND;
+ f[0].f_list = &f[1];
+ f[0].f_next = NULL;
+ f[1].f_choice = LDAP_FILTER_EQUALITY;
+ f[1].f_ava = ava;
+ f[1].f_av_desc = slap_schema.si_ad_objectClass;
+ f[1].f_av_value = dli->dli_oc->soc_cname;
+ f[1].f_next = f_new;
+ }
+
+ if ( o.o_callback != sc ) {
+ o.o_callback = sc;
+ o.ors_filter = f;
+ if ( tmpwant ) {
+ o.o_req_dn = op->o_bd->be_suffix[0];
+ o.o_req_ndn = op->o_bd->be_nsuffix[0];
+ o.ors_scope = LDAP_SCOPE_SUBTREE;
+ } else {
+ o.o_req_dn = op->o_req_dn;
+ o.o_req_ndn = op->o_req_ndn;
+ o.ors_scope = op->ors_scope;
+ }
+ o.ors_attrsonly = 0;
+ o.ors_attrs = an;
+ o.o_bd = select_backend( op->o_bd->be_nsuffix, 1 );
+ BER_BVZERO( &o.ors_filterstr );
+ sc->sc_response = dynlist_search1resp;
+ }
+
+ ds->ds_dli = dli;
+ if ( o.ors_filterstr.bv_val )
+ o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx );
+ filter2bv_x( &o, f, &o.ors_filterstr );
+ an[0].an_desc = dli->dli_ad;
+ an[0].an_name = dli->dli_ad->ad_cname;
+ found = ds->ds_found;
+ {
+ SlapReply r = { REP_SEARCH };
+ (void)o.o_bd->be_search( &o, &r );
+ }
+ o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx );
+ o.ors_filterstr.bv_val = NULL;
+ filter_free_x( &o, f_new, 1 );
+ if ( found != ds->ds_found && nested )
+ dynlist_nestlink( op, ds );
+ }
+ }
+simple:
+
+ if ( dlg->dlg_dli || ds->ds_names != NULL ) {
+ sc->sc_response = dynlist_search2resp;
+ sc->sc_cleanup = dynlist_search_cleanup;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ ds->ds_on = on;
+
+ /* dynamic lists need this */
+ ds->ds_dli = dlg->dlg_dli;
+
+ /* see if filter needs fixing */
+ if ( dlg->dlg_memberOf ) {
+ for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+
+ /* if attribute is in filter, fix it */
+ if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) {
+ ds->ds_dli = dli;
+ ds->ds_dlm = dlm;
+ dynlist_fix_filter( op, dlm->dlm_memberOf_ad, ds );
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dynlist_build_def_filter( dynlist_info_t *dli )
+{
+ char *ptr;
+
+ dli->dli_default_filter.bv_len = STRLENOF( "(!(objectClass=" "))" )
+ + dli->dli_oc->soc_cname.bv_len;
+ dli->dli_default_filter.bv_val = ch_malloc( dli->dli_default_filter.bv_len + 1 );
+ if ( dli->dli_default_filter.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: malloc failed.\n" );
+ return -1;
+ }
+
+ ptr = lutil_strcopy( dli->dli_default_filter.bv_val, "(!(objectClass=" );
+ ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "))" );
+
+ assert( ptr == &dli->dli_default_filter.bv_val[dli->dli_default_filter.bv_len] );
+
+ return 0;
+}
+
+enum {
+ DL_ATTRSET = 1,
+ DL_ATTRPAIR,
+ DL_ATTRPAIR_COMPAT,
+ DL_LAST
+};
+
+static ConfigDriver dl_cfgen;
+
+/* XXXmanu 255 is the maximum arguments we allow. Can we go beyond? */
+static ConfigTable dlcfg[] = {
+ { "dynlist-attrset", "group-oc> [uri] <URL-ad> <[mapped:]member-ad> [...]",
+ 3, 0, 0, ARG_MAGIC|DL_ATTRSET, dl_cfgen,
+ "( OLcfgOvAt:8.1 NAME ( 'olcDynListAttrSet' 'olcDlAttrSet' ) "
+ "DESC 'Dynamic list: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "dynlist-attrpair", "member-ad> <URL-ad",
+ 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR, dl_cfgen,
+ NULL, NULL, NULL },
+#ifdef TAKEOVER_DYNGROUP
+ { "attrpair", "member-ad> <URL-ad",
+ 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR_COMPAT, dl_cfgen,
+ NULL, NULL, NULL },
+#endif
+ { "dynlist-simple", NULL, 0, 0, 0, ARG_OFFSET|ARG_ON_OFF,
+ (void *)offsetof(dynlist_gen_t, dlg_simple),
+ "( OLcfgOvAt:8.2 NAME 'olcDynListSimple' "
+ "DESC 'Simple mode - disable features added since 2.4.' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dlocs[] = {
+ { "( OLcfgOvOc:8.1 "
+ "NAME ( 'olcDynListConfig' 'olcDynamicList' ) "
+ "DESC 'Dynamic list configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcDynListAttrSet $ olcDynListSimple ) )",
+ Cft_Overlay, dlcfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+dl_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+
+ int rc = 0, i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case DL_ATTRSET:
+ for ( i = 0; dli; i++, dli = dli->dli_next ) {
+ struct berval bv;
+ char *ptr = c->cr_msg;
+ dynlist_map_t *dlm;
+
+ assert( dli->dli_oc != NULL );
+ assert( dli->dli_ad != NULL );
+
+ /* FIXME: check buffer overflow! */
+ ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ SLAP_X_ORDERED_FMT "%s", i,
+ dli->dli_oc->soc_cname.bv_val );
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strncopy( ptr, dli->dli_uri.bv_val,
+ dli->dli_uri.bv_len );
+ *ptr++ = '"';
+ }
+
+ *ptr++ = ' ';
+ ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val,
+ dli->dli_ad->ad_cname.bv_len );
+
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ ptr[ 0 ] = ' ';
+ ptr++;
+ if ( dlm->dlm_mapped_ad ) {
+ ptr = lutil_strcopy( ptr, dlm->dlm_mapped_ad->ad_cname.bv_val );
+ ptr[ 0 ] = ':';
+ ptr++;
+ }
+
+ ptr = lutil_strcopy( ptr, dlm->dlm_member_ad->ad_cname.bv_val );
+
+ if ( dlm->dlm_memberOf_ad ) {
+ *ptr++ = '+';
+ ptr = lutil_strcopy( ptr, dlm->dlm_memberOf_ad->ad_cname.bv_val );
+ if ( dlm->dlm_static_oc ) {
+ *ptr++ = '@';
+ ptr = lutil_strcopy( ptr, dlm->dlm_static_oc->soc_cname.bv_val );
+ }
+ if ( dlm->dlm_memberOf_nested ) {
+ *ptr++ = '*';
+ }
+ }
+ }
+
+ bv.bv_val = c->cr_msg;
+ bv.bv_len = ptr - bv.bv_val;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case DL_ATTRPAIR_COMPAT:
+ case DL_ATTRPAIR:
+ rc = 1;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case DL_ATTRSET:
+ if ( c->valx < 0 ) {
+ dynlist_info_t *dli_next;
+
+ for ( dli_next = dli; dli_next; dli = dli_next ) {
+ dynlist_map_t *dlm = dli->dli_dlm;
+ dynlist_map_t *dlm_next;
+
+ dli_next = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+ }
+
+ dlg->dlg_dli = NULL;
+ dlg->dlg_memberOf = 0;
+
+ } else {
+ dynlist_info_t **dlip;
+ dynlist_map_t *dlm;
+ dynlist_map_t *dlm_next;
+
+ for ( i = 0, dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ i < c->valx; i++ )
+ {
+ if ( *dlip == NULL ) {
+ return 1;
+ }
+ dlip = &(*dlip)->dli_next;
+ }
+
+ dli = *dlip;
+ *dlip = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ dlm = dli->dli_dlm;
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ if ( dlm->dlm_memberOf_ad )
+ dlg->dlg_memberOf--;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+
+ dli = (dynlist_info_t *)dlg->dlg_dli;
+ }
+ break;
+
+ case DL_ATTRPAIR_COMPAT:
+ case DL_ATTRPAIR:
+ rc = 1;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ switch( c->type ) {
+ case DL_ATTRSET: {
+ dynlist_info_t **dlip,
+ *dli_next = NULL;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ int attridx = 2;
+ LDAPURLDesc *lud = NULL;
+ struct berval nbase = BER_BVNULL;
+ Filter *filter = NULL;
+ struct berval uri = BER_BVNULL;
+ dynlist_map_t *dlm = NULL, *dlml = NULL;
+ const char *text;
+
+ oc = oc_find( c->argv[ 1 ] );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to find ObjectClass \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 ) {
+ if ( ldap_url_parse( c->argv[ attridx ], &lud ) != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to parse URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_host != NULL ) {
+ if ( lud->lud_host[0] == '\0' ) {
+ ch_free( lud->lud_host );
+ lud->lud_host = NULL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "host not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+ }
+
+ if ( lud->lud_attrs != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "attrs not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_exts != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "extensions not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_dn != NULL && lud->lud_dn[ 0 ] != '\0' ) {
+ struct berval dn;
+ ber_str2bv( lud->lud_dn, 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &nbase, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "DN normalization failed in URI \"%s\"",
+ c->argv[ attridx ] );
+ goto done_uri;
+ }
+ }
+
+ if ( lud->lud_filter != NULL && lud->lud_filter[ 0 ] != '\0' ) {
+ filter = str2filter( lud->lud_filter );
+ if ( filter == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "filter parsing failed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+ }
+
+ ber_str2bv( c->argv[ attridx ], 0, 1, &uri );
+
+done_uri:;
+ if ( rc ) {
+ if ( lud ) {
+ ldap_free_urldesc( lud );
+ }
+
+ if ( !BER_BVISNULL( &nbase ) ) {
+ ber_memfree( nbase.bv_val );
+ }
+
+ if ( filter != NULL ) {
+ filter_free( filter );
+ }
+
+ while ( dlm != NULL ) {
+ dlml = dlm;
+ dlm = dlm->dlm_next;
+ ch_free( dlml );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+
+ return rc;
+ }
+
+ attridx++;
+ }
+
+ rc = slap_str2ad( c->argv[ attridx ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to find AttributeDescription \"%s\"",
+ c->argv[ attridx ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "AttributeDescription \"%s\" "
+ "must be a subtype of \"labeledURI\"",
+ c->argv[ attridx ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+
+ attridx++;
+
+ for ( i = attridx; i < c->argc; i++ ) {
+ char *arg;
+ char *cp;
+ AttributeDescription *member_ad = NULL;
+ AttributeDescription *mapped_ad = NULL;
+ AttributeDescription *memberOf_ad = NULL;
+ ObjectClass *static_oc = NULL;
+ int nested = 0;
+ dynlist_map_t *dlmp;
+
+
+ /*
+ * If no mapped attribute is given, dn is used
+ * for backward compatibility.
+ */
+ arg = c->argv[i];
+ if ( ( cp = strchr( arg, ':' ) ) != NULL ) {
+ struct berval bv;
+ ber_str2bv( arg, cp - arg, 0, &bv );
+ rc = slap_bv2ad( &bv, &mapped_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find mapped AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ arg = cp + 1;
+ }
+ if ( ( cp = strchr( arg, '+' ) ) != NULL ) {
+ struct berval bv;
+ char *ocp, *np;
+ np = strrchr( cp+1, '*' );
+ if ( np ) {
+ nested = 1;
+ *np = '\0';
+ }
+ ocp = strchr( cp+1, '@' );
+ if ( ocp ) {
+ static_oc = oc_find( ocp+1 );
+ if ( !static_oc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find static-oc ObjectClass #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ *ocp = '\0';
+ }
+ ber_str2bv( cp+1, 0, 0, &bv );
+ rc = slap_bv2ad( &bv, &memberOf_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find memberOf AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ dlg->dlg_memberOf++;
+ *cp = '\0';
+ }
+
+ rc = slap_str2ad( arg, &member_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+
+ dlmp = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) );
+ if ( dlm == NULL ) {
+ dlm = dlmp;
+ }
+ dlmp->dlm_member_ad = member_ad;
+ dlmp->dlm_mapped_ad = mapped_ad;
+ dlmp->dlm_memberOf_ad = memberOf_ad;
+ dlmp->dlm_static_oc = static_oc;
+ dlmp->dlm_memberOf_nested = nested;
+ dlmp->dlm_member_oper = is_at_operational( member_ad->ad_type );
+ if ( memberOf_ad ) {
+ dlmp->dlm_memberOf_oper = is_at_operational( memberOf_ad->ad_type );
+ } else {
+ dlmp->dlm_memberOf_oper = 0;
+ }
+ dlmp->dlm_next = NULL;
+
+ if ( dlml != NULL )
+ dlml->dlm_next = dlmp;
+ dlml = dlmp;
+ }
+
+ if ( c->valx > 0 ) {
+ int i;
+
+ for ( i = 0, dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ i < c->valx; i++ )
+ {
+ if ( *dlip == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "invalid index {%d}\n",
+ c->valx );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ dlip = &(*dlip)->dli_next;
+ }
+ dli_next = *dlip;
+
+ } else {
+ for ( dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ *dlip; dlip = &(*dlip)->dli_next )
+ /* goto last */;
+ }
+
+ *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) );
+
+ (*dlip)->dli_oc = oc;
+ (*dlip)->dli_ad = ad;
+ (*dlip)->dli_dlm = dlm;
+ (*dlip)->dli_next = dli_next;
+
+ (*dlip)->dli_lud = lud;
+ (*dlip)->dli_uri_nbase = nbase;
+ (*dlip)->dli_uri_filter = filter;
+ (*dlip)->dli_uri = uri;
+
+ rc = dynlist_build_def_filter( *dlip );
+
+ } break;
+
+ case DL_ATTRPAIR_COMPAT:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: \"attrpair\" only supported for limited "
+ "backward compatibility with overlay \"dyngroup\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ /* fallthru */
+
+ case DL_ATTRPAIR: {
+ dynlist_info_t **dlip;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL,
+ *member_ad = NULL;
+ const char *text;
+
+ oc = oc_find( "groupOfURLs" );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find default ObjectClass \"groupOfURLs\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ rc = slap_str2ad( c->argv[ 1 ], &member_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find AttributeDescription \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ rc = slap_str2ad( c->argv[ 2 ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find AttributeDescription \"%s\"\n",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "AttributeDescription \"%s\" "
+ "must be a subtype of \"labeledURI\"",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ *dlip; dlip = &(*dlip)->dli_next )
+ {
+ /*
+ * The same URL attribute / member attribute pair
+ * cannot be repeated, but we enforce this only
+ * when the member attribute is unique. Performing
+ * the check for multiple values would require
+ * sorting and comparing the lists, which is left
+ * as a future improvement
+ */
+ if ( (*dlip)->dli_ad == ad &&
+ (*dlip)->dli_dlm->dlm_next == NULL &&
+ member_ad == (*dlip)->dli_dlm->dlm_member_ad ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "URL attributeDescription \"%s\" already mapped.\n",
+ ad->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+#if 0
+ /* make it a warning... */
+ return 1;
+#endif
+ }
+ }
+
+ *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) );
+
+ (*dlip)->dli_oc = oc;
+ (*dlip)->dli_ad = ad;
+ (*dlip)->dli_dlm = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) );
+ (*dlip)->dli_dlm->dlm_member_ad = member_ad;
+ (*dlip)->dli_dlm->dlm_mapped_ad = NULL;
+
+ rc = dynlist_build_def_filter( *dlip );
+
+ } break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+dynlist_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dynlist_gen_t *dlg;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Debug( LDAP_DEBUG_ANY, "dynlist cannot be used as global overlay.\n" );
+ return 1;
+ }
+
+ dlg = (dynlist_gen_t *)ch_calloc( 1, sizeof( *dlg ));
+ on->on_bi.bi_private = dlg;
+
+ return 0;
+}
+
+static int
+dynlist_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ if ( dli == NULL ) {
+ dli = ch_calloc( 1, sizeof( dynlist_info_t ) );
+ dlg->dlg_dli = dli;
+ }
+
+ for ( ; dli; dli = dli->dli_next ) {
+ if ( dli->dli_oc == NULL ) {
+ if ( oc == NULL ) {
+ oc = oc_find( "groupOfURLs" );
+ if ( oc == NULL ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch objectClass \"groupOfURLs\"" );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg );
+ return 1;
+ }
+ }
+
+ dli->dli_oc = oc;
+ }
+
+ if ( dli->dli_ad == NULL ) {
+ if ( ad == NULL ) {
+ rc = slap_str2ad( "memberURL", &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"memberURL\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg );
+ return 1;
+ }
+ }
+
+ dli->dli_ad = ad;
+ }
+
+ if ( BER_BVISNULL( &dli->dli_default_filter ) ) {
+ rc = dynlist_build_def_filter( dli );
+ if ( rc != 0 ) {
+ return rc;
+ }
+ }
+ }
+
+ if ( ad_dgIdentity == NULL ) {
+ rc = slap_str2ad( "dgIdentity", &ad_dgIdentity, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"dgIdentity\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg );
+ /* Just a warning */
+ }
+ }
+
+ if ( ad_dgAuthz == NULL ) {
+ rc = slap_str2ad( "dgAuthz", &ad_dgAuthz, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"dgAuthz\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg );
+ /* Just a warning */
+ }
+ }
+
+ return 0;
+}
+
+static int
+dynlist_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+
+ if ( on->on_bi.bi_private ) {
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli,
+ *dli_next;
+
+ for ( dli_next = dli; dli_next; dli = dli_next ) {
+ dynlist_map_t *dlm;
+ dynlist_map_t *dlm_next;
+
+ dli_next = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ dlm = dli->dli_dlm;
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+ }
+ ch_free( dlg );
+ }
+
+ return 0;
+}
+
+static slap_overinst dynlist = { { NULL } };
+#ifdef TAKEOVER_DYNGROUP
+static char *obsolete_names[] = {
+ "dyngroup",
+ NULL
+};
+#endif
+
+#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC */
+int
+dynlist_initialize(void)
+{
+ const char *text;
+ int rc = 0;
+
+ /* See if we need to define memberOf opattr */
+ rc = slap_str2ad( "memberOf", &ad_memberOf, &text );
+ if ( rc ) {
+ rc = register_at(
+ "( 1.2.840.113556.1.2.102 "
+ "NAME 'memberOf' "
+ "DESC 'Group that the entry belongs to' "
+ "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' "
+ "EQUALITY distinguishedNameMatch " /* added */
+ "USAGE dSAOperation " /* added; questioned */
+ "NO-USER-MODIFICATION " /* added */
+ "X-ORIGIN 'iPlanet Delegated Administrator' )",
+ &ad_memberOf, 0 );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dynlist_initialize: register_at (memberOf) failed\n" );
+ return rc;
+ }
+ }
+
+ dynlist.on_bi.bi_type = "dynlist";
+
+#ifdef TAKEOVER_DYNGROUP
+ /* makes dynlist incompatible with dyngroup */
+ dynlist.on_bi.bi_obsolete_names = obsolete_names;
+#endif
+
+ dynlist.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ dynlist.on_bi.bi_db_init = dynlist_db_init;
+ dynlist.on_bi.bi_db_config = config_generic_wrapper;
+ dynlist.on_bi.bi_db_open = dynlist_db_open;
+ dynlist.on_bi.bi_db_destroy = dynlist_db_destroy;
+
+ dynlist.on_bi.bi_op_search = dynlist_search;
+ dynlist.on_bi.bi_op_compare = dynlist_compare;
+
+ dynlist.on_bi.bi_cf_ocs = dlocs;
+
+ rc = config_register_schema( dlcfg, dlocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &dynlist );
+}
+
+#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return dynlist_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_DYNLIST */
diff --git a/servers/slapd/overlays/homedir.c b/servers/slapd/overlays/homedir.c
new file mode 100644
index 0000000..159090e
--- /dev/null
+++ b/servers/slapd/overlays/homedir.c
@@ -0,0 +1,2074 @@
+/* homedir.c - create/remove user home directories */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 The OpenLDAP Foundation.
+ * Portions copyright 2009-2010 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Emily Backes at Symas
+ * Corp. for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_HOMEDIR
+
+#define _FILE_OFFSET_BITS 64
+
+#include <stdio.h>
+#include <fcntl.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include <ac/unistd.h>
+#include <ac/dirent.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "slap-config.h"
+
+#define DEFAULT_MIN_UID ( 100 )
+#define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" )
+
+typedef struct homedir_regexp {
+ char *match;
+ char *replace;
+ regex_t compiled;
+ struct homedir_regexp *next;
+} homedir_regexp;
+
+typedef enum {
+ DEL_IGNORE,
+ DEL_DELETE,
+ DEL_ARCHIVE
+} delete_style;
+
+typedef struct homedir_data {
+ char *skeleton_path;
+ unsigned min_uid;
+ AttributeDescription *home_ad;
+ AttributeDescription *uidn_ad;
+ AttributeDescription *gidn_ad;
+ homedir_regexp *regexps;
+ delete_style style;
+ char *archive_path;
+} homedir_data;
+
+typedef struct homedir_cb_data {
+ slap_overinst *on;
+ Entry *entry;
+} homedir_cb_data;
+
+typedef struct name_list {
+ char *name;
+ struct stat st;
+ struct name_list *next;
+} name_list;
+
+typedef struct name_list_list {
+ name_list *list;
+ struct name_list_list *next;
+} name_list_list;
+
+typedef enum {
+ TRAVERSE_CB_CONTINUE,
+ TRAVERSE_CB_DONE,
+ TRAVERSE_CB_FAIL
+} traverse_cb_ret;
+
+/* private, file info, context */
+typedef traverse_cb_ret (*traverse_cb_func)(
+ void *,
+ const char *,
+ const struct stat *,
+ void * );
+typedef struct traverse_cb {
+ traverse_cb_func pre_func;
+ traverse_cb_func post_func;
+ void *pre_private;
+ void *post_private;
+} traverse_cb;
+
+typedef struct copy_private {
+ int source_prefix_len;
+ const char *dest_prefix;
+ int dest_prefix_len;
+ uid_t uidn;
+ gid_t gidn;
+} copy_private;
+
+typedef struct chown_private {
+ uid_t old_uidn;
+ uid_t new_uidn;
+ gid_t old_gidn;
+ gid_t new_gidn;
+} chown_private;
+
+typedef struct ustar_header {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char checksum[8];
+ char typeflag[1];
+ char linkname[100];
+ char magic[6];
+ char version[2];
+ char uname[32];
+ char gname[32];
+ char devmajor[8];
+ char devminor[8];
+ char prefix[155];
+ char pad[12];
+} ustar_header;
+
+typedef struct tar_private {
+ FILE *file;
+ const char *name;
+} tar_private;
+
+/* FIXME: This mutex really needs to be executable-global, but this
+ * will have to do for now.
+ */
+static ldap_pvt_thread_mutex_t readdir_mutex;
+static ConfigDriver homedir_regexp_cfg;
+static ConfigDriver homedir_style_cfg;
+static slap_overinst homedir;
+
+static ConfigTable homedircfg[] = {
+ { "homedir-skeleton-path", "pathname", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(homedir_data, skeleton_path),
+ "( OLcfgCtAt:8.1 "
+ "NAME 'olcSkeletonPath' "
+ "DESC 'Pathname for home directory skeleton template' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, { .v_string = DEFAULT_SKEL }
+ },
+
+ { "homedir-min-uidnumber", "uid number", 2, 2, 0,
+ ARG_UINT|ARG_OFFSET,
+ (void *)offsetof(homedir_data, min_uid),
+ "( OLcfgCtAt:8.2 "
+ "NAME 'olcMinimumUidNumber' "
+ "DESC 'Minimum uidNumber attribute to consider' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, { .v_uint = DEFAULT_MIN_UID }
+ },
+
+ { "homedir-regexp", "regexp> <path", 3, 3, 0,
+ ARG_MAGIC,
+ homedir_regexp_cfg,
+ "( OLcfgCtAt:8.3 "
+ "NAME 'olcHomedirRegexp' "
+ "DESC 'Regular expression for matching and transforming paths' "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL
+ },
+
+ { "homedir-delete-style", "style", 2, 2, 0,
+ ARG_MAGIC,
+ homedir_style_cfg,
+ "( OLcfgCtAt:8.4 "
+ "NAME 'olcHomedirDeleteStyle' "
+ "DESC 'Action to perform when removing a home directory' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ { "homedir-archive-path", "pathname", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(homedir_data, archive_path),
+ "( OLcfgCtAt:8.5 "
+ "NAME 'olcHomedirArchivePath' "
+ "DESC 'Pathname for home directory archival' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs homedirocs[] = {
+ { "( OLcfgCtOc:8.1 "
+ "NAME 'olcHomedirConfig' "
+ "DESC 'Homedir configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSkeletonPath $ olcMinimumUidNumber "
+ "$ olcHomedirRegexp $ olcHomedirDeleteStyle "
+ "$ olcHomedirArchivePath ) )",
+ Cft_Overlay, homedircfg },
+
+ { NULL, 0, NULL }
+};
+
+static int
+homedir_regexp_cfg( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ homedir_data *data = (homedir_data *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert( data != NULL );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT: {
+ int i;
+ homedir_regexp *r;
+ struct berval bv;
+ char buf[4096];
+
+ bv.bv_val = buf;
+ for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) {
+ bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i,
+ r->match, r->replace );
+ if ( bv.bv_len >= sizeof(buf) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: "
+ "emit serialization failed: size %lu\n",
+ (unsigned long)bv.bv_len );
+ return ARG_BAD_CONF;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ } break;
+
+ case LDAP_MOD_DELETE:
+ if ( c->valx < 0 ) { /* delete all values */
+ homedir_regexp *r, *rnext;
+
+ for ( r = data->regexps; r != NULL; r = rnext ) {
+ rnext = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+ }
+ data->regexps = NULL;
+ rc = 0;
+
+ } else { /* delete value by index*/
+ homedir_regexp **rp, *r;
+ int i;
+
+ for ( i = 0, rp = &data->regexps; i < c->valx;
+ ++i, rp = &(*rp)->next )
+ ;
+
+ r = *rp;
+ *rp = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+
+ rc = 0;
+ }
+ break;
+
+ case LDAP_MOD_ADD: /* fallthrough */
+ case SLAP_CONFIG_ADD: { /* add values */
+ char *match = c->argv[1];
+ char *replace = c->argv[2];
+ regex_t compiled;
+ homedir_regexp **rp, *r;
+
+ memset( &compiled, 0, sizeof(compiled) );
+ rc = regcomp( &compiled, match, REG_EXTENDED );
+ if ( rc ) {
+ regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) );
+ regfree( &compiled );
+ return ARG_BAD_CONF;
+ }
+
+ r = ch_calloc( 1, sizeof(homedir_regexp) );
+ r->match = strdup( match );
+ r->replace = strdup( replace );
+ r->compiled = compiled;
+
+ if ( c->valx == -1 ) { /* append */
+ for ( rp = &data->regexps; ( *rp ) != NULL;
+ rp = &(*rp)->next )
+ ;
+ *rp = r;
+
+ } else { /* insert at valx */
+ int i;
+ for ( i = 0, rp = &data->regexps; i < c->valx;
+ rp = &(*rp)->next, ++i )
+ ;
+ r->next = *rp;
+ *rp = r;
+ }
+ rc = 0;
+ break;
+ }
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+homedir_style_cfg( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ homedir_data *data = (homedir_data *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+ struct berval bv;
+
+ assert( data != NULL );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" :
+ data->style == DEL_DELETE ? "DELETE" :
+ "ARCHIVE";
+ bv.bv_len = strlen( bv.bv_val );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( rc != 0 ) return ARG_BAD_CONF;
+ break;
+
+ case LDAP_MOD_DELETE:
+ data->style = DEL_IGNORE;
+ rc = 0;
+ break;
+
+ case LDAP_MOD_ADD: /* fallthrough */
+ case SLAP_CONFIG_ADD: /* add values */
+ if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 )
+ data->style = DEL_IGNORE;
+ else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 )
+ data->style = DEL_DELETE;
+ else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 )
+ data->style = DEL_ARCHIVE;
+ else {
+ Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: "
+ "unrecognized style keyword\n" );
+ return ARG_BAD_CONF;
+ }
+ rc = 0;
+ break;
+
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+#define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) )
+static void
+report_errno( const char *parent_func, const char *func, const char *filename )
+{
+ int save_errno = errno;
+ char ebuf[1024];
+
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "%s: %s: \"%s\": %d (%s)\n",
+ HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func),
+ HOMEDIR_NULLWRAP(filename), save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+}
+
+static int
+copy_link(
+ const char *dest_file,
+ const char *source_file,
+ const struct stat *st,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ char *buf = NULL;
+ int rc;
+
+ assert( dest_file != NULL );
+ assert( source_file != NULL );
+ assert( st != NULL );
+ assert( (st->st_mode & S_IFMT) == S_IFLNK );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_link: %s to %s\n",
+ source_file, dest_file );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_link: %s uid %ld gid %ld\n",
+ dest_file, (long)uidn, (long)gidn );
+
+ /* calloc +1 for terminator */
+ buf = ber_memcalloc_x( 1, st->st_size + 1, ctx );
+ if ( buf == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "copy_link: alloc failed\n" );
+ return 1;
+ }
+ rc = readlink( source_file, buf, st->st_size );
+ if ( rc == -1 ) {
+ report_errno( "copy_link", "readlink", source_file );
+ goto fail;
+ }
+ rc = symlink( buf, dest_file );
+ if ( rc ) {
+ report_errno( "copy_link", "symlink", dest_file );
+ goto fail;
+ }
+ rc = lchown( dest_file, uidn, gidn );
+ if ( rc ) {
+ report_errno( "copy_link", "lchown", dest_file );
+ goto fail;
+ }
+ goto out;
+
+fail:
+ rc = 1;
+
+out:
+ if ( buf != NULL ) ber_memfree_x( buf, ctx );
+ return rc;
+}
+
+static int
+copy_blocks(
+ FILE *source,
+ FILE *dest,
+ const char *source_file,
+ const char *dest_file )
+{
+ char buf[4096];
+ size_t nread = 0;
+ int done = 0;
+
+ while ( !done ) {
+ nread = fread( buf, 1, sizeof(buf), source );
+ if ( nread == 0 ) {
+ if ( feof( source ) ) {
+ done = 1;
+ } else if ( ferror( source ) ) {
+ if ( source_file != NULL )
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "read error on %s\n",
+ source_file );
+ goto fail;
+ }
+ } else {
+ size_t nwritten = 0;
+ nwritten = fwrite( buf, 1, nread, dest );
+ if ( nwritten < nread ) {
+ if ( dest_file != NULL )
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "write error on %s\n",
+ dest_file );
+ goto fail;
+ }
+ }
+ }
+ return 0;
+fail:
+ return 1;
+}
+
+static int
+copy_file(
+ const char *dest_file,
+ const char *source_file,
+ uid_t uid,
+ gid_t gid,
+ int mode )
+{
+ FILE *source = NULL;
+ FILE *dest = NULL;
+ int rc;
+
+ assert( dest_file != NULL );
+ assert( source_file != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s to %s mode 0%o\n",
+ source_file, dest_file, mode );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s uid %ld gid %ld\n",
+ dest_file, (long)uid, (long)gid );
+
+ source = fopen( source_file, "rb" );
+ if ( source == NULL ) {
+ report_errno( "copy_file", "fopen", source_file );
+ goto fail;
+ }
+ dest = fopen( dest_file, "wb" );
+ if ( dest == NULL ) {
+ report_errno( "copy_file", "fopen", dest_file );
+ goto fail;
+ }
+
+ rc = copy_blocks( source, dest, source_file, dest_file );
+ if ( rc != 0 ) goto fail;
+
+ fclose( source );
+ source = NULL;
+ rc = fclose( dest );
+ dest = NULL;
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "fclose", dest_file );
+ goto fail;
+ }
+
+ /* set owner/permission */
+ rc = lchown( dest_file, uid, gid );
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "lchown", dest_file );
+ goto fail;
+ }
+ rc = chmod( dest_file, mode );
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "chmod", dest_file );
+ goto fail;
+ }
+
+ rc = 0;
+ goto out;
+fail:
+ rc = 1;
+out:
+ if ( source != NULL ) fclose( source );
+ if ( dest != NULL ) fclose( dest );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s to %s exit %d\n",
+ source_file, dest_file, rc );
+ return rc;
+}
+
+static void
+free_name_list( name_list *names, void *ctx )
+{
+ name_list *next;
+
+ while ( names != NULL ) {
+ next = names->next;
+ if ( names->name != NULL ) ber_memfree_x( names->name, ctx );
+ ber_memfree_x( names, ctx );
+ names = next;
+ }
+}
+
+static int
+grab_names( const char *dir_path, name_list **names, void *ctx )
+{
+ int locked = 0;
+ DIR *dir = NULL;
+ struct dirent *entry = NULL;
+ name_list **tail = NULL;
+ int dir_path_len = 0;
+ int rc = 0;
+
+ assert( dir_path != NULL );
+ assert( names != NULL );
+ assert( *names == NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: %s\n", dir_path );
+
+ tail = names;
+ dir_path_len = strlen( dir_path );
+ ldap_pvt_thread_mutex_lock( &readdir_mutex );
+ locked = 1;
+
+ dir = opendir( dir_path );
+ if ( dir == NULL ) {
+ report_errno( "grab_names", "opendir", dir_path );
+ goto fail;
+ }
+
+ while ( ( entry = readdir( dir ) ) != NULL ) {
+ /* no d_namelen in ac/dirent.h */
+ int d_namelen = strlen( entry->d_name );
+ int full_len;
+
+ /* Skip . and .. */
+ if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) ||
+ ( d_namelen == 2 && entry->d_name[0] == '.' &&
+ entry->d_name[1] == '.' ) ) {
+ continue;
+ }
+
+ *tail = ber_memcalloc_x( 1, sizeof(**tail), ctx );
+ if ( *tail == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "grab_names: list alloc failed\n" );
+ goto fail;
+ }
+ (*tail)->next = NULL;
+
+ /* +1 for dirsep, +1 for term */
+ full_len = dir_path_len + 1 + d_namelen + 1;
+ (*tail)->name = ber_memalloc_x( full_len, ctx );
+ if ( (*tail)->name == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "grab_names: name alloc failed\n" );
+ goto fail;
+ }
+ snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s",
+ dir_path, entry->d_name );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: found \"%s\"\n",
+ (*tail)->name );
+
+ rc = lstat( (*tail)->name, &(*tail)->st );
+ if ( rc ) {
+ report_errno( "grab_names", "lstat", (*tail)->name );
+ goto fail;
+ }
+
+ tail = &(*tail)->next;
+ }
+ closedir( dir );
+ ldap_pvt_thread_mutex_unlock( &readdir_mutex );
+ locked = 0;
+
+ dir = NULL;
+ goto success;
+
+success:
+ rc = 0;
+ goto out;
+fail:
+ rc = 1;
+ goto out;
+out:
+ if ( dir != NULL ) closedir( dir );
+ if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex );
+ if ( rc != 0 && *names != NULL ) {
+ free_name_list( *names, ctx );
+ *names = NULL;
+ }
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: %s exit %d\n",
+ dir_path, rc );
+ return rc;
+}
+
+static int
+traverse( const char *path, const traverse_cb *cb, void *ctx )
+{
+ name_list *next_name = NULL;
+ name_list_list *dir_stack = NULL;
+ name_list_list *next_dir;
+ int rc = 0;
+
+ assert( path != NULL );
+ assert( cb != NULL );
+ assert( cb->pre_func || cb->post_func );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: %s\n", path );
+
+ dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx );
+ if ( dir_stack == NULL ) goto alloc_fail;
+ dir_stack->next = NULL;
+ dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx );
+ if ( dir_stack->list == NULL ) goto alloc_fail;
+ rc = lstat( path, &dir_stack->list->st );
+ if ( rc != 0 ) {
+ report_errno( "traverse", "lstat", path );
+ goto fail;
+ }
+ dir_stack->list->next = NULL;
+ dir_stack->list->name = ber_strdup_x( path, ctx );
+ if ( dir_stack->list->name == NULL ) goto alloc_fail;
+
+ while ( dir_stack != NULL ) {
+ while ( dir_stack->list != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: top of loop with \"%s\"\n",
+ dir_stack->list->name );
+
+ if ( cb->pre_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name,
+ &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) {
+ /* push dir onto stack */
+ next_dir = dir_stack;
+ dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx );
+ if ( dir_stack == NULL ) {
+ dir_stack = next_dir;
+ goto alloc_fail;
+ }
+ dir_stack->list = NULL;
+ dir_stack->next = next_dir;
+ rc = grab_names(
+ dir_stack->next->list->name, &dir_stack->list, ctx );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: grab_names %s failed\n",
+ dir_stack->next->list->name );
+ goto fail;
+ }
+ } else {
+ /* just a file */
+ if ( cb->post_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->post_func( cb->post_private,
+ dir_stack->list->name, &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ next_name = dir_stack->list->next;
+ ber_memfree_x( dir_stack->list->name, ctx );
+ ber_memfree_x( dir_stack->list, ctx );
+ dir_stack->list = next_name;
+ }
+ }
+ /* Time to pop a directory off the stack */
+ next_dir = dir_stack->next;
+ ber_memfree_x( dir_stack, ctx );
+ dir_stack = next_dir;
+ if ( dir_stack != NULL ) {
+ if ( cb->post_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->post_func( cb->post_private, dir_stack->list->name,
+ &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ next_name = dir_stack->list->next;
+ ber_memfree_x( dir_stack->list->name, ctx );
+ ber_memfree_x( dir_stack->list, ctx );
+ dir_stack->list = next_name;
+ }
+ }
+
+ goto success;
+
+cb_done:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: cb signaled completion\n" );
+success:
+ rc = 0;
+ goto out;
+
+cb_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: cb signaled failure\n" );
+ goto fail;
+alloc_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: allocation failed\n" );
+fail:
+ rc = 1;
+ goto out;
+
+out:
+ while ( dir_stack != NULL ) {
+ free_name_list( dir_stack->list, ctx );
+ next_dir = dir_stack->next;
+ ber_memfree_x( dir_stack, ctx );
+ dir_stack = next_dir;
+ }
+ return rc;
+}
+
+static traverse_cb_ret
+traverse_copy_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ copy_private *cp = private;
+ char *dest_name = NULL;
+ int source_name_len;
+ int dest_name_len;
+ int rc;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: %s entering\n",
+ name );
+
+ assert( cp->source_prefix_len >= 0 );
+ assert( cp->dest_prefix != NULL );
+ assert( cp->dest_prefix_len > 1 );
+
+ source_name_len = strlen( name );
+ assert( source_name_len >= cp->source_prefix_len );
+ /* +1 for terminator */
+ dest_name_len =
+ source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1;
+ dest_name = ber_memalloc_x( dest_name_len, ctx );
+ if ( dest_name == NULL ) goto alloc_fail;
+
+ snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix,
+ name + cp->source_prefix_len );
+
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFDIR:
+ rc = mkdir( dest_name, st->st_mode & 06775 );
+ if ( rc ) {
+ int save_errno = errno;
+ switch ( save_errno ) {
+ case EEXIST:
+ /* directory already present; nothing to do */
+ goto exists;
+ break;
+ case ENOENT:
+ /* FIXME: should mkdir -p here */
+ /* fallthrough for now */
+ default:
+ report_errno( "traverse_copy_pre", "mkdir", dest_name );
+ goto fail;
+ }
+ }
+ rc = lchown( dest_name, cp->uidn, cp->gidn );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "lchown", dest_name );
+ goto fail;
+ }
+ rc = chmod( dest_name, st->st_mode & 07777 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "chmod", dest_name );
+ goto fail;
+ }
+ break;
+ case S_IFREG:
+ rc = copy_file(
+ dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 );
+ if ( rc ) goto fail;
+ break;
+ case S_IFIFO:
+ rc = mkfifo( dest_name, 0700 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "mkfifo", dest_name );
+ goto fail;
+ }
+ rc = lchown( dest_name, cp->uidn, cp->gidn );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "lchown", dest_name );
+ goto fail;
+ }
+ rc = chmod( dest_name, st->st_mode & 07777 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "chmod", dest_name );
+ goto fail;
+ }
+ break;
+ case S_IFLNK:
+ rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx );
+ if ( rc ) goto fail;
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: skipping special: %s\n",
+ name );
+ }
+
+ goto success;
+
+alloc_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse_copy_pre: allocation failed\n" );
+fail:
+ rc = TRAVERSE_CB_FAIL;
+ goto out;
+
+exists:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: \"%s\" already exists,"
+ " skipping the rest\n",
+ dest_name );
+ rc = TRAVERSE_CB_DONE;
+ goto out;
+
+success:
+ rc = TRAVERSE_CB_CONTINUE;
+out:
+ if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: exit %d\n", rc );
+ return rc;
+}
+
+static int
+copy_tree(
+ const char *dest_path,
+ const char *source_path,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ traverse_cb cb;
+ copy_private cp;
+ int rc;
+
+ assert( dest_path != NULL );
+ assert( source_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_tree: %s to %s entering\n",
+ source_path, dest_path );
+
+ cb.pre_func = traverse_copy_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &cp;
+ cb.post_private = NULL;
+
+ cp.source_prefix_len = strlen( source_path );
+ cp.dest_prefix = dest_path;
+ cp.dest_prefix_len = strlen( dest_path );
+ cp.uidn = uidn;
+ cp.gidn = gidn;
+
+ if ( cp.source_prefix_len <= cp.dest_prefix_len &&
+ strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 &&
+ ( cp.source_prefix_len == cp.dest_prefix_len ||
+ dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "copy_tree: aborting: %s contains %s\n",
+ source_path, dest_path );
+ return 1;
+ }
+
+ rc = traverse( source_path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_tree: %s exit %d\n", source_path,
+ rc );
+
+ return rc;
+}
+
+static int
+homedir_provision(
+ const char *dest_path,
+ const char *skel_path,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ int rc;
+
+ assert( dest_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s from skeleton %s\n",
+ dest_path, skel_path == NULL ? "(none)" : skel_path );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s uidn %ld gidn %ld\n",
+ dest_path, (long)uidn, (long)gidn );
+
+ if ( skel_path == NULL ) {
+ rc = mkdir( dest_path, 0700 );
+ if ( rc ) {
+ int save_errno = errno;
+ switch ( save_errno ) {
+ case EEXIST:
+ /* directory already present; nothing to do */
+ /* but down chown either */
+ rc = 0;
+ goto out;
+ break;
+ default:
+ report_errno( "provision_homedir", "mkdir", dest_path );
+ goto fail;
+ }
+ }
+ rc = lchown( dest_path, uidn, gidn );
+ if ( rc ) {
+ report_errno( "provision_homedir", "lchown", dest_path );
+ goto fail;
+ }
+
+ } else {
+ rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx );
+ }
+
+ goto out;
+
+fail:
+ rc = 1;
+ goto out;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s to %s exit %d\n",
+ skel_path, dest_path, rc );
+ return rc;
+}
+
+/* traverse func for rm -rf */
+static traverse_cb_ret
+traverse_remove_post(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s entering\n",
+ name );
+
+ if ( (st->st_mode & S_IFMT) == S_IFDIR ) {
+ rc = rmdir( name );
+ if ( rc != 0 ) {
+ report_errno( "traverse_remove_post", "rmdir", name );
+ goto fail;
+ }
+ } else {
+ rc = unlink( name );
+ if ( rc != 0 ) {
+ report_errno( "traverse_remove_post", "unlink", name );
+ goto fail;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s exit continue\n",
+ name );
+ return TRAVERSE_CB_CONTINUE;
+
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s exit failure\n",
+ name );
+ return TRAVERSE_CB_FAIL;
+}
+
+static int
+delete_tree( const char *path, void *ctx )
+{
+ const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL };
+ int rc;
+
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "delete_tree: %s entering\n", path );
+
+ rc = traverse( path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "delete_tree: %s exit %d\n", path, rc );
+
+ return rc;
+}
+
+static int
+get_tar_name(
+ const char *path,
+ const char *tar_path,
+ char *tar_name,
+ int name_size )
+{
+ int rc = 0;
+ const char *ch;
+ int fd = -1;
+ int counter = 0;
+ time_t now;
+
+ assert( path != NULL );
+ assert( tar_path != NULL );
+ assert( tar_name != NULL );
+
+ for ( ch = path + strlen( path );
+ *ch != LDAP_DIRSEP[0] && ch > path;
+ --ch )
+ ;
+ if ( ch <= path || strlen( ch ) < 2 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "get_tar_name: unable to construct a tar name from input "
+ "path \"%s\"\n",
+ path );
+ goto fail;
+ }
+ ++ch; /* skip past sep */
+ time( &now );
+
+ while ( fd < 0 ) {
+ snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar",
+ tar_path, ch, (long)now, counter );
+ fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 );
+ if ( fd < 0 ) {
+ int save_errno = errno;
+ if ( save_errno != EEXIST ) {
+ report_errno( "get_tar_name", "open", tar_name );
+ goto fail;
+ }
+ ++counter;
+ }
+ }
+
+ rc = 0;
+ goto out;
+
+fail:
+ rc = 1;
+ *tar_name = '\0';
+out:
+ if ( fd >= 0 ) close( fd );
+ return rc;
+}
+
+/* traverse func for rechown */
+static traverse_cb_ret
+traverse_chown_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+ chown_private *cp = private;
+ uid_t set_uidn = -1;
+ gid_t set_gidn = -1;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s entering\n",
+ name );
+
+ if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn;
+ if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn;
+
+ if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) {
+ rc = lchown( name, set_uidn, set_gidn );
+ if ( rc ) {
+ report_errno( "traverse_chown_pre", "lchown", name );
+ goto fail;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s exit continue\n",
+ name );
+ return TRAVERSE_CB_CONTINUE;
+
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s exit failure\n",
+ name );
+ return TRAVERSE_CB_FAIL;
+}
+
+static int
+chown_tree(
+ const char *path,
+ uid_t old_uidn,
+ uid_t new_uidn,
+ gid_t old_gidn,
+ gid_t new_gidn,
+ void *ctx )
+{
+ traverse_cb cb;
+ chown_private cp;
+ int rc;
+
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "chown_tree: %s entering\n", path );
+
+ cb.pre_func = traverse_chown_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &cp;
+ cb.post_private = NULL;
+
+ cp.old_uidn = old_uidn;
+ cp.new_uidn = new_uidn;
+ cp.old_gidn = old_gidn;
+ cp.new_gidn = new_gidn;
+
+ rc = traverse( path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "chown_tree: %s exit %d\n", path, rc );
+
+ return rc;
+}
+
+static int
+homedir_rename( const char *source_path, const char *dest_path )
+{
+ int rc = 0;
+
+ assert( source_path != NULL );
+ assert( dest_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_rename: %s to %s\n",
+ source_path, dest_path );
+ rc = rename( source_path, dest_path );
+ if ( rc != 0 ) {
+ char ebuf[1024];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_rename: rename(\"%s\", \"%s\"): (%s)\n",
+ source_path, dest_path,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_rename: %s to %s exit %d\n",
+ source_path, dest_path, rc );
+ return rc;
+}
+
+/* FIXME: This assumes ASCII; needs fixing for z/OS */
+static int
+tar_set_header( ustar_header *tar, const struct stat *st, const char *name )
+{
+ int name_len;
+ int rc;
+ const char *ch, *end;
+
+ assert( tar != NULL );
+ assert( st != NULL );
+ assert( name != NULL );
+ assert( sizeof(*tar) == 512 );
+ assert( sizeof(tar->name) == 100 );
+ assert( sizeof(tar->prefix) == 155 );
+ assert( sizeof(tar->checksum) == 8 );
+
+ memset( tar, 0, sizeof(*tar) );
+
+ assert( name[0] == LDAP_DIRSEP[0] );
+ name += 1; /* skip leading / */
+
+ name_len = strlen( name );
+
+ /* fits in tar->name? */
+ /* Yes, name and prefix do not need a trailing nul. */
+ if ( name_len <= 100 ) {
+ strncpy( tar->name, name, 100 );
+
+ /* try fit in tar->name + tar->prefix */
+ } else {
+ /* try to find something to stick into tar->name */
+ for ( ch = name + name_len - 100, end = name + name_len;
+ ch < end && *ch != LDAP_DIRSEP[0];
+ ++ch )
+ ;
+ if ( end - ch > 0 ) /* +1 skip past sep */
+ ch++;
+ else {
+ /* reset; name too long for UStar */
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "tar_set_header: name too long: \"%s\"\n",
+ name );
+ ch = name + name_len - 100;
+ }
+ strncpy( tar->name, ch + 1, 100 );
+ {
+ int prefix_len = ( ch - 1 ) - name;
+ if ( prefix_len > 155 ) prefix_len = 155;
+ strncpy( tar->prefix, name, prefix_len );
+ }
+ }
+
+ snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 );
+ snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid );
+ snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid );
+ snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime );
+ snprintf( tar->size, 12, "%010lo ", (long)0 );
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFREG:
+ tar->typeflag[0] = '0';
+ snprintf( tar->size, 12, "%010lo ", (long)st->st_size );
+ break;
+ case S_IFLNK:
+ tar->typeflag[0] = '2';
+ rc = readlink( name - 1, tar->linkname, 99 );
+ if ( rc == -1 ) {
+ report_errno( "tar_set_header", "readlink", name );
+ goto fail;
+ }
+ break;
+ case S_IFCHR:
+ tar->typeflag[0] = '3';
+ /* FIXME: this is probably wrong but shouldn't likely be an issue */
+ snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
+ snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
+ break;
+ case S_IFBLK:
+ tar->typeflag[0] = '4';
+ /* FIXME: this is probably wrong but shouldn't likely be an issue */
+ snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
+ snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
+ break;
+ case S_IFDIR:
+ tar->typeflag[0] = '5';
+ break;
+ case S_IFIFO:
+ tar->typeflag[0] = '6';
+ break;
+ default:
+ goto fail;
+ }
+ snprintf( tar->magic, 6, "ustar" );
+ tar->version[0] = '0';
+ tar->version[1] = '0';
+
+ {
+ unsigned char *uch = (unsigned char *)tar;
+ unsigned char *uend = uch + 512;
+ unsigned long sum = 0;
+
+ memset( &tar->checksum, ' ', sizeof(tar->checksum) );
+
+ for ( ; uch < uend; ++uch )
+ sum += *uch;
+
+ /* zero-padded, six octal digits, followed by NUL then space (!) */
+ /* Yes, that's terminated exactly reverse of the others. */
+ snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum );
+ }
+
+ return 0;
+fail:
+ return 1;
+}
+
+static traverse_cb_ret
+traverse_tar_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+ traverse_cb_ret cbrc;
+ tar_private *tp = private;
+ ustar_header tar;
+ FILE *source = NULL;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s entering\n", name );
+
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFREG:
+ if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s is larger than 8GiB POSIX UStar "
+ "file size limit\n",
+ name );
+ goto fail;
+ }
+ /* fallthrough */
+ case S_IFDIR:
+ case S_IFLNK:
+ case S_IFIFO:
+ case S_IFCHR:
+ case S_IFBLK:
+ rc = tar_set_header( &tar, st, name );
+ if ( rc ) goto fail;
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: skipping \"%s\" mode %o\n",
+ name, st->st_mode );
+ goto done;
+ }
+
+ rc = fwrite( &tar, 1, 512, tp->file );
+ if ( rc != 512 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: write error in tar header\n" );
+ goto fail;
+ }
+
+ if ( (st->st_mode & S_IFMT) == S_IFREG ) {
+ source = fopen( name, "rb" );
+ if ( source == NULL ) {
+ report_errno( "traverse_tar_pre", "fopen", name );
+ goto fail;
+ }
+ rc = copy_blocks( source, tp->file, name, tp->name );
+ if ( rc != 0 ) goto fail;
+ fclose( source );
+ source = NULL;
+ }
+
+ { /* advance to end of record */
+ off_t pos = ftello( tp->file );
+ if ( pos == -1 ) {
+ report_errno( "traverse_tar_pre", "ftello", tp->name );
+ goto fail;
+ }
+ pos += ( 512 - ( pos % 512 ) ) % 512;
+ rc = fseeko( tp->file, pos, SEEK_SET );
+ if ( rc != 0 ) {
+ report_errno( "traverse_tar_pre", "fseeko", tp->name );
+ goto fail;
+ }
+ }
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s exit continue\n",
+ name );
+ cbrc = TRAVERSE_CB_CONTINUE;
+ goto out;
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s exit failure\n",
+ name );
+ cbrc = TRAVERSE_CB_FAIL;
+
+out:
+ if ( source != NULL ) fclose( source );
+ return cbrc;
+}
+
+static int
+tar_tree( const char *path, const char *tar_name, void *ctx )
+{
+ traverse_cb cb;
+ tar_private tp;
+ int rc;
+
+ assert( path != NULL );
+ assert( tar_name != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "tar_tree: %s into %s entering\n", path,
+ tar_name );
+
+ cb.pre_func = traverse_tar_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &tp;
+ cb.post_private = NULL;
+
+ tp.name = tar_name;
+ tp.file = fopen( tar_name, "wb" );
+ if ( tp.file == NULL ) {
+ report_errno( "tar_tree", "fopen", tar_name );
+ goto fail;
+ }
+
+ rc = traverse( path, &cb, ctx );
+ if ( rc != 0 ) goto fail;
+
+ {
+ off_t pos = ftello( tp.file );
+ if ( pos == -1 ) {
+ report_errno( "tar_tree", "ftello", tp.name );
+ goto fail;
+ }
+ pos += 1024; /* two zero records */
+ pos += ( 10240 - ( pos % 10240 ) ) % 10240;
+ rc = ftruncate( fileno( tp.file ), pos );
+ if ( rc != 0 ) {
+ report_errno( "tar_tree", "ftrunctate", tp.name );
+ goto fail;
+ }
+ }
+
+ rc = fclose( tp.file );
+ tp.file = NULL;
+ if ( rc != 0 ) {
+ report_errno( "tar_tree", "fclose", tp.name );
+ goto fail;
+ }
+ goto out;
+
+fail:
+ rc = 1;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "tar_tree: %s exit %d\n", path, rc );
+ if ( tp.file != NULL ) fclose( tp.file );
+ return rc;
+}
+
+static int
+homedir_deprovision( const homedir_data *data, const char *path, void *ctx )
+{
+ int rc = 0;
+ char tar_name[1024];
+
+ assert( data != NULL );
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: %s entering\n",
+ path );
+
+ switch ( data->style ) {
+ case DEL_IGNORE:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: style is ignore\n" );
+ break;
+ case DEL_ARCHIVE:
+ if ( data->archive_path == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_deprovision: archive path not set\n" );
+ goto fail;
+ }
+ rc = get_tar_name( path, data->archive_path, tar_name, 1024 );
+ if ( rc != 0 ) goto fail;
+ rc = tar_tree( path, tar_name, ctx );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_deprovision: archive failed, not deleting\n" );
+ goto fail;
+ }
+ /* fall-through */
+ case DEL_DELETE:
+ rc = delete_tree( path, ctx );
+ break;
+ default:
+ abort();
+ }
+
+ rc = 0;
+ goto out;
+
+fail:
+ rc = 1;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: %s leaving\n",
+ path );
+
+ return rc;
+}
+
+/* FIXME: This assumes ASCII; needs fixing for z/OS */
+/* FIXME: This should also be in a slapd library function somewhere */
+#define MAX_MATCHES ( 10 )
+static int
+homedir_match(
+ const homedir_regexp *r,
+ const char *homedir,
+ char *result,
+ size_t result_size )
+{
+ int rc;
+ int n;
+ regmatch_t matches[MAX_MATCHES];
+ char *resc, *repc;
+
+ assert( r != NULL );
+ assert( homedir != NULL );
+ assert( result_size > 1 );
+
+ memset( matches, 0, sizeof(matches) );
+ rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 );
+ if ( rc ) {
+ if ( rc != REG_NOMATCH ) {
+ char msg[256];
+ regerror( rc, &r->compiled, msg, sizeof(msg) );
+ Debug( LDAP_DEBUG_ANY, "homedir_match: "
+ "%s\n", msg );
+ }
+ return rc;
+ }
+
+ for ( resc = result, repc = r->replace;
+ result_size > 1 && *repc != '\0';
+ ++repc, ++resc, --result_size ) {
+ switch ( *repc ) {
+ case '$':
+ ++repc;
+ n = ( *repc ) - '0';
+ if ( n < 0 || n > ( MAX_MATCHES - 1 ) ||
+ matches[n].rm_so < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "invalid regex term expansion in \"%s\" "
+ "at char %ld, n is %d\n",
+ r->replace, (long)( repc - r->replace ), n );
+ return 1;
+ }
+ {
+ size_t match_len = matches[n].rm_eo - matches[n].rm_so;
+ const char *match_start = homedir + matches[n].rm_so;
+ if ( match_len >= result_size ) goto too_long;
+
+ memcpy( resc, match_start, match_len );
+ result_size -= match_len;
+ resc += match_len - 1;
+ }
+ break;
+
+ case '\\':
+ ++repc;
+ /* fallthrough */
+
+ default:
+ *resc = *repc;
+ }
+ }
+ *resc = '\0';
+ if ( *repc != '\0' ) goto too_long;
+
+ return 0;
+
+too_long:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "regex expansion of %s too long\n",
+ r->replace );
+ *result = '\0';
+ return 1;
+}
+
+/* Sift through an entry for interesting values
+ * return 0 on success and set vars
+ * return 1 if homedir is not present or not valid
+ * sets presence if any homedir attributes are noticed
+ */
+static int
+harvest_values(
+ const homedir_data *data,
+ const Entry *e,
+ char *home_buf,
+ int home_buf_size,
+ uid_t *uidn,
+ gid_t *gidn,
+ int *presence )
+{
+ Attribute *a;
+ char *homedir = NULL;
+
+ assert( data != NULL );
+ assert( e != NULL );
+ assert( home_buf != NULL );
+ assert( home_buf_size > 1 );
+ assert( uidn != NULL );
+ assert( gidn != NULL );
+ assert( presence != NULL );
+
+ *presence = 0;
+ if ( e == NULL ) return 1;
+ *uidn = 0;
+ *gidn = 0;
+
+ for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) {
+ if ( a->a_desc == data->home_ad ) {
+ homedir = a->a_vals[0].bv_val;
+ *presence = 1;
+ } else if ( a->a_desc == data->uidn_ad ) {
+ *uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
+ *presence = 1;
+ } else if ( a->a_desc == data->gidn_ad ) {
+ *gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
+ *presence = 1;
+ }
+ }
+ if ( homedir != NULL ) {
+ homedir_regexp *r;
+
+ for ( r = data->regexps; r != NULL; r = r->next ) {
+ int rc = homedir_match( r, homedir, home_buf, home_buf_size );
+ if ( rc == 0 ) return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+homedir_mod_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = NULL;
+ slap_callback **cbp = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: entering\n" );
+
+ for ( cbp = &op->o_callback;
+ *cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup;
+ cbp = &(*cbp)->sc_next )
+ ;
+
+ if ( *cbp == NULL ) goto out;
+ cb = *cbp;
+
+ cb_data = (homedir_cb_data *)cb->sc_private;
+ e = cb_data->entry;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: found <%s>\n",
+ e->e_nname.bv_val );
+ entry_free( e );
+ op->o_tmpfree( cb_data, op->o_tmpmemctx );
+ *cbp = cb->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+
+out:
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: leaving\n" );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_mod_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = NULL;
+ homedir_data *data = NULL;
+ slap_callback *cb = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+ int rc = SLAP_CB_CONTINUE;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: entering\n" );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: op was not successful\n" );
+ goto out;
+ }
+
+ /* Retrieve stashed entry */
+ for ( cb = op->o_callback;
+ cb != NULL && cb->sc_cleanup != homedir_mod_cleanup;
+ cb = cb->sc_next )
+ ;
+ if ( cb == NULL ) goto out;
+ cb_data = (homedir_cb_data *)cb->sc_private;
+ e = cb_data->entry;
+ on = cb_data->on;
+ data = on->on_bi.bi_private;
+ assert( e != NULL );
+ assert( data != NULL );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: found <%s>\n",
+ e->e_nname.bv_val );
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_DELETE: {
+ char home_buf[1024];
+ uid_t uidn = 0;
+ gid_t gidn = 0;
+ int presence;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: successful delete found\n" );
+ rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn,
+ &gidn, &presence );
+ if ( rc == 0 && uidn >= data->min_uid ) {
+ homedir_deprovision( data, home_buf, op->o_tmpmemctx );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: skipping\n" );
+ }
+ rc = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN: {
+ Operation nop = *op;
+ Entry *old_entry = e;
+ Entry *new_entry = NULL;
+ Entry *etmp;
+ char old_home[1024];
+ char new_home[1024];
+ uid_t old_uidn, new_uidn;
+ uid_t old_gidn, new_gidn;
+ int old_valid = 0;
+ int new_valid = 0;
+ int old_presence, new_presence;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: successful modify/modrdn found\n" );
+
+ /* retrieve the revised entry */
+ nop.o_bd = on->on_info->oi_origdb;
+ rc = overlay_entry_get_ov(
+ &nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on );
+ if ( etmp != NULL ) {
+ new_entry = entry_dup( etmp );
+ overlay_entry_release_ov( &nop, etmp, 0, on );
+ }
+ if ( rc || new_entry == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: unable to get revised <%s>\n",
+ op->o_req_ndn.bv_val );
+ if ( new_entry != NULL ) {
+ entry_free( new_entry );
+ new_entry = NULL;
+ }
+ }
+
+ /* analyze old and new */
+ rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn,
+ &old_gidn, &old_presence );
+ if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1;
+ if ( new_entry != NULL ) {
+ rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn,
+ &new_gidn, &new_presence );
+ if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1;
+ entry_free( new_entry );
+ new_entry = NULL;
+ }
+
+ if ( new_valid && !old_valid ) { /* like an add */
+ if ( old_presence )
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: old entry is now valid\n" );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like an add\n" );
+ homedir_provision( new_home, data->skeleton_path, new_uidn,
+ new_gidn, op->o_tmpmemctx );
+
+ } else if ( old_valid && !new_valid &&
+ !new_presence ) { /* like a del */
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like a del\n" );
+ homedir_deprovision( data, old_home, op->o_tmpmemctx );
+
+ } else if ( new_valid && old_valid ) { /* change */
+ int did_something = 0;
+
+ if ( strcmp( old_home, new_home ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like a rename\n" );
+ homedir_rename( old_home, new_home );
+ did_something = 1;
+ }
+ if ( old_uidn != new_uidn || old_gidn != new_gidn ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: rechowning\n" );
+ chown_tree( new_home, old_uidn, new_uidn, old_gidn,
+ new_gidn, op->o_tmpmemctx );
+ did_something = 1;
+ }
+ if ( !did_something ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: nothing to do\n" );
+ }
+ } else if ( old_presence || new_presence ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: <%s> values present "
+ "but invalid; ignoring\n",
+ op->o_req_ndn.bv_val );
+ }
+ rc = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ default:
+ rc = SLAP_CB_CONTINUE;
+ }
+
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: leaving\n" );
+ return rc;
+}
+
+static int
+homedir_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *cb = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+ Entry *se = NULL;
+ Operation nop = *op;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_op_mod: entering\n" );
+
+ /* retrieve the entry */
+ nop.o_bd = on->on_info->oi_origdb;
+ rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on );
+ if ( e != NULL ) {
+ se = entry_dup( e );
+ overlay_entry_release_ov( &nop, e, 0, on );
+ e = se;
+ }
+ if ( rc || e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_op_mod: unable to get <%s>\n",
+ op->o_req_ndn.bv_val );
+ goto out;
+ }
+
+ /* Allocate the callback to hold the entry */
+ cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx );
+ cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx );
+ cb->sc_cleanup = homedir_mod_cleanup;
+ cb->sc_response = homedir_mod_response;
+ cb->sc_private = cb_data;
+ cb_data->entry = e;
+ e = NULL;
+ cb_data->on = on;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+out:
+ if ( e != NULL ) entry_free( e );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_op_mod: leaving\n" );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ homedir_data *data = on->on_bi.bi_private;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_response: entering\n" );
+ if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE;
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD: { /* Check for new homedir */
+ char home_buf[1024];
+ uid_t uidn = 0;
+ gid_t gidn = 0;
+ int rc, presence;
+
+ rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf),
+ &uidn, &gidn, &presence );
+ if ( rc == 0 && uidn >= data->min_uid ) {
+ homedir_provision( home_buf, data->skeleton_path, uidn, gidn,
+ op->o_tmpmemctx );
+ }
+ return SLAP_CB_CONTINUE;
+ }
+
+ default:
+ return SLAP_CB_CONTINUE;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ homedir_data *data = ch_calloc( 1, sizeof(homedir_data) );
+ const char *text;
+
+ if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) ||
+ slap_str2ad( "uidNumber", &data->uidn_ad, &text ) ||
+ slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "nis schema not available\n" );
+ return 1;
+ }
+
+ data->skeleton_path = strdup( DEFAULT_SKEL );
+ data->min_uid = DEFAULT_MIN_UID;
+ data->archive_path = NULL;
+
+ on->on_bi.bi_private = data;
+ return 0;
+}
+
+static int
+homedir_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ homedir_data *data = on->on_bi.bi_private;
+ homedir_regexp *r, *rnext;
+
+ if ( data != NULL ) {
+ for ( r = data->regexps; r != NULL; r = rnext ) {
+ rnext = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+ }
+ data->regexps = NULL;
+ if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path );
+ if ( data->archive_path != NULL ) ch_free( data->archive_path );
+ ch_free( data );
+ }
+
+ return 0;
+}
+
+int
+homedir_initialize()
+{
+ int rc;
+
+ assert( ' ' == 32 ); /* Lots of ASCII requirements for now */
+
+ memset( &homedir, 0, sizeof(homedir) );
+
+ homedir.on_bi.bi_type = "homedir";
+ homedir.on_bi.bi_db_init = homedir_db_init;
+ homedir.on_bi.bi_db_destroy = homedir_db_destroy;
+ homedir.on_bi.bi_op_delete = homedir_op_mod;
+ homedir.on_bi.bi_op_modify = homedir_op_mod;
+ homedir.on_response = homedir_response;
+
+ homedir.on_bi.bi_cf_ocs = homedirocs;
+ rc = config_register_schema( homedircfg, homedirocs );
+ if ( rc ) return rc;
+
+ ldap_pvt_thread_mutex_init( &readdir_mutex );
+
+ return overlay_register( &homedir );
+}
+
+int
+homedir_terminate()
+{
+ ldap_pvt_thread_mutex_destroy( &readdir_mutex );
+ return 0;
+}
+
+#if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC)
+int
+init_module( int argc, char *argv[] )
+{
+ return homedir_initialize();
+}
+
+int
+term_module()
+{
+ return homedir_terminate();
+}
+#endif
+
+#endif /* SLAPD_OVER_HOMEDIR */
diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c
new file mode 100644
index 0000000..5affbbf
--- /dev/null
+++ b/servers/slapd/overlays/memberof.c
@@ -0,0 +1,2185 @@
+/* memberof.c - back-reference for group membership */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.r.l.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_MEMBEROF
+
+#include <stdio.h>
+
+#include "ac/string.h"
+#include "ac/socket.h"
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+
+/*
+ * Glossary:
+ *
+ * GROUP a group object (an entry with GROUP_OC
+ * objectClass)
+ * MEMBER a member object (an entry whose DN is
+ * listed as MEMBER_AT value of a GROUP)
+ * GROUP_OC the objectClass of the group object
+ * (default: groupOfNames)
+ * MEMBER_AT the membership attribute, DN-valued;
+ * note: nameAndOptionalUID is tolerated
+ * as soon as the optionalUID is absent
+ * (default: member)
+ * MEMBER_OF reverse membership attribute
+ * (default: memberOf)
+ *
+ * - add:
+ * - if the entry that is being added is a GROUP,
+ * the MEMBER_AT defined as values of the add operation
+ * get the MEMBER_OF value directly from the request.
+ *
+ * if configured to do so, the MEMBER objects do not exist,
+ * and no relax control is issued, either:
+ * - fail
+ * - drop non-existing members
+ * (by default: don't muck with values)
+ *
+ * - if (configured to do so,) the referenced GROUP exists,
+ * the relax control is set and the user has
+ * "manage" privileges, allow to add MEMBER_OF values to
+ * generic entries.
+ *
+ * - modify:
+ * - if the entry being modified is a GROUP_OC and the
+ * MEMBER_AT attribute is modified, the MEMBER_OF value
+ * of the (existing) MEMBER_AT entries that are affected
+ * is modified according to the request:
+ * - if a MEMBER is removed from the group,
+ * delete the corresponding MEMBER_OF
+ * - if a MEMBER is added to a group,
+ * add the corresponding MEMBER_OF
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP_OC, and we need to check, from the
+ * modification list, if the MEMBER_AT attribute is being
+ * affected, and what MEMBER_AT values are affected.
+ *
+ * if configured to do so, the entries corresponding to
+ * the MEMBER_AT values do not exist, and no relax control
+ * is issued, either:
+ * - fail
+ * - drop non-existing members
+ * (by default: don't muck with values)
+ *
+ * - if configured to do so, the referenced GROUP exists,
+ * (the relax control is set) and the user has
+ * "manage" privileges, allow to add MEMBER_OF values to
+ * generic entries; the change is NOT automatically reflected
+ * in the MEMBER attribute of the GROUP referenced
+ * by the value of MEMBER_OF; a separate modification,
+ * with or without relax control, needs to be performed.
+ *
+ * - modrdn:
+ * - if the entry being renamed is a GROUP, the MEMBER_OF
+ * value of the (existing) MEMBER objects is modified
+ * accordingly based on the newDN of the GROUP.
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP; the list of MEMBER objects is obtained from
+ * the database.
+ *
+ * Non-existing MEMBER objects are ignored, since the
+ * MEMBER_AT is not being addressed by the operation.
+ *
+ * - if the entry being renamed has the MEMBER_OF attribute,
+ * the corresponding MEMBER value must be modified in the
+ * respective group entries.
+ *
+ *
+ * - delete:
+ * - if the entry being deleted is a GROUP, the (existing)
+ * MEMBER objects are modified accordingly; a copy of the
+ * values of the MEMBER_AT is saved and, if the delete
+ * succeeds, the MEMBER_OF value of the (existing) MEMBER
+ * objects is deleted.
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP.
+ *
+ * Non-existing MEMBER objects are ignored, since the entry
+ * is being deleted.
+ *
+ * - if the entry being deleted has the MEMBER_OF attribute,
+ * the corresponding value of the MEMBER_AT must be deleted
+ * from the respective GROUP entries.
+ */
+
+#define SLAPD_MEMBEROF_ATTR "memberOf"
+
+static AttributeDescription *ad_member;
+static AttributeDescription *ad_memberOf;
+
+static ObjectClass *oc_group;
+
+static slap_overinst memberof;
+
+typedef struct memberof_t {
+ struct berval mo_dn;
+ struct berval mo_ndn;
+
+ ObjectClass *mo_oc_group;
+ AttributeDescription *mo_ad_member;
+ AttributeDescription *mo_ad_memberof;
+
+ struct berval mo_groupFilterstr;
+ AttributeAssertion mo_groupAVA;
+ Filter mo_groupFilter;
+
+ struct berval mo_memberFilterstr;
+ Filter mo_memberFilter;
+
+ unsigned mo_flags;
+#define MEMBEROF_NONE 0x00U
+#define MEMBEROF_FDANGLING_DROP 0x01U
+#define MEMBEROF_FDANGLING_ERROR 0x02U
+#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR)
+#define MEMBEROF_FREFINT 0x04U
+#define MEMBEROF_FREVERSE 0x08U
+
+ ber_int_t mo_dangling_err;
+
+#define MEMBEROF_CHK(mo,f) \
+ (((mo)->mo_flags & (f)) == (f))
+#define MEMBEROF_DANGLING_CHECK(mo) \
+ ((mo)->mo_flags & MEMBEROF_FDANGLING_MASK)
+#define MEMBEROF_DANGLING_DROP(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP)
+#define MEMBEROF_DANGLING_ERROR(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR)
+#define MEMBEROF_REFINT(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FREFINT)
+#define MEMBEROF_REVERSE(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FREVERSE)
+} memberof_t;
+
+typedef enum memberof_is_t {
+ MEMBEROF_IS_NONE = 0x00,
+ MEMBEROF_IS_GROUP = 0x01,
+ MEMBEROF_IS_MEMBER = 0x02,
+ MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER)
+} memberof_is_t;
+
+typedef struct memberof_cookie_t {
+ AttributeDescription *ad;
+ BerVarray vals;
+ int foundit;
+} memberof_cookie_t;
+
+typedef struct memberof_cbinfo_t {
+ slap_overinst *on;
+ BerVarray member;
+ BerVarray memberof;
+ memberof_is_t what;
+} memberof_cbinfo_t;
+
+static void
+memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on )
+{
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ if ( bi->bi_type == memberof.on_bi.bi_type )
+ op_target->o_bd->bd_info = (BackendInfo *)on->on_info;
+}
+
+static int
+memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ memberof_cookie_t *mc;
+
+ mc = (memberof_cookie_t *)op->o_callback->sc_private;
+ mc->foundit = 1;
+ }
+
+ return 0;
+}
+
+/*
+ * callback for internal search that saves the member attribute values
+ * of groups being deleted.
+ */
+static int
+memberof_saveMember_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ memberof_cookie_t *mc;
+ Attribute *a;
+
+ mc = (memberof_cookie_t *)op->o_callback->sc_private;
+ mc->foundit = 1;
+
+ assert( rs->sr_entry != NULL );
+ assert( rs->sr_entry->e_attrs != NULL );
+
+ a = attr_find( rs->sr_entry->e_attrs, mc->ad );
+ if ( a != NULL ) {
+ ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx );
+
+ assert( attr_find( a->a_next, mc->ad ) == NULL );
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * the delete hook performs an internal search that saves the member
+ * attribute values of groups being deleted.
+ */
+static int
+memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci )
+{
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Operation op2 = *op;
+ slap_callback cb = { 0 };
+ BackendInfo *bi = op->o_bd->bd_info;
+ AttributeName an[ 2 ];
+
+ memberof_is_t iswhat = MEMBEROF_IS_NONE;
+ memberof_cookie_t mc;
+
+ assert( mci->what != MEMBEROF_IS_NONE );
+
+ cb.sc_private = &mc;
+ if ( op->o_tag == LDAP_REQ_DELETE ) {
+ cb.sc_response = memberof_saveMember_cb;
+
+ } else {
+ cb.sc_response = memberof_isGroupOrMember_cb;
+ }
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_callback = &cb;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ BER_BVZERO( &an[ 1 ].an_name );
+ op2.ors_attrs = an;
+ op2.ors_attrsonly = 0;
+ op2.ors_limit = NULL;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+
+ if ( mci->what & MEMBEROF_IS_GROUP ) {
+ SlapReply rs2 = { REP_RESULT };
+
+ mc.ad = mo->mo_ad_member;
+ mc.foundit = 0;
+ mc.vals = NULL;
+ an[ 0 ].an_desc = mo->mo_ad_member;
+ an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+ op2.ors_filterstr = mo->mo_groupFilterstr;
+ op2.ors_filter = &mo->mo_groupFilter;
+ op2.o_do_not_cache = 1; /* internal search, don't log */
+
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_search( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+
+ if ( mc.foundit ) {
+ iswhat |= MEMBEROF_IS_GROUP;
+ if ( mc.vals ) mci->member = mc.vals;
+
+ }
+ }
+
+ if ( mci->what & MEMBEROF_IS_MEMBER ) {
+ SlapReply rs2 = { REP_RESULT };
+
+ mc.ad = mo->mo_ad_memberof;
+ mc.foundit = 0;
+ mc.vals = NULL;
+ an[ 0 ].an_desc = mo->mo_ad_memberof;
+ an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+ op2.ors_filterstr = mo->mo_memberFilterstr;
+ op2.ors_filter = &mo->mo_memberFilter;
+ op2.o_do_not_cache = 1; /* internal search, don't log */
+
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_search( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+
+ if ( mc.foundit ) {
+ iswhat |= MEMBEROF_IS_MEMBER;
+ if ( mc.vals ) mci->memberof = mc.vals;
+
+ }
+ }
+
+ mci->what = iswhat;
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * response callback that adds memberof values when a group is modified.
+ */
+static void
+memberof_value_modify(
+ Operation *op,
+ struct berval *ndn,
+ AttributeDescription *ad,
+ struct berval *old_dn,
+ struct berval *old_ndn,
+ struct berval *new_dn,
+ struct berval *new_ndn )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Operation op2 = *op;
+ unsigned long opid = op->o_opid;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ Modifications mod[ 2 ] = { { { 0 } } }, *ml;
+ struct berval values[ 4 ], nvalues[ 4 ];
+ int mcnt = 0;
+
+ if ( old_ndn != NULL && new_ndn != NULL &&
+ ber_bvcmp( old_ndn, new_ndn ) == 0 ) {
+ /* DNs compare equal, it's a noop */
+ return;
+ }
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+
+ op2.o_req_dn = *ndn;
+ op2.o_req_ndn = *ndn;
+
+ op2.o_callback = &cb;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ op2.orm_modlist = NULL;
+
+ /* Internal ops, never replicate these */
+ op2.o_opid = 0; /* shared with op, saved above */
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+
+ if ( !BER_BVISNULL( &mo->mo_ndn ) ) {
+ ml = &mod[ mcnt ];
+ ml->sml_numvals = 1;
+ ml->sml_values = &values[ 0 ];
+ ml->sml_values[ 0 ] = mo->mo_dn;
+ BER_BVZERO( &ml->sml_values[ 1 ] );
+ ml->sml_nvalues = &nvalues[ 0 ];
+ ml->sml_nvalues[ 0 ] = mo->mo_ndn;
+ BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+ ml->sml_desc = slap_schema.si_ad_modifiersName;
+ ml->sml_type = ml->sml_desc->ad_cname;
+ ml->sml_op = LDAP_MOD_REPLACE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_next = op2.orm_modlist;
+ op2.orm_modlist = ml;
+
+ mcnt++;
+ }
+
+ ml = &mod[ mcnt ];
+ ml->sml_numvals = 1;
+ ml->sml_values = &values[ 2 ];
+ BER_BVZERO( &ml->sml_values[ 1 ] );
+ ml->sml_nvalues = &nvalues[ 2 ];
+ BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+ ml->sml_desc = ad;
+ ml->sml_type = ml->sml_desc->ad_cname;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_next = op2.orm_modlist;
+ op2.orm_modlist = ml;
+
+ if ( new_ndn != NULL ) {
+ BackendInfo *bi = op2.o_bd->bd_info;
+ OpExtra oex;
+
+ assert( !BER_BVISNULL( new_dn ) );
+ assert( !BER_BVISNULL( new_ndn ) );
+
+ ml = &mod[ mcnt ];
+ ml->sml_op = LDAP_MOD_ADD;
+
+ ml->sml_values[ 0 ] = *new_dn;
+ ml->sml_nvalues[ 0 ] = *new_ndn;
+
+ oex.oe_key = (void *)&memberof;
+ LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_modify( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+ LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d\n",
+ op->o_log_prefix, op2.o_req_dn.bv_val,
+ ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err );
+ }
+
+ assert( op2.orm_modlist == &mod[ mcnt ] );
+ assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] );
+ ml = op2.orm_modlist->sml_next;
+ if ( mcnt == 1 ) {
+ assert( ml == &mod[ 0 ] );
+ ml = ml->sml_next;
+ }
+ if ( ml != NULL ) {
+ slap_mods_free( ml, 1 );
+ }
+
+ mod[ 0 ].sml_next = NULL;
+ }
+
+ if ( old_ndn != NULL ) {
+ BackendInfo *bi = op2.o_bd->bd_info;
+ OpExtra oex;
+
+ assert( !BER_BVISNULL( old_dn ) );
+ assert( !BER_BVISNULL( old_ndn ) );
+
+ ml = &mod[ mcnt ];
+ ml->sml_op = LDAP_MOD_DELETE;
+
+ ml->sml_values[ 0 ] = *old_dn;
+ ml->sml_nvalues[ 0 ] = *old_ndn;
+
+ oex.oe_key = (void *)&memberof;
+ LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_modify( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+ LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d\n",
+ op->o_log_prefix, op2.o_req_dn.bv_val,
+ ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err );
+ }
+
+ assert( op2.orm_modlist == &mod[ mcnt ] );
+ ml = op2.orm_modlist->sml_next;
+ if ( mcnt == 1 ) {
+ assert( ml == &mod[ 0 ] );
+ ml = ml->sml_next;
+ }
+ if ( ml != NULL ) {
+ slap_mods_free( ml, 1 );
+ }
+ }
+ /* restore original opid */
+ op->o_opid = opid;
+
+ /* FIXME: if old_group_ndn doesn't exist, both delete __and__
+ * add will fail; better split in two operations, although
+ * not optimal in terms of performance. At least it would
+ * move towards self-repairing capabilities. */
+}
+
+static int
+memberof_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ memberof_cbinfo_t *mci = sc->sc_private;
+
+ op->o_callback = sc->sc_next;
+ if ( mci->memberof )
+ ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx );
+ if ( mci->member )
+ ber_bvarray_free_x( mci->member, op->o_tmpmemctx );
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+static int memberof_res_add( Operation *op, SlapReply *rs );
+static int memberof_res_delete( Operation *op, SlapReply *rs );
+static int memberof_res_modify( Operation *op, SlapReply *rs );
+static int memberof_res_modrdn( Operation *op, SlapReply *rs );
+
+static int
+memberof_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Attribute **ap, **map = NULL;
+ int rc = SLAP_CB_CONTINUE;
+ int i;
+ struct berval save_dn, save_ndn;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( op->ora_e->e_attrs == NULL ) {
+ /* FIXME: global overlay; need to deal with */
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "consistency checks not implemented when overlay "
+ "is instantiated as global.\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) {
+ Attribute *a = *ap;
+
+ if ( a->a_desc == mo->mo_ad_memberof ) {
+ map = ap;
+ break;
+ }
+ }
+ }
+
+ save_dn = op->o_dn;
+ save_ndn = op->o_ndn;
+
+ if ( MEMBEROF_DANGLING_CHECK( mo )
+ && !get_relax( op )
+ && is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) )
+ {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ for ( ap = &op->ora_e->e_attrs; *ap; ) {
+ Attribute *a = *ap;
+
+ if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) {
+ ap = &a->a_next;
+ continue;
+ }
+
+ assert( a->a_nvals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e = NULL;
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+ NULL, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS ) {
+ be_entry_release_r( op, e );
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as group member";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "member=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->ora_e->e_name.bv_val,
+ a->a_vals[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+ ber_memfree( a->a_vals[ i ].bv_val );
+ BER_BVZERO( &a->a_vals[ i ] );
+ if ( a->a_nvals != a->a_vals ) {
+ ber_memfree( a->a_nvals[ i ].bv_val );
+ BER_BVZERO( &a->a_nvals[ i ] );
+ }
+ a->a_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( a->a_nvals != a->a_vals ) {
+ AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+ }
+
+ /* If all values have been removed,
+ * remove the attribute itself. */
+ if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+ *ap = a->a_next;
+ attr_free( a );
+
+ } else {
+ ap = &a->a_next;
+ }
+ }
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ if ( map != NULL ) {
+ Attribute *a = *map;
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof,
+ &a->a_nvals[ i ], ACL_WADD,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( get_relax( op ) ) {
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->ora_e->e_name.bv_val,
+ a->a_nvals[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+ ber_memfree( a->a_vals[ i ].bv_val );
+ BER_BVZERO( &a->a_vals[ i ] );
+ if ( a->a_nvals != a->a_vals ) {
+ ber_memfree( a->a_nvals[ i ].bv_val );
+ BER_BVZERO( &a->a_nvals[ i ] );
+ }
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( a->a_nvals != a->a_vals ) {
+ AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn, ACL_WADD, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+ *map = a->a_next;
+ attr_free( a );
+ }
+ }
+
+ rc = SLAP_CB_CONTINUE;
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_add;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+done:;
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ return rc;
+}
+
+static int
+memberof_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_delete;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ mci->what = MEMBEROF_IS_GROUP;
+ if ( MEMBEROF_REFINT( mo ) ) {
+ mci->what = MEMBEROF_IS_BOTH;
+ }
+
+ memberof_isGroupOrMember( op, mci );
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+memberof_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Modifications **mlp, **mmlp = NULL;
+ int rc = SLAP_CB_CONTINUE, save_member = 0;
+ struct berval save_dn, save_ndn;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci, mcis;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) {
+ Modifications *ml = *mlp;
+
+ if ( ml->sml_desc == mo->mo_ad_memberof ) {
+ mmlp = mlp;
+ break;
+ }
+ }
+ }
+
+ save_dn = op->o_dn;
+ save_ndn = op->o_ndn;
+ mcis.on = on;
+ mcis.what = MEMBEROF_IS_GROUP;
+
+ if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS
+ && ( mcis.what & MEMBEROF_IS_GROUP ) )
+ {
+ Modifications *ml;
+
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc == mo->mo_ad_member ) {
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ save_member = 1;
+ break;
+ }
+ }
+ }
+
+
+ if ( MEMBEROF_DANGLING_CHECK( mo )
+ && !get_relax( op ) )
+ {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ assert( op->orm_modlist != NULL );
+
+ for ( mlp = &op->orm_modlist; *mlp; ) {
+ Modifications *ml = *mlp;
+ int i;
+
+ if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) {
+ mlp = &ml->sml_next;
+ continue;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ /* we don't care about cancellations: if the value
+ * exists, fine; if it doesn't, we let the underlying
+ * database fail as appropriate; */
+ mlp = &ml->sml_next;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ /* Handle this just like a delete (see above) */
+ if ( !ml->sml_values ) {
+ mlp = &ml->sml_next;
+ break;
+ }
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ /* NOTE: right now, the attributeType we use
+ * for member must have a normalized value */
+ assert( ml->sml_nvalues != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
+ continue;
+
+ if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e ) == LDAP_SUCCESS )
+ {
+ be_entry_release_r( op, e );
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as group member";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "member=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ i--;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+
+ } else {
+ mlp = &ml->sml_next;
+ }
+
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+ }
+ }
+
+ if ( mmlp != NULL ) {
+ Modifications *ml = *mmlp;
+ int i;
+ Entry *target;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn,
+ NULL, NULL, 0, &target );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ if ( ml->sml_nvalues != NULL ) {
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ &ml->sml_nvalues[ i ],
+ ACL_WDEL,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( get_relax( op ) ) {
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "deleting non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ }
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn,
+ ACL_WDEL, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mmlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ NULL,
+ ACL_WDEL, NULL );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ {
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ &ml->sml_nvalues[ i ],
+ ACL_WADD,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ }
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn,
+ ACL_WDEL, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mmlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ } break;
+
+ default:
+ assert( 0 );
+ }
+
+done2:;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, target );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_modify;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ mci->what = mcis.what;
+
+ if ( save_member ) {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ mo->mo_ad_member, &mci->member, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ rc = SLAP_CB_CONTINUE;
+
+done:;
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ return rc;
+}
+
+static int
+memberof_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_modrdn;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds memberof values when a group is added.
+ */
+static int
+memberof_res_add( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ Attribute *ma;
+
+ ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof );
+ if ( ma != NULL ) {
+ /* relax is required to allow to add
+ * a non-existing member */
+ op->o_relax = SLAP_CONTROL_CRITICAL;
+
+ for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) {
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn ))
+ continue;
+
+ /* the modification is attempted
+ * with the original identity */
+ memberof_value_modify( op,
+ &ma->a_nvals[ i ], mo->mo_ad_member,
+ NULL, NULL, &op->o_req_dn, &op->o_req_ndn );
+ }
+ }
+ }
+
+ if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) {
+ Attribute *a;
+
+ for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member );
+ a != NULL;
+ a = attrs_find( a->a_next, mo->mo_ad_member ) )
+ {
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &op->o_req_ndn ))
+ continue;
+
+ memberof_value_modify( op,
+ &a->a_nvals[ i ],
+ mo->mo_ad_memberof,
+ NULL, NULL,
+ &op->o_req_dn,
+ &op->o_req_ndn );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that deletes memberof values when a group is deleted.
+ */
+static int
+memberof_res_delete( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ BerVarray vals;
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ vals = mci->member;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+
+ if ( MEMBEROF_REFINT( mo ) ) {
+ vals = mci->memberof;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes memberof values when a group
+ * is modified.
+ */
+static int
+memberof_res_modify( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i, rc;
+ Modifications *ml, *mml = NULL;
+ BerVarray vals;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc == mo->mo_ad_memberof ) {
+ mml = ml;
+ break;
+ }
+ }
+ }
+
+ if ( mml != NULL ) {
+ BerVarray vals = mml->sml_nvalues;
+
+ switch ( mml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+ /* delete all ... */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ mo->mo_ad_memberof, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ assert( vals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ NULL, NULL,
+ &op->o_req_dn, &op->o_req_ndn );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+
+ if ( mci->what & MEMBEROF_IS_GROUP )
+ {
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc != mo->mo_ad_member ) {
+ continue;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ vals = ml->sml_nvalues;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+ vals = mci->member;
+
+ /* delete all ... */
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */
+ assert( ml->sml_nvalues != NULL );
+ vals = ml->sml_nvalues;
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ NULL, NULL,
+ &op->o_req_dn, &op->o_req_ndn );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes member values when a group member
+ * is renamed.
+ */
+static int
+memberof_res_modrdn( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i, rc;
+ BerVarray vals;
+
+ struct berval save_dn, save_ndn;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ mci->what = MEMBEROF_IS_GROUP;
+ if ( MEMBEROF_REFINT( mo ) ) {
+ mci->what |= MEMBEROF_IS_MEMBER;
+ }
+
+ save_dn = op->o_req_dn;
+ save_ndn = op->o_req_ndn;
+
+ op->o_req_dn = op->orr_newDN;
+ op->o_req_ndn = op->orr_nnewDN;
+ rc = memberof_isGroupOrMember( op, mci );
+ op->o_req_dn = save_dn;
+ op->o_req_ndn = save_ndn;
+
+ if ( rc != LDAP_SUCCESS || mci->what == MEMBEROF_IS_NONE ) {
+ goto done;
+ }
+
+ if ( mci->what & MEMBEROF_IS_GROUP ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->orr_nnewDN,
+ mo->mo_ad_member, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ &op->orr_newDN, &op->orr_nnewDN );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+ if ( MEMBEROF_REFINT( mo ) && ( mci->what & MEMBEROF_IS_MEMBER ) ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->orr_nnewDN,
+ mo->mo_ad_memberof, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ &op->orr_newDN, &op->orr_nnewDN );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+done:;
+ return SLAP_CB_CONTINUE;
+}
+
+
+static int
+memberof_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo;
+ const char *text = NULL;
+ int rc;
+
+ mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) );
+
+ /* safe default */
+ mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
+
+ if ( !ad_memberOf ) {
+ rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
+ "unable to find attribute=\"%s\": %s (%d)\n",
+ SLAPD_MEMBEROF_ATTR, text, rc );
+ return rc;
+ }
+ }
+
+ if ( !ad_member ) {
+ rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
+ "unable to find attribute=\"%s\": %s (%d)\n",
+ SLAPD_GROUP_ATTR, text, rc );
+ return rc;
+ }
+ }
+
+ if ( !oc_group ) {
+ oc_group = oc_find( SLAPD_GROUP_CLASS );
+ if ( oc_group == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "memberof_db_init: "
+ "unable to find objectClass=\"%s\"\n",
+ SLAPD_GROUP_CLASS );
+ return 1;
+ }
+ }
+
+ on->on_bi.bi_private = (void *)mo;
+
+ return 0;
+}
+
+enum {
+ MO_DN = 1,
+ MO_DANGLING,
+ MO_REFINT,
+ MO_GROUP_OC,
+ MO_MEMBER_AD,
+ MO_MEMBER_OF_AD,
+#if 0
+ MO_REVERSE,
+#endif
+
+ MO_DANGLING_ERROR,
+
+ MO_LAST
+};
+
+static ConfigDriver mo_cf_gen;
+
+#define OID "1.3.6.1.4.1.7136.2.666.4"
+#define OIDAT OID ".1.1"
+#define OIDCFGAT OID ".1.2"
+#define OIDOC OID ".2.1"
+#define OIDCFGOC OID ".2.2"
+
+
+static ConfigTable mo_cfg[] = {
+ { "memberof-dn", "modifiersName",
+ 2, 2, 0, ARG_MAGIC|ARG_QUOTE|ARG_DN|MO_DN, mo_cf_gen,
+ "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' "
+ "DESC 'DN to be used as modifiersName' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-dangling", "ignore|drop|error",
+ 2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen,
+ "( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' "
+ "DESC 'Behavior with respect to dangling members, "
+ "constrained to ignore, drop, error' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-refint", "true|FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen,
+ "( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' "
+ "DESC 'Take care of referential integrity' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-group-oc", "objectClass",
+ 2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen,
+ "( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' "
+ "DESC 'Group objectClass' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-member-ad", "member attribute",
+ 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen,
+ "( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' "
+ "DESC 'member attribute' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-memberof-ad", "memberOf attribute",
+ 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen,
+ "( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' "
+ "DESC 'memberOf attribute' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+#if 0
+ { "memberof-reverse", "true|FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen,
+ "( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' "
+ "DESC 'Take care of referential integrity "
+ "also when directly modifying memberOf' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+#endif
+
+ { "memberof-dangling-error", "error code",
+ 2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen,
+ "( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' "
+ "DESC 'Error code returned in case of dangling back reference' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs mo_ocs[] = {
+ { "( OLcfgOvOc:18.1 "
+ "NAME ( 'olcMemberOfConfig' 'olcMemberOf' ) "
+ "DESC 'Member-of configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcMemberOfDN "
+ "$ olcMemberOfDangling "
+ "$ olcMemberOfDanglingError"
+ "$ olcMemberOfRefInt "
+ "$ olcMemberOfGroupOC "
+ "$ olcMemberOfMemberAD "
+ "$ olcMemberOfMemberOfAD "
+#if 0
+ "$ olcMemberOfReverse "
+#endif
+ ") "
+ ")",
+ Cft_Overlay, mo_cfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static slap_verbmasks dangling_mode[] = {
+ { BER_BVC( "ignore" ), MEMBEROF_NONE },
+ { BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP },
+ { BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR },
+ { BER_BVNULL, 0 }
+};
+
+static int
+memberof_make_group_filter( memberof_t *mo )
+{
+ char *ptr;
+
+ if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ ch_free( mo->mo_groupFilterstr.bv_val );
+ }
+
+ mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY;
+ mo->mo_groupFilter.f_ava = &mo->mo_groupAVA;
+
+ mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass;
+ mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname;
+
+ mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" )
+ + slap_schema.si_ad_objectClass->ad_cname.bv_len
+ + mo->mo_oc_group->soc_cname.bv_len;
+ ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 );
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val );
+ *ptr++ = '=';
+ ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val );
+ *ptr++ = ')';
+ *ptr = '\0';
+
+ return 0;
+}
+
+static int
+memberof_make_member_filter( memberof_t *mo )
+{
+ char *ptr;
+
+ if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ ch_free( mo->mo_memberFilterstr.bv_val );
+ }
+
+ mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT;
+ mo->mo_memberFilter.f_desc = mo->mo_ad_memberof;
+
+ mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" )
+ + mo->mo_ad_memberof->ad_cname.bv_len;
+ ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 );
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "=*)" );
+
+ return 0;
+}
+
+static int
+mo_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i, rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch( c->type ) {
+ case MO_DN:
+ if ( mo->mo_dn.bv_val != NULL) {
+ value_add_one( &c->rvalue_vals, &mo->mo_dn );
+ value_add_one( &c->rvalue_nvals, &mo->mo_ndn );
+ }
+ break;
+
+ case MO_DANGLING:
+ enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case MO_DANGLING_ERROR:
+ if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ bv.bv_val = buf;
+ } else {
+ rc = 1;
+ break;
+ }
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case MO_REFINT:
+ c->value_int = MEMBEROF_REFINT( mo );
+ break;
+
+#if 0
+ case MO_REVERSE:
+ c->value_int = MEMBEROF_REVERSE( mo );
+ break;
+#endif
+
+ case MO_GROUP_OC:
+ if ( mo->mo_oc_group != NULL ){
+ value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname );
+ }
+ break;
+
+ case MO_MEMBER_AD:
+ c->value_ad = mo->mo_ad_member;
+ break;
+
+ case MO_MEMBER_OF_AD:
+ c->value_ad = mo->mo_ad_memberof;
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case MO_DN:
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ BER_BVZERO( &mo->mo_dn );
+ BER_BVZERO( &mo->mo_ndn );
+ }
+ break;
+
+ case MO_DANGLING:
+ mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
+ break;
+
+ case MO_DANGLING_ERROR:
+ mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
+ break;
+
+ case MO_REFINT:
+ mo->mo_flags &= ~MEMBEROF_FREFINT;
+ break;
+
+#if 0
+ case MO_REVERSE:
+ mo->mo_flags &= ~MEMBEROF_FREVERSE;
+ break;
+#endif
+
+ case MO_GROUP_OC:
+ mo->mo_oc_group = oc_group;
+ memberof_make_group_filter( mo );
+ break;
+
+ case MO_MEMBER_AD:
+ mo->mo_ad_member = ad_member;
+ break;
+
+ case MO_MEMBER_OF_AD:
+ mo->mo_ad_memberof = ad_memberOf;
+ memberof_make_member_filter( mo );
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ } else {
+ switch( c->type ) {
+ case MO_DN:
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ }
+ mo->mo_dn = c->value_dn;
+ mo->mo_ndn = c->value_ndn;
+ break;
+
+ case MO_DANGLING:
+ i = verb_to_mask( c->argv[ 1 ], dangling_mode );
+ if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) {
+ return 1;
+ }
+
+ mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
+ mo->mo_flags |= dangling_mode[ i ].mask;
+ break;
+
+ case MO_DANGLING_ERROR:
+ i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code );
+ if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) {
+ mo->mo_dangling_err = slap_ldap_response_code[ i ].mask;
+ } else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) {
+ return 1;
+ }
+ break;
+
+ case MO_REFINT:
+ if ( c->value_int ) {
+ mo->mo_flags |= MEMBEROF_FREFINT;
+
+ } else {
+ mo->mo_flags &= ~MEMBEROF_FREFINT;
+ }
+ break;
+
+#if 0
+ case MO_REVERSE:
+ if ( c->value_int ) {
+ mo->mo_flags |= MEMBEROF_FREVERSE;
+
+ } else {
+ mo->mo_flags &= ~MEMBEROF_FREVERSE;
+ }
+ break;
+#endif
+
+ case MO_GROUP_OC: {
+ ObjectClass *oc = NULL;
+
+ oc = oc_find( c->argv[ 1 ] );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to find group objectClass=\"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ mo->mo_oc_group = oc;
+ memberof_make_group_filter( mo );
+ } break;
+
+ case MO_MEMBER_AD: {
+ AttributeDescription *ad = c->value_ad;
+
+ if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
+ && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "member attribute=\"%s\" must either "
+ "have DN (%s) or nameUID (%s) syntax",
+ c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ mo->mo_ad_member = ad;
+ } break;
+
+ case MO_MEMBER_OF_AD: {
+ AttributeDescription *ad = c->value_ad;
+
+ if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
+ && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "memberof attribute=\"%s\" must either "
+ "have DN (%s) or nameUID (%s) syntax",
+ c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg );
+ return 1;
+ }
+
+ mo->mo_ad_memberof = ad;
+ memberof_make_member_filter( mo );
+ } break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+memberof_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int rc;
+
+ if ( !mo->mo_ad_memberof ) {
+ mo->mo_ad_memberof = ad_memberOf;
+ }
+
+ if ( ! mo->mo_ad_member ) {
+ mo->mo_ad_member = ad_member;
+ }
+
+ if ( ! mo->mo_oc_group ) {
+ mo->mo_oc_group = oc_group;
+ }
+
+ if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) {
+ ber_dupbv( &mo->mo_dn, &be->be_rootdn );
+ ber_dupbv( &mo->mo_ndn, &be->be_rootndn );
+ }
+
+ if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ memberof_make_group_filter( mo );
+ }
+
+ if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ memberof_make_member_filter( mo );
+ }
+
+ return 0;
+}
+
+static int
+memberof_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ if ( mo ) {
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ ber_memfree( mo->mo_groupFilterstr.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ ber_memfree( mo->mo_memberFilterstr.bv_val );
+ }
+
+ ber_memfree( mo );
+ }
+
+ return 0;
+}
+
+static struct {
+ char *desc;
+ AttributeDescription **adp;
+} as[] = {
+ { "( 1.2.840.113556.1.2.102 "
+ "NAME 'memberOf' "
+ "DESC 'Group that the entry belongs to' "
+ "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' "
+ "EQUALITY distinguishedNameMatch " /* added */
+ "USAGE dSAOperation " /* added; questioned */
+ "NO-USER-MODIFICATION " /* added */
+ "X-ORIGIN 'iPlanet Delegated Administrator' )",
+ &ad_memberOf },
+ { NULL }
+};
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+int
+memberof_initialize( void )
+{
+ int code, i;
+
+ for ( i = 0; as[ i ].desc != NULL; i++ ) {
+ code = register_at( as[ i ].desc, as[ i ].adp, 1 );
+ if ( code && code != SLAP_SCHERR_ATTR_DUP ) {
+ Debug( LDAP_DEBUG_ANY,
+ "memberof_initialize: register_at #%d failed\n",
+ i );
+ return code;
+ }
+ }
+
+ memberof.on_bi.bi_type = "memberof";
+
+ memberof.on_bi.bi_db_init = memberof_db_init;
+ memberof.on_bi.bi_db_open = memberof_db_open;
+ memberof.on_bi.bi_db_destroy = memberof_db_destroy;
+
+ memberof.on_bi.bi_op_add = memberof_op_add;
+ memberof.on_bi.bi_op_delete = memberof_op_delete;
+ memberof.on_bi.bi_op_modify = memberof_op_modify;
+ memberof.on_bi.bi_op_modrdn = memberof_op_modrdn;
+
+ memberof.on_bi.bi_cf_ocs = mo_ocs;
+
+ code = config_register_schema( mo_cfg, mo_ocs );
+ if ( code ) return code;
+
+ return overlay_register( &memberof );
+}
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return memberof_initialize();
+}
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_MEMBEROF */
diff --git a/servers/slapd/overlays/otp.c b/servers/slapd/overlays/otp.c
new file mode 100644
index 0000000..590ee50
--- /dev/null
+++ b/servers/slapd/overlays/otp.c
@@ -0,0 +1,1004 @@
+/* otp.c - OATH 2-factor authentication module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2015-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2015 by Howard Chu, Symas Corp.
+ * Portions Copyright 2016-2017 by Michael Ströder <michael@stroeder.com>
+ * Portions Copyright 2018 by Ondřej Kuzník, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work includes code from the lastbind overlay.
+ */
+
+#include <portable.h>
+
+#ifdef SLAPD_OVER_OTP
+
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#include <lber.h>
+#include <lber_pvt.h>
+#include "lutil.h"
+#include <ac/stdlib.h>
+#include <ac/ctype.h>
+#include <ac/string.h>
+/* include socket.h to get sys/types.h and/or winsock2.h */
+#include <ac/socket.h>
+
+#if HAVE_OPENSSL
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+
+#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_LENGTH
+
+#if OPENSSL_VERSION_MAJOR >= 3
+#define TOTP_SHA1 SN_sha1
+#define TOTP_SHA224 SN_sha224
+#define TOTP_SHA256 SN_sha256
+#define TOTP_SHA384 SN_sha384
+#define TOTP_SHA512 SN_sha512
+#define TOTP_HMAC_CTX EVP_MAC_CTX *
+#else
+#define TOTP_SHA1 EVP_sha1()
+#define TOTP_SHA224 EVP_sha224()
+#define TOTP_SHA256 EVP_sha256()
+#define TOTP_SHA384 EVP_sha384()
+#define TOTP_SHA512 EVP_sha512()
+#define TOTP_HMAC_CTX HMAC_CTX *
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static HMAC_CTX *
+HMAC_CTX_new( void )
+{
+ HMAC_CTX *ctx = OPENSSL_malloc( sizeof(*ctx) );
+ if ( ctx != NULL ) {
+ HMAC_CTX_init( ctx );
+ }
+ return ctx;
+}
+
+static void
+HMAC_CTX_free( HMAC_CTX *ctx )
+{
+ if ( ctx != NULL ) {
+ HMAC_CTX_cleanup( ctx );
+ OPENSSL_free( ctx );
+ }
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+#if OPENSSL_VERSION_MAJOR >= 3
+static EVP_MAC *evp_mac;
+#define HMAC_setup( ctx, key, len, hash ) \
+ { OSSL_PARAM params[2]; \
+ ctx = EVP_MAC_CTX_new( evp_mac ); \
+ params[0] = OSSL_PARAM_construct_utf8_string( "digest", (char *)hash, 0 ); \
+ params[1] = OSSL_PARAM_construct_end(); \
+ EVP_MAC_init( ctx, key, len, params ); }
+#define HMAC_crunch( ctx, buf, len ) EVP_MAC_update( ctx, buf, len )
+#define HMAC_finish( ctx, dig, dlen ) \
+ { size_t outlen; \
+ EVP_MAC_final( ctx, dig, &outlen, TOTP_SHA512_DIGEST_LENGTH ); \
+ dlen = outlen; } \
+ EVP_MAC_CTX_free( ctx )
+
+#else
+#define HMAC_setup( ctx, key, len, hash ) \
+ ctx = HMAC_CTX_new(); \
+ HMAC_Init_ex( ctx, key, len, hash, 0 )
+#define HMAC_crunch( ctx, buf, len ) HMAC_Update( ctx, buf, len )
+#define HMAC_finish( ctx, dig, dlen ) \
+ HMAC_Final( ctx, dig, &dlen ); \
+ HMAC_CTX_free( ctx )
+#endif
+
+#elif HAVE_GNUTLS
+#include <nettle/hmac.h>
+
+#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_SIZE
+#define TOTP_SHA1 &nettle_sha1
+#define TOTP_SHA224 &nettle_sha224
+#define TOTP_SHA256 &nettle_sha256
+#define TOTP_SHA384 &nettle_sha384
+#define TOTP_SHA512 &nettle_sha512
+#define TOTP_HMAC_CTX struct hmac_sha512_ctx
+
+#define HMAC_setup( ctx, key, len, hash ) \
+ const struct nettle_hash *h = hash; \
+ hmac_set_key( &ctx.outer, &ctx.inner, &ctx.state, h, len, key )
+#define HMAC_crunch( ctx, buf, len ) hmac_update( &ctx.state, h, len, buf )
+#define HMAC_finish( ctx, dig, dlen ) \
+ hmac_digest( &ctx.outer, &ctx.inner, &ctx.state, h, h->digest_size, dig ); \
+ dlen = h->digest_size
+
+#else
+#error Unsupported crypto backend.
+#endif
+
+#include "slap.h"
+#include "slap-config.h"
+
+/* Schema from OATH-LDAP project by Michael Ströder */
+
+static struct {
+ char *name, *oid;
+} otp_oid[] = {
+ { "oath-ldap", "1.3.6.1.4.1.5427.1.389.4226" },
+ { "oath-ldap-at", "oath-ldap:4" },
+ { "oath-ldap-oc", "oath-ldap:6" },
+ { NULL }
+};
+
+AttributeDescription *ad_oathOTPToken;
+AttributeDescription *ad_oathSecret;
+AttributeDescription *ad_oathOTPLength;
+AttributeDescription *ad_oathHMACAlgorithm;
+
+AttributeDescription *ad_oathHOTPParams;
+AttributeDescription *ad_oathHOTPToken;
+AttributeDescription *ad_oathHOTPCounter;
+AttributeDescription *ad_oathHOTPLookahead;
+
+AttributeDescription *ad_oathTOTPTimeStepPeriod;
+AttributeDescription *ad_oathTOTPParams;
+AttributeDescription *ad_oathTOTPToken;
+AttributeDescription *ad_oathTOTPLastTimeStep;
+AttributeDescription *ad_oathTOTPTimeStepWindow;
+AttributeDescription *ad_oathTOTPTimeStepDrift;
+
+static struct otp_at {
+ char *schema;
+ AttributeDescription **adp;
+} otp_at[] = {
+ { "( oath-ldap-at:1 "
+ "NAME 'oathSecret' "
+ "DESC 'OATH-LDAP: Shared Secret (possibly encrypted with public key in oathEncKey)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
+ &ad_oathSecret },
+
+ { "( oath-ldap-at:2 "
+ "NAME 'oathTokenSerialNumber' "
+ "DESC 'OATH-LDAP: Proprietary hardware token serial number assigned by vendor' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64})" },
+
+ { "( oath-ldap-at:3 "
+ "NAME 'oathTokenIdentifier' "
+ "DESC 'OATH-LDAP: Globally unique OATH token identifier' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )" },
+
+ { "( oath-ldap-at:4 "
+ "NAME 'oathParamsEntry' "
+ "DESC 'OATH-LDAP: DN pointing to OATH parameter/policy object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP distinguishedName )" },
+ { "( oath-ldap-at:4.1 "
+ "NAME 'oathTOTPTimeStepPeriod' "
+ "DESC 'OATH-LDAP: Time window for TOTP (seconds)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ &ad_oathTOTPTimeStepPeriod },
+
+ { "( oath-ldap-at:5 "
+ "NAME 'oathOTPLength' "
+ "DESC 'OATH-LDAP: Length of OTP (number of digits)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ &ad_oathOTPLength },
+ { "( oath-ldap-at:5.1 "
+ "NAME 'oathHOTPParams' "
+ "DESC 'OATH-LDAP: DN pointing to HOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathParamsEntry )",
+ &ad_oathHOTPParams },
+ { "( oath-ldap-at:5.2 "
+ "NAME 'oathTOTPParams' "
+ "DESC 'OATH-LDAP: DN pointing to TOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathParamsEntry )",
+ &ad_oathTOTPParams },
+
+ { "( oath-ldap-at:6 "
+ "NAME 'oathHMACAlgorithm' "
+ "DESC 'OATH-LDAP: HMAC algorithm used for generating OTP values' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ &ad_oathHMACAlgorithm },
+
+ { "( oath-ldap-at:7 "
+ "NAME 'oathTimestamp' "
+ "DESC 'OATH-LDAP: Timestamp (not directly used).' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )" },
+ { "( oath-ldap-at:7.1 "
+ "NAME 'oathLastFailure' "
+ "DESC 'OATH-LDAP: Timestamp of last failed OATH validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+ { "( oath-ldap-at:7.2 "
+ "NAME 'oathLastLogin' "
+ "DESC 'OATH-LDAP: Timestamp of last successful OATH validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+ { "( oath-ldap-at:7.3 "
+ "NAME 'oathSecretTime' "
+ "DESC 'OATH-LDAP: Timestamp of generation of oathSecret attribute.' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+
+ { "( oath-ldap-at:8 "
+ "NAME 'oathSecretMaxAge' "
+ "DESC 'OATH-LDAP: Time in seconds for which the shared secret (oathSecret) will be valid from oathSecretTime value.' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+
+ { "( oath-ldap-at:9 "
+ "NAME 'oathToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP distinguishedName )" },
+ { "( oath-ldap-at:9.1 "
+ "NAME 'oathHOTPToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH/HOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathToken )",
+ &ad_oathHOTPToken },
+ { "( oath-ldap-at:9.2 "
+ "NAME 'oathTOTPToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH/TOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathToken )",
+ &ad_oathTOTPToken },
+
+ { "( oath-ldap-at:10 "
+ "NAME 'oathCounter' "
+ "DESC 'OATH-LDAP: Counter for OATH data (not directly used)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+ { "( oath-ldap-at:10.1 "
+ "NAME 'oathFailureCount' "
+ "DESC 'OATH-LDAP: OATH failure counter' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.2 "
+ "NAME 'oathHOTPCounter' "
+ "DESC 'OATH-LDAP: Counter for HOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathHOTPCounter },
+ { "( oath-ldap-at:10.3 "
+ "NAME 'oathHOTPLookAhead' "
+ "DESC 'OATH-LDAP: Look-ahead window for HOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathHOTPLookahead },
+ { "( oath-ldap-at:10.5 "
+ "NAME 'oathThrottleLimit' "
+ "DESC 'OATH-LDAP: Failure throttle limit' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.6 "
+ "NAME 'oathTOTPLastTimeStep' "
+ "DESC 'OATH-LDAP: Last time step seen for TOTP (time/period)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPLastTimeStep },
+ { "( oath-ldap-at:10.7 "
+ "NAME 'oathMaxUsageCount' "
+ "DESC 'OATH-LDAP: Maximum number of times a token can be used' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.8 "
+ "NAME 'oathTOTPTimeStepWindow' "
+ "DESC 'OATH-LDAP: Size of time step +/- tolerance window used for TOTP validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPTimeStepWindow },
+ { "( oath-ldap-at:10.9 "
+ "NAME 'oathTOTPTimeStepDrift' "
+ "DESC 'OATH-LDAP: Last observed time step shift seen for TOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPTimeStepDrift },
+
+ { "( oath-ldap-at:11 "
+ "NAME 'oathSecretLength' "
+ "DESC 'OATH-LDAP: Length of plain-text shared secret (number of bytes)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+
+ { "( oath-ldap-at:12 "
+ "NAME 'oathEncKey' "
+ "DESC 'OATH-LDAP: public key to be used for encrypting new shared secrets' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
+
+ { "( oath-ldap-at:13 "
+ "NAME 'oathResultCode' "
+ "DESC 'OATH-LDAP: LDAP resultCode to use in response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+ { "( oath-ldap-at:13.1 "
+ "NAME 'oathSuccessResultCode' "
+ "DESC 'OATH-LDAP: success resultCode to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathResultCode )" },
+ { "( oath-ldap-at:13.2 "
+ "NAME 'oathFailureResultCode' "
+ "DESC 'OATH-LDAP: failure resultCode to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathResultCode )" },
+
+ { "( oath-ldap-at:14 "
+ "NAME 'oathTokenPIN' "
+ "DESC 'OATH-LDAP: Configuration PIN (possibly encrypted with oathEncKey)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
+
+ { "( oath-ldap-at:15 "
+ "NAME 'oathMessage' "
+ "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )" },
+ { "( oath-ldap-at:15.1 "
+ "NAME 'oathSuccessMessage' "
+ "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathMessage )" },
+ { "( oath-ldap-at:15.2 "
+ "NAME 'oathFailureMessage' "
+ "DESC 'OATH-LDAP: failure diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathMessage )" },
+
+ { NULL }
+};
+
+ObjectClass *oc_oathOTPUser;
+ObjectClass *oc_oathHOTPToken;
+ObjectClass *oc_oathTOTPToken;
+ObjectClass *oc_oathHOTPParams;
+ObjectClass *oc_oathTOTPParams;
+
+static struct otp_oc {
+ char *schema;
+ ObjectClass **ocp;
+} otp_oc[] = {
+ { "( oath-ldap-oc:1 "
+ "NAME 'oathUser' "
+ "DESC 'OATH-LDAP: User Object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT )",
+ &oc_oathOTPUser },
+ { "( oath-ldap-oc:1.1 "
+ "NAME 'oathHOTPUser' "
+ "DESC 'OATH-LDAP: HOTP user object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathUser "
+ "MAY ( oathHOTPToken ) )" },
+ { "( oath-ldap-oc:1.2 "
+ "NAME 'oathTOTPUser' "
+ "DESC 'OATH-LDAP: TOTP user object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathUser "
+ "MUST ( oathTOTPToken ) )" },
+ { "( oath-ldap-oc:2 "
+ "NAME 'oathParams' "
+ "DESC 'OATH-LDAP: Parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT "
+ "MUST ( oathOTPLength $ oathHMACAlgorithm ) "
+ "MAY ( oathSecretMaxAge $ oathSecretLength $ "
+ "oathMaxUsageCount $ oathThrottleLimit $ oathEncKey $ "
+ "oathSuccessResultCode $ oathSuccessMessage $ "
+ "oathFailureResultCode $ oathFailureMessage ) )" },
+ { "( oath-ldap-oc:2.1 "
+ "NAME 'oathHOTPParams' "
+ "DESC 'OATH-LDAP: HOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathParams "
+ "MUST ( oathHOTPLookAhead ) )",
+ &oc_oathHOTPParams },
+ { "( oath-ldap-oc:2.2 "
+ "NAME 'oathTOTPParams' "
+ "DESC 'OATH-LDAP: TOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathParams "
+ "MUST ( oathTOTPTimeStepPeriod ) "
+ "MAY ( oathTOTPTimeStepWindow ) )",
+ &oc_oathTOTPParams },
+ { "( oath-ldap-oc:3 "
+ "NAME 'oathToken' "
+ "DESC 'OATH-LDAP: User Object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT "
+ "MAY ( oathSecret $ oathSecretTime $ "
+ "oathLastLogin $ oathFailureCount $ oathLastFailure $ "
+ "oathTokenSerialNumber $ oathTokenIdentifier $ oathTokenPIN ) )" },
+ { "( oath-ldap-oc:3.1 "
+ "NAME 'oathHOTPToken' "
+ "DESC 'OATH-LDAP: HOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathToken "
+ "MAY ( oathHOTPParams $ oathHOTPCounter ) )",
+ &oc_oathHOTPToken },
+ { "( oath-ldap-oc:3.2 "
+ "NAME 'oathTOTPToken' "
+ "DESC 'OATH-LDAP: TOTP token' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathToken "
+ "MAY ( oathTOTPParams $ oathTOTPLastTimeStep $ oathTOTPTimeStepDrift ) )",
+ &oc_oathTOTPToken },
+ { NULL }
+};
+
+typedef struct myval {
+ ber_len_t mv_len;
+ void *mv_val;
+} myval;
+
+static void
+do_hmac( const void *hash, myval *key, myval *data, myval *out )
+{
+ TOTP_HMAC_CTX ctx;
+ unsigned int digestLen;
+
+ HMAC_setup( ctx, key->mv_val, key->mv_len, hash );
+ HMAC_crunch( ctx, data->mv_val, data->mv_len );
+ HMAC_finish( ctx, out->mv_val, digestLen );
+ out->mv_len = digestLen;
+}
+
+#define MAX_DIGITS 8
+static const int DIGITS_POWER[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
+};
+
+static const void *
+otp_choose_mech( struct berval *oid )
+{
+ /* RFC 8018 OIDs */
+ const struct berval oid_hmacwithSHA1 = BER_BVC("1.2.840.113549.2.7");
+ const struct berval oid_hmacwithSHA224 = BER_BVC("1.2.840.113549.2.8");
+ const struct berval oid_hmacwithSHA256 = BER_BVC("1.2.840.113549.2.9");
+ const struct berval oid_hmacwithSHA384 = BER_BVC("1.2.840.113549.2.10");
+ const struct berval oid_hmacwithSHA512 = BER_BVC("1.2.840.113549.2.11");
+
+ if ( !ber_bvcmp( &oid_hmacwithSHA1, oid ) ) {
+ return TOTP_SHA1;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA224, oid ) ) {
+ return TOTP_SHA224;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA256, oid ) ) {
+ return TOTP_SHA256;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA384, oid ) ) {
+ return TOTP_SHA384;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA512, oid ) ) {
+ return TOTP_SHA512;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "otp_choose_mech: "
+ "hmac OID %s unsupported\n",
+ oid->bv_val );
+ return NULL;
+}
+
+static void
+generate(
+ struct berval *bv,
+ uint64_t tval,
+ int digits,
+ struct berval *out,
+ const void *mech )
+{
+ unsigned char digest[TOTP_SHA512_DIGEST_LENGTH];
+ myval digval;
+ myval key, data;
+ unsigned char msg[8];
+ int i, offset, res, otp;
+
+#if WORDS_BIGENDIAN
+ *(uint64_t *)msg = tval;
+#else
+ for ( i = 7; i >= 0; i-- ) {
+ msg[i] = tval & 0xff;
+ tval >>= 8;
+ }
+#endif
+
+ key.mv_len = bv->bv_len;
+ key.mv_val = bv->bv_val;
+
+ data.mv_val = msg;
+ data.mv_len = sizeof(msg);
+
+ digval.mv_val = digest;
+ digval.mv_len = sizeof(digest);
+ do_hmac( mech, &key, &data, &digval );
+
+ offset = digest[digval.mv_len - 1] & 0xf;
+ res = ( (digest[offset] & 0x7f) << 24 ) |
+ ( ( digest[offset + 1] & 0xff ) << 16 ) |
+ ( ( digest[offset + 2] & 0xff ) << 8 ) |
+ ( digest[offset + 3] & 0xff );
+
+ otp = res % DIGITS_POWER[digits];
+ out->bv_len = snprintf( out->bv_val, out->bv_len, "%0*d", digits, otp );
+}
+
+static int
+otp_bind_response( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ /* If the bind succeeded, return our result */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static long
+otp_hotp( Operation *op, Entry *token )
+{
+ char outbuf[MAX_DIGITS + 1];
+ Entry *params = NULL;
+ Attribute *a;
+ BerValue *secret, client_otp;
+ const void *mech;
+ long last_step = -1, found = -1;
+ int i, otp_len, window;
+
+ a = attr_find( token->e_attrs, ad_oathSecret );
+ secret = &a->a_vals[0];
+
+ a = attr_find( token->e_attrs, ad_oathHOTPCounter );
+ if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathHOTPCounter value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( token->e_attrs, ad_oathHOTPParams );
+ if ( !a ||
+ be_entry_get_rw( op, &a->a_nvals[0], oc_oathHOTPParams, NULL, 0,
+ &params ) ) {
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathOTPLength );
+ if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathOTPLength value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
+ /* Client didn't even send the token, fail immediately */
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathHOTPLookahead );
+ if ( lutil_atoi( &window, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathHOTPLookAhead value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ window++;
+
+ a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
+ if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
+ goto done;
+ }
+ be_entry_release_r( op, params );
+ params = NULL;
+
+ /* We are provided "password" + "OTP", split accordingly */
+ client_otp.bv_len = otp_len;
+ client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
+
+ /* If check succeeds, advance the step counter accordingly */
+ for ( i = 1; i <= window; i++ ) {
+ BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
+
+ generate( secret, last_step + i, otp_len, &out, mech );
+ if ( !ber_bvcmp( &out, &client_otp ) ) {
+ found = last_step + i;
+ /* Would we leak information if we stopped right now? */
+ }
+ }
+
+ if ( found >= 0 ) {
+ /* OTP check passed, trim the password */
+ op->orb_cred.bv_len -= otp_len;
+ Debug( LDAP_DEBUG_STATS, "%s HOTP token %s no. %ld redeemed\n",
+ op->o_log_prefix, token->e_name.bv_val, found );
+ }
+
+done:
+ memset( outbuf, 0, sizeof(outbuf) );
+ if ( params ) {
+ be_entry_release_r( op, params );
+ }
+ return found;
+}
+
+static long
+otp_totp( Operation *op, Entry *token, long *drift )
+{
+ char outbuf[MAX_DIGITS + 1];
+ Entry *params = NULL;
+ Attribute *a;
+ BerValue *secret, client_otp;
+ const void *mech;
+ long t, last_step = -1, found = -1, window = 0, old_drift;
+ int i, otp_len, time_step;
+
+ a = attr_find( token->e_attrs, ad_oathSecret );
+ secret = &a->a_vals[0];
+
+ a = attr_find( token->e_attrs, ad_oathTOTPLastTimeStep );
+ if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPLastTimeStep value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( token->e_attrs, ad_oathTOTPParams );
+ if ( !a ||
+ be_entry_get_rw( op, &a->a_nvals[0], oc_oathTOTPParams, NULL, 0,
+ &params ) ) {
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepPeriod );
+ if ( lutil_atoi( &time_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepPeriod value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepWindow );
+ if ( a && lutil_atol( &window, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepWindow value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepDrift );
+ if ( a && lutil_atol( drift, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepDrift value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ old_drift = *drift;
+ t = op->o_time / time_step + *drift;
+
+ a = attr_find( params->e_attrs, ad_oathOTPLength );
+ if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathOTPLength value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
+ /* Client didn't even send the token, fail immediately */
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
+ if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
+ goto done;
+ }
+ be_entry_release_r( op, params );
+ params = NULL;
+
+ /* We are provided "password" + "OTP", split accordingly */
+ client_otp.bv_len = otp_len;
+ client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
+
+ /* If check succeeds, advance the step counter accordingly */
+ /* Negation of A001057 series that enumerates all integers:
+ * (0, -1, 1, -2, 2, ...) */
+ for ( i = 0; i >= -window; i = ( i < 0 ) ? -i : ~i ) {
+ BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
+
+ if ( t + i <= last_step ) continue;
+
+ generate( secret, t + i, otp_len, &out, mech );
+ if ( !ber_bvcmp( &out, &client_otp ) ) {
+ found = t + i;
+ *drift = old_drift + i;
+ /* Would we leak information if we stopped right now? */
+ }
+ }
+
+ /* OTP check passed, trim the password */
+ if ( found >= 0 ) {
+ assert( found > last_step );
+
+ op->orb_cred.bv_len -= otp_len;
+ Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with new drift of %ld\n",
+ op->o_log_prefix, token->e_name.bv_val, *drift );
+ }
+
+done:
+ memset( outbuf, 0, sizeof(outbuf) );
+ if ( params ) {
+ be_entry_release_r( op, params );
+ }
+ return found;
+}
+
+static int
+otp_op_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ BerValue totpdn = BER_BVNULL, hotpdn = BER_BVNULL, ndn;
+ Entry *user = NULL, *token = NULL;
+ AttributeDescription *ad = NULL, *drift_ad = NULL;
+ Attribute *a;
+ long t = -1, drift = 0;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( op->oq_bind.rb_method != LDAP_AUTH_SIMPLE ) {
+ return rc;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ if ( be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &user ) ) {
+ goto done;
+ }
+
+ if ( !is_entry_objectclass_or_sub( user, oc_oathOTPUser ) ) {
+ be_entry_release_r( op, user );
+ goto done;
+ }
+
+ if ( (a = attr_find( user->e_attrs, ad_oathTOTPToken )) ) {
+ ber_dupbv_x( &totpdn, &a->a_nvals[0], op->o_tmpmemctx );
+ }
+
+ if ( (a = attr_find( user->e_attrs, ad_oathHOTPToken )) ) {
+ ber_dupbv_x( &hotpdn, &a->a_nvals[0], op->o_tmpmemctx );
+ }
+ be_entry_release_r( op, user );
+
+ if ( !BER_BVISNULL( &totpdn ) &&
+ be_entry_get_rw( op, &totpdn, oc_oathTOTPToken, ad_oathSecret, 0,
+ &token ) == LDAP_SUCCESS ) {
+ ndn = totpdn;
+ ad = ad_oathTOTPLastTimeStep;
+ drift_ad = ad_oathTOTPTimeStepDrift;
+ t = otp_totp( op, token, &drift );
+ be_entry_release_r( op, token );
+ token = NULL;
+ }
+ if ( t < 0 && !BER_BVISNULL( &hotpdn ) &&
+ be_entry_get_rw( op, &hotpdn, oc_oathHOTPToken, ad_oathSecret, 0,
+ &token ) == LDAP_SUCCESS ) {
+ ndn = hotpdn;
+ ad = ad_oathHOTPCounter;
+ t = otp_hotp( op, token );
+ be_entry_release_r( op, token );
+ token = NULL;
+ }
+
+ /* If check succeeds, advance the step counter and drift accordingly */
+ if ( t >= 0 ) {
+ char outbuf[32], drift_buf[32];
+ Operation op2;
+ Opheader oh;
+ Modifications mod[2], *m = mod;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb = { .sc_response = &slap_null_cb };
+ BerValue bv[2], bv_drift[2];
+
+ bv[0].bv_val = outbuf;
+ bv[0].bv_len = snprintf( bv[0].bv_val, sizeof(outbuf), "%ld", t );
+ BER_BVZERO( &bv[1] );
+
+ m->sml_numvals = 1;
+ m->sml_values = bv;
+ m->sml_nvalues = NULL;
+ m->sml_desc = ad;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+
+ if ( drift_ad ) {
+ m->sml_next = &mod[1];
+
+ bv_drift[0].bv_val = drift_buf;
+ bv_drift[0].bv_len = snprintf(
+ bv_drift[0].bv_val, sizeof(drift_buf), "%ld", drift );
+ BER_BVZERO( &bv_drift[1] );
+
+ m++;
+ m->sml_numvals = 1;
+ m->sml_values = bv_drift;
+ m->sml_nvalues = NULL;
+ m->sml_desc = drift_ad;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+ }
+ m->sml_next = NULL;
+
+ op2 = *op;
+ oh = *op->o_hdr;
+ op2.o_hdr = &oh;
+
+ op2.o_callback = &cb;
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.orm_modlist = mod;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ op2.o_req_dn = ndn;
+ op2.o_req_ndn = ndn;
+ op2.o_opid = -1;
+
+ op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ } else {
+ /* Client failed the bind, but we still have to pass it over to the
+ * backend and fail the Bind later */
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_response = otp_bind_response;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+
+done:
+ if ( !BER_BVISNULL( &hotpdn ) ) {
+ ber_memfree_x( hotpdn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &totpdn ) ) {
+ ber_memfree_x( totpdn.bv_val, op->o_tmpmemctx );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+ return rc;
+}
+
+static slap_overinst otp;
+
+int
+otp_initialize( void )
+{
+ ConfigArgs ca;
+ char *argv[4];
+ int i;
+
+ otp.on_bi.bi_type = "otp";
+ otp.on_bi.bi_op_bind = otp_op_bind;
+
+ ca.argv = argv;
+ argv[0] = "otp";
+ ca.argv = argv;
+ ca.argc = 3;
+ ca.fname = argv[0];
+
+ argv[3] = NULL;
+ for ( i = 0; otp_oid[i].name; i++ ) {
+ argv[1] = otp_oid[i].name;
+ argv[2] = otp_oid[i].oid;
+ parse_oidm( &ca, 0, NULL );
+ }
+
+ /* schema integration */
+ for ( i = 0; otp_at[i].schema; i++ ) {
+ if ( register_at( otp_at[i].schema, otp_at[i].adp, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "otp_initialize: "
+ "register_at failed\n" );
+ return -1;
+ }
+ }
+
+ for ( i = 0; otp_oc[i].schema; i++ ) {
+ if ( register_oc( otp_oc[i].schema, otp_oc[i].ocp, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "otp_initialize: "
+ "register_oc failed\n" );
+ return -1;
+ }
+ }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ evp_mac = EVP_MAC_fetch( NULL, "HMAC", "provider=default" );
+#endif
+ return overlay_register( &otp );
+}
+
+#if SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return otp_initialize();
+}
+#endif /* SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_OTP) */
diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c
new file mode 100644
index 0000000..8290200
--- /dev/null
+++ b/servers/slapd/overlays/overlays.c
@@ -0,0 +1,44 @@
+/* overlays.c - Static overlay framework */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Copyright 2003 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include "slap.h"
+
+extern OverlayInit slap_oinfo[];
+
+int
+overlay_init(void)
+{
+ int i, rc = 0;
+
+ for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) {
+ rc = slap_oinfo[i].ov_init();
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s overlay setup failed, err %d\n",
+ slap_oinfo[i].ov_type, rc );
+ break;
+ }
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c
new file mode 100644
index 0000000..2b947e4
--- /dev/null
+++ b/servers/slapd/overlays/pcache.c
@@ -0,0 +1,5815 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 IBM Corporation.
+ * Portions Copyright 2003-2009 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Apurva Kumar for inclusion
+ * in OpenLDAP Software and subsequently rewritten by Howard Chu.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_PROXYCACHE
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+#include "ldap_avl.h"
+
+#include "../back-monitor/back-monitor.h"
+
+#include "slap-config.h"
+
+/*
+ * Control that allows to access the private DB
+ * instead of the public one
+ */
+#define PCACHE_CONTROL_PRIVDB "1.3.6.1.4.1.4203.666.11.9.5.1"
+
+/*
+ * Extended Operation that allows to remove a query from the cache
+ */
+#define PCACHE_EXOP_QUERY_DELETE "1.3.6.1.4.1.4203.666.11.9.6.1"
+
+/*
+ * Monitoring
+ */
+#define PCACHE_MONITOR
+
+/* query cache structs */
+/* query */
+
+typedef struct Query_s {
+ Filter* filter; /* Search Filter */
+ struct berval base; /* Search Base */
+ int scope; /* Search scope */
+} Query;
+
+struct query_template_s;
+
+typedef struct Qbase_s {
+ TAvlnode *scopes[4]; /* threaded AVL trees of cached queries */
+ struct berval base;
+ int queries;
+} Qbase;
+
+/* struct representing a cached query */
+typedef struct cached_query_s {
+ Filter *filter;
+ Filter *first;
+ Qbase *qbase;
+ int scope;
+ struct berval q_uuid; /* query identifier */
+ int q_sizelimit;
+ struct query_template_s *qtemp; /* template of the query */
+ time_t expiry_time; /* time till the query is considered invalid */
+ time_t refresh_time; /* time till the query is refreshed */
+ time_t bindref_time; /* time till the bind is refreshed */
+ int bind_refcnt; /* number of bind operation referencing this query */
+ unsigned long answerable_cnt; /* how many times it was answerable */
+ int refcnt; /* references since last refresh */
+ int in_lru; /* query is in LRU list */
+ ldap_pvt_thread_mutex_t answerable_cnt_mutex;
+ struct cached_query_s *next; /* next query in the template */
+ struct cached_query_s *prev; /* previous query in the template */
+ struct cached_query_s *lru_up; /* previous query in the LRU list */
+ struct cached_query_s *lru_down; /* next query in the LRU list */
+ ldap_pvt_thread_rdwr_t rwlock;
+} CachedQuery;
+
+/*
+ * URL representation:
+ *
+ * ldap:///<base>??<scope>?<filter>?x-uuid=<uid>,x-template=<template>,x-attrset=<attrset>,x-expiry=<expiry>,x-refresh=<refresh>
+ *
+ * <base> ::= CachedQuery.qbase->base
+ * <scope> ::= CachedQuery.scope
+ * <filter> ::= filter2bv(CachedQuery.filter)
+ * <uuid> ::= CachedQuery.q_uuid
+ * <attrset> ::= CachedQuery.qtemp->attr_set_index
+ * <expiry> ::= CachedQuery.expiry_time
+ * <refresh> ::= CachedQuery.refresh_time
+ *
+ * quick hack: parse URI, call add_query() and then fix
+ * CachedQuery.expiry_time and CachedQuery.q_uuid
+ *
+ * NOTE: if the <attrset> changes, all stored URLs will be invalidated.
+ */
+
+/*
+ * Represents a set of projected attributes.
+ */
+
+struct attr_set {
+ struct query_template_s *templates;
+ AttributeName* attrs; /* specifies the set */
+ unsigned flags;
+#define PC_CONFIGURED (0x1)
+#define PC_REFERENCED (0x2)
+#define PC_GOT_OC (0x4)
+ int count; /* number of attributes */
+};
+
+/* struct representing a query template
+ * e.g. template string = &(cn=)(mail=)
+ */
+typedef struct query_template_s {
+ struct query_template_s *qtnext;
+ struct query_template_s *qmnext;
+
+ Avlnode* qbase;
+ CachedQuery* query; /* most recent query cached for the template */
+ CachedQuery* query_last; /* oldest query cached for the template */
+ ldap_pvt_thread_rdwr_t t_rwlock; /* Rd/wr lock for accessing queries in the template */
+ struct berval querystr; /* Filter string corresponding to the QT */
+ struct berval bindbase; /* base DN for Bind request */
+ struct berval bindfilterstr; /* Filter string for Bind request */
+ struct berval bindftemp; /* bind filter template */
+ Filter *bindfilter;
+ AttributeDescription **bindfattrs; /* attrs to substitute in ftemp */
+
+ int bindnattrs; /* number of bindfattrs */
+ int bindscope;
+ int attr_set_index; /* determines the projected attributes */
+ int no_of_queries; /* Total number of queries in the template */
+ time_t ttl; /* TTL for the queries of this template */
+ time_t negttl; /* TTL for negative results */
+ time_t limitttl; /* TTL for sizelimit exceeding results */
+ time_t ttr; /* time to refresh */
+ time_t bindttr; /* TTR for cached binds */
+ struct attr_set t_attrs; /* filter attrs + attr_set */
+} QueryTemplate;
+
+typedef enum {
+ PC_IGNORE = 0,
+ PC_POSITIVE,
+ PC_NEGATIVE,
+ PC_SIZELIMIT
+} pc_caching_reason_t;
+
+static const char *pc_caching_reason_str[] = {
+ "IGNORE",
+ "POSITIVE",
+ "NEGATIVE",
+ "SIZELIMIT",
+
+ NULL
+};
+
+struct query_manager_s;
+
+/* prototypes for functions for 1) query containment
+ * 2) query addition, 3) cache replacement
+ */
+typedef CachedQuery *(QCfunc)(Operation *op, struct query_manager_s*,
+ Query*, QueryTemplate*);
+typedef CachedQuery *(AddQueryfunc)(Operation *op, struct query_manager_s*,
+ Query*, QueryTemplate*, pc_caching_reason_t, int wlock);
+typedef void (CRfunc)(struct query_manager_s*, struct berval*);
+
+/* LDAP query cache */
+typedef struct query_manager_s {
+ struct attr_set* attr_sets; /* possible sets of projected attributes */
+ QueryTemplate* templates; /* cacheable templates */
+
+ CachedQuery* lru_top; /* top and bottom of LRU list */
+ CachedQuery* lru_bottom;
+
+ ldap_pvt_thread_mutex_t lru_mutex; /* mutex for accessing LRU list */
+
+ /* Query cache methods */
+ QCfunc *qcfunc; /* Query containment*/
+ CRfunc *crfunc; /* cache replacement */
+ AddQueryfunc *addfunc; /* add query */
+} query_manager;
+
+/* LDAP query cache manager */
+typedef struct cache_manager_s {
+ BackendDB db; /* underlying database */
+ unsigned long num_cached_queries; /* total number of cached queries */
+ unsigned long max_queries; /* upper bound on # of cached queries */
+ int save_queries; /* save cached queries across restarts */
+ int check_cacheability; /* check whether a query is cacheable */
+ int numattrsets; /* number of attribute sets */
+ int cur_entries; /* current number of entries cached */
+ int max_entries; /* max number of entries cached */
+ int num_entries_limit; /* max # of entries in a cacheable query */
+
+ char response_cb; /* install the response callback
+ * at the tail of the callback list */
+#define PCACHE_RESPONSE_CB_HEAD 0
+#define PCACHE_RESPONSE_CB_TAIL 1
+ char defer_db_open; /* defer open for online add */
+ char cache_binds; /* cache binds or just passthru */
+
+ time_t cc_period; /* interval between successive consistency checks (sec) */
+#define PCACHE_CC_PAUSED 1
+#define PCACHE_CC_OFFLINE 2
+ int cc_paused;
+ void *cc_arg;
+
+ ldap_pvt_thread_mutex_t cache_mutex;
+
+ query_manager* qm; /* query cache managed by the cache manager */
+
+#ifdef PCACHE_MONITOR
+ void *monitor_cb;
+ struct berval monitor_ndn;
+#endif /* PCACHE_MONITOR */
+} cache_manager;
+
+#ifdef PCACHE_MONITOR
+static int pcache_monitor_db_init( BackendDB *be );
+static int pcache_monitor_db_open( BackendDB *be );
+static int pcache_monitor_db_close( BackendDB *be );
+static int pcache_monitor_db_destroy( BackendDB *be );
+#endif /* PCACHE_MONITOR */
+
+static int pcache_debug;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+static int privDB_cid;
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static AttributeDescription *ad_queryId, *ad_cachedQueryURL;
+
+#ifdef PCACHE_MONITOR
+static AttributeDescription *ad_numQueries, *ad_numEntries;
+static ObjectClass *oc_olmPCache;
+#endif /* PCACHE_MONITOR */
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "PCacheOID", "1.3.6.1.4.1.4203.666.11.9.1" },
+ { "PCacheAttributes", "PCacheOID:1" },
+ { "PCacheObjectClasses", "PCacheOID:2" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **adp;
+} s_ad[] = {
+ { "( PCacheAttributes:1 "
+ "NAME 'pcacheQueryID' "
+ "DESC 'ID of query the entry belongs to, formatted as a UUID' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64} "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_queryId },
+ { "( PCacheAttributes:2 "
+ "NAME 'pcacheQueryURL' "
+ "DESC 'URI describing a cached query' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_cachedQueryURL },
+#ifdef PCACHE_MONITOR
+ { "( PCacheAttributes:3 "
+ "NAME 'pcacheNumQueries' "
+ "DESC 'Number of cached queries' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_numQueries },
+ { "( PCacheAttributes:4 "
+ "NAME 'pcacheNumEntries' "
+ "DESC 'Number of cached entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_numEntries },
+#endif /* PCACHE_MONITOR */
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **ocp;
+} s_oc[] = {
+#ifdef PCACHE_MONITOR
+ /* augments an existing object, so it must be AUXILIARY */
+ { "( PCacheObjectClasses:1 "
+ "NAME ( 'olmPCache' ) "
+ "SUP top AUXILIARY "
+ "MAY ( "
+ "pcacheQueryURL "
+ "$ pcacheNumQueries "
+ "$ pcacheNumEntries "
+ " ) )",
+ &oc_olmPCache },
+#endif /* PCACHE_MONITOR */
+
+ { NULL }
+};
+
+static int
+filter2template(
+ Operation *op,
+ Filter *f,
+ struct berval *fstr );
+
+static CachedQuery *
+add_query(
+ Operation *op,
+ query_manager* qm,
+ Query* query,
+ QueryTemplate *templ,
+ pc_caching_reason_t why,
+ int wlock);
+
+static int
+remove_query_data(
+ Operation *op,
+ struct berval *query_uuid );
+
+/*
+ * Turn a cached query into its URL representation
+ */
+static int
+query2url( Operation *op, CachedQuery *q, struct berval *urlbv, int dolock )
+{
+ struct berval bv_scope,
+ bv_filter;
+ char attrset_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ expiry_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ refresh_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ answerable_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ *ptr;
+ ber_len_t attrset_len,
+ expiry_len,
+ refresh_len,
+ answerable_len;
+
+ if ( dolock ) {
+ ldap_pvt_thread_rdwr_rlock( &q->rwlock );
+ }
+
+ ldap_pvt_scope2bv( q->scope, &bv_scope );
+ filter2bv_x( op, q->filter, &bv_filter );
+ attrset_len = sprintf( attrset_buf,
+ "%lu", (unsigned long)q->qtemp->attr_set_index );
+ expiry_len = sprintf( expiry_buf,
+ "%lu", (unsigned long)q->expiry_time );
+ answerable_len = snprintf( answerable_buf, sizeof( answerable_buf ),
+ "%lu", q->answerable_cnt );
+ if ( q->refresh_time )
+ refresh_len = sprintf( refresh_buf,
+ "%lu", (unsigned long)q->refresh_time );
+ else
+ refresh_len = 0;
+
+ urlbv->bv_len = STRLENOF( "ldap:///" )
+ + q->qbase->base.bv_len
+ + STRLENOF( "??" )
+ + bv_scope.bv_len
+ + STRLENOF( "?" )
+ + bv_filter.bv_len
+ + STRLENOF( "?x-uuid=" )
+ + q->q_uuid.bv_len
+ + STRLENOF( ",x-attrset=" )
+ + attrset_len
+ + STRLENOF( ",x-expiry=" )
+ + expiry_len
+ + STRLENOF( ",x-answerable=" )
+ + answerable_len;
+ if ( refresh_len )
+ urlbv->bv_len += STRLENOF( ",x-refresh=" )
+ + refresh_len;
+
+ ptr = urlbv->bv_val = ber_memalloc_x( urlbv->bv_len + 1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( ptr, "ldap:///" );
+ ptr = lutil_strcopy( ptr, q->qbase->base.bv_val );
+ ptr = lutil_strcopy( ptr, "??" );
+ ptr = lutil_strcopy( ptr, bv_scope.bv_val );
+ ptr = lutil_strcopy( ptr, "?" );
+ ptr = lutil_strcopy( ptr, bv_filter.bv_val );
+ ptr = lutil_strcopy( ptr, "?x-uuid=" );
+ ptr = lutil_strcopy( ptr, q->q_uuid.bv_val );
+ ptr = lutil_strcopy( ptr, ",x-attrset=" );
+ ptr = lutil_strcopy( ptr, attrset_buf );
+ ptr = lutil_strcopy( ptr, ",x-expiry=" );
+ ptr = lutil_strcopy( ptr, expiry_buf );
+ ptr = lutil_strcopy( ptr, ",x-answerable=" );
+ ptr = lutil_strcopy( ptr, answerable_buf );
+ if ( refresh_len ) {
+ ptr = lutil_strcopy( ptr, ",x-refresh=" );
+ ptr = lutil_strcopy( ptr, refresh_buf );
+ }
+
+ ber_memfree_x( bv_filter.bv_val, op->o_tmpmemctx );
+
+ if ( dolock ) {
+ ldap_pvt_thread_rdwr_runlock( &q->rwlock );
+ }
+
+ return 0;
+}
+
+/* Find and record the empty filter clauses */
+
+static int
+ftemp_attrs( struct berval *ftemp, struct berval *template,
+ AttributeDescription ***ret, const char **text )
+{
+ int i;
+ int attr_cnt=0;
+ struct berval bv;
+ char *p1, *p2, *t1;
+ AttributeDescription *ad;
+ AttributeDescription **descs = NULL;
+ char *temp2;
+
+ temp2 = ch_malloc( ftemp->bv_len + 1 );
+ p1 = ftemp->bv_val;
+ t1 = temp2;
+
+ *ret = NULL;
+
+ for (;;) {
+ while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' )
+ *t1++ = *p1++;
+
+ p2 = strchr( p1, '=' );
+ if ( !p2 ) {
+ if ( !descs ) {
+ ch_free( temp2 );
+ return -1;
+ }
+ break;
+ }
+ i = p2 - p1;
+ AC_MEMCPY( t1, p1, i );
+ t1 += i;
+ *t1++ = '=';
+
+ if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
+ bv.bv_val = p1;
+ bv.bv_len = p2 - p1;
+ ad = NULL;
+ i = slap_bv2ad( &bv, &ad, text );
+ if ( i ) {
+ ch_free( temp2 );
+ ch_free( descs );
+ return -1;
+ }
+ if ( *p2 == '<' || *p2 == '>' ) p2++;
+ if ( p2[1] != ')' ) {
+ p2++;
+ while ( *p2 != ')' ) p2++;
+ p1 = p2;
+ continue;
+ }
+
+ descs = (AttributeDescription **)ch_realloc(descs,
+ (attr_cnt + 2)*sizeof(AttributeDescription *));
+
+ descs[attr_cnt++] = ad;
+
+ p1 = p2+1;
+ }
+ *t1 = '\0';
+ descs[attr_cnt] = NULL;
+ *ret = descs;
+ template->bv_val = temp2;
+ template->bv_len = t1 - temp2;
+ return attr_cnt;
+}
+
+static int
+template_attrs( char *template, struct attr_set *set, AttributeName **ret,
+ const char **text )
+{
+ int got_oc = 0;
+ int alluser = 0;
+ int allop = 0;
+ int i;
+ int attr_cnt;
+ int t_cnt = 0;
+ struct berval bv;
+ char *p1, *p2;
+ AttributeDescription *ad;
+ AttributeName *attrs;
+
+ p1 = template;
+
+ *ret = NULL;
+
+ attrs = ch_calloc( set->count + 1, sizeof(AttributeName) );
+ for ( i=0; i < set->count; i++ )
+ attrs[i] = set->attrs[i];
+ attr_cnt = i;
+ alluser = an_find( attrs, slap_bv_all_user_attrs );
+ allop = an_find( attrs, slap_bv_all_operational_attrs );
+
+ for (;;) {
+ while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) p1++;
+ p2 = strchr( p1, '=' );
+ if ( !p2 )
+ break;
+ if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
+ bv.bv_val = p1;
+ bv.bv_len = p2 - p1;
+ ad = NULL;
+ i = slap_bv2ad( &bv, &ad, text );
+ if ( i ) {
+ ch_free( attrs );
+ return -1;
+ }
+ t_cnt++;
+
+ if ( ad == slap_schema.si_ad_objectClass )
+ got_oc = 1;
+
+ if ( is_at_operational(ad->ad_type)) {
+ if ( allop ) {
+ goto bottom;
+ }
+ } else if ( alluser ) {
+ goto bottom;
+ }
+ if ( !ad_inlist( ad, attrs )) {
+ attrs = (AttributeName *)ch_realloc(attrs,
+ (attr_cnt + 2)*sizeof(AttributeName));
+
+ attrs[attr_cnt].an_desc = ad;
+ attrs[attr_cnt].an_name = ad->ad_cname;
+ attrs[attr_cnt].an_oc = NULL;
+ attrs[attr_cnt].an_flags = 0;
+ BER_BVZERO( &attrs[attr_cnt+1].an_name );
+ attr_cnt++;
+ }
+
+bottom:
+ p1 = p2+2;
+ }
+ if ( !t_cnt ) {
+ *text = "couldn't parse template";
+ ch_free(attrs);
+ return -1;
+ }
+ if ( !got_oc && !( set->flags & PC_GOT_OC )) {
+ attrs = (AttributeName *)ch_realloc(attrs,
+ (attr_cnt + 2)*sizeof(AttributeName));
+
+ ad = slap_schema.si_ad_objectClass;
+ attrs[attr_cnt].an_desc = ad;
+ attrs[attr_cnt].an_name = ad->ad_cname;
+ attrs[attr_cnt].an_oc = NULL;
+ attrs[attr_cnt].an_flags = 0;
+ BER_BVZERO( &attrs[attr_cnt+1].an_name );
+ attr_cnt++;
+ }
+ *ret = attrs;
+ return attr_cnt;
+}
+
+/*
+ * Turn an URL representing a formerly cached query into a cached query,
+ * and try to cache it
+ */
+static int
+url2query(
+ char *url,
+ Operation *op,
+ query_manager *qm )
+{
+ Query query = { 0 };
+ QueryTemplate *qt;
+ CachedQuery *cq;
+ LDAPURLDesc *lud = NULL;
+ struct berval base,
+ tempstr = BER_BVNULL,
+ uuid = BER_BVNULL;
+ int attrset;
+ time_t expiry_time;
+ time_t refresh_time;
+ unsigned long answerable_cnt;
+ int i,
+ got = 0,
+#define GOT_UUID 0x1U
+#define GOT_ATTRSET 0x2U
+#define GOT_EXPIRY 0x4U
+#define GOT_ANSWERABLE 0x8U
+#define GOT_REFRESH 0x10U
+#define GOT_ALL (GOT_UUID|GOT_ATTRSET|GOT_EXPIRY|GOT_ANSWERABLE)
+ rc = 0;
+
+ rc = ldap_url_parse( url, &lud );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ return -1;
+ }
+
+ /* non-allowed fields */
+ if ( lud->lud_host != NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_attrs != NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ /* be pedantic */
+ if ( strcmp( lud->lud_scheme, "ldap" ) != 0 ) {
+ rc = 1;
+ goto error;
+ }
+
+ /* required fields */
+ if ( lud->lud_dn == NULL || lud->lud_dn[ 0 ] == '\0' ) {
+ rc = 1;
+ goto error;
+ }
+
+ switch ( lud->lud_scope ) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ break;
+
+ default:
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_filter == NULL || lud->lud_filter[ 0 ] == '\0' ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_exts == NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ for ( i = 0; lud->lud_exts[ i ] != NULL; i++ ) {
+ if ( strncmp( lud->lud_exts[ i ], "x-uuid=", STRLENOF( "x-uuid=" ) ) == 0 ) {
+ struct berval tmpUUID;
+ Syntax *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax;
+
+ if ( got & GOT_UUID ) {
+ rc = 1;
+ goto error;
+ }
+
+ ber_str2bv( &lud->lud_exts[ i ][ STRLENOF( "x-uuid=" ) ], 0, 0, &tmpUUID );
+ if ( !BER_BVISEMPTY( &tmpUUID ) ) {
+ rc = syn_UUID->ssyn_pretty( syn_UUID, &tmpUUID, &uuid, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto error;
+ }
+ }
+ got |= GOT_UUID;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-attrset=", STRLENOF( "x-attrset=" ) ) == 0 ) {
+ if ( got & GOT_ATTRSET ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoi( &attrset, &lud->lud_exts[ i ][ STRLENOF( "x-attrset=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ got |= GOT_ATTRSET;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-expiry=", STRLENOF( "x-expiry=" ) ) == 0 ) {
+ unsigned long l;
+
+ if ( got & GOT_EXPIRY ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-expiry=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ expiry_time = (time_t)l;
+ got |= GOT_EXPIRY;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-answerable=", STRLENOF( "x-answerable=" ) ) == 0 ) {
+ if ( got & GOT_ANSWERABLE ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &answerable_cnt, &lud->lud_exts[ i ][ STRLENOF( "x-answerable=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ got |= GOT_ANSWERABLE;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-refresh=", STRLENOF( "x-refresh=" ) ) == 0 ) {
+ unsigned long l;
+
+ if ( got & GOT_REFRESH ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-refresh=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ refresh_time = (time_t)l;
+ got |= GOT_REFRESH;
+
+ } else {
+ rc = -1;
+ goto error;
+ }
+ }
+
+ if ( got != GOT_ALL ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( !(got & GOT_REFRESH ))
+ refresh_time = 0;
+
+ /* ignore expired queries */
+ if ( expiry_time <= slap_get_time()) {
+ Operation op2 = *op;
+
+ memset( &op2.oq_search, 0, sizeof( op2.oq_search ) );
+
+ (void)remove_query_data( &op2, &uuid );
+
+ rc = 0;
+
+ } else {
+ ber_str2bv( lud->lud_dn, 0, 0, &base );
+ rc = dnNormalize( 0, NULL, NULL, &base, &query.base, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto error;
+ }
+ query.scope = lud->lud_scope;
+ query.filter = str2filter( lud->lud_filter );
+ if ( query.filter == NULL ) {
+ rc = -1;
+ goto error;
+ }
+
+ tempstr.bv_val = ch_malloc( strlen( lud->lud_filter ) + 1 );
+ tempstr.bv_len = 0;
+ if ( filter2template( op, query.filter, &tempstr ) ) {
+ ch_free( tempstr.bv_val );
+ rc = -1;
+ goto error;
+ }
+
+ /* check for query containment */
+ qt = qm->attr_sets[attrset].templates;
+ for ( ; qt; qt = qt->qtnext ) {
+ /* find if template i can potentially answer tempstr */
+ if ( bvmatch( &qt->querystr, &tempstr ) ) {
+ break;
+ }
+ }
+
+ if ( qt == NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ cq = add_query( op, qm, &query, qt, PC_POSITIVE, 0 );
+ if ( cq != NULL ) {
+ cq->expiry_time = expiry_time;
+ cq->refresh_time = refresh_time;
+ cq->q_uuid = uuid;
+ cq->answerable_cnt = answerable_cnt;
+ cq->refcnt = 0;
+
+ /* it's now into cq->filter */
+ BER_BVZERO( &uuid );
+ query.filter = NULL;
+
+ } else {
+ rc = 1;
+ }
+ }
+
+error:;
+ if ( query.filter != NULL ) filter_free( query.filter );
+ if ( !BER_BVISNULL( &tempstr ) ) ch_free( tempstr.bv_val );
+ if ( !BER_BVISNULL( &query.base ) ) ch_free( query.base.bv_val );
+ if ( !BER_BVISNULL( &uuid ) ) ch_free( uuid.bv_val );
+ if ( lud != NULL ) ldap_free_urldesc( lud );
+
+ return rc;
+}
+
+/* Return 1 for an added entry, else 0 */
+static int
+merge_entry(
+ Operation *op,
+ Entry *e,
+ int dup,
+ struct berval* query_uuid )
+{
+ int rc;
+ Modifications* modlist = NULL;
+ const char* text = NULL;
+ Attribute *attr;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+
+ SlapReply sreply = {REP_RESULT};
+
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+
+ if ( dup )
+ e = entry_dup( e );
+ attr = e->e_attrs;
+ e->e_attrs = NULL;
+
+ /* add queryId attribute */
+ attr_merge_one( e, ad_queryId, query_uuid, NULL );
+
+ /* append the attribute list from the fetched entry */
+ e->e_attrs->a_next = attr;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+
+ op->ora_e = e;
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ rc = op->o_bd->be_add( op, &sreply );
+
+ if ( rc != LDAP_SUCCESS ) {
+ if ( rc == LDAP_ALREADY_EXISTS ) {
+ rs_reinit( &sreply, REP_RESULT );
+ slap_entry2mods( e, &modlist, &text, textbuf, textlen );
+ modlist->sml_op = LDAP_MOD_ADD;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = modlist;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_bd->be_modify( op, &sreply );
+ slap_mods_free( modlist, 1 );
+ } else if ( rc == LDAP_REFERRAL ||
+ rc == LDAP_NO_SUCH_OBJECT ) {
+ syncrepl_add_glue( op, e );
+ e = NULL;
+ rc = 1;
+ }
+ if ( e ) {
+ entry_free( e );
+ rc = 0;
+ }
+ } else {
+ if ( op->ora_e == e )
+ entry_free( e );
+ rc = 1;
+ }
+
+ return rc;
+}
+
+/* Length-ordered sort on normalized DNs */
+static int pcache_dn_cmp( const void *v1, const void *v2 )
+{
+ const Qbase *q1 = v1, *q2 = v2;
+
+ int rc = q1->base.bv_len - q2->base.bv_len;
+ if ( rc == 0 )
+ rc = strncmp( q1->base.bv_val, q2->base.bv_val, q1->base.bv_len );
+ return rc;
+}
+
+static int lex_bvcmp( struct berval *bv1, struct berval *bv2 )
+{
+ int len, dif;
+ dif = bv1->bv_len - bv2->bv_len;
+ len = bv1->bv_len;
+ if ( dif > 0 ) len -= dif;
+ len = memcmp( bv1->bv_val, bv2->bv_val, len );
+ if ( !len )
+ len = dif;
+ return len;
+}
+
+/* compare the current value in each filter */
+static int pcache_filter_cmp( Filter *f1, Filter *f2 )
+{
+ int rc, weight1, weight2;
+
+ switch( f1->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ weight1 = 0;
+ break;
+ case LDAP_FILTER_PRESENT:
+ weight1 = 1;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ weight1 = 2;
+ break;
+ default:
+ weight1 = 3;
+ }
+ switch( f2->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ weight2 = 0;
+ break;
+ case LDAP_FILTER_PRESENT:
+ weight2 = 1;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ weight2 = 2;
+ break;
+ default:
+ weight2 = 3;
+ }
+ rc = weight1 - weight2;
+ if ( !rc ) {
+ switch( weight1 ) {
+ case 0:
+ rc = pcache_filter_cmp( f1->f_and, f2->f_and );
+ break;
+ case 1:
+ break;
+ case 2:
+ rc = lex_bvcmp( &f1->f_av_value, &f2->f_av_value );
+ break;
+ case 3:
+ if ( f1->f_choice == LDAP_FILTER_SUBSTRINGS ) {
+ rc = 0;
+ if ( !BER_BVISNULL( &f1->f_sub_initial )) {
+ if ( !BER_BVISNULL( &f2->f_sub_initial )) {
+ rc = lex_bvcmp( &f1->f_sub_initial,
+ &f2->f_sub_initial );
+ } else {
+ rc = 1;
+ }
+ } else if ( !BER_BVISNULL( &f2->f_sub_initial )) {
+ rc = -1;
+ }
+ if ( rc ) break;
+ if ( f1->f_sub_any ) {
+ if ( f2->f_sub_any ) {
+ rc = lex_bvcmp( f1->f_sub_any,
+ f2->f_sub_any );
+ } else {
+ rc = 1;
+ }
+ } else if ( f2->f_sub_any ) {
+ rc = -1;
+ }
+ if ( rc ) break;
+ if ( !BER_BVISNULL( &f1->f_sub_final )) {
+ if ( !BER_BVISNULL( &f2->f_sub_final )) {
+ rc = lex_bvcmp( &f1->f_sub_final,
+ &f2->f_sub_final );
+ } else {
+ rc = 1;
+ }
+ } else if ( !BER_BVISNULL( &f2->f_sub_final )) {
+ rc = -1;
+ }
+ } else {
+ rc = lex_bvcmp( &f1->f_mr_value,
+ &f2->f_mr_value );
+ }
+ break;
+ }
+ while ( !rc ) {
+ f1 = f1->f_next;
+ f2 = f2->f_next;
+ if ( f1 || f2 ) {
+ if ( !f1 )
+ rc = -1;
+ else if ( !f2 )
+ rc = 1;
+ else {
+ rc = pcache_filter_cmp( f1, f2 );
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+/* compare filters in each query */
+static int pcache_query_cmp( const void *v1, const void *v2 )
+{
+ const CachedQuery *q1 = v1, *q2 =v2;
+ return pcache_filter_cmp( q1->filter, q2->filter );
+}
+
+/* add query on top of LRU list */
+static void
+add_query_on_top (query_manager* qm, CachedQuery* qc)
+{
+ CachedQuery* top = qm->lru_top;
+
+ qc->in_lru = 1;
+ qm->lru_top = qc;
+
+ if (top)
+ top->lru_up = qc;
+ else
+ qm->lru_bottom = qc;
+
+ qc->lru_down = top;
+ qc->lru_up = NULL;
+ Debug( pcache_debug, "Base of added query = %s\n",
+ qc->qbase->base.bv_val );
+}
+
+/* remove_query from LRU list */
+
+static void
+remove_query (query_manager* qm, CachedQuery* qc)
+{
+ CachedQuery* up;
+ CachedQuery* down;
+
+ if (!qc || !qc->in_lru)
+ return;
+
+ qc->in_lru = 0;
+ up = qc->lru_up;
+ down = qc->lru_down;
+
+ if (!up)
+ qm->lru_top = down;
+
+ if (!down)
+ qm->lru_bottom = up;
+
+ if (down)
+ down->lru_up = up;
+
+ if (up)
+ up->lru_down = down;
+
+ qc->lru_up = qc->lru_down = NULL;
+}
+
+/* find and remove string2 from string1
+ * from start if position = 1,
+ * from end if position = 3,
+ * from anywhere if position = 2
+ * string1 is overwritten if position = 2.
+ */
+
+static int
+find_and_remove(struct berval* ber1, struct berval* ber2, int position)
+{
+ int ret=0;
+
+ if ( !ber2->bv_val )
+ return 1;
+ if ( !ber1->bv_val )
+ return 0;
+
+ switch( position ) {
+ case 1:
+ if ( ber1->bv_len >= ber2->bv_len && !memcmp( ber1->bv_val,
+ ber2->bv_val, ber2->bv_len )) {
+ ret = 1;
+ ber1->bv_val += ber2->bv_len;
+ ber1->bv_len -= ber2->bv_len;
+ }
+ break;
+ case 2: {
+ char *temp;
+ ber1->bv_val[ber1->bv_len] = '\0';
+ temp = strstr( ber1->bv_val, ber2->bv_val );
+ if ( temp ) {
+ strcpy( temp, temp+ber2->bv_len );
+ ber1->bv_len -= ber2->bv_len;
+ ret = 1;
+ }
+ break;
+ }
+ case 3:
+ if ( ber1->bv_len >= ber2->bv_len &&
+ !memcmp( ber1->bv_val+ber1->bv_len-ber2->bv_len, ber2->bv_val,
+ ber2->bv_len )) {
+ ret = 1;
+ ber1->bv_len -= ber2->bv_len;
+ }
+ break;
+ }
+ return ret;
+}
+
+
+static struct berval*
+merge_init_final(Operation *op, struct berval* init, struct berval* any,
+ struct berval* final)
+{
+ struct berval* merged, *temp;
+ int i, any_count, count;
+
+ for (any_count=0; any && any[any_count].bv_val; any_count++)
+ ;
+
+ count = any_count;
+
+ if (init->bv_val)
+ count++;
+ if (final->bv_val)
+ count++;
+
+ merged = (struct berval*)op->o_tmpalloc( (count+1)*sizeof(struct berval),
+ op->o_tmpmemctx );
+ temp = merged;
+
+ if (init->bv_val) {
+ ber_dupbv_x( temp, init, op->o_tmpmemctx );
+ temp++;
+ }
+
+ for (i=0; i<any_count; i++) {
+ ber_dupbv_x( temp, any, op->o_tmpmemctx );
+ temp++; any++;
+ }
+
+ if (final->bv_val){
+ ber_dupbv_x( temp, final, op->o_tmpmemctx );
+ temp++;
+ }
+ BER_BVZERO( temp );
+ return merged;
+}
+
+/* Each element in stored must be found in incoming. Incoming is overwritten.
+ */
+static int
+strings_containment(struct berval* stored, struct berval* incoming)
+{
+ struct berval* element;
+ int k=0;
+ int j, rc = 0;
+
+ for ( element=stored; element->bv_val != NULL; element++ ) {
+ for (j = k; incoming[j].bv_val != NULL; j++) {
+ if (find_and_remove(&(incoming[j]), element, 2)) {
+ k = j;
+ rc = 1;
+ break;
+ }
+ rc = 0;
+ }
+ if ( rc ) {
+ continue;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+substr_containment_substr(Operation *op, Filter* stored, Filter* incoming)
+{
+ int rc = 0;
+
+ struct berval init_incoming;
+ struct berval final_incoming;
+ struct berval *remaining_incoming = NULL;
+
+ if ((!(incoming->f_sub_initial.bv_val) && (stored->f_sub_initial.bv_val))
+ || (!(incoming->f_sub_final.bv_val) && (stored->f_sub_final.bv_val)))
+ return 0;
+
+ init_incoming = incoming->f_sub_initial;
+ final_incoming = incoming->f_sub_final;
+
+ if (find_and_remove(&init_incoming,
+ &(stored->f_sub_initial), 1) && find_and_remove(&final_incoming,
+ &(stored->f_sub_final), 3))
+ {
+ if (stored->f_sub_any == NULL) {
+ rc = 1;
+ goto final;
+ }
+ remaining_incoming = merge_init_final(op, &init_incoming,
+ incoming->f_sub_any, &final_incoming);
+ rc = strings_containment(stored->f_sub_any, remaining_incoming);
+ ber_bvarray_free_x( remaining_incoming, op->o_tmpmemctx );
+ }
+final:
+ return rc;
+}
+
+static int
+substr_containment_equality(Operation *op, Filter* stored, Filter* incoming)
+{
+ struct berval incoming_val[2];
+ int rc = 0;
+
+ incoming_val[1] = incoming->f_av_value;
+
+ if (find_and_remove(incoming_val+1,
+ &(stored->f_sub_initial), 1) && find_and_remove(incoming_val+1,
+ &(stored->f_sub_final), 3)) {
+ if (stored->f_sub_any == NULL){
+ rc = 1;
+ goto final;
+ }
+ ber_dupbv_x( incoming_val, incoming_val+1, op->o_tmpmemctx );
+ BER_BVZERO( incoming_val+1 );
+ rc = strings_containment(stored->f_sub_any, incoming_val);
+ op->o_tmpfree( incoming_val[0].bv_val, op->o_tmpmemctx );
+ }
+final:
+ return rc;
+}
+
+static Filter *
+filter_first( Filter *f )
+{
+ while ( f->f_choice == LDAP_FILTER_OR || f->f_choice == LDAP_FILTER_AND )
+ f = f->f_and;
+ return f;
+}
+
+typedef struct fstack {
+ struct fstack *fs_next;
+ Filter *fs_fs;
+ Filter *fs_fi;
+} fstack;
+
+static CachedQuery *
+find_filter( Operation *op, TAvlnode *root, Filter *inputf, Filter *first )
+{
+ Filter* fs;
+ Filter* fi;
+ MatchingRule* mrule = NULL;
+ int res=0, eqpass= 0;
+ int ret, rc, dir;
+ TAvlnode *ptr;
+ CachedQuery cq, *qc;
+ fstack *stack = NULL, *fsp;
+
+ cq.filter = inputf;
+ cq.first = first;
+
+ /* substring matches sort to the end, and we just have to
+ * walk the entire list.
+ */
+ if ( first->f_choice == LDAP_FILTER_SUBSTRINGS ) {
+ ptr = ldap_tavl_end( root, 1 );
+ dir = TAVL_DIR_LEFT;
+ } else {
+ ptr = ldap_tavl_find3( root, &cq, pcache_query_cmp, &ret );
+ dir = (first->f_choice == LDAP_FILTER_GE) ? TAVL_DIR_LEFT :
+ TAVL_DIR_RIGHT;
+ }
+
+ while (ptr) {
+ qc = ptr->avl_data;
+ fi = inputf;
+ fs = qc->filter;
+
+ /* an incoming substr query can only be satisfied by a cached
+ * substr query.
+ */
+ if ( first->f_choice == LDAP_FILTER_SUBSTRINGS &&
+ qc->first->f_choice != LDAP_FILTER_SUBSTRINGS )
+ break;
+
+ /* an incoming eq query can be satisfied by a cached eq or substr
+ * query
+ */
+ if ( first->f_choice == LDAP_FILTER_EQUALITY ) {
+ if ( eqpass == 0 ) {
+ if ( qc->first->f_choice != LDAP_FILTER_EQUALITY ) {
+nextpass: eqpass = 1;
+ ptr = ldap_tavl_end( root, 1 );
+ dir = TAVL_DIR_LEFT;
+ continue;
+ }
+ } else {
+ if ( qc->first->f_choice != LDAP_FILTER_SUBSTRINGS )
+ break;
+ }
+ }
+ do {
+ res=0;
+ switch (fs->f_choice) {
+ case LDAP_FILTER_EQUALITY:
+ if (fi->f_choice == LDAP_FILTER_EQUALITY)
+ mrule = fs->f_ava->aa_desc->ad_type->sat_equality;
+ else
+ ret = 1;
+ break;
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ mrule = fs->f_ava->aa_desc->ad_type->sat_ordering;
+ break;
+ default:
+ mrule = NULL;
+ }
+ if (mrule) {
+ const char *text;
+ rc = value_match(&ret, fs->f_ava->aa_desc, mrule,
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &(fi->f_ava->aa_value),
+ &(fs->f_ava->aa_value), &text);
+ if (rc != LDAP_SUCCESS) {
+ return NULL;
+ }
+ if ( fi==first && fi->f_choice==LDAP_FILTER_EQUALITY && ret )
+ goto nextpass;
+ }
+ switch (fs->f_choice) {
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_AND:
+ if ( fs->f_next ) {
+ /* save our stack position */
+ fsp = op->o_tmpalloc(sizeof(fstack), op->o_tmpmemctx);
+ fsp->fs_next = stack;
+ fsp->fs_fs = fs->f_next;
+ fsp->fs_fi = fi->f_next;
+ stack = fsp;
+ }
+ fs = fs->f_and;
+ fi = fi->f_and;
+ res=1;
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ /* check if the equality query can be
+ * answered with cached substring query */
+ if ((fi->f_choice == LDAP_FILTER_EQUALITY)
+ && substr_containment_equality( op,
+ fs, fi))
+ res=1;
+ /* check if the substring query can be
+ * answered with cached substring query */
+ if ((fi->f_choice ==LDAP_FILTER_SUBSTRINGS
+ ) && substr_containment_substr( op,
+ fs, fi))
+ res= 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_PRESENT:
+ res=1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ if (ret == 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_GE:
+ if (mrule && ret >= 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_LE:
+ if (mrule && ret <= 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_NOT:
+ res=0;
+ break;
+ default:
+ break;
+ }
+ if (!fs && !fi && stack) {
+ /* pop the stack */
+ fsp = stack;
+ stack = fsp->fs_next;
+ fs = fsp->fs_fs;
+ fi = fsp->fs_fi;
+ op->o_tmpfree(fsp, op->o_tmpmemctx);
+ }
+ } while((res) && (fi != NULL) && (fs != NULL));
+
+ if ( res )
+ return qc;
+ ptr = ldap_tavl_next( ptr, dir );
+ }
+ return NULL;
+}
+
+/* check whether query is contained in any of
+ * the cached queries in template
+ */
+static CachedQuery *
+query_containment(Operation *op, query_manager *qm,
+ Query *query,
+ QueryTemplate *templa)
+{
+ CachedQuery* qc;
+ int depth = 0, tscope;
+ Qbase qbase, *qbptr = NULL;
+ struct berval pdn;
+
+ if (query->filter != NULL) {
+ Filter *first;
+
+ Debug( pcache_debug, "Lock QC index = %p\n",
+ (void *) templa );
+ qbase.base = query->base;
+
+ first = filter_first( query->filter );
+
+ ldap_pvt_thread_rdwr_rlock(&templa->t_rwlock);
+ for( ;; ) {
+ /* Find the base */
+ qbptr = ldap_avl_find( templa->qbase, &qbase, pcache_dn_cmp );
+ if ( qbptr ) {
+ tscope = query->scope;
+ /* Find a matching scope:
+ * match at depth 0 OK
+ * scope is BASE,
+ * one at depth 1 OK
+ * subord at depth > 0 OK
+ * subtree at any depth OK
+ * scope is ONE,
+ * subtree or subord at any depth OK
+ * scope is SUBORD,
+ * subtree or subord at any depth OK
+ * scope is SUBTREE,
+ * subord at depth > 0 OK
+ * subtree at any depth OK
+ */
+ for ( tscope = 0 ; tscope <= LDAP_SCOPE_CHILDREN; tscope++ ) {
+ switch ( query->scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( tscope == LDAP_SCOPE_BASE && depth ) continue;
+ if ( tscope == LDAP_SCOPE_ONE && depth != 1) continue;
+ if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue;
+ break;
+ case LDAP_SCOPE_ONE:
+ if ( tscope == LDAP_SCOPE_BASE )
+ tscope = LDAP_SCOPE_ONE;
+ if ( tscope == LDAP_SCOPE_ONE && depth ) continue;
+ if ( !depth ) break;
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue;
+ break;
+ case LDAP_SCOPE_CHILDREN:
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ break;
+ }
+ if ( !qbptr->scopes[tscope] ) continue;
+
+ /* Find filter */
+ qc = find_filter( op, qbptr->scopes[tscope],
+ query->filter, first );
+ if ( qc ) {
+ if ( qc->q_sizelimit ) {
+ ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock);
+ return NULL;
+ }
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if (qm->lru_top != qc) {
+ remove_query(qm, qc);
+ add_query_on_top(qm, qc);
+ }
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ return qc;
+ }
+ }
+ }
+ if ( be_issuffix( op->o_bd, &qbase.base ))
+ break;
+ /* Up a level */
+ dnParent( &qbase.base, &pdn );
+ qbase.base = pdn;
+ depth++;
+ }
+
+ Debug( pcache_debug,
+ "Not answerable: Unlock QC index=%p\n",
+ (void *) templa );
+ ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock);
+ }
+ return NULL;
+}
+
+static void
+free_query (CachedQuery* qc)
+{
+ free(qc->q_uuid.bv_val);
+ filter_free(qc->filter);
+ ldap_pvt_thread_mutex_destroy(&qc->answerable_cnt_mutex);
+ ldap_pvt_thread_rdwr_destroy( &qc->rwlock );
+ memset(qc, 0, sizeof(*qc));
+ free(qc);
+}
+
+
+/* Add query to query cache, the returned Query is locked for writing */
+static CachedQuery *
+add_query(
+ Operation *op,
+ query_manager* qm,
+ Query* query,
+ QueryTemplate *templ,
+ pc_caching_reason_t why,
+ int wlock)
+{
+ CachedQuery* new_cached_query = (CachedQuery*) ch_malloc(sizeof(CachedQuery));
+ Qbase *qbase, qb;
+ Filter *first;
+ int rc;
+ time_t ttl = 0, ttr = 0;
+ time_t now;
+
+ new_cached_query->qtemp = templ;
+ BER_BVZERO( &new_cached_query->q_uuid );
+ new_cached_query->q_sizelimit = 0;
+
+ now = slap_get_time();
+ switch ( why ) {
+ case PC_POSITIVE:
+ ttl = templ->ttl;
+ if ( templ->ttr )
+ ttr = now + templ->ttr;
+ break;
+
+ case PC_NEGATIVE:
+ ttl = templ->negttl;
+ break;
+
+ case PC_SIZELIMIT:
+ ttl = templ->limitttl;
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ new_cached_query->expiry_time = now + ttl;
+ new_cached_query->refresh_time = ttr;
+ new_cached_query->bindref_time = 0;
+
+ new_cached_query->bind_refcnt = 0;
+ new_cached_query->answerable_cnt = 0;
+ new_cached_query->refcnt = 1;
+ ldap_pvt_thread_mutex_init(&new_cached_query->answerable_cnt_mutex);
+
+ new_cached_query->lru_up = NULL;
+ new_cached_query->lru_down = NULL;
+ Debug( pcache_debug, "Added query expires at %ld (%s)\n",
+ (long) new_cached_query->expiry_time,
+ pc_caching_reason_str[ why ] );
+
+ new_cached_query->scope = query->scope;
+ new_cached_query->filter = query->filter;
+ new_cached_query->first = first = filter_first( query->filter );
+
+ ldap_pvt_thread_rdwr_init(&new_cached_query->rwlock);
+ if (wlock)
+ ldap_pvt_thread_rdwr_wlock(&new_cached_query->rwlock);
+
+ qb.base = query->base;
+
+ /* Adding a query */
+ Debug( pcache_debug, "Lock AQ index = %p\n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ qbase = ldap_avl_find( templ->qbase, &qb, pcache_dn_cmp );
+ if ( !qbase ) {
+ qbase = ch_calloc( 1, sizeof(Qbase) + qb.base.bv_len + 1 );
+ qbase->base.bv_len = qb.base.bv_len;
+ qbase->base.bv_val = (char *)(qbase+1);
+ memcpy( qbase->base.bv_val, qb.base.bv_val, qb.base.bv_len );
+ qbase->base.bv_val[qbase->base.bv_len] = '\0';
+ ldap_avl_insert( &templ->qbase, qbase, pcache_dn_cmp, ldap_avl_dup_error );
+ }
+ new_cached_query->next = templ->query;
+ new_cached_query->prev = NULL;
+ new_cached_query->qbase = qbase;
+ rc = ldap_tavl_insert( &qbase->scopes[query->scope], new_cached_query,
+ pcache_query_cmp, ldap_avl_dup_error );
+ if ( rc == 0 ) {
+ qbase->queries++;
+ if (templ->query == NULL)
+ templ->query_last = new_cached_query;
+ else
+ templ->query->prev = new_cached_query;
+ templ->query = new_cached_query;
+ templ->no_of_queries++;
+ } else {
+ ldap_pvt_thread_mutex_destroy(&new_cached_query->answerable_cnt_mutex);
+ if (wlock)
+ ldap_pvt_thread_rdwr_wunlock(&new_cached_query->rwlock);
+ ldap_pvt_thread_rdwr_destroy( &new_cached_query->rwlock );
+ ch_free( new_cached_query );
+ new_cached_query = find_filter( op, qbase->scopes[query->scope],
+ query->filter, first );
+ filter_free( query->filter );
+ query->filter = NULL;
+ }
+ Debug( pcache_debug, "TEMPLATE %p QUERIES++ %d\n",
+ (void *) templ, templ->no_of_queries );
+
+ /* Adding on top of LRU list */
+ if ( rc == 0 ) {
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ add_query_on_top(qm, new_cached_query);
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ }
+ Debug( pcache_debug, "Unlock AQ index = %p \n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+
+ return rc == 0 ? new_cached_query : NULL;
+}
+
+static void
+remove_from_template (CachedQuery* qc, QueryTemplate* template)
+{
+ if (!qc->prev && !qc->next) {
+ template->query_last = template->query = NULL;
+ } else if (qc->prev == NULL) {
+ qc->next->prev = NULL;
+ template->query = qc->next;
+ } else if (qc->next == NULL) {
+ qc->prev->next = NULL;
+ template->query_last = qc->prev;
+ } else {
+ qc->next->prev = qc->prev;
+ qc->prev->next = qc->next;
+ }
+ ldap_tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp );
+ qc->qbase->queries--;
+ if ( qc->qbase->queries == 0 ) {
+ ldap_avl_delete( &template->qbase, qc->qbase, pcache_dn_cmp );
+ ch_free( qc->qbase );
+ qc->qbase = NULL;
+ }
+
+ template->no_of_queries--;
+}
+
+/* remove bottom query of LRU list from the query cache */
+/*
+ * NOTE: slight change in functionality.
+ *
+ * - if result->bv_val is NULL, the query at the bottom of the LRU
+ * is removed
+ * - otherwise, the query whose UUID is *result is removed
+ * - if not found, result->bv_val is zeroed
+ */
+static void
+cache_replacement(query_manager* qm, struct berval *result)
+{
+ CachedQuery* bottom;
+ QueryTemplate *temp;
+
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if ( BER_BVISNULL( result ) ) {
+ bottom = qm->lru_bottom;
+
+ if (!bottom) {
+ Debug ( pcache_debug,
+ "Cache replacement invoked without "
+ "any query in LRU list\n" );
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ return;
+ }
+
+ } else {
+ for ( bottom = qm->lru_bottom;
+ bottom != NULL;
+ bottom = bottom->lru_up )
+ {
+ if ( bvmatch( result, &bottom->q_uuid ) ) {
+ break;
+ }
+ }
+
+ if ( !bottom ) {
+ Debug ( pcache_debug,
+ "Could not find query with uuid=\"%s\""
+ "in LRU list\n", result->bv_val );
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ BER_BVZERO( result );
+ return;
+ }
+ }
+
+ temp = bottom->qtemp;
+ remove_query(qm, bottom);
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+
+ *result = bottom->q_uuid;
+ BER_BVZERO( &bottom->q_uuid );
+
+ Debug( pcache_debug, "Lock CR index = %p\n", (void *) temp );
+ ldap_pvt_thread_rdwr_wlock(&temp->t_rwlock);
+ remove_from_template(bottom, temp);
+ Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n",
+ (void *) temp, temp->no_of_queries );
+ Debug( pcache_debug, "Unlock CR index = %p\n", (void *) temp );
+ ldap_pvt_thread_rdwr_wunlock(&temp->t_rwlock);
+ free_query(bottom);
+}
+
+struct query_info {
+ struct query_info *next;
+ struct berval xdn;
+ int del;
+};
+
+static int
+remove_func (
+ Operation *op,
+ SlapReply *rs
+)
+{
+ Attribute *attr;
+ struct query_info *qi;
+ int count = 0;
+
+ if ( rs->sr_type != REP_SEARCH ) return 0;
+
+ attr = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ if ( attr == NULL ) return 0;
+
+ count = attr->a_numvals;
+ assert( count > 0 );
+ qi = op->o_tmpalloc( sizeof( struct query_info ), op->o_tmpmemctx );
+ qi->next = op->o_callback->sc_private;
+ op->o_callback->sc_private = qi;
+ ber_dupbv_x( &qi->xdn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ qi->del = ( count == 1 );
+
+ return 0;
+}
+
+static int
+remove_query_data(
+ Operation *op,
+ struct berval *query_uuid )
+{
+ struct query_info *qi, *qnext;
+ char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ Filter filter = {LDAP_FILTER_EQUALITY};
+ SlapReply sreply = {REP_RESULT};
+ slap_callback cb = { NULL, remove_func, NULL, NULL };
+ int deleted = 0;
+
+ op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str),
+ "(%s=%s)", ad_queryId->ad_cname.bv_val, query_uuid->bv_val);
+ filter.f_ava = &ava;
+ filter.f_av_desc = ad_queryId;
+ filter.f_av_value = *query_uuid;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ op->ors_filter = &filter;
+ op->ors_filterstr.bv_val = filter_str;
+ op->ors_filterstr.bv_len = strlen(filter_str);
+ op->ors_attrs = NULL;
+ op->ors_attrsonly = 0;
+
+ op->o_bd->be_search( op, &sreply );
+
+ for ( qi=cb.sc_private; qi; qi=qnext ) {
+ qnext = qi->next;
+
+ op->o_req_dn = qi->xdn;
+ op->o_req_ndn = qi->xdn;
+ rs_reinit( &sreply, REP_RESULT );
+
+ if ( qi->del ) {
+ Debug( pcache_debug, "DELETING ENTRY TEMPLATE=%s\n",
+ query_uuid->bv_val );
+
+ op->o_tag = LDAP_REQ_DELETE;
+
+ if (op->o_bd->be_delete(op, &sreply) == LDAP_SUCCESS) {
+ deleted++;
+ }
+
+ } else {
+ Modifications mod;
+ struct berval vals[2];
+
+ vals[0] = *query_uuid;
+ vals[1].bv_val = NULL;
+ vals[1].bv_len = 0;
+ mod.sml_op = LDAP_MOD_DELETE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_queryId;
+ mod.sml_type = ad_queryId->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+ Debug( pcache_debug,
+ "REMOVING TEMP ATTR : TEMPLATE=%s\n",
+ query_uuid->bv_val );
+
+ op->orm_modlist = &mod;
+
+ op->o_bd->be_modify( op, &sreply );
+ }
+ op->o_tmpfree( qi->xdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( qi, op->o_tmpmemctx );
+ }
+ return deleted;
+}
+
+static int
+get_attr_set(
+ AttributeName* attrs,
+ query_manager* qm,
+ int num
+);
+
+static int
+filter2template(
+ Operation *op,
+ Filter *f,
+ struct berval *fstr )
+{
+ AttributeDescription *ad;
+ int len, ret;
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_GE:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(>=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s>=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_LE:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(<=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s<=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_APPROX:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(~=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s~=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ ad = f->f_sub_desc;
+ len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ len = STRLENOF( "(=*)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=*)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ int rc = 0;
+ fstr->bv_val[fstr->bv_len++] = '(';
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ fstr->bv_val[fstr->bv_len] = '&';
+ break;
+ case LDAP_FILTER_OR:
+ fstr->bv_val[fstr->bv_len] = '|';
+ break;
+ case LDAP_FILTER_NOT:
+ fstr->bv_val[fstr->bv_len] = '!';
+ break;
+ }
+ fstr->bv_len++;
+
+ for ( f = f->f_list; f != NULL; f = f->f_next ) {
+ rc = filter2template( op, f, fstr );
+ if ( rc ) break;
+ }
+ fstr->bv_val[fstr->bv_len++] = ')';
+ fstr->bv_val[fstr->bv_len] = '\0';
+
+ return rc;
+ }
+
+ default:
+ /* a filter should at least have room for "()",
+ * an "=" and for a 1-char attr */
+ strcpy( fstr->bv_val, "(?=)" );
+ fstr->bv_len += STRLENOF("(?=)");
+ return -1;
+ }
+
+ return 0;
+}
+
+#define BI_HASHED 0x01
+#define BI_DIDCB 0x02
+#define BI_LOOKUP 0x04
+
+struct search_info;
+
+typedef struct bindinfo {
+ cache_manager *bi_cm;
+ CachedQuery *bi_cq;
+ QueryTemplate *bi_templ;
+ struct search_info *bi_si;
+ int bi_flags;
+ slap_callback bi_cb;
+} bindinfo;
+
+struct search_info {
+ slap_overinst *on;
+ Query query;
+ QueryTemplate *qtemp;
+ AttributeName* save_attrs; /* original attributes, saved for response */
+ int swap_saved_attrs;
+ int max;
+ int over;
+ int count;
+ int slimit;
+ int slimit_exceeded;
+ pc_caching_reason_t caching_reason;
+ Entry *head, *tail;
+ bindinfo *pbi;
+};
+
+static void
+remove_query_and_data(
+ Operation *op,
+ cache_manager *cm,
+ struct berval *uuid )
+{
+ query_manager* qm = cm->qm;
+
+ qm->crfunc( qm, uuid );
+ if ( !BER_BVISNULL( uuid ) ) {
+ int return_val;
+
+ Debug( pcache_debug,
+ "Removing query UUID %s\n",
+ uuid->bv_val );
+ return_val = remove_query_data( op, uuid );
+ Debug( pcache_debug,
+ "QUERY REMOVED, SIZE=%d\n",
+ return_val );
+ ldap_pvt_thread_mutex_lock( &cm->cache_mutex );
+ cm->cur_entries -= return_val;
+ cm->num_cached_queries--;
+ Debug( pcache_debug,
+ "STORED QUERIES = %lu\n",
+ cm->num_cached_queries );
+ ldap_pvt_thread_mutex_unlock( &cm->cache_mutex );
+ Debug( pcache_debug,
+ "QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries );
+ }
+}
+
+/*
+ * Callback used to fetch queryId values based on entryUUID;
+ * used by pcache_remove_entries_from_cache()
+ */
+static int
+fetch_queryId_cb( Operation *op, SlapReply *rs )
+{
+ int rc = 0;
+
+ /* only care about searchEntry responses */
+ if ( rs->sr_type != REP_SEARCH ) {
+ return 0;
+ }
+
+ /* allow only one response per entryUUID */
+ if ( op->o_callback->sc_private != NULL ) {
+ rc = 1;
+
+ } else {
+ Attribute *a;
+
+ /* copy all queryId values into callback's private data */
+ a = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ if ( a != NULL ) {
+ BerVarray vals = NULL;
+
+ ber_bvarray_dup_x( &vals, a->a_nvals, op->o_tmpmemctx );
+ op->o_callback->sc_private = (void *)vals;
+ }
+ }
+
+ /* clear entry if required */
+ rs_flush_entry( op, rs, (slap_overinst *) op->o_bd->bd_info );
+
+ return rc;
+}
+
+/*
+ * Call that allows to remove a set of entries from the cache,
+ * by forcing the removal of all the related queries.
+ */
+int
+pcache_remove_entries_from_cache(
+ Operation *op,
+ cache_manager *cm,
+ BerVarray entryUUIDs )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation op2;
+ slap_callback sc = { 0 };
+ Filter f = { 0 };
+ char filtbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(entryUUID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+ int s, rc;
+
+ if ( op == NULL ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ } else {
+ op2 = *op;
+ op = &op2;
+ }
+
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ f.f_choice = LDAP_FILTER_EQUALITY;
+ f.f_ava = &ava;
+ ava.aa_desc = slap_schema.si_ad_entryUUID;
+ op->ors_filter = &f;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ attrs[ 0 ].an_desc = ad_queryId;
+ attrs[ 0 ].an_name = ad_queryId->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+
+ op->o_req_dn = cm->db.be_suffix[ 0 ];
+ op->o_req_ndn = cm->db.be_nsuffix[ 0 ];
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_bd = &cm->db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ sc.sc_response = fetch_queryId_cb;
+ op->o_callback = &sc;
+
+ for ( s = 0; !BER_BVISNULL( &entryUUIDs[ s ] ); s++ ) {
+ BerVarray vals = NULL;
+ SlapReply rs = { REP_RESULT };
+
+ op->ors_filterstr.bv_len = snprintf( filtbuf, sizeof( filtbuf ),
+ "(entryUUID=%s)", entryUUIDs[ s ].bv_val );
+ op->ors_filterstr.bv_val = filtbuf;
+ ava.aa_value = entryUUIDs[ s ];
+
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ vals = (BerVarray)op->o_callback->sc_private;
+ if ( vals != NULL ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ struct berval val = vals[ i ];
+
+ remove_query_and_data( op, cm, &val );
+
+ if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) {
+ ch_free( val.bv_val );
+ }
+ }
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ op->o_callback->sc_private = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Call that allows to remove a query from the cache.
+ */
+int
+pcache_remove_query_from_cache(
+ Operation *op,
+ cache_manager *cm,
+ struct berval *queryid )
+{
+ Operation op2 = *op;
+
+ op2.o_bd = &cm->db;
+
+ /* remove the selected query */
+ remove_query_and_data( &op2, cm, queryid );
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Call that allows to remove a set of queries related to an entry
+ * from the cache; if queryid is not null, the entry must belong to
+ * the query indicated by queryid.
+ */
+int
+pcache_remove_entry_queries_from_cache(
+ Operation *op,
+ cache_manager *cm,
+ struct berval *ndn,
+ struct berval *queryid )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation op2;
+ slap_callback sc = { 0 };
+ SlapReply rs = { REP_RESULT };
+ Filter f = { 0 };
+ char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+ int rc;
+
+ BerVarray vals = NULL;
+
+ if ( op == NULL ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ } else {
+ op2 = *op;
+ op = &op2;
+ }
+
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+ op->ors_scope = LDAP_SCOPE_BASE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ if ( queryid == NULL || BER_BVISNULL( queryid ) ) {
+ BER_BVSTR( &op->ors_filterstr, "(objectClass=*)" );
+ f.f_choice = LDAP_FILTER_PRESENT;
+ f.f_desc = slap_schema.si_ad_objectClass;
+
+ } else {
+ op->ors_filterstr.bv_len = snprintf( filter_str,
+ sizeof( filter_str ), "(%s=%s)",
+ ad_queryId->ad_cname.bv_val, queryid->bv_val );
+ f.f_choice = LDAP_FILTER_EQUALITY;
+ f.f_ava = &ava;
+ f.f_av_desc = ad_queryId;
+ f.f_av_value = *queryid;
+ }
+ op->ors_filter = &f;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ attrs[ 0 ].an_desc = ad_queryId;
+ attrs[ 0 ].an_name = ad_queryId->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+
+ op->o_req_dn = *ndn;
+ op->o_req_ndn = *ndn;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_bd = &cm->db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ sc.sc_response = fetch_queryId_cb;
+ op->o_callback = &sc;
+
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ vals = (BerVarray)op->o_callback->sc_private;
+ if ( vals != NULL ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ struct berval val = vals[ i ];
+
+ remove_query_and_data( op, cm, &val );
+
+ if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) {
+ ch_free( val.bv_val );
+ }
+ }
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+cache_entries(
+ Operation *op,
+ struct berval *query_uuid )
+{
+ struct search_info *si = op->o_callback->sc_private;
+ slap_overinst *on = si->on;
+ cache_manager *cm = on->on_bi.bi_private;
+ int return_val = 0;
+ Entry *e;
+ struct berval crp_uuid;
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+ Operation *op_tmp;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ query_uuid->bv_len = lutil_uuidstr(uuidbuf, sizeof(uuidbuf));
+ ber_str2bv(uuidbuf, query_uuid->bv_len, 1, query_uuid);
+
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op_tmp = &opbuf.ob_op;
+ op_tmp->o_bd = &cm->db;
+ op_tmp->o_dn = cm->db.be_rootdn;
+ op_tmp->o_ndn = cm->db.be_rootndn;
+
+ Debug( pcache_debug, "UUID for query being added = %s\n",
+ uuidbuf );
+
+ for ( e=si->head; e; e=si->head ) {
+ si->head = e->e_private;
+ e->e_private = NULL;
+ while ( cm->cur_entries > (cm->max_entries) ) {
+ BER_BVZERO( &crp_uuid );
+ remove_query_and_data( op_tmp, cm, &crp_uuid );
+ }
+
+ return_val = merge_entry(op_tmp, e, 0, query_uuid);
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->cur_entries += return_val;
+ Debug( pcache_debug,
+ "ENTRY ADDED/MERGED, CACHED ENTRIES=%d\n",
+ cm->cur_entries );
+ return_val = 0;
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+ }
+
+ return return_val;
+}
+
+static int
+pcache_op_cleanup( Operation *op, SlapReply *rs ) {
+ slap_callback *cb = op->o_callback;
+ struct search_info *si = cb->sc_private;
+ slap_overinst *on = si->on;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+
+ if ( rs->sr_type == REP_RESULT ||
+ op->o_abandon || rs->sr_err == SLAPD_ABANDON )
+ {
+ if ( si->swap_saved_attrs ) {
+ rs->sr_attrs = si->save_attrs;
+ op->ors_attrs = si->save_attrs;
+ }
+ if ( (op->o_abandon || rs->sr_err == SLAPD_ABANDON) &&
+ si->caching_reason == PC_IGNORE )
+ {
+ filter_free( si->query.filter );
+ if ( si->count ) {
+ /* duplicate query, free it */
+ Entry *e;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else if ( si->caching_reason != PC_IGNORE ) {
+ CachedQuery *qc = qm->addfunc(op, qm, &si->query,
+ si->qtemp, si->caching_reason, 1 );
+
+ if ( qc != NULL ) {
+ switch ( si->caching_reason ) {
+ case PC_POSITIVE:
+ cache_entries( op, &qc->q_uuid );
+ if ( si->pbi ) {
+ qc->bind_refcnt++;
+ si->pbi->bi_cq = qc;
+ }
+ break;
+
+ case PC_SIZELIMIT:
+ qc->q_sizelimit = rs->sr_nentries;
+ break;
+
+ case PC_NEGATIVE:
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ ldap_pvt_thread_rdwr_wunlock(&qc->rwlock);
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->num_cached_queries++;
+ Debug( pcache_debug, "STORED QUERIES = %lu\n",
+ cm->num_cached_queries );
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+
+ /* If the consistency checker suspended itself,
+ * wake it back up
+ */
+ if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
+ cm->cc_paused = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ } else if ( si->count ) {
+ /* duplicate query, free it */
+ Entry *e;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else {
+ filter_free( si->query.filter );
+ }
+
+ op->o_callback = op->o_callback->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct search_info *si = op->o_callback->sc_private;
+
+ if ( si->swap_saved_attrs ) {
+ rs->sr_attrs = si->save_attrs;
+ rs->sr_attr_flags = slap_attr_flags( si->save_attrs );
+ op->ors_attrs = si->save_attrs;
+ }
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ Entry *e;
+
+ /* don't return more entries than requested by the client */
+ if ( si->slimit > 0 && rs->sr_nentries >= si->slimit ) {
+ si->slimit_exceeded = 1;
+ }
+
+ /* If we haven't exceeded the limit for this query,
+ * build a chain of answers to store. If we hit the
+ * limit, empty the chain and ignore the rest.
+ */
+ if ( !si->over ) {
+ slap_overinst *on = si->on;
+ cache_manager *cm = on->on_bi.bi_private;
+
+ /* check if the entry contains undefined
+ * attributes/objectClasses (ITS#5680) */
+ if ( cm->check_cacheability && test_filter( op, rs->sr_entry, si->query.filter ) != LDAP_COMPARE_TRUE ) {
+ Debug( pcache_debug, "%s: query not cacheable because of schema issues in DN \"%s\"\n",
+ op->o_log_prefix, rs->sr_entry->e_name.bv_val );
+ goto over;
+ }
+
+ /* check for malformed entries: attrs with no values */
+ {
+ Attribute *a = rs->sr_entry->e_attrs;
+ for (; a; a=a->a_next) {
+ if ( !a->a_numvals ) {
+ Debug( pcache_debug, "%s: query not cacheable because of attrs without values in DN \"%s\" (%s)\n",
+ op->o_log_prefix, rs->sr_entry->e_name.bv_val,
+ a->a_desc->ad_cname.bv_val );
+ goto over;
+ }
+ }
+ }
+
+ if ( si->count < si->max ) {
+ si->count++;
+ e = entry_dup( rs->sr_entry );
+ if ( !si->head ) si->head = e;
+ if ( si->tail ) si->tail->e_private = e;
+ si->tail = e;
+
+ } else {
+over:;
+ si->over = 1;
+ si->count = 0;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ si->tail = NULL;
+ }
+ }
+ if ( si->slimit_exceeded ) {
+ return 0;
+ }
+ } else if ( rs->sr_type == REP_RESULT ) {
+
+ if ( si->count ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ si->caching_reason = PC_POSITIVE;
+
+ } else if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED
+ && si->qtemp->limitttl )
+ {
+ Entry *e;
+
+ si->caching_reason = PC_SIZELIMIT;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else if ( si->qtemp->negttl && !si->count && !si->over &&
+ rs->sr_err == LDAP_SUCCESS )
+ {
+ si->caching_reason = PC_NEGATIVE;
+ }
+
+
+ if ( si->slimit_exceeded ) {
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* NOTE: this is a quick workaround to let pcache minimally interact
+ * with pagedResults. A more articulated solutions would be to
+ * perform the remote query without control and cache all results,
+ * performing the pagedResults search only within the client
+ * and the proxy. This requires pcache to understand pagedResults. */
+static int
+pcache_chk_controls(
+ Operation *op,
+ SlapReply *rs )
+{
+ const char *non = "";
+ const char *stripped = "";
+
+ switch( op->o_pagedresults ) {
+ case SLAP_CONTROL_NONCRITICAL:
+ non = "non-";
+ stripped = "; stripped";
+ /* fallthru */
+
+ case SLAP_CONTROL_CRITICAL:
+ Debug( pcache_debug, "%s: "
+ "%scritical pagedResults control "
+ "disabled with proxy cache%s.\n",
+ op->o_log_prefix, non, stripped );
+
+ slap_remove_control( op, rs, slap_cids.sc_pagedResults, NULL );
+ break;
+
+ default:
+ rs->sr_err = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+pc_setpw( Operation *op, struct berval *pwd, cache_manager *cm )
+{
+ struct berval vals[2];
+
+ {
+ const char *text = NULL;
+ BER_BVZERO( &vals[0] );
+ slap_passwd_hash( pwd, &vals[0], &text );
+ if ( BER_BVISEMPTY( &vals[0] )) {
+ Debug( pcache_debug, "pc_setpw: hash failed %s\n",
+ text );
+ return LDAP_OTHER;
+ }
+ }
+
+ BER_BVZERO( &vals[1] );
+
+ {
+ Modifications mod;
+ SlapReply sr = { REP_RESULT };
+ slap_callback cb = { 0, slap_null_cb, 0, 0 };
+ int rc;
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = 0;
+ mod.sml_desc = slap_schema.si_ad_userPassword;
+ mod.sml_type = mod.sml_desc->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->o_bd = &cm->db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_callback = &cb;
+ Debug( pcache_debug, "pc_setpw: CACHING BIND for %s\n",
+ op->o_req_dn.bv_val );
+ rc = op->o_bd->be_modify( op, &sr );
+ ch_free( vals[0].bv_val );
+ return rc;
+ }
+}
+
+typedef struct bindcacheinfo {
+ slap_overinst *on;
+ CachedQuery *qc;
+} bindcacheinfo;
+
+static int
+pc_bind_save( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ bindcacheinfo *bci = op->o_callback->sc_private;
+ slap_overinst *on = bci->on;
+ cache_manager *cm = on->on_bi.bi_private;
+ CachedQuery *qc = bci->qc;
+ int delete = 0;
+
+ ldap_pvt_thread_rdwr_wlock( &qc->rwlock );
+ if ( qc->bind_refcnt-- ) {
+ Operation op2 = *op;
+ if ( pc_setpw( &op2, &op->orb_cred, cm ) == LDAP_SUCCESS )
+ bci->qc->bindref_time = op->o_time + bci->qc->qtemp->bindttr;
+ } else {
+ bci->qc = NULL;
+ delete = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &qc->rwlock );
+ if ( delete ) free_query(qc);
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static Filter *
+pc_bind_attrs( Operation *op, Entry *e, QueryTemplate *temp,
+ struct berval *fbv )
+{
+ int i, len = 0;
+ struct berval *vals, pres = BER_BVC("*");
+ char *p1, *p2;
+ Attribute *a;
+
+ vals = op->o_tmpalloc( temp->bindnattrs * sizeof( struct berval ),
+ op->o_tmpmemctx );
+
+ for ( i=0; i<temp->bindnattrs; i++ ) {
+ a = attr_find( e->e_attrs, temp->bindfattrs[i] );
+ if ( a && a->a_vals ) {
+ vals[i] = a->a_vals[0];
+ len += a->a_vals[0].bv_len;
+ } else {
+ vals[i] = pres;
+ }
+ }
+ fbv->bv_len = len + temp->bindftemp.bv_len;
+ fbv->bv_val = op->o_tmpalloc( fbv->bv_len + 1, op->o_tmpmemctx );
+
+ p1 = temp->bindftemp.bv_val;
+ p2 = fbv->bv_val;
+ i = 0;
+ while ( *p1 ) {
+ *p2++ = *p1;
+ if ( p1[0] == '=' && p1[1] == ')' ) {
+ AC_MEMCPY( p2, vals[i].bv_val, vals[i].bv_len );
+ p2 += vals[i].bv_len;
+ i++;
+ }
+ p1++;
+ }
+ *p2 = '\0';
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+
+ /* FIXME: are we sure str2filter_x can't fail?
+ * caller needs to check */
+ {
+ Filter *f = str2filter_x( op, fbv->bv_val );
+ assert( f != NULL );
+ return f;
+ }
+}
+
+/* Check if the requested entry is from the cache and has a valid
+ * ttr and password hash
+ */
+static int
+pc_bind_search( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ bindinfo *pbi = op->o_callback->sc_private;
+
+ /* We only care if this is an already cached result and we're
+ * below the refresh time, or we're offline.
+ */
+ if ( pbi->bi_cq ) {
+ if (( pbi->bi_cm->cc_paused & PCACHE_CC_OFFLINE ) ||
+ op->o_time < pbi->bi_cq->bindref_time ) {
+ Attribute *a;
+
+ /* See if a recognized password is hashed here */
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_userPassword );
+ if ( a && a->a_vals[0].bv_val[0] == '{' &&
+ lutil_passwd_scheme( a->a_vals[0].bv_val ))
+ pbi->bi_flags |= BI_HASHED;
+ } else {
+ Debug( pcache_debug, "pc_bind_search: cache is stale, "
+ "reftime: %ld, current time: %ld\n",
+ pbi->bi_cq->bindref_time, op->o_time );
+ }
+ } else if ( pbi->bi_si ) {
+ /* This search result is going into the cache */
+ struct berval fbv;
+ Filter *f;
+
+ filter_free( pbi->bi_si->query.filter );
+ f = pc_bind_attrs( op, rs->sr_entry, pbi->bi_templ, &fbv );
+ op->o_tmpfree( fbv.bv_val, op->o_tmpmemctx );
+ pbi->bi_si->query.filter = filter_dup( f, NULL );
+ filter_free_x( op, f, 1 );
+ }
+ }
+ return 0;
+}
+
+/* We always want pc_bind_search to run after the search handlers */
+static int
+pc_bind_resp( Operation *op, SlapReply *rs )
+{
+ bindinfo *pbi = op->o_callback->sc_private;
+ if ( !( pbi->bi_flags & BI_DIDCB )) {
+ slap_callback *sc = op->o_callback;
+ while ( sc && sc->sc_response != pcache_response )
+ sc = sc->sc_next;
+ if ( !sc )
+ sc = op->o_callback;
+ pbi->bi_cb.sc_next = sc->sc_next;
+ sc->sc_next = &pbi->bi_cb;
+ pbi->bi_flags |= BI_DIDCB;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+#ifdef PCACHE_CONTROL_PRIVDB
+static int
+pcache_op_privdb(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ slap_callback *save_cb;
+ slap_op_t type;
+
+ /* skip if control is unset */
+ if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_CRITICAL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* The cache DB isn't open yet */
+ if ( cm->defer_db_open ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+ "pcachePrivDB: cacheDB not available" );
+ return rs->sr_err;
+ }
+
+ /* FIXME: might be a little bit exaggerated... */
+ if ( !be_isroot( op ) ) {
+ save_cb = op->o_callback;
+ op->o_callback = NULL;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "pcachePrivDB: operation not allowed" );
+ op->o_callback = save_cb;
+
+ return rs->sr_err;
+ }
+
+ /* map tag to operation */
+ type = slap_req2op( op->o_tag );
+ if ( type != SLAP_OP_LAST ) {
+ BackendInfo *bi = cm->db.bd_info;
+ int rc;
+
+ /* execute, if possible */
+ if ( (&bi->bi_op_bind)[ type ] ) {
+ Operation op2 = *op;
+
+ op2.o_bd = &cm->db;
+
+ rc = (&bi->bi_op_bind)[ type ]( &op2, rs );
+ if ( type == SLAP_OP_BIND && rc == LDAP_SUCCESS ) {
+ op->o_conn->c_authz_cookie = cm->db.be_private;
+ }
+
+ return rs->sr_err;
+ }
+ }
+
+ /* otherwise fall back to error */
+ save_cb = op->o_callback;
+ op->o_callback = NULL;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported with pcachePrivDB control" );
+ op->o_callback = save_cb;
+
+ return rs->sr_err;
+}
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static int
+pcache_op_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ QueryTemplate *temp;
+ Entry *e;
+ slap_callback cb = { 0 }, *sc;
+ bindinfo bi = { 0 };
+ bindcacheinfo *bci;
+ Operation op2;
+ int rc;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL )
+ return pcache_op_privdb( op, rs );
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+ /* Skip if we're not configured for Binds, or cache DB isn't open yet */
+ if ( !cm->cache_binds || cm->defer_db_open )
+ return SLAP_CB_CONTINUE;
+
+ /* First find a matching template with Bind info */
+ for ( temp=cm->qm->templates; temp; temp=temp->qmnext ) {
+ if ( temp->bindttr && dnIsSuffix( &op->o_req_ndn, &temp->bindbase ))
+ break;
+ }
+ /* Didn't find a suitable template, just passthru */
+ if ( !temp )
+ return SLAP_CB_CONTINUE;
+
+ /* See if the entry is already locally cached. If so, we can
+ * populate the query filter to retrieve the cached query. We
+ * need to check the bindrefresh time in the query.
+ */
+ op2 = *op;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.o_bd = &cm->db;
+ e = NULL;
+ rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e ) {
+ bi.bi_flags |= BI_LOOKUP;
+ op2.ors_filter = pc_bind_attrs( op, e, temp, &op2.ors_filterstr );
+ be_entry_release_r( &op2, e );
+ } else {
+ op2.ors_filter = temp->bindfilter;
+ op2.ors_filterstr = temp->bindfilterstr;
+ }
+
+ op2.o_bd = op->o_bd;
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_limit = NULL;
+ op2.ors_attrs = cm->qm->attr_sets[temp->attr_set_index].attrs;
+ op2.ors_attrsonly = 0;
+
+ /* We want to invoke search at the same level of the stack
+ * as we're already at...
+ */
+ bi.bi_cm = cm;
+ bi.bi_templ = temp;
+
+ bi.bi_cb.sc_response = pc_bind_search;
+ bi.bi_cb.sc_private = &bi;
+ cb.sc_private = &bi;
+ cb.sc_response = pc_bind_resp;
+ op2.o_callback = &cb;
+ overlay_op_walk( &op2, rs, op_search, on->on_info, on );
+
+ /* OK, just bind locally */
+ if ( bi.bi_flags & BI_HASHED ) {
+ int delete = 0;
+ BackendDB *be = op->o_bd;
+ op->o_bd = &cm->db;
+
+ Debug( pcache_debug, "pcache_op_bind: CACHED BIND for %s\n",
+ op->o_req_dn.bv_val );
+
+ if ( op->o_bd->be_bind( op, rs ) == LDAP_SUCCESS ) {
+ op->o_conn->c_authz_cookie = cm->db.be_private;
+ }
+ op->o_bd = be;
+ ldap_pvt_thread_rdwr_wlock( &bi.bi_cq->rwlock );
+ if ( !bi.bi_cq->bind_refcnt-- ) {
+ delete = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &bi.bi_cq->rwlock );
+ if ( delete ) free_query( bi.bi_cq );
+ return rs->sr_err;
+ }
+
+ /* We have a cached query to work with */
+ if ( bi.bi_cq ) {
+ sc = op->o_tmpalloc( sizeof(slap_callback) + sizeof(bindcacheinfo),
+ op->o_tmpmemctx );
+ sc->sc_response = pc_bind_save;
+ sc->sc_cleanup = NULL;
+ sc->sc_private = sc+1;
+ sc->sc_writewait = NULL;
+ bci = sc->sc_private;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ bci->on = on;
+ bci->qc = bi.bi_cq;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_response refresh_merge;
+
+static int
+pcache_op_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+
+ int i = -1;
+
+ Query query;
+ QueryTemplate *qtemp = NULL;
+ bindinfo *pbi = NULL;
+
+ int attr_set = -1;
+ CachedQuery *answerable = NULL;
+ int cacheable = 0;
+
+ struct berval tempstr;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) {
+ return pcache_op_privdb( op, rs );
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+ /* The cache DB isn't open yet */
+ if ( cm->defer_db_open ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+ "pcachePrivDB: cacheDB not available" );
+ return rs->sr_err;
+ }
+
+ /* pickup runtime ACL changes */
+ cm->db.be_acl = op->o_bd->be_acl;
+
+ {
+ /* See if we're processing a Bind request
+ * or a cache refresh */
+ slap_callback *cb = op->o_callback;
+
+ for ( ; cb; cb=cb->sc_next ) {
+ if ( cb->sc_response == pc_bind_resp ) {
+ pbi = cb->sc_private;
+ break;
+ }
+ if ( cb->sc_response == refresh_merge ) {
+ /* This is a refresh, do not search the cache */
+ return SLAP_CB_CONTINUE;
+ }
+ }
+ }
+
+ /* FIXME: cannot cache/answer requests with pagedResults control */
+
+ query.filter = op->ors_filter;
+
+ if ( pbi ) {
+ query.base = pbi->bi_templ->bindbase;
+ query.scope = pbi->bi_templ->bindscope;
+ attr_set = pbi->bi_templ->attr_set_index;
+ cacheable = 1;
+ qtemp = pbi->bi_templ;
+ if ( pbi->bi_flags & BI_LOOKUP )
+ answerable = qm->qcfunc(op, qm, &query, qtemp);
+
+ } else {
+ tempstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len+1,
+ op->o_tmpmemctx );
+ tempstr.bv_len = 0;
+ if ( filter2template( op, op->ors_filter, &tempstr ))
+ {
+ op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug( pcache_debug, "query template of incoming query = %s\n",
+ tempstr.bv_val );
+
+ /* find attr set */
+ attr_set = get_attr_set(op->ors_attrs, qm, cm->numattrsets);
+
+ query.base = op->o_req_ndn;
+ query.scope = op->ors_scope;
+
+ /* check for query containment */
+ if (attr_set > -1) {
+ QueryTemplate *qt = qm->attr_sets[attr_set].templates;
+ for (; qt; qt = qt->qtnext ) {
+ /* find if template i can potentially answer tempstr */
+ if ( ber_bvstrcasecmp( &qt->querystr, &tempstr ) != 0 )
+ continue;
+ cacheable = 1;
+ qtemp = qt;
+ Debug( pcache_debug, "Entering QC, querystr = %s\n",
+ op->ors_filterstr.bv_val );
+ answerable = qm->qcfunc(op, qm, &query, qt);
+
+ /* if != NULL, rlocks qtemp->t_rwlock */
+ if (answerable)
+ break;
+ }
+ }
+ op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
+ }
+
+ if (answerable) {
+ BackendDB *save_bd = op->o_bd;
+
+ ldap_pvt_thread_mutex_lock( &answerable->answerable_cnt_mutex );
+ answerable->answerable_cnt++;
+ /* we only care about refcnts if we're refreshing */
+ if ( answerable->refresh_time )
+ answerable->refcnt++;
+ Debug( pcache_debug, "QUERY ANSWERABLE (answered %lu times)\n",
+ answerable->answerable_cnt );
+ ldap_pvt_thread_mutex_unlock( &answerable->answerable_cnt_mutex );
+
+ ldap_pvt_thread_rdwr_wlock(&answerable->rwlock);
+ if ( BER_BVISNULL( &answerable->q_uuid )) {
+ /* No entries cached, just an empty result set */
+ i = rs->sr_err = 0;
+ send_ldap_result( op, rs );
+ } else {
+ /* Let Bind know we used a cached query */
+ if ( pbi ) {
+ answerable->bind_refcnt++;
+ pbi->bi_cq = answerable;
+ }
+
+ op->o_bd = &cm->db;
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_TAIL ) {
+ slap_callback cb;
+ /* The cached entry was already processed by any
+ * other overlays, so don't let it get processed again.
+ *
+ * This loop removes over_back_response from the stack.
+ */
+ if ( overlay_callback_after_backover( op, &cb, 0) == 0 ) {
+ slap_callback **scp;
+ for ( scp = &op->o_callback; *scp != NULL;
+ scp = &(*scp)->sc_next ) {
+ if ( (*scp)->sc_next == &cb ) {
+ *scp = cb.sc_next;
+ break;
+ }
+ }
+ }
+ }
+ i = cm->db.bd_info->bi_op_search( op, rs );
+ }
+ ldap_pvt_thread_rdwr_wunlock(&answerable->rwlock);
+ /* locked by qtemp->qcfunc (query_containment) */
+ ldap_pvt_thread_rdwr_runlock(&qtemp->t_rwlock);
+ op->o_bd = save_bd;
+ return i;
+ }
+
+ Debug( pcache_debug, "QUERY NOT ANSWERABLE\n" );
+
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ if (cm->num_cached_queries >= cm->max_queries) {
+ cacheable = 0;
+ }
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+
+ if (op->ors_attrsonly)
+ cacheable = 0;
+
+ if (cacheable) {
+ slap_callback *cb;
+ struct search_info *si;
+
+ Debug( pcache_debug, "QUERY CACHEABLE\n" );
+ query.filter = filter_dup(op->ors_filter, NULL);
+
+ cb = op->o_tmpalloc( sizeof(*cb) + sizeof(*si), op->o_tmpmemctx );
+ cb->sc_response = pcache_response;
+ cb->sc_cleanup = pcache_op_cleanup;
+ cb->sc_private = (cb+1);
+ cb->sc_writewait = 0;
+ si = cb->sc_private;
+ si->on = on;
+ si->query = query;
+ si->qtemp = qtemp;
+ si->max = cm->num_entries_limit ;
+ si->over = 0;
+ si->count = 0;
+ si->slimit = 0;
+ si->slimit_exceeded = 0;
+ si->caching_reason = PC_IGNORE;
+ if ( op->ors_slimit > 0 && op->ors_slimit < cm->num_entries_limit ) {
+ si->slimit = op->ors_slimit;
+ op->ors_slimit = cm->num_entries_limit;
+ }
+ si->head = NULL;
+ si->tail = NULL;
+ si->swap_saved_attrs = 1;
+ si->save_attrs = op->ors_attrs;
+ si->pbi = pbi;
+ if ( pbi )
+ pbi->bi_si = si;
+
+ op->ors_attrs = qtemp->t_attrs.attrs;
+
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) {
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ } else {
+ slap_callback **pcb;
+
+ /* need to move the callback at the end, in case other
+ * overlays are present, so that the final entry is
+ * actually cached */
+ cb->sc_next = NULL;
+ for ( pcb = &op->o_callback; *pcb; pcb = &(*pcb)->sc_next );
+ *pcb = cb;
+ }
+
+ } else {
+ Debug( pcache_debug, "QUERY NOT CACHEABLE\n" );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+get_attr_set(
+ AttributeName* attrs,
+ query_manager* qm,
+ int num )
+{
+ int i = 0;
+ int count = 0;
+
+ if ( attrs ) {
+ for ( ; attrs[i].an_name.bv_val; i++ ) {
+ /* only count valid attribute names
+ * (searches ignore others, this overlay does the same) */
+ if ( attrs[i].an_desc ) {
+ count++;
+ }
+ }
+ }
+
+ /* recognize default or explicit single "*" */
+ if ( ! attrs ||
+ ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_all_user_attrs ) ) )
+ {
+ count = 1;
+ attrs = slap_anlist_all_user_attributes;
+
+ /* recognize implicit (no valid attributes) or explicit single "1.1" */
+ } else if ( count == 0 ||
+ ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_no_attrs ) ) )
+ {
+ count = 0;
+ attrs = NULL;
+ }
+
+ for ( i = 0; i < num; i++ ) {
+ AttributeName *a2;
+ int found = 1;
+
+ if ( count > qm->attr_sets[i].count ) {
+ if ( qm->attr_sets[i].count &&
+ bvmatch( &qm->attr_sets[i].attrs[0].an_name, slap_bv_all_user_attrs )) {
+ break;
+ }
+ continue;
+ }
+
+ if ( !count ) {
+ if ( !qm->attr_sets[i].count ) {
+ break;
+ }
+ continue;
+ }
+
+ for ( a2 = attrs; a2->an_name.bv_val; a2++ ) {
+ if ( !a2->an_desc && !bvmatch( &a2->an_name, slap_bv_all_user_attrs ) ) continue;
+
+ if ( !an_find( qm->attr_sets[i].attrs, &a2->an_name ) ) {
+ found = 0;
+ break;
+ }
+ }
+
+ if ( found ) {
+ break;
+ }
+ }
+
+ if ( i == num ) {
+ i = -1;
+ }
+
+ return i;
+}
+
+/* Refresh a cached query:
+ * 1: Replay the query on the remote DB and merge each entry into
+ * the local DB. Remember the DNs of each remote entry.
+ * 2: Search the local DB for all entries matching this queryID.
+ * Delete any entry whose DN is not in the list from (1).
+ */
+typedef struct dnlist {
+ struct dnlist *next;
+ struct berval dn;
+ char delete;
+} dnlist;
+
+typedef struct refresh_info {
+ dnlist *ri_dns;
+ dnlist *ri_tail;
+ dnlist *ri_dels;
+ BackendDB *ri_be;
+ CachedQuery *ri_q;
+} refresh_info;
+
+static dnlist *dnl_alloc( Operation *op, struct berval *bvdn )
+{
+ dnlist *dn = op->o_tmpalloc( sizeof(dnlist) + bvdn->bv_len + 1,
+ op->o_tmpmemctx );
+ dn->dn.bv_len = bvdn->bv_len;
+ dn->dn.bv_val = (char *)(dn+1);
+ AC_MEMCPY( dn->dn.bv_val, bvdn->bv_val, dn->dn.bv_len );
+ dn->dn.bv_val[dn->dn.bv_len] = '\0';
+ return dn;
+}
+
+static int
+refresh_merge( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ refresh_info *ri = op->o_callback->sc_private;
+ Entry *e;
+ dnlist *dnl;
+ slap_callback *ocb;
+ int rc;
+
+ ocb = op->o_callback;
+ /* Find local entry, merge */
+ op->o_bd = ri->ri_be;
+ rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e );
+ if ( rc != LDAP_SUCCESS || e == NULL ) {
+ /* No local entry, just add it. FIXME: we are not checking
+ * the cache entry limit here
+ */
+ merge_entry( op, rs->sr_entry, 1, &ri->ri_q->q_uuid );
+ } else {
+ /* Entry exists, update it */
+ Entry ne;
+ Attribute *a, **b;
+ Modifications *modlist, *mods = NULL;
+ const char* text = NULL;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+
+ ne = *e;
+ b = &ne.e_attrs;
+ /* Get a copy of only the attrs we requested */
+ for ( a=e->e_attrs; a; a=a->a_next ) {
+ if ( ad_inlist( a->a_desc, rs->sr_attrs )) {
+ *b = attr_alloc( a->a_desc );
+ *(*b) = *a;
+ /* The actual values still belong to e */
+ (*b)->a_flags |= SLAP_ATTR_DONT_FREE_VALS |
+ SLAP_ATTR_DONT_FREE_DATA;
+ b = &((*b)->a_next);
+ }
+ }
+ *b = NULL;
+ slap_entry2mods( rs->sr_entry, &modlist, &text, textbuf, textlen );
+ syncrepl_diff_entry( op, ne.e_attrs, rs->sr_entry->e_attrs,
+ &mods, &modlist, 0 );
+ be_entry_release_r( op, e );
+ attrs_free( ne.e_attrs );
+ slap_mods_free( modlist, 1 );
+ /* mods is NULL if there are no changes */
+ if ( mods ) {
+ SlapReply rs2 = { REP_RESULT };
+ struct berval dn = op->o_req_dn;
+ struct berval ndn = op->o_req_ndn;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = mods;
+ op->o_req_dn = rs->sr_entry->e_name;
+ op->o_req_ndn = rs->sr_entry->e_nname;
+ op->o_callback = &cb;
+ op->o_bd->be_modify( op, &rs2 );
+ rs->sr_err = rs2.sr_err;
+ rs_assert_done( &rs2 );
+ slap_mods_free( mods, 1 );
+ op->o_req_dn = dn;
+ op->o_req_ndn = ndn;
+ }
+ }
+
+ /* Add DN to list */
+ dnl = dnl_alloc( op, &rs->sr_entry->e_nname );
+ dnl->next = NULL;
+ if ( ri->ri_tail ) {
+ ri->ri_tail->next = dnl;
+ } else {
+ ri->ri_dns = dnl;
+ }
+ ri->ri_tail = dnl;
+ op->o_callback = ocb;
+ }
+ return 0;
+}
+
+static int
+refresh_purge( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ refresh_info *ri = op->o_callback->sc_private;
+ dnlist **dn;
+ int del = 1;
+
+ /* Did the entry exist on the remote? */
+ for ( dn=&ri->ri_dns; *dn; dn = &(*dn)->next ) {
+ if ( dn_match( &(*dn)->dn, &rs->sr_entry->e_nname )) {
+ dnlist *dnext = (*dn)->next;
+ op->o_tmpfree( *dn, op->o_tmpmemctx );
+ *dn = dnext;
+ del = 0;
+ break;
+ }
+ }
+ /* No, so put it on the list to delete */
+ if ( del ) {
+ Attribute *a;
+ dnlist *dnl = dnl_alloc( op, &rs->sr_entry->e_nname );
+ dnl->next = ri->ri_dels;
+ ri->ri_dels = dnl;
+ a = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ /* If ours is the only queryId, delete entry */
+ dnl->delete = ( a->a_numvals == 1 );
+ }
+ }
+ return 0;
+}
+
+static int
+refresh_query( Operation *op, CachedQuery *query, slap_overinst *on )
+{
+ SlapReply rs = {REP_RESULT};
+ slap_callback cb = { 0 };
+ refresh_info ri = { 0 };
+ char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ Filter filter = {LDAP_FILTER_EQUALITY};
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+ dnlist *dn;
+ int rc;
+
+ ldap_pvt_thread_mutex_lock( &query->answerable_cnt_mutex );
+ query->refcnt = 0;
+ ldap_pvt_thread_mutex_unlock( &query->answerable_cnt_mutex );
+
+ cb.sc_response = refresh_merge;
+ cb.sc_private = &ri;
+
+ /* cache DB */
+ ri.ri_be = op->o_bd;
+ ri.ri_q = query;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_do_not_cache = 1;
+
+ op->o_req_dn = query->qbase->base;
+ op->o_req_ndn = query->qbase->base;
+ op->ors_scope = query->scope;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ op->ors_filter = query->filter;
+ filter2bv_x( op, query->filter, &op->ors_filterstr );
+ op->ors_attrs = query->qtemp->t_attrs.attrs;
+ op->ors_attrsonly = 0;
+
+ op->o_bd = on->on_info->oi_origdb;
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc ) {
+ op->o_bd = ri.ri_be;
+ goto leave;
+ }
+
+ /* Get the DNs of all entries matching this query */
+ cb.sc_response = refresh_purge;
+
+ op->o_bd = ri.ri_be;
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str),
+ "(%s=%s)", ad_queryId->ad_cname.bv_val, query->q_uuid.bv_val);
+ filter.f_ava = &ava;
+ filter.f_av_desc = ad_queryId;
+ filter.f_av_value = query->q_uuid;
+ attrs[ 0 ].an_desc = ad_queryId;
+ attrs[ 0 ].an_name = ad_queryId->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+ rs_reinit( &rs, REP_RESULT );
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc ) goto leave;
+
+ while (( dn = ri.ri_dels )) {
+ op->o_req_dn = dn->dn;
+ op->o_req_ndn = dn->dn;
+ rs_reinit( &rs, REP_RESULT );
+ if ( dn->delete ) {
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_bd->be_delete( op, &rs );
+ } else {
+ Modifications mod;
+ struct berval vals[2];
+
+ vals[0] = query->q_uuid;
+ BER_BVZERO( &vals[1] );
+ mod.sml_op = LDAP_MOD_DELETE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_queryId;
+ mod.sml_type = ad_queryId->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->o_bd->be_modify( op, &rs );
+ }
+ ri.ri_dels = dn->next;
+ op->o_tmpfree( dn, op->o_tmpmemctx );
+ }
+
+leave:
+ /* reset our local heap, we're done with it */
+ slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, op->o_threadctx, 1 );
+ return rc;
+}
+
+static void*
+consistency_check(
+ void *ctx,
+ void *arg )
+{
+ struct re_s *rtask = arg;
+ slap_overinst *on = rtask->arg;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+
+ CachedQuery *query, *qprev;
+ CachedQuery *expires;
+ int return_val, pause = PCACHE_CC_PAUSED;
+ QueryTemplate *templ;
+
+ /* Don't expire anything when we're offline */
+ if ( cm->cc_paused & PCACHE_CC_OFFLINE ) {
+ pause = PCACHE_CC_OFFLINE;
+ goto leave;
+ }
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ op->o_bd = &cm->db;
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+
+ cm->cc_arg = arg;
+
+ for (templ = qm->templates; templ; templ=templ->qmnext) {
+ time_t ttl;
+ if ( !templ->query_last ) continue;
+ pause = 0;
+ expires = NULL;
+ op->o_time = slap_get_time();
+ if ( !templ->ttr ) {
+ ttl = templ->ttl;
+ if ( templ->negttl && templ->negttl < ttl )
+ ttl = templ->negttl;
+ if ( templ->limitttl && templ->limitttl < ttl )
+ ttl = templ->limitttl;
+ /* The oldest timestamp that needs expiration checking */
+ ttl += op->o_time;
+ }
+
+ Debug( pcache_debug, "Lock CR index = %p\n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ for ( query=templ->query_last; query; query=qprev ) {
+ qprev = query->prev;
+ if ( query->refresh_time && query->refresh_time < op->o_time ) {
+ /* A refresh will extend the expiry if the query has been
+ * referenced, but not if it's unreferenced. If the
+ * expiration has been hit, then skip the refresh since
+ * we're just going to discard the result anyway.
+ */
+ if ( query->refcnt )
+ query->expiry_time = op->o_time + templ->ttl;
+ if ( query->expiry_time > op->o_time ) {
+ /* perform actual refresh below */
+ continue;
+ }
+ }
+
+ if (query->expiry_time < op->o_time) {
+ int rem = 0;
+ if ( query != templ->query_last )
+ continue;
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if (query->in_lru) {
+ remove_query(qm, query);
+ rem = 1;
+ }
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ if (!rem)
+ continue;
+ remove_from_template(query, templ);
+ Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n",
+ (void *) templ, templ->no_of_queries );
+ query->prev = expires;
+ expires = query;
+ query->qtemp = NULL;
+ } else if ( !templ->ttr && query->expiry_time > ttl ) {
+ /* We don't need to check for refreshes, and this
+ * query's expiry is too new, and all subsequent queries
+ * will be newer yet. So stop looking.
+ *
+ * If we have refreshes, then we always have to walk the
+ * entire query list.
+ */
+ break;
+ }
+ }
+ Debug( pcache_debug, "Unlock CR index = %p\n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+ for ( query=expires; query; query=qprev ) {
+ int rem;
+ qprev = query->prev;
+ if ( BER_BVISNULL( &query->q_uuid ))
+ return_val = 0;
+ else
+ return_val = remove_query_data(op, &query->q_uuid);
+ Debug( pcache_debug, "STALE QUERY REMOVED, SIZE=%d\n",
+ return_val );
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->cur_entries -= return_val;
+ cm->num_cached_queries--;
+ Debug( pcache_debug, "STORED QUERIES = %lu\n",
+ cm->num_cached_queries );
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+ Debug( pcache_debug,
+ "STALE QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries );
+ ldap_pvt_thread_rdwr_wlock( &query->rwlock );
+ if ( query->bind_refcnt-- ) {
+ rem = 0;
+ } else {
+ rem = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &query->rwlock );
+ if ( rem ) free_query(query);
+ }
+
+ /* handle refreshes that we skipped earlier */
+ if ( templ->ttr ) {
+ ldap_pvt_thread_rdwr_rlock(&templ->t_rwlock);
+ for ( query=templ->query_last; query; query=qprev ) {
+ qprev = query->prev;
+ if ( query->refresh_time && query->refresh_time < op->o_time ) {
+ /* A refresh will extend the expiry if the query has been
+ * referenced, but not if it's unreferenced. If the
+ * expiration has been hit, then skip the refresh since
+ * we're just going to discard the result anyway.
+ */
+ if ( query->expiry_time > op->o_time ) {
+ refresh_query( op, query, on );
+ query->refresh_time = op->o_time + templ->ttr;
+ }
+ }
+ }
+ ldap_pvt_thread_rdwr_runlock(&templ->t_rwlock);
+ }
+ }
+
+leave:
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+ /* If there were no queries, defer processing for a while */
+ if ( cm->cc_paused != pause )
+ cm->cc_paused = pause;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, pause );
+
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
+
+#define MAX_ATTR_SETS 500
+
+enum {
+ PC_MAIN = 1,
+ PC_ATTR,
+ PC_TEMP,
+ PC_RESP,
+ PC_QUERIES,
+ PC_OFFLINE,
+ PC_BIND,
+ PC_PRIVATE_DB
+};
+
+static ConfigDriver pc_cf_gen;
+static ConfigLDAPadd pc_ldadd;
+static ConfigCfAdd pc_cfadd;
+
+static ConfigTable pccfg[] = {
+ { "pcache", "backend> <max_entries> <numattrsets> <entry limit> "
+ "<cycle_time",
+ 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
+ "( OLcfgOvAt:2.1 NAME ( 'olcPcache' 'olcProxyCache' ) "
+ "DESC 'Proxy Cache basic parameters' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "pcacheAttrset", "index> <attributes...",
+ 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
+ "( OLcfgOvAt:2.2 NAME ( 'olcPcacheAttrset' 'olcProxyAttrset' ) "
+ "DESC 'A set of attributes to cache' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcacheTemplate", "filter> <attrset-index> <TTL> <negTTL> "
+ "<limitTTL> <TTR",
+ 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+ "( OLcfgOvAt:2.3 NAME ( 'olcPcacheTemplate' 'olcProxyCacheTemplate' ) "
+ "DESC 'Filter template, attrset, cache TTL, "
+ "optional negative TTL, optional sizelimit TTL, "
+ "optional TTR' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcachePosition", "head|tail(default)",
+ 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
+ "( OLcfgOvAt:2.4 NAME 'olcPcachePosition' "
+ "DESC 'Response callback position in overlay stack' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "pcacheMaxQueries", "queries",
+ 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
+ "( OLcfgOvAt:2.5 NAME ( 'olcPcacheMaxQueries' 'olcProxyCacheQueries' ) "
+ "DESC 'Maximum number of queries to cache' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "pcachePersist", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
+ "( OLcfgOvAt:2.6 NAME ( 'olcPcachePersist' 'olcProxySaveQueries' ) "
+ "DESC 'Save cached queries for hot restart' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheValidate", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+ "( OLcfgOvAt:2.7 NAME ( 'olcPcacheValidate' 'olcProxyCheckCacheability' ) "
+ "DESC 'Check whether the results of a query are cacheable, e.g. for schema issues' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheOffline", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|PC_OFFLINE, pc_cf_gen,
+ "( OLcfgOvAt:2.8 NAME 'olcPcacheOffline' "
+ "DESC 'Set cache to offline mode and disable expiration' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheBind", "filter> <attrset-index> <TTR> <scope> <base",
+ 6, 6, 0, ARG_MAGIC|PC_BIND, pc_cf_gen,
+ "( OLcfgOvAt:2.9 NAME 'olcPcacheBind' "
+ "DESC 'Parameters for caching Binds' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcache-", "private database args",
+ 1, 0, STRLENOF("pcache-"), ARG_MAGIC|PC_PRIVATE_DB, pc_cf_gen,
+ NULL, NULL, NULL },
+
+ /* Legacy keywords */
+ { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> "
+ "<cycle_time",
+ 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxyattrset", "index> <attributes...",
+ 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL",
+ 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "response-callback", "head|tail(default)",
+ 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxyCacheQueries", "queries",
+ 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxySaveQueries", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
+ NULL, NULL, NULL },
+ { "proxyCheckCacheability", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+ NULL, NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs pcocs[] = {
+ { "( OLcfgOvOc:2.1 "
+ "NAME 'olcPcacheConfig' "
+ "DESC 'ProxyCache configuration' "
+ "SUP olcOverlayConfig "
+ "MUST ( olcPcache $ olcPcacheAttrset $ olcPcacheTemplate ) "
+ "MAY ( olcPcachePosition $ olcPcacheMaxQueries $ olcPcachePersist $ "
+ "olcPcacheValidate $ olcPcacheOffline $ olcPcacheBind ) )",
+ Cft_Overlay, pccfg, NULL, pc_cfadd },
+ { "( OLcfgOvOc:2.2 "
+ "NAME 'olcPcacheDatabase' "
+ "DESC 'Cache database configuration' "
+ /* co_table is initialized in pcache_initialize */
+ "AUXILIARY )", Cft_Misc, NULL, pc_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int pcache_db_open2( slap_overinst *on, ConfigReply *cr );
+
+static int
+pc_ldadd_cleanup( ConfigArgs *c )
+{
+ slap_overinst *on = c->ca_private;
+ return pcache_db_open2( on, &c->reply );
+}
+
+static int
+pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ cache_manager *cm;
+
+ if ( p->ce_type != Cft_Overlay || !p->ce_bi ||
+ p->ce_bi->bi_cf_ocs != pcocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ on = (slap_overinst *)p->ce_bi;
+ cm = on->on_bi.bi_private;
+ ca->be = &cm->db;
+ /* Defer open if this is an LDAPadd */
+ if ( CONFIG_ONLINE_ADD( ca ))
+ config_push_cleanup( ca, pc_ldadd_cleanup );
+ else
+ cm->defer_db_open = 0;
+ ca->ca_private = on;
+ return LDAP_SUCCESS;
+}
+
+static int
+pc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ CfEntryInfo *pe = p->e_private;
+ slap_overinst *on = (slap_overinst *)pe->ce_bi;
+ cache_manager *cm = on->on_bi.bi_private;
+ struct berval bv;
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase=" SLAP_X_ORDERED_FMT "%s",
+ 0, cm->db.bd_info->bi_type );
+ if ( bv.bv_len >= sizeof( ca->cr_msg ) ) {
+ return -1;
+ }
+ bv.bv_val = ca->cr_msg;
+ ca->be = &cm->db;
+ cm->defer_db_open = 0;
+
+ /* We can only create this entry if the database is table-driven
+ */
+ if ( cm->db.be_cf_ocs )
+ config_build_entry( op, rs, pe, ca, &bv, cm->db.be_cf_ocs,
+ &pcocs[1] );
+
+ return 0;
+}
+
+static int
+pc_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ cache_manager* cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ QueryTemplate* temp;
+ AttributeName* attr_name;
+ AttributeName* attrarray;
+ const char* text=NULL;
+ int i, num, rc = 0;
+ char *ptr;
+ unsigned long t;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv;
+ switch( c->type ) {
+ case PC_MAIN:
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s %d %d %d %ld",
+ cm->db.bd_info->bi_type, cm->max_entries, cm->numattrsets,
+ cm->num_entries_limit, cm->cc_period );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ case PC_ATTR:
+ for (i=0; i<cm->numattrsets; i++) {
+ if ( !qm->attr_sets[i].count ) continue;
+
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%d", i );
+
+ /* count the attr length */
+ for ( attr_name = qm->attr_sets[i].attrs;
+ attr_name->an_name.bv_val; attr_name++ )
+ {
+ bv.bv_len += attr_name->an_name.bv_len + 1;
+ if ( attr_name->an_desc &&
+ ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) {
+ bv.bv_len += STRLENOF("undef:");
+ }
+ }
+
+ bv.bv_val = ch_malloc( bv.bv_len+1 );
+ ptr = lutil_strcopy( bv.bv_val, c->cr_msg );
+ for ( attr_name = qm->attr_sets[i].attrs;
+ attr_name->an_name.bv_val; attr_name++ ) {
+ *ptr++ = ' ';
+ if ( attr_name->an_desc &&
+ ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) {
+ ptr = lutil_strcopy( ptr, "undef:" );
+ }
+ ptr = lutil_strcopy( ptr, attr_name->an_name.bv_val );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_TEMP:
+ for (temp=qm->templates; temp; temp=temp->qmnext) {
+ /* HEADS-UP: always print all;
+ * if optional == 0, ignore */
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ " %d %ld %ld %ld %ld",
+ temp->attr_set_index,
+ temp->ttl,
+ temp->negttl,
+ temp->limitttl,
+ temp->ttr );
+ bv.bv_len += temp->querystr.bv_len + 2;
+ bv.bv_val = ch_malloc( bv.bv_len+1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->querystr.bv_val );
+ *ptr++ = '"';
+ strcpy( ptr, c->cr_msg );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_BIND:
+ for (temp=qm->templates; temp; temp=temp->qmnext) {
+ if ( !temp->bindttr ) continue;
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ " %d %ld %s ",
+ temp->attr_set_index,
+ temp->bindttr,
+ ldap_pvt_scope2str( temp->bindscope ));
+ bv.bv_len += temp->bindbase.bv_len + temp->bindftemp.bv_len + 4;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->bindftemp.bv_val );
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, c->cr_msg );
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->bindbase.bv_val );
+ *ptr++ = '"';
+ *ptr = '\0';
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_RESP:
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) {
+ BER_BVSTR( &bv, "head" );
+ } else {
+ BER_BVSTR( &bv, "tail" );
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ case PC_QUERIES:
+ c->value_int = cm->max_queries;
+ break;
+ case PC_OFFLINE:
+ c->value_int = (cm->cc_paused & PCACHE_CC_OFFLINE) != 0;
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ rc = 1;
+ switch( c->type ) {
+ case PC_ATTR: /* FIXME */
+ case PC_TEMP:
+ case PC_BIND:
+ break;
+ case PC_OFFLINE:
+ cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+ /* If there were cached queries when we went offline,
+ * restart the checker now.
+ */
+ if ( cm->num_cached_queries ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ cm->cc_paused = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ rc = 0;
+ break;
+ }
+ return rc;
+ }
+
+ switch( c->type ) {
+ case PC_MAIN:
+ if ( cm->numattrsets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->numattrsets, c->argv[3] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse num attrsets=\"%s\" (arg #3)",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( cm->numattrsets <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( cm->numattrsets > MAX_ATTR_SETS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be <= %d", MAX_ATTR_SETS );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( !backend_db_init( c->argv[1], &cm->db, -1, NULL )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown backend type (arg #1)" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->max_entries, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse max entries=\"%s\" (arg #2)",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( cm->max_entries <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "max entries (arg #2) must be positive.\n" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->num_entries_limit, c->argv[4] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse entry limit=\"%s\" (arg #4)",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( cm->num_entries_limit <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( cm->num_entries_limit > cm->max_entries ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be less than max entries %d (arg #2)", cm->max_entries );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( lutil_parse_time( c->argv[5], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse period=\"%s\" (arg #5)",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ cm->cc_period = (time_t)t;
+ Debug( pcache_debug,
+ "Total # of attribute sets to be cached = %d.\n",
+ cm->numattrsets );
+ qm->attr_sets = ( struct attr_set * )ch_calloc( cm->numattrsets,
+ sizeof( struct attr_set ) );
+ break;
+ case PC_ATTR:
+ if ( cm->numattrsets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( lutil_atoi( &num, c->argv[1] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse attrset #=\"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( num < 0 || num >= cm->numattrsets ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "attrset index %d out of bounds (must be %s%d)",
+ num, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ qm->attr_sets[num].flags |= PC_CONFIGURED;
+ if ( c->argc == 2 ) {
+ /* assume "1.1" */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need an explicit attr in attrlist; use \"*\" to indicate all attrs" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+
+ } else if ( c->argc == 3 ) {
+ if ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) {
+ qm->attr_sets[num].count = 1;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES );
+ break;
+
+ } else if ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) {
+ qm->attr_sets[num].count = 1;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ break;
+
+ } else if ( strcmp( c->argv[2], LDAP_NO_ATTRS ) == 0 ) {
+ break;
+ }
+ /* else: fallthru */
+
+ } else if ( c->argc == 4 ) {
+ if ( ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 )
+ || ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) )
+ {
+ qm->attr_sets[num].count = 2;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 3,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES );
+ BER_BVSTR( &qm->attr_sets[num].attrs[1].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ break;
+ }
+ /* else: fallthru */
+ }
+
+ if ( c->argc > 2 ) {
+ int all_user = 0, all_op = 0;
+
+ qm->attr_sets[num].count = c->argc - 2;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( c->argc - 1,
+ sizeof( AttributeName ) );
+ attr_name = qm->attr_sets[num].attrs;
+ for ( i = 2; i < c->argc; i++ ) {
+ attr_name->an_desc = NULL;
+ if ( strcmp( c->argv[i], LDAP_NO_ATTRS ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid attr #%d \"%s\" in attrlist",
+ i - 2, c->argv[i] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ ch_free( qm->attr_sets[num].attrs );
+ qm->attr_sets[num].attrs = NULL;
+ qm->attr_sets[num].count = 0;
+ return 1;
+ }
+ if ( strcmp( c->argv[i], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) {
+ all_user = 1;
+ BER_BVSTR( &attr_name->an_name, LDAP_ALL_USER_ATTRIBUTES );
+ } else if ( strcmp( c->argv[i], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) {
+ all_op = 1;
+ BER_BVSTR( &attr_name->an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ } else {
+ if ( strncasecmp( c->argv[i], "undef:", STRLENOF("undef:") ) == 0 ) {
+ struct berval bv;
+ ber_str2bv( c->argv[i] + STRLENOF("undef:"), 0, 0, &bv );
+ attr_name->an_desc = slap_bv2tmp_ad( &bv, NULL );
+
+ } else if ( slap_str2ad( c->argv[i], &attr_name->an_desc, &text ) ) {
+ strcpy( c->cr_msg, text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ ch_free( qm->attr_sets[num].attrs );
+ qm->attr_sets[num].attrs = NULL;
+ qm->attr_sets[num].count = 0;
+ return 1;
+ }
+ attr_name->an_name = attr_name->an_desc->ad_cname;
+ }
+ attr_name->an_oc = NULL;
+ attr_name->an_flags = 0;
+ if ( attr_name->an_desc == slap_schema.si_ad_objectClass )
+ qm->attr_sets[num].flags |= PC_GOT_OC;
+ attr_name++;
+ BER_BVZERO( &attr_name->an_name );
+ }
+
+ /* warn if list contains both "*" and "+" */
+ if ( i > 4 && all_user && all_op ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: attribute list contains \"*\" and \"+\"" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ }
+ break;
+ case PC_TEMP:
+ if ( cm->numattrsets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( lutil_atoi( &i, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template #=\"%s\"",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( i < 0 || i >= cm->numattrsets ||
+ !(qm->attr_sets[i].flags & PC_CONFIGURED )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "template index %d invalid (%s%d)",
+ i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ {
+ AttributeName *attrs;
+ int cnt;
+ cnt = template_attrs( c->argv[1], &qm->attr_sets[i], &attrs, &text );
+ if ( cnt < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s",
+ text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ temp = ch_calloc( 1, sizeof( QueryTemplate ));
+ temp->qmnext = qm->templates;
+ qm->templates = temp;
+ temp->t_attrs.attrs = attrs;
+ temp->t_attrs.count = cnt;
+ }
+ ldap_pvt_thread_rdwr_init( &temp->t_rwlock );
+ temp->query = temp->query_last = NULL;
+ if ( lutil_parse_time( c->argv[3], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template ttl=\"%s\"",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+pc_temp_fail:
+ ch_free( temp->t_attrs.attrs );
+ ch_free( temp );
+ return( 1 );
+ }
+ temp->ttl = (time_t)t;
+ temp->negttl = (time_t)0;
+ temp->limitttl = (time_t)0;
+ temp->ttr = (time_t)0;
+ switch ( c->argc ) {
+ case 7:
+ if ( lutil_parse_time( c->argv[6], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template ttr=\"%s\"",
+ c->argv[6] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ goto pc_temp_fail;
+ }
+ temp->ttr = (time_t)t;
+ /* fallthru */
+
+ case 6:
+ if ( lutil_parse_time( c->argv[5], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template sizelimit ttl=\"%s\"",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ goto pc_temp_fail;
+ }
+ temp->limitttl = (time_t)t;
+ /* fallthru */
+
+ case 5:
+ if ( lutil_parse_time( c->argv[4], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template negative ttl=\"%s\"",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ goto pc_temp_fail;
+ }
+ temp->negttl = (time_t)t;
+ break;
+ }
+
+ temp->no_of_queries = 0;
+
+ ber_str2bv( c->argv[1], 0, 1, &temp->querystr );
+ Debug( pcache_debug, "Template:\n" );
+ Debug( pcache_debug, " query template: %s\n",
+ temp->querystr.bv_val );
+ temp->attr_set_index = i;
+ qm->attr_sets[i].flags |= PC_REFERENCED;
+ temp->qtnext = qm->attr_sets[i].templates;
+ qm->attr_sets[i].templates = temp;
+ Debug( pcache_debug, " attributes: \n" );
+ if ( ( attrarray = qm->attr_sets[i].attrs ) != NULL ) {
+ for ( i=0; attrarray[i].an_name.bv_val; i++ )
+ Debug( pcache_debug, "\t%s\n",
+ attrarray[i].an_name.bv_val );
+ }
+ break;
+ case PC_BIND:
+ if ( !qm->templates ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcacheTemplate\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ if ( lutil_atoi( &i, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse Bind index #=\"%s\"",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( i < 0 || i >= cm->numattrsets ||
+ !(qm->attr_sets[i].flags & PC_CONFIGURED )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind index %d invalid (%s%d)",
+ i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ { struct berval bv, tempbv;
+ AttributeDescription **descs;
+ int ndescs;
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ ndescs = ftemp_attrs( &bv, &tempbv, &descs, &text );
+ if ( ndescs < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s",
+ text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ for ( temp = qm->templates; temp; temp=temp->qmnext ) {
+ if ( temp->attr_set_index == i && bvmatch( &tempbv,
+ &temp->querystr ))
+ break;
+ }
+ ch_free( tempbv.bv_val );
+ if ( !temp ) {
+ ch_free( descs );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind template %s %d invalid",
+ c->argv[1], i );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ ber_dupbv( &temp->bindftemp, &bv );
+ temp->bindfattrs = descs;
+ temp->bindnattrs = ndescs;
+ }
+ if ( lutil_parse_time( c->argv[3], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bind ttr=\"%s\"",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+pc_bind_fail:
+ ch_free( temp->bindfattrs );
+ temp->bindfattrs = NULL;
+ ch_free( temp->bindftemp.bv_val );
+ BER_BVZERO( &temp->bindftemp );
+ return( 1 );
+ }
+ num = ldap_pvt_str2scope( c->argv[4] );
+ if ( num < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bind scope=\"%s\"",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ goto pc_bind_fail;
+ }
+ {
+ struct berval dn, ndn;
+ ber_str2bv( c->argv[5], 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid bind baseDN=\"%s\"",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ goto pc_bind_fail;
+ }
+ if ( temp->bindbase.bv_val )
+ ch_free( temp->bindbase.bv_val );
+ temp->bindbase = ndn;
+ }
+ {
+ /* convert the template into dummy filter */
+ struct berval bv;
+ char *eq = temp->bindftemp.bv_val, *e2;
+ Filter *f;
+ i = 0;
+ while ((eq = strchr(eq, '=' ))) {
+ eq++;
+ if ( eq[0] == ')' )
+ i++;
+ }
+ bv.bv_len = temp->bindftemp.bv_len + i;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ for ( e2 = bv.bv_val, eq = temp->bindftemp.bv_val;
+ *eq; eq++ ) {
+ if ( *eq == '=' ) {
+ *e2++ = '=';
+ if ( eq[1] == ')' )
+ *e2++ = '*';
+ } else {
+ *e2++ = *eq;
+ }
+ }
+ *e2 = '\0';
+ f = str2filter( bv.bv_val );
+ if ( !f ) {
+ ch_free( bv.bv_val );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bindfilter=\"%s\"", bv.bv_val );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ ch_free( temp->bindbase.bv_val );
+ BER_BVZERO( &temp->bindbase );
+ goto pc_bind_fail;
+ }
+ if ( temp->bindfilter )
+ filter_free( temp->bindfilter );
+ if ( temp->bindfilterstr.bv_val )
+ ch_free( temp->bindfilterstr.bv_val );
+ temp->bindfilterstr = bv;
+ temp->bindfilter = f;
+ }
+ temp->bindttr = (time_t)t;
+ temp->bindscope = num;
+ cm->cache_binds = 1;
+ break;
+
+ case PC_RESP:
+ if ( strcasecmp( c->argv[1], "head" ) == 0 ) {
+ cm->response_cb = PCACHE_RESPONSE_CB_HEAD;
+
+ } else if ( strcasecmp( c->argv[1], "tail" ) == 0 ) {
+ cm->response_cb = PCACHE_RESPONSE_CB_TAIL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown specifier" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ break;
+ case PC_QUERIES:
+ if ( c->value_int <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "max queries must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ cm->max_queries = c->value_int;
+ break;
+ case PC_OFFLINE:
+ if ( c->value_int )
+ cm->cc_paused |= PCACHE_CC_OFFLINE;
+ else
+ cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+ break;
+ case PC_PRIVATE_DB:
+ if ( cm->db.be_private == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "private database must be defined before setting database specific options" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( cm->db.bd_info->bi_cf_ocs ) {
+ ConfigTable *ct;
+ ConfigArgs c2 = *c;
+ char *argv0 = c->argv[ 0 ];
+
+ c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ];
+
+ ct = config_find_keyword( cm->db.bd_info->bi_cf_ocs->co_table, c );
+ if ( ct == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "private database does not recognize specific option '%s'",
+ c->argv[ 0 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+
+ } else {
+ c->table = cm->db.bd_info->bi_cf_ocs->co_type;
+ c->be = &cm->db;
+ c->bi = c->be->bd_info;
+
+ rc = config_add_vals( ct, c );
+
+ c->bi = c2.bi;
+ c->be = c2.be;
+ c->table = c2.table;
+ }
+
+ c->argv[ 0 ] = argv0;
+
+ } else if ( cm->db.be_config != NULL ) {
+ char *argv0 = c->argv[ 0 ];
+
+ c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ];
+ rc = cm->db.be_config( &cm->db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[ 0 ] = argv0;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no means to set private database specific options" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ break;
+ default:
+ rc = SLAP_CONF_UNKNOWN;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+pcache_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager* cm = on->on_bi.bi_private;
+
+ /* Something for the cache database? */
+ if ( cm->db.bd_info && cm->db.bd_info->bi_db_config )
+ return cm->db.bd_info->bi_db_config( &cm->db, fname, lineno,
+ argc, argv );
+ return SLAP_CONF_UNKNOWN;
+}
+
+static int
+pcache_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm;
+ query_manager *qm;
+
+ cm = (cache_manager *)ch_malloc(sizeof(cache_manager));
+ on->on_bi.bi_private = cm;
+
+ qm = (query_manager*)ch_malloc(sizeof(query_manager));
+
+ cm->db = *be;
+ cm->db.bd_info = NULL;
+ SLAP_DBFLAGS(&cm->db) |= SLAP_DBFLAG_NO_SCHEMA_CHECK;
+ cm->db.be_private = NULL;
+ cm->db.bd_self = &cm->db;
+ cm->qm = qm;
+ cm->numattrsets = 0;
+ cm->num_entries_limit = 5;
+ cm->num_cached_queries = 0;
+ cm->max_entries = 0;
+ cm->cur_entries = 0;
+ cm->max_queries = 10000;
+ cm->save_queries = 0;
+ cm->check_cacheability = 0;
+ cm->response_cb = PCACHE_RESPONSE_CB_TAIL;
+ cm->defer_db_open = 1;
+ cm->cache_binds = 0;
+ cm->cc_period = 1000;
+ cm->cc_paused = 0;
+ cm->cc_arg = NULL;
+#ifdef PCACHE_MONITOR
+ cm->monitor_cb = NULL;
+#endif /* PCACHE_MONITOR */
+
+ qm->attr_sets = NULL;
+ qm->templates = NULL;
+ qm->lru_top = NULL;
+ qm->lru_bottom = NULL;
+
+ qm->qcfunc = query_containment;
+ qm->crfunc = cache_replacement;
+ qm->addfunc = add_query;
+ ldap_pvt_thread_mutex_init(&qm->lru_mutex);
+
+ ldap_pvt_thread_mutex_init(&cm->cache_mutex);
+
+#ifndef PCACHE_MONITOR
+ return 0;
+#else /* PCACHE_MONITOR */
+ return pcache_monitor_db_init( be );
+#endif /* PCACHE_MONITOR */
+}
+
+static int
+pcache_cachedquery_open_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ Attribute *a;
+
+ a = attr_find( rs->sr_entry->e_attrs, ad_cachedQueryURL );
+ if ( a != NULL ) {
+ BerVarray *valsp;
+
+ assert( a->a_nvals != NULL );
+
+ valsp = op->o_callback->sc_private;
+ assert( *valsp == NULL );
+
+ ber_bvarray_dup_x( valsp, a->a_nvals, op->o_tmpmemctx );
+ }
+ }
+
+ return 0;
+}
+
+static int
+pcache_cachedquery_count_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ int *countp = (int *)op->o_callback->sc_private;
+
+ (*countp)++;
+ }
+
+ return 0;
+}
+
+static int
+pcache_db_open2(
+ slap_overinst *on,
+ ConfigReply *cr )
+{
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ int rc;
+
+ rc = backend_startup_one( &cm->db, cr );
+ if ( rc == 0 ) {
+ cm->defer_db_open = 0;
+ }
+
+ /* There is no runqueue in TOOL mode */
+ if (( slapMode & SLAP_SERVER_MODE ) && rc == 0 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, cm->cc_period,
+ consistency_check, on,
+ "pcache_consistency", cm->db.be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ /* Cached database must have the rootdn */
+ if ( BER_BVISNULL( &cm->db.be_rootndn )
+ || BER_BVISEMPTY( &cm->db.be_rootndn ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "pcache_db_open(): "
+ "underlying database of type \"%s\"\n"
+ " serving naming context \"%s\"\n"
+ " has no \"rootdn\", required by \"pcache\".\n",
+ on->on_info->oi_orig->bi_type,
+ cm->db.be_suffix[0].bv_val );
+ return 1;
+ }
+
+ if ( cm->save_queries ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { 0 };
+ SlapReply rs = { REP_RESULT };
+ BerVarray vals = NULL;
+ Filter f = { 0 }, f2 = { 0 };
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_bd = &cm->db;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ cb.sc_response = pcache_cachedquery_open_cb;
+ cb.sc_private = &vals;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+ op->o_req_dn = cm->db.be_suffix[ 0 ];
+ op->o_req_ndn = cm->db.be_nsuffix[ 0 ];
+
+ op->ors_scope = LDAP_SCOPE_BASE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ ber_str2bv( "(pcacheQueryURL=*)", 0, 0, &op->ors_filterstr );
+ f.f_choice = LDAP_FILTER_PRESENT;
+ f.f_desc = ad_cachedQueryURL;
+ op->ors_filter = &f;
+ attrs[ 0 ].an_desc = ad_cachedQueryURL;
+ attrs[ 0 ].an_name = ad_cachedQueryURL->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc == LDAP_SUCCESS && vals != NULL ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ if ( url2query( vals[ i ].bv_val, op, qm ) == 0 ) {
+ cm->num_cached_queries++;
+ }
+ }
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ /* count cached entries */
+ f.f_choice = LDAP_FILTER_NOT;
+ f.f_not = &f2;
+ f2.f_choice = LDAP_FILTER_EQUALITY;
+ f2.f_ava = &ava;
+ f2.f_av_desc = slap_schema.si_ad_objectClass;
+ BER_BVSTR( &f2.f_av_value, "glue" );
+ ber_str2bv( "(!(objectClass=glue))", 0, 0, &op->ors_filterstr );
+
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ rs_reinit( &rs, REP_RESULT );
+ op->o_callback->sc_response = pcache_cachedquery_count_cb;
+ op->o_callback->sc_private = &rs.sr_nentries;
+
+ rc = op->o_bd->be_search( op, &rs );
+
+ cm->cur_entries = rs.sr_nentries;
+
+ /* ignore errors */
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static int
+pcache_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ int i, ncf = 0, rf = 0, nrf = 0, rc = 0;
+
+ /* check attr sets */
+ for ( i = 0; i < cm->numattrsets; i++) {
+ if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) {
+ if ( qm->attr_sets[i].flags & PC_REFERENCED ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i );
+ rf++;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i );
+ }
+ ncf++;
+
+ } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i );
+ nrf++;
+ }
+ }
+
+ if ( ncf || rf || nrf ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf );
+
+ if ( rf > 0 ) {
+ return 1;
+ }
+ }
+
+ /* need to inherit something from the original database... */
+ cm->db.be_def_limit = be->be_def_limit;
+ cm->db.be_limits = be->be_limits;
+ cm->db.be_acl = be->be_acl;
+ cm->db.be_dfltaccess = be->be_dfltaccess;
+
+ if ( SLAP_DBMONITORING( be ) ) {
+ SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING;
+
+ } else {
+ SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING;
+ }
+
+ if ( !cm->defer_db_open ) {
+ rc = pcache_db_open2( on, cr );
+ }
+
+#ifdef PCACHE_MONITOR
+ if ( rc == LDAP_SUCCESS ) {
+ rc = pcache_monitor_db_open( be );
+ }
+#endif /* PCACHE_MONITOR */
+
+ return rc;
+}
+
+static void
+pcache_free_qbase( void *v )
+{
+ Qbase *qb = v;
+ int i;
+
+ for (i=0; i<3; i++)
+ ldap_tavl_free( qb->scopes[i], NULL );
+ ch_free( qb );
+}
+
+static int
+pcache_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ QueryTemplate *tm;
+ int rc = 0;
+
+ /* stop the thread ... */
+ if ( cm->cc_arg ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, cm->cc_arg ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, cm->cc_arg );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, cm->cc_arg );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ cm->cc_arg = NULL;
+ }
+
+ if ( cm->save_queries ) {
+ CachedQuery *qc;
+ BerVarray vals = NULL;
+
+ void *thrctx;
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { 0 };
+
+ SlapReply rs = { REP_RESULT };
+ Modifications mod = {{ 0 }};
+
+ thrctx = ldap_pvt_thread_pool_context();
+
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ mod.sml_numvals = 0;
+ if ( qm->templates != NULL ) {
+ for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) {
+ for ( qc = tm->query; qc; qc = qc->next ) {
+ struct berval bv;
+
+ if ( query2url( op, qc, &bv, 0 ) == 0 ) {
+ ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx );
+ mod.sml_numvals++;
+ }
+ }
+ }
+ }
+
+ op->o_bd = &cm->db;
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_protocol = LDAP_VERSION3;
+ cb.sc_response = slap_null_cb;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_cachedQueryURL;
+ mod.sml_type = ad_cachedQueryURL->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_next = NULL;
+ Debug( pcache_debug,
+ "%sSETTING CACHED QUERY URLS\n",
+ vals == NULL ? "RE" : "" );
+
+ op->orm_modlist = &mod;
+
+ op->o_bd->be_modify( op, &rs );
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ /* cleanup stuff inherited from the original database... */
+ cm->db.be_limits = NULL;
+ cm->db.be_acl = NULL;
+
+ if ( cm->db.bd_info->bi_db_close ) {
+ rc = cm->db.bd_info->bi_db_close( &cm->db, NULL );
+ }
+
+#ifdef PCACHE_MONITOR
+ if ( rc == LDAP_SUCCESS ) {
+ rc = pcache_monitor_db_close( be );
+ }
+#endif /* PCACHE_MONITOR */
+
+ return rc;
+}
+
+static int
+pcache_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ QueryTemplate *tm;
+ int i;
+
+ if ( cm->db.be_private != NULL ) {
+ backend_stopdown_one( &cm->db );
+ }
+
+ while ( (tm = qm->templates) != NULL ) {
+ CachedQuery *qc, *qn;
+ qm->templates = tm->qmnext;
+ for ( qc = tm->query; qc; qc = qn ) {
+ qn = qc->next;
+ free_query( qc );
+ }
+ ldap_avl_free( tm->qbase, pcache_free_qbase );
+ free( tm->querystr.bv_val );
+ free( tm->bindfattrs );
+ free( tm->bindftemp.bv_val );
+ free( tm->bindfilterstr.bv_val );
+ free( tm->bindbase.bv_val );
+ filter_free( tm->bindfilter );
+ ldap_pvt_thread_rdwr_destroy( &tm->t_rwlock );
+ free( tm->t_attrs.attrs );
+ free( tm );
+ }
+
+ for ( i = 0; i < cm->numattrsets; i++ ) {
+ int j;
+
+ /* Account of LDAP_NO_ATTRS */
+ if ( !qm->attr_sets[i].count ) continue;
+
+ for ( j = 0; !BER_BVISNULL( &qm->attr_sets[i].attrs[j].an_name ); j++ ) {
+ if ( qm->attr_sets[i].attrs[j].an_desc &&
+ ( qm->attr_sets[i].attrs[j].an_desc->ad_flags &
+ SLAP_DESC_TEMPORARY ) ) {
+ slap_sl_mfuncs.bmf_free( qm->attr_sets[i].attrs[j].an_desc, NULL );
+ }
+ }
+ free( qm->attr_sets[i].attrs );
+ }
+ free( qm->attr_sets );
+ qm->attr_sets = NULL;
+
+ ldap_pvt_thread_mutex_destroy( &qm->lru_mutex );
+ ldap_pvt_thread_mutex_destroy( &cm->cache_mutex );
+ free( qm );
+ free( cm );
+
+#ifdef PCACHE_MONITOR
+ pcache_monitor_db_destroy( be );
+#endif /* PCACHE_MONITOR */
+
+ return 0;
+}
+
+#ifdef PCACHE_CONTROL_PRIVDB
+/*
+ Control ::= SEQUENCE {
+ controlType LDAPOID,
+ criticality BOOLEAN DEFAULT FALSE,
+ controlValue OCTET STRING OPTIONAL }
+
+ controlType ::= 1.3.6.1.4.1.4203.666.11.9.5.1
+
+ * criticality must be TRUE; controlValue must be absent.
+ */
+static int
+parse_privdb_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "privateDB control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "privateDB control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !ctrl->ldctl_iscritical ) {
+ rs->sr_text = "privateDB control criticality required";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_ctrlflag[ privDB_cid ] = SLAP_CONTROL_CRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static char *extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ NULL
+};
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static struct berval pcache_exop_MODIFY_PASSWD = BER_BVC( LDAP_EXOP_MODIFY_PASSWD );
+#ifdef PCACHE_EXOP_QUERY_DELETE
+static struct berval pcache_exop_QUERY_DELETE = BER_BVC( PCACHE_EXOP_QUERY_DELETE );
+
+#define LDAP_TAG_EXOP_QUERY_DELETE_BASE ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 0)
+#define LDAP_TAG_EXOP_QUERY_DELETE_DN ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 1)
+#define LDAP_TAG_EXOP_QUERY_DELETE_UUID ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 2)
+
+/*
+ ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
+ requestName [0] LDAPOID,
+ requestValue [1] OCTET STRING OPTIONAL }
+
+ requestName ::= 1.3.6.1.4.1.4203.666.11.9.6.1
+
+ requestValue ::= SEQUENCE { CHOICE {
+ baseDN [0] LDAPDN
+ entryDN [1] LDAPDN },
+ queryID [2] OCTET STRING (SIZE(16))
+ -- constrained to UUID }
+
+ * Either baseDN or entryDN must be present, to allow database selection.
+ *
+ * 1. if baseDN and queryID are present, then the query corresponding
+ * to queryID is deleted;
+ * 2. if baseDN is present and queryID is absent, then all queries
+ * are deleted;
+ * 3. if entryDN is present and queryID is absent, then all queries
+ * corresponding to the queryID values present in entryDN are deleted;
+ * 4. if entryDN and queryID are present, then all queries
+ * corresponding to the queryID values present in entryDN are deleted,
+ * but only if the value of queryID is contained in the entry;
+ *
+ * Currently, only 1, 3 and 4 are implemented. 2 can be obtained by either
+ * recursively deleting the database (ldapdelete -r) with PRIVDB control,
+ * or by removing the database files.
+
+ ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
+ COMPONENTS OF LDAPResult,
+ responseName [10] LDAPOID OPTIONAL,
+ responseValue [11] OCTET STRING OPTIONAL }
+
+ * responseName and responseValue must be absent.
+ */
+
+/*
+ * - on success, *tagp is either LDAP_TAG_EXOP_QUERY_DELETE_BASE
+ * or LDAP_TAG_EXOP_QUERY_DELETE_DN.
+ * - if ndn != NULL, it is set to the normalized DN in the request
+ * corresponding to either the baseDN or the entryDN, according
+ * to *tagp; memory is malloc'ed on the Operation's slab, and must
+ * be freed by the caller.
+ * - if uuid != NULL, it is set to point to the normalized UUID;
+ * memory is malloc'ed on the Operation's slab, and must
+ * be freed by the caller.
+ */
+static int
+pcache_parse_query_delete(
+ struct berval *in,
+ ber_tag_t *tagp,
+ struct berval *ndn,
+ struct berval *uuid,
+ const char **text,
+ void *ctx )
+{
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval reqdata = BER_BVNULL;
+
+ *text = NULL;
+
+ if ( ndn ) {
+ BER_BVZERO( ndn );
+ }
+
+ if ( uuid ) {
+ BER_BVZERO( uuid );
+ }
+
+ if ( in == NULL || in->bv_len == 0 ) {
+ *text = "empty request data field in queryDelete exop";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_dupbv_x( &reqdata, in, ctx );
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, &reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE
+ || tag == LDAP_TAG_EXOP_QUERY_DELETE_DN )
+ {
+ *tagp = tag;
+
+ if ( ndn != NULL ) {
+ struct berval dn;
+
+ tag = ber_scanf( ber, "m", &dn );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: DN parse failed.\n" );
+ goto decoding_error;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ *text = "invalid DN in queryDelete exop request data";
+ goto done;
+ }
+
+ } else {
+ tag = ber_scanf( ber, "x" /* "m" */ );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_UUID ) {
+ if ( uuid != NULL ) {
+ struct berval bv;
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+
+ tag = ber_scanf( ber, "m", &bv );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: UUID parse failed.\n" );
+ goto decoding_error;
+ }
+
+ if ( bv.bv_len != 16 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: invalid UUID length %lu.\n",
+ (unsigned long)bv.bv_len );
+ goto decoding_error;
+ }
+
+ rc = lutil_uuidstr_from_normalized(
+ bv.bv_val, bv.bv_len,
+ uuidbuf, sizeof( uuidbuf ) );
+ if ( rc == -1 ) {
+ goto decoding_error;
+ }
+ ber_str2bv( uuidbuf, rc, 1, uuid );
+ rc = LDAP_SUCCESS;
+
+ } else {
+ tag = ber_skip_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+
+ if ( len != 16 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: invalid UUID length %lu.\n",
+ (unsigned long)len );
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag != LBER_DEFAULT || len != 0 ) {
+decoding_error:;
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: decoding error\n" );
+ rc = LDAP_PROTOCOL_ERROR;
+ *text = "queryDelete data decoding error";
+
+done:;
+ if ( ndn && !BER_BVISNULL( ndn ) ) {
+ slap_sl_free( ndn->bv_val, ctx );
+ BER_BVZERO( ndn );
+ }
+
+ if ( uuid && !BER_BVISNULL( uuid ) ) {
+ slap_sl_free( uuid->bv_val, ctx );
+ BER_BVZERO( uuid );
+ }
+ }
+
+ if ( !BER_BVISNULL( &reqdata ) ) {
+ ber_memfree_x( reqdata.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+static int
+pcache_exop_query_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ struct berval uuid = BER_BVNULL,
+ *uuidp = NULL;
+ char buf[ SLAP_TEXT_BUFLEN ];
+ unsigned len;
+ ber_tag_t tag = LBER_DEFAULT;
+
+ if ( LogTest( LDAP_DEBUG_STATS ) ) {
+ uuidp = &uuid;
+ }
+
+ rs->sr_err = pcache_parse_query_delete( op->ore_reqdata,
+ &tag, &op->o_req_ndn, uuidp,
+ &rs->sr_text, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ if ( LogTest( LDAP_DEBUG_STATS ) ) {
+ assert( !BER_BVISNULL( &op->o_req_ndn ) );
+ len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val );
+
+ if ( !BER_BVISNULL( &uuid ) && len < sizeof( buf ) ) {
+ snprintf( &buf[ len ], sizeof( buf ) - len, " pcacheQueryId=\"%s\"", uuid.bv_val );
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s QUERY DELETE%s\n",
+ op->o_log_prefix, buf );
+ }
+ op->o_req_dn = op->o_req_ndn;
+
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL ) {
+ send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT,
+ "no global superior knowledge" );
+ }
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&pcache_exop_QUERY_DELETE );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if ( op->o_bd->be_extended == NULL ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support extended operations" );
+ goto done;
+ }
+
+ op->o_bd->be_extended( op, rs );
+
+done:;
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ BER_BVZERO( &op->o_req_dn );
+ }
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
+ }
+
+ op->o_bd = bd;
+
+ return rs->sr_err;
+}
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+static int
+pcache_op_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) {
+ return pcache_op_privdb( op, rs );
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+#ifdef PCACHE_EXOP_QUERY_DELETE
+ if ( bvmatch( &op->ore_reqoid, &pcache_exop_QUERY_DELETE ) ) {
+ struct berval uuid = BER_BVNULL;
+ ber_tag_t tag = LBER_DEFAULT;
+
+ rs->sr_err = pcache_parse_query_delete( op->ore_reqdata,
+ &tag, NULL, &uuid, &rs->sr_text, op->o_tmpmemctx );
+ assert( rs->sr_err == LDAP_SUCCESS );
+
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) {
+ /* remove all queries related to the selected entry */
+ rs->sr_err = pcache_remove_entry_queries_from_cache( op,
+ cm, &op->o_req_ndn, &uuid );
+
+ } else if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE ) {
+ if ( !BER_BVISNULL( &uuid ) ) {
+ /* remove the selected query */
+ rs->sr_err = pcache_remove_query_from_cache( op,
+ cm, &uuid );
+
+ } else {
+ /* TODO: remove all queries */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "deletion of all queries not implemented";
+ }
+ }
+
+ op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
+ return rs->sr_err;
+ }
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+ /* We only care if we're configured for Bind caching */
+ if ( bvmatch( &op->ore_reqoid, &pcache_exop_MODIFY_PASSWD ) &&
+ cm->cache_binds ) {
+ /* See if the local entry exists and has a password.
+ * It's too much work to find the matching query, so
+ * we just see if there's a hashed password to update.
+ */
+ Operation op2 = *op;
+ Entry *e = NULL;
+ int rc;
+ int doit = 0;
+
+ op2.o_bd = &cm->db;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL,
+ slap_schema.si_ad_userPassword, 0, &e );
+ if ( rc == LDAP_SUCCESS && e ) {
+ /* See if a recognized password is hashed here */
+ Attribute *a = attr_find( e->e_attrs,
+ slap_schema.si_ad_userPassword );
+ if ( a && a->a_vals[0].bv_val[0] == '{' &&
+ lutil_passwd_scheme( a->a_vals[0].bv_val )) {
+ doit = 1;
+ }
+ be_entry_release_r( &op2, e );
+ }
+
+ if ( doit ) {
+ rc = overlay_op_walk( op, rs, op_extended, on->on_info,
+ on->on_next );
+ if ( rc == LDAP_SUCCESS ) {
+ req_pwdexop_s *qpw = &op->oq_pwdexop;
+
+ /* We don't care if it succeeds or not */
+ pc_setpw( &op2, &qpw->rs_new, cm );
+ }
+ return rc;
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_entry_release( Operation *op, Entry *e, int rw )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ BackendDB *db = op->o_bd;
+ int rc;
+
+ op->o_bd = &cm->db;
+ rc = be_entry_release_rw( op, e, rw );
+ op->o_bd = db;
+ return rc;
+}
+
+#ifdef PCACHE_MONITOR
+
+static int
+pcache_monitor_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ cache_manager *cm = (cache_manager *) priv;
+ query_manager *qm = cm->qm;
+
+ CachedQuery *qc;
+ BerVarray vals = NULL;
+
+ attr_delete( &e->e_attrs, ad_cachedQueryURL );
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( ad_cachedQueryURL, rs->sr_attrs ) )
+ && qm->templates != NULL )
+ {
+ QueryTemplate *tm;
+
+ for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) {
+ for ( qc = tm->query; qc; qc = qc->next ) {
+ struct berval bv;
+
+ if ( query2url( op, qc, &bv, 1 ) == 0 ) {
+ ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx );
+ }
+ }
+ }
+
+
+ if ( vals != NULL ) {
+ attr_merge_normalize( e, ad_cachedQueryURL, vals, NULL );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+ {
+ Attribute *a;
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+
+ /* number of cached queries */
+ a = attr_find( e->e_attrs, ad_numQueries );
+ assert( a != NULL );
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", cm->num_cached_queries );
+
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvreplace( &a->a_nvals[ 0 ], &bv );
+ }
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ /* number of cached entries */
+ a = attr_find( e->e_attrs, ad_numEntries );
+ assert( a != NULL );
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", cm->cur_entries );
+
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvreplace( &a->a_nvals[ 0 ], &bv );
+ }
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_monitor_free(
+ Entry *e,
+ void **priv )
+{
+ struct berval values[ 2 ];
+ Modification mod = { 0 };
+
+ const char *text;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ int rc;
+
+ /* NOTE: if slap_shutdown != 0, priv might have already been freed */
+ *priv = NULL;
+
+ /* Remove objectClass */
+ mod.sm_op = LDAP_MOD_DELETE;
+ mod.sm_desc = slap_schema.si_ad_objectClass;
+ mod.sm_values = values;
+ mod.sm_numvals = 1;
+ values[ 0 ] = oc_olmPCache->soc_cname;
+ BER_BVZERO( &values[ 1 ] );
+
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_cachedQueryURL;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_numQueries;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_numEntries;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * call from within pcache_initialize()
+ */
+static int
+pcache_monitor_initialize( void )
+{
+ static int pcache_monitor_initialized = 0;
+
+ if ( backend_info( "monitor" ) == NULL ) {
+ return -1;
+ }
+
+ if ( pcache_monitor_initialized++ ) {
+ return 0;
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_init( BackendDB *be )
+{
+ if ( pcache_monitor_initialize() == LDAP_SUCCESS ) {
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING;
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_open( BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ Attribute *a, *next;
+ monitor_callback_t *cb = NULL;
+ int rc = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+
+ if ( !SLAP_DBMONITORING( be ) ) {
+ return 0;
+ }
+
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING;
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache_monitor_db_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ return 0;
+ }
+
+ /* alloc as many as required (plus 1 for objectClass) */
+ a = attrs_alloc( 1 + 2 );
+ if ( a == NULL ) {
+ rc = 1;
+ goto cleanup;
+ }
+
+ a->a_desc = slap_schema.si_ad_objectClass;
+ attr_valadd( a, &oc_olmPCache->soc_cname, NULL, 1 );
+ next = a->a_next;
+
+ {
+ struct berval bv = BER_BVC( "0" );
+
+ next->a_desc = ad_numQueries;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_numEntries;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+ }
+
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = pcache_monitor_update;
+ cb->mc_free = pcache_monitor_free;
+ cb->mc_private = (void *)cm;
+
+ /* make sure the database is registered; then add monitor attributes */
+ BER_BVZERO( &cm->monitor_ndn );
+ rc = mbe->register_overlay( be, on, &cm->monitor_ndn );
+ if ( rc == 0 ) {
+ rc = mbe->register_entry_attrs( &cm->monitor_ndn, a, cb,
+ NULL, -1, NULL);
+ }
+
+cleanup:;
+ if ( rc != 0 ) {
+ if ( cb != NULL ) {
+ ch_free( cb );
+ cb = NULL;
+ }
+
+ if ( a != NULL ) {
+ attrs_free( a );
+ a = NULL;
+ }
+ }
+
+ /* store for cleanup */
+ cm->monitor_cb = (void *)cb;
+
+ /* we don't need to keep track of the attributes, because
+ * mdb_monitor_free() takes care of everything */
+ if ( a != NULL ) {
+ attrs_free( a );
+ }
+
+ return rc;
+}
+
+static int
+pcache_monitor_db_close( BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+
+ if ( !BER_BVISNULL( &cm->monitor_ndn )) {
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe;
+
+ if ( mi && mi->bi_extra ) {
+ struct berval dummy = BER_BVNULL;
+ mbe = mi->bi_extra;
+ mbe->unregister_entry_callback( &cm->monitor_ndn,
+ (monitor_callback_t *)cm->monitor_cb,
+ &dummy, 0, &dummy );
+ }
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_destroy( BackendDB *be )
+{
+ return 0;
+}
+
+#endif /* PCACHE_MONITOR */
+
+static slap_overinst pcache;
+
+static char *obsolete_names[] = {
+ "proxycache",
+ NULL
+};
+
+#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC */
+int
+pcache_initialize()
+{
+ int i, code;
+ struct berval debugbv = BER_BVC("pcache");
+ ConfigArgs c;
+ char *argv[ 4 ];
+
+ /* olcDatabaseDummy is defined in slapd, and Windows
+ will not let us initialize a struct element with a data pointer
+ from another library, so we have to initialize this element
+ "by hand". */
+ pcocs[1].co_table = olcDatabaseDummy;
+
+
+ code = slap_loglevel_get( &debugbv, &pcache_debug );
+ if ( code ) {
+ return code;
+ }
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ code = register_supported_control( PCACHE_CONTROL_PRIVDB,
+ SLAP_CTRL_BIND|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, extops,
+ parse_privdb_ctrl, &privDB_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: failed to register control %s (%d)\n",
+ PCACHE_CONTROL_PRIVDB, code );
+ return code;
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+#ifdef PCACHE_EXOP_QUERY_DELETE
+ code = load_extop2( (struct berval *)&pcache_exop_QUERY_DELETE,
+ SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, pcache_exop_query_delete,
+ 0 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: unable to register queryDelete exop: %d.\n",
+ code );
+ return code;
+ }
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+ argv[ 0 ] = "back-mdb monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+
+ for ( i = 0; s_oid[ i ].name; i++ ) {
+ c.lineno = i;
+ argv[ 1 ] = s_oid[ i ].name;
+ argv[ 2 ] = s_oid[ i ].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "pcache_initialize: "
+ "unable to add objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid );
+ return 1;
+ }
+ }
+
+ for ( i = 0; s_ad[i].desc != NULL; i++ ) {
+ code = register_at( s_ad[i].desc, s_ad[i].adp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: register_at #%d failed\n", i );
+ return code;
+ }
+ (*s_ad[i].adp)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+
+ for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+ code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: register_oc #%d failed\n", i );
+ return code;
+ }
+ (*s_oc[i].ocp)->soc_flags |= SLAP_OC_HIDE;
+ }
+
+ pcache.on_bi.bi_type = "pcache";
+ pcache.on_bi.bi_obsolete_names = obsolete_names;
+ pcache.on_bi.bi_db_init = pcache_db_init;
+ pcache.on_bi.bi_db_config = pcache_db_config;
+ pcache.on_bi.bi_db_open = pcache_db_open;
+ pcache.on_bi.bi_db_close = pcache_db_close;
+ pcache.on_bi.bi_db_destroy = pcache_db_destroy;
+
+ pcache.on_bi.bi_op_search = pcache_op_search;
+ pcache.on_bi.bi_op_bind = pcache_op_bind;
+#ifdef PCACHE_CONTROL_PRIVDB
+ pcache.on_bi.bi_op_compare = pcache_op_privdb;
+ pcache.on_bi.bi_op_modrdn = pcache_op_privdb;
+ pcache.on_bi.bi_op_modify = pcache_op_privdb;
+ pcache.on_bi.bi_op_add = pcache_op_privdb;
+ pcache.on_bi.bi_op_delete = pcache_op_privdb;
+#endif /* PCACHE_CONTROL_PRIVDB */
+ pcache.on_bi.bi_extended = pcache_op_extended;
+
+ pcache.on_bi.bi_entry_release_rw = pcache_entry_release;
+ pcache.on_bi.bi_chk_controls = pcache_chk_controls;
+
+ pcache.on_bi.bi_cf_ocs = pcocs;
+
+ code = config_register_schema( pccfg, pcocs );
+ if ( code ) return code;
+
+ return overlay_register( &pcache );
+}
+
+#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return pcache_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_PROXYCACHE) */
diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c
new file mode 100644
index 0000000..29a77c8
--- /dev/null
+++ b/servers/slapd/overlays/ppolicy.c
@@ -0,0 +1,3490 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004-2005 Howard Chu, Symas Corporation.
+ * Portions Copyright 2004 Hewlett-Packard Company.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Howard Chu for inclusion in
+ * OpenLDAP Software, based on prior work by Neil Dunbar (HP).
+ * This work was sponsored by the Hewlett-Packard Company.
+ */
+
+#include "portable.h"
+
+/* This file implements "Password Policy for LDAP Directories",
+ * based on draft behera-ldap-password-policy-09
+ */
+
+#ifdef SLAPD_OVER_PPOLICY
+
+#include <ldap.h>
+#include "lutil.h"
+#include "slap.h"
+#ifdef SLAPD_MODULES
+#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */
+#include <ltdl.h>
+#endif
+#include <ac/errno.h>
+#include <ac/time.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include "slap-config.h"
+
+#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
+#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5
+#endif
+
+ /* External password quality checking function.
+ * The error message must have a preallocated buffer and size
+ * passed in. Module can still allocate a buffer for
+ * it if the provided one is too small.
+ */
+typedef int (check_func)( char *passwd, struct berval *errmsg, Entry *ent, struct berval *arg );
+#define ERRBUFSIZ 256
+
+/* Per-instance configuration information */
+typedef struct pp_info {
+ struct berval def_policy; /* DN of default policy subentry */
+ int use_lockout; /* send AccountLocked result? */
+ int hash_passwords; /* transparently hash cleartext pwds */
+ int forward_updates; /* use frontend for policy state updates */
+ int disable_write;
+ int send_netscape_controls; /* send netscape password controls */
+ char *pwdCheckModule; /* name of module to dynamically
+ load to check password */
+#ifdef SLAPD_MODULES
+ lt_dlhandle pwdCheckHandle; /* handle from lt_dlopen */
+ check_func *pwdCheckFunc;
+#endif /* SLAPD_MODULES */
+ ldap_pvt_thread_mutex_t pwdFailureTime_mutex;
+} pp_info;
+
+/* Our per-connection info - note, it is not per-instance, it is
+ * used by all instances
+ */
+typedef struct pw_conn {
+ struct berval dn; /* DN of restricted user */
+} pw_conn;
+
+static pw_conn *pwcons;
+static int ppolicy_cid;
+static int account_usability_cid;
+static int ov_count;
+
+typedef struct pass_policy {
+ AttributeDescription *ad; /* attribute to which the policy applies */
+ int pwdMinAge; /* minimum time (seconds) until passwd can change */
+ int pwdMaxAge; /* time in seconds until pwd will expire after change */
+ int pwdMaxIdle; /* number of seconds since last successful bind before
+ passwd gets locked out */
+ int pwdInHistory; /* number of previous passwords kept */
+ int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible,
+ 2 = check mandatory; fail if not possible */
+ int pwdMinLength; /* minimum number of chars in password */
+ int pwdMaxLength; /* maximum number of chars in password */
+ int pwdExpireWarning; /* number of seconds that warning controls are
+ sent before a password expires */
+ int pwdGraceExpiry; /* number of seconds after expiry grace logins are
+ valid */
+ int pwdGraceAuthNLimit; /* number of times you can log in with an
+ expired password */
+ int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */
+ int pwdLockoutDuration; /* time in seconds a password is locked out for */
+ int pwdMinDelay; /* base bind delay in seconds on failure */
+ int pwdMaxDelay; /* maximum bind delay in seconds */
+ int pwdMaxFailure; /* number of failed binds allowed before lockout */
+ int pwdMaxRecordedFailure; /* number of failed binds to store */
+ int pwdFailureCountInterval; /* number of seconds before failure
+ counts are zeroed */
+ int pwdMustChange; /* 0 = users can use admin set password
+ 1 = users must change password after admin set */
+ int pwdAllowUserChange; /* 0 = users cannot change their passwords
+ 1 = users can change them */
+ int pwdSafeModify; /* 0 = old password doesn't need to come
+ with password change request
+ 1 = password change must supply existing pwd */
+ int pwdUseCheckModule; /* 0 = do not use password check module, 1 = use */
+ struct berval pwdCheckModuleArg; /* Optional argument to the password check
+ module */
+} PassPolicy;
+
+typedef struct pw_hist {
+ time_t t; /* timestamp of history entry */
+ struct berval pw; /* old password hash */
+ struct berval bv; /* text of entire entry */
+ struct pw_hist *next;
+} pw_hist;
+
+/* Operational attributes */
+static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime,
+ *ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset,
+ *ad_pwdPolicySubentry, *ad_pwdStartTime, *ad_pwdEndTime,
+ *ad_pwdLastSuccess, *ad_pwdAccountTmpLockoutEnd;
+
+/* Policy attributes */
+static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdMaxIdle,
+ *ad_pwdInHistory, *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxLength,
+ *ad_pwdMaxFailure, *ad_pwdGraceExpiry, *ad_pwdGraceAuthNLimit,
+ *ad_pwdExpireWarning, *ad_pwdMinDelay, *ad_pwdMaxDelay,
+ *ad_pwdLockoutDuration, *ad_pwdFailureCountInterval,
+ *ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdUseCheckModule, *ad_pwdLockout,
+ *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
+ *ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
+
+static struct schema_info {
+ char *def;
+ AttributeDescription **ad;
+} pwd_OpSchema[] = {
+ { "( 1.3.6.1.4.1.42.2.27.8.1.16 "
+ "NAME ( 'pwdChangedTime' ) "
+ "DESC 'The time the password was last changed' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdChangedTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.17 "
+ "NAME ( 'pwdAccountLockedTime' ) "
+ "DESC 'The time an user account was locked' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+#if 0 /* FIXME: ITS#9671 until we introduce a separate lockout flag? */
+ "NO-USER-MODIFICATION "
+#endif
+ "USAGE directoryOperation )",
+ &ad_pwdAccountLockedTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.19 "
+ "NAME ( 'pwdFailureTime' ) "
+ "DESC 'The timestamps of the last consecutive authentication failures' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdFailureTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.20 "
+ "NAME ( 'pwdHistory' ) "
+ "DESC 'The history of users passwords' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdHistory },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.21 "
+ "NAME ( 'pwdGraceUseTime' ) "
+ "DESC 'The timestamps of the grace login once the password has expired' "
+ "EQUALITY generalizedTimeMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdGraceUseTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.22 "
+ "NAME ( 'pwdReset' ) "
+ "DESC 'The indication that the password has been reset' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation )",
+ &ad_pwdReset },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.23 "
+ "NAME ( 'pwdPolicySubentry' ) "
+ "DESC 'The pwdPolicy subentry in effect for this object' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE "
+#if 0 /* ITS#9671: until we implement ITS#9343 or similar */
+ "NO-USER-MODIFICATION "
+#endif
+ "USAGE directoryOperation )",
+ &ad_pwdPolicySubentry },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.27 "
+ "NAME ( 'pwdStartTime' ) "
+ "DESC 'The time the password becomes enabled' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation )",
+ &ad_pwdStartTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.28 "
+ "NAME ( 'pwdEndTime' ) "
+ "DESC 'The time the password becomes disabled' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation )",
+ &ad_pwdEndTime },
+ /* Defined in schema_prep.c now
+ { "( 1.3.6.1.4.1.42.2.27.8.1.29 "
+ "NAME ( 'pwdLastSuccess' ) "
+ "DESC 'The timestamp of the last successful authentication' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdLastSuccess },
+ */
+ { "( 1.3.6.1.4.1.42.2.27.8.1.33 "
+ "NAME ( 'pwdAccountTmpLockoutEnd' ) "
+ "DESC 'Temporary lockout end' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdAccountTmpLockoutEnd },
+
+ { "( 1.3.6.1.4.1.42.2.27.8.1.1 "
+ "NAME ( 'pwdAttribute' ) "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ &ad_pwdAttribute },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.2 "
+ "NAME ( 'pwdMinAge' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMinAge },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.3 "
+ "NAME ( 'pwdMaxAge' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxAge },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.4 "
+ "NAME ( 'pwdInHistory' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdInHistory },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.5 "
+ "NAME ( 'pwdCheckQuality' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckQuality },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.6 "
+ "NAME ( 'pwdMinLength' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMinLength },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.31 "
+ "NAME ( 'pwdMaxLength' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxLength },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.7 "
+ "NAME ( 'pwdExpireWarning' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdExpireWarning },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.8 "
+ "NAME ( 'pwdGraceAuthNLimit' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdGraceAuthNLimit },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.30 "
+ "NAME ( 'pwdGraceExpiry' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdGraceExpiry },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.9 "
+ "NAME ( 'pwdLockout' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdLockout },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.10 "
+ "NAME ( 'pwdLockoutDuration' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdLockoutDuration },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.11 "
+ "NAME ( 'pwdMaxFailure' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxFailure },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.12 "
+ "NAME ( 'pwdFailureCountInterval' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdFailureCountInterval },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.13 "
+ "NAME ( 'pwdMustChange' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdMustChange },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.14 "
+ "NAME ( 'pwdAllowUserChange' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdAllowUserChange },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.15 "
+ "NAME ( 'pwdSafeModify' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdSafeModify },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.24 "
+ "NAME ( 'pwdMinDelay' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMinDelay },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.25 "
+ "NAME ( 'pwdMaxDelay' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxDelay },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.26 "
+ "NAME ( 'pwdMaxIdle' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxIdle },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.32 "
+ "NAME ( 'pwdMaxRecordedFailure' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxRecordedFailure },
+ { "( 1.3.6.1.4.1.4754.1.99.1 "
+ "NAME ( 'pwdCheckModule' ) "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 "
+ "DESC 'Obsolete, no longer used' "
+ "OBSOLETE "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckModule },
+ { "( 1.3.6.1.4.1.4754.1.99.2 "
+ "NAME ( 'pwdCheckModuleArg' ) "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "DESC 'Argument to pass to check_password() function' "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckModuleArg },
+ { "( 1.3.6.1.4.1.4754.1.99.3 "
+ "NAME ( 'pwdUseCheckModule' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "DESC 'Toggle use of the loaded pwdCheckModule' "
+ "SINGLE-VALUE )",
+ &ad_pwdUseCheckModule },
+
+ { NULL, NULL }
+};
+
+static char *pwd_ocs[] = {
+ "( 1.3.6.1.4.1.4754.2.99.1 "
+ "NAME 'pwdPolicyChecker' "
+ "SUP top "
+ "AUXILIARY "
+ "MAY ( pwdCheckModule $ pwdCheckModuleArg $ pwdUseCheckModule ) )" ,
+ "( 1.3.6.1.4.1.42.2.27.8.2.1 "
+ "NAME 'pwdPolicy' "
+ "SUP top "
+ "AUXILIARY "
+ "MUST ( pwdAttribute ) "
+ "MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $ "
+ "pwdMinLength $ pwdMaxLength $ pwdExpireWarning $ "
+ "pwdGraceAuthNLimit $ pwdGraceExpiry $ pwdLockout $ "
+ "pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ "
+ "pwdMustChange $ pwdAllowUserChange $ pwdSafeModify $ "
+ "pwdMinDelay $ pwdMaxDelay $ pwdMaxIdle $ "
+ "pwdMaxRecordedFailure ) )",
+ NULL
+};
+
+static ldap_pvt_thread_mutex_t chk_syntax_mutex;
+
+enum {
+ PPOLICY_DEFAULT = 1,
+ PPOLICY_HASH_CLEARTEXT,
+ PPOLICY_USE_LOCKOUT,
+ PPOLICY_DISABLE_WRITE,
+ PPOLICY_CHECK_MODULE,
+};
+
+static ConfigDriver ppolicy_cf_default, ppolicy_cf_checkmod;
+
+static ConfigTable ppolicycfg[] = {
+ { "ppolicy_default", "policyDN", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
+ "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' "
+ "DESC 'DN of a pwdPolicy object for uncustomized objects' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_hash_cleartext", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT,
+ (void *)offsetof(pp_info,hash_passwords),
+ "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' "
+ "DESC 'Hash passwords on add or modify' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_forward_updates", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(pp_info,forward_updates),
+ "( OLcfgOvAt:12.4 NAME 'olcPPolicyForwardUpdates' "
+ "DESC 'Allow policy state updates to be forwarded via updateref' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_use_lockout", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT,
+ (void *)offsetof(pp_info,use_lockout),
+ "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' "
+ "DESC 'Warn clients with AccountLocked' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_disable_write", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_DISABLE_WRITE,
+ (void *)offsetof(pp_info,disable_write),
+ "( OLcfgOvAt:12.5 NAME 'olcPPolicyDisableWrite' "
+ "DESC 'Prevent all policy overlay writes' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_send_netscape_controls", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(pp_info,send_netscape_controls),
+ "( OLcfgOvAt:12.6 NAME 'olcPPolicySendNetscapeControls' "
+ "DESC 'Send Netscape policy controls' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_check_module", "path", 2, 2, 0,
+#ifdef SLAPD_MODULES
+ ARG_STRING|ARG_MAGIC|PPOLICY_CHECK_MODULE, ppolicy_cf_checkmod,
+#else
+ ARG_IGNORED, NULL,
+#endif /* SLAPD_MODULES */
+ "( OLcfgOvAt:12.7 NAME 'olcPPolicyCheckModule' "
+ "DESC 'Loadable module that instantiates check_password() function' "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX OMsIA5String "
+ "SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs ppolicyocs[] = {
+ { "( OLcfgOvOc:12.1 "
+ "NAME 'olcPPolicyConfig' "
+ "DESC 'Password Policy configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
+ "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ "
+ "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls $ "
+ "olcPPolicyCheckModule ) )",
+ Cft_Overlay, ppolicycfg },
+ { NULL, 0, NULL }
+};
+
+static int
+ppolicy_cf_default( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ pp_info *pi = (pp_info *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert ( c->type == PPOLICY_DEFAULT );
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n" );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n" );
+ rc = 0;
+ if ( !BER_BVISEMPTY( &pi->def_policy )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &pi->def_policy );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &pi->def_policy );
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n" );
+ if ( pi->def_policy.bv_val ) {
+ ber_memfree ( pi->def_policy.bv_val );
+ pi->def_policy.bv_val = NULL;
+ }
+ pi->def_policy.bv_len = 0;
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n" );
+ if ( pi->def_policy.bv_val ) {
+ ber_memfree ( pi->def_policy.bv_val );
+ }
+ pi->def_policy = c->value_ndn;
+ ber_memfree( c->value_dn.bv_val );
+ BER_BVZERO( &c->value_dn );
+ BER_BVZERO( &c->value_ndn );
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+#ifdef SLAPD_MODULES
+static int
+ppolicy_cf_checkmod( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ pp_info *pi = (pp_info *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert ( c->type == PPOLICY_CHECK_MODULE );
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_checkmod\n" );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( pi->pwdCheckModule ) {
+ c->value_string = ch_strdup( pi->pwdCheckModule );
+ rc = 0;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( pi->pwdCheckHandle ) {
+ lt_dlclose( pi->pwdCheckHandle );
+ pi->pwdCheckHandle = NULL;
+ pi->pwdCheckFunc = NULL;
+ }
+ ch_free( pi->pwdCheckModule );
+ pi->pwdCheckModule = NULL;
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ pi->pwdCheckHandle = lt_dlopen( c->value_string );
+ if ( pi->pwdCheckHandle == NULL ) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlopen(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ if (( pi->pwdCheckFunc = lt_dlsym( pi->pwdCheckHandle, "check_password" )) == NULL) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlsym(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ pi->pwdCheckModule = c->value_string;
+ rc = 0;
+ }
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+#endif /* SLAPD_MODULES */
+
+static time_t
+parse_time( char *atm )
+{
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+ time_t ret = (time_t)-1;
+
+ if ( lutil_parsetime( atm, &tm ) == 0) {
+ lutil_tm2time( &tm, &tt );
+ ret = tt.tt_sec;
+ }
+ return ret;
+}
+
+static int
+account_locked( Operation *op, Entry *e,
+ PassPolicy *pp, Modifications **mod )
+{
+ Attribute *la;
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdStartTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * Password has a defined start of validity
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( now < then ) {
+ return 1;
+ }
+ }
+ }
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdEndTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * Password has a defined end of validity
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( then <= now ) {
+ return 1;
+ }
+ }
+ }
+
+ if ( !pp->pwdLockout )
+ return 0;
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * We have temporarily locked the account after a failure
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( now < then ) {
+ return 1;
+ }
+ }
+ }
+
+ /* Only check if database maintains lastbind */
+ if ( pp->pwdMaxIdle && SLAP_LASTBIND( op->o_bd ) ) {
+ time_t lastbindtime = (time_t)-1;
+
+ la = attr_find( e->e_attrs, ad_pwdLastSuccess );
+ if ( la == NULL ) {
+ la = attr_find( e->e_attrs, ad_pwdChangedTime );
+ }
+ if ( la != NULL ) {
+ lastbindtime = parse_time( la->a_nvals[0].bv_val );
+ }
+
+ if ( lastbindtime != (time_t)-1 &&
+ op->o_time > lastbindtime + pp->pwdMaxIdle ) {
+ return 1;
+ }
+ }
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+
+ /*
+ * there is a lockout stamp - we now need to know if it's
+ * a valid one.
+ */
+ if (vals[0].bv_val != NULL) {
+ time_t then, now;
+ Modifications *m;
+
+ if ((then = parse_time( vals[0].bv_val )) == (time_t)0)
+ return 1;
+
+ now = slap_get_time();
+
+ /* Still in the future? not yet in effect */
+ if (now < then)
+ return 0;
+
+ if (!pp->pwdLockoutDuration)
+ return 1;
+
+ if (now < then + pp->pwdLockoutDuration)
+ return 1;
+
+ if ( mod != NULL ) {
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+ m->sml_desc = ad_pwdAccountLockedTime;
+ m->sml_next = *mod;
+ *mod = m;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* IMPLICIT TAGS, all context-specific */
+#define PPOLICY_WARNING 0xa0L /* constructed + 0 */
+#define PPOLICY_ERROR 0x81L /* primitive + 1 */
+
+#define PPOLICY_EXPIRE 0x80L /* primitive + 0 */
+#define PPOLICY_GRACE 0x81L /* primitive + 1 */
+
+static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
+static const char ppolicy_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY;
+static const char ppolicy_pwd_expired_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRED;
+static const char ppolicy_pwd_expiring_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRING;
+
+static LDAPControl *
+create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err )
+{
+ BerElementBuffer berbuf, bb2;
+ BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2;
+ LDAPControl c = { 0 }, *cp;
+ struct berval bv;
+ int rc;
+
+ BER_BVZERO( &c.ldctl_value );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_printf( ber, "{" /*}*/ );
+
+ if ( exptime >= 0 ) {
+ ber_init2( b2, NULL, LBER_USE_DER );
+ ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime );
+ rc = ber_flatten2( b2, &bv, 1 );
+ (void)ber_free_buf(b2);
+ if (rc == -1) {
+ cp = NULL;
+ goto fail;
+ }
+ ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
+ ch_free( bv.bv_val );
+ } else if ( grace >= 0 ) {
+ ber_init2( b2, NULL, LBER_USE_DER );
+ ber_printf( b2, "ti", PPOLICY_GRACE, grace );
+ rc = ber_flatten2( b2, &bv, 1 );
+ (void)ber_free_buf(b2);
+ if (rc == -1) {
+ cp = NULL;
+ goto fail;
+ }
+ ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
+ ch_free( bv.bv_val );
+ }
+
+ if (err != PP_noError ) {
+ ber_printf( ber, "te", PPOLICY_ERROR, err );
+ }
+ ber_printf( ber, /*{*/ "N}" );
+
+ if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
+ return NULL;
+ }
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = (char *)ppolicy_ctrl_oid;
+ cp->ldctl_iscritical = 0;
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = c.ldctl_value.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len );
+fail:
+ (void)ber_free_buf(ber);
+
+ return cp;
+}
+
+static LDAPControl *
+create_passexpiry( Operation *op, int expired, int warn )
+{
+ LDAPControl *cp;
+ char buf[sizeof("-2147483648")];
+ struct berval bv = { .bv_val = buf, .bv_len = sizeof(buf) };
+
+ bv.bv_len = snprintf( bv.bv_val, bv.bv_len, "%d", warn );
+
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
+ if ( expired ) {
+ cp->ldctl_oid = (char *)ppolicy_pwd_expired_oid;
+ } else {
+ cp->ldctl_oid = (char *)ppolicy_pwd_expiring_oid;
+ }
+ cp->ldctl_iscritical = 0;
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ return cp;
+}
+
+static LDAPControl **
+add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
+{
+ LDAPControl **ctrls, **oldctrls = rs->sr_ctrls;
+ int n;
+
+ n = 0;
+ if ( oldctrls ) {
+ for ( ; oldctrls[n]; n++ )
+ ;
+ }
+ n += 2;
+
+ ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx );
+
+ n = 0;
+ if ( oldctrls ) {
+ for ( ; oldctrls[n]; n++ ) {
+ ctrls[n] = oldctrls[n];
+ }
+ }
+ ctrls[n] = ctrl;
+ ctrls[n+1] = NULL;
+
+ rs->sr_ctrls = ctrls;
+
+ return oldctrls;
+}
+
+static void
+add_account_control(
+ Operation *op,
+ SlapReply *rs,
+ int available,
+ int remaining,
+ LDAPAccountUsabilityMoreInfo *more_info )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ LDAPControl c = { 0 }, *cp = NULL, **ctrls;
+ int i = 0;
+
+ BER_BVZERO( &c.ldctl_value );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( available ) {
+ ber_put_int( ber, remaining, LDAP_TAG_X_ACCOUNT_USABILITY_AVAILABLE );
+ } else {
+ assert( more_info != NULL );
+
+ ber_start_seq( ber, LDAP_TAG_X_ACCOUNT_USABILITY_NOT_AVAILABLE );
+ ber_put_boolean( ber, more_info->inactive, LDAP_TAG_X_ACCOUNT_USABILITY_INACTIVE );
+ ber_put_boolean( ber, more_info->reset, LDAP_TAG_X_ACCOUNT_USABILITY_RESET );
+ ber_put_boolean( ber, more_info->expired, LDAP_TAG_X_ACCOUNT_USABILITY_EXPIRED );
+ ber_put_int( ber, more_info->remaining_grace, LDAP_TAG_X_ACCOUNT_USABILITY_REMAINING_GRACE );
+ ber_put_int( ber, more_info->seconds_before_unlock, LDAP_TAG_X_ACCOUNT_USABILITY_UNTIL_UNLOCK );
+ ber_put_seq( ber );
+ }
+
+ if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
+ goto fail;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) /* Count */;
+ }
+
+ ctrls = op->o_tmprealloc( rs->sr_ctrls, sizeof(LDAPControl *)*( i + 2 ), op->o_tmpmemctx );
+ if ( ctrls == NULL ) {
+ goto fail;
+ }
+
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = (char *)ppolicy_account_ctrl_oid;
+ cp->ldctl_iscritical = 0;
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = c.ldctl_value.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len );
+
+ ctrls[ i ] = cp;
+ ctrls[ i + 1 ] = NULL;
+ rs->sr_ctrls = ctrls;
+
+fail:
+ (void)ber_free_buf(ber);
+}
+
+static void
+ppolicy_get_default( PassPolicy *pp )
+{
+ memset( pp, 0, sizeof(PassPolicy) );
+
+ pp->ad = slap_schema.si_ad_userPassword;
+
+ /* Users can change their own password by default */
+ pp->pwdAllowUserChange = 1;
+}
+
+
+static int
+ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ BackendDB *bd, *bd_orig = op->o_bd;
+ AttributeDescription *ad = NULL;
+ Attribute *a;
+ BerVarray vals;
+ int rc = LDAP_SUCCESS;
+ Entry *pe = NULL;
+#if 0
+ const char *text;
+#endif
+
+ ppolicy_get_default( pp );
+
+ ad = ad_pwdPolicySubentry;
+ if ( (a = attr_find( e->e_attrs, ad )) == NULL ) {
+ /*
+ * entry has no password policy assigned - use default
+ */
+ vals = &pi->def_policy;
+ if ( !vals->bv_val )
+ goto defaultpol;
+ } else {
+ vals = a->a_nvals;
+ if (vals[0].bv_val == NULL) {
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_get: NULL value for policySubEntry\n" );
+ goto defaultpol;
+ }
+ }
+
+ op->o_bd = bd = select_backend( vals, 0 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd_orig;
+ goto defaultpol;
+ }
+
+ rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe );
+ op->o_bd = bd_orig;
+
+ if ( rc ) goto defaultpol;
+
+#if 0 /* Only worry about userPassword for now */
+ if ((a = attr_find( pe->e_attrs, ad_pwdAttribute )))
+ slap_bv2ad( &a->a_vals[0], &pp->ad, &text );
+#endif
+
+ ad = ad_pwdMinAge;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxAge;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxIdle;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxIdle, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdInHistory;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdCheckQuality;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMinLength;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxLength;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxLength, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxFailure;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxRecordedFailure;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdGraceExpiry;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdGraceExpiry, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdGraceAuthNLimit;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdExpireWarning;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdFailureCountInterval;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdLockoutDuration;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMinDelay;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinDelay, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxDelay;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxDelay, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdCheckModule;
+ if ( attr_find( pe->e_attrs, ad )) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "WARNING: Ignoring OBSOLETE attribute %s in policy %s.\n",
+ ad->ad_cname.bv_val, pe->e_name.bv_val );
+ }
+
+ ad = ad_pwdUseCheckModule;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdUseCheckModule = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdCheckModuleArg;
+ if ( (a = attr_find( pe->e_attrs, ad )) ) {
+ ber_dupbv_x( &pp->pwdCheckModuleArg, &a->a_vals[0], op->o_tmpmemctx );
+ }
+
+ ad = ad_pwdLockout;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdMustChange;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdAllowUserChange;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdSafeModify;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
+ pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
+
+ if ( !pp->pwdMaxRecordedFailure && pp->pwdMinDelay )
+ pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
+
+ if ( pp->pwdMinDelay && !pp->pwdMaxDelay ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "pwdMinDelay was set but pwdMaxDelay wasn't, assuming they "
+ "are equal\n" );
+ pp->pwdMaxDelay = pp->pwdMinDelay;
+ }
+
+ op->o_bd = bd;
+ be_entry_release_r( op, pe );
+ op->o_bd = bd_orig;
+
+ return LDAP_SUCCESS;
+
+defaultpol:
+ if ( pe ) {
+ op->o_bd = bd;
+ be_entry_release_r( op, pe );
+ op->o_bd = bd_orig;
+ }
+
+ if ( rc && !BER_BVISNULL( vals ) ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "policy subentry %s missing or invalid at '%s', "
+ "no policy will be applied!\n",
+ vals->bv_val, ad ? ad->ad_cname.bv_val : "" );
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_get: using default policy\n" );
+ }
+
+ ppolicy_get_default( pp );
+
+ return -1;
+}
+
+static int
+password_scheme( struct berval *cred, struct berval *sch )
+{
+ int e;
+
+ assert( cred != NULL );
+
+ if (sch) {
+ sch->bv_val = NULL;
+ sch->bv_len = 0;
+ }
+
+ if ((cred->bv_len == 0) || (cred->bv_val == NULL) ||
+ (cred->bv_val[0] != '{')) return LDAP_OTHER;
+
+ for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++);
+ if (cred->bv_val[e]) {
+ int rc;
+ rc = lutil_passwd_scheme( cred->bv_val );
+ if (rc) {
+ if (sch) {
+ sch->bv_val = cred->bv_val;
+ sch->bv_len = e;
+ }
+ return LDAP_SUCCESS;
+ }
+ }
+ return LDAP_OTHER;
+}
+
+static int
+check_password_quality( struct berval *cred, pp_info *pi, PassPolicy *pp, LDAPPasswordPolicyError *err,
+ Entry *e, struct berval *errmsg )
+{
+ int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
+ char *ptr;
+ struct berval sch;
+
+ assert( cred != NULL );
+ assert( pp != NULL );
+ assert( errmsg != NULL );
+
+ ptr = errmsg->bv_val;
+ *ptr = '\0';
+
+ ptr = cred->bv_val;
+
+ if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( err ) *err = PP_passwordTooShort;
+ return rc;
+ }
+
+ if ( pp->pwdMaxLength && cred->bv_len > pp->pwdMaxLength ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( err ) *err = PP_passwordTooLong;
+ return rc;
+ }
+
+ /*
+ * We need to know if the password is already hashed - if so
+ * what scheme is it. The reason being that the "hash" of
+ * {cleartext} still allows us to check the password.
+ */
+ rc = password_scheme( cred, &sch );
+ if (rc == LDAP_SUCCESS) {
+ if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}",
+ sch.bv_len ) == 0)) {
+ /*
+ * We can check the cleartext "hash"
+ */
+ ptr = cred->bv_val + sch.bv_len;
+ } else {
+ /* everything else, we can't check */
+ if (pp->pwdCheckQuality == 2) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if (err) *err = PP_insufficientPasswordQuality;
+ return rc;
+ }
+ /*
+ * We can't check the syntax of the password, but it's not
+ * mandatory (according to the policy), so we return success.
+ */
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+
+ if (pp->pwdUseCheckModule) {
+#ifdef SLAPD_MODULES
+ check_func *prog;
+
+ if ( !pi->pwdCheckFunc ) {
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: no CheckModule loaded\n" );
+ ok = LDAP_OTHER;
+ } else {
+ struct berval *arg = NULL;
+ if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) {
+ arg = &pp->pwdCheckModuleArg;
+ }
+
+ ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
+ ok = pi->pwdCheckFunc( ptr, errmsg, e, arg );
+ ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
+ if (ok != LDAP_SUCCESS) {
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: module error: (%s) %s.[%d]\n",
+ pi->pwdCheckModule, errmsg->bv_val ? errmsg->bv_val : "", ok );
+ }
+ }
+#else
+ Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
+ "supported. pwdCheckModule ignored.\n" );
+#endif /* SLAPD_MODULES */
+ }
+
+ if (ok != LDAP_SUCCESS) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if (err) *err = PP_insufficientPasswordQuality;
+ }
+
+ return rc;
+}
+
+static int
+parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw )
+{
+ char *ptr;
+ struct berval nv, npw;
+ ber_len_t i, j;
+
+ assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );
+
+ if ( oid ) {
+ *oid = 0;
+ }
+ *oldtime = (time_t)-1;
+ BER_BVZERO( oldpw );
+
+ ber_dupbv( &nv, bv );
+
+ /* first get the time field */
+ for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ ptr = nv.bv_val;
+ *oldtime = parse_time( ptr );
+ if (*oldtime == (time_t)-1) {
+ goto exit_failure;
+ }
+
+ /* get the OID field */
+ for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ if ( oid ) {
+ *oid = ber_strdup( ptr );
+ }
+
+ /* get the length field */
+ for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ oldpw->bv_len = strtol( ptr, NULL, 10 );
+ if (errno == ERANGE) {
+ goto exit_failure;
+ }
+
+ /* lastly, get the octets of the string */
+ for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ )
+ ;
+ if ( i - j != oldpw->bv_len) {
+ goto exit_failure; /* length is wrong */
+ }
+
+ npw.bv_val = ptr;
+ npw.bv_len = oldpw->bv_len;
+ ber_dupbv( oldpw, &npw );
+ ber_memfree( nv.bv_val );
+
+ return LDAP_SUCCESS;
+
+exit_failure:;
+ if ( oid && *oid ) {
+ ber_memfree(*oid);
+ *oid = NULL;
+ }
+ if ( oldpw->bv_val ) {
+ ber_memfree( oldpw->bv_val);
+ BER_BVZERO( oldpw );
+ }
+ ber_memfree( nv.bv_val );
+
+ return LDAP_OTHER;
+}
+
+static void
+add_to_pwd_history( pw_hist **l, time_t t,
+ struct berval *oldpw, struct berval *bv )
+{
+ pw_hist *p, *p1, *p2;
+
+ if (!l) return;
+
+ p = ch_malloc( sizeof( pw_hist ));
+ p->pw = *oldpw;
+ ber_dupbv( &p->bv, bv );
+ p->t = t;
+ p->next = NULL;
+
+ if (*l == NULL) {
+ /* degenerate case */
+ *l = p;
+ return;
+ }
+ /*
+ * advance p1 and p2 such that p1 is the node before the
+ * new one, and p2 is the node after it
+ */
+ for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next );
+ p->next = p2;
+ if (p1 == NULL) { *l = p; return; }
+ p1->next = p;
+}
+
+#ifndef MAX_PWD_HISTORY_SZ
+#define MAX_PWD_HISTORY_SZ 1024
+#endif /* MAX_PWD_HISTORY_SZ */
+
+static void
+make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa )
+{
+ char str[ MAX_PWD_HISTORY_SZ ];
+ int nlen;
+
+ snprintf( str, MAX_PWD_HISTORY_SZ,
+ "%s#%s#%lu#", timebuf,
+ pa->a_desc->ad_type->sat_syntax->ssyn_oid,
+ (unsigned long) pa->a_nvals[0].bv_len );
+ str[MAX_PWD_HISTORY_SZ-1] = 0;
+ nlen = strlen(str);
+
+ /*
+ * We have to assume that the string is a string of octets,
+ * not readable characters. In reality, yes, it probably is
+ * a readable (ie, base64) string, but we can't count on that
+ * Hence, while the first 3 fields of the password history
+ * are definitely readable (a timestamp, an OID and an integer
+ * length), the remaining octets of the actual password
+ * are deemed to be binary data.
+ */
+ AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len );
+ nlen += pa->a_nvals[0].bv_len;
+ bv->bv_val = ch_malloc( nlen + 1 );
+ AC_MEMCPY( bv->bv_val, str, nlen );
+ bv->bv_val[nlen] = '\0';
+ bv->bv_len = nlen;
+}
+
+static void
+free_pwd_history_list( pw_hist **l )
+{
+ pw_hist *p;
+
+ if (!l) return;
+ p = *l;
+ while (p) {
+ pw_hist *pp = p->next;
+
+ free(p->pw.bv_val);
+ free(p->bv.bv_val);
+ free(p);
+ p = pp;
+ }
+ *l = NULL;
+}
+
+typedef struct ppbind {
+ pp_info *pi;
+ BackendDB *be;
+ int send_ctrl;
+ int set_restrict;
+ LDAPControl **oldctrls;
+ Modifications *mod;
+ LDAPPasswordPolicyError pErr;
+ PassPolicy pp;
+} ppbind;
+
+static void
+ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls )
+{
+ int n;
+
+ assert( rs->sr_ctrls != NULL );
+ assert( rs->sr_ctrls[0] != NULL );
+
+ for ( n = 0; rs->sr_ctrls[n]; n++ ) {
+ if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ||
+ rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expired_oid ||
+ rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expiring_oid ) {
+ op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx );
+ rs->sr_ctrls[n] = (LDAPControl *)(-1);
+ break;
+ }
+ }
+
+ if ( rs->sr_ctrls[n] == NULL ) {
+ /* missed? */
+ }
+
+ op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );
+
+ rs->sr_ctrls = oldctrls;
+}
+
+static int
+ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs )
+{
+ ppbind *ppb = op->o_callback->sc_private;
+ if ( ppb->send_ctrl ) {
+ ctrls_cleanup( op, rs, ppb->oldctrls );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind_response( Operation *op, SlapReply *rs )
+{
+ ppbind *ppb = op->o_callback->sc_private;
+ pp_info *pi = ppb->pi;
+ Modifications *mod = ppb->mod, *m;
+ int pwExpired = 0;
+ int ngut = -1, warn = -1, fc = 0, age, rc;
+ Attribute *a;
+ time_t now, pwtime = (time_t)-1;
+ struct lutil_tm now_tm;
+ struct lutil_timet now_usec;
+ char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ];
+ struct berval timestamp, timestamp_usec;
+ BackendDB *be = op->o_bd;
+ LDAPControl *ctrl = NULL;
+ Entry *e;
+
+ ldap_pvt_thread_mutex_lock( &pi->pwdFailureTime_mutex );
+ /* If we already know it's locked, just get on with it */
+ if ( ppb->pErr != PP_noError ) {
+ goto locked;
+ }
+
+ op->o_bd = ppb->be;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ op->o_bd = be;
+
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex );
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* ITS#7089 Skip lockout checks/modifications if password attribute missing */
+ if ( attr_find( e->e_attrs, ppb->pp.ad ) == NULL ) {
+ goto done;
+ }
+
+ ldap_pvt_gettime(&now_tm); /* stored for later consideration */
+ lutil_tm2time(&now_tm, &now_usec);
+ now = now_usec.tt_sec;
+ timestamp.bv_val = nowstr;
+ timestamp.bv_len = sizeof(nowstr);
+ slap_timestamp( &now, &timestamp );
+
+ /* Separate timestamp for pwdFailureTime with microsecond granularity */
+ strcpy(nowstr_usec, nowstr);
+ timestamp_usec.bv_val = nowstr_usec;
+ timestamp_usec.bv_len = timestamp.bv_len;
+ snprintf( timestamp_usec.bv_val + timestamp_usec.bv_len-1, sizeof(".123456Z"), ".%06dZ", now_usec.tt_nsec / 1000 );
+ timestamp_usec.bv_len += STRLENOF(".123456");
+
+ if ( rs->sr_err == LDAP_INVALID_CREDENTIALS && ppb->pp.pwdMaxRecordedFailure ) {
+ int i = 0;
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdFailureTime->ad_cname;
+ m->sml_desc = ad_pwdFailureTime;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+
+ ber_dupbv( &m->sml_values[0], &timestamp_usec );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp_usec );
+ m->sml_next = mod;
+ mod = m;
+
+ /*
+ * Count the pwdFailureTimes - if it's
+ * greater than the policy pwdMaxFailure,
+ * then lock the account.
+ */
+ if ((a = attr_find( e->e_attrs, ad_pwdFailureTime )) != NULL) {
+ for(i=0; a->a_nvals[i].bv_val; i++) {
+
+ /*
+ * If the interval is 0, then failures
+ * stay on the record until explicitly
+ * reset by successful authentication.
+ */
+ if (ppb->pp.pwdFailureCountInterval == 0) {
+ fc++;
+ } else if (now <=
+ parse_time(a->a_nvals[i].bv_val) +
+ ppb->pp.pwdFailureCountInterval) {
+
+ fc++;
+ }
+ /*
+ * We only count those failures
+ * which are not due to expire.
+ */
+ }
+ /* Do we have too many timestamps? If so, delete some values.
+ * We don't bother to sort the values here. OpenLDAP keeps the
+ * values in order by default. Fundamentally, relying on the
+ * information here is wrong anyway; monitoring systems should
+ * be tracking Bind failures in syslog, not here.
+ */
+ if (a->a_numvals >= ppb->pp.pwdMaxRecordedFailure) {
+ int j = ppb->pp.pwdMaxRecordedFailure-1;
+ /* If more than 2x, cheaper to perform a Replace */
+ if (a->a_numvals >= 2 * ppb->pp.pwdMaxRecordedFailure) {
+ struct berval v, nv;
+
+ /* Change the mod we constructed above */
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_numvals = ppb->pp.pwdMaxRecordedFailure;
+ v = m->sml_values[0];
+ nv = m->sml_nvalues[0];
+ ch_free(m->sml_values);
+ ch_free(m->sml_nvalues);
+ m->sml_values = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 );
+ for (i=0; i<j; i++) {
+ ber_dupbv(&m->sml_values[i], &a->a_vals[a->a_numvals-j+i]);
+ ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[a->a_numvals-j+i]);
+ }
+ m->sml_values[i] = v;
+ m->sml_nvalues[i] = nv;
+ } else {
+ /* else just delete some */
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_type = ad_pwdFailureTime->ad_cname;
+ m->sml_desc = ad_pwdFailureTime;
+ m->sml_numvals = a->a_numvals - j;
+ m->sml_values = ch_calloc( sizeof(struct berval), m->sml_numvals+1 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), m->sml_numvals+1 );
+ for (i=0; i<m->sml_numvals; i++) {
+ ber_dupbv(&m->sml_values[i], &a->a_vals[i]);
+ ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[i]);
+ }
+ m->sml_next = mod;
+ mod = m;
+ }
+ }
+ }
+
+ if ((ppb->pp.pwdMaxFailure > 0) &&
+ (fc >= ppb->pp.pwdMaxFailure - 1)) {
+
+ /*
+ * We subtract 1 from the failure max
+ * because the new failure entry hasn't
+ * made it to the entry yet.
+ */
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+ m->sml_desc = ad_pwdAccountLockedTime;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+ ber_dupbv( &m->sml_values[0], &timestamp );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp );
+ m->sml_next = mod;
+ mod = m;
+ } else if ( ppb->pp.pwdMinDelay ) {
+ int waittime = ppb->pp.pwdMinDelay << fc;
+ time_t wait_end;
+ char lockout_stamp_buf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval lockout_stamp = BER_BVC(lockout_stamp_buf);
+
+ if ( waittime > ppb->pp.pwdMaxDelay ) {
+ waittime = ppb->pp.pwdMaxDelay;
+ }
+ wait_end = now + waittime;
+
+ slap_timestamp( &wait_end, &lockout_stamp );
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdAccountTmpLockoutEnd->ad_cname;
+ m->sml_desc = ad_pwdAccountTmpLockoutEnd;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+ ber_dupbv( &m->sml_values[0], &lockout_stamp );
+ ber_dupbv( &m->sml_nvalues[0], &lockout_stamp );
+ m->sml_next = mod;
+ mod = m;
+ }
+ } else if ( rs->sr_err == LDAP_SUCCESS ) {
+ if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+ pwtime = parse_time( a->a_nvals[0].bv_val );
+
+ /* delete all pwdFailureTimes */
+ if ( attr_find( e->e_attrs, ad_pwdFailureTime )) {
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdFailureTime->ad_cname;
+ m->sml_desc = ad_pwdFailureTime;
+ m->sml_next = mod;
+ mod = m;
+ }
+
+ /*
+ * check to see if the password must be changed
+ */
+ if ( ppb->pp.pwdMustChange &&
+ (a = attr_find( e->e_attrs, ad_pwdReset )) &&
+ bvmatch( &a->a_nvals[0], &slap_true_bv ) )
+ {
+ /*
+ * need to inject client controls here to give
+ * more information. For the moment, we ensure
+ * that we are disallowed from doing anything
+ * other than change password.
+ */
+ if ( ppb->set_restrict ) {
+ ber_dupbv( &pwcons[op->o_conn->c_conn_idx].dn,
+ &op->o_conn->c_ndn );
+ }
+
+ ppb->pErr = PP_changeAfterReset;
+
+ } else {
+ /*
+ * the password does not need to be changed, so
+ * we now check whether the password has expired.
+ *
+ * We can skip this bit if passwords don't age in
+ * the policy. Also, if there was no pwdChangedTime
+ * attribute in the entry, the password never expires.
+ */
+ if (ppb->pp.pwdMaxAge == 0) goto grace;
+
+ if (pwtime != (time_t)-1) {
+ /*
+ * Check: was the last change time of
+ * the password older than the maximum age
+ * allowed. (Ignore case 2 from I-D, it's just silly.)
+ */
+ if (now - pwtime > ppb->pp.pwdMaxAge ) pwExpired = 1;
+ }
+ }
+
+grace:
+ if (!pwExpired) goto check_expiring_password;
+
+ if ( ppb->pp.pwdGraceExpiry &&
+ now - pwtime > ppb->pp.pwdMaxAge + ppb->pp.pwdGraceExpiry ) {
+ /* Grace logins have expired now */
+ ngut = 0;
+ } else if ((a = attr_find( e->e_attrs, ad_pwdGraceUseTime )) == NULL) {
+ ngut = ppb->pp.pwdGraceAuthNLimit;
+ } else {
+ for(ngut=0; a->a_nvals[ngut].bv_val; ngut++);
+ ngut = ppb->pp.pwdGraceAuthNLimit - ngut;
+ }
+
+ /*
+ * ngut is the number of remaining grace logins
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_bind: Entry %s has an expired password: %d grace logins\n",
+ e->e_name.bv_val, ngut );
+
+ ngut--;
+
+ if (ngut < 0) {
+ ppb->pErr = PP_passwordExpired;
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ /*
+ * Add a grace user time to the entry
+ */
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdGraceUseTime->ad_cname;
+ m->sml_desc = ad_pwdGraceUseTime;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+ ber_dupbv( &m->sml_values[0], &timestamp_usec );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp_usec );
+ m->sml_next = mod;
+ mod = m;
+
+check_expiring_password:
+ /*
+ * Now we need to check to see
+ * if it is about to expire, and if so, should the user
+ * be warned about it in the password policy control.
+ *
+ * If the password has expired, and we're in the grace period, then
+ * we don't need to do this bit. Similarly, if we don't have password
+ * aging, then there's no need to do this bit either.
+ *
+ * If pwdtime is -1 there is no password Change Time attribute on the
+ * entry so we skip the expiry check.
+ *
+ */
+ if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1) ||
+ (pwtime == -1))
+ goto done;
+
+ age = (int)(now - pwtime);
+
+ /*
+ * We know that there is a password Change Time attribute - if
+ * there wasn't, then the pwdExpired value would be true, unless
+ * there is no password aging - and if there is no password aging,
+ * then this section isn't called anyway - you can't have an
+ * expiring password if there's no limit to expire.
+ */
+ if (ppb->pp.pwdMaxAge - age < ppb->pp.pwdExpireWarning ) {
+ /*
+ * Set the warning value.
+ */
+ warn = ppb->pp.pwdMaxAge - age; /* seconds left until expiry */
+ if (warn < 0) warn = 0; /* something weird here - why is pwExpired not set? */
+
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n",
+ op->o_req_dn.bv_val, warn );
+ }
+ }
+
+done:
+ op->o_bd = ppb->be;
+ be_entry_release_r( op, e );
+ op->o_bd = be;
+
+locked:
+ if ( mod && !pi->disable_write ) {
+ Operation op2 = *op;
+ SlapReply r2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ LDAPControl c, *ca[2];
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_callback = &cb;
+ op2.orm_modlist = mod;
+ op2.orm_no_opattrs = 0;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ /* If this server is a shadow and forward_updates is true,
+ * use the frontend to perform this modify. That will trigger
+ * the update referral, which can then be forwarded by the
+ * chain overlay. Obviously the updateref and chain overlay
+ * must be configured appropriately for this to be useful.
+ */
+ if ( SLAP_SHADOW( op->o_bd ) && pi->forward_updates ) {
+ op2.o_bd = frontendDB;
+
+ /* Must use Relax control since these are no-user-mod */
+ op2.o_relax = SLAP_CONTROL_CRITICAL;
+ op2.o_ctrls = ca;
+ ca[0] = &c;
+ ca[1] = NULL;
+ BER_BVZERO( &c.ldctl_value );
+ c.ldctl_iscritical = 1;
+ c.ldctl_oid = LDAP_CONTROL_RELAX;
+ } else {
+ /* If not forwarding, don't update opattrs and don't replicate */
+ if ( SLAP_SINGLE_SHADOW( op->o_bd )) {
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+ }
+ op2.o_bd = ppb->be;
+ }
+ rc = op2.o_bd->be_modify( &op2, &r2 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s ppolicy_bind_response: "
+ "ppolicy state change failed with rc=%d text=%s\n",
+ op->o_log_prefix, rc, r2.sr_text );
+ }
+ }
+ if ( mod ) {
+ slap_mods_free( mod, 1 );
+ }
+
+ if ( ppb->send_ctrl ) {
+
+ /* Do we really want to tell that the account is locked? */
+ if ( ppb->pErr == PP_accountLocked && !pi->use_lockout ) {
+ ppb->pErr = PP_noError;
+ }
+ ctrl = create_passcontrol( op, warn, ngut, ppb->pErr );
+ } else if ( pi->send_netscape_controls ) {
+ if ( ppb->pErr != PP_noError || pwExpired ) {
+ ctrl = create_passexpiry( op, 1, 0 );
+ } else if ( warn > 0 ) {
+ ctrl = create_passexpiry( op, 0, warn );
+ }
+ }
+ if ( ctrl ) {
+ ppb->oldctrls = add_passcontrol( op, rs, ctrl );
+ op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup;
+ }
+ ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ /* Reset lockout status on all Bind requests */
+ if ( !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ }
+
+ /* Root bypasses policy */
+ if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn )) {
+ Entry *e;
+ int rc;
+ ppbind *ppb;
+ slap_callback *cb;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+ ppb = (ppbind *)(cb+1);
+ ppb->pi = on->on_bi.bi_private;
+ ppb->be = op->o_bd->bd_self;
+ ppb->pErr = PP_noError;
+ ppb->set_restrict = 1;
+
+ /* Setup a callback so we can munge the result */
+
+ cb->sc_response = ppolicy_bind_response;
+ cb->sc_private = ppb;
+ overlay_callback_after_backover( op, cb, 1 );
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ ppb->send_ctrl = 1;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) {
+ rc = account_locked( op, e, &ppb->pp, &ppb->mod );
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+
+ if ( rc ) {
+ ppb->pErr = PP_accountLocked;
+ send_ldap_error( op, rs, LDAP_INVALID_CREDENTIALS, NULL );
+ return rs->sr_err;
+ }
+
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* Reset the restricted info for the next session on this connection */
+static int
+ppolicy_connection_destroy( BackendDB *bd, Connection *conn )
+{
+ if ( pwcons && !BER_BVISEMPTY( &pwcons[conn->c_conn_idx].dn )) {
+ ch_free( pwcons[conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[conn->c_conn_idx].dn );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* Check if this connection is restricted */
+static int
+ppolicy_restrict(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ int send_ctrl = 0;
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+
+ if ( op->o_conn && !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+ LDAPControl **oldctrls;
+ /* if the current authcDN doesn't match the one we recorded,
+ * then an intervening Bind has succeeded and the restriction
+ * no longer applies. (ITS#4516)
+ */
+ if ( !dn_match( &op->o_conn->c_ndn,
+ &pwcons[op->o_conn->c_conn_idx].dn )) {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "connection restricted to password changing only\n" );
+ if ( send_ctrl ) {
+ LDAPControl *ctrl = NULL;
+ ctrl = create_passcontrol( op, -1, -1, PP_changeAfterReset );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "Operations are restricted to bind/unbind/abandon/StartTLS/modify password" );
+ if ( send_ctrl ) {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_account_usability_entry_cb( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = op->o_callback->sc_private;
+ BackendInfo *bi = op->o_bd->bd_info;
+ LDAPControl *ctrl = NULL;
+ PassPolicy pp;
+ Attribute *a;
+ Entry *e = NULL;
+ time_t pwtime = 0, seconds_until_expiry = -1, now = op->o_time;
+ int isExpired = 0, grace = -1;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ) != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( ppolicy_get( op, e, &pp ) != LDAP_SUCCESS ) {
+ /* TODO: If there is no policy, should we check if */
+ goto done;
+ }
+
+ if ( !access_allowed( op, e, pp.ad, NULL, ACL_COMPARE, NULL ) ) {
+ goto done;
+ }
+
+ if ( attr_find( e->e_attrs, pp.ad ) == NULL ) {
+ goto done;
+ }
+
+ if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) {
+ pwtime = parse_time( a->a_nvals[0].bv_val );
+ }
+
+ if ( pp.pwdMaxAge && pwtime ) {
+ seconds_until_expiry = pwtime + pp.pwdMaxAge - now;
+ if ( seconds_until_expiry <= 0 ) isExpired = 1;
+ if ( pp.pwdGraceAuthNLimit ) {
+ if ( !pp.pwdGraceExpiry || seconds_until_expiry + pp.pwdGraceExpiry > 0 ) {
+ grace = pp.pwdGraceAuthNLimit;
+ if ( attr_find( e->e_attrs, ad_pwdGraceUseTime ) ) {
+ grace -= a->a_numvals;
+ }
+ }
+ }
+ }
+ if ( !isExpired && pp.pwdMaxIdle && (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) ) {
+ time_t lastbindtime = pwtime;
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) != NULL ) {
+ lastbindtime = parse_time( a->a_nvals[0].bv_val );
+ }
+
+ if ( lastbindtime ) {
+ int remaining_idle = lastbindtime + pp.pwdMaxIdle - now;
+ if ( remaining_idle <= 0 ) {
+ isExpired = 1;
+ } else if ( seconds_until_expiry == -1 || remaining_idle < seconds_until_expiry ) {
+ seconds_until_expiry = remaining_idle;
+ }
+ }
+ }
+
+ if ( isExpired || account_locked( op, e, &pp, NULL ) ) {
+ LDAPAccountUsabilityMoreInfo more_info = { 0, 0, 0, -1, -1 };
+ time_t then, lockoutEnd = 0;
+
+ if ( isExpired ) more_info.remaining_grace = grace;
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
+ then = parse_time( a->a_vals[0].bv_val );
+ if ( then == 0 )
+ lockoutEnd = -1;
+
+ /* Still in the future? not yet in effect */
+ if ( now < then )
+ then = 0;
+
+ if ( !pp.pwdLockoutDuration )
+ lockoutEnd = -1;
+
+ if ( now < then + pp.pwdLockoutDuration )
+ lockoutEnd = then + pp.pwdLockoutDuration;
+ }
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
+ then = parse_time( a->a_vals[0].bv_val );
+ if ( lockoutEnd != -1 && then > lockoutEnd )
+ lockoutEnd = then;
+ }
+
+ if ( lockoutEnd > now ) {
+ more_info.inactive = 1;
+ more_info.seconds_before_unlock = lockoutEnd - now;
+ }
+
+ if ( pp.pwdMustChange &&
+ (a = attr_find( e->e_attrs, ad_pwdReset )) &&
+ bvmatch( &a->a_nvals[0], &slap_true_bv ) )
+ {
+ more_info.reset = 1;
+ }
+
+ add_account_control( op, rs, 0, -1, &more_info );
+ } else {
+ add_account_control( op, rs, 1, seconds_until_expiry, NULL );
+ }
+
+done:
+ op->o_bd->bd_info = bi;
+ if ( e ) {
+ be_entry_release_r( op, e );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ int rc = ppolicy_restrict( op, rs );
+
+ if ( rc != SLAP_CB_CONTINUE ) {
+ return rc;
+ }
+
+ if ( op->o_ctrlflag[account_usability_cid] ) {
+ slap_callback *cb;
+
+ cb = op->o_tmpcalloc( sizeof(slap_callback), 1, op->o_tmpmemctx );
+
+ cb->sc_response = ppolicy_account_usability_entry_cb;
+ cb->sc_private = on;
+ overlay_callback_after_backover( op, cb, 1 );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_compare_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ /* map compare responses to bind responses */
+ if ( rs->sr_err == LDAP_COMPARE_TRUE )
+ rs->sr_err = LDAP_SUCCESS;
+ else if ( rs->sr_err == LDAP_COMPARE_FALSE )
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+
+ ppolicy_bind_response( op, rs );
+
+ /* map back to compare */
+ if ( rs->sr_err == LDAP_SUCCESS )
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ else if ( rs->sr_err == LDAP_INVALID_CREDENTIALS )
+ rs->sr_err = LDAP_COMPARE_FALSE;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+ return rs->sr_err;
+
+ /* Did we receive a password policy request control?
+ * Are we testing the userPassword?
+ */
+ if ( op->o_ctrlflag[ppolicy_cid] &&
+ op->orc_ava->aa_desc == slap_schema.si_ad_userPassword ) {
+ Entry *e;
+ int rc;
+ ppbind *ppb;
+ slap_callback *cb;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+ ppb = (ppbind *)(cb+1);
+ ppb->pi = on->on_bi.bi_private;
+ ppb->be = op->o_bd->bd_self;
+ ppb->pErr = PP_noError;
+ ppb->send_ctrl = 1;
+ /* failures here don't lockout the connection */
+ ppb->set_restrict = 0;
+
+ /* Setup a callback so we can munge the result */
+
+ cb->sc_response = ppolicy_compare_response;
+ cb->sc_private = ppb;
+ overlay_callback_after_backover( op, cb, 1 );
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) {
+ rc = account_locked( op, e, &ppb->pp, &ppb->mod );
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+
+ if ( rc ) {
+ ppb->pErr = PP_accountLocked;
+ send_ldap_error( op, rs, LDAP_COMPARE_FALSE, NULL );
+ return rs->sr_err;
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_add(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ PassPolicy pp;
+ Attribute *pa;
+ const char *txt;
+ int is_pwdadmin = 0;
+
+ if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+ return rs->sr_err;
+
+ /* If this is a replica, assume the provider checked everything */
+ if ( be_shadow_update( op ) )
+ return SLAP_CB_CONTINUE;
+
+ ppolicy_get( op, op->ora_e, &pp );
+
+ if ( access_allowed( op, op->ora_e, pp.ad, NULL, ACL_MANAGE, NULL ) ) {
+ is_pwdadmin = 1;
+ }
+
+ /* Check for password in entry */
+ if ( (pa = attr_find( op->oq_add.rs_e->e_attrs, pp.ad )) ) {
+ assert( pa->a_vals != NULL );
+ assert( !BER_BVISNULL( &pa->a_vals[ 0 ] ) );
+
+ if ( !BER_BVISNULL( &pa->a_vals[ 1 ] ) ) {
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "Password policy only allows one password value" );
+ return rs->sr_err;
+ }
+
+ /*
+ * new entry contains a password - if we're not the password admin
+ * then we need to check that the password fits in with the
+ * security policy for the new entry.
+ */
+
+ if ( pp.pwdCheckQuality > 0 && !is_pwdadmin ) {
+ struct berval *bv = &(pa->a_vals[0]);
+ int rc, send_ctrl = 0;
+ LDAPPasswordPolicyError pErr = PP_noError;
+ char errbuf[ERRBUFSIZ];
+ struct berval errmsg = BER_BVC( errbuf );
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+ rc = check_password_quality( bv, pi, &pp, &pErr, op->ora_e, &errmsg );
+ if (rc != LDAP_SUCCESS) {
+ char *txt = errmsg.bv_val;
+ LDAPControl **oldctrls = NULL;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ if ( send_ctrl ) {
+ LDAPControl *ctrl = NULL;
+ ctrl = create_passcontrol( op, -1, -1, pErr );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ send_ldap_error( op, rs, rc, txt && txt[0] ? txt : "Password fails quality checking policy" );
+ if ( txt != errbuf ) {
+ free( txt );
+ }
+ if ( send_ctrl ) {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ return rs->sr_err;
+ }
+ }
+ /*
+ * A controversial bit. We hash cleartext
+ * passwords provided via add and modify operations
+ * You're not really supposed to do this, since
+ * the X.500 model says "store attributes" as they
+ * get provided. By default, this is what we do
+ *
+ * But if the hash_passwords flag is set, we hash
+ * any cleartext password attribute values via the
+ * default password hashing scheme.
+ */
+ if ((pi->hash_passwords) &&
+ (password_scheme( &(pa->a_vals[0]), NULL ) != LDAP_SUCCESS)) {
+ struct berval hpw;
+
+ slap_passwd_hash( &(pa->a_vals[0]), &hpw, &txt );
+ if (hpw.bv_val == NULL) {
+ /*
+ * hashing didn't work. Emit an error.
+ */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = txt;
+ send_ldap_error( op, rs, LDAP_OTHER, "Password hashing failed" );
+ return rs->sr_err;
+ }
+
+ memset( pa->a_vals[0].bv_val, 0, pa->a_vals[0].bv_len);
+ ber_memfree( pa->a_vals[0].bv_val );
+ pa->a_vals[0].bv_val = hpw.bv_val;
+ pa->a_vals[0].bv_len = hpw.bv_len;
+ }
+
+ /* If password aging is in effect, set the pwdChangedTime */
+ if ( ( pp.pwdMaxAge || pp.pwdMinAge ) &&
+ !attr_find( op->ora_e->e_attrs, ad_pwdChangedTime ) ) {
+ struct berval timestamp;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ time_t now = slap_get_time();
+
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = sizeof(timebuf);
+ slap_timestamp( &now, &timestamp );
+
+ attr_merge_one( op->ora_e, ad_pwdChangedTime, &timestamp, &timestamp );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_mod_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ op->o_callback = sc->sc_next;
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ }
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_text_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+
+ if ( rs->sr_text == sc->sc_private ) {
+ rs->sr_text = NULL;
+ }
+ free( sc->sc_private );
+
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ int i, rc, mod_pw_only, pwmod = 0, pwmop = -1, deladd,
+ hsize = 0, hskip;
+ PassPolicy pp;
+ Modifications *mods = NULL, *modtail = NULL,
+ *ml, *delmod, *addmod;
+ Attribute *pa, *ha, at;
+ const char *txt;
+ char errbuf[ERRBUFSIZ];
+ pw_hist *tl = NULL, *p;
+ int zapReset, send_ctrl = 0, free_txt = 0;
+ Entry *e;
+ struct berval newpw = BER_BVNULL, oldpw = BER_BVNULL,
+ *bv, cr[2];
+ LDAPPasswordPolicyError pErr = PP_noError;
+ LDAPControl *ctrl = NULL;
+ LDAPControl **oldctrls = NULL;
+ int is_pwdexop = 0, is_pwdadmin = 0;
+ int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0,
+ got_del_success = 0;
+ int got_changed = 0, got_history = 0;
+ int have_policy = 0;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+ if ( pi->disable_write ) return SLAP_CB_CONTINUE;
+
+ /* If this is a replica, we may need to tweak some of the
+ * provider's modifications. Otherwise, just pass it through.
+ */
+ if ( be_shadow_update( op ) ) {
+ Modifications **prev;
+ Attribute *a_grace, *a_lock, *a_fail, *a_success;
+
+ a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime );
+ a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime );
+ a_fail = attr_find( e->e_attrs, ad_pwdFailureTime );
+ a_success = attr_find( e->e_attrs, ad_pwdLastSuccess );
+
+ for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) {
+
+ if ( ml->sml_desc == slap_schema.si_ad_userPassword )
+ got_pw = 1;
+
+ /* If we're deleting an attr that didn't exist,
+ * drop this delete op
+ */
+ if ( ml->sml_op == LDAP_MOD_DELETE ||
+ ml->sml_op == SLAP_MOD_SOFTDEL ) {
+ int drop = 0;
+
+ if ( ml->sml_desc == ad_pwdGraceUseTime ) {
+ if ( !a_grace || got_del_grace ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_grace = 1;
+ }
+ } else
+ if ( ml->sml_desc == ad_pwdAccountLockedTime ) {
+ if ( !a_lock || got_del_lock ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_lock = 1;
+ }
+ } else
+ if ( ml->sml_desc == ad_pwdFailureTime ) {
+ if ( !a_fail || got_del_fail ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_fail = 1;
+ }
+ }
+ if ( ml->sml_desc == ad_pwdLastSuccess ) {
+ if ( !a_success || got_del_success ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_success = 1;
+ }
+ }
+ if ( drop ) {
+ *prev = ml->sml_next;
+ ml->sml_next = NULL;
+ slap_mods_free( ml, 1 );
+ continue;
+ }
+ }
+ prev = &ml->sml_next;
+ }
+
+ /* If we're resetting the password, make sure grace, accountlock,
+ * success, and failure also get removed.
+ */
+ if ( got_pw ) {
+ if ( a_grace && !got_del_grace ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdGraceUseTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ prev = &ml->sml_next;
+ }
+ if ( a_lock && !got_del_lock ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdAccountLockedTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ }
+ if ( a_fail && !got_del_fail ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdFailureTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ }
+ if ( a_success && !got_del_success ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdLastSuccess;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+
+ /* See if this is a pwdModify exop. If so, we can
+ * access the plaintext passwords from that request.
+ */
+ {
+ slap_callback *sc;
+
+ for ( sc = op->o_callback; sc; sc=sc->sc_next ) {
+ if ( sc->sc_response == slap_null_cb &&
+ sc->sc_private ) {
+ req_pwdexop_s *qpw = sc->sc_private;
+ newpw = qpw->rs_new;
+ oldpw = qpw->rs_old;
+ is_pwdexop = 1;
+ break;
+ }
+ }
+ }
+
+ /* ppolicy_hash_cleartext depends on pwmod being determined first */
+ if ( ppolicy_get( op, e, &pp ) == LDAP_SUCCESS ) {
+ have_policy = 1;
+ }
+
+ if ( access_allowed( op, e, pp.ad, NULL, ACL_MANAGE, NULL ) ) {
+ is_pwdadmin = 1;
+ }
+
+ for ( ml = op->orm_modlist,
+ pwmod = 0, mod_pw_only = 1,
+ deladd = 0, delmod = NULL,
+ addmod = NULL,
+ zapReset = 1;
+ ml != NULL; modtail = ml, ml = ml->sml_next )
+ {
+ if ( ml->sml_desc == pp.ad ) {
+ pwmod = 1;
+ pwmop = ml->sml_op;
+ if ((deladd == 0) && (ml->sml_op == LDAP_MOD_DELETE) &&
+ (ml->sml_values) && !BER_BVISNULL( &ml->sml_values[0] ))
+ {
+ deladd = 1;
+ delmod = ml;
+ }
+
+ if ((ml->sml_op == LDAP_MOD_ADD) ||
+ (ml->sml_op == LDAP_MOD_REPLACE))
+ {
+ if ( ml->sml_values && !BER_BVISNULL( &ml->sml_values[0] )) {
+ if ( deladd == 1 )
+ deladd = 2;
+
+ /* FIXME: there's no easy way to ensure
+ * that add does not cause multiple
+ * userPassword values; one way (that
+ * would be consistent with the single
+ * password constraint) would be to turn
+ * add into replace); another would be
+ * to disallow add.
+ *
+ * Let's check at least that a single value
+ * is being added
+ */
+ if ( addmod || !BER_BVISNULL( &ml->sml_values[ 1 ] ) ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password policy only allows one password value";
+ goto return_results;
+ }
+
+ addmod = ml;
+ } else {
+ /* replace can have no values, add cannot */
+ assert( ml->sml_op == LDAP_MOD_REPLACE );
+ }
+ }
+
+ } else if ( !(ml->sml_flags & SLAP_MOD_INTERNAL) && !is_at_operational( ml->sml_desc->ad_type ) ) {
+ mod_pw_only = 0;
+ /* modifying something other than password */
+ }
+
+ /*
+ * If there is a request to explicitly add a pwdReset
+ * attribute, then we suppress the normal behaviour on
+ * password change, which is to remove the pwdReset
+ * attribute.
+ *
+ * This enables an administrator to assign a new password
+ * and place a "must reset" flag on the entry, which will
+ * stay until the user explicitly changes his/her password.
+ */
+ if (ml->sml_desc == ad_pwdReset ) {
+ if ((ml->sml_op == LDAP_MOD_ADD) ||
+ (ml->sml_op == LDAP_MOD_REPLACE))
+ zapReset = 0;
+ }
+ if ( ml->sml_op == LDAP_MOD_DELETE ) {
+ if ( ml->sml_desc == ad_pwdGraceUseTime ) {
+ got_del_grace = 1;
+ } else if ( ml->sml_desc == ad_pwdAccountLockedTime ) {
+ got_del_lock = 1;
+ } else if ( ml->sml_desc == ad_pwdFailureTime ) {
+ got_del_fail = 1;
+ } else if ( ml->sml_desc == ad_pwdLastSuccess ) {
+ got_del_success = 1;
+ }
+ }
+ if ( ml->sml_desc == ad_pwdChangedTime ) {
+ got_changed = 1;
+ } else if (ml->sml_desc == ad_pwdHistory ) {
+ got_history = 1;
+ }
+ }
+
+ if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn ) && !mod_pw_only ) {
+ if ( dn_match( &op->o_conn->c_ndn,
+ &pwcons[op->o_conn->c_conn_idx].dn )) {
+ Debug( LDAP_DEBUG_TRACE,
+ "connection restricted to password changing only\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "Operations are restricted to bind/unbind/abandon/StartTLS/modify password";
+ pErr = PP_changeAfterReset;
+ goto return_results;
+ } else {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ }
+ }
+
+ /*
+ * if we have a "safe password modify policy", then we need to check if we're doing
+ * a delete (with the old password), followed by an add (with the new password).
+ *
+ * If we got just a delete with nothing else, just let it go. We also skip all the checks if
+ * the root user is bound. Root can do anything, including avoid the policies.
+ */
+
+ if (!have_policy || !pwmod) goto do_modify;
+
+ /*
+ * Build the password history list in ascending time order
+ * We need this, even if the user is root, in order to maintain
+ * the pwdHistory operational attributes properly.
+ */
+ if (addmod && pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) {
+ struct berval oldpw;
+ time_t oldtime;
+
+ for(i=0; ha->a_nvals[i].bv_val; i++) {
+ rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL,
+ &oldtime, &oldpw );
+
+ if (rc != LDAP_SUCCESS) continue; /* invalid history entry */
+
+ if (oldpw.bv_val) {
+ add_to_pwd_history( &tl, oldtime, &oldpw,
+ &(ha->a_nvals[i]) );
+ oldpw.bv_val = NULL;
+ oldpw.bv_len = 0;
+ }
+ }
+ for(p=tl; p; p=p->next, hsize++); /* count history size */
+ }
+
+ if (is_pwdadmin) goto do_modify;
+
+ /* NOTE: according to draft-behera-ldap-password-policy
+ * pwdAllowUserChange == FALSE must only prevent pwd changes
+ * by the user the pwd belongs to (ITS#7021) */
+ if (!pp.pwdAllowUserChange && dn_match(&op->o_req_ndn, &op->o_ndn)) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "User alteration of password is not allowed";
+ pErr = PP_passwordModNotAllowed;
+ goto return_results;
+ }
+
+ /* Just deleting? */
+ if (!addmod) {
+ /* skip everything else */
+ pwmod = 0;
+ goto do_modify;
+ }
+
+ /* This is a pwdModify exop that provided the old pw.
+ * We need to create a Delete mod for this old pw and
+ * let the matching value get found later
+ */
+ if (pp.pwdSafeModify && oldpw.bv_val ) {
+ ml = (Modifications *)ch_calloc( sizeof( Modifications ), 1 );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_desc = pp.ad;
+ ml->sml_type = pp.ad->ad_cname;
+ ml->sml_numvals = 1;
+ ml->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &ml->sml_values[0], &oldpw );
+ BER_BVZERO( &ml->sml_values[1] );
+ ml->sml_next = op->orm_modlist;
+ op->orm_modlist = ml;
+ delmod = ml;
+ deladd = 2;
+ }
+
+ if (pp.pwdSafeModify && deladd != 2) {
+ Debug( LDAP_DEBUG_TRACE,
+ "change password must use DELETE followed by ADD/REPLACE\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "Must supply old password to be changed as well as new one";
+ pErr = PP_mustSupplyOldPassword;
+ goto return_results;
+ }
+
+ /* Check age, but only if pwdReset is not TRUE */
+ pa = attr_find( e->e_attrs, ad_pwdReset );
+ if ((!pa || !bvmatch( &pa->a_nvals[0], &slap_true_bv )) &&
+ pp.pwdMinAge > 0) {
+ time_t pwtime = (time_t)-1, now;
+ int age;
+
+ if ((pa = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+ pwtime = parse_time( pa->a_nvals[0].bv_val );
+ now = slap_get_time();
+ age = (int)(now - pwtime);
+ if ((pwtime != (time_t)-1) && (age < pp.pwdMinAge)) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password is too young to change";
+ pErr = PP_passwordTooYoung;
+ goto return_results;
+ }
+ }
+
+ /* pa is used in password history check below, be sure it's set */
+ if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL && delmod) {
+ /*
+ * we have a password to check
+ */
+ bv = oldpw.bv_val ? &oldpw : delmod->sml_values;
+ /* FIXME: no access checking? */
+ rc = slap_passwd_check( op, NULL, pa, bv, &txt );
+ if (rc != LDAP_SUCCESS) {
+ Debug( LDAP_DEBUG_TRACE,
+ "old password check failed: %s\n", txt );
+
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Must supply correct old password to change to new one";
+ pErr = PP_mustSupplyOldPassword;
+ goto return_results;
+
+ } else {
+ int i;
+
+ /*
+ * replace the delete value with the (possibly hashed)
+ * value which is currently in the password.
+ */
+ for ( i = 0; !BER_BVISNULL( &delmod->sml_values[i] ); i++ ) {
+ free( delmod->sml_values[i].bv_val );
+ BER_BVZERO( &delmod->sml_values[i] );
+ }
+ free( delmod->sml_values );
+ delmod->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ BER_BVZERO( &delmod->sml_values[1] );
+ ber_dupbv( &(delmod->sml_values[0]), &(pa->a_nvals[0]) );
+ }
+ }
+
+ bv = newpw.bv_val ? &newpw : &addmod->sml_values[0];
+ if (pp.pwdCheckQuality > 0) {
+ struct berval errmsg = BER_BVC( errbuf );
+
+ rc = check_password_quality( bv, pi, &pp, &pErr, e, &errmsg );
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = rc;
+ txt = errmsg.bv_val;
+ if ( txt && txt[0] ) {
+ rs->sr_text = txt;
+ if ( txt != errbuf )
+ free_txt = 1;
+ } else {
+ rs->sr_text = "Password fails quality checking policy";
+ }
+ goto return_results;
+ }
+ }
+
+ /* If pwdInHistory is zero, passwords may be reused */
+ if (pa && pp.pwdInHistory > 0) {
+ /*
+ * Last check - the password history.
+ */
+ /* FIXME: no access checking? */
+ if (slap_passwd_check( op, NULL, pa, bv, &txt ) == LDAP_SUCCESS) {
+ /*
+ * This is bad - it means that the user is attempting
+ * to set the password to the same as the old one.
+ */
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password is not being changed from existing value";
+ pErr = PP_passwordInHistory;
+ goto return_results;
+ }
+
+ /* We need this when reduce pwdInHistory */
+ hskip = hsize - pp.pwdInHistory;
+
+ /*
+ * Iterate through the password history, and fail on any
+ * password matches.
+ */
+ at = *pa;
+ at.a_vals = cr;
+ cr[1].bv_val = NULL;
+ for(p=tl; p; p=p->next) {
+ if(hskip > 0){
+ hskip--;
+ continue;
+ }
+ cr[0] = p->pw;
+ /* FIXME: no access checking? */
+ rc = slap_passwd_check( op, NULL, &at, bv, &txt );
+
+ if (rc != LDAP_SUCCESS) continue;
+
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password is in history of old passwords";
+ pErr = PP_passwordInHistory;
+ goto return_results;
+ }
+ }
+
+do_modify:
+ if (pwmod) {
+ struct berval timestamp;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ time_t now = slap_get_time();
+
+ /* If the conn is restricted, set a callback to clear it
+ * if the pwmod succeeds
+ */
+ if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+ slap_callback *sc = op->o_tmpcalloc( 1, sizeof( slap_callback ),
+ op->o_tmpmemctx );
+ sc->sc_next = op->o_callback;
+ /* Must use sc_response to insure we reset on success, before
+ * the client sees the response. Must use sc_cleanup to insure
+ * that it gets cleaned up if sc_response is not called.
+ */
+ sc->sc_response = ppolicy_mod_cb;
+ sc->sc_cleanup = ppolicy_mod_cb;
+ op->o_callback = sc;
+ }
+
+ /*
+ * keep the necessary pwd.. operational attributes
+ * up to date.
+ */
+
+ if (!got_changed) {
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = sizeof(timebuf);
+ slap_timestamp( &now, &timestamp );
+
+ mods = NULL;
+ if (pwmop != LDAP_MOD_DELETE) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_REPLACE;
+ mods->sml_numvals = 1;
+ mods->sml_values = (BerVarray) ch_calloc( sizeof( struct berval ), 2 );
+ mods->sml_nvalues = (BerVarray) ch_calloc( sizeof( struct berval ), 2 );
+
+ ber_dupbv( &mods->sml_values[0], &timestamp );
+ ber_dupbv( &mods->sml_nvalues[0], &timestamp );
+ } else if (attr_find(e->e_attrs, ad_pwdChangedTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ }
+ if (mods) {
+ mods->sml_desc = ad_pwdChangedTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ }
+
+ if (!got_del_grace && attr_find(e->e_attrs, ad_pwdGraceUseTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdGraceUseTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_del_lock && attr_find(e->e_attrs, ad_pwdAccountLockedTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdAccountLockedTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdFailureTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if ( zapReset ) {
+ /*
+ * ITS#7084 Is this a modification by the password
+ * administrator? Then force a reset if configured.
+ * Otherwise clear it.
+ */
+ if ( pp.pwdMustChange && is_pwdadmin ) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_REPLACE;
+ mods->sml_desc = ad_pwdReset;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_numvals = 1;
+ mods->sml_values = (BerVarray) ch_calloc( sizeof( struct berval ), 2 );
+ mods->sml_nvalues = (BerVarray) ch_calloc( sizeof( struct berval ), 2 );
+
+ ber_dupbv( &mods->sml_values[0], (struct berval *)&slap_true_bv );
+ ber_dupbv( &mods->sml_nvalues[0], (struct berval *)&slap_true_bv );
+
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ } else if ( attr_find( e->e_attrs, ad_pwdReset ) ) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdReset;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ }
+
+ /* TODO: do we remove pwdLastSuccess or set it to 'now'? */
+ if (!got_del_success && attr_find(e->e_attrs, ad_pwdLastSuccess )){
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_desc = ad_pwdLastSuccess;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ /* Delete all pwdInHistory attribute */
+ if (!got_history && pp.pwdInHistory == 0 &&
+ attr_find(e->e_attrs, ad_pwdHistory )){
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_desc = ad_pwdHistory;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_history && pp.pwdInHistory > 0){
+ if (hsize >= pp.pwdInHistory) {
+ /*
+ * We use the >= operator, since we are going to add
+ * the existing password attribute value into the
+ * history - thus the cardinality of history values is
+ * about to rise by one.
+ *
+ * If this would push it over the limit of history
+ * values (remembering - the password policy could have
+ * changed since the password was last altered), we must
+ * delete at least 1 value from the pwdHistory list.
+ *
+ * In fact, we delete '(#pwdHistory attrs - max pwd
+ * history length) + 1' values, starting with the oldest.
+ * This is easily evaluated, since the linked list is
+ * created in ascending time order.
+ */
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_desc = ad_pwdHistory;
+ mods->sml_numvals = hsize - pp.pwdInHistory + 1;
+ mods->sml_values = ch_calloc( sizeof( struct berval ),
+ hsize - pp.pwdInHistory + 2 );
+ BER_BVZERO( &mods->sml_values[ hsize - pp.pwdInHistory + 1 ] );
+ for(i=0,p=tl; i < (hsize - pp.pwdInHistory + 1); i++, p=p->next) {
+ BER_BVZERO( &mods->sml_values[i] );
+ ber_dupbv( &(mods->sml_values[i]), &p->bv );
+ }
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ free_pwd_history_list( &tl );
+
+ /*
+ * Now add the existing password into the history list.
+ * This will be executed even if the operation is to delete
+ * the password entirely.
+ *
+ * This isn't in the spec explicitly, but it seems to make
+ * sense that the password history list is the list of all
+ * previous passwords - even if they were deleted. Thus, if
+ * someone tries to add a historical password at some future
+ * point, it will fail.
+ */
+ if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL) {
+ mods = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mods->sml_op = LDAP_MOD_ADD;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_type.bv_val = NULL;
+ mods->sml_desc = ad_pwdHistory;
+ mods->sml_nvalues = NULL;
+ mods->sml_numvals = 1;
+ mods->sml_values = ch_calloc( sizeof( struct berval ), 2 );
+ mods->sml_values[ 1 ].bv_val = NULL;
+ mods->sml_values[ 1 ].bv_len = 0;
+ make_pwd_history_value( timebuf, &mods->sml_values[0], pa );
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_modify: password attr lookup failed\n" );
+ }
+ }
+
+ /*
+ * Controversial bit here. If the new password isn't hashed
+ * (ie, is cleartext), we probably should hash it according
+ * to the default hash. The reason for this is that we want
+ * to use the policy if possible, but if we hash the password
+ * before, then we're going to run into trouble when it
+ * comes time to check the password.
+ *
+ * Now, the right thing to do is to use the extended password
+ * modify operation, but not all software can do this,
+ * therefore it makes sense to hash the new password, now
+ * we know it passes the policy requirements.
+ *
+ * Of course, if the password is already hashed, then we
+ * leave it alone.
+ */
+
+ if ((pi->hash_passwords) && (addmod) && !newpw.bv_val &&
+ (password_scheme( &(addmod->sml_values[0]), NULL ) != LDAP_SUCCESS))
+ {
+ struct berval hpw, bv;
+
+ slap_passwd_hash( &(addmod->sml_values[0]), &hpw, &txt );
+ if (hpw.bv_val == NULL) {
+ /*
+ * hashing didn't work. Emit an error.
+ */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = txt;
+ goto return_results;
+ }
+ bv = addmod->sml_values[0];
+ /* clear and discard the clear password */
+ memset(bv.bv_val, 0, bv.bv_len);
+ ber_memfree(bv.bv_val);
+ addmod->sml_values[0] = hpw;
+ }
+ } else {
+ /* ITS#8762 Make sure we drop pwdFailureTime if unlocking */
+ if (got_del_lock && !got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdFailureTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ return SLAP_CB_CONTINUE;
+
+return_results:
+ free_pwd_history_list( &tl );
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ if ( send_ctrl ) {
+ ctrl = create_passcontrol( op, -1, -1, pErr );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ send_ldap_result( op, rs );
+ if ( free_txt ) {
+ if ( is_pwdexop ) {
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+
+ /* Setup a callback so we can free the text when sent */
+ cb->sc_cleanup = ppolicy_text_cleanup;
+ cb->sc_private = (void *)txt;
+ overlay_callback_after_backover( op, cb, 1 );
+ } else {
+ if ( rs->sr_text == txt ) {
+ rs->sr_text = NULL;
+ }
+ free( (char *)txt );
+ }
+ }
+ if ( send_ctrl ) {
+ if ( is_pwdexop ) {
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) {
+ op->o_tmpfree( oldctrls, op->o_tmpmemctx );
+ }
+ oldctrls = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+
+ } else {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ }
+ return rs->sr_err;
+}
+
+static int
+ppolicy_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "passwordPolicyRequest control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ op->o_ctrlflag[ppolicy_cid] = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+ppolicy_au_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "account usability control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ op->o_ctrlflag[account_usability_cid] = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+attrPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ AttributeDescription *ad = NULL;
+ const char *err;
+ int code;
+
+ code = slap_bv2ad( val, &ad, &err );
+ if ( !code ) {
+ ber_dupbv_x( out, &ad->ad_type->sat_cname, ctx );
+ }
+ return code;
+}
+
+static int
+attrNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ AttributeDescription *ad = NULL;
+ const char *err;
+ int code;
+
+ code = slap_bv2ad( val, &ad, &err );
+ if ( !code ) {
+ ber_str2bv_x( ad->ad_type->sat_oid, 0, 1, out, ctx );
+ }
+ return code;
+}
+
+static int
+ppolicy_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ pp_info *pi;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ /* do not allow slapo-ppolicy to be global by now (ITS#5858) */
+ if ( cr ){
+ snprintf( cr->msg, sizeof(cr->msg),
+ "slapo-ppolicy cannot be global" );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg );
+ }
+ return 1;
+ }
+
+ pi = on->on_bi.bi_private = ch_calloc( sizeof(pp_info), 1 );
+
+ if ( !pwcons ) {
+ /* accommodate for c_conn_idx == -1 */
+ pwcons = ch_calloc( sizeof(pw_conn), dtblsize + 1 );
+ pwcons++;
+ }
+
+ ov_count++;
+
+ ldap_pvt_thread_mutex_init( &pi->pwdFailureTime_mutex );
+
+ return 0;
+}
+
+static int
+ppolicy_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ int rc;
+
+ if ( (rc = overlay_register_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY )) != LDAP_SUCCESS ) {
+ return rc;
+ }
+ return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
+}
+
+static int
+ppolicy_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
+ overlay_unregister_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY );
+#endif /* SLAP_CONFIG_DELETE */
+
+ return 0;
+}
+
+static int
+ppolicy_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+
+ on->on_bi.bi_private = NULL;
+ ldap_pvt_thread_mutex_destroy( &pi->pwdFailureTime_mutex );
+ free( pi->def_policy.bv_val );
+ free( pi );
+
+ ov_count--;
+ if ( ov_count <=0 && pwcons ) {
+ pw_conn *pwc = pwcons;
+ pwcons = NULL;
+ pwc--;
+ ch_free( pwc );
+ }
+ return 0;
+}
+
+static char *extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ NULL
+};
+
+static slap_overinst ppolicy;
+
+int ppolicy_initialize()
+{
+ int i, code;
+
+ for (i=0; pwd_OpSchema[i].def; i++) {
+ code = register_at( pwd_OpSchema[i].def, pwd_OpSchema[i].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_initialize: register_at failed\n" );
+ return code;
+ }
+ /* Allow Manager to set these as needed */
+ if ( is_at_no_user_mod( (*pwd_OpSchema[i].ad)->ad_type )) {
+ (*pwd_OpSchema[i].ad)->ad_type->sat_flags |=
+ SLAP_AT_MANAGEABLE;
+ }
+ }
+ ad_pwdLastSuccess = slap_schema.si_ad_pwdLastSuccess;
+ {
+ Syntax *syn;
+ MatchingRule *mr;
+
+ syn = ch_malloc( sizeof( Syntax ));
+ *syn = *ad_pwdAttribute->ad_type->sat_syntax;
+ syn->ssyn_pretty = attrPretty;
+ ad_pwdAttribute->ad_type->sat_syntax = syn;
+
+ mr = ch_malloc( sizeof( MatchingRule ));
+ *mr = *ad_pwdAttribute->ad_type->sat_equality;
+ mr->smr_normalize = attrNormalize;
+ ad_pwdAttribute->ad_type->sat_equality = mr;
+ }
+
+ for (i=0; pwd_ocs[i]; i++) {
+ code = register_oc( pwd_ocs[i], NULL, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_initialize: "
+ "register_oc failed\n" );
+ return code;
+ }
+ }
+
+ code = register_supported_control( LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ SLAP_CTRL_ADD|SLAP_CTRL_BIND|SLAP_CTRL_MODIFY, extops,
+ ppolicy_parseCtrl, &ppolicy_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
+ return code;
+ }
+
+ code = register_supported_control( LDAP_CONTROL_X_ACCOUNT_USABILITY,
+ SLAP_CTRL_SEARCH, NULL,
+ ppolicy_au_parseCtrl, &account_usability_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
+ return code;
+ }
+
+ /* We don't expect to receive these controls, only send them */
+ code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRED,
+ 0, NULL, NULL, NULL );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
+ return code;
+ }
+
+ code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRING,
+ 0, NULL, NULL, NULL );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
+ return code;
+ }
+
+ ldap_pvt_thread_mutex_init( &chk_syntax_mutex );
+
+ ppolicy.on_bi.bi_type = "ppolicy";
+ ppolicy.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ ppolicy.on_bi.bi_db_init = ppolicy_db_init;
+ ppolicy.on_bi.bi_db_open = ppolicy_db_open;
+ ppolicy.on_bi.bi_db_close = ppolicy_db_close;
+ ppolicy.on_bi.bi_db_destroy = ppolicy_db_destroy;
+
+ ppolicy.on_bi.bi_op_add = ppolicy_add;
+ ppolicy.on_bi.bi_op_bind = ppolicy_bind;
+ ppolicy.on_bi.bi_op_compare = ppolicy_compare;
+ ppolicy.on_bi.bi_op_delete = ppolicy_restrict;
+ ppolicy.on_bi.bi_op_modify = ppolicy_modify;
+ ppolicy.on_bi.bi_op_search = ppolicy_search;
+ ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy;
+
+ ppolicy.on_bi.bi_cf_ocs = ppolicyocs;
+ code = config_register_schema( ppolicycfg, ppolicyocs );
+ if ( code ) return code;
+
+ return overlay_register( &ppolicy );
+}
+
+#if SLAPD_OVER_PPOLICY == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return ppolicy_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_PPOLICY) */
diff --git a/servers/slapd/overlays/refint.c b/servers/slapd/overlays/refint.c
new file mode 100644
index 0000000..201803b
--- /dev/null
+++ b/servers/slapd/overlays/refint.c
@@ -0,0 +1,1086 @@
+/* refint.c - referential integrity module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+/* This module maintains referential integrity for a set of
+ * DN-valued attributes by searching for all references to a given
+ * DN whenever the DN is changed or its entry is deleted, and making
+ * the appropriate update.
+ *
+ * Updates are performed using the database rootdn in a separate task
+ * to allow the original operation to complete immediately.
+ */
+
+#ifdef SLAPD_OVER_REFINT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "ldap_rq.h"
+
+static slap_overinst refint;
+
+/* The DN to use in the ModifiersName for all refint updates */
+static BerValue refint_dn = BER_BVC("cn=Referential Integrity Overlay");
+static BerValue refint_ndn = BER_BVC("cn=referential integrity overlay");
+
+typedef struct refint_attrs_s {
+ struct refint_attrs_s *next;
+ AttributeDescription *attr;
+ BerVarray old_vals;
+ BerVarray old_nvals;
+ BerVarray new_vals;
+ BerVarray new_nvals;
+ int ra_numvals;
+ int dont_empty;
+} refint_attrs;
+
+typedef struct dependents_s {
+ struct dependents_s *next;
+ BerValue dn; /* target dn */
+ BerValue ndn;
+ refint_attrs *attrs;
+} dependent_data;
+
+typedef struct refint_q {
+ struct refint_q *next;
+ struct refint_data_s *rdata;
+ dependent_data *attrs; /* entries and attrs returned from callback */
+ BackendDB *db;
+ BerValue olddn;
+ BerValue oldndn;
+ BerValue newdn;
+ BerValue newndn;
+ int do_sub;
+} refint_q;
+
+typedef struct refint_data_s {
+ struct refint_attrs_s *attrs; /* list of known attrs */
+ BerValue dn; /* basedn in parent, */
+ BerValue nothing; /* the nothing value, if needed */
+ BerValue nnothing; /* normalized nothingness */
+ BerValue refint_dn; /* modifier's name */
+ BerValue refint_ndn; /* normalized modifier's name */
+ struct re_s *qtask;
+ refint_q *qhead;
+ refint_q *qtail;
+ BackendDB *db;
+ ldap_pvt_thread_mutex_t qmutex;
+} refint_data;
+
+typedef struct refint_pre_s {
+ slap_overinst *on;
+ int do_sub;
+} refint_pre;
+
+#define RUNQ_INTERVAL 36000 /* a long time */
+
+static MatchingRule *mr_dnSubtreeMatch;
+
+enum {
+ REFINT_ATTRS = 1,
+ REFINT_NOTHING,
+ REFINT_MODIFIERSNAME
+};
+
+static ConfigDriver refint_cf_gen;
+
+static ConfigTable refintcfg[] = {
+ { "refint_attributes", "attribute...", 2, 0, 0,
+ ARG_MAGIC|REFINT_ATTRS, refint_cf_gen,
+ "( OLcfgOvAt:11.1 NAME 'olcRefintAttribute' "
+ "DESC 'Attributes for referential integrity' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "refint_nothing", "string", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|REFINT_NOTHING, refint_cf_gen,
+ "( OLcfgOvAt:11.2 NAME 'olcRefintNothing' "
+ "DESC 'Replacement DN to supply when needed' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "refint_modifiersName", "DN", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|REFINT_MODIFIERSNAME, refint_cf_gen,
+ "( OLcfgOvAt:11.3 NAME 'olcRefintModifiersName' "
+ "DESC 'The DN to use as modifiersName' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs refintocs[] = {
+ { "( OLcfgOvOc:11.1 "
+ "NAME 'olcRefintConfig' "
+ "DESC 'Referential integrity configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcRefintAttribute "
+ "$ olcRefintNothing "
+ "$ olcRefintModifiersName "
+ ") )",
+ Cft_Overlay, refintcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+refint_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ refint_data *dd = (refint_data *)on->on_bi.bi_private;
+ refint_attrs *ip, *pip, **pipp = NULL;
+ AttributeDescription *ad;
+ const char *text;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ ip = dd->attrs;
+ while ( ip ) {
+ value_add_one( &c->rvalue_vals,
+ &ip->attr->ad_cname );
+ ip = ip->next;
+ }
+ rc = 0;
+ break;
+ case REFINT_NOTHING:
+ if ( !BER_BVISEMPTY( &dd->nothing )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &dd->nothing );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &dd->nnothing );
+ return rc;
+ }
+ rc = 0;
+ break;
+ case REFINT_MODIFIERSNAME:
+ if ( !BER_BVISEMPTY( &dd->refint_dn )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &dd->refint_dn );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &dd->refint_ndn );
+ return rc;
+ }
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ pipp = &dd->attrs;
+ if ( c->valx < 0 ) {
+ ip = *pipp;
+ *pipp = NULL;
+ while ( ip ) {
+ pip = ip;
+ ip = ip->next;
+ ch_free ( pip );
+ }
+ } else {
+ /* delete from linked list */
+ for ( i=0; i < c->valx; ++i ) {
+ pipp = &(*pipp)->next;
+ }
+ ip = *pipp;
+ *pipp = (*pipp)->next;
+
+ /* AttributeDescriptions are global so
+ * shouldn't be freed here... */
+ ch_free ( ip );
+ }
+ rc = 0;
+ break;
+ case REFINT_NOTHING:
+ ch_free( dd->nothing.bv_val );
+ ch_free( dd->nnothing.bv_val );
+ BER_BVZERO( &dd->nothing );
+ BER_BVZERO( &dd->nnothing );
+ rc = 0;
+ break;
+ case REFINT_MODIFIERSNAME:
+ ch_free( dd->refint_dn.bv_val );
+ ch_free( dd->refint_ndn.bv_val );
+ BER_BVZERO( &dd->refint_dn );
+ BER_BVZERO( &dd->refint_ndn );
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ rc = 0;
+ if ( c->op != SLAP_CONFIG_ADD && c->argc > 2 ) {
+ /* We wouldn't know how to delete these values later */
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "Supplying multiple names in a single %s value is "
+ "unsupported and will be disallowed in a future version\n",
+ c->argv[0] );
+ }
+
+ for ( i=1; i < c->argc; ++i ) {
+ ad = NULL;
+ if ( slap_str2ad ( c->argv[i], &ad, &text )
+ == LDAP_SUCCESS) {
+ ip = ch_malloc (
+ sizeof ( refint_attrs ) );
+ ip->attr = ad;
+
+ for ( pipp = &dd->attrs; *pipp; pipp = &(*pipp)->next )
+ /* Get to the end */ ;
+ ip->next = *pipp;
+ *pipp = ip;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s", c->argv[0], c->argv[i], text );
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ }
+ break;
+ case REFINT_NOTHING:
+ if ( !BER_BVISNULL( &c->value_ndn )) {
+ ch_free ( dd->nothing.bv_val );
+ ch_free ( dd->nnothing.bv_val );
+ dd->nothing = c->value_dn;
+ dd->nnothing = c->value_ndn;
+ rc = 0;
+ } else {
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REFINT_MODIFIERSNAME:
+ if ( !BER_BVISNULL( &c->value_ndn )) {
+ ch_free( dd->refint_dn.bv_val );
+ ch_free( dd->refint_ndn.bv_val );
+ dd->refint_dn = c->value_dn;
+ dd->refint_ndn = c->value_ndn;
+ rc = 0;
+ } else {
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ default:
+ abort ();
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+/*
+** allocate new refint_data;
+** store in on_bi.bi_private;
+**
+*/
+
+static int
+refint_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ refint_data *id = ch_calloc(1,sizeof(refint_data));
+
+ on->on_bi.bi_private = id;
+ ldap_pvt_thread_mutex_init( &id->qmutex );
+ return(0);
+}
+
+static int
+refint_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+
+ if ( on->on_bi.bi_private ) {
+ refint_data *id = on->on_bi.bi_private;
+ refint_attrs *ii, *ij;
+
+ on->on_bi.bi_private = NULL;
+ ldap_pvt_thread_mutex_destroy( &id->qmutex );
+
+ for(ii = id->attrs; ii; ii = ij) {
+ ij = ii->next;
+ ch_free(ii);
+ }
+
+ ch_free( id->nothing.bv_val );
+ BER_BVZERO( &id->nothing );
+ ch_free( id->nnothing.bv_val );
+ BER_BVZERO( &id->nnothing );
+
+ ch_free( id );
+ }
+ return(0);
+}
+
+/*
+** initialize, copy basedn if not already set
+**
+*/
+
+static int
+refint_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+
+ if ( BER_BVISNULL( &id->dn )) {
+ if ( BER_BVISNULL( &be->be_nsuffix[0] ))
+ return -1;
+ ber_dupbv( &id->dn, &be->be_nsuffix[0] );
+ }
+ if ( BER_BVISNULL( &id->refint_dn ) ) {
+ ber_dupbv( &id->refint_dn, &refint_dn );
+ ber_dupbv( &id->refint_ndn, &refint_ndn );
+ }
+
+ /*
+ ** find the backend that matches our configured basedn;
+ ** make sure it exists and has search and modify methods;
+ **
+ */
+
+ if ( on->on_info->oi_origdb != frontendDB ) {
+ BackendDB *db = select_backend(&id->dn, 1);
+
+ if ( db ) {
+ BackendInfo *bi;
+ if ( db == be )
+ bi = on->on_info->oi_orig;
+ else
+ bi = db->bd_info;
+ if ( !bi->bi_op_search || !bi->bi_op_modify ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "refint_response: backend missing search and/or modify\n" );
+ return -1;
+ }
+ id->db = db;
+ } else {
+ Debug( LDAP_DEBUG_CONFIG,
+ "refint_response: no backend for our baseDN %s??\n",
+ id->dn.bv_val );
+ return -1;
+ }
+ }
+ return(0);
+}
+
+
+/*
+** free our basedn;
+** free our refintdn
+**
+*/
+
+static int
+refint_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+
+ ch_free( id->dn.bv_val );
+ BER_BVZERO( &id->dn );
+ ch_free( id->refint_dn.bv_val );
+ BER_BVZERO( &id->refint_dn );
+ ch_free( id->refint_ndn.bv_val );
+ BER_BVZERO( &id->refint_ndn );
+
+ return(0);
+}
+
+/*
+** search callback
+** generates a list of Attributes from search results
+*/
+
+static int
+refint_search_cb(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ Attribute *a;
+ BerVarray b = NULL;
+ refint_q *rq = op->o_callback->sc_private;
+ refint_data *dd = rq->rdata;
+ refint_attrs *ia, *da = dd->attrs, *na;
+ dependent_data *ip;
+ int i;
+
+ Debug(LDAP_DEBUG_TRACE, "refint_search_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING" );
+
+ if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0);
+
+ /*
+ ** foreach configured attribute type:
+ ** if this attr exists in the search result,
+ ** and it has a value matching the target:
+ ** allocate an attr;
+ ** save/build DNs of any subordinate matches;
+ ** handle special case: found exact + subordinate match;
+ ** handle olcRefintNothing;
+ **
+ */
+
+ ip = op->o_tmpalloc(sizeof(dependent_data), op->o_tmpmemctx );
+ ber_dupbv_x( &ip->dn, &rs->sr_entry->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &ip->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ ip->next = rq->attrs;
+ rq->attrs = ip;
+ ip->attrs = NULL;
+ for(ia = da; ia; ia = ia->next) {
+ if ( (a = attr_find(rs->sr_entry->e_attrs, ia->attr) ) ) {
+ int exact = -1, is_exact;
+
+ na = NULL;
+
+ /* Are we doing subtree matching or simple equality? */
+ if ( rq->do_sub ) {
+ for(i = 0, b = a->a_nvals; b[i].bv_val; i++) {
+ if(dnIsSuffix(&b[i], &rq->oldndn)) {
+ is_exact = b[i].bv_len == rq->oldndn.bv_len;
+
+ /* Paranoia: skip buggy duplicate exact match,
+ * it would break ra_numvals
+ */
+ if ( is_exact && exact >= 0 )
+ continue;
+
+ /* first match? create structure */
+ if ( na == NULL ) {
+ na = op->o_tmpcalloc( 1,
+ sizeof( refint_attrs ),
+ op->o_tmpmemctx );
+ na->next = ip->attrs;
+ ip->attrs = na;
+ na->attr = ia->attr;
+ }
+
+ na->ra_numvals++;
+
+ if ( is_exact ) {
+ /* Exact match: refint_repair will deduce the DNs */
+ exact = i;
+
+ } else {
+ /* Subordinate match */
+ struct berval newsub, newdn, olddn, oldndn;
+
+ /* Save old DN */
+ ber_dupbv_x( &olddn, &a->a_vals[i], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_vals, &olddn, op->o_tmpmemctx );
+
+ ber_dupbv_x( &oldndn, &a->a_nvals[i], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_nvals, &oldndn, op->o_tmpmemctx );
+
+ if ( BER_BVISEMPTY( &rq->newdn ) )
+ continue;
+
+ /* Rename subordinate match: Build new DN */
+ newsub = a->a_vals[i];
+ newsub.bv_len -= rq->olddn.bv_len + 1;
+ build_new_dn( &newdn, &rq->newdn, &newsub, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_vals, &newdn, op->o_tmpmemctx );
+
+ newsub = a->a_nvals[i];
+ newsub.bv_len -= rq->oldndn.bv_len + 1;
+ build_new_dn( &newdn, &rq->newndn, &newsub, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_nvals, &newdn, op->o_tmpmemctx );
+ }
+ }
+ }
+
+ /* If we got both subordinate and exact match,
+ * refint_repair won't special-case the exact match */
+ if ( exact >= 0 && na->old_vals ) {
+ struct berval dn;
+
+ ber_dupbv_x( &dn, &a->a_vals[exact], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_vals, &dn, op->o_tmpmemctx );
+ ber_dupbv_x( &dn, &a->a_nvals[exact], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_nvals, &dn, op->o_tmpmemctx );
+
+ if ( !BER_BVISEMPTY( &rq->newdn ) ) {
+ ber_dupbv_x( &dn, &rq->newdn, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_vals, &dn, op->o_tmpmemctx );
+ ber_dupbv_x( &dn, &rq->newndn, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_nvals, &dn, op->o_tmpmemctx );
+ }
+ }
+ } else {
+ /* entry has no children, just equality matching */
+ is_exact = attr_valfind( a,
+ SLAP_MR_EQUALITY|SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH|
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, &rq->oldndn, &i, NULL );
+ if ( is_exact == LDAP_SUCCESS ) {
+ na = op->o_tmpcalloc( 1,
+ sizeof( refint_attrs ),
+ op->o_tmpmemctx );
+ na->next = ip->attrs;
+ ip->attrs = na;
+ na->attr = ia->attr;
+ na->ra_numvals = 1;
+ }
+ }
+
+ /* Deleting/replacing all values and a nothing DN is configured? */
+ if ( na && na->ra_numvals == a->a_numvals && !BER_BVISNULL(&dd->nothing) )
+ na->dont_empty = 1;
+
+ Debug( LDAP_DEBUG_TRACE, "refint_search_cb: %s: %s (#%d)\n",
+ a->a_desc->ad_cname.bv_val, rq->olddn.bv_val, i );
+ }
+ }
+
+ return(0);
+}
+
+static int
+refint_repair(
+ Operation *op,
+ refint_data *id,
+ refint_q *rq )
+{
+ dependent_data *dp;
+ SlapReply rs = {REP_RESULT};
+ Operation op2;
+ unsigned long opid;
+ int rc;
+ int cache;
+
+ op->o_callback->sc_response = refint_search_cb;
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ cache = op->o_do_not_cache;
+ op->o_do_not_cache = 1;
+
+ /* search */
+ rc = op->o_bd->be_search( op, &rs );
+ op->o_do_not_cache = cache;
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: search failed: %d\n",
+ rc );
+ return rc;
+ }
+
+ /* safety? paranoid just in case */
+ if ( op->o_callback->sc_private == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: callback wiped out sc_private?!\n" );
+ return 0;
+ }
+
+ /* Set up the Modify requests */
+ op->o_callback->sc_response = &slap_null_cb;
+
+ /*
+ * [our search callback builds a list of attrs]
+ * foreach attr:
+ * make sure its dn has a backend;
+ * build Modification* chain;
+ * call the backend modify function;
+ *
+ */
+
+ opid = op->o_opid;
+ op2 = *op;
+ for ( dp = rq->attrs; dp; dp = dp->next ) {
+ SlapReply rs2 = {REP_RESULT};
+ refint_attrs *ra;
+ Modifications *m;
+
+ if ( dp->attrs == NULL ) continue; /* TODO: Is this needed? */
+
+ op2.o_bd = select_backend( &dp->ndn, 1 );
+ if ( !op2.o_bd ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: no backend for DN %s!\n",
+ dp->dn.bv_val );
+ continue;
+ }
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.orm_modlist = NULL;
+ op2.o_req_dn = dp->dn;
+ op2.o_req_ndn = dp->ndn;
+ /* Internal ops, never replicate these */
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+ op2.o_opid = 0;
+
+ /* Set our ModifiersName */
+ if ( SLAP_LASTMOD( op->o_bd ) ) {
+ m = op2.o_tmpalloc( sizeof(Modifications) +
+ 4*sizeof(BerValue), op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+ m->sml_desc = slap_schema.si_ad_modifiersName;
+ m->sml_type = m->sml_desc->ad_cname;
+ m->sml_numvals = 1;
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ m->sml_values[0] = id->refint_dn;
+ m->sml_nvalues[0] = id->refint_ndn;
+ }
+
+ for ( ra = dp->attrs; ra; ra = ra->next ) {
+ size_t len;
+
+ /* Add values */
+ if ( ra->dont_empty || !BER_BVISEMPTY( &rq->newdn ) ) {
+ len = sizeof(Modifications);
+
+ if ( ra->new_vals == NULL ) {
+ len += 4*sizeof(BerValue);
+ }
+
+ m = op2.o_tmpalloc( len, op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_desc = ra->attr;
+ m->sml_type = ra->attr->ad_cname;
+ if ( ra->new_vals == NULL ) {
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ m->sml_numvals = 1;
+ if ( BER_BVISEMPTY( &rq->newdn ) ) {
+ m->sml_values[0] = id->nothing;
+ m->sml_nvalues[0] = id->nnothing;
+ } else {
+ m->sml_values[0] = rq->newdn;
+ m->sml_nvalues[0] = rq->newndn;
+ }
+ } else {
+ m->sml_values = ra->new_vals;
+ m->sml_nvalues = ra->new_nvals;
+ m->sml_numvals = ra->ra_numvals;
+ }
+ }
+
+ /* Delete values */
+ len = sizeof(Modifications);
+ if ( ra->old_vals == NULL ) {
+ len += 4*sizeof(BerValue);
+ }
+ m = op2.o_tmpalloc( len, op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_desc = ra->attr;
+ m->sml_type = ra->attr->ad_cname;
+ if ( ra->old_vals == NULL ) {
+ m->sml_numvals = 1;
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ m->sml_values[0] = rq->olddn;
+ m->sml_nvalues[0] = rq->oldndn;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ } else {
+ m->sml_values = ra->old_vals;
+ m->sml_nvalues = ra->old_nvals;
+ m->sml_numvals = ra->ra_numvals;
+ }
+ }
+
+ op2.o_dn = op2.o_bd->be_rootdn;
+ op2.o_ndn = op2.o_bd->be_rootndn;
+ rc = op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: dependent modify failed: %d\n",
+ rs2.sr_err );
+ }
+
+ while ( ( m = op2.orm_modlist ) ) {
+ op2.orm_modlist = m->sml_next;
+ op2.o_tmpfree( m, op2.o_tmpmemctx );
+ }
+ }
+ op2.o_opid = opid;
+
+ return 0;
+}
+
+static void *
+refint_qtask( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ refint_data *id = rtask->arg;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ Filter ftop, *fptr;
+ refint_q *rq;
+ refint_attrs *ip;
+ int pausing = 0, rc = 0;
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ /*
+ ** build a search filter for all configured attributes;
+ ** populate our Operation;
+ ** pass our data (attr list, dn) to backend via sc_private;
+ ** call the backend search function;
+ ** nb: (|(one=thing)) is valid, but do smart formatting anyway;
+ ** nb: 16 is arbitrarily a dozen or so extra bytes;
+ **
+ */
+
+ ftop.f_choice = LDAP_FILTER_OR;
+ ftop.f_next = NULL;
+ ftop.f_or = NULL;
+ op->ors_filter = &ftop;
+ for(ip = id->attrs; ip; ip = ip->next) {
+ /* this filter can be either EQUALITY or EXT */
+ fptr = op->o_tmpcalloc( sizeof(Filter) + sizeof(MatchingRuleAssertion),
+ 1, op->o_tmpmemctx );
+ fptr->f_mra = (MatchingRuleAssertion *)(fptr+1);
+ fptr->f_mr_rule = mr_dnSubtreeMatch;
+ fptr->f_mr_rule_text = mr_dnSubtreeMatch->smr_bvoid;
+ fptr->f_mr_desc = ip->attr;
+ fptr->f_mr_dnattrs = 0;
+ fptr->f_next = ftop.f_or;
+ ftop.f_or = fptr;
+ }
+
+ for (;;) {
+ dependent_data *dp, *dp_next;
+ refint_attrs *ra, *ra_next;
+
+ if ( ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) {
+ pausing = 1;
+ break;
+ }
+
+ /* Dequeue an op */
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ rq = id->qhead;
+ if ( rq ) {
+ id->qhead = rq->next;
+ if ( !id->qhead )
+ id->qtail = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+ if ( !rq )
+ break;
+
+ for (fptr = ftop.f_or; fptr; fptr = fptr->f_next ) {
+ fptr->f_mr_value = rq->oldndn;
+ /* Use (attr:dnSubtreeMatch:=value) to catch subtree rename
+ * and subtree delete where supported */
+ if (rq->do_sub)
+ fptr->f_choice = LDAP_FILTER_EXT;
+ else
+ fptr->f_choice = LDAP_FILTER_EQUALITY;
+ }
+
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+
+ /* callback gets the searched dn instead */
+ cb.sc_private = rq;
+ cb.sc_response = refint_search_cb;
+ op->o_callback = &cb;
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_limit = NULL;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+
+ /* no attrs! */
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ if ( rq->db != NULL ) {
+ op->o_bd = rq->db;
+ rc = refint_repair( op, id, rq );
+
+ } else {
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ /* we may want to skip cn=config */
+ if ( be == LDAP_STAILQ_FIRST(&backendDB) ) {
+ continue;
+ }
+
+ if ( be->be_search && be->be_modify ) {
+ op->o_bd = be;
+ rc = refint_repair( op, id, rq );
+ }
+ }
+ }
+
+ for ( dp = rq->attrs; dp; dp = dp_next ) {
+ dp_next = dp->next;
+ for ( ra = dp->attrs; ra; ra = ra_next ) {
+ ra_next = ra->next;
+ ber_bvarray_free_x( ra->new_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->new_vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->old_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->old_vals, op->o_tmpmemctx );
+ op->o_tmpfree( ra, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( dp->ndn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( dp->dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( dp, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ if ( rc == LDAP_BUSY ) {
+ pausing = 1;
+ /* re-queue this op */
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ rq->next = id->qhead;
+ id->qhead = rq;
+ if ( !id->qtail )
+ id->qtail = rq;
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+ break;
+ }
+
+ if ( !BER_BVISNULL( &rq->newndn )) {
+ ch_free( rq->newndn.bv_val );
+ ch_free( rq->newdn.bv_val );
+ }
+ ch_free( rq->oldndn.bv_val );
+ ch_free( rq->olddn.bv_val );
+ ch_free( rq );
+ }
+
+ /* free filter */
+ for ( fptr = ftop.f_or; fptr; ) {
+ Filter *f_next = fptr->f_next;
+ op->o_tmpfree( fptr, op->o_tmpmemctx );
+ fptr = f_next;
+ }
+
+ /* wait until we get explicitly scheduled again */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, id->qtask );
+ if ( pausing ) {
+ /* try to run again as soon as the pause is done */
+ id->qtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 );
+ id->qtask->interval.tv_sec = RUNQ_INTERVAL;
+ } else {
+ ldap_pvt_runqueue_resched( &slapd_rq,id->qtask, 1 );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+/*
+** refint_response
+** search for matching records and modify them
+*/
+
+static int
+refint_response(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ refint_pre *rp;
+ slap_overinst *on;
+ refint_data *id;
+ refint_q *rq;
+ refint_attrs *ip;
+ int ac;
+
+ /* If the main op failed or is not a Delete or ModRdn, ignore it */
+ if (( op->o_tag != LDAP_REQ_DELETE && op->o_tag != LDAP_REQ_MODRDN ) ||
+ rs->sr_err != LDAP_SUCCESS )
+ return SLAP_CB_CONTINUE;
+
+ rp = op->o_callback->sc_private;
+ on = rp->on;
+ id = on->on_bi.bi_private;
+
+ rq = ch_calloc( 1, sizeof( refint_q ));
+ ber_dupbv( &rq->olddn, &op->o_req_dn );
+ ber_dupbv( &rq->oldndn, &op->o_req_ndn );
+ rq->db = id->db;
+ rq->rdata = id;
+ rq->do_sub = rp->do_sub;
+
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ ber_dupbv( &rq->newdn, &op->orr_newDN );
+ ber_dupbv( &rq->newndn, &op->orr_nnewDN );
+ }
+
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ if ( id->qtail ) {
+ id->qtail->next = rq;
+ } else {
+ id->qhead = rq;
+ }
+ id->qtail = rq;
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+
+ ac = 0;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( !id->qtask ) {
+ id->qtask = ldap_pvt_runqueue_insert( &slapd_rq, RUNQ_INTERVAL,
+ refint_qtask, id, "refint_qtask",
+ op->o_bd->be_suffix[0].bv_val );
+ ac = 1;
+ } else {
+ if ( !ldap_pvt_runqueue_isrunning( &slapd_rq, id->qtask ) &&
+ !id->qtask->next_sched.tv_sec ) {
+ id->qtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 );
+ id->qtask->interval.tv_sec = RUNQ_INTERVAL;
+ ac = 1;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ if ( ac )
+ slap_wake_listener();
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* Check if the target entry exists and has children.
+ * Do nothing if target doesn't exist.
+ */
+static int
+refint_preop(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+ Entry *e;
+ int rc;
+
+ /* are any attrs configured? */
+ if ( !id->attrs )
+ return SLAP_CB_CONTINUE;
+
+ rc = overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on );
+ if ( rc == LDAP_SUCCESS ) {
+ slap_callback *sc = op->o_tmpcalloc( 1,
+ sizeof(slap_callback)+sizeof(refint_pre), op->o_tmpmemctx );
+ refint_pre *rp = (refint_pre *)(sc+1);
+ rp->on = on;
+ rp->do_sub = 1; /* assume there are children */
+ if ( op->o_bd->be_has_subordinates ) {
+ int has = 0;
+ rc = op->o_bd->be_has_subordinates( op, e, &has );
+ /* there definitely are not children */
+ if ( rc == LDAP_SUCCESS && has == LDAP_COMPARE_FALSE )
+ rp->do_sub = 0;
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ sc->sc_response = refint_response;
+ sc->sc_private = rp;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int refint_initialize() {
+ int rc;
+
+ mr_dnSubtreeMatch = mr_find( "dnSubtreeMatch" );
+ if ( mr_dnSubtreeMatch == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "refint_initialize: "
+ "unable to find MatchingRule 'dnSubtreeMatch'.\n" );
+ return 1;
+ }
+
+ /* statically declared just after the #includes at top */
+ refint.on_bi.bi_type = "refint";
+ refint.on_bi.bi_db_init = refint_db_init;
+ refint.on_bi.bi_db_destroy = refint_db_destroy;
+ refint.on_bi.bi_db_open = refint_open;
+ refint.on_bi.bi_db_close = refint_close;
+ refint.on_bi.bi_op_delete = refint_preop;
+ refint.on_bi.bi_op_modrdn = refint_preop;
+
+ refint.on_bi.bi_cf_ocs = refintocs;
+ rc = config_register_schema ( refintcfg, refintocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&refint));
+}
+
+#if SLAPD_OVER_REFINT == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return refint_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_REFINT */
diff --git a/servers/slapd/overlays/remoteauth.c b/servers/slapd/overlays/remoteauth.c
new file mode 100644
index 0000000..1d60af0
--- /dev/null
+++ b/servers/slapd/overlays/remoteauth.c
@@ -0,0 +1,1002 @@
+/* $OpenLDAP$ */
+/* remoteauth.c - Overlay to delegate bind processing to a remote server */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2017-2021 Ondřej Kuzník, Symas Corporation.
+ * Portions Copyright 2004-2017 Howard Chu, Symas Corporation.
+ * Portions Copyright 2004 Hewlett-Packard Company.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ldap.h>
+#if SLAPD_MODULES
+#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */
+#include <ltdl.h>
+#endif
+#include <ac/errno.h>
+#include <ac/time.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+#ifndef UP_STR
+#define UP_STR "userPassword"
+#endif /* UP_STR */
+
+#ifndef LDAP_PREFIX
+#define LDAP_PREFIX "ldap://"
+#endif /* LDAP_PREFIX */
+
+#ifndef FILE_PREFIX
+#define FILE_PREFIX "file://"
+#endif /* LDAP_PREFIX */
+
+typedef struct _ad_info {
+ struct _ad_info *next;
+ char *domain;
+ char *realm;
+} ad_info;
+
+typedef struct _ad_pin {
+ struct _ad_pin *next;
+ char *hostname;
+ char *pin;
+} ad_pin;
+
+typedef struct _ad_private {
+ char *dn;
+ AttributeDescription *dn_ad;
+ char *domain_attr;
+ AttributeDescription *domain_ad;
+
+ AttributeDescription *up_ad;
+ ad_info *mappings;
+
+ char *default_realm;
+ char *default_domain;
+
+ int up_set;
+ int retry_count;
+ int store_on_success;
+
+ ad_pin *pins;
+ slap_bindconf ad_tls;
+} ad_private;
+
+enum {
+ REMOTE_AUTH_MAPPING = 1,
+ REMOTE_AUTH_DN_ATTRIBUTE,
+ REMOTE_AUTH_DOMAIN_ATTRIBUTE,
+ REMOTE_AUTH_DEFAULT_DOMAIN,
+ REMOTE_AUTH_DEFAULT_REALM,
+ REMOTE_AUTH_CACERT_DIR,
+ REMOTE_AUTH_CACERT_FILE,
+ REMOTE_AUTH_VALIDATE_CERTS,
+ REMOTE_AUTH_RETRY_COUNT,
+ REMOTE_AUTH_TLS,
+ REMOTE_AUTH_TLS_PIN,
+ REMOTE_AUTH_STORE_ON_SUCCESS,
+};
+
+static ConfigDriver remoteauth_cf_gen;
+
+static ConfigTable remoteauthcfg[] = {
+ { "remoteauth_mapping", "mapping between domain and realm", 2, 3, 0,
+ ARG_MAGIC|REMOTE_AUTH_MAPPING,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.1 NAME 'olcRemoteAuthMapping' "
+ "DESC 'Mapping from domain name to server' "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+ { "remoteauth_dn_attribute", "Attribute to use as AD bind DN", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DN_ATTRIBUTE,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.2 NAME 'olcRemoteAuthDNAttribute' "
+ "DESC 'Attribute in entry to use as bind DN for AD' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_domain_attribute", "Attribute to use as domain determinant", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DOMAIN_ATTRIBUTE,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.3 NAME 'olcRemoteAuthDomainAttribute' "
+ "DESC 'Attribute in entry to determine windows domain' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_default_domain", "Default Windows domain", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DEFAULT_DOMAIN,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.4 NAME 'olcRemoteAuthDefaultDomain' "
+ "DESC 'Default Windows domain to use' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_default_realm", "Default AD realm", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DEFAULT_REALM,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.5 NAME 'olcRemoteAuthDefaultRealm' "
+ "DESC 'Default AD realm to use' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_store", "on|off", 1, 2, 0,
+ ARG_OFFSET|ARG_ON_OFF|REMOTE_AUTH_STORE_ON_SUCCESS,
+ (void *)offsetof(ad_private, store_on_success),
+ "( OLcfgOvAt:24.6 NAME 'olcRemoteAuthStore' "
+ "DESC 'Store password locally on success' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_retry_count", "integer", 2, 2, 0,
+ ARG_OFFSET|ARG_UINT|REMOTE_AUTH_RETRY_COUNT,
+ (void *)offsetof(ad_private, retry_count),
+ "( OLcfgOvAt:24.7 NAME 'olcRemoteAuthRetryCount' "
+ "DESC 'Number of retries attempted' "
+ "SYNTAX OMsInteger SINGLE-VALUE )",
+ NULL, { .v_uint = 3 }
+ },
+ { "remoteauth_tls", "tls settings", 2, 0, 0,
+ ARG_MAGIC|REMOTE_AUTH_TLS,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.8 NAME 'olcRemoteAuthTLS' "
+ "DESC 'StartTLS settings' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_tls_peerkey_hash", "mapping between hostnames and their public key hash", 3, 3, 0,
+ ARG_MAGIC|REMOTE_AUTH_TLS_PIN,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.9 NAME 'olcRemoteAuthTLSPeerkeyHash' "
+ "DESC 'StartTLS hostname to public key pin mapping file' "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL }
+};
+
+static ConfigOCs remoteauthocs[] = {
+ { "( OLcfgOvOc:24.1 "
+ "NAME 'olcRemoteAuthCfg' "
+ "DESC 'Remote Directory passthough authentication configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcRemoteAuthTLS "
+ "MAY ( olcRemoteAuthMapping $ olcRemoteAuthDNAttribute $ "
+ " olcRemoteAuthDomainAttribute $ olcRemoteAuthDefaultDomain $ "
+ " olcRemoteAuthDefaultRealm $ olcRemoteAuthStore $ "
+ " olcRemoteAuthRetryCount $ olcRemoteAuthTLSPeerkeyHash ) )",
+ Cft_Overlay, remoteauthcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+remoteauth_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ ad_private *ad = (ad_private *)on->on_bi.bi_private;
+ struct berval bv;
+ int i, rc = 0;
+ ad_info *map;
+ const char *text = NULL;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ for ( map = ad->mappings; map; map = map->next ) {
+ char *str;
+
+ str = ch_malloc( strlen( map->domain ) +
+ strlen( map->realm ) + 2 );
+ sprintf( str, "%s %s", map->domain, map->realm );
+ ber_str2bv( str, 0, 0, &bv );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( !rc )
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ ch_free( str );
+ if ( rc ) break;
+ }
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( ad->dn )
+ value_add_one( &c->rvalue_vals, &ad->dn_ad->ad_cname );
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( ad->domain_attr )
+ value_add_one(
+ &c->rvalue_vals, &ad->domain_ad->ad_cname );
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ber_str2bv( ad->default_domain, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ber_str2bv( ad->default_realm, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ case REMOTE_AUTH_TLS:
+ bindconf_tls_unparse( &ad->ad_tls, &bv );
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ value_add_one( &c->rvalue_vals, &bv );
+ ch_free( bv.bv_val );
+ break;
+ case REMOTE_AUTH_TLS_PIN: {
+ ad_pin *pin = ad->pins;
+ for ( pin = ad->pins; pin; pin = pin->next ) {
+ bv.bv_val = ch_malloc( strlen( pin->hostname ) +
+ strlen( pin->pin ) + 2 );
+ bv.bv_len = sprintf(
+ bv.bv_val, "%s %s", pin->hostname, pin->pin );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ if ( rc ) return rc;
+ }
+ } break;
+
+ default:
+ abort();
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ if ( c->valx < 0 ) {
+ /* delete all mappings */
+ while ( ad->mappings ) {
+ map = ad->mappings;
+ ad->mappings = ad->mappings->next;
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ }
+ } else {
+ /* delete a specific mapping indicated by 'valx'*/
+ ad_info *pmap = NULL;
+
+ for ( map = ad->mappings, i = 0;
+ ( map ) && ( i < c->valx );
+ pmap = map, map = map->next, i++ )
+ ;
+
+ if ( pmap ) {
+ pmap->next = map->next;
+ map->next = NULL;
+
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ } else if ( ad->mappings ) {
+ /* delete the first item in the list */
+ map = ad->mappings;
+ ad->mappings = map->next;
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ }
+ }
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( ad->dn ) {
+ ch_free( ad->dn );
+ ad->dn = NULL; /* Don't free AttributeDescription */
+ }
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( ad->domain_attr ) {
+ ch_free( ad->domain_attr );
+ /* Don't free AttributeDescription */
+ ad->domain_attr = NULL;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ch_free( ad->default_domain );
+ ad->default_domain = NULL;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ch_free( ad->default_realm );
+ ad->default_realm = NULL;
+ }
+ break;
+ case REMOTE_AUTH_TLS:
+ /* MUST + SINGLE-VALUE -> this is a replace */
+ bindconf_free( &ad->ad_tls );
+ break;
+ case REMOTE_AUTH_TLS_PIN:
+ while ( ad->pins ) {
+ ad_pin *pin = ad->pins;
+ ad->pins = ad->pins->next;
+ ch_free( pin->hostname );
+ ch_free( pin->pin );
+ ch_free( pin );
+ }
+ break;
+ /* ARG_OFFSET */
+ case REMOTE_AUTH_STORE_ON_SUCCESS:
+ case REMOTE_AUTH_RETRY_COUNT:
+ abort();
+ break;
+ default:
+ abort();
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ /* add mapping to head of list */
+ map = ch_malloc( sizeof(ad_info) );
+ map->domain = ber_strdup( c->argv[1] );
+ map->realm = ber_strdup( c->argv[2] );
+ map->next = ad->mappings;
+ ad->mappings = map;
+
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( slap_str2ad( c->argv[1], &ad->dn_ad, &text ) ==
+ LDAP_SUCCESS ) {
+ ad->dn = ber_strdup( ad->dn_ad->ad_cname.bv_val );
+ } else {
+ strncpy( c->cr_msg, text, sizeof(c->cr_msg) );
+ c->cr_msg[sizeof(c->cr_msg) - 1] = '\0';
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( slap_str2ad( c->argv[1], &ad->domain_ad, &text ) ==
+ LDAP_SUCCESS ) {
+ ad->domain_attr =
+ ber_strdup( ad->domain_ad->ad_cname.bv_val );
+ } else {
+ strncpy( c->cr_msg, text, sizeof(c->cr_msg) );
+ c->cr_msg[sizeof(c->cr_msg) - 1] = '\0';
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ch_free( ad->default_domain );
+ ad->default_domain = NULL;
+ }
+ ad->default_domain = ber_strdup( c->argv[1] );
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ch_free( ad->default_realm );
+ ad->default_realm = NULL;
+ }
+ ad->default_realm = ber_strdup( c->argv[1] );
+ break;
+ case REMOTE_AUTH_TLS:
+ for ( i=1; i < c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &ad->ad_tls ) ) {
+ rc = 1;
+ break;
+ }
+ }
+ bindconf_tls_defaults( &ad->ad_tls );
+ break;
+ case REMOTE_AUTH_TLS_PIN: {
+ ad_pin *pin = ch_calloc( 1, sizeof(ad_pin) );
+
+ pin->hostname = ber_strdup( c->argv[1] );
+ pin->pin = ber_strdup( c->argv[2] );
+ pin->next = ad->pins;
+ ad->pins = pin;
+ } break;
+ /* ARG_OFFSET */
+ case REMOTE_AUTH_STORE_ON_SUCCESS:
+ case REMOTE_AUTH_RETRY_COUNT:
+ abort();
+ break;
+ default:
+ abort();
+ }
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static char *
+get_realm(
+ const char *domain,
+ ad_info *mappings,
+ const char *default_realm,
+ int *isfile )
+{
+ ad_info *ai;
+ char *dom = NULL, *ch, *ret = NULL;
+
+ if ( isfile ) *isfile = 0;
+
+ if ( !domain ) {
+ ret = default_realm ? ch_strdup( default_realm ) : NULL;
+ goto exit;
+ }
+
+ /* munge any DOMAIN\user or DOMAIN:user values into just DOMAIN */
+
+ ch = strchr( domain, '\\' );
+ if ( !ch ) ch = strchr( domain, ':' );
+
+ if ( ch ) {
+ dom = ch_malloc( ch - domain + 1 );
+ strncpy( dom, domain, ch - domain );
+ dom[ch - domain] = '\0';
+ } else {
+ dom = ch_strdup( domain );
+ }
+
+ for ( ai = mappings; ai; ai = ai->next )
+ if ( strcasecmp( ai->domain, dom ) == 0 ) {
+ ret = ch_strdup( ai->realm );
+ break;
+ }
+
+ if ( !ai )
+ ret = default_realm ? ch_strdup( default_realm ) :
+ NULL; /* no mapping found */
+exit:
+ if ( dom ) ch_free( dom );
+ if ( ret &&
+ ( strncasecmp( ret, FILE_PREFIX, strlen( FILE_PREFIX ) ) == 0 ) ) {
+ char *p;
+
+ p = ret;
+ ret = ch_strdup( p + strlen( FILE_PREFIX ) );
+ ch_free( p );
+ if ( isfile ) *isfile = 1;
+ }
+
+ return ret;
+}
+
+static char *
+get_ldap_url( const char *realm, int isfile )
+{
+ char *ldap_url = NULL;
+ FILE *fp;
+
+ if ( !realm ) return NULL;
+
+ if ( !isfile ) {
+ if ( strstr( realm, "://" ) ) {
+ return ch_strdup( realm );
+ }
+
+ ldap_url = ch_malloc( 1 + strlen( LDAP_PREFIX ) + strlen( realm ) );
+ sprintf( ldap_url, "%s%s", LDAP_PREFIX, realm );
+ return ldap_url;
+ }
+
+ fp = fopen( realm, "r" );
+ if ( !fp ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_TRACE, "remoteauth: "
+ "Unable to open realm file (%s)\n",
+ sock_errstr( saved_errno, ebuf, sizeof(ebuf) ) );
+ return NULL;
+ }
+ /*
+ * Read each line in the file and return a URL of the form
+ * "ldap://<line1> ldap://<line2> ... ldap://<lineN>"
+ * which can be passed to ldap_initialize.
+ */
+ while ( !feof( fp ) ) {
+ char line[512], *p;
+
+ p = fgets( line, sizeof(line), fp );
+ if ( !p ) continue;
+
+ /* terminate line at first whitespace */
+ for ( p = line; *p; p++ )
+ if ( isspace( *p ) ) {
+ *p = '\0';
+ break;
+ }
+
+ if ( ldap_url ) {
+ char *nu;
+
+ nu = ch_malloc( strlen( ldap_url ) + 2 + strlen( LDAP_PREFIX ) +
+ strlen( line ) );
+
+ if ( strstr( line, "://" ) ) {
+ sprintf( nu, "%s %s", ldap_url, line );
+ } else {
+ sprintf( nu, "%s %s%s", ldap_url, LDAP_PREFIX, line );
+ }
+ ch_free( ldap_url );
+ ldap_url = nu;
+ } else {
+ ldap_url = ch_malloc( 1 + strlen( line ) + strlen( LDAP_PREFIX ) );
+ if ( strstr( line, "://" ) ) {
+ strcpy( ldap_url, line );
+ } else {
+ sprintf( ldap_url, "%s%s", LDAP_PREFIX, line );
+ }
+ }
+ }
+
+ fclose( fp );
+
+ return ldap_url;
+}
+
+static void
+trace_remoteauth_parameters( ad_private *ap )
+{
+ ad_info *pad_info;
+ struct berval bv;
+
+ if ( !ap ) return;
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_dn_attribute: %s\n",
+ ap->dn ? ap->dn : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_domain_attribute: %s\n",
+ ap->domain_attr ? ap->domain_attr : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_default_realm: %s\n",
+ ap->default_realm ? ap->default_realm : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_default_domain: %s\n",
+ ap->default_domain ? ap->default_domain : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_retry_count: %d\n", ap->retry_count );
+
+ bindconf_tls_unparse( &ap->ad_tls, &bv );
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_tls:%s\n", bv.bv_val );
+ ch_free( bv.bv_val );
+
+ pad_info = ap->mappings;
+ while ( pad_info ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_mappings(%s,%s)\n",
+ pad_info->domain ? pad_info->domain : "NULL",
+ pad_info->realm ? pad_info->realm : "NULL" );
+ pad_info = pad_info->next;
+ }
+
+ return;
+}
+
+static int
+remoteauth_conn_cb(
+ LDAP *ld,
+ Sockbuf *sb,
+ LDAPURLDesc *srv,
+ struct sockaddr *addr,
+ struct ldap_conncb *ctx )
+{
+ ad_private *ap = ctx->lc_arg;
+ ad_pin *pin = NULL;
+ char *host;
+
+ host = srv->lud_host;
+ if ( !host || !*host ) {
+ host = "localhost";
+ }
+
+ for ( pin = ap->pins; pin; pin = pin->next ) {
+ if ( !strcasecmp( host, pin->hostname ) ) break;
+ }
+
+ if ( pin ) {
+ int rc = ldap_set_option( ld, LDAP_OPT_X_TLS_PEERKEY_HASH, pin->pin );
+ if ( rc == LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: "
+ "TLS Peerkey hash could not be set to '%s': %d\n",
+ pin->pin, rc );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: "
+ "No TLS Peerkey hash found for host '%s'\n",
+ host );
+ }
+
+ return -1;
+}
+
+static void
+remoteauth_conn_delcb( LDAP *ld, Sockbuf *sb, struct ldap_conncb *ctx )
+{
+ return;
+}
+
+static int
+remoteauth_bind( Operation *op, SlapReply *rs )
+{
+ Entry *e;
+ int rc;
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ ad_private *ap = (ad_private *)on->on_bi.bi_private;
+ Attribute *a_dom, *a_dn;
+ struct ldap_conncb ad_conncb = { .lc_add = remoteauth_conn_cb,
+ .lc_del = remoteauth_conn_delcb,
+ .lc_arg = ap };
+ struct berval dn = { 0 };
+ char *dom_val, *realm = NULL;
+ char *ldap_url = NULL;
+ LDAP *ld = NULL;
+ int protocol = LDAP_VERSION3, isfile = 0;
+ int tries = 0;
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ trace_remoteauth_parameters( ap );
+ }
+
+ if ( op->orb_method != LDAP_AUTH_SIMPLE )
+ return SLAP_CB_CONTINUE; /* only do password auth */
+
+ /* Can't handle root via this mechanism */
+ if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) return SLAP_CB_CONTINUE;
+
+ if ( !ap->up_set ) {
+ const char *txt = NULL;
+
+ if ( slap_str2ad( UP_STR, &ap->up_ad, &txt ) )
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "userPassword attr undefined: %s\n",
+ txt ? txt : "" );
+ ap->up_set = 1;
+ }
+
+ if ( !ap->up_ad ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "password attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* userPassword not defined */
+ }
+
+ if ( !ap->dn ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "remote DN attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* no mapped DN attribute */
+ }
+
+ if ( !ap->domain_attr ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "domain attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* no way to know domain */
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+
+ rc = SLAP_CB_CONTINUE;
+ /* if userPassword is defined in entry, skip to the end */
+ if ( attr_find( e->e_attrs, ap->up_ad ) ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "user has a password, skipping\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+ a_dom = attr_find( e->e_attrs, ap->domain_ad );
+ if ( !a_dom )
+ dom_val = ap->default_domain;
+ else {
+ dom_val = a_dom->a_vals[0].bv_val;
+ }
+
+ if ( !dom_val ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "user has no domain nor do we have a default, skipping\n",
+ op->o_log_prefix );
+ goto exit; /* user has no domain */
+ }
+
+ realm = get_realm( dom_val, ap->mappings, ap->default_realm, &isfile );
+ if ( !realm ) goto exit;
+
+ a_dn = attr_find( e->e_attrs, ap->dn_ad );
+ if ( !a_dn ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "no remote DN found on user\n",
+ op->o_log_prefix );
+ goto exit; /* user has no DN for the other directory */
+ }
+
+ ber_dupbv_x( &dn, a_dn->a_vals, op->o_tmpmemctx );
+ be_entry_release_r( op, e );
+ e = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "(realm, dn) = (%s, %s)\n",
+ op->o_log_prefix, realm, dn.bv_val );
+
+ ldap_url = get_ldap_url( realm, isfile );
+ if ( !ldap_url ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "No LDAP URL obtained\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+retry:
+ rc = ldap_initialize( &ld, ldap_url );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "Cannot initialize %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit; /* user has no DN for the other directory */
+ }
+
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol );
+
+#ifdef HAVE_TLS
+ rc = bindconf_tls_set( &ap->ad_tls, ld );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "bindconf_tls_set failed\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+ if ( ap->pins ) {
+ if ( (rc = ldap_set_option( ld, LDAP_OPT_CONNECT_CB, &ad_conncb )) !=
+ LDAP_SUCCESS ) {
+ goto exit;
+ }
+ }
+
+ if ( (rc = ldap_connect( ld )) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "Cannot connect to %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit;
+ }
+
+ if ( ap->ad_tls.sb_tls && !ldap_tls_inplace( ld ) ) {
+ if ( (rc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "LDAP TLS failed %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit;
+ }
+ }
+
+#endif /* HAVE_TLS */
+
+ rc = ldap_sasl_bind_s( ld, dn.bv_val, LDAP_SASL_SIMPLE,
+ &op->oq_bind.rb_cred, NULL, NULL, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( ap->store_on_success ) {
+ const char *txt;
+
+ Operation op2 = *op;
+ SlapReply r2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ Modifications m = {};
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_callback = &cb;
+ op2.orm_modlist = &m;
+ op2.orm_no_opattrs = 0;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ m.sml_op = LDAP_MOD_ADD;
+ m.sml_flags = 0;
+ m.sml_next = NULL;
+ m.sml_type = ap->up_ad->ad_cname;
+ m.sml_desc = ap->up_ad;
+ m.sml_numvals = 1;
+ m.sml_values = op->o_tmpcalloc(
+ sizeof(struct berval), 2, op->o_tmpmemctx );
+
+ slap_passwd_hash( &op->oq_bind.rb_cred, &m.sml_values[0], &txt );
+ if ( m.sml_values[0].bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: "
+ "password hashing for '%s' failed, storing password in "
+ "plain text\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+ ber_dupbv( &m.sml_values[0], &op->oq_bind.rb_cred );
+ }
+
+ /*
+ * If this server is a shadow use the frontend to perform this
+ * modify. That will trigger the update referral, which can then be
+ * forwarded by the chain overlay. Obviously the updateref and
+ * chain overlay must be configured appropriately for this to be
+ * useful.
+ */
+ if ( SLAP_SHADOW(op->o_bd) ) {
+ op2.o_bd = frontendDB;
+ } else {
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+ }
+
+ if ( op2.o_bd->be_modify( &op2, &r2 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: "
+ "attempt to store password in entry '%s' failed, "
+ "ignoring\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+ }
+ ch_free( m.sml_values[0].bv_val );
+ }
+ goto exit;
+ }
+
+ if ( rc == LDAP_INVALID_CREDENTIALS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "ldap_sasl_bind_s (%s) failed: invalid credentials\n",
+ op->o_log_prefix, ldap_url );
+ goto exit;
+ }
+
+ if ( tries < ap->retry_count ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "ldap_sasl_bind_s failed %s: %s (try #%d)\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ), tries );
+ if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL );
+ tries++;
+ goto retry;
+ } else
+ goto exit;
+
+exit:
+ if ( dn.bv_val ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( e ) {
+ be_entry_release_r( op, e );
+ }
+ if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL );
+ if ( ldap_url ) ch_free( ldap_url );
+ if ( realm ) ch_free( realm );
+ if ( rc == SLAP_CB_CONTINUE ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "continue\n", op->o_log_prefix );
+ return rc;
+ } else {
+ /* for rc == 0, frontend sends result */
+ if ( rc ) {
+ if ( rc > 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "failed\n", op->o_log_prefix );
+ send_ldap_error( op, rs, rc, "remoteauth_bind failed" );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "operations error\n", op->o_log_prefix );
+ send_ldap_error( op, rs, LDAP_OPERATIONS_ERROR,
+ "remoteauth_bind operations error" );
+ }
+ }
+
+ return rs->sr_err;
+ }
+}
+
+static int
+remoteauth_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ad_private *ap;
+
+ if ( SLAP_ISGLOBALOVERLAY(be) ) {
+ Debug( LDAP_DEBUG_ANY, "remoteauth_db_init: "
+ "remoteauth overlay must be instantiated within a "
+ "database.\n" );
+ return 1;
+ }
+
+ ap = ch_calloc( 1, sizeof(ad_private) );
+
+ ap->dn = NULL;
+ ap->dn_ad = NULL;
+ ap->domain_attr = NULL;
+ ap->domain_ad = NULL;
+
+ ap->up_ad = NULL;
+ ap->mappings = NULL;
+
+ ap->default_realm = NULL;
+ ap->default_domain = NULL;
+
+ ap->pins = NULL;
+
+ ap->up_set = 0;
+ ap->retry_count = 3;
+
+ on->on_bi.bi_private = ap;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+remoteauth_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ad_private *ap = (ad_private *)on->on_bi.bi_private;
+ ad_info *ai = ap->mappings;
+
+ while ( ai ) {
+ ad_info *next = ai->next;
+
+ if ( ai->domain ) ch_free( ai->domain );
+ if ( ai->realm ) ch_free( ai->realm );
+
+ ch_free( ai );
+ ai = next;
+ }
+
+ if ( ap->dn ) ch_free( ap->dn );
+ if ( ap->default_domain ) ch_free( ap->default_domain );
+ if ( ap->default_realm ) ch_free( ap->default_realm );
+ if ( ap->domain_attr ) ch_free( ap->domain_attr );
+
+ bindconf_free( &ap->ad_tls );
+
+ ch_free( ap );
+
+ return 0;
+}
+
+static slap_overinst remoteauth;
+
+int
+remoteauth_initialize( void )
+{
+ int rc;
+
+ remoteauth.on_bi.bi_type = "remoteauth";
+ remoteauth.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+
+ remoteauth.on_bi.bi_cf_ocs = remoteauthocs;
+ rc = config_register_schema( remoteauthcfg, remoteauthocs );
+ if ( rc ) return rc;
+
+ remoteauth.on_bi.bi_db_init = remoteauth_db_init;
+ remoteauth.on_bi.bi_db_destroy = remoteauth_db_destroy;
+ remoteauth.on_bi.bi_op_bind = remoteauth_bind;
+
+ return overlay_register( &remoteauth );
+}
+
+#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return remoteauth_initialize();
+}
+#endif
diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c
new file mode 100644
index 0000000..15052e0
--- /dev/null
+++ b/servers/slapd/overlays/retcode.c
@@ -0,0 +1,1577 @@
+/* retcode.c - customizable response for client testing purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RETCODE
+
+#include <stdio.h>
+
+#include <ac/unistd.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldif.h"
+
+static slap_overinst retcode;
+
+static AttributeDescription *ad_errCode;
+static AttributeDescription *ad_errText;
+static AttributeDescription *ad_errOp;
+static AttributeDescription *ad_errSleepTime;
+static AttributeDescription *ad_errMatchedDN;
+static AttributeDescription *ad_errUnsolicitedOID;
+static AttributeDescription *ad_errUnsolicitedData;
+static AttributeDescription *ad_errDisconnect;
+
+static ObjectClass *oc_errAbsObject;
+static ObjectClass *oc_errObject;
+static ObjectClass *oc_errAuxObject;
+
+typedef enum retcode_op_e {
+ SN_DG_OP_NONE = 0x0000,
+ SN_DG_OP_ADD = 0x0001,
+ SN_DG_OP_BIND = 0x0002,
+ SN_DG_OP_COMPARE = 0x0004,
+ SN_DG_OP_DELETE = 0x0008,
+ SN_DG_OP_MODIFY = 0x0010,
+ SN_DG_OP_RENAME = 0x0020,
+ SN_DG_OP_SEARCH = 0x0040,
+ SN_DG_EXTENDED = 0x0080,
+ SN_DG_OP_AUTH = SN_DG_OP_BIND,
+ SN_DG_OP_READ = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH),
+ SN_DG_OP_WRITE = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME),
+ SN_DG_OP_ALL = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED)
+} retcode_op_e;
+
+typedef struct retcode_item_t {
+ struct berval rdi_line;
+ struct berval rdi_dn;
+ struct berval rdi_ndn;
+ struct berval rdi_text;
+ struct berval rdi_matched;
+ int rdi_err;
+ BerVarray rdi_ref;
+ int rdi_sleeptime;
+ Entry rdi_e;
+ slap_mask_t rdi_mask;
+ struct berval rdi_unsolicited_oid;
+ struct berval rdi_unsolicited_data;
+
+ unsigned rdi_flags;
+#define RDI_PRE_DISCONNECT (0x1U)
+#define RDI_POST_DISCONNECT (0x2U)
+
+ struct retcode_item_t *rdi_next;
+} retcode_item_t;
+
+typedef struct retcode_t {
+ struct berval rd_pdn;
+ struct berval rd_npdn;
+
+ int rd_sleep;
+
+ retcode_item_t *rd_item;
+
+ int rd_indir;
+#define RETCODE_FINDIR 0x01
+#define RETCODE_INDIR( rd ) ( (rd)->rd_indir )
+} retcode_t;
+
+static int
+retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e );
+
+static unsigned int
+retcode_sleep( int s )
+{
+ unsigned int r = 0;
+
+ /* sleep as required */
+ if ( s < 0 ) {
+#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */
+ r = rand() % (-s);
+#endif
+ r = ((double)(-s))*rand()/(RAND_MAX + 1.0);
+ } else if ( s > 0 ) {
+ r = (unsigned int)s;
+ }
+ if ( r ) {
+ sleep( r );
+ }
+
+ return r;
+}
+
+static int
+retcode_cleanup_cb( Operation *op, SlapReply *rs )
+{
+ rs->sr_matched = NULL;
+ rs->sr_text = NULL;
+
+ if ( rs->sr_ref != NULL ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ ch_free( op->o_callback );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_send_onelevel( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ retcode_item_t *rdi;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
+ if ( op->o_abandon ) {
+ return rs->sr_err = SLAPD_ABANDON;
+ }
+
+ rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter );
+ if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ /* safe default */
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_ctrls = NULL;
+ rs->sr_flags = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_entry = &rdi->rdi_e;
+
+ rs->sr_err = send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+
+ switch ( rs->sr_err ) {
+ case LDAP_UNAVAILABLE: /* connection closed */
+ rs->sr_err = LDAP_OTHER;
+ /* fallthru */
+ case LDAP_SIZELIMIT_EXCEEDED:
+ goto done;
+ }
+ }
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+done:;
+
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+}
+
+static int
+retcode_op_add( Operation *op, SlapReply *rs )
+{
+ return retcode_entry_response( op, rs, NULL, op->ora_e );
+}
+
+typedef struct retcode_cb_t {
+ BackendInfo *rdc_info;
+ unsigned rdc_flags;
+ ber_tag_t rdc_tag;
+ AttributeName *rdc_attrs;
+} retcode_cb_t;
+
+static int
+retcode_cb_response( Operation *op, SlapReply *rs )
+{
+ retcode_cb_t *rdc = (retcode_cb_t *)op->o_callback->sc_private;
+
+ op->o_tag = rdc->rdc_tag;
+ if ( rs->sr_type == REP_SEARCH ) {
+ ber_tag_t o_tag = op->o_tag;
+ int rc;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ rs->sr_attrs = rdc->rdc_attrs;
+ }
+ rc = retcode_entry_response( op, rs, rdc->rdc_info, rs->sr_entry );
+ op->o_tag = o_tag;
+
+ return rc;
+ }
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ case LDAP_NO_SUCH_OBJECT:
+ /* in case of noSuchObject, stop the internal search
+ * for in-directory error stuff */
+ if ( !op->o_abandon ) {
+ rdc->rdc_flags = SLAP_CB_CONTINUE;
+ }
+ return 0;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_op_internal( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ Operation op2 = *op;
+ BackendDB db = *op->o_bd;
+ slap_callback sc = { 0 };
+ retcode_cb_t rdc;
+
+ int rc;
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_limit = NULL;
+ op2.ors_attrsonly = 0;
+ op2.ors_attrs = slap_anlist_all_attributes;
+
+ ber_str2bv_x( "(objectClass=errAbsObject)",
+ STRLENOF( "(objectClass=errAbsObject)" ),
+ 1, &op2.ors_filterstr, op2.o_tmpmemctx );
+ op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val );
+
+ /* errAbsObject is defined by this overlay! */
+ assert( op2.ors_filter != NULL );
+
+ db.bd_info = on->on_info->oi_orig;
+ op2.o_bd = &db;
+
+ rdc.rdc_info = on->on_info->oi_orig;
+ rdc.rdc_flags = RETCODE_FINDIR;
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ rdc.rdc_attrs = op->ors_attrs;
+ }
+ rdc.rdc_tag = op->o_tag;
+ sc.sc_response = retcode_cb_response;
+ sc.sc_private = &rdc;
+ op2.o_callback = &sc;
+
+ rc = op2.o_bd->be_search( &op2, rs );
+ op->o_abandon = op2.o_abandon;
+
+ filter_free_x( &op2, op2.ors_filter, 1 );
+ ber_memfree_x( op2.ors_filterstr.bv_val, op2.o_tmpmemctx );
+
+ if ( rdc.rdc_flags == SLAP_CB_CONTINUE ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ return rc;
+}
+
+static int
+retcode_op_func( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ retcode_item_t *rdi;
+ struct berval nrdn, npdn;
+
+ slap_callback *cb = NULL;
+
+ /* sleep as required */
+ retcode_sleep( rd->rd_sleep );
+
+ if ( !dnIsSuffix( &op->o_req_ndn, &rd->rd_npdn ) ) {
+ if ( RETCODE_INDIR( rd ) ) {
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ return retcode_op_add( op, rs );
+
+ case LDAP_REQ_BIND:
+ /* skip if rootdn */
+ /* FIXME: better give the db a chance? */
+ if ( be_isroot_pw( op ) ) {
+ return LDAP_SUCCESS;
+ }
+ return retcode_op_internal( op, rs );
+
+ case LDAP_REQ_SEARCH:
+ if ( op->ors_scope == LDAP_SCOPE_BASE ) {
+ rs->sr_err = retcode_op_internal( op, rs );
+ switch ( rs->sr_err ) {
+ case SLAP_CB_CONTINUE:
+ if ( rs->sr_nentries == 0 ) {
+ break;
+ }
+ rs->sr_err = LDAP_SUCCESS;
+ /* fallthru */
+
+ default:
+ send_ldap_result( op, rs );
+ break;
+ }
+ return rs->sr_err;
+ }
+ break;
+
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_COMPARE:
+ return retcode_op_internal( op, rs );
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( op->o_tag == LDAP_REQ_SEARCH
+ && op->ors_scope != LDAP_SCOPE_BASE
+ && op->o_req_ndn.bv_len == rd->rd_npdn.bv_len )
+ {
+ return retcode_send_onelevel( op, rs );
+ }
+
+ dnParent( &op->o_req_ndn, &npdn );
+ if ( npdn.bv_len != rd->rd_npdn.bv_len ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_matched = rd->rd_pdn.bv_val;
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ return rs->sr_err;
+ }
+
+ dnRdn( &op->o_req_ndn, &nrdn );
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
+ struct berval rdi_nrdn;
+
+ dnRdn( &rdi->rdi_ndn, &rdi_nrdn );
+ if ( dn_match( &nrdn, &rdi_nrdn ) ) {
+ break;
+ }
+ }
+
+ if ( rdi != NULL && rdi->rdi_mask != SN_DG_OP_ALL ) {
+ retcode_op_e o_tag = SN_DG_OP_NONE;
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ o_tag = SN_DG_OP_ADD;
+ break;
+
+ case LDAP_REQ_BIND:
+ o_tag = SN_DG_OP_BIND;
+ break;
+
+ case LDAP_REQ_COMPARE:
+ o_tag = SN_DG_OP_COMPARE;
+ break;
+
+ case LDAP_REQ_DELETE:
+ o_tag = SN_DG_OP_DELETE;
+ break;
+
+ case LDAP_REQ_MODIFY:
+ o_tag = SN_DG_OP_MODIFY;
+ break;
+
+ case LDAP_REQ_MODRDN:
+ o_tag = SN_DG_OP_RENAME;
+ break;
+
+ case LDAP_REQ_SEARCH:
+ o_tag = SN_DG_OP_SEARCH;
+ break;
+
+ case LDAP_REQ_EXTENDED:
+ o_tag = SN_DG_EXTENDED;
+ break;
+
+ default:
+ /* Should not happen */
+ break;
+ }
+
+ if ( !( o_tag & rdi->rdi_mask ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+ }
+
+ if ( rdi == NULL ) {
+ rs->sr_matched = rd->rd_pdn.bv_val;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "retcode not found";
+
+ } else {
+ if ( rdi->rdi_flags & RDI_PRE_DISCONNECT ) {
+ return rs->sr_err = SLAPD_DISCONNECT;
+ }
+
+ rs->sr_err = rdi->rdi_err;
+ rs->sr_text = rdi->rdi_text.bv_val;
+ rs->sr_matched = rdi->rdi_matched.bv_val;
+
+ /* FIXME: we only honor the rdi_ref field in case rdi_err
+ * is LDAP_REFERRAL otherwise send_ldap_result() bails out */
+ if ( rs->sr_err == LDAP_REFERRAL ) {
+ BerVarray ref;
+
+ if ( rdi->rdi_ref != NULL ) {
+ ref = rdi->rdi_ref;
+ } else {
+ ref = default_referral;
+ }
+
+ if ( ref != NULL ) {
+ rs->sr_ref = referral_rewrite( ref,
+ NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "bad referral object";
+ }
+ }
+
+ retcode_sleep( rdi->rdi_sleeptime );
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_EXTENDED:
+ if ( rdi == NULL ) {
+ break;
+ }
+ cb = ( slap_callback * )ch_malloc( sizeof( slap_callback ) );
+ memset( cb, 0, sizeof( slap_callback ) );
+ cb->sc_cleanup = retcode_cleanup_cb;
+ op->o_callback = cb;
+ break;
+
+ default:
+ if ( rdi && !BER_BVISNULL( &rdi->rdi_unsolicited_oid ) ) {
+ ber_int_t msgid = op->o_msgid;
+
+ /* RFC 4511 unsolicited response */
+
+ op->o_msgid = 0;
+ if ( strcmp( rdi->rdi_unsolicited_oid.bv_val, "0" ) == 0 ) {
+ send_ldap_result( op, rs );
+
+ } else {
+ ber_tag_t tag = op->o_tag;
+
+ op->o_tag = LDAP_REQ_EXTENDED;
+ rs->sr_rspoid = rdi->rdi_unsolicited_oid.bv_val;
+ if ( !BER_BVISNULL( &rdi->rdi_unsolicited_data ) ) {
+ rs->sr_rspdata = &rdi->rdi_unsolicited_data;
+ }
+ send_ldap_extended( op, rs );
+ rs->sr_rspoid = NULL;
+ rs->sr_rspdata = NULL;
+ op->o_tag = tag;
+
+ }
+ op->o_msgid = msgid;
+
+ } else {
+ send_ldap_result( op, rs );
+ }
+
+ if ( rs->sr_ref != NULL ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ rs->sr_matched = NULL;
+ rs->sr_text = NULL;
+
+ if ( rdi && rdi->rdi_flags & RDI_POST_DISCONNECT ) {
+ return rs->sr_err = SLAPD_DISCONNECT;
+ }
+ break;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+retcode_op2str( ber_tag_t op, struct berval *bv )
+{
+ switch ( op ) {
+ case LDAP_REQ_BIND:
+ BER_BVSTR( bv, "bind" );
+ return 0;
+ case LDAP_REQ_ADD:
+ BER_BVSTR( bv, "add" );
+ return 0;
+ case LDAP_REQ_DELETE:
+ BER_BVSTR( bv, "delete" );
+ return 0;
+ case LDAP_REQ_MODRDN:
+ BER_BVSTR( bv, "modrdn" );
+ return 0;
+ case LDAP_REQ_MODIFY:
+ BER_BVSTR( bv, "modify" );
+ return 0;
+ case LDAP_REQ_COMPARE:
+ BER_BVSTR( bv, "compare" );
+ return 0;
+ case LDAP_REQ_SEARCH:
+ BER_BVSTR( bv, "search" );
+ return 0;
+ case LDAP_REQ_EXTENDED:
+ BER_BVSTR( bv, "extended" );
+ return 0;
+ }
+ return -1;
+}
+
+static int
+retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e )
+{
+ Attribute *a;
+ int err;
+ char *next;
+ int disconnect = 0;
+
+ if ( get_manageDSAit( op ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( !is_entry_objectclass_or_sub( e, oc_errAbsObject ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* operation */
+ a = attr_find( e->e_attrs, ad_errOp );
+ if ( a != NULL ) {
+ int i,
+ gotit = 0;
+ struct berval bv = BER_BVNULL;
+
+ (void)retcode_op2str( op->o_tag, &bv );
+
+ if ( BER_BVISNULL( &bv ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ if ( bvmatch( &a->a_nvals[ i ], &bv ) ) {
+ gotit = 1;
+ break;
+ }
+ }
+
+ if ( !gotit ) {
+ return SLAP_CB_CONTINUE;
+ }
+ }
+
+ /* disconnect */
+ a = attr_find( e->e_attrs, ad_errDisconnect );
+ if ( a != NULL ) {
+ if ( bvmatch( &a->a_nvals[ 0 ], &slap_true_bv ) ) {
+ return rs->sr_err = SLAPD_DISCONNECT;
+ }
+ disconnect = 1;
+ }
+
+ /* error code */
+ a = attr_find( e->e_attrs, ad_errCode );
+ if ( a == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+ err = strtol( a->a_nvals[ 0 ].bv_val, &next, 0 );
+ if ( next == a->a_nvals[ 0 ].bv_val || next[ 0 ] != '\0' ) {
+ return SLAP_CB_CONTINUE;
+ }
+ rs->sr_err = err;
+
+ /* sleep time */
+ a = attr_find( e->e_attrs, ad_errSleepTime );
+ if ( a != NULL && a->a_nvals[ 0 ].bv_val[ 0 ] != '-' ) {
+ int sleepTime;
+
+ if ( lutil_atoi( &sleepTime, a->a_nvals[ 0 ].bv_val ) == 0 ) {
+ retcode_sleep( sleepTime );
+ }
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS && !LDAP_API_ERROR( rs->sr_err )) {
+ BackendDB db = *op->o_bd,
+ *o_bd = op->o_bd;
+ void *o_callback = op->o_callback;
+
+ /* message text */
+ a = attr_find( e->e_attrs, ad_errText );
+ if ( a != NULL ) {
+ rs->sr_text = a->a_vals[ 0 ].bv_val;
+ }
+
+ /* matched DN */
+ a = attr_find( e->e_attrs, ad_errMatchedDN );
+ if ( a != NULL ) {
+ rs->sr_matched = a->a_vals[ 0 ].bv_val;
+ }
+
+ if ( bi == NULL ) {
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ bi = on->on_info->oi_orig;
+ }
+
+ db.bd_info = bi;
+ op->o_bd = &db;
+ op->o_callback = NULL;
+
+ /* referral */
+ if ( rs->sr_err == LDAP_REFERRAL ) {
+ BerVarray refs = default_referral;
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_ref );
+ if ( a != NULL ) {
+ refs = a->a_vals;
+ }
+ rs->sr_ref = referral_rewrite( refs,
+ NULL, &op->o_req_dn, op->oq_search.rs_scope );
+
+ send_search_reference( op, rs );
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+
+ } else {
+ a = attr_find( e->e_attrs, ad_errUnsolicitedOID );
+ if ( a != NULL ) {
+ struct berval oid = BER_BVNULL,
+ data = BER_BVNULL;
+ ber_int_t msgid = op->o_msgid;
+
+ /* RFC 4511 unsolicited response */
+
+ op->o_msgid = 0;
+
+ oid = a->a_nvals[ 0 ];
+
+ a = attr_find( e->e_attrs, ad_errUnsolicitedData );
+ if ( a != NULL ) {
+ data = a->a_nvals[ 0 ];
+ }
+
+ if ( strcmp( oid.bv_val, "0" ) == 0 ) {
+ send_ldap_result( op, rs );
+
+ } else {
+ ber_tag_t tag = op->o_tag;
+
+ op->o_tag = LDAP_REQ_EXTENDED;
+ rs->sr_rspoid = oid.bv_val;
+ if ( !BER_BVISNULL( &data ) ) {
+ rs->sr_rspdata = &data;
+ }
+ send_ldap_extended( op, rs );
+ rs->sr_rspoid = NULL;
+ rs->sr_rspdata = NULL;
+ op->o_tag = tag;
+ }
+ op->o_msgid = msgid;
+
+ } else {
+ send_ldap_result( op, rs );
+ }
+ }
+
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ op->o_bd = o_bd;
+ op->o_callback = o_callback;
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( disconnect ) {
+ return rs->sr_err = SLAPD_DISCONNECT;
+ }
+
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ if ( rs->sr_type != REP_SEARCH || !RETCODE_INDIR( rd ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ return retcode_entry_response( op, rs, NULL, rs->sr_entry );
+}
+
+static int
+retcode_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd;
+
+ srand( getpid() );
+
+ rd = (retcode_t *)ch_malloc( sizeof( retcode_t ) );
+ memset( rd, 0, sizeof( retcode_t ) );
+
+ on->on_bi.bi_private = (void *)rd;
+
+ return 0;
+}
+
+static void
+retcode_item_destroy( retcode_item_t *rdi )
+{
+ ber_memfree( rdi->rdi_line.bv_val );
+
+ ber_memfree( rdi->rdi_dn.bv_val );
+ ber_memfree( rdi->rdi_ndn.bv_val );
+
+ if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+ ber_memfree( rdi->rdi_text.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
+ ber_memfree( rdi->rdi_matched.bv_val );
+ }
+
+ if ( rdi->rdi_ref ) {
+ ber_bvarray_free( rdi->rdi_ref );
+ }
+
+ BER_BVZERO( &rdi->rdi_e.e_name );
+ BER_BVZERO( &rdi->rdi_e.e_nname );
+
+ entry_clean( &rdi->rdi_e );
+
+ if ( !BER_BVISNULL( &rdi->rdi_unsolicited_oid ) ) {
+ ber_memfree( rdi->rdi_unsolicited_oid.bv_val );
+ if ( !BER_BVISNULL( &rdi->rdi_unsolicited_data ) )
+ ber_memfree( rdi->rdi_unsolicited_data.bv_val );
+ }
+
+ ch_free( rdi );
+}
+
+enum {
+ RC_PARENT = 1,
+ RC_ITEM
+};
+
+static ConfigDriver rc_cf_gen;
+
+static ConfigTable rccfg[] = {
+ { "retcode-parent", "dn",
+ 2, 2, 0, ARG_MAGIC|ARG_DN|ARG_QUOTE|RC_PARENT, rc_cf_gen,
+ "( OLcfgOvAt:20.1 NAME 'olcRetcodeParent' "
+ "DESC '' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "retcode-item", "rdn> <retcode> <...",
+ 3, 0, 0, ARG_MAGIC|RC_ITEM, rc_cf_gen,
+ "( OLcfgOvAt:20.2 NAME 'olcRetcodeItem' "
+ "DESC '' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )", NULL, NULL },
+ { "retcode-indir", "on|off",
+ 1, 2, 0, ARG_OFFSET|ARG_ON_OFF,
+ (void *)offsetof(retcode_t, rd_indir),
+ "( OLcfgOvAt:20.3 NAME 'olcRetcodeInDir' "
+ "DESC '' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+
+ { "retcode-sleep", "sleeptime",
+ 2, 2, 0, ARG_OFFSET|ARG_INT,
+ (void *)offsetof(retcode_t, rd_sleep),
+ "( OLcfgOvAt:20.4 NAME 'olcRetcodeSleep' "
+ "DESC '' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs rcocs[] = {
+ { "( OLcfgOvOc:20.1 "
+ "NAME 'olcRetcodeConfig' "
+ "DESC 'Retcode configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcRetcodeParent "
+ "$ olcRetcodeItem "
+ "$ olcRetcodeInDir "
+ "$ olcRetcodeSleep "
+ ") )",
+ Cft_Overlay, rccfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+rc_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( !BER_BVISEMPTY( &rd->rd_pdn )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &rd->rd_pdn );
+ if ( rc == 0 ) {
+ rc = value_add_one( &c->rvalue_nvals,
+ &rd->rd_npdn );
+ }
+ return rc;
+ }
+ rc = 0;
+ break;
+
+ case RC_ITEM: {
+ retcode_item_t *rdi;
+ int i;
+
+ for ( rdi = rd->rd_item, i = 0; rdi; rdi = rdi->rdi_next, i++ ) {
+ char buf[4096];
+ struct berval bv;
+ char *ptr;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len += rdi->rdi_line.bv_len;
+ ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, rdi->rdi_line.bv_val, rdi->rdi_line.bv_len );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ } break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( rd->rd_pdn.bv_val ) {
+ ber_memfree ( rd->rd_pdn.bv_val );
+ rc = 0;
+ }
+ if ( rd->rd_npdn.bv_val ) {
+ ber_memfree ( rd->rd_npdn.bv_val );
+ }
+ break;
+
+ case RC_ITEM:
+ if ( c->valx == -1 ) {
+ retcode_item_t *rdi, *next;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
+ next = rdi->rdi_next;
+ retcode_item_destroy( rdi );
+ }
+
+ } else {
+ retcode_item_t **rdip, *rdi;
+ int i;
+
+ for ( rdip = &rd->rd_item, i = 0; i <= c->valx && *rdip; i++, rdip = &(*rdip)->rdi_next )
+ ;
+ if ( *rdip == NULL ) {
+ return 1;
+ }
+ rdi = *rdip;
+ *rdip = rdi->rdi_next;
+
+ retcode_item_destroy( rdi );
+ }
+ rc = 0;
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ return rc; /* FIXME */
+ }
+
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( rd->rd_pdn.bv_val ) {
+ ber_memfree ( rd->rd_pdn.bv_val );
+ }
+ if ( rd->rd_npdn.bv_val ) {
+ ber_memfree ( rd->rd_npdn.bv_val );
+ }
+ rd->rd_pdn = c->value_dn;
+ rd->rd_npdn = c->value_ndn;
+ rc = 0;
+ break;
+
+ case RC_ITEM: {
+ retcode_item_t rdi = { BER_BVNULL }, **rdip;
+ struct berval bv, rdn, nrdn;
+ char *next = NULL;
+ int i;
+
+ if ( c->argc < 3 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"retcode-item <RDN> <retcode> [<text>]\": "
+ "missing args" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ ber_str2bv( c->argv[ 1 ], 0, 0, &bv );
+
+ rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to normalize RDN \"%s\": %d",
+ c->argv[ 1 ], rc );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ if ( !dnIsOneLevelRDN( &nrdn ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "value \"%s\" is not a RDN",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ if ( BER_BVISNULL( &rd->rd_npdn ) ) {
+ /* FIXME: we use the database suffix */
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "either \"retcode-parent\" "
+ "or \"suffix\" must be defined" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ ber_dupbv( &rd->rd_pdn, &c->be->be_suffix[ 0 ] );
+ ber_dupbv( &rd->rd_npdn, &c->be->be_nsuffix[ 0 ] );
+ }
+
+ build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL );
+ build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL );
+
+ ch_free( rdn.bv_val );
+ ch_free( nrdn.bv_val );
+
+ rdi.rdi_err = strtol( c->argv[ 2 ], &next, 0 );
+ if ( next == c->argv[ 2 ] || next[ 0 ] != '\0' ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse return code \"%s\"",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ rdi.rdi_mask = SN_DG_OP_ALL;
+
+ if ( c->argc > 3 ) {
+ for ( i = 3; i < c->argc; i++ ) {
+ if ( strncasecmp( c->argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 )
+ {
+ char **ops;
+ int j;
+
+ ops = ldap_str2charray( &c->argv[ i ][ STRLENOF( "op=" ) ], "," );
+ assert( ops != NULL );
+
+ rdi.rdi_mask = SN_DG_OP_NONE;
+
+ for ( j = 0; ops[ j ] != NULL; j++ ) {
+ if ( strcasecmp( ops[ j ], "add" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_ADD;
+
+ } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_BIND;
+
+ } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_COMPARE;
+
+ } else if ( strcasecmp( ops[ j ], "delete" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_DELETE;
+
+ } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_MODIFY;
+
+ } else if ( strcasecmp( ops[ j ], "rename" ) == 0
+ || strcasecmp( ops[ j ], "modrdn" ) == 0 )
+ {
+ rdi.rdi_mask |= SN_DG_OP_RENAME;
+
+ } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_SEARCH;
+
+ } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_EXTENDED;
+
+ } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_AUTH;
+
+ } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_READ;
+
+ } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_WRITE;
+
+ } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_ALL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown op \"%s\"",
+ ops[ j ] );
+ ldap_charray_free( ops );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ }
+
+ ldap_charray_free( ops );
+
+ } else if ( strncasecmp( c->argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 )
+ {
+ if ( !BER_BVISNULL( &rdi.rdi_text ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"text\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text );
+
+ } else if ( strncasecmp( c->argv[ i ], "matched=", STRLENOF( "matched=" ) ) == 0 )
+ {
+ struct berval dn;
+
+ if ( !BER_BVISNULL( &rdi.rdi_matched ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"matched\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "matched=" ) ], 0, 0, &dn );
+ if ( dnPretty( NULL, &dn, &rdi.rdi_matched, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to prettify matched DN \"%s\"",
+ &c->argv[ i ][ STRLENOF( "matched=" ) ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 )
+ {
+ char **refs;
+ int j;
+
+ if ( rdi.rdi_ref != NULL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"ref\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ if ( rdi.rdi_err != LDAP_REFERRAL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "providing \"ref\" "
+ "along with a non-referral "
+ "resultCode may cause slapd failures "
+ "related to internal checks" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ }
+
+ refs = ldap_str2charray( &c->argv[ i ][ STRLENOF( "ref=" ) ], " " );
+ assert( refs != NULL );
+
+ for ( j = 0; refs[ j ] != NULL; j++ ) {
+ struct berval bv;
+
+ ber_str2bv( refs[ j ], 0, 1, &bv );
+ ber_bvarray_add( &rdi.rdi_ref, &bv );
+ }
+
+ ldap_charray_free( refs );
+
+ } else if ( strncasecmp( c->argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 )
+ {
+ if ( rdi.rdi_sleeptime != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"sleeptime\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ if ( lutil_atoi( &rdi.rdi_sleeptime, &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse \"sleeptime=%s\"",
+ &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "unsolicited=", STRLENOF( "unsolicited=" ) ) == 0 )
+ {
+ char *data;
+
+ if ( !BER_BVISNULL( &rdi.rdi_unsolicited_oid ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"unsolicited\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ data = strchr( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], ':' );
+ if ( data != NULL ) {
+ struct berval oid;
+
+ if ( ldif_parse_line2( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ],
+ &oid, &rdi.rdi_unsolicited_data, NULL ) )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse \"unsolicited\"" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ ber_dupbv( &rdi.rdi_unsolicited_oid, &oid );
+
+ } else {
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], 0, 1,
+ &rdi.rdi_unsolicited_oid );
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 )
+ {
+ char *arg = &c->argv[ i ][ STRLENOF( "flags=" ) ];
+ if ( strcasecmp( arg, "disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_PRE_DISCONNECT;
+
+ } else if ( strcasecmp( arg, "pre-disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_PRE_DISCONNECT;
+
+ } else if ( strcasecmp( arg, "post-disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_POST_DISCONNECT;
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown flag \"%s\"", arg );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown option \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ }
+ }
+
+ rdi.rdi_line.bv_len = 2*(c->argc - 1) + c->argc - 2;
+ for ( i = 1; i < c->argc; i++ ) {
+ rdi.rdi_line.bv_len += strlen( c->argv[ i ] );
+ }
+ next = rdi.rdi_line.bv_val = ch_malloc( rdi.rdi_line.bv_len + 1 );
+
+ for ( i = 1; i < c->argc; i++ ) {
+ *next++ = '"';
+ next = lutil_strcopy( next, c->argv[ i ] );
+ *next++ = '"';
+ *next++ = ' ';
+ }
+ *--next = '\0';
+
+ /* We're marked X-ORDERED 'VALUES', valx might be valid */
+ for ( i = 0, rdip = &rd->rd_item;
+ *rdip && (c->valx < 0 || i < c->valx);
+ rdip = &(*rdip)->rdi_next, i++ )
+ /* go to position */ ;
+
+
+ rdi.rdi_next = *rdip;
+ *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) );
+ *(*rdip) = rdi;
+
+ rc = 0;
+ } break;
+
+ default:
+ rc = SLAP_CONF_UNKNOWN;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+retcode_db_open( BackendDB *be, ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ retcode_item_t *rdi;
+
+ for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) {
+ LDAPRDN rdn = NULL;
+ int rc, j;
+ char* p;
+ struct berval val[ 3 ];
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ /* DN */
+ rdi->rdi_e.e_name = rdi->rdi_dn;
+ rdi->rdi_e.e_nname = rdi->rdi_ndn;
+
+ /* objectClass */
+ val[ 0 ] = oc_errObject->soc_cname;
+ val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname;
+ BER_BVZERO( &val[ 2 ] );
+
+ attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL );
+
+ /* RDN avas */
+ rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p,
+ LDAP_DN_FORMAT_LDAP );
+
+ assert( rc == LDAP_SUCCESS );
+
+ for ( j = 0; rdn[ j ]; j++ ) {
+ LDAPAVA *ava = rdn[ j ];
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ assert( rc == LDAP_SUCCESS );
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad,
+ &ava->la_value, NULL );
+ }
+
+ ldap_rdnfree( rdn );
+
+ /* error code */
+ snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err );
+ ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+ attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL );
+
+ if ( rdi->rdi_ref != NULL ) {
+ attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref,
+ rdi->rdi_ref, NULL );
+ }
+
+ /* text */
+ if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+ val[ 0 ] = rdi->rdi_text;
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL );
+ }
+
+ /* matched */
+ if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
+ val[ 0 ] = rdi->rdi_matched;
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errMatchedDN, &val[ 0 ], NULL );
+ }
+
+ /* sleep time */
+ if ( rdi->rdi_sleeptime ) {
+ snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime );
+ ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+ attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL );
+ }
+
+ /* operations */
+ if ( rdi->rdi_mask & SN_DG_OP_ADD ) {
+ BER_BVSTR( &val[ 0 ], "add" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_BIND ) {
+ BER_BVSTR( &val[ 0 ], "bind" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) {
+ BER_BVSTR( &val[ 0 ], "compare" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_DELETE ) {
+ BER_BVSTR( &val[ 0 ], "delete" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_EXTENDED ) {
+ BER_BVSTR( &val[ 0 ], "extended" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) {
+ BER_BVSTR( &val[ 0 ], "modify" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_RENAME ) {
+ BER_BVSTR( &val[ 0 ], "rename" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) {
+ BER_BVSTR( &val[ 0 ], "search" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+ }
+
+ return 0;
+}
+
+static int
+retcode_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ if ( rd ) {
+ retcode_item_t *rdi, *next;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
+ next = rdi->rdi_next;
+ retcode_item_destroy( rdi );
+ }
+
+ if ( !BER_BVISNULL( &rd->rd_pdn ) ) {
+ ber_memfree( rd->rd_pdn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &rd->rd_npdn ) ) {
+ ber_memfree( rd->rd_npdn.bv_val );
+ }
+
+ ber_memfree( rd );
+ }
+
+ return 0;
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+int
+retcode_initialize( void )
+{
+ int i, code;
+
+ static struct {
+ char *desc;
+ AttributeDescription **ad;
+ } retcode_at[] = {
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.1 "
+ "NAME ( 'errCode' ) "
+ "DESC 'LDAP error code' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_errCode },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.2 "
+ "NAME ( 'errOp' ) "
+ "DESC 'Operations the errObject applies to' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ &ad_errOp},
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.3 "
+ "NAME ( 'errText' ) "
+ "DESC 'LDAP error textual description' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "SINGLE-VALUE )",
+ &ad_errText },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.4 "
+ "NAME ( 'errSleepTime' ) "
+ "DESC 'Time to wait before returning the error' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_errSleepTime },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.5 "
+ "NAME ( 'errMatchedDN' ) "
+ "DESC 'Value to be returned as matched DN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE )",
+ &ad_errMatchedDN },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.6 "
+ "NAME ( 'errUnsolicitedOID' ) "
+ "DESC 'OID to be returned within unsolicited response' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 "
+ "SINGLE-VALUE )",
+ &ad_errUnsolicitedOID },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.7 "
+ "NAME ( 'errUnsolicitedData' ) "
+ "DESC 'Data to be returned within unsolicited response' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "SINGLE-VALUE )",
+ &ad_errUnsolicitedData },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.8 "
+ "NAME ( 'errDisconnect' ) "
+ "DESC 'Disconnect without notice' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_errDisconnect },
+ { NULL }
+ };
+
+ static struct {
+ char *desc;
+ ObjectClass **oc;
+ } retcode_oc[] = {
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.0 "
+ "NAME ( 'errAbsObject' ) "
+ "SUP top ABSTRACT "
+ "MUST ( errCode ) "
+ "MAY ( "
+ "cn "
+ "$ description "
+ "$ errOp "
+ "$ errText "
+ "$ errSleepTime "
+ "$ errMatchedDN "
+ "$ errUnsolicitedOID "
+ "$ errUnsolicitedData "
+ "$ errDisconnect "
+ ") )",
+ &oc_errAbsObject },
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.1 "
+ "NAME ( 'errObject' ) "
+ "SUP errAbsObject STRUCTURAL "
+ ")",
+ &oc_errObject },
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.2 "
+ "NAME ( 'errAuxObject' ) "
+ "SUP errAbsObject AUXILIARY "
+ ")",
+ &oc_errAuxObject },
+ { NULL }
+ };
+
+
+ for ( i = 0; retcode_at[ i ].desc != NULL; i++ ) {
+ code = register_at( retcode_at[ i ].desc, retcode_at[ i ].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "retcode: register_at failed\n" );
+ return code;
+ }
+
+ (*retcode_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+
+ for ( i = 0; retcode_oc[ i ].desc != NULL; i++ ) {
+ code = register_oc( retcode_oc[ i ].desc, retcode_oc[ i ].oc, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "retcode: register_oc failed\n" );
+ return code;
+ }
+
+ (*retcode_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+
+ retcode.on_bi.bi_type = "retcode";
+
+ retcode.on_bi.bi_db_init = retcode_db_init;
+ retcode.on_bi.bi_db_open = retcode_db_open;
+ retcode.on_bi.bi_db_destroy = retcode_db_destroy;
+
+ retcode.on_bi.bi_op_add = retcode_op_func;
+ retcode.on_bi.bi_op_bind = retcode_op_func;
+ retcode.on_bi.bi_op_compare = retcode_op_func;
+ retcode.on_bi.bi_op_delete = retcode_op_func;
+ retcode.on_bi.bi_op_modify = retcode_op_func;
+ retcode.on_bi.bi_op_modrdn = retcode_op_func;
+ retcode.on_bi.bi_op_search = retcode_op_func;
+
+ retcode.on_bi.bi_extended = retcode_op_func;
+
+ retcode.on_response = retcode_response;
+
+ retcode.on_bi.bi_cf_ocs = rcocs;
+
+ code = config_register_schema( rccfg, rcocs );
+ if ( code ) {
+ return code;
+ }
+
+ return overlay_register( &retcode );
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return retcode_initialize();
+}
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RETCODE */
diff --git a/servers/slapd/overlays/rwm.c b/servers/slapd/overlays/rwm.c
new file mode 100644
index 0000000..af10f6d
--- /dev/null
+++ b/servers/slapd/overlays/rwm.c
@@ -0,0 +1,2768 @@
+/* rwm.c - rewrite/remap operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "rwm.h"
+
+typedef struct rwm_op_state {
+ ber_tag_t r_tag;
+ struct berval ro_dn;
+ struct berval ro_ndn;
+ struct berval r_dn;
+ struct berval r_ndn;
+ struct berval rx_dn;
+ struct berval rx_ndn;
+ AttributeName *mapped_attrs;
+ OpRequest o_request;
+} rwm_op_state;
+
+typedef struct rwm_op_cb {
+ slap_callback cb;
+ rwm_op_state ros;
+} rwm_op_cb;
+
+static int
+rwm_db_destroy( BackendDB *be, ConfigReply *cr );
+
+static int
+rwm_send_entry( Operation *op, SlapReply *rs );
+
+static void
+rwm_op_rollback( Operation *op, SlapReply *rs, rwm_op_state *ros )
+{
+ /* in case of successful extended operation cleanup
+ * gets called *after* (ITS#6632); this hack counts
+ * on others to cleanup our o_req_dn/o_req_ndn,
+ * while we cleanup theirs. */
+ if ( ros->r_tag == LDAP_REQ_EXTENDED && rs->sr_err == LDAP_SUCCESS ) {
+ if ( !BER_BVISNULL( &ros->rx_dn ) ) {
+ ch_free( ros->rx_dn.bv_val );
+ }
+ if ( !BER_BVISNULL( &ros->rx_ndn ) ) {
+ ch_free( ros->rx_ndn.bv_val );
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &ros->ro_dn ) ) {
+ op->o_req_dn = ros->ro_dn;
+ }
+ if ( !BER_BVISNULL( &ros->ro_ndn ) ) {
+ op->o_req_ndn = ros->ro_ndn;
+ }
+
+ if ( !BER_BVISNULL( &ros->r_dn )
+ && ros->r_dn.bv_val != ros->ro_dn.bv_val )
+ {
+ assert( ros->r_dn.bv_val != ros->r_ndn.bv_val );
+ ch_free( ros->r_dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &ros->r_ndn )
+ && ros->r_ndn.bv_val != ros->ro_ndn.bv_val )
+ {
+ ch_free( ros->r_ndn.bv_val );
+ }
+ }
+
+ BER_BVZERO( &ros->r_dn );
+ BER_BVZERO( &ros->r_ndn );
+ BER_BVZERO( &ros->ro_dn );
+ BER_BVZERO( &ros->ro_ndn );
+ BER_BVZERO( &ros->rx_dn );
+ BER_BVZERO( &ros->rx_ndn );
+
+ switch( ros->r_tag ) {
+ case LDAP_REQ_COMPARE:
+ if ( op->orc_ava->aa_value.bv_val != ros->orc_ava->aa_value.bv_val )
+ op->o_tmpfree( op->orc_ava->aa_value.bv_val, op->o_tmpmemctx );
+ op->orc_ava = ros->orc_ava;
+ break;
+ case LDAP_REQ_MODIFY:
+ slap_mods_free( op->orm_modlist, 1 );
+ op->orm_modlist = ros->orm_modlist;
+ break;
+ case LDAP_REQ_MODRDN:
+ if ( op->orr_newSup != ros->orr_newSup ) {
+ if ( op->orr_newSup ) {
+ ch_free( op->orr_newSup->bv_val );
+ ch_free( op->orr_nnewSup->bv_val );
+ op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx );
+ }
+ op->orr_newSup = ros->orr_newSup;
+ op->orr_nnewSup = ros->orr_nnewSup;
+ }
+ if ( op->orr_newrdn.bv_val != ros->orr_newrdn.bv_val ) {
+ ch_free( op->orr_newrdn.bv_val );
+ ch_free( op->orr_nnewrdn.bv_val );
+ op->orr_newrdn = ros->orr_newrdn;
+ op->orr_nnewrdn = ros->orr_nnewrdn;
+ }
+ if ( op->orr_newDN.bv_val != ros->orr_newDN.bv_val ) {
+ ch_free( op->orr_newDN.bv_val );
+ ch_free( op->orr_nnewDN.bv_val );
+ op->orr_newDN = ros->orr_newDN;
+ op->orr_nnewDN = ros->orr_nnewDN;
+ }
+ break;
+ case LDAP_REQ_SEARCH:
+ op->o_tmpfree( ros->mapped_attrs, op->o_tmpmemctx );
+ op->ors_attrs = ros->ors_attrs;
+ if ( op->ors_filter != ros->ors_filter ) {
+ filter_free_x( op, op->ors_filter, 1 );
+ op->ors_filter = ros->ors_filter;
+ }
+ if ( op->ors_filterstr.bv_val != ros->ors_filterstr.bv_val ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ op->ors_filterstr = ros->ors_filterstr;
+ }
+ break;
+ case LDAP_REQ_EXTENDED:
+ if ( op->ore_reqdata != ros->ore_reqdata ) {
+ ber_bvfree( op->ore_reqdata );
+ op->ore_reqdata = ros->ore_reqdata;
+ }
+ break;
+ case LDAP_REQ_BIND:
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+#if 0
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ /* too late, c_mutex released */
+ Debug( LDAP_DEBUG_ANY, "*** DN: \"%s\" => \"%s\"\n",
+ op->o_conn->c_ndn.bv_val,
+ op->o_req_ndn.bv_val );
+ ber_bvreplace( &op->o_conn->c_ndn,
+ &op->o_req_ndn );
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+#endif
+ }
+ break;
+ default: break;
+ }
+}
+
+static int
+rwm_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ rwm_op_state *ros = cb->sc_private;
+
+ if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_EXTENDED ||
+ op->o_abandon || rs->sr_err == SLAPD_ABANDON )
+ {
+ rwm_op_rollback( op, rs, ros );
+
+ op->o_callback = op->o_callback->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static rwm_op_cb *
+rwm_callback_get( Operation *op )
+{
+ rwm_op_cb *roc;
+
+ roc = op->o_tmpcalloc( 1, sizeof( struct rwm_op_cb ), op->o_tmpmemctx );
+ roc->cb.sc_cleanup = rwm_op_cleanup;
+ roc->cb.sc_response = NULL;
+ roc->cb.sc_next = op->o_callback;
+ roc->cb.sc_private = &roc->ros;
+ roc->ros.r_tag = op->o_tag;
+ roc->ros.ro_dn = op->o_req_dn;
+ roc->ros.ro_ndn = op->o_req_ndn;
+ BER_BVZERO( &roc->ros.r_dn );
+ BER_BVZERO( &roc->ros.r_ndn );
+ BER_BVZERO( &roc->ros.rx_dn );
+ BER_BVZERO( &roc->ros.rx_ndn );
+ roc->ros.mapped_attrs = NULL;
+ roc->ros.o_request = op->o_request;
+
+ return roc;
+}
+
+
+static int
+rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie,
+ rwm_op_state *ros )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ int rc = 0;
+ dncookie dc;
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ /* NOTE: in those cases where only the ndn is available,
+ * and the caller sets op->o_req_dn = op->o_req_ndn,
+ * only rewrite the op->o_req_ndn and use it as
+ * op->o_req_dn as well */
+ ndn = op->o_req_ndn;
+ if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) {
+ dn = op->o_req_dn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &op->o_req_dn, &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_normalize( &dc, &op->o_req_ndn, &ndn );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val && dn.bv_val == op->o_req_dn.bv_val )
+ || ndn.bv_val == op->o_req_ndn.bv_val )
+ {
+ return LDAP_SUCCESS;
+ }
+
+ if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) {
+ op->o_req_dn = dn;
+ assert( BER_BVISNULL( &ros->r_dn ) );
+ ros->r_dn = dn;
+ } else {
+ op->o_req_dn = ndn;
+ }
+ op->o_req_ndn = ndn;
+ assert( BER_BVISNULL( &ros->r_ndn ) );
+ ros->r_ndn = ndn;
+
+ if ( ros->r_tag == LDAP_REQ_EXTENDED ) {
+ ros->rx_dn = ros->r_dn;
+ ros->rx_ndn = ros->r_ndn;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+rwm_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc,
+ i;
+ Attribute **ap = NULL;
+ char *olddn = op->o_req_dn.bv_val;
+ int isupdate;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "addDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "addDN massage error" );
+ return -1;
+ }
+
+ if ( olddn != op->o_req_dn.bv_val ) {
+ ber_bvreplace( &op->ora_e->e_name, &op->o_req_dn );
+ ber_bvreplace( &op->ora_e->e_nname, &op->o_req_ndn );
+ }
+
+ /* Count number of attributes in entry */
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ap = &op->oq_add.rs_e->e_attrs; *ap; ) {
+ Attribute *a;
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_objectClass ||
+ (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ int j, last;
+
+ last = (*ap)->a_numvals - 1;
+ for ( j = 0; !BER_BVISNULL( &(*ap)->a_vals[ j ] ); j++ ) {
+ struct ldapmapping *mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_oc, &(*ap)->a_vals[ j ],
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ /* FIXME: we allow to remove objectClasses as well;
+ * if the resulting entry is inconsistent, that's
+ * the relayed database's business...
+ */
+ ch_free( (*ap)->a_vals[ j ].bv_val );
+ if ( last > j ) {
+ (*ap)->a_vals[ j ] = (*ap)->a_vals[ last ];
+ }
+ BER_BVZERO( &(*ap)->a_vals[ last ] );
+ (*ap)->a_numvals--;
+ last--;
+ j--;
+ }
+
+ } else {
+ ch_free( (*ap)->a_vals[ j ].bv_val );
+ ber_dupbv( &(*ap)->a_vals[ j ], &mapping->m_dst );
+ }
+ }
+
+ } else if ( !isupdate && !get_relax( op ) && (*ap)->a_desc->ad_type->sat_no_user_mod )
+ {
+ goto next_attr;
+
+ } else {
+ struct ldapmapping *mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_at, &(*ap)->a_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ goto cleanup_attr;
+ }
+ }
+
+ if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ /*
+ * FIXME: rewrite could fail; in this case
+ * the operation should give up, right?
+ */
+ rc = rwm_dnattr_rewrite( op, rs, "addAttrDN",
+ (*ap)->a_vals,
+ (*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+ if ( rc ) {
+ goto cleanup_attr;
+ }
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+ rc = rwm_referral_rewrite( op, rs, "referralAttrDN",
+ (*ap)->a_vals,
+ (*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+ }
+
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+ (*ap)->a_desc = mapping->m_dst_ad;
+ }
+ }
+
+next_attr:;
+ ap = &(*ap)->a_next;
+ continue;
+
+cleanup_attr:;
+ /* FIXME: leaking attribute/values? */
+ a = *ap;
+
+ *ap = (*ap)->a_next;
+ attr_free( a );
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_conn_init( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ ( void )rewrite_session_init( rwmap->rwm_rw, conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_conn_destroy( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ ( void )rewrite_session_delete( rwmap->rwm_rw, conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "bindDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "bindDN massage error" );
+ return -1;
+ }
+
+ overlay_callback_after_backover( op, &roc->cb, 1 );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_unbind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ rewrite_session_delete( rwmap->rwm_rw, op->o_conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+ struct berval mapped_vals[2] = { BER_BVNULL, BER_BVNULL };
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "compareDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "compareDN massage error" );
+ return -1;
+ }
+
+ /* if the attribute is an objectClass, try to remap its value */
+ if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass
+ || op->orc_ava->aa_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ rwm_map( &rwmap->rwm_oc, &op->orc_ava->aa_value,
+ &mapped_vals[0], RWM_MAP );
+ if ( BER_BVISNULL( &mapped_vals[0] ) || BER_BVISEMPTY( &mapped_vals[0] ) )
+ {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_OTHER, "compare objectClass map error" );
+ return -1;
+
+ } else if ( mapped_vals[0].bv_val != op->orc_ava->aa_value.bv_val ) {
+ ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0],
+ op->o_tmpmemctx );
+ }
+
+ } else {
+ struct ldapmapping *mapping = NULL;
+ AttributeDescription *ad = op->orc_ava->aa_desc;
+
+ ( void )rwm_mapping( &rwmap->rwm_at, &op->orc_ava->aa_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_OTHER, "compare attributeType map error" );
+ return -1;
+ }
+
+ } else {
+ assert( mapping->m_dst_ad != NULL );
+ ad = mapping->m_dst_ad;
+ }
+
+ if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ struct berval *mapped_valsp[2];
+
+ mapped_valsp[0] = &mapped_vals[0];
+ mapped_valsp[1] = &mapped_vals[1];
+
+ mapped_vals[0] = op->orc_ava->aa_value;
+
+ rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", NULL, mapped_valsp );
+
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "compareAttrDN massage error" );
+ return -1;
+ }
+
+ if ( mapped_vals[ 0 ].bv_val != op->orc_ava->aa_value.bv_val ) {
+ /* NOTE: if we get here, rwm_dnattr_rewrite()
+ * already freed the old value, so now
+ * it's invalid */
+ ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0],
+ op->o_tmpmemctx );
+ ber_memfree_x( mapped_vals[ 0 ].bv_val, NULL );
+ }
+ }
+ op->orc_ava->aa_desc = ad;
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "deleteDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "deleteDN massage error" );
+ return -1;
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int isupdate;
+ Modifications **mlp;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "modifyDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "modifyDN massage error" );
+ return -1;
+ }
+
+ isupdate = be_shadow_update( op );
+ for ( mlp = &op->orm_modlist; *mlp; ) {
+ int is_oc = 0;
+ Modifications *ml = *mlp;
+ struct ldapmapping *mapping = NULL;
+
+ /* ml points to a temporary mod until needs duplication */
+ if ( ml->sml_desc == slap_schema.si_ad_objectClass
+ || ml->sml_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ is_oc = 1;
+
+ } else if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ ml = ch_malloc( sizeof( Modifications ) );
+ *ml = **mlp;
+ if ( (*mlp)->sml_values ) {
+ ber_bvarray_dup_x( &ml->sml_values, (*mlp)->sml_values, NULL );
+ if ( (*mlp)->sml_nvalues ) {
+ ber_bvarray_dup_x( &ml->sml_nvalues, (*mlp)->sml_nvalues, NULL );
+ }
+ }
+ *mlp = ml;
+ goto next_mod;
+
+ } else {
+ int drop_missing;
+
+ drop_missing = rwm_mapping( &rwmap->rwm_at,
+ &ml->sml_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( drop_missing || ( mapping != NULL && BER_BVISNULL( &mapping->m_dst ) ) )
+ {
+ goto skip_mod;
+ }
+ }
+
+ /* duplicate the modlist */
+ ml = ch_malloc( sizeof( Modifications ));
+ *ml = **mlp;
+ *mlp = ml;
+
+ if ( ml->sml_values != NULL ) {
+ int i, num;
+ struct berval *bva;
+
+ for ( num = 0; !BER_BVISNULL( &ml->sml_values[ num ] ); num++ )
+ /* count values */ ;
+
+ bva = ch_malloc( (num+1) * sizeof( struct berval ));
+ for (i=0; i<num; i++)
+ ber_dupbv( &bva[i], &ml->sml_values[i] );
+ BER_BVZERO( &bva[i] );
+ ml->sml_values = bva;
+
+ if ( ml->sml_nvalues ) {
+ bva = ch_malloc( (num+1) * sizeof( struct berval ));
+ for (i=0; i<num; i++)
+ ber_dupbv( &bva[i], &ml->sml_nvalues[i] );
+ BER_BVZERO( &bva[i] );
+ ml->sml_nvalues = bva;
+ }
+
+ if ( is_oc ) {
+ int last, j;
+
+ last = num-1;
+
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+ struct ldapmapping *oc_mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_oc, &ml->sml_values[ j ],
+ &oc_mapping, RWM_MAP );
+ if ( oc_mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ /* FIXME: we allow to remove objectClasses as well;
+ * if the resulting entry is inconsistent, that's
+ * the relayed database's business...
+ */
+ if ( last > j ) {
+ ch_free( ml->sml_values[ j ].bv_val );
+ ml->sml_values[ j ] = ml->sml_values[ last ];
+ }
+ BER_BVZERO( &ml->sml_values[ last ] );
+ last--;
+ j--;
+ }
+
+ } else {
+ ch_free( ml->sml_values[ j ].bv_val );
+ ber_dupbv( &ml->sml_values[ j ], &oc_mapping->m_dst );
+ }
+ }
+
+ } else {
+ if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ rc = rwm_dnattr_rewrite( op, rs, "modifyAttrDN",
+ ml->sml_values,
+ ml->sml_nvalues ? &ml->sml_nvalues : NULL );
+
+ } else if ( ml->sml_desc == slap_schema.si_ad_ref ) {
+ rc = rwm_referral_rewrite( op, rs,
+ "referralAttrDN",
+ ml->sml_values,
+ ml->sml_nvalues ? &ml->sml_nvalues : NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_mod;
+ }
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_mod;
+ }
+ }
+ }
+
+next_mod:;
+ if ( mapping != NULL ) {
+ /* use new attribute description */
+ assert( mapping->m_dst_ad != NULL );
+ ml->sml_desc = mapping->m_dst_ad;
+ }
+
+ mlp = &ml->sml_next;
+ continue;
+
+skip_mod:;
+ *mlp = (*mlp)->sml_next;
+ continue;
+
+cleanup_mod:;
+ ml = *mlp;
+ *mlp = (*mlp)->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+ struct berval pdn, pndn;
+
+ int rc;
+ dncookie dc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ if ( op->orr_newSup ) {
+ struct berval nnewSup = BER_BVNULL;
+ struct berval newSup = BER_BVNULL;
+
+ /*
+ * Rewrite the new superior, if defined and required
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "newSuperiorDN";
+ newSup = *op->orr_newSup;
+ nnewSup = *op->orr_nnewSup;
+ rc = rwm_dn_massage_pretty_normalize( &dc, op->orr_newSup, &newSup, &nnewSup );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "newSuperiorDN massage error" );
+ return -1;
+ }
+
+ if ( op->orr_newSup->bv_val != newSup.bv_val ) {
+ op->orr_newSup = op->o_tmpalloc( sizeof( struct berval ),
+ op->o_tmpmemctx );
+ op->orr_nnewSup = op->o_tmpalloc( sizeof( struct berval ),
+ op->o_tmpmemctx );
+ *op->orr_newSup = newSup;
+ *op->orr_nnewSup = nnewSup;
+ }
+ pdn = newSup;
+ pndn = nnewSup;
+ }
+
+ /*
+ * Rewrite the newRDN, if needed
+ */
+ {
+ struct berval newrdn = BER_BVNULL;
+ struct berval nnewrdn = BER_BVNULL;
+
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "newRDN";
+ newrdn = op->orr_newrdn;
+ nnewrdn = op->orr_nnewrdn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &op->orr_newrdn, &newrdn, &nnewrdn );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "newRDN massage error" );
+ goto err;
+ }
+
+ if ( op->orr_newrdn.bv_val != newrdn.bv_val ) {
+ op->orr_newrdn = newrdn;
+ op->orr_nnewrdn = nnewrdn;
+ }
+ }
+
+ /*
+ * Rewrite the dn, if needed
+ */
+ rc = rwm_op_dn_massage( op, rs, "renameDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "renameDN massage error" );
+ goto err;
+ }
+ if ( !op->orr_newSup ) {
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+ }
+
+ /*
+ * Update the new DN
+ */
+ build_new_dn( &op->orr_newDN, &pdn, &op->orr_newrdn, op->o_tmpmemctx );
+ build_new_dn( &op->orr_nnewDN, &pndn, &op->orr_nnewrdn, op->o_tmpmemctx );
+
+ op->o_callback = &roc->cb;
+
+ rc = SLAP_CB_CONTINUE;
+
+ if ( 0 ) {
+err:;
+ if ( op->orr_newSup != roc->ros.orr_newSup ) {
+ ch_free( op->orr_newSup->bv_val );
+ ch_free( op->orr_nnewSup->bv_val );
+ op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx );
+ op->orr_newSup = roc->ros.orr_newSup;
+ op->orr_nnewSup = roc->ros.orr_nnewSup;
+ }
+
+ if ( op->orr_newrdn.bv_val != roc->ros.orr_newrdn.bv_val ) {
+ ch_free( op->orr_newrdn.bv_val );
+ ch_free( op->orr_nnewrdn.bv_val );
+ op->orr_newrdn = roc->ros.orr_newrdn;
+ op->orr_nnewrdn = roc->ros.orr_nnewrdn;
+ }
+
+ if ( op->orr_newDN.bv_val != roc->ros.orr_newDN.bv_val ) {
+ op->o_tmpfree( op->orr_newDN.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewDN.bv_val, op->o_tmpmemctx );
+ op->orr_newDN = roc->ros.orr_newDN;
+ op->orr_nnewDN = roc->ros.orr_nnewDN;
+ }
+ }
+
+ return rc;
+}
+
+
+static int
+rwm_swap_attrs( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ rwm_op_state *ros = cb->sc_private;
+
+ rs->sr_attrs = ros->ors_attrs;
+
+ /* other overlays might have touched op->ors_attrs,
+ * so we restore the original version here, otherwise
+ * attribute-mapping might fail */
+ op->ors_attrs = ros->mapped_attrs;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * NOTE: this implementation of get/release entry is probably far from
+ * optimal. The rationale consists in intercepting the request directed
+ * to the underlying database, in order to rewrite/remap the request,
+ * perform it using the modified data, duplicate the resulting entry
+ * and finally free it when release is called.
+ * This implies that subsequent overlays are not called, as the request
+ * is directly shunted to the underlying database.
+ */
+static int
+rwm_entry_release_rw( Operation *op, Entry *e, int rw )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+
+ /* can't be ours */
+ if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* just free entry if (probably) ours */
+ if ( e->e_private == NULL && BER_BVISNULL( &e->e_bv ) ) {
+ entry_free( e );
+ return LDAP_SUCCESS;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static struct berval *passwd_oid;
+
+static int
+rwm_entry_get_rw( Operation *op, struct berval *ndn,
+ ObjectClass *oc, AttributeDescription *at, int rw, Entry **ep )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ BackendDB db;
+ Operation op2;
+ SlapReply rs = { REP_SEARCH };
+
+ rwm_op_state ros = { 0 };
+ struct berval mndn = BER_BVNULL;
+
+ if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* If we're fetching the target of a password mod, must let real DNs thru */
+ if ( op->o_tag == LDAP_REQ_EXTENDED && bvmatch( passwd_oid, &op->oq_extended.rs_reqoid ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* massage DN */
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2 = *op;
+ op2.o_req_dn = *ndn;
+ op2.o_req_ndn = *ndn;
+ rc = rwm_op_dn_massage( &op2, &rs, "searchDN", &ros );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_OTHER;
+ }
+
+ mndn = BER_BVISNULL( &ros.r_ndn ) ? *ndn : ros.r_ndn;
+
+ /* map attribute & objectClass */
+ if ( at != NULL ) {
+ }
+
+ if ( oc != NULL ) {
+ }
+
+ /* fetch entry */
+ db = *op->o_bd;
+ op2.o_bd = &db;
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info->oi_orig;
+ op2.ors_attrs = slap_anlist_all_attributes;
+ rc = op2.o_bd->bd_info->bi_entry_get_rw( &op2, &mndn, oc, at, rw, ep );
+ if ( rc == LDAP_SUCCESS && *ep != NULL ) {
+ /* we assume be_entry_release() needs to be called */
+ rs.sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs.sr_entry = *ep;
+
+ /* duplicate & release */
+ op2.o_bd->bd_info = (BackendInfo *)on;
+ rc = rwm_send_entry( &op2, &rs );
+ RS_ASSERT( rs.sr_flags & REP_ENTRY_MUSTFLUSH );
+ if ( rc == SLAP_CB_CONTINUE ) {
+ *ep = rs.sr_entry;
+ rc = LDAP_SUCCESS;
+ } else {
+ assert( rc != LDAP_SUCCESS && rs.sr_entry == *ep );
+ *ep = NULL;
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( &op2, rs.sr_entry );
+ op2.o_bd->bd_info = (BackendInfo *)on;
+ }
+ }
+
+ if ( !BER_BVISNULL( &ros.r_ndn) && ros.r_ndn.bv_val != ndn->bv_val ) {
+ op->o_tmpfree( ros.r_ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ return rc;
+}
+
+static int
+rwm_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+ dncookie dc;
+
+ struct berval fstr = BER_BVNULL;
+ Filter *f = NULL;
+
+ AttributeName *an = NULL;
+
+ char *text = NULL;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rewrite_session_var_set( rwmap->rwm_rw, op->o_conn,
+ "searchFilter", op->ors_filterstr.bv_val );
+ if ( rc == LDAP_SUCCESS )
+ rc = rwm_op_dn_massage( op, rs, "searchDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "searchDN massage error";
+ goto error_return;
+ }
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "searchFilterAttrDN";
+
+ rc = rwm_filter_map_rewrite( op, &dc, op->ors_filter, &fstr );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "searchFilter/searchFilterAttrDN massage error";
+ goto error_return;
+ }
+
+ f = str2filter_x( op, fstr.bv_val );
+
+ if ( f == NULL ) {
+ text = "massaged filter parse error";
+ goto error_return;
+ }
+
+ op->ors_filter = f;
+ op->ors_filterstr = fstr;
+
+ rc = rwm_map_attrnames( op, &rwmap->rwm_at, &rwmap->rwm_oc,
+ op->ors_attrs, &an, RWM_MAP );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "attribute list mapping error";
+ goto error_return;
+ }
+
+ op->ors_attrs = an;
+ /* store the mapped Attributes for later usage, in
+ * the case that other overlays change op->ors_attrs */
+ roc->ros.mapped_attrs = an;
+ roc->cb.sc_response = rwm_swap_attrs;
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+
+error_return:;
+ if ( an != NULL ) {
+ ch_free( an );
+ }
+
+ if ( f != NULL ) {
+ filter_free_x( op, f, 1 );
+ }
+
+ if ( !BER_BVISNULL( &fstr ) ) {
+ op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx );
+ }
+
+ rwm_op_rollback( op, rs, &roc->ros );
+ op->oq_search = roc->ros.oq_search;
+ op->o_tmpfree( roc, op->o_tmpmemctx );
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, text );
+
+ return -1;
+
+}
+
+static int
+rwm_exop_passwd( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ rwm_op_cb *roc;
+
+ struct berval id = BER_BVNULL,
+ pwold = BER_BVNULL,
+ pwnew = BER_BVNULL;
+ BerElement *ber = NULL;
+
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( !SLAP_ISGLOBALOVERLAY( op->o_bd ) ) {
+ rs->sr_err = LDAP_OTHER;
+ return rs->sr_err;
+ }
+
+ rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id,
+ &pwold, &pwnew, &rs->sr_text );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ if ( !BER_BVISNULL( &id ) ) {
+ char idNul = id.bv_val[id.bv_len];
+ id.bv_val[id.bv_len] = '\0';
+ rs->sr_err = dnPrettyNormal( NULL, &id, &op->o_req_dn,
+ &op->o_req_ndn, op->o_tmpmemctx );
+ id.bv_val[id.bv_len] = idNul;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = "Invalid DN";
+ return rs->sr_err;
+ }
+
+ } else {
+ ber_dupbv_x( &op->o_req_dn, &op->o_dn, op->o_tmpmemctx );
+ ber_dupbv_x( &op->o_req_ndn, &op->o_ndn, op->o_tmpmemctx );
+ }
+
+ roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "extendedDN massage error" );
+ return -1;
+ }
+
+ ber = ber_alloc_t( LBER_USE_DER );
+ if ( !ber ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "No memory";
+ return rs->sr_err;
+ }
+ ber_printf( ber, "{" );
+ if ( !BER_BVISNULL( &id )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+ &op->o_req_dn );
+ }
+ if ( !BER_BVISNULL( &pwold )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, &pwold );
+ }
+ if ( !BER_BVISNULL( &pwnew )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &pwnew );
+ }
+ ber_printf( ber, "N}" );
+ ber_flatten( ber, &op->ore_reqdata );
+ ber_free( ber, 1 );
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static struct exop {
+ struct berval oid;
+ BI_op_extended *extended;
+} exop_table[] = {
+ { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), rwm_exop_passwd },
+ { BER_BVNULL, NULL }
+};
+
+static struct berval *passwd_oid = &exop_table[0].oid;
+
+static int
+rwm_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ rwm_op_cb *roc;
+
+ int i;
+
+ for ( i = 0; exop_table[i].extended != NULL; i++ ) {
+ if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) )
+ {
+ rc = exop_table[i].extended( op, rs );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case SLAP_CB_CONTINUE:
+ case SLAPD_ABANDON:
+ return rc;
+
+ default:
+ send_ldap_result( op, rs );
+ return rc;
+ }
+ break;
+ }
+ }
+
+ roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "extendedDN massage error" );
+ return -1;
+ }
+
+ /* TODO: rewrite/map extended data ? ... */
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+rwm_matched( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval dn, mdn;
+ dncookie dc;
+ int rc;
+
+ if ( rs->sr_matched == NULL ) {
+ return;
+ }
+
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "matchedDN";
+ ber_str2bv( rs->sr_matched, 0, 0, &dn );
+ mdn = dn;
+ rc = rwm_dn_massage_pretty( &dc, &dn, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ rs->sr_text = "Rewrite error";
+
+ } else if ( mdn.bv_val != dn.bv_val ) {
+ if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) {
+ ch_free( (void *)rs->sr_matched );
+
+ } else {
+ rs->sr_flags |= REP_MATCHED_MUSTBEFREED;
+ }
+ rs->sr_matched = mdn.bv_val;
+ }
+}
+
+static int
+rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first, int stripEntryDN )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ dncookie dc;
+ int rc;
+ Attribute **ap;
+ int isupdate;
+ int check_duplicate_attrs = 0;
+
+ /*
+ * Rewrite the dn attrs, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+
+ /* FIXME: in principle, one could map an attribute
+ * on top of another, which already exists.
+ * As such, in the end there might exist more than
+ * one instance of an attribute.
+ * We should at least check if this occurs, and issue
+ * an error (because multiple instances of attrs in
+ * response are not valid), or merge the values (what
+ * about duplicate values?) */
+ isupdate = be_shadow_update( op );
+ for ( ap = a_first; *ap; ) {
+ struct ldapmapping *mapping = NULL;
+ int drop_missing;
+ int last = -1;
+ Attribute *a;
+
+ if ( ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ) &&
+ op->ors_attrs != NULL &&
+ !SLAP_USERATTRS( rs->sr_attr_flags ) &&
+ !ad_inlist( (*ap)->a_desc, op->ors_attrs ) )
+ {
+ goto cleanup_attr;
+ }
+
+ drop_missing = rwm_mapping( &rwmap->rwm_at,
+ &(*ap)->a_desc->ad_cname, &mapping, RWM_REMAP );
+ if ( drop_missing || ( mapping != NULL && BER_BVISEMPTY( &mapping->m_dst ) ) )
+ {
+ goto cleanup_attr;
+ }
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+
+ /* try to normalize mapped Attributes if the original
+ * AttributeType was not normalized */
+ if ( (!(*ap)->a_desc->ad_type->sat_equality ||
+ !(*ap)->a_desc->ad_type->sat_equality->smr_normalize) &&
+ mapping->m_dst_ad->ad_type->sat_equality &&
+ mapping->m_dst_ad->ad_type->sat_equality->smr_normalize )
+ {
+ if ((rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS))
+ {
+ int i = 0;
+
+ last = (*ap)->a_numvals;
+ if ( last )
+ {
+ (*ap)->a_nvals = ch_malloc( (last+1) * sizeof(struct berval) );
+
+ for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[i]); i++ ) {
+ int rc;
+ /*
+ * check that each value is valid per syntax
+ * and pretty if appropriate
+ */
+ rc = mapping->m_dst_ad->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mapping->m_dst_ad->ad_type->sat_syntax,
+ mapping->m_dst_ad->ad_type->sat_equality,
+ &(*ap)->a_vals[i], &(*ap)->a_nvals[i],
+ NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ /* FIXME: this is wrong, putting a non-normalized value
+ * into nvals. But when a proxy sends us bogus data,
+ * we still need to give it to the client, even if it
+ * violates the syntax. I.e., we don't want to silently
+ * drop things and trigger an apparent data loss.
+ */
+ ber_dupbv( &(*ap)->a_nvals[i], &(*ap)->a_vals[i] );
+ }
+ }
+ BER_BVZERO( &(*ap)->a_nvals[i] );
+ }
+
+ } else {
+ assert( (*ap)->a_nvals == (*ap)->a_vals );
+ (*ap)->a_nvals = NULL;
+ ber_bvarray_dup_x( &(*ap)->a_nvals, (*ap)->a_vals, NULL );
+ }
+ }
+
+ /* rewrite the attribute description */
+ (*ap)->a_desc = mapping->m_dst_ad;
+
+ /* will need to check for duplicate attrs */
+ check_duplicate_attrs++;
+ }
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_entryDN ) {
+ if ( stripEntryDN ) {
+ /* will be generated by frontend */
+ goto cleanup_attr;
+ }
+
+ } else if ( !isupdate
+ && !get_relax( op )
+ && (*ap)->a_desc->ad_type->sat_no_user_mod
+ && (*ap)->a_desc->ad_type != slap_schema.si_at_undefined )
+ {
+ goto next_attr;
+ }
+
+ if ( last == -1 ) { /* not yet counted */
+ last = (*ap)->a_numvals;
+ }
+
+ if ( last == 0 ) {
+ /* empty? leave it in place because of attrsonly and vlv */
+ goto next_attr;
+ }
+ last--;
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_objectClass
+ || (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ struct berval *bv;
+
+ for ( bv = (*ap)->a_vals; !BER_BVISNULL( bv ); bv++ ) {
+ struct berval mapped;
+
+ rwm_map( &rwmap->rwm_oc, &bv[0], &mapped, RWM_REMAP );
+ if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+remove_oc:;
+ ch_free( bv[0].bv_val );
+ BER_BVZERO( &bv[0] );
+ if ( &(*ap)->a_vals[last] > &bv[0] ) {
+ bv[0] = (*ap)->a_vals[last];
+ BER_BVZERO( &(*ap)->a_vals[last] );
+ }
+ last--;
+ bv--;
+
+ } else if ( mapped.bv_val != bv[0].bv_val
+ && ber_bvstrcasecmp( &mapped, &bv[0] ) != 0 )
+ {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[ i ] ); i++ ) {
+ if ( &(*ap)->a_vals[ i ] == bv ) {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &mapped, &(*ap)->a_vals[ i ] ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( !BER_BVISNULL( &(*ap)->a_vals[ i ] ) ) {
+ goto remove_oc;
+ }
+
+ /*
+ * FIXME: after LBER_FREEing
+ * the value is replaced by
+ * ch_alloc'ed memory
+ */
+ ber_bvreplace( &bv[0], &mapped );
+
+ /* FIXME: will need to check
+ * if the structuralObjectClass
+ * changed */
+ }
+ }
+
+ /*
+ * It is necessary to try to rewrite attributes with
+ * dn syntax because they might be used in ACLs as
+ * members of groups; since ACLs are applied to the
+ * rewritten stuff, no dn-based subject clause could
+ * be used at the ldap backend side (see
+ * http://www.OpenLDAP.org/faq/data/cache/452.html)
+ * The problem can be overcome by moving the dn-based
+ * ACLs to the target directory server, and letting
+ * everything pass thru the ldap backend. */
+ /* FIXME: handle distinguishedName-like syntaxes, like
+ * nameAndOptionalUID */
+ } else if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_src_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ dc.ctx = "searchAttrDN";
+ rc = rwm_dnattr_result_rewrite( &dc, (*ap)->a_vals, (*ap)->a_nvals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+ dc.ctx = "searchAttrDN";
+ rc = rwm_referral_result_rewrite( &dc, (*ap)->a_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+ }
+
+
+next_attr:;
+ ap = &(*ap)->a_next;
+ continue;
+
+cleanup_attr:;
+ a = *ap;
+ *ap = (*ap)->a_next;
+
+ attr_free( a );
+ }
+
+ /* only check if some mapping occurred */
+ if ( check_duplicate_attrs ) {
+ for ( ap = a_first; *ap != NULL; ap = &(*ap)->a_next ) {
+ Attribute **tap;
+
+ for ( tap = &(*ap)->a_next; *tap != NULL; ) {
+ if ( (*tap)->a_desc == (*ap)->a_desc ) {
+ Entry e = { 0 };
+ Modification mod = { 0 };
+ const char *text = NULL;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ Attribute *next = (*tap)->a_next;
+
+ BER_BVSTR( &e.e_name, "" );
+ BER_BVSTR( &e.e_nname, "" );
+ e.e_attrs = *ap;
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = (*ap)->a_desc;
+ mod.sm_type = mod.sm_desc->ad_cname;
+ mod.sm_numvals = (*tap)->a_numvals;
+ mod.sm_values = (*tap)->a_vals;
+ if ( (*tap)->a_nvals != (*tap)->a_vals ) {
+ mod.sm_nvalues = (*tap)->a_nvals;
+ }
+
+ (void)modify_add_values( &e, &mod,
+ /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+
+ /* should not insert new attrs! */
+ assert( e.e_attrs == *ap );
+
+ attr_free( *tap );
+ *tap = next;
+
+ } else {
+ tap = &(*tap)->a_next;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Should return SLAP_CB_CONTINUE or failure, never LDAP_SUCCESS. */
+static int
+rwm_send_entry( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ Entry *e = NULL;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ dncookie dc;
+ int rc;
+
+ assert( rs->sr_entry != NULL );
+
+ /*
+ * Rewrite the dn of the result, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+ dc.ctx = "searchEntryDN";
+
+ e = rs->sr_entry;
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ /* FIXME: all we need to duplicate are:
+ * - dn
+ * - ndn
+ * - attributes that are requested
+ * - no values if attrsonly is set
+ */
+ e = entry_dup( e );
+ if ( e == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto fail;
+ }
+ } else if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) {
+ /* ITS#6423: REP_ENTRY_MUSTRELEASE incompatible
+ * with REP_ENTRY_MODIFIABLE */
+ RS_ASSERT( 0 );
+ rc = 1;
+ goto fail;
+ }
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ */
+ dn = e->e_name;
+ ndn = e->e_nname;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &e->e_name, &dn, &ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = 1;
+ goto fail;
+ }
+
+ if ( e->e_name.bv_val != dn.bv_val ) {
+ ch_free( e->e_name.bv_val );
+ ch_free( e->e_nname.bv_val );
+
+ e->e_name = dn;
+ e->e_nname = ndn;
+ }
+
+ /* TODO: map entry attribute types, objectclasses
+ * and dn-valued attribute values */
+
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+ (void)rwm_attrs( op, rs, &e->e_attrs, 1 );
+
+ if ( e != rs->sr_entry ) {
+ /* Reimplementing rs_replace_entry(), I suppose to
+ * bypass our own dubious rwm_entry_release_rw() */
+ if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) {
+ rs->sr_flags ^= REP_ENTRY_MUSTRELEASE;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, rs->sr_entry );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ } else if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) {
+ entry_free( rs->sr_entry );
+ }
+ rs->sr_entry = e;
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return SLAP_CB_CONTINUE;
+
+fail:;
+ if ( e != NULL && e != rs->sr_entry ) {
+ if ( e->e_name.bv_val == dn.bv_val ) {
+ BER_BVZERO( &e->e_name );
+ }
+
+ if ( e->e_nname.bv_val == ndn.bv_val ) {
+ BER_BVZERO( &e->e_nname );
+ }
+
+ entry_free( e );
+ }
+
+ if ( !BER_BVISNULL( &dn ) ) {
+ ch_free( dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &ndn ) ) {
+ ch_free( ndn.bv_val );
+ }
+
+ return rc;
+}
+
+static int
+rwm_operational( Operation *op, SlapReply *rs )
+{
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+ if ( rs->sr_operational_attrs ) {
+ rwm_attrs( op, rs, &rs->sr_operational_attrs, 1 );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+#if 0
+/* don't use this; it cannot be reverted, and leaves op->o_req_dn
+ * rewritten for subsequent operations; fine for plain suffixmassage,
+ * but destroys everything else */
+static int
+rwm_chk_referrals( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rc = rwm_op_dn_massage( op, rs, "referralCheckDN" );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "referralCheckDN massage error" );
+ return -1;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+#endif
+
+static int
+rwm_rw_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ return rewrite_parse( rwmap->rwm_rw,
+ fname, lineno, argc, argv );
+
+ return 0;
+}
+
+static int
+rwm_suffixmassage_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval bvnc, nvnc, pvnc, brnc, nrnc, prnc;
+ int massaged;
+ int rc;
+
+ /*
+ * syntax:
+ *
+ * suffixmassage [<suffix>] <massaged suffix>
+ *
+ * the [<suffix>] field must be defined as a valid suffix
+ * for the current database;
+ * the <massaged suffix> shouldn't have already been
+ * defined as a valid suffix for the current server
+ */
+ if ( argc == 2 ) {
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ " \"suffixMassage [<suffix>]"
+ " <massaged suffix>\" without "
+ "<suffix> part requires database "
+ "suffix be defined first.\n",
+ fname, lineno );
+ return 1;
+ }
+ bvnc = be->be_suffix[ 0 ];
+ massaged = 1;
+
+ } else if ( argc == 3 ) {
+ ber_str2bv( argv[ 1 ], 0, 0, &bvnc );
+ massaged = 2;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is"
+ " \"suffixMassage [<suffix>]"
+ " <massaged suffix>\"\n",
+ fname, lineno );
+ return 1;
+ }
+
+ if ( dnPrettyNormal( NULL, &bvnc, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n",
+ fname, lineno, bvnc.bv_val );
+ return 1;
+ }
+
+ ber_str2bv( argv[ massaged ], 0, 0, &brnc );
+ if ( dnPrettyNormal( NULL, &brnc, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n",
+ fname, lineno, brnc.bv_val );
+ free( nvnc.bv_val );
+ free( pvnc.bv_val );
+ return 1;
+ }
+
+ /*
+ * The suffix massaging is emulated
+ * by means of the rewrite capabilities
+ */
+ rc = rwm_suffix_massage_config( rwmap->rwm_rw,
+ &pvnc, &nvnc, &prnc, &nrnc );
+ free( nvnc.bv_val );
+ free( pvnc.bv_val );
+ free( nrnc.bv_val );
+ free( prnc.bv_val );
+
+ return rc;
+}
+
+static int
+rwm_m_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ /* objectclass/attribute mapping */
+ return rwm_map_config( &rwmap->rwm_oc,
+ &rwmap->rwm_at,
+ fname, lineno, argc, argv );
+}
+
+static int
+rwm_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) {
+ return rwm_send_entry( op, rs );
+ }
+
+ switch( op->o_tag ) {
+ case LDAP_REQ_SEARCH:
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_EXTENDED:
+ if ( rs->sr_ref ) {
+ dncookie dc;
+
+ /*
+ * Rewrite the dn of the referrals, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+ dc.ctx = "referralDN";
+ rc = rwm_referral_result_rewrite( &dc, rs->sr_ref );
+ /* FIXME: impossible, so far */
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ break;
+ }
+ }
+
+ rwm_matched( op, rs );
+ break;
+ }
+
+ if ( op->o_tag == LDAP_REQ_ADD && op->ora_e ) {
+ /*
+ * Rewrite back the dn and attributes of the added entry op->ora_e
+ */
+ SlapReply rs2 = *rs;
+ rs2.sr_entry = op->ora_e;
+ rs2.sr_flags |= REP_ENTRY_MODIFIABLE;
+ return rwm_send_entry( op, &rs2 );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc = 0;
+ char *argv0 = NULL;
+
+ if ( strncasecmp( argv[ 0 ], "rwm-", STRLENOF( "rwm-" ) ) == 0 ) {
+ argv0 = argv[ 0 ];
+ argv[ 0 ] = &argv0[ STRLENOF( "rwm-" ) ];
+ }
+
+ if ( strncasecmp( argv[0], "rewrite", STRLENOF("rewrite") ) == 0 ) {
+ rc = rwm_rw_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "map" ) == 0 ) {
+ rc = rwm_m_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "t-f-support" ) == 0 ) {
+ if ( argc != 2 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"t-f-support {no|yes|discover}\" needs 1 argument.\n",
+ fname, lineno );
+ return( 1 );
+ }
+
+ if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) {
+ rwmap->rwm_flags &= ~(RWM_F_SUPPORT_T_F_MASK2);
+
+ } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) {
+ rwmap->rwm_flags |= RWM_F_SUPPORT_T_F;
+
+ /* TODO: not implemented yet */
+ } else if ( strcasecmp( argv[ 1 ], "discover" ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"discover\" not supported yet "
+ "in \"t-f-support {no|yes|discover}\".\n",
+ fname, lineno );
+ return( 1 );
+#if 0
+ rwmap->rwm_flags |= RWM_F_SUPPORT_T_F_DISCOVER;
+#endif
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: unknown value \"%s\" for \"t-f-support {no|yes|discover}\".\n",
+ fname, lineno, argv[ 1 ] );
+ return 1;
+ }
+
+ } else if ( strcasecmp( argv[0], "normalize-mapped-attrs" ) == 0 ) {
+ if ( argc !=2 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"normalize-mapped-attrs {no|yes}\" needs 1 argument.\n",
+ fname, lineno );
+ return( 1 );
+ }
+
+ if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) {
+ rwmap->rwm_flags &= ~(RWM_F_NORMALIZE_MAPPED_ATTRS);
+
+ } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) {
+ rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS;
+ }
+
+ } else {
+ rc = SLAP_CONF_UNKNOWN;
+ }
+
+ if ( argv0 ) {
+ argv[ 0 ] = argv0;
+ }
+
+ return rc;
+}
+
+/*
+ * dynamic configuration...
+ */
+
+enum {
+ /* rewrite */
+ RWM_CF_REWRITE = 1,
+
+ /* map */
+ RWM_CF_MAP,
+ RWM_CF_T_F_SUPPORT,
+ RWM_CF_NORMALIZE_MAPPED,
+ RWM_CF_DROP_UNREQUESTED,
+
+ RWM_CF_LAST
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "true" ), RWM_F_SUPPORT_T_F },
+ { BER_BVC( "yes" ), RWM_F_SUPPORT_T_F },
+ { BER_BVC( "discover" ), RWM_F_SUPPORT_T_F_DISCOVER },
+ { BER_BVC( "false" ), RWM_F_NONE },
+ { BER_BVC( "no" ), RWM_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static ConfigDriver rwm_cf_gen;
+
+static ConfigTable rwmcfg[] = {
+ { "rwm-rewrite", "rewrite",
+ 2, 0, STRLENOF("rwm-rewrite"),
+ ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen,
+ "( OLcfgOvAt:16.1 NAME 'olcRwmRewrite' "
+ "DESC 'Rewrites strings' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+
+ { "rwm-suffixmassage", "[virtual]> <real",
+ 2, 3, 0, ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen,
+ NULL, NULL, NULL },
+
+ { "rwm-t-f-support", "true|false|discover",
+ 2, 2, 0, ARG_MAGIC|RWM_CF_T_F_SUPPORT, rwm_cf_gen,
+ "( OLcfgOvAt:16.2 NAME 'olcRwmTFSupport' "
+ "DESC 'Absolute filters support' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "rwm-map", "{objectClass|attribute}",
+ 2, 4, 0, ARG_MAGIC|RWM_CF_MAP, rwm_cf_gen,
+ "( OLcfgOvAt:16.3 NAME 'olcRwmMap' "
+ "DESC 'maps attributes/objectClasses' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+
+ { "rwm-normalize-mapped-attrs", "true|false",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_NORMALIZE_MAPPED, rwm_cf_gen,
+ "( OLcfgOvAt:16.4 NAME 'olcRwmNormalizeMapped' "
+ "DESC 'Normalize mapped attributes/objectClasses' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "rwm-drop-unrequested-attrs", "true|false",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_DROP_UNREQUESTED, rwm_cf_gen,
+ "( OLcfgOvAt:16.5 NAME 'olcRwmDropUnrequested' "
+ "DESC 'Drop unrequested attributes' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs rwmocs[] = {
+ { "( OLcfgOvOc:16.1 "
+ "NAME 'olcRwmConfig' "
+ "DESC 'Rewrite/remap configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcRwmRewrite $ "
+ "olcRwmTFSupport $ "
+ "olcRwmMap $ "
+ "olcRwmNormalizeMapped $ "
+ "olcRwmDropUnrequested"
+ ") )",
+ Cft_Overlay, rwmcfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+rwm_bva_add(
+ BerVarray *bva,
+ int idx,
+ char **argv )
+{
+ char *line;
+ struct berval bv;
+
+ line = ldap_charray2str( argv, "\" \"" );
+ if ( line != NULL ) {
+ int len = strlen( argv[ 0 ] );
+
+ ber_str2bv( line, 0, 0, &bv );
+ AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ],
+ bv.bv_len - ( len + 1 ) );
+ bv.bv_val[ bv.bv_len - 1 ] = '"';
+
+ if ( idx == -1 ) {
+ ber_bvarray_add( bva, &bv );
+
+ } else {
+ (*bva)[ idx ] = bv;
+ }
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+rwm_bva_rewrite_add(
+ struct ldaprwmap *rwmap,
+ int idx,
+ char **argv )
+{
+ return rwm_bva_add( &rwmap->rwm_bva_rewrite, idx, argv );
+}
+
+#ifdef unused
+static int
+rwm_bva_map_add(
+ struct ldaprwmap *rwmap,
+ int idx,
+ char **argv )
+{
+ return rwm_bva_add( &rwmap->rwm_bva_map, idx, argv );
+}
+#endif /* unused */
+
+static int
+rwm_info_init( struct rewrite_info ** rwm_rw )
+{
+ char *rargv[ 3 ];
+
+ *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+ if ( *rwm_rw == NULL ) {
+ return -1;
+ }
+
+ /* this rewriteContext by default must be null;
+ * rules can be added if required */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchFilter";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 2, 2, rargv );
+
+ return 0;
+}
+
+static int
+rwm_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ BackendDB db;
+ char *argv0;
+ int idx0 = 0;
+ int rc = 0;
+
+ db = *c->be;
+ db.bd_info = c->bi;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( rwmap->rwm_bva_rewrite == NULL ) {
+ rc = 1;
+
+ } else {
+ rc = slap_bv_x_ordered_unparse( rwmap->rwm_bva_rewrite, &c->rvalue_vals );
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ enum_to_verb( t_f_mode, (rwmap->rwm_flags & RWM_F_SUPPORT_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case RWM_CF_MAP:
+ if ( rwmap->rwm_bva_map == NULL ) {
+ rc = 1;
+
+ } else {
+ slap_bv_x_ordered_unparse( rwmap->rwm_bva_map, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) {
+ rc = 1;
+ }
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ c->value_int = ( rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS );
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ c->value_int = ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS );
+ break;
+
+ default:
+ assert( 0 );
+ rc = 1;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( c->valx >= 0 ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ /* count'em */ ;
+
+ if ( c->valx >= i ) {
+ rc = 1;
+ break;
+ }
+
+ ber_memfree( rwmap->rwm_bva_rewrite[ c->valx ].bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i + 1 ] ); i++ )
+ {
+ rwmap->rwm_bva_rewrite[ i ] = rwmap->rwm_bva_rewrite[ i + 1 ];
+ }
+ BER_BVZERO( &rwmap->rwm_bva_rewrite[ i ] );
+
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ } else if ( rwmap->rwm_rw != NULL ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ ber_bvarray_free( rwmap->rwm_bva_rewrite );
+ rwmap->rwm_bva_rewrite = NULL;
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2;
+ break;
+
+ case RWM_CF_MAP:
+ if ( c->valx >= 0 ) {
+ struct ldapmap rwm_oc = rwmap->rwm_oc;
+ struct ldapmap rwm_at = rwmap->rwm_at;
+ char *argv[5];
+ int cnt = 0;
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( c->valx >= cnt ) {
+ rc = 1;
+ break;
+ }
+
+ memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) );
+ memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) );
+
+ /* re-parse all mappings except the one
+ * that needs to be eliminated */
+ argv[0] = "map";
+ for ( cnt = 0; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ if ( cnt == c->valx ) {
+ continue;
+ }
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ rwmap->rwm_oc = rwm_oc;
+ rwmap->rwm_at = rwm_at;
+ break;
+ }
+ }
+
+ /* in case of success, destroy the old mapping
+ * and eliminate the deleted one */
+ if ( rc == 0 ) {
+ ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_at.map, rwm_mapping_free );
+
+ ber_memfree( rwmap->rwm_bva_map[ c->valx ].bv_val );
+ for ( cnt = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ rwmap->rwm_bva_map[ cnt ] = rwmap->rwm_bva_map[ cnt + 1 ];
+ }
+ }
+
+ } else {
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+
+ rwmap->rwm_oc.remap = NULL;
+ rwmap->rwm_oc.map = NULL;
+ rwmap->rwm_at.remap = NULL;
+ rwmap->rwm_at.map = NULL;
+
+ ber_bvarray_free( rwmap->rwm_bva_map );
+ rwmap->rwm_bva_map = NULL;
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS;
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS;
+ break;
+
+ default:
+ return 1;
+ }
+ return rc;
+ }
+
+ if ( strncasecmp( c->argv[ 0 ], "olcRwm", STRLENOF( "olcRwm" ) ) == 0 ) {
+ idx0 = 1;
+ }
+
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( c->valx >= 0 ) {
+ struct rewrite_info *rwm_rw = rwmap->rwm_rw;
+ int i, last;
+
+ for ( last = 0; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ last ] ); last++ )
+ /* count'em */ ;
+
+ if ( c->valx > last ) {
+ c->valx = last;
+ }
+
+ rwmap->rwm_rw = NULL;
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ for ( i = 0; i < c->valx; i++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ argv0 = c->argv[ idx0 ];
+ if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) {
+ return 1;
+ }
+ c->argv[ idx0 ] += STRLENOF( "rwm-" );
+ if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+ }
+ c->argv[ idx0 ] = argv0;
+ if ( rc != 0 ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ rwmap->rwm_rw = rwm_rw;
+ return 1;
+ }
+
+ for ( i = c->valx; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ rwmap->rwm_bva_rewrite = ch_realloc( rwmap->rwm_bva_rewrite,
+ ( last + 2 )*sizeof( struct berval ) );
+ BER_BVZERO( &rwmap->rwm_bva_rewrite[last+1] );
+
+ for ( i = last - 1; i >= c->valx; i-- )
+ {
+ rwmap->rwm_bva_rewrite[ i + 1 ] = rwmap->rwm_bva_rewrite[ i ];
+ }
+
+ rwm_bva_rewrite_add( rwmap, c->valx, &c->argv[ idx0 ] );
+
+ rewrite_info_delete( &rwm_rw );
+ assert( rwm_rw == NULL );
+
+ break;
+ }
+
+ argv0 = c->argv[ idx0 ];
+ if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) {
+ return 1;
+ }
+ c->argv[ idx0 ] += STRLENOF( "rwm-" );
+ if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+ }
+ c->argv[ idx0 ] = argv0;
+ if ( rc ) {
+ return 1;
+
+ } else {
+ rwm_bva_rewrite_add( rwmap, -1, &c->argv[ idx0 ] );
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ rc = verb_to_mask( c->argv[ 1 ], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[ rc ].word ) ) {
+ return 1;
+ }
+
+ rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2;
+ rwmap->rwm_flags |= t_f_mode[ rc ].mask;
+ rc = 0;
+ break;
+
+ case RWM_CF_MAP:
+ if ( c->valx >= 0 ) {
+ struct ldapmap rwm_oc = rwmap->rwm_oc;
+ struct ldapmap rwm_at = rwmap->rwm_at;
+ char *argv[5];
+ int cnt = 0;
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( c->valx >= cnt ) {
+ c->valx = cnt;
+ }
+
+ memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) );
+ memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) );
+
+ /* re-parse all mappings, including the one
+ * that needs to be added */
+ argv[0] = "map";
+ for ( cnt = 0; cnt < c->valx; cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+ }
+
+ argv0 = c->argv[0];
+ c->argv[0] = "map";
+ rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[0] = argv0;
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+ }
+ }
+
+ /* in case of success, destroy the old mapping
+ * and add the new one */
+ if ( rc == 0 ) {
+ BerVarray tmp;
+ struct berval bv, *bvp = &bv;
+
+ if ( rwm_bva_add( &bvp, 0, &c->argv[ idx0 ] ) ) {
+ rc = 1;
+ goto rwmmap_fail;
+ }
+
+ tmp = ber_memrealloc( rwmap->rwm_bva_map,
+ sizeof( struct berval )*( cnt + 2 ) );
+ if ( tmp == NULL ) {
+ ber_memfree( bv.bv_val );
+ rc = 1;
+ goto rwmmap_fail;
+ }
+ rwmap->rwm_bva_map = tmp;
+ BER_BVZERO( &rwmap->rwm_bva_map[ cnt + 1 ] );
+
+ ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_at.map, rwm_mapping_free );
+
+ for ( ; cnt-- > c->valx; ) {
+ rwmap->rwm_bva_map[ cnt + 1 ] = rwmap->rwm_bva_map[ cnt ];
+ }
+ rwmap->rwm_bva_map[ c->valx ] = bv;
+
+ } else {
+rwmmap_fail:;
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ rwmap->rwm_oc = rwm_oc;
+ rwmap->rwm_at = rwm_at;
+ }
+
+ break;
+ }
+
+ argv0 = c->argv[ 0 ];
+ c->argv[ 0 ] += STRLENOF( "rwm-" );
+ rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[ 0 ] = argv0;
+ if ( rc ) {
+ return 1;
+
+ } else {
+ char *line;
+ struct berval bv;
+
+ line = ldap_charray2str( &c->argv[ 1 ], " " );
+ if ( line != NULL ) {
+ ber_str2bv( line, 0, 0, &bv );
+ ber_bvarray_add( &rwmap->rwm_bva_map, &bv );
+ }
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ if ( c->value_int ) {
+ rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS;
+ } else {
+ rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS;
+ }
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ if ( c->value_int ) {
+ rwmap->rwm_flags |= RWM_F_DROP_UNREQUESTED_ATTRS;
+ } else {
+ rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ return rc;
+}
+
+static int
+rwm_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap;
+ int rc = 0;
+
+ rwmap = (struct ldaprwmap *)ch_calloc( 1, sizeof( struct ldaprwmap ) );
+
+ /* default */
+ rwmap->rwm_flags = RWM_F_DROP_UNREQUESTED_ATTRS;
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ on->on_bi.bi_private = (void *)rwmap;
+
+ if ( rc ) {
+ (void)rwm_db_destroy( be, NULL );
+ }
+
+ return rc;
+}
+
+static int
+rwm_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ int rc = 0;
+
+ if ( on->on_bi.bi_private ) {
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ if ( rwmap->rwm_rw ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ if ( rwmap->rwm_bva_rewrite )
+ ber_bvarray_free( rwmap->rwm_bva_rewrite );
+ }
+
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ ber_bvarray_free( rwmap->rwm_bva_map );
+
+ ch_free( rwmap );
+ }
+
+ return rc;
+}
+
+static slap_overinst rwm = { { NULL } };
+
+#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */
+int
+rwm_initialize( void )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( RWM_CF_LAST );
+
+ memset( &rwm, 0, sizeof( slap_overinst ) );
+
+ rwm.on_bi.bi_type = "rwm";
+ rwm.on_bi.bi_flags =
+ SLAPO_BFLAG_SINGLE |
+ 0;
+
+ rwm.on_bi.bi_db_init = rwm_db_init;
+ rwm.on_bi.bi_db_config = rwm_db_config;
+ rwm.on_bi.bi_db_destroy = rwm_db_destroy;
+
+ rwm.on_bi.bi_op_bind = rwm_op_bind;
+ rwm.on_bi.bi_op_search = rwm_op_search;
+ rwm.on_bi.bi_op_compare = rwm_op_compare;
+ rwm.on_bi.bi_op_modify = rwm_op_modify;
+ rwm.on_bi.bi_op_modrdn = rwm_op_modrdn;
+ rwm.on_bi.bi_op_add = rwm_op_add;
+ rwm.on_bi.bi_op_delete = rwm_op_delete;
+ rwm.on_bi.bi_op_unbind = rwm_op_unbind;
+ rwm.on_bi.bi_extended = rwm_extended;
+#if 1 /* TODO */
+ rwm.on_bi.bi_entry_release_rw = rwm_entry_release_rw;
+ rwm.on_bi.bi_entry_get_rw = rwm_entry_get_rw;
+#endif
+
+ rwm.on_bi.bi_operational = rwm_operational;
+ rwm.on_bi.bi_chk_referrals = 0 /* rwm_chk_referrals */ ;
+
+ rwm.on_bi.bi_connection_init = rwm_conn_init;
+ rwm.on_bi.bi_connection_destroy = rwm_conn_destroy;
+
+ rwm.on_response = rwm_response;
+
+ rwm.on_bi.bi_cf_ocs = rwmocs;
+
+ rc = config_register_schema( rwmcfg, rwmocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &rwm );
+}
+
+#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return rwm_initialize();
+}
+#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwm.h b/servers/slapd/overlays/rwm.h
new file mode 100644
index 0000000..6753737
--- /dev/null
+++ b/servers/slapd/overlays/rwm.h
@@ -0,0 +1,183 @@
+/* rwm.h - dn rewrite/attribute mapping header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef RWM_H
+#define RWM_H
+
+/* String rewrite library */
+#include "rewrite.h"
+
+LDAP_BEGIN_DECL
+
+/* define to enable referral DN massage by default */
+#undef RWM_REFERRAL_REWRITE
+
+struct ldapmap {
+ int drop_missing;
+
+ Avlnode *map;
+ Avlnode *remap;
+};
+
+struct ldapmapping {
+ int m_flags;
+#define RWMMAP_F_NONE 0x00
+#define RWMMAP_F_IS_OC 0x01
+#define RWMMAP_F_FREE_SRC 0x10
+#define RWMMAP_F_FREE_DST 0x20
+ struct berval m_src;
+ union {
+ AttributeDescription *m_s_ad;
+ ObjectClass *m_s_oc;
+ } m_src_ref;
+#define m_src_ad m_src_ref.m_s_ad
+#define m_src_oc m_src_ref.m_s_oc
+ struct berval m_dst;
+ union {
+ AttributeDescription *m_d_ad;
+ ObjectClass *m_d_oc;
+ } m_dst_ref;
+#define m_dst_ad m_dst_ref.m_d_ad
+#define m_dst_oc m_dst_ref.m_d_oc
+};
+
+struct ldaprwmap {
+ /*
+ * DN rewriting
+ */
+ struct rewrite_info *rwm_rw;
+ BerVarray rwm_bva_rewrite;
+
+ /*
+ * Attribute/objectClass mapping
+ */
+ struct ldapmap rwm_oc;
+ struct ldapmap rwm_at;
+ BerVarray rwm_bva_map;
+
+#define RWM_F_NONE (0x0000U)
+#define RWM_F_NORMALIZE_MAPPED_ATTRS (0x0001U)
+#define RWM_F_DROP_UNREQUESTED_ATTRS (0x0002U)
+#define RWM_F_SUPPORT_T_F (0x4000U)
+#define RWM_F_SUPPORT_T_F_DISCOVER (0x8000U)
+#define RWM_F_SUPPORT_T_F_MASK (RWM_F_SUPPORT_T_F)
+#define RWM_F_SUPPORT_T_F_MASK2 (RWM_F_SUPPORT_T_F|RWM_F_SUPPORT_T_F_DISCOVER)
+ unsigned rwm_flags;
+};
+
+/* Whatever context ldap_back_dn_massage needs... */
+typedef struct dncookie {
+ struct ldaprwmap *rwmap;
+
+ Connection *conn;
+ char *ctx;
+ SlapReply *rs;
+} dncookie;
+
+int rwm_dn_massage( dncookie *dc, struct berval *in, struct berval *dn );
+int rwm_dn_massage_pretty( dncookie *dc, struct berval *in, struct berval *pdn );
+int rwm_dn_massage_normalize( dncookie *dc, struct berval *in, struct berval *ndn );
+int rwm_dn_massage_pretty_normalize( dncookie *dc, struct berval *in, struct berval *pdn, struct berval *ndn );
+
+/* attributeType/objectClass mapping */
+int rwm_mapping_cmp (const void *, const void *);
+int rwm_mapping_dup (void *, void *);
+
+int rwm_map_init ( struct ldapmap *lm, struct ldapmapping ** );
+void rwm_map ( struct ldapmap *map, struct berval *s, struct berval *m,
+ int remap );
+int rwm_mapping ( struct ldapmap *map, struct berval *s,
+ struct ldapmapping **m, int remap );
+#define RWM_MAP 0
+#define RWM_REMAP 1
+char *
+rwm_map_filter(
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ struct berval *f );
+
+#if 0 /* unused! */
+int
+rwm_map_attrs(
+ struct ldapmap *at_map,
+ AttributeName *a,
+ int remap,
+ char ***mapped_attrs );
+#endif
+
+int
+rwm_map_attrnames(
+ Operation *op,
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ AttributeName *an,
+ AttributeName **anp,
+ int remap );
+
+extern void rwm_mapping_dst_free ( void *mapping );
+
+extern void rwm_mapping_free ( void *mapping );
+
+extern int rwm_map_config(
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv );
+
+extern int
+rwm_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr );
+
+/* suffix massaging by means of librewrite */
+extern int
+rwm_suffix_massage_config(
+ struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc);
+extern int
+rwm_dnattr_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals );
+extern int
+rwm_referral_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals );
+extern int rwm_dnattr_result_rewrite( dncookie *dc, BerVarray a_vals, BerVarray a_nvals );
+extern int rwm_referral_result_rewrite( dncookie *dc, BerVarray a_vals );
+
+LDAP_END_DECL
+
+#endif /* RWM_H */
diff --git a/servers/slapd/overlays/rwmconf.c b/servers/slapd/overlays/rwmconf.c
new file mode 100644
index 0000000..a1a9f36
--- /dev/null
+++ b/servers/slapd/overlays/rwmconf.c
@@ -0,0 +1,413 @@
+/* rwmconf.c - rewrite/map configuration file routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+#include "lutil.h"
+
+int
+rwm_map_config(
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ struct ldapmap *map;
+ struct ldapmapping *mapping;
+ char *src, *dst;
+ int is_oc = 0;
+ int rc = 0;
+
+ if ( argc < 3 || argc > 4 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: syntax is \"map {objectclass | attribute} [<local> | *] {<foreign> | *}\"\n",
+ fname, lineno );
+ return 1;
+ }
+
+ if ( strcasecmp( argv[1], "objectclass" ) == 0 ) {
+ map = oc_map;
+ is_oc = 1;
+
+ } else if ( strcasecmp( argv[1], "attribute" ) == 0 ) {
+ map = at_map;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is "
+ "\"map {objectclass | attribute} [<local> | *] "
+ "{<foreign> | *}\"\n",
+ fname, lineno );
+ return 1;
+ }
+
+ if ( !is_oc && map->map == NULL ) {
+ /* only init if required */
+ if ( rwm_map_init( map, &mapping ) != LDAP_SUCCESS ) {
+ return 1;
+ }
+ }
+
+ if ( strcmp( argv[2], "*" ) == 0 ) {
+ if ( argc < 4 || strcmp( argv[3], "*" ) == 0 ) {
+ map->drop_missing = ( argc < 4 );
+ goto success_return;
+ }
+ src = dst = argv[3];
+
+ } else if ( argc < 4 ) {
+ src = "";
+ dst = argv[2];
+
+ } else {
+ src = argv[2];
+ dst = ( strcmp( argv[3], "*" ) == 0 ? src : argv[3] );
+ }
+
+ if ( ( map == at_map )
+ && ( strcasecmp( src, "objectclass" ) == 0
+ || strcasecmp( dst, "objectclass" ) == 0 ) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: objectclass attribute cannot be mapped\n",
+ fname, lineno );
+ return 1;
+ }
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof(struct ldapmapping) );
+ if ( mapping == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: out of memory\n",
+ fname, lineno );
+ return 1;
+ }
+ ber_str2bv( src, 0, 1, &mapping[0].m_src );
+ ber_str2bv( dst, 0, 1, &mapping[0].m_dst );
+ mapping[1].m_src = mapping[0].m_dst;
+ mapping[1].m_dst = mapping[0].m_src;
+
+ mapping[0].m_flags = RWMMAP_F_NONE;
+ mapping[1].m_flags = RWMMAP_F_NONE;
+
+ /*
+ * schema check
+ */
+ if ( is_oc ) {
+ if ( src[0] != '\0' ) {
+ mapping[0].m_src_oc = oc_bvfind( &mapping[0].m_src );
+ if ( mapping[0].m_src_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, source objectClass '%s' "
+ "should be defined in schema\n",
+ fname, lineno, src );
+
+ /*
+ * FIXME: this should become an err
+ */
+ mapping[0].m_src_oc = ch_malloc( sizeof( ObjectClass ) );
+ memset( mapping[0].m_src_oc, 0, sizeof( ObjectClass ) );
+ mapping[0].m_src_oc->soc_cname = mapping[0].m_src;
+ mapping[0].m_flags |= RWMMAP_F_FREE_SRC;
+ }
+ mapping[1].m_dst_oc = mapping[0].m_src_oc;
+ }
+
+ mapping[0].m_dst_oc = oc_bvfind( &mapping[0].m_dst );
+ if ( mapping[0].m_dst_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, destination objectClass '%s' "
+ "is not defined in schema\n",
+ fname, lineno, dst );
+
+ mapping[0].m_dst_oc = oc_bvfind_undef( &mapping[0].m_dst );
+ if ( mapping[0].m_dst_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: unable to mimic destination objectClass '%s'\n",
+ fname, lineno, dst );
+ goto error_return;
+ }
+ }
+ mapping[1].m_src_oc = mapping[0].m_dst_oc;
+
+ mapping[0].m_flags |= RWMMAP_F_IS_OC;
+ mapping[1].m_flags |= RWMMAP_F_IS_OC;
+
+ } else {
+ int rc;
+ const char *text = NULL;
+
+ if ( src[0] != '\0' ) {
+ rc = slap_bv2ad( &mapping[0].m_src,
+ &mapping[0].m_src_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, source attributeType '%s' "
+ "should be defined in schema\n",
+ fname, lineno, src );
+
+ /*
+ * we create a fake "proxied" ad
+ * and add it here.
+ */
+
+ rc = slap_bv2undef_ad( &mapping[0].m_src,
+ &mapping[0].m_src_ad, &text,
+ SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: line %d: source attributeType '%s': %d (%s)\n",
+ fname, lineno, src, rc,
+ text ? text : "null" );
+ goto error_return;
+ }
+
+ }
+ mapping[1].m_dst_ad = mapping[0].m_src_ad;
+ }
+
+ rc = slap_bv2ad( &mapping[0].m_dst, &mapping[0].m_dst_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, destination attributeType '%s' "
+ "is not defined in schema\n",
+ fname, lineno, dst );
+
+ rc = slap_bv2undef_ad( &mapping[0].m_dst,
+ &mapping[0].m_dst_ad, &text,
+ SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: line %d: destination attributeType '%s': %d (%s)\n",
+ fname, lineno, dst, rc,
+ text ? text : "null" );
+ goto error_return;
+ }
+ }
+ mapping[1].m_src_ad = mapping[0].m_dst_ad;
+ }
+
+ if ( ( src[0] != '\0' && ldap_avl_find( map->map, (caddr_t)mapping, rwm_mapping_cmp ) != NULL)
+ || ldap_avl_find( map->remap, (caddr_t)&mapping[1], rwm_mapping_cmp ) != NULL)
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: duplicate mapping found.\n",
+ fname, lineno );
+ /* FIXME: free stuff */
+ goto error_return;
+ }
+
+ if ( src[0] != '\0' ) {
+ ldap_avl_insert( &map->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ }
+ ldap_avl_insert( &map->remap, (caddr_t)&mapping[1],
+ rwm_mapping_cmp, rwm_mapping_dup );
+
+success_return:;
+ return rc;
+
+error_return:;
+ if ( mapping ) {
+ rwm_mapping_free( mapping );
+ }
+
+ return 1;
+}
+
+static char *
+rwm_suffix_massage_regexize( const char *s )
+{
+ char *res, *ptr;
+ const char *p, *r;
+ int i;
+
+ if ( s[0] == '\0' ) {
+ return ch_strdup( "^(.+)$" );
+ }
+
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1, i++ )
+ ;
+
+ res = ch_calloc( sizeof( char ), strlen( s )
+ + STRLENOF( "((.+),)?" )
+ + STRLENOF( "[ ]?" ) * i
+ + STRLENOF( "$" ) + 1 );
+
+ ptr = lutil_strcopy( res, "((.+),)?" );
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1 , i++ ) {
+ ptr = lutil_strncopy( ptr, p, r - p + 1 );
+ ptr = lutil_strcopy( ptr, "[ ]?" );
+
+ if ( r[ 1 ] == ' ' ) {
+ r++;
+ }
+ }
+ ptr = lutil_strcopy( ptr, p );
+ ptr[0] = '$';
+ ptr[1] = '\0';
+
+ return res;
+}
+
+static char *
+rwm_suffix_massage_patternize( const char *s, const char *p )
+{
+ ber_len_t len;
+ char *res, *ptr;
+
+ len = strlen( p );
+
+ if ( s[ 0 ] == '\0' ) {
+ len++;
+ }
+
+ res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 );
+ if ( res == NULL ) {
+ return NULL;
+ }
+
+ ptr = lutil_strcopy( res, ( p[0] == '\0' ? "%2" : "%1" ) );
+ if ( s[ 0 ] == '\0' ) {
+ ptr[ 0 ] = ',';
+ ptr++;
+ }
+ lutil_strcopy( ptr, p );
+
+ return res;
+}
+
+int
+rwm_suffix_massage_config(
+ struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc
+)
+{
+ char *rargv[ 5 ];
+ int line = 0;
+
+ rargv[ 0 ] = "rewriteEngine";
+ rargv[ 1 ] = "on";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = rwm_suffix_massage_regexize( pvnc->bv_val );
+ rargv[ 2 ] = rwm_suffix_massage_patternize( pvnc->bv_val, prnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( pvnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = prnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchEntryDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = rwm_suffix_massage_regexize( prnc->bv_val );
+ rargv[ 2 ] = rwm_suffix_massage_patternize( prnc->bv_val, pvnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( prnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = pvnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "matchedDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+#ifdef RWM_REFERRAL_REWRITE
+ /* FIXME: we don't want this on by default, do we? */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+#else /* ! RWM_REFERRAL_REWRITE */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralAttrDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+#endif /* ! RWM_REFERRAL_REWRITE */
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchAttrDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+ return 0;
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwmdn.c b/servers/slapd/overlays/rwmdn.c
new file mode 100644
index 0000000..c67e3cf
--- /dev/null
+++ b/servers/slapd/overlays/rwmdn.c
@@ -0,0 +1,215 @@
+/* rwmdn.c - massages dns */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+
+/* FIXME: after rewriting, we should also remap attributes ... */
+
+/*
+ * massages "in" and normalizes it into "ndn"
+ *
+ * "ndn" may be untouched if no massaging occurred and its value was not null
+ */
+int
+rwm_dn_massage_normalize(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *ndn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage and normalize a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( ndn ) ) {
+ return rc;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" and prettifies it into "pdn"
+ *
+ * "pdn" may be untouched if no massaging occurred and its value was not null
+ */
+int
+rwm_dn_massage_pretty(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *pdn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage and pretty a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) {
+ return rc;
+ }
+
+ rc = dnPretty( NULL, &mdn, pdn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" and prettifies and normalizes it into "pdn" and "ndn"
+ *
+ * "pdn" may be untouched if no massaging occurred and its value was not null;
+ * "ndn" may be untouched if no massaging occurred and its value was not null;
+ * if no massage occurred and "ndn" value was not null, it is filled
+ * with the normalized value of "pdn", much like ndn = dnNormalize( pdn )
+ */
+int
+rwm_dn_massage_pretty_normalize(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *pdn,
+ struct berval *ndn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage, pretty and normalize a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) {
+ if ( BER_BVISNULL( ndn ) ) {
+ rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL );
+ }
+ return rc;
+ }
+
+ rc = dnPrettyNormal( NULL, &mdn, pdn, ndn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" into "dn"
+ *
+ * "dn" may contain the value of "in" if no massage occurred
+ */
+int
+rwm_dn_massage(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *dn
+)
+{
+ int rc = 0;
+ struct berval mdn;
+ static char *dmy = "";
+ char *in_val;
+
+ assert( dc != NULL );
+ assert( in != NULL );
+ assert( dn != NULL );
+
+ /* protect from NULL berval */
+ in_val = in->bv_val ? in->bv_val : dmy;
+
+ rc = rewrite_session( dc->rwmap->rwm_rw, dc->ctx,
+ in_val, dc->conn, &mdn.bv_val );
+ switch ( rc ) {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( &mdn ) && mdn.bv_val != in_val ) {
+ mdn.bv_len = strlen( mdn.bv_val );
+ *dn = mdn;
+ } else {
+ dn->bv_len = in->bv_len;
+ dn->bv_val = in_val;
+ }
+ rc = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ dc->ctx, in_val, dn->bv_val );
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ dc->rs->sr_text = "Operation not allowed";
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_OTHER;
+ dc->rs->sr_text = "Rewrite error";
+ }
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ if ( mdn.bv_val == dmy ) {
+ BER_BVZERO( &mdn );
+ }
+
+ if ( dn->bv_val == dmy ) {
+ BER_BVZERO( dn );
+ }
+
+ return rc;
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwmmap.c b/servers/slapd/overlays/rwmmap.c
new file mode 100644
index 0000000..74ffd05
--- /dev/null
+++ b/servers/slapd/overlays/rwmmap.c
@@ -0,0 +1,1347 @@
+/* rwmmap.c - rewrite/mapping routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+
+#undef ldap_debug /* silence a warning in ldap-int.h */
+#include "../../../libraries/libldap/ldap-int.h"
+
+int
+rwm_mapping_cmp( const void *c1, const void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+ int rc = map1->m_src.bv_len - map2->m_src.bv_len;
+
+ if ( rc ) {
+ return rc;
+ }
+
+ return strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val );
+}
+
+int
+rwm_mapping_dup( void *c1, void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+ int rc = map1->m_src.bv_len - map2->m_src.bv_len;
+
+ if ( rc ) {
+ return 0;
+ }
+
+ return ( ( strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ) == 0 ) ? -1 : 0 );
+}
+
+int
+rwm_map_init( struct ldapmap *lm, struct ldapmapping **m )
+{
+ struct ldapmapping *mapping;
+ const char *text;
+ int rc;
+
+ assert( m != NULL );
+
+ *m = NULL;
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof( struct ldapmapping ) );
+ if ( mapping == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ /* NOTE: this is needed to make sure that
+ * rwm-map attribute *
+ * does not filter out all attributes including objectClass */
+ rc = slap_str2ad( "objectClass", &mapping[0].m_src_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ ch_free( mapping );
+ return rc;
+ }
+
+ mapping[0].m_dst_ad = mapping[0].m_src_ad;
+ ber_dupbv( &mapping[0].m_src, &mapping[0].m_src_ad->ad_cname );
+ ber_dupbv( &mapping[0].m_dst, &mapping[0].m_src );
+
+ mapping[1].m_src = mapping[0].m_src;
+ mapping[1].m_dst = mapping[0].m_dst;
+ mapping[1].m_src_ad = mapping[0].m_src_ad;
+ mapping[1].m_dst_ad = mapping[1].m_src_ad;
+
+ ldap_avl_insert( &lm->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ ldap_avl_insert( &lm->remap, (caddr_t)&mapping[1],
+ rwm_mapping_cmp, rwm_mapping_dup );
+
+ *m = mapping;
+
+ return rc;
+}
+
+int
+rwm_mapping( struct ldapmap *map, struct berval *s, struct ldapmapping **m, int remap )
+{
+ Avlnode *tree;
+ struct ldapmapping fmapping;
+
+ if ( map == NULL ) {
+ return 0;
+ }
+
+ assert( m != NULL );
+
+ /* let special attrnames slip through (ITS#5760) */
+ if ( bvmatch( s, slap_bv_no_attrs )
+ || bvmatch( s, slap_bv_all_user_attrs )
+ || bvmatch( s, slap_bv_all_operational_attrs ) )
+ {
+ *m = NULL;
+ return 0;
+ }
+
+ if ( remap == RWM_REMAP ) {
+ tree = map->remap;
+
+ } else {
+ tree = map->map;
+ }
+
+ fmapping.m_src = *s;
+ *m = (struct ldapmapping *)ldap_avl_find( tree, (caddr_t)&fmapping,
+ rwm_mapping_cmp );
+
+ if ( *m == NULL ) {
+ return map->drop_missing;
+ }
+
+ return 0;
+}
+
+void
+rwm_map( struct ldapmap *map, struct berval *s, struct berval *bv, int remap )
+{
+ struct ldapmapping *mapping;
+
+ /* map->map may be NULL when mapping is configured,
+ * but map->remap can't */
+ if ( map->remap == NULL ) {
+ *bv = *s;
+ return;
+ }
+
+ BER_BVZERO( bv );
+ ( void )rwm_mapping( map, s, &mapping, remap );
+ if ( mapping != NULL ) {
+ if ( !BER_BVISNULL( &mapping->m_dst ) ) {
+ *bv = mapping->m_dst;
+ }
+ return;
+ }
+
+ if ( !map->drop_missing ) {
+ *bv = *s;
+ }
+}
+
+/*
+ * Map attribute names in place
+ */
+int
+rwm_map_attrnames(
+ Operation *op,
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ AttributeName *an,
+ AttributeName **anp,
+ int remap )
+{
+ int i, j, x;
+
+ assert( anp != NULL );
+
+ *anp = NULL;
+
+ if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ i = 0;
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ )
+ /* just count */ ;
+ }
+
+ x = 0;
+ if ( op->o_bd->be_extra_anlist ) {
+ for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ )
+ /* just count */ ;
+ }
+
+ assert( i > 0 || x > 0 );
+ *anp = op->o_tmpcalloc( ( i + x + 1 ), sizeof( AttributeName ),
+ op->o_tmpmemctx );
+ if ( *anp == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ j = 0;
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ struct ldapmapping *m;
+ int at_drop_missing = 0,
+ oc_drop_missing = 0;
+
+ if ( an[i].an_desc ) {
+ if ( !at_map ) {
+ /* FIXME: better leave as is? */
+ continue;
+ }
+
+ at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap );
+ if ( at_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) {
+ continue;
+ }
+
+ if ( !m ) {
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_desc = m->m_dst_ad;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_desc = m->m_src_ad;
+
+ }
+
+ j++;
+ continue;
+
+ } else if ( an[i].an_oc ) {
+ if ( !oc_map ) {
+ /* FIXME: better leave as is? */
+ continue;
+ }
+
+ oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap );
+
+ if ( oc_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) {
+ continue;
+ }
+
+ if ( !m ) {
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_oc = m->m_dst_oc;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_oc = m->m_src_oc;
+ }
+
+ } else {
+ at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap );
+
+ if ( at_drop_missing || !m ) {
+ oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap );
+
+ /* if both at_map and oc_map required to drop missing,
+ * then do it */
+ if ( oc_drop_missing && at_drop_missing ) {
+ continue;
+ }
+
+ /* if no oc_map mapping was found and at_map required
+ * to drop missing, then do it; otherwise, at_map wins
+ * and an is considered an attr and is left unchanged */
+ if ( !m ) {
+ if ( at_drop_missing ) {
+ continue;
+ }
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ if ( BER_BVISNULL( &m->m_dst ) ) {
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_oc = m->m_dst_oc;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_oc = m->m_src_oc;
+ }
+ j++;
+ continue;
+ }
+
+ if ( !BER_BVISNULL( &m->m_dst ) ) {
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_desc = m->m_dst_ad;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_desc = m->m_src_ad;
+ }
+ j++;
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( op->o_bd->be_extra_anlist != NULL ) {
+ /* we assume be_extra_anlist are already mapped */
+ for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) {
+ BER_BVZERO( &(*anp)[j].an_name );
+ if ( op->o_bd->be_extra_anlist[x].an_desc &&
+ ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, *anp ) )
+ {
+ continue;
+ }
+
+ (*anp)[j] = op->o_bd->be_extra_anlist[x];
+ j++;
+ }
+ }
+
+ if ( j == 0 && ( i != 0 || x != 0 ) ) {
+ memset( &(*anp)[0], 0, sizeof( AttributeName ) );
+ (*anp)[0].an_name = *slap_bv_no_attrs;
+ j = 1;
+ }
+ memset( &(*anp)[j], 0, sizeof( AttributeName ) );
+
+ return LDAP_SUCCESS;
+}
+
+#if 0 /* unused! */
+int
+rwm_map_attrs(
+ struct ldapmap *at_map,
+ AttributeName *an,
+ int remap,
+ char ***mapped_attrs )
+{
+ int i, j;
+ char **na;
+
+ if ( an == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ); i++ )
+ /* count'em */ ;
+
+ na = (char **)ch_calloc( i + 1, sizeof( char * ) );
+ if ( na == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = j = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ struct ldapmapping *mapping;
+
+ if ( rwm_mapping( at_map, &an[i].an_name, &mapping, remap ) ) {
+ continue;
+ }
+
+ if ( !mapping ) {
+ na[ j++ ] = an[ i ].an_name.bv_val;
+
+ } else if ( !BER_BVISNULL( &mapping->m_dst ) ) {
+ na[ j++ ] = mapping->m_dst.bv_val;
+ }
+ }
+
+ if ( j == 0 && i != 0 ) {
+ na[ j++ ] = LDAP_NO_ATTRS;
+ }
+
+ na[ j ] = NULL;
+
+ *mapped_attrs = na;
+
+ return LDAP_SUCCESS;
+}
+#endif
+
+static int
+map_attr_value(
+ dncookie *dc,
+ AttributeDescription **adp,
+ struct berval *mapped_attr,
+ struct berval *value,
+ struct berval *mapped_value,
+ int remap,
+ void *memctx )
+{
+ struct berval vtmp = BER_BVNULL;
+ int freeval = 0;
+ AttributeDescription *ad = *adp;
+ struct ldapmapping *mapping = NULL;
+
+ rwm_mapping( &dc->rwmap->rwm_at, &ad->ad_cname, &mapping, remap );
+ if ( mapping == NULL ) {
+ if ( dc->rwmap->rwm_at.drop_missing ) {
+ return -1;
+ }
+
+ *mapped_attr = ad->ad_cname;
+
+ } else {
+ *mapped_attr = mapping->m_dst;
+ }
+
+ if ( value != NULL ) {
+ assert( mapped_value != NULL );
+
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ dncookie fdc = *dc;
+ int rc;
+
+ fdc.ctx = "searchFilterAttrDN";
+
+ vtmp = *value;
+ rc = rwm_dn_massage_normalize( &fdc, value, &vtmp );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ if ( vtmp.bv_val != value->bv_val ) {
+ freeval = 1;
+ }
+ break;
+
+ case LDAP_UNWILLING_TO_PERFORM:
+ case LDAP_OTHER:
+ default:
+ return -1;
+ }
+
+ } else if ( ad->ad_type->sat_equality &&
+ ( ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) )
+ {
+ if ( ad->ad_type->sat_equality->smr_normalize(
+ (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX),
+ NULL, NULL, value, &vtmp, memctx ) )
+ {
+ return -1;
+ }
+ freeval = 2;
+
+ } else if ( ad == slap_schema.si_ad_objectClass
+ || ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ rwm_map( &dc->rwmap->rwm_oc, value, &vtmp, remap );
+ if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) {
+ vtmp = *value;
+ }
+
+ } else {
+ vtmp = *value;
+ }
+
+ filter_escape_value_x( &vtmp, mapped_value, memctx );
+
+ switch ( freeval ) {
+ case 1:
+ ch_free( vtmp.bv_val );
+ break;
+
+ case 2:
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+ }
+ }
+
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+ *adp = mapping->m_dst_ad;
+ }
+
+ return 0;
+}
+
+static int
+rwm_int_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int i;
+ Filter *p, ftmp;
+ AttributeDescription *ad;
+ struct berval atmp,
+ vtmp,
+ *tmp;
+ static struct berval
+ /* better than nothing... */
+ ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+ ber_bvtf_false = BER_BVC( "(|)" ),
+ /* better than nothing... */
+ ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+ ber_bvtf_true = BER_BVC( "(&)" ),
+#if 0
+ /* no longer needed; preserved for completeness */
+ ber_bvundefined = BER_BVC( "(?=undefined)" ),
+#endif
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" );
+ ber_len_t len;
+
+ assert( fstr != NULL );
+ BER_BVZERO( fstr );
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx );
+ return LDAP_OTHER;
+ }
+
+#if 0
+ /* ITS#6814: give the caller a chance to use undefined filters */
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ goto computed;
+ }
+#endif
+
+ switch ( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(>=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(<=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(~=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ ad = f->f_sub_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ NULL, NULL, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ /* cannot be a DN ... */
+
+ fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &vtmp, op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3,
+ /* "(attr=" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i], &vtmp,
+ op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len + 1;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &vtmp, op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ NULL, NULL, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF( "(%)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ int rc;
+
+ len = fstr->bv_len;
+
+ rc = rwm_int_filter_map_rewrite( op, dc, p, &vtmp );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+ /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT: {
+ if ( f->f_mr_desc ) {
+ ad = f->f_mr_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_mr_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ } else {
+ BER_BVSTR( &atmp, "" );
+ filter_escape_value_x( &f->f_mr_value, &vtmp, op->o_tmpmemctx );
+ }
+
+
+ fstr->bv_len = atmp.bv_len +
+ ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+ ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+ vtmp.bv_len + STRLENOF( "(:=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ atmp.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+ }
+
+ case -1:
+computed:;
+ f = &ftmp;
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = SLAPD_COMPARE_UNDEFINED;
+ /* fallthru */
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ case LDAP_COMPARE_FALSE:
+ /* FIXME: treat UNDEFINED as FALSE */
+ case SLAPD_COMPARE_UNDEFINED:
+ if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) {
+ tmp = &ber_bvtf_false;
+ break;
+ }
+ tmp = &ber_bvfalse;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) {
+ tmp = &ber_bvtf_true;
+ break;
+ }
+ tmp = &ber_bvtrue;
+ break;
+
+ default:
+ tmp = &ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, tmp, op->o_tmpmemctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx );
+ break;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+rwm_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int rc;
+ dncookie fdc;
+ struct berval ftmp;
+
+ rc = rwm_int_filter_map_rewrite( op, dc, f, fstr );
+
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ fdc = *dc;
+ ftmp = *fstr;
+
+ fdc.ctx = "searchFilter";
+
+ switch ( rewrite_session( fdc.rwmap->rwm_rw, fdc.ctx,
+ ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : "" ),
+ fdc.conn, &fstr->bv_val ) )
+ {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( fstr ) ) {
+ fstr->bv_len = strlen( fstr->bv_val );
+
+ } else {
+ *fstr = ftmp;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ fdc.ctx, ftmp.bv_val, fstr->bv_val );
+ if ( fstr->bv_val != ftmp.bv_val ) {
+ ber_bvreplace_x( &ftmp, fstr, op->o_tmpmemctx );
+ ch_free( fstr->bv_val );
+ *fstr = ftmp;
+ }
+ rc = LDAP_SUCCESS;
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ fdc.rs->sr_text = "Operation not allowed";
+ }
+ op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_OTHER;
+ fdc.rs->sr_text = "Rewrite error";
+ }
+ op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+rwm_referral_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int i, last;
+
+ dncookie dc;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+
+ assert( a_vals != NULL );
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+ ;
+ last--;
+
+ if ( pa_nvals != NULL ) {
+ if ( *pa_nvals == NULL ) {
+ *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) );
+ memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) );
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval olddn = BER_BVNULL,
+ oldval;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ oldval = a_vals[i];
+ rc = ldap_url_parse( oldval.bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ber_dupbv( &(*pa_nvals)[i], &oldval );
+ }
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ dn = olddn;
+ if ( pa_nvals ) {
+ ndn = olddn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &olddn,
+ &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_pretty( &dc, &olddn, &dn );
+ }
+
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( a_vals[i].bv_val );
+ if (last > i ) {
+ a_vals[i] = a_vals[last];
+ if ( pa_nvals ) {
+ (*pa_nvals)[i] = (*pa_nvals)[last];
+ }
+ }
+ BER_BVZERO( &a_vals[last] );
+ if ( pa_nvals ) {
+ BER_BVZERO( &(*pa_nvals)[last] );
+ }
+ last--;
+ break;
+
+ case LDAP_SUCCESS:
+ if ( !BER_BVISNULL( &dn ) && dn.bv_val != olddn.bv_val ) {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ ludp->lud_dn = olddn.bv_val;
+ ch_free( dn.bv_val );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ break;
+ }
+
+ ber_str2bv( newurl, 0, 1, &a_vals[i] );
+ ber_memfree( newurl );
+
+ if ( pa_nvals ) {
+ ludp->lud_dn = ndn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ ludp->lud_dn = olddn.bv_val;
+ ch_free( ndn.bv_val );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = oldval;
+ break;
+ }
+
+ if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ }
+ ber_str2bv( newurl, 0, 1, &(*pa_nvals)[i] );
+ ber_memfree( newurl );
+ }
+
+ ch_free( oldval.bv_val );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ber_dupbv( &(*pa_nvals)[i], &a_vals[i] );
+ }
+ break;
+ }
+ ldap_free_urldesc( ludp );
+ }
+
+ return 0;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+rwm_dnattr_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int i, last;
+
+ dncookie dc;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ BerVarray in;
+
+ if ( a_vals ) {
+ in = a_vals;
+
+ } else {
+ if ( pa_nvals == NULL || *pa_nvals == NULL ) {
+ return LDAP_OTHER;
+ }
+ in = *pa_nvals;
+ }
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ for ( last = 0; !BER_BVISNULL( &in[last] ); last++ );
+ last--;
+ if ( pa_nvals != NULL ) {
+ if ( *pa_nvals == NULL ) {
+ *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) );
+ memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) );
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+ int rc;
+
+ if ( a_vals ) {
+ dn = in[i];
+ if ( pa_nvals ) {
+ ndn = (*pa_nvals)[i];
+ rc = rwm_dn_massage_pretty_normalize( &dc, &in[i], &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_pretty( &dc, &in[i], &dn );
+ }
+ } else {
+ ndn = in[i];
+ rc = rwm_dn_massage_normalize( &dc, &in[i], &ndn );
+ }
+
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( in[i].bv_val );
+ if (last > i ) {
+ in[i] = in[last];
+ if ( a_vals && pa_nvals ) {
+ (*pa_nvals)[i] = (*pa_nvals)[last];
+ }
+ }
+ BER_BVZERO( &in[last] );
+ if ( a_vals && pa_nvals ) {
+ BER_BVZERO( &(*pa_nvals)[last] );
+ }
+ last--;
+ break;
+
+ case LDAP_SUCCESS:
+ if ( a_vals ) {
+ if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) {
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = dn;
+
+ if ( pa_nvals ) {
+ if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ }
+ (*pa_nvals)[i] = ndn;
+ }
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &ndn ) && ndn.bv_val != (*pa_nvals)[i].bv_val ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ (*pa_nvals)[i] = ndn;
+ }
+ }
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( a_vals && pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ dnNormalize( 0, NULL, NULL, &a_vals[i], &(*pa_nvals)[i], NULL );
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int
+rwm_referral_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals )
+{
+ int i, last;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval dn,
+ olddn = BER_BVNULL;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ rc = ldap_url_parse( a_vals[i].bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ dn = olddn;
+ rc = rwm_dn_massage_pretty( dc, &olddn, &dn );
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( a_vals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ last--;
+ i--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ break;
+ }
+
+ ch_free( a_vals[i].bv_val );
+ ber_str2bv( newurl, 0, 1, &a_vals[i] );
+ ber_memfree( newurl );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ break;
+ }
+
+ ldap_free_urldesc( ludp );
+ }
+
+ return 0;
+}
+
+int
+rwm_dnattr_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals,
+ BerVarray a_nvals )
+{
+ int i, last;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval pdn, ndn = BER_BVNULL;
+ int rc;
+
+ pdn = a_vals[i];
+ rc = rwm_dn_massage_pretty_normalize( dc, &a_vals[i], &pdn, &ndn );
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ assert( a_vals[i].bv_val != a_nvals[i].bv_val );
+ ch_free( a_vals[i].bv_val );
+ ch_free( a_nvals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ a_nvals[i] = a_nvals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ BER_BVZERO( &a_nvals[last] );
+ last--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &pdn ) && a_vals[i].bv_val != pdn.bv_val ) {
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = pdn;
+ }
+ if ( !BER_BVISNULL( &ndn ) && a_nvals[i].bv_val != ndn.bv_val ) {
+ ch_free( a_nvals[i].bv_val );
+ a_nvals[i] = ndn;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void
+rwm_mapping_dst_free( void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+
+ if ( BER_BVISEMPTY( &mapping[0].m_dst ) ) {
+ rwm_mapping_free( &mapping[ -1 ] );
+ }
+}
+
+void
+rwm_mapping_free( void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+
+ if ( !BER_BVISNULL( &mapping[0].m_src ) ) {
+ ch_free( mapping[0].m_src.bv_val );
+ }
+
+ if ( mapping[0].m_flags & RWMMAP_F_FREE_SRC ) {
+ if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) {
+ if ( mapping[0].m_src_oc ) {
+ ch_free( mapping[0].m_src_oc );
+ }
+
+ } else {
+ if ( mapping[0].m_src_ad ) {
+ ch_free( mapping[0].m_src_ad );
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &mapping[0].m_dst ) ) {
+ ch_free( mapping[0].m_dst.bv_val );
+ }
+
+ if ( mapping[0].m_flags & RWMMAP_F_FREE_DST ) {
+ if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) {
+ if ( mapping[0].m_dst_oc ) {
+ ch_free( mapping[0].m_dst_oc );
+ }
+
+ } else {
+ if ( mapping[0].m_dst_ad ) {
+ ch_free( mapping[0].m_dst_ad );
+ }
+ }
+ }
+
+ ch_free( mapping );
+
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/seqmod.c b/servers/slapd/overlays/seqmod.c
new file mode 100644
index 0000000..503d6a6
--- /dev/null
+++ b/servers/slapd/overlays/seqmod.c
@@ -0,0 +1,207 @@
+/* seqmod.c - sequenced modifies */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SEQMOD
+
+#include "slap.h"
+#include "slap-config.h"
+
+/* This overlay serializes concurrent attempts to modify a single entry */
+
+typedef struct modtarget {
+ struct modtarget *mt_next;
+ struct modtarget *mt_tail;
+ Operation *mt_op;
+} modtarget;
+
+typedef struct seqmod_info {
+ Avlnode *sm_mods; /* entries being modified */
+ ldap_pvt_thread_mutex_t sm_mutex;
+} seqmod_info;
+
+static int
+sm_avl_cmp( const void *c1, const void *c2 )
+{
+ const modtarget *m1, *m2;
+ int rc;
+
+ m1 = c1; m2 = c2;
+ rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len;
+
+ if ( rc ) return rc;
+ return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn );
+}
+
+static int
+seqmod_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ seqmod_info *sm = sc->sc_private;
+ modtarget *mt, mtdummy;
+ Avlnode *av;
+
+ mtdummy.mt_op = op;
+ /* This op is done, remove it */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ av = ldap_avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp );
+ assert(av != NULL);
+
+ mt = av->avl_data;
+
+ /* If there are more, promote the next one */
+ if ( mt->mt_next ) {
+ av->avl_data = mt->mt_next;
+ mt->mt_next->mt_tail = mt->mt_tail;
+ } else {
+ ldap_avl_delete( &sm->sm_mods, mt, sm_avl_cmp );
+ }
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ return 0;
+}
+
+static int
+seqmod_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ seqmod_info *sm = on->on_bi.bi_private;
+ modtarget *mt;
+ Avlnode *av;
+ slap_callback *cb;
+
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget),
+ op->o_tmpmemctx );
+ mt = (modtarget *)(cb+1);
+ mt->mt_next = NULL;
+ mt->mt_tail = mt;
+ mt->mt_op = op;
+
+ /* See if we're already modifying this entry - don't allow
+ * near-simultaneous mods of the same entry
+ */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ av = ldap_avl_find2( sm->sm_mods, mt, sm_avl_cmp );
+ if ( av ) {
+ modtarget *mtp = av->avl_data;
+ mtp->mt_tail->mt_next = mt;
+ mtp->mt_tail = mt;
+ /* Wait for this op to get to head of list */
+ while ( mtp != mt ) {
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+ ldap_pvt_thread_yield();
+ /* Let it finish - should use a condition
+ * variable here... */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ mtp = av->avl_data;
+ }
+ } else {
+ /* Record that we're modifying this now */
+ ldap_avl_insert( &sm->sm_mods, mt, sm_avl_cmp, ldap_avl_dup_error );
+ }
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+
+ cb->sc_cleanup = seqmod_op_cleanup;
+ cb->sc_private = sm;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+seqmod_op_extended(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ if ( exop_is_write( op )) return seqmod_op_mod( op, rs );
+ else return SLAP_CB_CONTINUE;
+}
+
+static int
+seqmod_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ seqmod_info *sm;
+
+ sm = ch_calloc(1, sizeof(seqmod_info));
+ on->on_bi.bi_private = sm;
+
+ ldap_pvt_thread_mutex_init( &sm->sm_mutex );
+
+ return 0;
+}
+
+static int
+seqmod_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ seqmod_info *sm = (seqmod_info *)on->on_bi.bi_private;
+
+ if ( sm ) {
+ ldap_pvt_thread_mutex_destroy( &sm->sm_mutex );
+
+ ch_free( sm );
+ on->on_bi.bi_private = NULL;
+ }
+
+ return 0;
+}
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+static slap_overinst seqmod;
+
+int
+seqmod_initialize()
+{
+ seqmod.on_bi.bi_type = "seqmod";
+ seqmod.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ seqmod.on_bi.bi_db_open = seqmod_db_open;
+ seqmod.on_bi.bi_db_close = seqmod_db_close;
+
+ seqmod.on_bi.bi_op_modify = seqmod_op_mod;
+ seqmod.on_bi.bi_op_modrdn = seqmod_op_mod;
+ seqmod.on_bi.bi_extended = seqmod_op_extended;
+
+ return overlay_register( &seqmod );
+}
+
+#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return seqmod_initialize();
+}
+#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_SEQMOD) */
diff --git a/servers/slapd/overlays/slapover.txt b/servers/slapd/overlays/slapover.txt
new file mode 100644
index 0000000..2015d8d
--- /dev/null
+++ b/servers/slapd/overlays/slapover.txt
@@ -0,0 +1,158 @@
+slapd internal APIs
+
+Introduction
+
+Frontend, backend, database, callback, overlay - what does it all mean?
+
+The "frontend" refers to all the code that deals with the actual interaction
+with an LDAP client. This includes the code to read requests from the network
+and parse them into C data structures, all of the session management, and the
+formatting of responses for transmission onto the network. It also includes the
+access control engine and other features that are generic to LDAP processing,
+features which are not dependent on a particular database implementation.
+Because the frontend serves as the framework that ties everything together,
+it should not change much over time.
+
+The terms "backend" and "database" have historically been used interchangeably
+and/or in combination as if they are the same thing, but the code has a clear
+distinction between the two. A "backend" is a type of module, and a "database"
+is an instance of a backend type. Together they work with the frontend to
+manage the actual data that are operated on by LDAP requests. Originally the
+backend interface was relatively compact, with individual functions
+corresponding to each LDAP operation type, plus functions for init, config, and
+shutdown. The number of entry points has grown to allow greater flexibility,
+but the concept is much the same as before.
+
+The language here can get a bit confusing. A backend in slapd is embodied in a
+BackendInfo data structure, and a database is held in a BackendDB structure.
+Originally it was all just a single Backend data structure, but things have
+grown and the concept was split into these two parts. The idea behind the
+distinct BackendInfo is to allow for any initialization and configuration that
+may be needed by every instance of a type of database, as opposed to items that
+are specific to just one instance. For example, you might have a database
+library that requires an initialization routine to be called exactly once at
+program startup. Then there may be a "open" function that must be called once
+for each database instance. The BackendInfo.bi_open function provides the
+one-time startup, while the BackendInfo.bi_db_open function provides the
+per-database startup. The main feature of the BackendInfo structure is its
+table of entry points for all of the database functions that it implements.
+There's also a bi_private pointer that can be used to carry any configuration
+state needed by the backend. (Note that this is state that applies to the
+backend type, and thus to all database instances of the backend as well.) The
+BackendDB structure carries all of the per-instance state for a backend
+database. This includes the database suffix, ACLs, flags, various DNs, etc. It
+also has a pointer to its BackendInfo, and a be_private pointer for use by the
+particular backend instance. In practice, the per-type features are seldom
+used, and all of the work is done in the per-instance data structures.
+
+Ordinarily an LDAP request is received by the slapd frontend, parsed into a
+request structure, and then passed to the backend for processing. The backend
+may call various utility functions in the frontend to assist in processing, and
+then it eventually calls some send_ldap_result function in the frontend to send
+results back to the client. The processing flow is pretty rigidly defined; even
+though slapd is capable of dynamically loading new code modules, it was
+difficult to add extensions that changed the basic protocol operations. If you
+wanted to extend the server with special behaviors you would need to modify the
+frontend or the backend or both, and generally you would need to write an
+entire new backend to get some set of special features working. With OpenLDAP
+2.1 we added the notion of a callback, which can intercept the results sent
+from a backend before they are sent to a client. Using callbacks makes it
+possible to modify the results if desired, or to simply discard the results
+instead of sending them to any client. This callback feature is used
+extensively in the SASL support to perform internal searches of slapd databases
+when mapping authentication IDs into regular DNs. The callback mechanism is
+also the basis of backglue, which allows separate databases to be searched as
+if they were a single naming context.
+
+Very often, one needs to add just a tiny feature onto an otherwise "normal"
+database. The usual way to achieve this was to use a programmable backend (like
+back-perl) to preprocess various requests and then forward them back into slapd
+to be handled by the real database. While this technique works, it is fairly
+inefficient because it involves many transitions from network to slapd and back
+again. The overlay concept introduced in OpenLDAP 2.2 allows code to be
+inserted between the slapd frontend and any backend, so that incoming requests
+can be intercepted before reaching the backend database. (There is also a SLAPI
+plugin framework in OpenLDAP 2.2; it offers a lot of flexibility as well but is
+not discussed here.) The overlay framework also uses the callback mechanism, so
+outgoing results can also be intercepted by external code. All of this could
+get unwieldy if a lot of overlays were being used, but there was also another
+significant API change in OpenLDAP 2.2 to streamline internal processing. (See
+the document "Refactoring the slapd ABI"...)
+
+OK, enough generalities... You should probably have a copy of slap.h in front
+of you to continue here.
+
+What is an overlay? The structure defining it includes a BackendInfo structure
+plus a few additional fields. It gets inserted into the usual frontend->backend
+call chain by replacing the BackendDB's BackendInfo pointer with its own. The
+framework to accomplish this is in backover.c. For a given backend, the
+BackendInfo will point to a slap_overinfo structure. The slap_overinfo has a
+BackendInfo that points to all of the overlay framework's entry points. It also
+holds a copy of the original BackendInfo pointer, and a linked list of
+slap_overinst structures. There is one slap_overinst per configured overlay,
+and the set of overlays configured on a backend are treated like a stack; i.e.,
+the last one configured is at the top of the stack, and it executes first.
+
+Continuing with the stack notion - a request enters the frontend, is directed
+to a backend by select_backend, and then intercepted by the top of the overlay
+stack. This first overlay may do something with the request, and then return
+SLAP_CB_CONTINUE, which will then cause processing to fall into the next
+overlay, and so on down the stack until finally the request is handed to the
+actual backend database. Likewise, when the database finishes processing and
+sends a result, the overlay callback intercepts this and the topmost overlay
+gets to process the result. If it returns SLAP_CB_CONTINUE then processing will
+continue in the next overlay, and then any other callbacks, then finally the
+result reaches the frontend for sending back to the client. At any step along
+the way, a module may choose to fully process the request or result and not
+allow it to propagate any further down the stack. Whenever a module returns
+anything other than SLAP_CB_CONTINUE the processing stops.
+
+An overlay can call most frontend functions without any special consideration.
+However, if a call is going to result in any backend code being invoked, then
+the backend environment must be correct. During a normal backend invocation,
+op->o_bd points to the BackendDB structure for the backend, and
+op->o_bd->bd_info points to the BackendInfo for the backend. All of the
+information a specific backend instance needs is in op->o_bd->be_private and
+all of its entry points are in the BackendInfo structure. When overlays are in
+use on a backend, op->o_bd->bd_info points to the BackendInfo (actually a
+slap_overinfo) that contains the overlay framework. When a particular overlay
+instance is executing, op->o_bd points to a copy of the original op->o_bd, and
+op->o_bd->bd_info points to a slap_overinst which carries the information about
+the current overlay. The slap_overinst contains an on_private pointer which can
+be used to carry any configuration or state information the overlay needs. The
+normal way to invoke a backend function is through the op->o_bd->bd_info table
+of entry points, but obviously this must be set to the backend's original
+BackendInfo in order to get to the right function.
+
+There are two approaches here. The slap_overinst also contains a on_info field
+that points to the top slap_overinfo that wraps the current backend. The
+simplest thing is for the overlay to set op->o_bd->bd_info to this on_info
+value before invoking a backend function. This will cause processing of that
+particular operation to begin at the top of the overlay stack, so all the other
+overlays on the backend will also get a chance to handle this internal request.
+The other possibility is to invoke the underlying backend directly, bypassing
+the rest of the overlays, by calling through on_info->oi_orig. You should be
+careful in choosing this approach, since it precludes other overlays from doing
+their jobs.
+
+One of the more interesting uses for an overlay is to attach two (or more)
+different database backends into a single execution stack. Assuming that the
+basic frontend-managed information (suffix, rootdn, ACLs, etc.) will be the
+same for all of the backends, the only thing the overlay needs to maintain is a
+be_private and bd_info pointer for the added backends. The chain and proxycache
+overlays are two complementary examples of this usage. The chain overlay
+attaches a back-ldap backend to a local database backend, and allows referrals
+to remote servers generated by the database to be processed by slapd instead of
+being returned to the client. The proxycache overlay attaches a local database
+to a back-ldap (or back-meta) backend and allows search results from remote
+servers to be cached locally. In both cases the overlays must provide a bit of
+glue to swap in the appropriate be_private and bd_info pointers before invoking
+the attached backend, which can then be invoked as usual.
+
+Note on overlay initialization/destruction: you should allocate storage for
+config info in the _db_init handler, and free this storage in the _db_destroy
+handler. You must not free it in the _db_close handler because a module may
+be opened/closed multiple times in a running slapd when using dynamic
+configuration and the config info must remain intact.
+
+---
diff --git a/servers/slapd/overlays/sssvlv.c b/servers/slapd/overlays/sssvlv.c
new file mode 100644
index 0000000..828782a
--- /dev/null
+++ b/servers/slapd/overlays/sssvlv.c
@@ -0,0 +1,1439 @@
+/* sssvlv.c - server side sort / virtual list view */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 The OpenLDAP Foundation.
+ * Portions copyright 2009 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software. Support for multiple sorts per connection added
+ * by Raphael Ouazana.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SSSVLV
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include <ldap_avl.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "slap-config.h"
+
+#include "../../../libraries/liblber/lber-int.h" /* ber_rewind */
+
+/* RFC2891: Server Side Sorting
+ * RFC2696: Paged Results
+ */
+#ifndef LDAP_MATCHRULE_IDENTIFIER
+#define LDAP_MATCHRULE_IDENTIFIER 0x80L
+#define LDAP_REVERSEORDER_IDENTIFIER 0x81L
+#define LDAP_ATTRTYPES_IDENTIFIER 0x80L
+#endif
+
+/* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views
+ */
+#ifndef LDAP_VLVBYINDEX_IDENTIFIER
+#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L
+#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L
+#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L
+
+#define LDAP_VLV_SSS_MISSING 0x4C
+#define LDAP_VLV_RANGE_ERROR 0x4D
+#endif
+
+#define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def))
+
+#define SSSVLV_DEFAULT_MAX_KEYS 5
+#define SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN 5
+
+#define NO_PS_COOKIE (PagedResultsCookie) -1
+#define NO_VC_CONTEXT (unsigned long) -1
+
+typedef struct vlv_ctrl {
+ int vc_before;
+ int vc_after;
+ int vc_offset;
+ int vc_count;
+ struct berval vc_value;
+ unsigned long vc_context;
+} vlv_ctrl;
+
+typedef struct sort_key
+{
+ AttributeDescription *sk_ad;
+ MatchingRule *sk_ordering;
+ int sk_direction; /* 1=normal, -1=reverse */
+} sort_key;
+
+typedef struct sort_ctrl {
+ int sc_nkeys;
+ sort_key sc_keys[1];
+} sort_ctrl;
+
+
+typedef struct sort_node
+{
+ int sn_conn;
+ int sn_session;
+ struct berval sn_dn;
+ struct berval *sn_vals;
+} sort_node;
+
+typedef struct sssvlv_info
+{
+ int svi_max; /* max concurrent sorts */
+ int svi_num; /* current # sorts */
+ int svi_max_keys; /* max sort keys per request */
+ int svi_max_percon; /* max concurrent sorts per con */
+} sssvlv_info;
+
+typedef struct sort_op
+{
+ TAvlnode *so_tree;
+ sort_ctrl *so_ctrl;
+ sssvlv_info *so_info;
+ int so_paged;
+ int so_page_size;
+ int so_nentries;
+ int so_vlv;
+ int so_vlv_rc;
+ int so_vlv_target;
+ int so_session;
+ unsigned long so_vcontext;
+ int so_running;
+} sort_op;
+
+/* There is only one conn table for all overlay instances */
+/* Each conn can handle one session by context */
+static sort_op ***sort_conns;
+static ldap_pvt_thread_mutex_t sort_conns_mutex;
+static int ov_count;
+static const char *debug_header = "sssvlv";
+
+static int sss_cid;
+static int vlv_cid;
+
+/* RFC 2981 Section 2.2
+ * If a sort key is a multi-valued attribute, and an entry happens to
+ * have multiple values for that attribute and no other controls are
+ * present that affect the sorting order, then the server SHOULD use the
+ * least value (according to the ORDERING rule for that attribute).
+ */
+static struct berval* select_value(
+ Attribute *attr,
+ sort_key *key )
+{
+ struct berval* ber1, *ber2;
+ MatchingRule *mr = key->sk_ordering;
+ unsigned i;
+ int cmp;
+
+ ber1 = &(attr->a_nvals[0]);
+ ber2 = ber1+1;
+ for ( i = 1; i < attr->a_numvals; i++,ber2++ ) {
+ mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 );
+ if ( cmp > 0 ) {
+ ber1 = ber2;
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n",
+ debug_header,
+ SAFESTR(ber1->bv_val, "<Empty>") );
+
+ return ber1;
+}
+
+static int node_cmp( const void* val1, const void* val2 )
+{
+ sort_node *sn1 = (sort_node *)val1;
+ sort_node *sn2 = (sort_node *)val2;
+ sort_ctrl *sc;
+ MatchingRule *mr;
+ int i, cmp = 0;
+ assert( sort_conns[sn1->sn_conn]
+ && sort_conns[sn1->sn_conn][sn1->sn_session]
+ && sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl );
+ sc = sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl;
+
+ for ( i=0; cmp == 0 && i<sc->sc_nkeys; i++ ) {
+ if ( BER_BVISNULL( &sn1->sn_vals[i] )) {
+ if ( BER_BVISNULL( &sn2->sn_vals[i] ))
+ cmp = 0;
+ else
+ cmp = sc->sc_keys[i].sk_direction;
+ } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) {
+ cmp = sc->sc_keys[i].sk_direction * -1;
+ } else {
+ mr = sc->sc_keys[i].sk_ordering;
+ mr->smr_match( &cmp, 0, mr->smr_syntax, mr,
+ &sn1->sn_vals[i], &sn2->sn_vals[i] );
+ if ( cmp )
+ cmp *= sc->sc_keys[i].sk_direction;
+ }
+ }
+ return cmp;
+}
+
+static int node_insert( const void *val1, const void *val2 )
+{
+ /* Never return equal so that new entries are always inserted */
+ return node_cmp( val1, val2 ) < 0 ? -1 : 1;
+}
+
+static int pack_vlv_response_control(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval cookie, bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ rc = ber_printf( ber, "{iie", so->so_vlv_target, so->so_nentries,
+ so->so_vlv_rc );
+
+ if ( rc != -1 && so->so_vcontext ) {
+ cookie.bv_val = (char *)&so->so_vcontext;
+ cookie.bv_len = sizeof(so->so_vcontext);
+ rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie );
+ }
+
+ if ( rc != -1 ) {
+ rc = ber_printf( ber, "}" );
+ }
+
+ if ( rc != -1 ) {
+ rc = ber_flatten2( ber, &bv, 0 );
+ }
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+static int pack_pagedresult_response_control(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie resp_cookie;
+ struct berval cookie, bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( so->so_nentries > 0 ) {
+ resp_cookie = ( PagedResultsCookie )so->so_tree;
+ cookie.bv_len = sizeof( PagedResultsCookie );
+ cookie.bv_val = (char *)&resp_cookie;
+ } else {
+ resp_cookie = ( PagedResultsCookie )0;
+ BER_BVZERO( &cookie );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie;
+ op->o_conn->c_pagedresults_state.ps_count
+ = ((PagedResultsState *)op->o_pagedresults_state)->ps_count
+ + rs->sr_nentries;
+
+ rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie );
+ if ( rc != -1 ) {
+ rc = ber_flatten2( ber, &bv, 0 );
+ }
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+static int pack_sss_response_control(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* Pack error code */
+ rc = ber_printf(ber, "{e}", rs->sr_err);
+
+ if ( rc != -1)
+ rc = ber_flatten2( ber, &bv, 0 );
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+/* Return the session id or -1 if unknown */
+static int find_session_by_so(
+ int svi_max_percon,
+ int conn_id,
+ sort_op *so )
+{
+ int sess_id;
+ if (so == NULL) {
+ return -1;
+ }
+ for (sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if ( sort_conns[conn_id] && sort_conns[conn_id][sess_id] == so )
+ return sess_id;
+ }
+ return -1;
+}
+
+/* Return the session id or -1 if unknown */
+static int find_session_by_context(
+ int svi_max_percon,
+ int conn_id,
+ unsigned long vc_context,
+ PagedResultsCookie ps_cookie )
+{
+ int sess_id;
+ for(sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if( sort_conns[conn_id] && sort_conns[conn_id][sess_id] &&
+ ( sort_conns[conn_id][sess_id]->so_vcontext == vc_context ||
+ (PagedResultsCookie) sort_conns[conn_id][sess_id]->so_tree == ps_cookie ) )
+ return sess_id;
+ }
+ return -1;
+}
+
+static int find_next_session(
+ int svi_max_percon,
+ int conn_id )
+{
+ int sess_id;
+ assert(sort_conns[conn_id] != NULL);
+ for(sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if(!sort_conns[conn_id][sess_id]) {
+ return sess_id;
+ }
+ }
+ if (sess_id >= svi_max_percon) {
+ return -1;
+ } else {
+ return sess_id;
+ }
+}
+
+static void free_sort_op( Connection *conn, sort_op *so )
+{
+ int sess_id;
+
+ ldap_pvt_thread_mutex_lock( &sort_conns_mutex );
+ sess_id = find_session_by_so( so->so_info->svi_max_percon, conn->c_conn_idx, so );
+ if ( sess_id > -1 ) {
+ sort_conns[conn->c_conn_idx][sess_id] = NULL;
+ so->so_info->svi_num--;
+ }
+ ldap_pvt_thread_mutex_unlock( &sort_conns_mutex );
+
+ if ( sess_id > -1 ){
+ if ( so->so_tree ) {
+ if ( so->so_paged > SLAP_CONTROL_IGNORED ) {
+ TAvlnode *cur_node, *next_node;
+ cur_node = so->so_tree;
+ while ( cur_node ) {
+ next_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT );
+ ch_free( cur_node->avl_data );
+ ber_memfree( cur_node );
+
+ cur_node = next_node;
+ }
+ } else {
+ ldap_tavl_free( so->so_tree, ch_free );
+ }
+ so->so_tree = NULL;
+ }
+
+ ch_free( so );
+ }
+}
+
+static void free_sort_ops( Connection *conn, sort_op **sos, int svi_max_percon )
+{
+ int sess_id;
+ sort_op *so;
+
+ for( sess_id = 0; sess_id < svi_max_percon ; sess_id++ ) {
+ so = sort_conns[conn->c_conn_idx][sess_id];
+ if ( so ) {
+ free_sort_op( conn, so );
+ sort_conns[conn->c_conn_idx][sess_id] = NULL;
+ }
+ }
+}
+
+static void send_list(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ TAvlnode *cur_node, *tmp_node;
+ vlv_ctrl *vc = op->o_controls[vlv_cid];
+ int i, j, dir, rc;
+ BackendDB *be;
+ Entry *e;
+ LDAPControl *ctrls[2];
+
+ rs->sr_attrs = op->ors_attrs;
+
+ /* FIXME: it may be better to just flatten the tree into
+ * an array before doing all of this...
+ */
+
+ /* Are we just counting an offset? */
+ if ( BER_BVISNULL( &vc->vc_value )) {
+ if ( vc->vc_offset == vc->vc_count ) {
+ /* wants the last entry in the list */
+ cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ so->so_vlv_target = so->so_nentries;
+ } else if ( vc->vc_offset == 1 ) {
+ /* wants the first entry in the list */
+ cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ so->so_vlv_target = 1;
+ } else {
+ int target;
+ /* Just iterate to the right spot */
+ if ( vc->vc_count && vc->vc_count != so->so_nentries ) {
+ if ( vc->vc_offset > vc->vc_count )
+ goto range_err;
+ target = so->so_nentries * vc->vc_offset / vc->vc_count;
+ } else {
+ if ( vc->vc_offset > so->so_nentries ) {
+range_err:
+ so->so_vlv_rc = LDAP_VLV_RANGE_ERROR;
+ pack_vlv_response_control( op, rs, so, ctrls );
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_VLV_ERROR;
+ return;
+ }
+ target = vc->vc_offset;
+ }
+ so->so_vlv_target = target;
+ /* Start at left and go right, or start at right and go left? */
+ if ( target < so->so_nentries / 2 ) {
+ cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ } else {
+ cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ dir = TAVL_DIR_LEFT;
+ target = so->so_nentries - target + 1;
+ }
+ for ( i=1; i<target; i++ )
+ cur_node = ldap_tavl_next( cur_node, dir );
+ }
+ } else {
+ /* we're looking for a specific value */
+ sort_ctrl *sc = so->so_ctrl;
+ MatchingRule *mr = sc->sc_keys[0].sk_ordering;
+ sort_node *sn;
+ struct berval bv;
+
+ if ( mr->smr_normalize ) {
+ rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX,
+ mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx );
+ if ( rc ) {
+ so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING;
+ pack_vlv_response_control( op, rs, so, ctrls );
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_VLV_ERROR;
+ return;
+ }
+ } else {
+ bv = vc->vc_value;
+ }
+
+ sn = op->o_tmpalloc( sizeof(sort_node) +
+ sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx );
+ sn->sn_vals = (struct berval *)(sn+1);
+ sn->sn_conn = op->o_conn->c_conn_idx;
+ sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so );
+ sn->sn_vals[0] = bv;
+ for (i=1; i<sc->sc_nkeys; i++) {
+ BER_BVZERO( &sn->sn_vals[i] );
+ }
+ cur_node = ldap_tavl_find3( so->so_tree, sn, node_cmp, &j );
+ /* didn't find >= match */
+ if ( j > 0 ) {
+ if ( cur_node )
+ cur_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT );
+ }
+ op->o_tmpfree( sn, op->o_tmpmemctx );
+
+ if ( !cur_node ) {
+ so->so_vlv_target = so->so_nentries + 1;
+ } else {
+ sort_node *sn = so->so_tree->avl_data;
+ /* start from the left or the right side? */
+ mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] );
+ if ( i > 0 ) {
+ tmp_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ dir = TAVL_DIR_LEFT;
+ } else {
+ tmp_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ }
+ for (i=0; tmp_node != cur_node;
+ tmp_node = ldap_tavl_next( tmp_node, dir ), i++);
+ so->so_vlv_target = (dir == TAVL_DIR_RIGHT) ? i+1 : so->so_nentries - i;
+ }
+ if ( bv.bv_val != vc->vc_value.bv_val )
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ }
+ if ( !cur_node ) {
+ i = 1;
+ cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ } else {
+ i = 0;
+ }
+ for ( ; i<vc->vc_before; i++ ) {
+ tmp_node = ldap_tavl_next( cur_node, TAVL_DIR_LEFT );
+ if ( !tmp_node ) break;
+ cur_node = tmp_node;
+ }
+ j = i + vc->vc_after + 1;
+ be = op->o_bd;
+ for ( i=0; i<j; i++ ) {
+ sort_node *sn = cur_node->avl_data;
+
+ if ( slapd_shutdown ) break;
+
+ op->o_bd = select_backend( &sn->sn_dn, 0 );
+ e = NULL;
+ rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e );
+
+ if ( e && rc == LDAP_SUCCESS ) {
+ rs->sr_entry = e;
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs->sr_err = send_search_entry( op, rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE )
+ break;
+ }
+ cur_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT );
+ if ( !cur_node ) break;
+ }
+ so->so_vlv_rc = LDAP_SUCCESS;
+
+ op->o_bd = be;
+}
+
+static void send_page( Operation *op, SlapReply *rs, sort_op *so )
+{
+ TAvlnode *cur_node = so->so_tree;
+ TAvlnode *next_node = NULL;
+ BackendDB *be = op->o_bd;
+ Entry *e;
+ int rc;
+
+ rs->sr_attrs = op->ors_attrs;
+
+ while ( cur_node && rs->sr_nentries < so->so_page_size ) {
+ sort_node *sn = cur_node->avl_data;
+
+ if ( slapd_shutdown ) break;
+
+ next_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT );
+
+ op->o_bd = select_backend( &sn->sn_dn, 0 );
+ e = NULL;
+ rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e );
+
+ ch_free( cur_node->avl_data );
+ ber_memfree( cur_node );
+
+ cur_node = next_node;
+ so->so_nentries--;
+
+ if ( e && rc == LDAP_SUCCESS ) {
+ rs->sr_entry = e;
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs->sr_err = send_search_entry( op, rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE )
+ break;
+ }
+ }
+
+ /* Set the first entry to send for the next page */
+ so->so_tree = next_node;
+ if ( next_node )
+ next_node->avl_left = NULL;
+
+ op->o_bd = be;
+}
+
+static void send_entry(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: response control: status=%d, text=%s\n",
+ debug_header, rs->sr_err, SAFESTR(rs->sr_text, "<None>"));
+
+ if ( !so->so_tree )
+ return;
+
+ /* RFC 2891: If critical then send the entries iff they were
+ * successfully sorted. If non-critical send all entries
+ * whether they were sorted or not.
+ */
+ if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) ||
+ (rs->sr_err == LDAP_SUCCESS) )
+ {
+ if ( so->so_vlv > SLAP_CONTROL_IGNORED ) {
+ send_list( op, rs, so );
+ } else {
+ /* Get the first node to send */
+ TAvlnode *start_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ so->so_tree = start_node;
+
+ if ( so->so_paged <= SLAP_CONTROL_IGNORED ) {
+ /* Not paged result search. Send all entries.
+ * Set the page size to the number of entries
+ * so that send_page() will send all entries.
+ */
+ so->so_page_size = so->so_nentries;
+ }
+
+ send_page( op, rs, so );
+ }
+ }
+}
+
+static void send_result(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ LDAPControl *ctrls[3];
+ int rc, i = 0;
+
+ rc = pack_sss_response_control( op, rs, ctrls );
+ if ( rc == LDAP_SUCCESS ) {
+ i++;
+ rc = -1;
+ if ( so->so_paged > SLAP_CONTROL_IGNORED ) {
+ rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 );
+ } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) {
+ rc = pack_vlv_response_control( op, rs, so, ctrls+1 );
+ }
+ if ( rc == LDAP_SUCCESS )
+ i++;
+ }
+ ctrls[i] = NULL;
+
+ if ( ctrls[0] != NULL )
+ slap_add_ctrls( op, rs, ctrls );
+ send_ldap_result( op, rs );
+
+ if ( so->so_tree == NULL ) {
+ /* Search finished, so clean up */
+ free_sort_op( op->o_conn, so );
+ } else {
+ so->so_running = 0;
+ }
+}
+
+static int sssvlv_op_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ sort_ctrl *sc = op->o_controls[sss_cid];
+ sort_op *so = op->o_callback->sc_private;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ int i;
+ size_t len;
+ sort_node *sn, *sn2;
+ struct berval *bv;
+ char *ptr;
+
+ len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) +
+ rs->sr_entry->e_nname.bv_len + 1;
+ sn = op->o_tmpalloc( len, op->o_tmpmemctx );
+ sn->sn_vals = (struct berval *)(sn+1);
+
+ /* Build tmp list of key values */
+ for ( i=0; i<sc->sc_nkeys; i++ ) {
+ Attribute *a = attr_find( rs->sr_entry->e_attrs,
+ sc->sc_keys[i].sk_ad );
+ if ( a ) {
+ if ( a->a_numvals > 1 ) {
+ bv = select_value( a, &sc->sc_keys[i] );
+ } else {
+ bv = a->a_nvals;
+ }
+ sn->sn_vals[i] = *bv;
+ len += bv->bv_len + 1;
+ } else {
+ BER_BVZERO( &sn->sn_vals[i] );
+ }
+ }
+
+ /* Now dup into regular memory */
+ sn2 = ch_malloc( len );
+ sn2->sn_vals = (struct berval *)(sn2+1);
+ AC_MEMCPY( sn2->sn_vals, sn->sn_vals,
+ sc->sc_nkeys * sizeof(struct berval));
+
+ ptr = (char *)(sn2->sn_vals + sc->sc_nkeys);
+ sn2->sn_dn.bv_val = ptr;
+ sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len;
+ AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val,
+ rs->sr_entry->e_nname.bv_len );
+ ptr += rs->sr_entry->e_nname.bv_len;
+ *ptr++ = '\0';
+ for ( i=0; i<sc->sc_nkeys; i++ ) {
+ if ( !BER_BVISNULL( &sn2->sn_vals[i] )) {
+ AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len);
+ sn2->sn_vals[i].bv_val = ptr;
+ ptr += sn2->sn_vals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ }
+ op->o_tmpfree( sn, op->o_tmpmemctx );
+ sn = sn2;
+ sn->sn_conn = op->o_conn->c_conn_idx;
+ sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so );
+
+ /* Insert into the AVL tree */
+ ldap_tavl_insert(&(so->so_tree), sn, node_insert, ldap_avl_dup_error);
+
+ so->so_nentries++;
+
+ /* Collected the keys so that they can be sorted. Thus, stop
+ * the entry from propagating.
+ */
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ else if ( rs->sr_type == REP_RESULT ) {
+ /* Remove serversort response callback.
+ * We don't want the entries that we are about to send to be
+ * processed by serversort response again.
+ */
+ if ( op->o_callback->sc_response == sssvlv_op_response ) {
+ op->o_callback = op->o_callback->sc_next;
+ }
+
+ send_entry( op, rs, so );
+ send_result( op, rs, so );
+ }
+
+ return rs->sr_err;
+}
+
+static int sssvlv_op_search(
+ Operation *op,
+ SlapReply *rs)
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+ int rc = SLAP_CB_CONTINUE;
+ int ok;
+ sort_op *so = NULL, so2;
+ sort_ctrl *sc;
+ PagedResultsState *ps;
+ vlv_ctrl *vc;
+ int sess_id;
+
+ if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) {
+ if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) {
+ LDAPControl *ctrls[2];
+ so2.so_vcontext = 0;
+ so2.so_vlv_target = 0;
+ so2.so_nentries = 0;
+ so2.so_vlv_rc = LDAP_VLV_SSS_MISSING;
+ so2.so_vlv = op->o_ctrlflag[vlv_cid];
+ rc = pack_vlv_response_control( op, rs, &so2, ctrls );
+ if ( rc == LDAP_SUCCESS ) {
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ }
+ rs->sr_err = LDAP_VLV_ERROR;
+ rs->sr_text = "Sort control is required with VLV";
+ goto leave;
+ }
+ /* Not server side sort so just continue */
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> sssvlv_search: <%s> %s, control flag: %d\n",
+ op->o_req_dn.bv_val, op->ors_filterstr.bv_val,
+ op->o_ctrlflag[sss_cid]);
+
+ sc = op->o_controls[sss_cid];
+ if ( sc->sc_nkeys > si->svi_max_keys ) {
+ rs->sr_text = "Too many sort keys";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto leave;
+ }
+
+ ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ?
+ (PagedResultsState*)(op->o_pagedresults_state) : NULL;
+ vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ?
+ op->o_controls[vlv_cid] : NULL;
+
+ if ( ps && vc ) {
+ rs->sr_text = "VLV incompatible with PagedResults";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto leave;
+ }
+
+ ok = 1;
+ ldap_pvt_thread_mutex_lock( &sort_conns_mutex );
+ /* Is there already a sort running on this conn? */
+ sess_id = find_session_by_context( si->svi_max_percon, op->o_conn->c_conn_idx, vc ? vc->vc_context : NO_VC_CONTEXT, ps ? ps->ps_cookie : NO_PS_COOKIE );
+ if ( sess_id >= 0 ) {
+ so = sort_conns[op->o_conn->c_conn_idx][sess_id];
+
+ if( so->so_running > 0 ){
+ /* another thread is handling, response busy to client */
+ so = NULL;
+ ok = 0;
+ } else {
+
+ /* Is it a continuation of a VLV search? */
+ if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED ||
+ vc->vc_context != so->so_vcontext ) {
+ /* Is it a continuation of a paged search? */
+ if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED ||
+ op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) {
+ ok = 0;
+ } else if ( !ps->ps_size ) {
+ /* Abandoning current request */
+ ok = 0;
+ so->so_nentries = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ }
+ if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) ||
+ ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) {
+ /* changed from paged to vlv or vice versa, abandon */
+ ok = 0;
+ so->so_nentries = 0;
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ if ( ok ) {
+ /* occupy before mutex unlock */
+ so->so_running = 1;
+ }
+
+ }
+ /* Are there too many running overall? */
+ } else if ( si->svi_num >= si->svi_max ) {
+ ok = 0;
+ } else if ( ( sess_id = find_next_session(si->svi_max_percon, op->o_conn->c_conn_idx ) ) < 0 ) {
+ ok = 0;
+ } else {
+ /* OK, this connection now has a sort running */
+ si->svi_num++;
+ sort_conns[op->o_conn->c_conn_idx][sess_id] = &so2;
+ sort_conns[op->o_conn->c_conn_idx][sess_id]->so_session = sess_id;
+ }
+ ldap_pvt_thread_mutex_unlock( &sort_conns_mutex );
+ if ( ok ) {
+ /* If we're a global overlay, this check got bypassed */
+ if ( !op->ors_limit && limits_check( op, rs ))
+ return rs->sr_err;
+ /* are we continuing a VLV search? */
+ if ( so && vc && vc->vc_context ) {
+ so->so_ctrl = sc;
+ send_list( op, rs, so );
+ send_result( op, rs, so );
+ rc = LDAP_SUCCESS;
+ /* are we continuing a paged search? */
+ } else if ( so && ps && ps->ps_cookie ) {
+ so->so_ctrl = sc;
+ send_page( op, rs, so );
+ send_result( op, rs, so );
+ rc = LDAP_SUCCESS;
+ } else {
+ slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback),
+ op->o_tmpmemctx );
+ /* Install serversort response callback to handle a new search */
+ if ( ps || vc ) {
+ so = ch_calloc( 1, sizeof(sort_op));
+ } else {
+ so = op->o_tmpcalloc( 1, sizeof(sort_op), op->o_tmpmemctx );
+ }
+ sort_conns[op->o_conn->c_conn_idx][sess_id] = so;
+
+ cb->sc_cleanup = NULL;
+ cb->sc_response = sssvlv_op_response;
+ cb->sc_next = op->o_callback;
+ cb->sc_private = so;
+ cb->sc_writewait = NULL;
+
+ so->so_tree = NULL;
+ so->so_ctrl = sc;
+ so->so_info = si;
+ if ( ps ) {
+ so->so_paged = op->o_pagedresults;
+ so->so_page_size = ps->ps_size;
+ op->o_pagedresults = SLAP_CONTROL_IGNORED;
+ } else {
+ so->so_paged = 0;
+ so->so_page_size = 0;
+ if ( vc ) {
+ so->so_vlv = op->o_ctrlflag[vlv_cid];
+ so->so_vlv_target = 0;
+ so->so_vlv_rc = 0;
+ } else {
+ so->so_vlv = SLAP_CONTROL_NONE;
+ }
+ }
+ so->so_session = sess_id;
+ so->so_vlv = op->o_ctrlflag[vlv_cid];
+ so->so_vcontext = (unsigned long)so;
+ so->so_nentries = 0;
+ so->so_running = 1;
+
+ op->o_callback = cb;
+ }
+ } else {
+ if ( so && !so->so_nentries ) {
+ free_sort_op( op->o_conn, so );
+ } else {
+ rs->sr_text = "Other sort requests already in progress";
+ rs->sr_err = LDAP_BUSY;
+ }
+leave:
+ rc = rs->sr_err;
+ send_ldap_result( op, rs );
+ }
+
+ return rc;
+}
+
+static int get_ordering_rule(
+ AttributeDescription *ad,
+ struct berval *matchrule,
+ SlapReply *rs,
+ MatchingRule **ordering )
+{
+ MatchingRule* mr;
+
+ if ( matchrule && matchrule->bv_val ) {
+ mr = mr_find( matchrule->bv_val );
+ if ( mr == NULL ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_MATCHING;
+ rs->sr_text = "serverSort control: No ordering rule";
+ Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n",
+ debug_header, matchrule->bv_val );
+ }
+ }
+ else {
+ mr = ad->ad_type->sat_ordering;
+ if ( mr == NULL ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_MATCHING;
+ rs->sr_text = "serverSort control: No ordering rule";
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: no ordering rule specified and no default ordering rule for attribute %s\n",
+ debug_header, ad->ad_cname.bv_val );
+ }
+ }
+
+ *ordering = mr;
+ return rs->sr_err;
+}
+
+static int count_key(BerElement *ber)
+{
+ char *end;
+ ber_len_t len;
+ ber_tag_t tag;
+ int count = 0;
+
+ /* Server Side Sort Control is a SEQUENCE of SEQUENCE */
+ for ( tag = ber_first_element( ber, &len, &end );
+ tag == LBER_SEQUENCE;
+ tag = ber_next_element( ber, &len, end ))
+ {
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ ++count;
+ }
+ ber_rewind( ber );
+
+ return count;
+}
+
+static int build_key(
+ BerElement *ber,
+ SlapReply *rs,
+ sort_key *key )
+{
+ struct berval attr;
+ struct berval matchrule = BER_BVNULL;
+ ber_int_t reverse = 0;
+ ber_tag_t tag;
+ ber_len_t len;
+ MatchingRule *ordering = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: attribute decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_MATCHRULE_IDENTIFIER ) {
+ if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: matchrule decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) {
+ if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: reverse decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+ }
+
+ if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) {
+ rs->sr_text =
+ "serverSort control: Unrecognized attribute type in sort key";
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: Unrecognized attribute type in sort key: %s\n",
+ debug_header, SAFESTR(attr.bv_val, "<None>") );
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ return rs->sr_err;
+ }
+
+ /* get_ordering_rule will set sr_err and sr_text */
+ get_ordering_rule( ad, &matchrule, rs, &ordering );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ key->sk_ad = ad;
+ key->sk_ordering = ordering;
+ key->sk_direction = reverse ? -1 : 1;
+
+ return rs->sr_err;
+}
+
+/* Conforms to RFC4510 re: Criticality, original RFC2891 spec is broken
+ * Also see ITS#7253 for discussion
+ */
+static int sss_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ int i;
+ sort_ctrl *sc;
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+
+ if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) {
+ rs->sr_text = "sorted results control specified multiple times";
+ } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is absent";
+ } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is empty";
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ if ( rs->sr_err != LDAP_SUCCESS )
+ return rs->sr_err;
+
+ op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+
+ ber = (BerElement *)&berbuf;
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+ i = count_key( ber );
+
+ sc = op->o_tmpalloc( sizeof(sort_ctrl) +
+ (i-1) * sizeof(sort_key), op->o_tmpmemctx );
+ sc->sc_nkeys = i;
+ op->o_controls[sss_cid] = sc;
+
+ /* peel off initial sequence */
+ ber_scanf( ber, "{" );
+
+ i = 0;
+ do {
+ if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS )
+ break;
+ i++;
+ tag = ber_peek_tag( ber, &len );
+ } while ( tag != LBER_DEFAULT );
+
+ return rs->sr_err;
+}
+
+static int vlv_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ vlv_ctrl *vc, vc2;
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = NULL;
+
+ if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) {
+ rs->sr_text = "vlv control specified multiple times";
+ } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "vlv control value is absent";
+ } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "vlv control value is empty";
+ }
+ if ( rs->sr_text != NULL )
+ return rs->sr_err;
+
+ op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+
+ ber = (BerElement *)&berbuf;
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+
+ tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after );
+ if ( tag == LBER_ERROR ) {
+ return rs->sr_err;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) {
+ tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count );
+ if ( tag == LBER_ERROR )
+ return rs->sr_err;
+ BER_BVZERO( &vc2.vc_value );
+ } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) {
+ tag = ber_scanf( ber, "m", &vc2.vc_value );
+ if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value ))
+ return rs->sr_err;
+ } else {
+ return rs->sr_err;
+ }
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) {
+ struct berval bv;
+ tag = ber_scanf( ber, "m", &bv );
+ if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context))
+ return rs->sr_err;
+ AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len );
+ } else {
+ vc2.vc_context = 0;
+ }
+
+ vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx );
+ *vc = vc2;
+ op->o_controls[vlv_cid] = vc;
+ rs->sr_err = LDAP_SUCCESS;
+
+ return rs->sr_err;
+}
+
+static int sssvlv_connection_destroy( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+
+ if ( sort_conns[conn->c_conn_idx] ) {
+ free_sort_ops( conn, sort_conns[conn->c_conn_idx], si->svi_max_percon );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int sssvlv_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+ int rc;
+ int conn_index;
+
+ /* If not set, default to 1/2 of available threads */
+ if ( !si->svi_max )
+ si->svi_max = connection_pool_max / 2;
+
+ if ( dtblsize && !sort_conns ) {
+ ldap_pvt_thread_mutex_init( &sort_conns_mutex );
+ /* accommodate for c_conn_idx == -1 */
+ sort_conns = ch_calloc( dtblsize + 1, sizeof(sort_op **) );
+ for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) {
+ sort_conns[conn_index] = ch_calloc( si->svi_max_percon, sizeof(sort_op *) );
+ }
+ sort_conns++;
+ }
+
+ rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST );
+ if ( rc == LDAP_SUCCESS )
+ rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST );
+ return rc;
+}
+
+static ConfigTable sssvlv_cfg[] = {
+ { "sssvlv-max", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max),
+ "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' "
+ "DESC 'Maximum number of concurrent Sort requests' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "sssvlv-maxkeys", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max_keys),
+ "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' "
+ "DESC 'Maximum number of Keys in a Sort request' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SSSVLV_DEFAULT_MAX_KEYS } },
+ { "sssvlv-maxperconn", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max_percon),
+ "( OLcfgOvAt:21.3 NAME 'olcSssVlvMaxPerConn' "
+ "DESC 'Maximum number of concurrent paged search requests per connection' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN } },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs sssvlv_ocs[] = {
+ { "( OLcfgOvOc:21.1 "
+ "NAME 'olcSssVlvConfig' "
+ "DESC 'SSS VLV configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys $ olcSssVlvMaxPerConn ) )",
+ Cft_Overlay, sssvlv_cfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int sssvlv_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si;
+
+ if ( ov_count == 0 ) {
+ int rc;
+
+ rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ sss_parseCtrl,
+ 1 /* replace */,
+ &sss_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register Sort Request control '%s' (%d)\n",
+ LDAP_CONTROL_SORTREQUEST, rc );
+ return rc;
+ }
+
+ rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ vlv_parseCtrl,
+ 1 /* replace */,
+ &vlv_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register VLV Request control '%s' (%d)\n",
+ LDAP_CONTROL_VLVREQUEST, rc );
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST );
+ unregister_supported_control( LDAP_CONTROL_SORTREQUEST );
+#endif /* SLAP_CONFIG_DELETE */
+ return rc;
+ }
+ }
+
+ si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info));
+ on->on_bi.bi_private = si;
+
+ si->svi_max = 0;
+ si->svi_num = 0;
+ si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS;
+ si->svi_max_percon = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN;
+
+ ov_count++;
+
+ return LDAP_SUCCESS;
+}
+
+static int sssvlv_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private;
+ int conn_index;
+
+ ov_count--;
+ if ( !ov_count && sort_conns) {
+ sort_conns--;
+ for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) {
+ ch_free(sort_conns[conn_index]);
+ }
+ ch_free(sort_conns);
+ ldap_pvt_thread_mutex_destroy( &sort_conns_mutex );
+ }
+
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST );
+ overlay_unregister_control( be, LDAP_CONTROL_VLVREQUEST );
+ if ( ov_count == 0 ) {
+ unregister_supported_control( LDAP_CONTROL_SORTREQUEST );
+ unregister_supported_control( LDAP_CONTROL_VLVREQUEST );
+ }
+#endif /* SLAP_CONFIG_DELETE */
+
+ if ( si ) {
+ ch_free( si );
+ on->on_bi.bi_private = NULL;
+ }
+ return LDAP_SUCCESS;
+}
+
+static slap_overinst sssvlv;
+
+int sssvlv_initialize()
+{
+ int rc;
+
+ sssvlv.on_bi.bi_type = "sssvlv";
+ sssvlv.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ sssvlv.on_bi.bi_db_init = sssvlv_db_init;
+ sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy;
+ sssvlv.on_bi.bi_db_open = sssvlv_db_open;
+ sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy;
+ sssvlv.on_bi.bi_op_search = sssvlv_op_search;
+
+ sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs;
+
+ rc = config_register_schema( sssvlv_cfg, sssvlv_ocs );
+ if ( rc )
+ return rc;
+
+ rc = overlay_register( &sssvlv );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register server side sort overlay\n" );
+ }
+
+ return rc;
+}
+
+#if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC
+int init_module( int argc, char *argv[])
+{
+ return sssvlv_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_SSSVLV */
diff --git a/servers/slapd/overlays/syncprov.c b/servers/slapd/overlays/syncprov.c
new file mode 100644
index 0000000..f208846
--- /dev/null
+++ b/servers/slapd/overlays/syncprov.c
@@ -0,0 +1,4412 @@
+/* $OpenLDAP$ */
+/* syncprov.c - syncrepl provider */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SYNCPROV
+
+#include <ac/string.h>
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+#include "ldap_rq.h"
+
+#ifdef LDAP_DEVEL
+#define CHECK_CSN 1
+#endif
+
+/* A modify request on a particular entry */
+typedef struct modinst {
+ struct modinst *mi_next;
+ Operation *mi_op;
+} modinst;
+
+typedef struct modtarget {
+ struct modinst *mt_mods;
+ struct modinst *mt_tail;
+ struct berval mt_dn;
+ ldap_pvt_thread_mutex_t mt_mutex;
+} modtarget;
+
+/* All the info of a psearch result that's shared between
+ * multiple queues
+ */
+typedef struct resinfo {
+ struct syncres *ri_list;
+ Entry *ri_e;
+ struct berval ri_dn;
+ struct berval ri_ndn;
+ struct berval ri_uuid;
+ struct berval ri_csn;
+ struct berval ri_cookie;
+ char ri_isref;
+ ldap_pvt_thread_mutex_t ri_mutex;
+} resinfo;
+
+/* A queued result of a persistent search */
+typedef struct syncres {
+ struct syncres *s_next; /* list of results on this psearch queue */
+ struct syncres *s_rilist; /* list of psearches using this result */
+ resinfo *s_info;
+ char s_mode;
+} syncres;
+
+/* Record of a persistent search */
+typedef struct syncops {
+ struct syncops *s_next;
+ struct syncprov_info_t *s_si;
+ struct berval s_base; /* ndn of search base */
+ ID s_eid; /* entryID of search base */
+ Operation *s_op; /* search op */
+ int s_rid;
+ int s_sid;
+ struct berval s_filterstr;
+ int s_flags; /* search status */
+#define PS_IS_REFRESHING 0x01
+#define PS_IS_DETACHED 0x02
+#define PS_WROTE_BASE 0x04
+#define PS_FIND_BASE 0x08
+#define PS_FIX_FILTER 0x10
+#define PS_TASK_QUEUED 0x20
+
+ int s_inuse; /* reference count */
+ struct syncres *s_res;
+ struct syncres *s_restail;
+ void *s_pool_cookie;
+ ldap_pvt_thread_mutex_t s_mutex;
+} syncops;
+
+/* A received sync control */
+typedef struct sync_control {
+ struct sync_cookie sr_state;
+ int sr_rhint;
+} sync_control;
+
+#if 0 /* moved back to slap.h */
+#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync]
+#endif
+/* o_sync_mode uses data bits of o_sync */
+#define o_sync_mode o_ctrlflag[slap_cids.sc_LDAPsync]
+
+#define SLAP_SYNC_NONE (LDAP_SYNC_NONE<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_REFRESH (LDAP_SYNC_REFRESH_ONLY<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_PERSIST (LDAP_SYNC_RESERVED<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_REFRESH_AND_PERSIST (LDAP_SYNC_REFRESH_AND_PERSIST<<SLAP_CONTROL_SHIFT)
+
+/* Record of which searches matched at premodify step */
+typedef struct syncmatches {
+ struct syncmatches *sm_next;
+ syncops *sm_op;
+} syncmatches;
+
+/* Session log data */
+typedef struct slog_entry {
+ struct berval se_uuid;
+ struct berval se_csn;
+ int se_sid;
+ ber_tag_t se_tag;
+} slog_entry;
+
+typedef struct sessionlog {
+ BerVarray sl_mincsn;
+ int *sl_sids;
+ int sl_numcsns;
+ int sl_num;
+ int sl_size;
+ int sl_playing;
+ TAvlnode *sl_entries;
+ ldap_pvt_thread_rdwr_t sl_mutex;
+} sessionlog;
+
+/* Accesslog callback data */
+typedef struct syncprov_accesslog_deletes {
+ Operation *op;
+ SlapReply *rs;
+ sync_control *srs;
+ BerVarray ctxcsn;
+ int numcsns, *sids;
+ Avlnode *uuids;
+ BerVarray uuid_list;
+ int ndel, list_len;
+ char *uuid_buf;
+} syncprov_accesslog_deletes;
+
+/* The main state for this overlay */
+typedef struct syncprov_info_t {
+ syncops *si_ops;
+ struct berval si_contextdn;
+ struct berval si_logbase;
+ BerVarray si_ctxcsn; /* ldapsync context */
+ int *si_sids;
+ int si_numcsns;
+ int si_chkops; /* checkpointing info */
+ int si_chktime;
+ int si_numops; /* number of ops since last checkpoint */
+ int si_nopres; /* Skip present phase */
+ int si_usehint; /* use reload hint */
+ int si_active; /* True if there are active mods */
+ int si_dirty; /* True if the context is dirty, i.e changes
+ * have been made without updating the csn. */
+ time_t si_chklast; /* time of last checkpoint */
+ Avlnode *si_mods; /* entries being modified */
+ sessionlog *si_logs;
+ ldap_pvt_thread_rdwr_t si_csn_rwlock;
+ ldap_pvt_thread_mutex_t si_ops_mutex;
+ ldap_pvt_thread_mutex_t si_mods_mutex;
+ ldap_pvt_thread_mutex_t si_resp_mutex;
+} syncprov_info_t;
+
+typedef struct opcookie {
+ slap_overinst *son;
+ syncmatches *smatches;
+ modtarget *smt;
+ Entry *se;
+ struct berval sdn; /* DN of entry, for deletes */
+ struct berval sndn;
+ struct berval suuid; /* UUID of entry */
+ struct berval sctxcsn;
+ short osid; /* sid of op csn */
+ short rsid; /* sid of relay */
+ short sreference; /* Is the entry a reference? */
+ syncres ssres;
+} opcookie;
+
+typedef struct fbase_cookie {
+ struct berval *fdn; /* DN of a modified entry, for scope testing */
+ syncops *fss; /* persistent search we're testing against */
+ int fbase; /* if TRUE we found the search base and it's still valid */
+ int fscope; /* if TRUE then fdn is within the psearch scope */
+} fbase_cookie;
+
+static AttributeName csn_anlist[3];
+static AttributeName uuid_anlist[2];
+
+static AttributeDescription *ad_reqType, *ad_reqResult, *ad_reqDN,
+ *ad_reqEntryUUID, *ad_minCSN, *ad_reqNewDN;
+
+/* Build a LDAPsync intermediate state control */
+static int
+syncprov_state_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ int entry_sync_state,
+ LDAPControl **ctrls,
+ int num_ctrls,
+ int send_cookie,
+ struct berval *cookie )
+{
+ Attribute* a;
+ int ret;
+
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPControl *cp;
+ struct berval bv;
+ struct berval entryuuid_bv = BER_BVNULL;
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ AttributeDescription *desc = a->a_desc;
+ if ( desc == slap_schema.si_ad_entryUUID ) {
+ entryuuid_bv = a->a_nvals[0];
+ break;
+ }
+ }
+
+ /* FIXME: what if entryuuid is NULL or empty ? */
+
+ if ( send_cookie && cookie ) {
+ ber_printf( ber, "{eOON}",
+ entry_sync_state, &entryuuid_bv, cookie );
+ } else {
+ ber_printf( ber, "{eON}",
+ entry_sync_state, &entryuuid_bv );
+ }
+
+ ret = ber_flatten2( ber, &bv, 0 );
+ if ( ret == 0 ) {
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = LDAP_CONTROL_SYNC_STATE;
+ cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrls[num_ctrls] = cp;
+ }
+ ber_free_buf( ber );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_build_sync_ctrl: ber_flatten2 failed (%d)\n",
+ ret );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Build a LDAPsync final state control */
+static int
+syncprov_done_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrls,
+ int num_ctrls,
+ int send_cookie,
+ struct berval *cookie,
+ int refreshDeletes )
+{
+ int ret;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPControl *cp;
+ struct berval bv;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ ber_printf( ber, "{" );
+ if ( send_cookie && cookie ) {
+ ber_printf( ber, "O", cookie );
+ }
+ if ( refreshDeletes == LDAP_SYNC_REFRESH_DELETES ) {
+ ber_printf( ber, "b", refreshDeletes );
+ }
+ ber_printf( ber, "N}" );
+
+ ret = ber_flatten2( ber, &bv, 0 );
+ if ( ret == 0 ) {
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = LDAP_CONTROL_SYNC_DONE;
+ cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrls[num_ctrls] = cp;
+ }
+
+ ber_free_buf( ber );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "syncprov_done_ctrl: ber_flatten2 failed (%d)\n",
+ ret );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_sendinfo(
+ Operation *op,
+ SlapReply *rs,
+ int type,
+ struct berval *cookie,
+ int refreshDone,
+ BerVarray syncUUIDs,
+ int refreshDeletes )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval rspdata;
+
+ int ret;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( type ) {
+ switch ( type ) {
+ case LDAP_TAG_SYNC_NEW_COOKIE:
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
+ "sending a new cookie=%s\n",
+ op->o_log_prefix, cookie->bv_val );
+ ber_printf( ber, "tO", type, cookie );
+ break;
+ case LDAP_TAG_SYNC_REFRESH_DELETE:
+ case LDAP_TAG_SYNC_REFRESH_PRESENT:
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
+ "%s cookie=%s\n",
+ op->o_log_prefix,
+ type == LDAP_TAG_SYNC_REFRESH_DELETE ? "refreshDelete" : "refreshPresent",
+ cookie ? cookie->bv_val : "" );
+ ber_printf( ber, "t{", type );
+ if ( cookie ) {
+ ber_printf( ber, "O", cookie );
+ }
+ if ( refreshDone == 0 ) {
+ ber_printf( ber, "b", refreshDone );
+ }
+ ber_printf( ber, "N}" );
+ break;
+ case LDAP_TAG_SYNC_ID_SET:
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
+ "%s syncIdSet cookie=%s\n",
+ op->o_log_prefix, refreshDeletes ? "delete" : "present",
+ cookie ? cookie->bv_val : "" );
+ ber_printf( ber, "t{", type );
+ if ( cookie ) {
+ ber_printf( ber, "O", cookie );
+ }
+ if ( refreshDeletes == 1 ) {
+ ber_printf( ber, "b", refreshDeletes );
+ }
+ ber_printf( ber, "[W]", syncUUIDs );
+ ber_printf( ber, "N}" );
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE,
+ "%s syncprov_sendinfo: invalid syncinfo type (%d)\n",
+ op->o_log_prefix, type );
+ return LDAP_OTHER;
+ }
+ }
+
+ ret = ber_flatten2( ber, &rspdata, 0 );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "syncprov_sendinfo: ber_flatten2 failed (%d)\n",
+ ret );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ rs->sr_rspoid = LDAP_SYNC_INFO;
+ rs->sr_rspdata = &rspdata;
+ send_ldap_intermediate( op, rs );
+ rs->sr_rspdata = NULL;
+ ber_free_buf( ber );
+
+ return LDAP_SUCCESS;
+}
+
+/* Find a modtarget in an AVL tree */
+static int
+sp_avl_cmp( const void *c1, const void *c2 )
+{
+ const modtarget *m1, *m2;
+ int rc;
+
+ m1 = c1; m2 = c2;
+ rc = m1->mt_dn.bv_len - m2->mt_dn.bv_len;
+
+ if ( rc ) return rc;
+ return ber_bvcmp( &m1->mt_dn, &m2->mt_dn );
+}
+
+static int
+sp_uuid_cmp( const void *l, const void *r )
+{
+ const struct berval *left = l, *right = r;
+
+ return ber_bvcmp( left, right );
+}
+
+static int
+syncprov_sessionlog_cmp( const void *l, const void *r )
+{
+ const slog_entry *left = l, *right = r;
+ int ret = ber_bvcmp( &left->se_csn, &right->se_csn );
+ if ( !ret )
+ ret = ber_bvcmp( &left->se_uuid, &right->se_uuid );
+ /* Only time we have two modifications with same CSN is when we detect a
+ * rename during replication.
+ * We invert the test here because LDAP_REQ_MODDN is
+ * numerically greater than LDAP_REQ_MODIFY but we
+ * want it to occur first.
+ */
+ if ( !ret )
+ ret = right->se_tag - left->se_tag;
+
+ return ret;
+}
+
+/* syncprov_findbase:
+ * finds the true DN of the base of a search (with alias dereferencing) and
+ * checks to make sure the base entry doesn't get replaced with a different
+ * entry (e.g., swapping trees via ModDN, or retargeting an alias). If a
+ * change is detected, any persistent search on this base must be terminated /
+ * reloaded.
+ * On the first call, we just save the DN and entryID. On subsequent calls
+ * we compare the DN and entryID with the saved values.
+ */
+static int
+findbase_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+
+ if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
+ fbase_cookie *fc = sc->sc_private;
+
+ /* If no entryID, we're looking for the first time.
+ * Just store whatever we got.
+ */
+ if ( fc->fss->s_eid == NOID ) {
+ fc->fbase = 2;
+ fc->fss->s_eid = rs->sr_entry->e_id;
+ ber_dupbv( &fc->fss->s_base, &rs->sr_entry->e_nname );
+
+ } else if ( rs->sr_entry->e_id == fc->fss->s_eid &&
+ dn_match( &rs->sr_entry->e_nname, &fc->fss->s_base )) {
+
+ /* OK, the DN is the same and the entryID is the same. */
+ fc->fbase = 1;
+ }
+ }
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "findbase failed! %d\n", rs->sr_err );
+ }
+ return LDAP_SUCCESS;
+}
+
+static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL };
+static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
+
+static int
+syncprov_findbase( Operation *op, fbase_cookie *fc )
+{
+ /* Use basic parameters from syncrepl search, but use
+ * current op's threadctx / tmpmemctx
+ */
+ ldap_pvt_thread_mutex_lock( &fc->fss->s_mutex );
+ if ( fc->fss->s_flags & PS_FIND_BASE ) {
+ slap_callback cb = {0};
+ Operation fop;
+ SlapReply frs = { REP_RESULT };
+ int rc;
+
+ fc->fss->s_flags ^= PS_FIND_BASE;
+ ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex );
+
+ fop = *fc->fss->s_op;
+
+ fop.o_bd = fop.o_bd->bd_self;
+ fop.o_hdr = op->o_hdr;
+ fop.o_time = op->o_time;
+ fop.o_tincr = op->o_tincr;
+ fop.o_extra = op->o_extra;
+
+ cb.sc_response = findbase_cb;
+ cb.sc_private = fc;
+
+ fop.o_sync_mode = 0; /* turn off sync mode */
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+ fop.o_callback = &cb;
+ fop.o_tag = LDAP_REQ_SEARCH;
+ fop.ors_scope = LDAP_SCOPE_BASE;
+ fop.ors_limit = NULL;
+ fop.ors_slimit = 1;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_attrs = slap_anlist_no_attrs;
+ fop.ors_attrsonly = 1;
+ fop.ors_filter = &generic_filter;
+ fop.ors_filterstr = generic_filterstr;
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_findbase: searching\n", op->o_log_prefix );
+ rc = fop.o_bd->be_search( &fop, &frs );
+ } else {
+ ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex );
+ fc->fbase = 1;
+ }
+
+ /* After the first call, see if the fdn resides in the scope */
+ if ( fc->fbase == 1 ) {
+ switch ( fc->fss->s_op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ fc->fscope = dn_match( fc->fdn, &fc->fss->s_base );
+ break;
+ case LDAP_SCOPE_ONELEVEL: {
+ struct berval pdn;
+ dnParent( fc->fdn, &pdn );
+ fc->fscope = dn_match( &pdn, &fc->fss->s_base );
+ break; }
+ case LDAP_SCOPE_SUBTREE:
+ fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base );
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ) &&
+ !dn_match( fc->fdn, &fc->fss->s_base );
+ break;
+ }
+ }
+
+ if ( fc->fbase )
+ return LDAP_SUCCESS;
+
+ /* If entryID has changed, then the base of this search has
+ * changed. Invalidate the psearch.
+ */
+ return LDAP_NO_SUCH_OBJECT;
+}
+
+/* syncprov_findcsn:
+ * This function has three different purposes, but they all use a search
+ * that filters on entryCSN so they're combined here.
+ * 1: at startup time, after a contextCSN has been read from the database,
+ * we search for all entries with CSN >= contextCSN in case the contextCSN
+ * was not checkpointed at the previous shutdown.
+ *
+ * 2: when the current contextCSN is known and we have a sync cookie, we search
+ * for one entry with CSN = the cookie CSN. If not found, try <= cookie CSN.
+ * If an entry is found, the cookie CSN is valid, otherwise it is stale.
+ *
+ * 3: during a refresh phase, we search for all entries with CSN <= the cookie
+ * CSN, and generate Present records for them. We always collect this result
+ * in SyncID sets, even if there's only one match.
+ */
+typedef enum find_csn_t {
+ FIND_MAXCSN = 1,
+ FIND_CSN = 2,
+ FIND_PRESENT = 3
+} find_csn_t;
+
+static int
+findmax_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
+ struct berval *maxcsn = op->o_callback->sc_private;
+ Attribute *a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryCSN );
+
+ if ( a && ber_bvcmp( &a->a_vals[0], maxcsn ) > 0 &&
+ slap_parse_csn_sid( &a->a_vals[0] ) == slap_serverID ) {
+ maxcsn->bv_len = a->a_vals[0].bv_len;
+ strcpy( maxcsn->bv_val, a->a_vals[0].bv_val );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+findcsn_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+
+ /* We just want to know that at least one exists, so it's OK if
+ * we exceed the unchecked limit.
+ */
+ if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ||
+ (rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS )) {
+ sc->sc_private = (void *)1;
+ }
+ return LDAP_SUCCESS;
+}
+
+/* Build a list of entryUUIDs for sending in a SyncID set */
+
+#define UUID_LEN 16
+
+typedef struct fpres_cookie {
+ int num;
+ BerVarray uuids;
+ char *last;
+} fpres_cookie;
+
+static int
+findpres_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ fpres_cookie *pc = sc->sc_private;
+ Attribute *a;
+ int ret = SLAP_CB_CONTINUE;
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a ) {
+ pc->uuids[pc->num].bv_val = pc->last;
+ AC_MEMCPY( pc->uuids[pc->num].bv_val, a->a_nvals[0].bv_val,
+ pc->uuids[pc->num].bv_len );
+ pc->num++;
+ pc->last = pc->uuids[pc->num].bv_val;
+ pc->uuids[pc->num].bv_val = NULL;
+ }
+ ret = LDAP_SUCCESS;
+ if ( pc->num != SLAP_SYNCUUID_SET_SIZE )
+ break;
+ /* FALLTHRU */
+ case REP_RESULT:
+ ret = rs->sr_err;
+ if ( pc->num ) {
+ ret = syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, NULL,
+ 0, pc->uuids, 0 );
+ pc->uuids[pc->num].bv_val = pc->last;
+ pc->num = 0;
+ pc->last = pc->uuids[0].bv_val;
+ }
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int
+syncprov_findcsn( Operation *op, find_csn_t mode, struct berval *csn )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+
+ slap_callback cb = {0};
+ Operation fop;
+ SlapReply frs = { REP_RESULT };
+ char buf[LDAP_PVT_CSNSTR_BUFSIZE + STRLENOF("(entryCSN<=)")];
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ struct berval maxcsn;
+ Filter cf;
+ AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
+ fpres_cookie pcookie;
+ sync_control *srs = NULL;
+ struct slap_limits_set fc_limits;
+ int i, rc = LDAP_SUCCESS, findcsn_retry = 1;
+ int maxid;
+
+ if ( mode != FIND_MAXCSN ) {
+ srs = op->o_controls[slap_cids.sc_LDAPsync];
+ }
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: mode=%s csn=%s\n",
+ op->o_log_prefix,
+ mode == FIND_MAXCSN ?
+ "FIND_MAXCSN" :
+ mode == FIND_CSN ?
+ "FIND_CSN" :
+ "FIND_PRESENT",
+ csn ? csn->bv_val : "" );
+
+again:
+ fop = *op;
+ fop.o_sync_mode &= SLAP_CONTROL_MASK; /* turn off sync_mode */
+ /* We want pure entries, not referrals */
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ cf.f_ava = &eq;
+ cf.f_av_desc = slap_schema.si_ad_entryCSN;
+ BER_BVZERO( &cf.f_av_value );
+ cf.f_next = NULL;
+
+ fop.o_callback = &cb;
+ fop.ors_limit = NULL;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_filter = &cf;
+ fop.ors_filterstr.bv_val = buf;
+
+ switch( mode ) {
+ case FIND_MAXCSN:
+ cf.f_choice = LDAP_FILTER_GE;
+ /* If there are multiple CSNs, use the one with our serverID */
+ for ( i=0; i<si->si_numcsns; i++) {
+ if ( slap_serverID == si->si_sids[i] ) {
+ maxid = i;
+ break;
+ }
+ }
+ if ( i == si->si_numcsns ) {
+ /* No match: this is multimaster, and none of the content in the DB
+ * originated locally. Treat like no CSN.
+ */
+ return LDAP_NO_SUCH_OBJECT;
+ }
+ cf.f_av_value = si->si_ctxcsn[maxid];
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN>=%s)", cf.f_av_value.bv_val );
+ if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
+ return LDAP_OTHER;
+ }
+ fop.ors_attrsonly = 0;
+ fop.ors_attrs = csn_anlist;
+ fop.ors_slimit = SLAP_NO_LIMIT;
+ cb.sc_private = &maxcsn;
+ cb.sc_response = findmax_cb;
+ strcpy( cbuf, cf.f_av_value.bv_val );
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = cf.f_av_value.bv_len;
+ break;
+ case FIND_CSN:
+ if ( BER_BVISEMPTY( &cf.f_av_value )) {
+ cf.f_av_value = *csn;
+ }
+ fop.o_dn = op->o_bd->be_rootdn;
+ fop.o_ndn = op->o_bd->be_rootndn;
+ fop.o_req_dn = op->o_bd->be_suffix[0];
+ fop.o_req_ndn = op->o_bd->be_nsuffix[0];
+ /* Look for exact match the first time */
+ if ( findcsn_retry ) {
+ cf.f_choice = LDAP_FILTER_EQUALITY;
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN=%s)", cf.f_av_value.bv_val );
+ /* On retry, look for <= */
+ } else {
+ cf.f_choice = LDAP_FILTER_LE;
+ fop.ors_limit = &fc_limits;
+ memset( &fc_limits, 0, sizeof( fc_limits ));
+ fc_limits.lms_s_unchecked = 1;
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN<=%s)", cf.f_av_value.bv_val );
+ }
+ if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
+ return LDAP_OTHER;
+ }
+ fop.ors_attrsonly = 1;
+ fop.ors_attrs = slap_anlist_no_attrs;
+ fop.ors_slimit = 1;
+ cb.sc_private = NULL;
+ cb.sc_response = findcsn_cb;
+ break;
+ case FIND_PRESENT:
+ fop.ors_filter = op->ors_filter;
+ fop.ors_filterstr = op->ors_filterstr;
+ fop.ors_attrsonly = 0;
+ fop.ors_attrs = uuid_anlist;
+ fop.ors_slimit = SLAP_NO_LIMIT;
+ cb.sc_private = &pcookie;
+ cb.sc_response = findpres_cb;
+ pcookie.num = 0;
+
+ /* preallocate storage for a full set */
+ pcookie.uuids = op->o_tmpalloc( (SLAP_SYNCUUID_SET_SIZE+1) *
+ sizeof(struct berval) + SLAP_SYNCUUID_SET_SIZE * UUID_LEN,
+ op->o_tmpmemctx );
+ pcookie.last = (char *)(pcookie.uuids + SLAP_SYNCUUID_SET_SIZE+1);
+ pcookie.uuids[0].bv_val = pcookie.last;
+ pcookie.uuids[0].bv_len = UUID_LEN;
+ for (i=1; i<SLAP_SYNCUUID_SET_SIZE; i++) {
+ pcookie.uuids[i].bv_val = pcookie.uuids[i-1].bv_val + UUID_LEN;
+ pcookie.uuids[i].bv_len = UUID_LEN;
+ }
+ break;
+ }
+
+ fop.o_bd->bd_info = (BackendInfo *)on->on_info;
+ fop.o_bd->be_search( &fop, &frs );
+ fop.o_bd->bd_info = (BackendInfo *)on;
+
+ switch( mode ) {
+ case FIND_MAXCSN:
+ if ( ber_bvcmp( &si->si_ctxcsn[maxid], &maxcsn )) {
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+ assert( !syn->ssyn_validate( syn, &maxcsn ));
+#endif
+ ber_bvreplace( &si->si_ctxcsn[maxid], &maxcsn );
+ si->si_numops++; /* ensure a checkpoint */
+ }
+ break;
+ case FIND_CSN:
+ /* If matching CSN was not found, invalidate the context. */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: csn%s=%s %sfound\n",
+ op->o_log_prefix,
+ cf.f_choice == LDAP_FILTER_EQUALITY ? "=" : "<",
+ cf.f_av_value.bv_val, cb.sc_private ? "" : "not " );
+ if ( !cb.sc_private ) {
+ /* If we didn't find an exact match, then try for <= */
+ if ( findcsn_retry ) {
+ findcsn_retry = 0;
+ rs_reinit( &frs, REP_RESULT );
+ goto again;
+ }
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ break;
+ case FIND_PRESENT:
+ op->o_tmpfree( pcookie.uuids, op->o_tmpmemctx );
+ break;
+ }
+
+ return rc;
+}
+
+static void free_resinfo( syncres *sr )
+{
+ syncres **st;
+ resinfo *ri = sr->s_info;
+ int freeit = 0;
+
+ ldap_pvt_thread_mutex_lock( &ri->ri_mutex );
+ for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) {
+ if (*st == sr) {
+ *st = sr->s_rilist;
+ if ( !sr->s_info->ri_list )
+ freeit = 1;
+ sr->s_info = NULL;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &ri->ri_mutex );
+ if ( freeit ) {
+ ldap_pvt_thread_mutex_destroy( &ri->ri_mutex );
+ if ( ri->ri_e )
+ entry_free( ri->ri_e );
+ if ( !BER_BVISNULL( &ri->ri_cookie ))
+ ch_free( ri->ri_cookie.bv_val );
+ ch_free( ri );
+ }
+}
+
+#define FS_UNLINK 1
+#define FS_LOCK 2
+#define FS_DEFER 4
+
+#define FSR_NOTFREE 0
+#define FSR_DIDFREE 1
+#define FSR_CANFREE 2
+
+static int
+syncprov_free_syncop( syncops *so, int flags )
+{
+ syncres *sr, *srnext;
+ GroupAssertion *ga, *gnext;
+
+ if ( flags & FS_LOCK )
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ /* already being freed, or still in use */
+ if ( !so->s_inuse || so->s_inuse > 1 ) {
+ if ( flags & FS_LOCK )
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ if ( !( flags & FS_DEFER ) && so->s_inuse )
+ so->s_inuse--;
+ return FSR_NOTFREE;
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+
+ /* caller wants to cleanup other stuff before actual free */
+ if ( flags & FS_DEFER )
+ return FSR_CANFREE;
+
+ if (( flags & FS_UNLINK ) && so->s_si ) {
+ syncops **sop;
+ ldap_pvt_thread_mutex_lock( &so->s_si->si_ops_mutex );
+ for ( sop = &so->s_si->si_ops; *sop; sop = &(*sop)->s_next ) {
+ if ( *sop == so ) {
+ *sop = so->s_next;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_si->si_ops_mutex );
+ }
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ filter_free( so->s_op->ors_filter );
+ for ( ga = so->s_op->o_groups; ga; ga=gnext ) {
+ gnext = ga->ga_next;
+ ch_free( ga );
+ }
+ ch_free( so->s_op );
+ }
+ ch_free( so->s_base.bv_val );
+ for ( sr=so->s_res; sr; sr=srnext ) {
+ srnext = sr->s_next;
+ free_resinfo( sr );
+ ch_free( sr );
+ }
+ ldap_pvt_thread_mutex_destroy( &so->s_mutex );
+ ch_free( so );
+ return FSR_DIDFREE;
+}
+
+/* Send a persistent search response */
+static int
+syncprov_sendresp( Operation *op, resinfo *ri, syncops *so, int mode )
+{
+ SlapReply rs = { REP_SEARCH };
+ struct berval cookie, csns[2];
+ Entry e_uuid = {0};
+ Attribute a_uuid = {0};
+
+ if ( so->s_op->o_abandon )
+ return SLAPD_ABANDON;
+
+ rs.sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, op->o_tmpmemctx );
+ rs.sr_ctrls[1] = NULL;
+ rs.sr_flags = REP_CTRLS_MUSTBEFREED;
+ csns[0] = ri->ri_csn;
+ BER_BVZERO( &csns[1] );
+ slap_compose_sync_cookie( op, &cookie, csns, so->s_rid,
+ slap_serverID ? slap_serverID : -1, NULL );
+
+#ifdef LDAP_DEBUG
+ if ( so->s_sid > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: to=%03x, cookie=%s\n",
+ op->o_log_prefix, so->s_sid, cookie.bv_val );
+ } else {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: cookie=%s\n",
+ op->o_log_prefix, cookie.bv_val );
+ }
+#endif
+
+ e_uuid.e_attrs = &a_uuid;
+ a_uuid.a_desc = slap_schema.si_ad_entryUUID;
+ a_uuid.a_nvals = &ri->ri_uuid;
+ rs.sr_err = syncprov_state_ctrl( op, &rs, &e_uuid,
+ mode, rs.sr_ctrls, 0, 1, &cookie );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+
+ rs.sr_entry = &e_uuid;
+ if ( mode == LDAP_SYNC_ADD || mode == LDAP_SYNC_MODIFY ) {
+ e_uuid = *ri->ri_e;
+ e_uuid.e_private = NULL;
+ }
+
+ switch( mode ) {
+ case LDAP_SYNC_ADD:
+ if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
+ rs.sr_ref = get_entry_referrals( op, rs.sr_entry );
+ rs.sr_err = send_search_reference( op, &rs );
+ ber_bvarray_free( rs.sr_ref );
+ break;
+ }
+ /* fallthru */
+ case LDAP_SYNC_MODIFY:
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: sending %s, dn=%s\n",
+ op->o_log_prefix,
+ mode == LDAP_SYNC_ADD ? "LDAP_SYNC_ADD" : "LDAP_SYNC_MODIFY",
+ e_uuid.e_nname.bv_val );
+ rs.sr_attrs = op->ors_attrs;
+ rs.sr_err = send_search_entry( op, &rs );
+ break;
+ case LDAP_SYNC_DELETE:
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: "
+ "sending LDAP_SYNC_DELETE, dn=%s\n",
+ op->o_log_prefix, ri->ri_dn.bv_val );
+ e_uuid.e_attrs = NULL;
+ e_uuid.e_name = ri->ri_dn;
+ e_uuid.e_nname = ri->ri_ndn;
+ if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
+ struct berval bv = BER_BVNULL;
+ rs.sr_ref = &bv;
+ rs.sr_err = send_search_reference( op, &rs );
+ } else {
+ rs.sr_err = send_search_entry( op, &rs );
+ }
+ break;
+ default:
+ assert(0);
+ }
+ return rs.sr_err;
+}
+
+static void
+syncprov_qstart( syncops *so );
+
+/* Play back queued responses */
+static int
+syncprov_qplay( Operation *op, syncops *so )
+{
+ syncres *sr;
+ int rc = 0;
+
+ do {
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ sr = so->s_res;
+ /* Exit loop with mutex held */
+ if ( !sr )
+ break;
+ so->s_res = sr->s_next;
+ if ( !so->s_res )
+ so->s_restail = NULL;
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+
+ if ( !so->s_op->o_abandon ) {
+
+ if ( sr->s_mode == LDAP_SYNC_NEW_COOKIE ) {
+ SlapReply rs = { REP_INTERMEDIATE };
+
+ rc = syncprov_sendinfo( op, &rs, LDAP_TAG_SYNC_NEW_COOKIE,
+ &sr->s_info->ri_cookie, 0, NULL, 0 );
+ } else {
+ rc = syncprov_sendresp( op, sr->s_info, so, sr->s_mode );
+ }
+ } else {
+ /* set rc so we don't do a new qstart */
+ rc = 1;
+ }
+
+ free_resinfo( sr );
+ ch_free( sr );
+
+ if ( so->s_op->o_abandon )
+ continue;
+
+ /* Exit loop with mutex held */
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ break;
+
+ } while (1);
+
+ /* We now only send one change at a time, to prevent one
+ * psearch from hogging all the CPU. Resubmit this task if
+ * there are more responses queued and no errors occurred.
+ */
+
+ if ( rc == 0 && so->s_res ) {
+ syncprov_qstart( so );
+ }
+
+ return rc;
+}
+
+static int
+syncprov_drop_psearch( syncops *so, int lock );
+
+/* task for playing back queued responses */
+static void *
+syncprov_qtask( void *ctx, void *arg )
+{
+ syncops *so = arg;
+ OperationBuffer opbuf;
+ Operation *op;
+ BackendDB be;
+ int rc, flag, frc;
+
+ op = &opbuf.ob_op;
+ *op = *so->s_op;
+ op->o_hdr = &opbuf.ob_hdr;
+ op->o_controls = opbuf.ob_controls;
+ memset( op->o_controls, 0, sizeof(opbuf.ob_controls) );
+ op->o_sync = SLAP_CONTROL_IGNORED;
+
+ *op->o_hdr = *so->s_op->o_hdr;
+
+ op->o_tmpmemctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 1);
+ op->o_tmpmfuncs = &slap_sl_mfuncs;
+ op->o_threadctx = ctx;
+ operation_counter_init( op, ctx );
+
+ /* syncprov_qplay expects a fake db */
+ be = *so->s_op->o_bd;
+ be.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &be;
+ LDAP_SLIST_FIRST(&op->o_extra) = NULL;
+ op->o_callback = NULL;
+
+ rc = syncprov_qplay( op, so );
+
+ /* if an error occurred, or no responses left, task is no longer queued */
+ if ( !rc && !so->s_res )
+ rc = 1;
+
+ flag = FS_UNLINK;
+ if ( rc && op->o_abandon )
+ flag = FS_DEFER;
+
+ /* decrement use count... */
+ frc = syncprov_free_syncop( so, flag );
+ if ( frc == FSR_NOTFREE ) {
+ if ( rc )
+ /* if we didn't unlink, and task is no longer queued, clear flag */
+ so->s_flags ^= PS_TASK_QUEUED;
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ }
+
+ /* if we got abandoned while processing, cleanup now */
+ if ( frc == FSR_CANFREE ) {
+ syncprov_drop_psearch( so, 1 );
+ }
+
+ return NULL;
+}
+
+/* Start the task to play back queued psearch responses */
+static void
+syncprov_qstart( syncops *so )
+{
+ so->s_flags |= PS_TASK_QUEUED;
+ so->s_inuse++;
+ ldap_pvt_thread_pool_submit2( &connection_pool,
+ syncprov_qtask, so, &so->s_pool_cookie );
+}
+
+/* Queue a persistent search response */
+static int
+syncprov_qresp( opcookie *opc, syncops *so, int mode )
+{
+ syncres *sr;
+ resinfo *ri;
+ int srsize;
+ struct berval csn = opc->sctxcsn;
+
+ sr = ch_malloc( sizeof( syncres ));
+ sr->s_next = NULL;
+ sr->s_mode = mode;
+ if ( !opc->ssres.s_info ) {
+
+ srsize = sizeof( resinfo );
+ if ( csn.bv_len )
+ srsize += csn.bv_len + 1;
+
+ if ( opc->se ) {
+ Attribute *a;
+ ri = ch_malloc( srsize );
+ ri->ri_dn = opc->se->e_name;
+ ri->ri_ndn = opc->se->e_nname;
+ a = attr_find( opc->se->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a )
+ ri->ri_uuid = a->a_nvals[0];
+ else
+ ri->ri_uuid.bv_len = 0;
+ if ( csn.bv_len ) {
+ ri->ri_csn.bv_val = (char *)(ri + 1);
+ ri->ri_csn.bv_len = csn.bv_len;
+ memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len );
+ ri->ri_csn.bv_val[csn.bv_len] = '\0';
+ } else {
+ ri->ri_csn.bv_val = NULL;
+ }
+ } else {
+ srsize += opc->suuid.bv_len +
+ opc->sdn.bv_len + 1 + opc->sndn.bv_len + 1;
+ ri = ch_malloc( srsize );
+ ri->ri_dn.bv_val = (char *)(ri + 1);
+ ri->ri_dn.bv_len = opc->sdn.bv_len;
+ ri->ri_ndn.bv_val = lutil_strcopy( ri->ri_dn.bv_val,
+ opc->sdn.bv_val ) + 1;
+ ri->ri_ndn.bv_len = opc->sndn.bv_len;
+ ri->ri_uuid.bv_val = lutil_strcopy( ri->ri_ndn.bv_val,
+ opc->sndn.bv_val ) + 1;
+ ri->ri_uuid.bv_len = opc->suuid.bv_len;
+ AC_MEMCPY( ri->ri_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len );
+ if ( csn.bv_len ) {
+ ri->ri_csn.bv_val = ri->ri_uuid.bv_val + ri->ri_uuid.bv_len;
+ memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len );
+ ri->ri_csn.bv_val[csn.bv_len] = '\0';
+ } else {
+ ri->ri_csn.bv_val = NULL;
+ }
+ }
+ ri->ri_list = &opc->ssres;
+ ri->ri_e = opc->se;
+ ri->ri_csn.bv_len = csn.bv_len;
+ ri->ri_isref = opc->sreference;
+ BER_BVZERO( &ri->ri_cookie );
+ ldap_pvt_thread_mutex_init( &ri->ri_mutex );
+ opc->se = NULL;
+ opc->ssres.s_info = ri;
+ }
+ ri = opc->ssres.s_info;
+ sr->s_info = ri;
+ ldap_pvt_thread_mutex_lock( &ri->ri_mutex );
+ sr->s_rilist = ri->ri_list;
+ ri->ri_list = sr;
+ if ( mode == LDAP_SYNC_NEW_COOKIE && BER_BVISNULL( &ri->ri_cookie )) {
+ syncprov_info_t *si = opc->son->on_bi.bi_private;
+
+ slap_compose_sync_cookie( NULL, &ri->ri_cookie, si->si_ctxcsn,
+ so->s_rid, slap_serverID ? slap_serverID : -1, NULL );
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_qresp: "
+ "set up a new syncres mode=%d csn=%s\n",
+ so->s_op->o_log_prefix, mode, csn.bv_val ? csn.bv_val : "" );
+ ldap_pvt_thread_mutex_unlock( &ri->ri_mutex );
+
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ if ( !so->s_res ) {
+ so->s_res = sr;
+ } else {
+ so->s_restail->s_next = sr;
+ }
+ so->s_restail = sr;
+
+ /* If the base of the psearch was modified, check it next time round */
+ if ( so->s_flags & PS_WROTE_BASE ) {
+ so->s_flags ^= PS_WROTE_BASE;
+ so->s_flags |= PS_FIND_BASE;
+ }
+ if (( so->s_flags & (PS_IS_DETACHED|PS_TASK_QUEUED)) == PS_IS_DETACHED ) {
+ syncprov_qstart( so );
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_drop_psearch( syncops *so, int lock )
+{
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ if ( lock )
+ ldap_pvt_thread_mutex_lock( &so->s_op->o_conn->c_mutex );
+ so->s_op->o_conn->c_n_ops_executing--;
+ so->s_op->o_conn->c_n_ops_completed++;
+ LDAP_STAILQ_REMOVE( &so->s_op->o_conn->c_ops, so->s_op, Operation,
+ o_next );
+ if ( lock )
+ ldap_pvt_thread_mutex_unlock( &so->s_op->o_conn->c_mutex );
+ }
+ return syncprov_free_syncop( so, FS_LOCK );
+}
+
+static int
+syncprov_ab_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ op->o_callback = sc->sc_next;
+ syncprov_drop_psearch( sc->sc_private, 0 );
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+static int
+syncprov_op_abandon( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncops *so, **sop;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( sop=&si->si_ops; (so = *sop); sop = &(*sop)->s_next ) {
+ if ( so->s_op->o_connid == op->o_connid &&
+ so->s_op->o_msgid == op->orn_msgid ) {
+ so->s_op->o_abandon = 1;
+ *sop = so->s_next;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( so ) {
+ /* Is this really a Cancel exop? */
+ if ( op->o_tag != LDAP_REQ_ABANDON ) {
+ so->s_op->o_cancel = SLAP_CANCEL_ACK;
+ rs->sr_err = LDAP_CANCELLED;
+ send_ldap_result( so->s_op, rs );
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = syncprov_ab_cleanup;
+ cb->sc_next = op->o_callback;
+ cb->sc_private = so;
+ op->o_callback = cb;
+ return SLAP_CB_CONTINUE;
+ }
+ }
+ /* if task is active, it must drop itself */
+ if ( !( so->s_flags & PS_TASK_QUEUED ))
+ syncprov_drop_psearch( so, 0 );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* Find which persistent searches are affected by this operation */
+static void
+syncprov_matchops( Operation *op, opcookie *opc, int saveit )
+{
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+
+ fbase_cookie fc;
+ syncops **pss;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc, gonext;
+ BackendDB *b0 = op->o_bd, db;
+
+ fc.fdn = saveit ? &op->o_req_ndn : &opc->sndn;
+ if ( !saveit && op->o_tag == LDAP_REQ_DELETE ) {
+ /* Delete succeeded, there is no entry */
+ } else if ( op->o_tag != LDAP_REQ_ADD ) {
+ if ( !SLAP_ISOVERLAY( op->o_bd )) {
+ db = *op->o_bd;
+ op->o_bd = &db;
+ }
+ rc = overlay_entry_get_ov( op, fc.fdn, NULL, NULL, 0, &e, on );
+ /* If we're sending responses now, make a copy and unlock the DB */
+ if ( e && !saveit ) {
+ if ( !opc->se )
+ opc->se = entry_dup( e );
+ overlay_entry_release_ov( op, e, 0, on );
+ e = opc->se;
+ }
+ if ( rc ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "%s check, error finding entry dn=%s in database\n",
+ op->o_log_prefix, saveit ? "initial" : "final", fc.fdn->bv_val );
+ op->o_bd = b0;
+ return;
+ }
+ } else {
+ e = op->ora_e;
+ if ( !saveit ) {
+ if ( !opc->se )
+ opc->se = entry_dup( e );
+ e = opc->se;
+ }
+ }
+
+ if ( saveit || op->o_tag == LDAP_REQ_ADD ) {
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ ber_dupbv_x( &opc->sdn, &op->orr_newDN, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sndn, &op->orr_nnewDN, op->o_tmpmemctx );
+ } else {
+ ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx );
+ }
+ opc->sreference = is_entry_referral( e );
+ a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a )
+ ber_dupbv_x( &opc->suuid, &a->a_nvals[0], op->o_tmpmemctx );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "%srecording uuid for dn=%s on opc=%p\n",
+ op->o_log_prefix, a ? "" : "not ", opc->sdn.bv_val, opc );
+ }
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for (pss = &si->si_ops; *pss; pss = gonext ? &(*pss)->s_next : pss)
+ {
+ Operation op2;
+ Opheader oh;
+ syncmatches *sm;
+ int found = 0;
+ syncops *snext, *ss = *pss;
+
+ gonext = 1;
+ if ( ss->s_op->o_abandon )
+ continue;
+
+ /* Don't send ops back to the originator */
+ if ( opc->osid > 0 && opc->osid == ss->s_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "skipping original sid %03x\n",
+ ss->s_op->o_log_prefix, opc->osid );
+ continue;
+ }
+
+ /* Don't send ops back to the messenger */
+ if ( opc->rsid > 0 && opc->rsid == ss->s_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "skipping relayed sid %03x\n",
+ ss->s_op->o_log_prefix, opc->rsid );
+ continue;
+ }
+
+ /* validate base */
+ fc.fss = ss;
+ fc.fbase = 0;
+ fc.fscope = 0;
+
+ /* If the base of the search is missing, signal a refresh */
+ rc = syncprov_findbase( op, &fc );
+ if ( rc != LDAP_SUCCESS ) {
+ SlapReply rs = {REP_RESULT};
+ send_ldap_error( ss->s_op, &rs, LDAP_SYNC_REFRESH_REQUIRED,
+ "search base has changed" );
+ snext = ss->s_next;
+ if ( syncprov_drop_psearch( ss, 1 ) )
+ *pss = snext;
+ gonext = 0;
+ continue;
+ }
+
+ /* If we're sending results now, look for this op in old matches */
+ if ( !saveit ) {
+ syncmatches *old;
+
+ /* Did we modify the search base? */
+ if ( dn_match( &op->o_req_ndn, &ss->s_base )) {
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ ss->s_flags |= PS_WROTE_BASE;
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ }
+
+ for ( sm=opc->smatches, old=(syncmatches *)&opc->smatches; sm;
+ old=sm, sm=sm->sm_next ) {
+ if ( sm->sm_op == ss ) {
+ found = 1;
+ old->sm_next = sm->sm_next;
+ op->o_tmpfree( sm, op->o_tmpmemctx );
+ break;
+ }
+ }
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+ if ( e && !is_entry_glue( e ) && fc.fscope ) {
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ op2 = *ss->s_op;
+ oh = *op->o_hdr;
+ oh.oh_conn = ss->s_op->o_conn;
+ oh.oh_connid = ss->s_op->o_connid;
+ op2.o_bd = op->o_bd->bd_self;
+ op2.o_hdr = &oh;
+ op2.o_extra = op->o_extra;
+ op2.o_callback = NULL;
+ if (ss->s_flags & PS_FIX_FILTER) {
+ /* Skip the AND/GE clause that we stuck on in front. We
+ would lose deletes/mods that happen during the refresh
+ phase otherwise (ITS#6555) */
+ op2.ors_filter = ss->s_op->ors_filter->f_and->f_next;
+ }
+ rc = test_filter( &op2, e, op2.ors_filter );
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s syncprov_matchops: "
+ "sid %03x fscope %d rc %d\n",
+ ss->s_op->o_log_prefix, ss->s_sid, fc.fscope, rc );
+
+ /* check if current o_req_dn is in scope and matches filter */
+ if ( fc.fscope && rc == LDAP_COMPARE_TRUE ) {
+ if ( saveit ) {
+ sm = op->o_tmpalloc( sizeof(syncmatches), op->o_tmpmemctx );
+ sm->sm_next = opc->smatches;
+ sm->sm_op = ss;
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ ++ss->s_inuse;
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ opc->smatches = sm;
+ } else {
+ /* if found send UPDATE else send ADD */
+ syncprov_qresp( opc, ss,
+ found ? LDAP_SYNC_MODIFY : LDAP_SYNC_ADD );
+ }
+ } else if ( !saveit && found ) {
+ /* send DELETE */
+ syncprov_qresp( opc, ss, LDAP_SYNC_DELETE );
+ } else if ( !saveit ) {
+ syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE );
+ }
+ if ( !saveit && found ) {
+ /* Decrement s_inuse, was incremented when called
+ * with saveit == TRUE
+ */
+ snext = ss->s_next;
+ if ( syncprov_free_syncop( ss, FS_LOCK ) ) {
+ *pss = snext;
+ gonext = 0;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ if ( op->o_tag != LDAP_REQ_ADD && e ) {
+ if ( !SLAP_ISOVERLAY( op->o_bd )) {
+ op->o_bd = &db;
+ }
+ if ( saveit )
+ overlay_entry_release_ov( op, e, 0, on );
+ op->o_bd = b0;
+ }
+ if ( !saveit ) {
+ if ( opc->ssres.s_info )
+ free_resinfo( &opc->ssres );
+ else if ( opc->se )
+ entry_free( opc->se );
+ }
+ op->o_bd = b0;
+}
+
+static int
+syncprov_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ opcookie *opc = cb->sc_private;
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncmatches *sm, *snext;
+ modtarget *mt;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ if ( si->si_active )
+ si->si_active--;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ for (sm = opc->smatches; sm; sm=snext) {
+ snext = sm->sm_next;
+ syncprov_free_syncop( sm->sm_op, FS_LOCK|FS_UNLINK );
+ op->o_tmpfree( sm, op->o_tmpmemctx );
+ }
+
+ /* Remove op from lock table */
+ mt = opc->smt;
+ if ( mt ) {
+ modinst *mi = (modinst *)(opc+1), **m2;
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+ for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) {
+ if ( *m2 == mi ) {
+ *m2 = mi->mi_next;
+ if ( mt->mt_tail == mi )
+ mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2;
+ break;
+ }
+ }
+ /* If there are more, promote the next one */
+ if ( mt->mt_mods ) {
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ } else {
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ ldap_pvt_thread_mutex_lock( &si->si_mods_mutex );
+ ldap_avl_delete( &si->si_mods, mt, sp_avl_cmp );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_destroy( &mt->mt_mutex );
+ ch_free( mt->mt_dn.bv_val );
+ ch_free( mt );
+ }
+ }
+ if ( !BER_BVISNULL( &opc->suuid ))
+ op->o_tmpfree( opc->suuid.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &opc->sndn ))
+ op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &opc->sdn ))
+ op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx );
+ op->o_callback = cb->sc_next;
+
+ if ( opc->ssres.s_info ) {
+ free_resinfo( &opc->ssres );
+ }
+ op->o_tmpfree(cb, op->o_tmpmemctx);
+
+ return 0;
+}
+
+static void
+syncprov_checkpoint( Operation *op, slap_overinst *on )
+{
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ Modifications mod;
+ Operation opm;
+ SlapReply rsm = {REP_RESULT};
+ slap_callback cb = {0};
+ BackendDB be;
+ BackendInfo *bi;
+
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+
+ int i;
+ for ( i=0; i<si->si_numcsns; i++ ) {
+ assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i ));
+ }
+#endif
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_checkpoint: running checkpoint\n",
+ op->o_log_prefix );
+
+ mod.sml_numvals = si->si_numcsns;
+ mod.sml_values = si->si_ctxcsn;
+ mod.sml_nvalues = NULL;
+ mod.sml_desc = slap_schema.si_ad_contextCSN;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ opm = *op;
+ opm.o_tag = LDAP_REQ_MODIFY;
+ opm.o_callback = &cb;
+ opm.orm_modlist = &mod;
+ opm.orm_no_opattrs = 1;
+ if ( SLAP_GLUE_SUBORDINATE( op->o_bd )) {
+ be = *on->on_info->oi_origdb;
+ opm.o_bd = &be;
+ }
+ opm.o_req_dn = si->si_contextdn;
+ opm.o_req_ndn = si->si_contextdn;
+ bi = opm.o_bd->bd_info;
+ opm.o_bd->bd_info = on->on_info->oi_orig;
+ opm.o_managedsait = SLAP_CONTROL_NONCRITICAL;
+ opm.o_no_schema_check = 1;
+ opm.o_dont_replicate = 1;
+ opm.o_opid = -1;
+ opm.o_bd->be_modify( &opm, &rsm );
+
+ if ( rsm.sr_err == LDAP_NO_SUCH_OBJECT &&
+ SLAP_SYNC_SUBENTRY( opm.o_bd )) {
+ const char *text;
+ char txtbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof txtbuf;
+ Entry *e = slap_create_context_csn_entry( opm.o_bd, NULL );
+ rs_reinit( &rsm, REP_RESULT );
+ slap_mods2entry( &mod, &e, 0, 1, &text, txtbuf, textlen);
+ opm.ora_e = e;
+ opm.o_bd->be_add( &opm, &rsm );
+ if ( e == opm.ora_e )
+ be_entry_release_w( &opm, opm.ora_e );
+ }
+ opm.o_bd->bd_info = bi;
+
+ if ( mod.sml_next != NULL ) {
+ slap_mods_free( mod.sml_next, 1 );
+ }
+#ifdef CHECK_CSN
+ for ( i=0; i<si->si_numcsns; i++ ) {
+ assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i ));
+ }
+#endif
+}
+
+static void
+syncprov_add_slog( Operation *op )
+{
+ opcookie *opc = op->o_callback->sc_private;
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ sessionlog *sl;
+ slog_entry *se;
+ char uuidstr[40];
+ int rc;
+
+ sl = si->si_logs;
+ {
+ if ( BER_BVISEMPTY( &op->o_csn ) ) {
+ /* During the syncrepl refresh phase we can receive operations
+ * without a csn. We cannot reliably determine the consumers
+ * state with respect to such operations, so we ignore them and
+ * wipe out anything in the log if we see them.
+ */
+ ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex );
+ /* can only do this if no one else is reading the log at the moment */
+ if ( !sl->sl_playing ) {
+ ldap_tavl_free( sl->sl_entries, (AVL_FREE)ch_free );
+ sl->sl_num = 0;
+ sl->sl_entries = NULL;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ return;
+ }
+
+ /* Allocate a record. UUIDs are not NUL-terminated. */
+ se = ch_malloc( sizeof( slog_entry ) + opc->suuid.bv_len +
+ op->o_csn.bv_len + 1 );
+ se->se_tag = op->o_tag;
+
+ se->se_uuid.bv_val = (char *)(&se[1]);
+ AC_MEMCPY( se->se_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len );
+ se->se_uuid.bv_len = opc->suuid.bv_len;
+
+ se->se_csn.bv_val = se->se_uuid.bv_val + opc->suuid.bv_len;
+ AC_MEMCPY( se->se_csn.bv_val, op->o_csn.bv_val, op->o_csn.bv_len );
+ se->se_csn.bv_val[op->o_csn.bv_len] = '\0';
+ se->se_csn.bv_len = op->o_csn.bv_len;
+ se->se_sid = slap_parse_csn_sid( &se->se_csn );
+
+ ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex );
+ if ( LogTest( LDAP_DEBUG_SYNC ) ) {
+ uuidstr[0] = 0;
+ if ( !BER_BVISEMPTY( &opc->suuid ) ) {
+ lutil_uuidstr_from_normalized( opc->suuid.bv_val, opc->suuid.bv_len,
+ uuidstr, 40 );
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: "
+ "adding csn=%s to sessionlog, uuid=%s\n",
+ op->o_log_prefix, se->se_csn.bv_val, uuidstr );
+ }
+ if ( !sl->sl_entries ) {
+ if ( !sl->sl_mincsn ) {
+ sl->sl_numcsns = 1;
+ sl->sl_mincsn = ch_malloc( 2*sizeof( struct berval ));
+ sl->sl_sids = ch_malloc( sizeof( int ));
+ sl->sl_sids[0] = se->se_sid;
+ ber_dupbv( sl->sl_mincsn, &se->se_csn );
+ BER_BVZERO( &sl->sl_mincsn[1] );
+ }
+ }
+ rc = ldap_tavl_insert( &sl->sl_entries, se, syncprov_sessionlog_cmp, ldap_avl_dup_error );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: "
+ "duplicate sessionlog entry ignored: csn=%s, uuid=%s\n",
+ op->o_log_prefix, se->se_csn.bv_val, uuidstr );
+ ch_free( se );
+ goto leave;
+ }
+ sl->sl_num++;
+ if ( !sl->sl_playing && sl->sl_num > sl->sl_size ) {
+ TAvlnode *edge = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT );
+ while ( sl->sl_num > sl->sl_size ) {
+ int i;
+ TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT );
+ se = edge->avl_data;
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: "
+ "expiring csn=%s from sessionlog (sessionlog size=%d)\n",
+ op->o_log_prefix, se->se_csn.bv_val, sl->sl_num );
+ for ( i=0; i<sl->sl_numcsns; i++ )
+ if ( sl->sl_sids[i] >= se->se_sid )
+ break;
+ if ( i == sl->sl_numcsns || sl->sl_sids[i] != se->se_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: "
+ "adding csn=%s to mincsn\n",
+ op->o_log_prefix, se->se_csn.bv_val );
+ slap_insert_csn_sids( (struct sync_cookie *)sl,
+ i, se->se_sid, &se->se_csn );
+ } else {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: "
+ "updating mincsn for sid=%d csn=%s to %s\n",
+ op->o_log_prefix, se->se_sid, sl->sl_mincsn[i].bv_val, se->se_csn.bv_val );
+ ber_bvreplace( &sl->sl_mincsn[i], &se->se_csn );
+ }
+ ldap_tavl_delete( &sl->sl_entries, se, syncprov_sessionlog_cmp );
+ ch_free( se );
+ edge = next;
+ sl->sl_num--;
+ }
+ }
+leave:
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ }
+}
+
+/* Just set a flag if we found the matching entry */
+static int
+playlog_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ op->o_callback->sc_private = (void *)1;
+ }
+ return rs->sr_err;
+}
+
+/*
+ * Check whether the last nmods UUIDs in the uuids list exist in the database
+ * and (still) match the op filter, zero out the bv_len of any that still exist
+ * and return the number of UUIDs we have confirmed are gone now.
+ */
+static int
+check_uuidlist_presence(
+ Operation *op,
+ struct berval *uuids,
+ int len,
+ int nmods )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ Operation fop = *op;
+ SlapReply frs = { REP_RESULT };
+ Filter mf, af;
+ AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
+ slap_callback cb = {0};
+ int i, mods = nmods;
+
+ fop.o_sync_mode = 0;
+ fop.o_callback = &cb;
+ fop.ors_limit = NULL;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_attrs = slap_anlist_all_attributes;
+ fop.ors_attrsonly = 0;
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ af.f_choice = LDAP_FILTER_AND;
+ af.f_next = NULL;
+ af.f_and = &mf;
+ mf.f_choice = LDAP_FILTER_EQUALITY;
+ mf.f_ava = &eq;
+ mf.f_av_desc = slap_schema.si_ad_entryUUID;
+ mf.f_next = fop.ors_filter;
+
+ fop.ors_filter = &af;
+
+ cb.sc_response = playlog_cb;
+
+ fop.o_bd->bd_info = (BackendInfo *)on->on_info;
+ for ( i=0; i<nmods; i++ ) {
+ mf.f_av_value = uuids[ len - 1 - i ];
+ cb.sc_private = NULL;
+ fop.ors_slimit = 1;
+
+ if ( BER_BVISEMPTY( &mf.f_av_value ) ) {
+ mods--;
+ continue;
+ }
+
+ rs_reinit( &frs, REP_RESULT );
+ fop.o_bd->be_search( &fop, &frs );
+ if ( cb.sc_private ) {
+ uuids[ len - 1 - i ].bv_len = 0;
+ mods--;
+ }
+ }
+ fop.o_bd->bd_info = (BackendInfo *)on;
+
+ return mods;
+}
+
+/*
+ * On each entry we get from the DB:
+ * - if it's an ADD, skip
+ * - check we've not handled it yet, skip if we have
+ * - check if it's a DELETE or missing from the DB now
+ * - send a new syncinfo entry
+ * - remember we've handled it already
+ *
+ * If we exhaust the list, clear it, forgetting entries we've handled so far.
+ */
+static int
+syncprov_accesslog_uuid_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ syncprov_accesslog_deletes *uuid_progress = sc->sc_private;
+ Attribute *a, *attrs;
+ sync_control *srs = uuid_progress->srs;
+ struct berval *bv, csn[2] = {}, uuid[2] = {},
+ add = BER_BVC("add"),
+ delete = BER_BVC("delete"),
+ modrdn = BER_BVC("modrdn");
+ int cmp, sid, i, is_delete = 0, rc;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return rs->sr_err;
+ }
+ attrs = rs->sr_entry->e_attrs;
+
+ a = attr_find( attrs, ad_reqType );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &add ) ) {
+ return rs->sr_err;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &delete ) ) {
+ is_delete = 1;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &modrdn ) ) {
+ a = attr_find( attrs, ad_reqDN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ /* Was it present in the first place? If not, skip: */
+ if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) {
+ return rs->sr_err;
+ }
+
+ a = attr_find( attrs, ad_reqNewDN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ /* Has it gone away? */
+ if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) {
+ is_delete = 1;
+ }
+ }
+
+ /*
+ * Only pick entries that are both:
+ */
+ a = attr_find( attrs, slap_schema.si_ad_entryCSN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+ csn[0] = a->a_nvals[0];
+
+ sid = slap_parse_csn_sid( &csn[0] );
+
+ /*
+ * newer than cookieCSN (srs->sr_state.ctxcsn)
+ */
+ cmp = 1;
+ for ( i=0; i<srs->sr_state.numcsns; i++ ) {
+ if ( sid == srs->sr_state.sids[i] ) {
+ cmp = ber_bvcmp( &csn[0], &srs->sr_state.ctxcsn[i] );
+ break;
+ }
+ }
+ if ( cmp <= 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "cmp %d, csn %s too old\n",
+ op->o_log_prefix, cmp, csn[0].bv_val );
+ return rs->sr_err;
+ }
+
+ /*
+ * not newer than snapshot ctxcsn (uuid_progress->ctxcsn)
+ */
+ cmp = 0;
+ for ( i=0; i<uuid_progress->numcsns; i++ ) {
+ if ( sid == uuid_progress->sids[i] ) {
+ cmp = ber_bvcmp( &csn[0], &uuid_progress->ctxcsn[i] );
+ break;
+ }
+ }
+ if ( cmp > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "cmp %d, csn %s too new\n",
+ op->o_log_prefix, cmp, csn[0].bv_val );
+ return rs->sr_err;
+ }
+
+ a = attr_find( attrs, ad_reqEntryUUID );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+ uuid[0] = a->a_nvals[0];
+
+ bv = ldap_avl_find( uuid_progress->uuids, uuid, sp_uuid_cmp );
+ if ( bv ) {
+ /* Already checked or sent, no change */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "uuid %s already checked\n",
+ op->o_log_prefix, a->a_vals[0].bv_val );
+ return rs->sr_err;
+ }
+
+ if ( !is_delete ) {
+ is_delete = check_uuidlist_presence( uuid_progress->op, uuid, 1, 1 );
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "uuid %s is %s present\n",
+ op->o_log_prefix, a->a_vals[0].bv_val,
+ is_delete ? "no longer" : "still" );
+
+ i = uuid_progress->ndel++;
+
+ bv = &uuid_progress->uuid_list[i];
+ bv->bv_val = &uuid_progress->uuid_buf[i*UUID_LEN];
+ bv->bv_len = a->a_nvals[0].bv_len;
+ AC_MEMCPY( bv->bv_val, a->a_nvals[0].bv_val, a->a_nvals[0].bv_len );
+
+ rc = ldap_avl_insert( &uuid_progress->uuids, bv, sp_uuid_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ if ( is_delete ) {
+ struct berval cookie;
+
+ slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn,
+ srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn );
+ syncprov_sendinfo( uuid_progress->op, uuid_progress->rs,
+ LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( uuid_progress->ndel >= uuid_progress->list_len ) {
+ int ndel;
+
+ assert( uuid_progress->ndel == uuid_progress->list_len );
+ ndel = ldap_avl_free( uuid_progress->uuids, NULL );
+ assert( ndel == uuid_progress->ndel );
+ uuid_progress->uuids = NULL;
+ uuid_progress->ndel = 0;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+syncprov_play_sessionlog( Operation *op, SlapReply *rs, sync_control *srs,
+ BerVarray ctxcsn, int numcsns, int *sids,
+ struct berval *mincsn, int minsid )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ sessionlog *sl = si->si_logs;
+ int i, j, ndel, num, nmods, mmods, do_play = 0, rc = -1;
+ BerVarray uuids, csns;
+ struct berval uuid[2] = {}, csn[2] = {};
+ slog_entry *se;
+ TAvlnode *entry;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ struct berval delcsn[2];
+
+ ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex );
+ /* Are there any log entries, and is the consumer state
+ * present in the session log?
+ */
+ if ( !sl->sl_num ) {
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ return rc;
+ }
+ assert( sl->sl_num > 0 );
+
+ for ( i=0; i<sl->sl_numcsns; i++ ) {
+ /* SID not present == new enough */
+ if ( minsid < sl->sl_sids[i] ) {
+ do_play = 1;
+ break;
+ }
+ /* SID present */
+ if ( minsid == sl->sl_sids[i] ) {
+ /* new enough? */
+ if ( ber_bvcmp( mincsn, &sl->sl_mincsn[i] ) >= 0 )
+ do_play = 1;
+ break;
+ }
+ }
+ /* SID not present == new enough */
+ if ( i == sl->sl_numcsns )
+ do_play = 1;
+
+ if ( !do_play ) {
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ return rc;
+ }
+
+ num = sl->sl_num;
+ i = 0;
+ nmods = 0;
+ sl->sl_playing++;
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+
+ uuids = op->o_tmpalloc( (num) * sizeof( struct berval ) +
+ num * UUID_LEN, op->o_tmpmemctx );
+ uuids[0].bv_val = (char *)(uuids + num);
+ csns = op->o_tmpalloc( (num) * sizeof( struct berval ) +
+ num * LDAP_PVT_CSNSTR_BUFSIZE, op->o_tmpmemctx );
+ csns[0].bv_val = (char *)(csns + num);
+
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ /* Make a copy of the relevant UUIDs. Put the Deletes up front
+ * and everything else at the end. Do this first so we can
+ * let the write side manage the sessionlog again.
+ */
+ assert( sl->sl_entries );
+
+ /* Find first relevant log entry. If greater than mincsn, backtrack one entry */
+ {
+ slog_entry te = {0};
+ te.se_csn = *mincsn;
+ entry = ldap_tavl_find3( sl->sl_entries, &te, syncprov_sessionlog_cmp, &ndel );
+ }
+ if ( ndel > 0 && entry )
+ entry = ldap_tavl_next( entry, TAVL_DIR_LEFT );
+ /* if none, just start at beginning */
+ if ( !entry )
+ entry = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT );
+
+ do {
+ char uuidstr[40] = {};
+ slog_entry *se = entry->avl_data;
+ int k;
+
+ /* Make sure writes can still make progress */
+ ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex );
+ ndel = 1;
+ for ( k=0; k<srs->sr_state.numcsns; k++ ) {
+ if ( se->se_sid == srs->sr_state.sids[k] ) {
+ ndel = ber_bvcmp( &se->se_csn, &srs->sr_state.ctxcsn[k] );
+ break;
+ }
+ }
+ if ( ndel <= 0 ) {
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ continue;
+ }
+ ndel = 0;
+ for ( k=0; k<numcsns; k++ ) {
+ if ( se->se_sid == sids[k] ) {
+ ndel = ber_bvcmp( &se->se_csn, &ctxcsn[k] );
+ break;
+ }
+ }
+ if ( ndel > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: "
+ "cmp %d, csn %s too new, we're finished\n",
+ op->o_log_prefix, ndel, se->se_csn.bv_val );
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ break;
+ }
+ if ( se->se_tag == LDAP_REQ_DELETE ) {
+ j = i;
+ i++;
+ } else {
+ if ( se->se_tag == LDAP_REQ_ADD ) {
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ continue;
+ }
+ nmods++;
+ j = num - nmods;
+ }
+ uuids[j].bv_val = uuids[0].bv_val + (j * UUID_LEN);
+ AC_MEMCPY(uuids[j].bv_val, se->se_uuid.bv_val, UUID_LEN);
+ uuids[j].bv_len = UUID_LEN;
+
+ csns[j].bv_val = csns[0].bv_val + (j * LDAP_PVT_CSNSTR_BUFSIZE);
+ AC_MEMCPY(csns[j].bv_val, se->se_csn.bv_val, se->se_csn.bv_len);
+ csns[j].bv_len = se->se_csn.bv_len;
+ /* We're printing it */
+ csns[j].bv_val[csns[j].bv_len] = '\0';
+
+ if ( LogTest( LDAP_DEBUG_SYNC ) ) {
+ lutil_uuidstr_from_normalized( uuids[j].bv_val, uuids[j].bv_len,
+ uuidstr, 40 );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: "
+ "picking a %s entry uuid=%s cookie=%s\n",
+ op->o_log_prefix, se->se_tag == LDAP_REQ_DELETE ? "deleted" : "modified",
+ uuidstr, csns[j].bv_val );
+ }
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ } while ( (entry = ldap_tavl_next( entry, TAVL_DIR_RIGHT )) != NULL );
+ ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex );
+ ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex );
+ sl->sl_playing--;
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+
+ ndel = i;
+
+ /* Zero out unused slots */
+ for ( i=ndel; i < num - nmods; i++ )
+ uuids[i].bv_len = 0;
+
+ /* Mods must be validated to see if they belong in this delete set.
+ */
+
+ mmods = nmods;
+ /* Strip any duplicates */
+ for ( i=0; i<nmods; i++ ) {
+ for ( j=0; j<ndel; j++ ) {
+ if ( bvmatch( &uuids[j], &uuids[num - 1 - i] )) {
+ uuids[num - 1 - i].bv_len = 0;
+ mmods --;
+ break;
+ }
+ }
+ if ( uuids[num - 1 - i].bv_len == 0 ) continue;
+ for ( j=0; j<i; j++ ) {
+ if ( bvmatch( &uuids[num - 1 - j], &uuids[num - 1 - i] )) {
+ uuids[num - 1 - i].bv_len = 0;
+ mmods --;
+ break;
+ }
+ }
+ }
+
+ /* Check mods now */
+ if ( mmods ) {
+ check_uuidlist_presence( op, uuids, num, nmods );
+ }
+
+ /* ITS#8768 Send entries sorted by CSN order */
+ i = j = 0;
+ while ( i < ndel || j < nmods ) {
+ struct berval cookie;
+ int index;
+
+ /* Skip over duplicate mods */
+ if ( j < nmods && BER_BVISEMPTY( &uuids[ num - 1 - j ] ) ) {
+ j++;
+ continue;
+ }
+ index = num - 1 - j;
+
+ if ( i >= ndel ) {
+ j++;
+ } else if ( j >= nmods ) {
+ index = i++;
+ /* Take the oldest by CSN order */
+ } else if ( ber_bvcmp( &csns[index], &csns[i] ) < 0 ) {
+ j++;
+ } else {
+ index = i++;
+ }
+
+ uuid[0] = uuids[index];
+ csn[0] = csns[index];
+
+ slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn,
+ srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn );
+ if ( LogTest( LDAP_DEBUG_SYNC ) ) {
+ char uuidstr[40];
+ lutil_uuidstr_from_normalized( uuid[0].bv_val, uuid[0].bv_len,
+ uuidstr, 40 );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: "
+ "sending a new disappearing entry uuid=%s cookie=%s\n",
+ op->o_log_prefix, uuidstr, cookie.bv_val );
+ }
+
+ /* TODO: we might batch those that share the same CSN (think present
+ * phase), but would have to limit how many we send out at once */
+ syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 );
+ }
+ op->o_tmpfree( uuids, op->o_tmpmemctx );
+ op->o_tmpfree( csns, op->o_tmpmemctx );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_play_accesslog( Operation *op, SlapReply *rs, sync_control *srs,
+ BerVarray ctxcsn, int numcsns, int *sids,
+ struct berval *mincsn, int minsid )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ Operation fop;
+ SlapReply frs = { REP_RESULT };
+ slap_callback cb = {};
+ Filter *f;
+ syncprov_accesslog_deletes uuid_progress = {
+ .op = op,
+ .rs = rs,
+ .srs = srs,
+ .ctxcsn = ctxcsn,
+ .numcsns = numcsns,
+ .sids = sids,
+ };
+ struct berval oldestcsn = BER_BVNULL, newestcsn = BER_BVNULL,
+ basedn, filterpattern = BER_BVC(
+ "(&"
+ "(entryCSN>=%s)"
+ "(entryCSN<=%s)"
+ "(reqResult=0)"
+ "(|"
+ "(reqDN:dnSubtreeMatch:=%s)"
+ "(reqNewDN:dnSubtreeMatch:=%s)"
+ ")"
+ "(|"
+ "(objectclass=auditWriteObject)"
+ "(objectclass=auditExtended)"
+ "))" );
+ BackendDB *db;
+ Entry *e;
+ Attribute *a;
+ int *minsids, i, j = 0, rc = -1;
+
+ assert( !BER_BVISNULL( &si->si_logbase ) );
+
+ db = select_backend( &si->si_logbase, 0 );
+ if ( !db ) {
+ Debug( LDAP_DEBUG_ANY, "%s syncprov_play_accesslog: "
+ "No database configured to hold accesslog dn=%s\n",
+ op->o_log_prefix, si->si_logbase.bv_val );
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ fop = *op;
+ fop.o_sync_mode = 0;
+ fop.o_bd = db;
+ rc = be_entry_get_rw( &fop, &si->si_logbase, NULL, ad_minCSN, 0, &e );
+ if ( rc ) {
+ return rc;
+ }
+
+ a = attr_find( e->e_attrs, ad_minCSN );
+ if ( !a ) {
+ be_entry_release_rw( &fop, e, 0 );
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ /*
+ * If we got here:
+ * - the consumer's cookie (srs->sr_state.ctxcsn) has the same sids in the
+ * same order as ctxcsn
+ * - at least one of the cookie's csns is older than its ctxcsn counterpart
+ *
+ * Now prepare the filter, we want it to be the union of all the intervals
+ * between the cookie and our contextCSN for each sid. Right now, we can't
+ * specify them separately, so just pick the boundary CSNs of non-empty
+ * intervals as a conservative overestimate.
+ *
+ * Also check accesslog can actually serve this query based on what's
+ * stored in minCSN.
+ */
+
+ assert( srs->sr_state.numcsns == numcsns );
+
+ minsids = slap_parse_csn_sids( a->a_nvals, a->a_numvals, op->o_tmpmemctx );
+ slap_sort_csn_sids( a->a_nvals, minsids, a->a_numvals, op->o_tmpmemctx );
+ for ( i=0, j=0; i < numcsns; i++ ) {
+ assert( srs->sr_state.sids[i] == sids[i] );
+ if ( ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[i] ) >= 0 ) {
+ /* Consumer is up to date for this sid */
+ continue;
+ }
+ for ( ; j < a->a_numvals && minsids[j] < sids[i]; j++ )
+ /* Find the right minCSN, if present */;
+ if ( j == a->a_numvals || minsids[j] != sids[i] ||
+ ber_bvcmp( &srs->sr_state.ctxcsn[i], &a->a_nvals[j] ) < 0 ) {
+ /* Consumer is missing changes for a sid and minCSN indicates we
+ * can't replay all relevant history */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: "
+ "accesslog information inadequate for log replay on csn=%s\n",
+ op->o_log_prefix, srs->sr_state.ctxcsn[i].bv_val );
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ be_entry_release_rw( &fop, e, 0 );
+ return 1;
+ }
+ if ( BER_BVISEMPTY( &oldestcsn ) ||
+ ber_bvcmp( &oldestcsn, &srs->sr_state.ctxcsn[i] ) > 0 ) {
+ oldestcsn = srs->sr_state.ctxcsn[i];
+ }
+ if ( BER_BVISEMPTY( &newestcsn ) ||
+ ber_bvcmp( &newestcsn, &ctxcsn[i] ) < 0 ) {
+ newestcsn = ctxcsn[i];
+ }
+ }
+ assert( !BER_BVISEMPTY( &oldestcsn ) && !BER_BVISEMPTY( &newestcsn ) &&
+ ber_bvcmp( &oldestcsn, &newestcsn ) < 0 );
+ slap_sl_free( minsids, op->o_tmpmemctx );
+
+ filter_escape_value_x( &op->o_req_ndn, &basedn, fop.o_tmpmemctx );
+ /* filter_escape_value_x sets output to BVNULL if input value is empty,
+ * supply our own copy */
+ if ( BER_BVISEMPTY( &basedn ) ) {
+ basedn.bv_val = "";
+ }
+ fop.o_req_ndn = fop.o_req_dn = si->si_logbase;
+ fop.ors_filterstr.bv_val = fop.o_tmpalloc(
+ filterpattern.bv_len +
+ oldestcsn.bv_len + newestcsn.bv_len + 2 * basedn.bv_len,
+ fop.o_tmpmemctx );
+ fop.ors_filterstr.bv_len = sprintf( fop.ors_filterstr.bv_val,
+ filterpattern.bv_val,
+ oldestcsn.bv_val, newestcsn.bv_val, basedn.bv_val, basedn.bv_val );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: "
+ "prepared filter '%s', base='%s'\n",
+ op->o_log_prefix, fop.ors_filterstr.bv_val, si->si_logbase.bv_val );
+ f = str2filter_x( &fop, fop.ors_filterstr.bv_val );
+ assert( f != NULL );
+ fop.ors_filter = f;
+
+ if ( !BER_BVISEMPTY( &basedn ) ) {
+ fop.o_tmpfree( basedn.bv_val, fop.o_tmpmemctx );
+ }
+ be_entry_release_rw( &fop, e, 0 );
+
+ /*
+ * Allocate memory for list_len uuids for use by the callback, populate
+ * with entries that we have sent or checked still match the filter.
+ * A disappearing entry gets its uuid sent as a delete.
+ *
+ * in the callback, we need:
+ * - original op and rs so we can send the message
+ * - sync_control
+ * - the uuid buffer and list and their length
+ * - number of uuids we already have in the list
+ * - the lookup structure so we don't have to check/send a uuid twice
+ * (AVL?)
+ */
+ uuid_progress.list_len = SLAP_SYNCUUID_SET_SIZE;
+ uuid_progress.uuid_list = fop.o_tmpalloc( (uuid_progress.list_len) * sizeof(struct berval), fop.o_tmpmemctx );
+ uuid_progress.uuid_buf = fop.o_tmpalloc( (uuid_progress.list_len) * UUID_LEN, fop.o_tmpmemctx );
+
+ cb.sc_private = &uuid_progress;
+ cb.sc_response = syncprov_accesslog_uuid_cb;
+
+ fop.o_callback = &cb;
+
+ rc = fop.o_bd->be_search( &fop, &frs );
+
+ ldap_avl_free( uuid_progress.uuids, NULL );
+ fop.o_tmpfree( uuid_progress.uuid_buf, fop.o_tmpmemctx );
+ fop.o_tmpfree( uuid_progress.uuid_list, fop.o_tmpmemctx );
+ fop.o_tmpfree( fop.ors_filterstr.bv_val, fop.o_tmpmemctx );
+ filter_free_x( &fop, f, 1 );
+
+ return rc;
+}
+
+static int
+syncprov_new_ctxcsn( opcookie *opc, syncprov_info_t *si, int csn_changed, int numvals, BerVarray vals )
+{
+ unsigned i;
+ int j, sid;
+
+ for ( i=0; i<numvals; i++ ) {
+ sid = slap_parse_csn_sid( &vals[i] );
+ for ( j=0; j<si->si_numcsns; j++ ) {
+ if ( sid < si->si_sids[j] )
+ break;
+ if ( sid == si->si_sids[j] ) {
+ if ( ber_bvcmp( &vals[i], &si->si_ctxcsn[j] ) > 0 ) {
+ ber_bvreplace( &si->si_ctxcsn[j], &vals[i] );
+ csn_changed = 1;
+ }
+ break;
+ }
+ }
+
+ if ( j == si->si_numcsns || sid != si->si_sids[j] ) {
+ slap_insert_csn_sids( (struct sync_cookie *)&si->si_ctxcsn,
+ j, sid, &vals[i] );
+ csn_changed = 1;
+ }
+ }
+ if ( csn_changed )
+ si->si_dirty = 0;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+
+ if ( csn_changed ) {
+ syncops *ss;
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( ss = si->si_ops; ss; ss = ss->s_next ) {
+ if ( ss->s_op->o_abandon )
+ continue;
+ /* Send the updated csn to all syncrepl consumers,
+ * including the server from which it originated.
+ * The syncrepl consumer and syncprov provider on
+ * the originating server may be configured to store
+ * their csn values in different entries.
+ */
+ syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE );
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ }
+ return csn_changed;
+}
+
+static int
+syncprov_op_response( Operation *op, SlapReply *rs )
+{
+ opcookie *opc = op->o_callback->sc_private;
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncmatches *sm;
+
+ if ( rs->sr_err == LDAP_SUCCESS )
+ {
+ struct berval maxcsn;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ int do_check = 0, have_psearches, foundit, csn_changed = 0;
+
+ ldap_pvt_thread_mutex_lock( &si->si_resp_mutex );
+
+ /* Update our context CSN */
+ cbuf[0] = '\0';
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = sizeof(cbuf);
+ ldap_pvt_thread_rdwr_wlock( &si->si_csn_rwlock );
+
+ slap_get_commit_csn( op, &maxcsn, &foundit );
+ if ( BER_BVISEMPTY( &maxcsn ) && SLAP_GLUE_SUBORDINATE( op->o_bd )) {
+ /* syncrepl queues the CSN values in the db where
+ * it is configured , not where the changes are made.
+ * So look for a value in the glue db if we didn't
+ * find any in this db.
+ */
+ BackendDB *be = op->o_bd;
+ op->o_bd = select_backend( &be->be_nsuffix[0], 1);
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = sizeof(cbuf);
+ slap_get_commit_csn( op, &maxcsn, &foundit );
+ op->o_bd = be;
+ }
+ if ( !BER_BVISEMPTY( &maxcsn ) ) {
+ int i, sid;
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+ assert( !syn->ssyn_validate( syn, &maxcsn ));
+#endif
+ sid = slap_parse_csn_sid( &maxcsn );
+ for ( i=0; i<si->si_numcsns; i++ ) {
+ if ( sid < si->si_sids[i] )
+ break;
+ if ( sid == si->si_sids[i] ) {
+ if ( ber_bvcmp( &maxcsn, &si->si_ctxcsn[i] ) > 0 ) {
+ ber_bvreplace( &si->si_ctxcsn[i], &maxcsn );
+ csn_changed = 1;
+ }
+ break;
+ }
+ }
+ /* It's a new SID for us */
+ if ( i == si->si_numcsns || sid != si->si_sids[i] ) {
+ slap_insert_csn_sids((struct sync_cookie *)&(si->si_ctxcsn),
+ i, sid, &maxcsn );
+ csn_changed = 1;
+ }
+ }
+
+ /* Don't do any processing for consumer contextCSN updates */
+ if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) &&
+ op->o_tag == LDAP_REQ_MODIFY &&
+ op->orm_modlist &&
+ op->orm_modlist->sml_op == LDAP_MOD_REPLACE &&
+ op->orm_modlist->sml_desc == slap_schema.si_ad_contextCSN ) {
+ /* Catch contextCSN updates from syncrepl. We have to look at
+ * all the attribute values, as there may be more than one csn
+ * that changed, and only one can be passed in the csn queue.
+ */
+ csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed,
+ op->orm_modlist->sml_numvals, op->orm_modlist->sml_values );
+ if ( csn_changed )
+ si->si_numops++;
+ goto leave;
+ }
+ if ( op->o_dont_replicate ) {
+ if ( csn_changed )
+ si->si_numops++;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+ goto leave;
+ }
+
+ /* If we're adding the context entry, parse all of its contextCSNs */
+ if ( op->o_tag == LDAP_REQ_ADD &&
+ dn_match( &op->o_req_ndn, &si->si_contextdn )) {
+ Attribute *a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, a->a_numvals, a->a_vals );
+ if ( csn_changed )
+ si->si_numops++;
+ goto added;
+ }
+ }
+
+ if ( csn_changed )
+ si->si_numops++;
+ if ( si->si_chkops || si->si_chktime ) {
+ /* Never checkpoint adding the context entry,
+ * it will deadlock
+ */
+ if ( op->o_tag != LDAP_REQ_ADD ||
+ !dn_match( &op->o_req_ndn, &si->si_contextdn )) {
+ if ( si->si_chkops && si->si_numops >= si->si_chkops ) {
+ do_check = 1;
+ si->si_numops = 0;
+ }
+ if ( si->si_chktime &&
+ (op->o_time - si->si_chklast >= si->si_chktime )) {
+ if ( si->si_chklast ) {
+ do_check = 1;
+ si->si_chklast = op->o_time;
+ } else {
+ si->si_chklast = 1;
+ }
+ }
+ }
+ }
+ si->si_dirty = !csn_changed;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+
+added:
+ if ( do_check ) {
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+ syncprov_checkpoint( op, on );
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+ }
+
+ /* only update consumer ctx if this is a newer csn */
+ if ( csn_changed ) {
+ opc->sctxcsn = maxcsn;
+ }
+
+ /* Handle any persistent searches */
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ have_psearches = ( si->si_ops != NULL );
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( have_psearches ) {
+ syncprov_matchops( op, opc, 0 );
+ }
+
+ /* Add any log records */
+ if ( si->si_logs ) {
+ syncprov_add_slog( op );
+ }
+leave: ldap_pvt_thread_mutex_unlock( &si->si_resp_mutex );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* We don't use a subentry to store the context CSN any more.
+ * We expose the current context CSN as an operational attribute
+ * of the suffix entry.
+ */
+static int
+syncprov_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( dn_match( &op->o_req_ndn, &si->si_contextdn ) &&
+ op->oq_compare.rs_ava->aa_desc == slap_schema.si_ad_contextCSN )
+ {
+ Entry e = {0};
+ Attribute a = {0};
+
+ e.e_name = si->si_contextdn;
+ e.e_nname = si->si_contextdn;
+ e.e_attrs = &a;
+
+ a.a_desc = slap_schema.si_ad_contextCSN;
+
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+
+ a.a_vals = si->si_ctxcsn;
+ a.a_nvals = a.a_vals;
+ a.a_numvals = si->si_numcsns;
+
+ rs->sr_err = access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc,
+ &op->oq_compare.rs_ava->aa_value, ACL_COMPARE, NULL );
+ if ( ! rs->sr_err ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &e, get_assertion( op ) ) != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+
+ rs->sr_err = LDAP_COMPARE_FALSE;
+
+ if ( attr_valfind( &a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->oq_compare.rs_ava->aa_value, NULL, op->o_tmpmemctx ) == 0 )
+ {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ }
+
+return_results:;
+
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+
+ send_ldap_result( op, rs );
+
+ if( rs->sr_err == LDAP_COMPARE_FALSE || rs->sr_err == LDAP_COMPARE_TRUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ rc = rs->sr_err;
+ }
+
+ return rc;
+}
+
+static int
+syncprov_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ slap_callback *cb;
+ opcookie *opc;
+ int have_psearches, cbsize;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ have_psearches = ( si->si_ops != NULL );
+ si->si_active++;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ cbsize = sizeof(slap_callback) + sizeof(opcookie) +
+ (have_psearches ? sizeof(modinst) : 0 );
+
+ cb = op->o_tmpcalloc(1, cbsize, op->o_tmpmemctx);
+ opc = (opcookie *)(cb+1);
+ opc->son = on;
+ cb->sc_response = syncprov_op_response;
+ cb->sc_cleanup = syncprov_op_cleanup;
+ cb->sc_private = opc;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ opc->osid = -1;
+ opc->rsid = -1;
+ if ( op->o_csn.bv_val ) {
+ opc->osid = slap_parse_csn_sid( &op->o_csn );
+ }
+ if ( op->o_controls ) {
+ struct sync_cookie *scook =
+ op->o_controls[slap_cids.sc_LDAPsync];
+ if ( scook )
+ opc->rsid = scook->sid;
+ }
+
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ /* If there are active persistent searches, lock this operation.
+ * See seqmod.c for the locking logic on its own.
+ */
+ if ( have_psearches ) {
+ modtarget *mt, mtdummy;
+ modinst *mi;
+
+ mi = (modinst *)(opc+1);
+ mi->mi_op = op;
+
+ /* See if we're already modifying this entry... */
+ mtdummy.mt_dn = op->o_req_ndn;
+retry:
+ ldap_pvt_thread_mutex_lock( &si->si_mods_mutex );
+ mt = ldap_avl_find( si->si_mods, &mtdummy, sp_avl_cmp );
+ if ( mt ) {
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+ if ( mt->mt_mods == NULL ) {
+ /* Cannot reuse this mt, as another thread is about
+ * to release it in syncprov_op_cleanup. Wait for them
+ * to finish; our own insert is required to succeed.
+ */
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ ldap_pvt_thread_yield();
+ goto retry;
+ }
+ }
+ if ( mt ) {
+ mt->mt_tail->mi_next = mi;
+ mt->mt_tail = mi;
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ /* wait for this op to get to head of list */
+ while ( mt->mt_mods != mi ) {
+ modinst *m2;
+ /* don't wait on other mods from the same thread */
+ for ( m2 = mt->mt_mods; m2; m2 = m2->mi_next ) {
+ if ( m2->mi_op->o_threadctx == op->o_threadctx ) {
+ break;
+ }
+ }
+ if ( m2 )
+ break;
+
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ /* FIXME: if dynamic config can delete overlays or
+ * databases we'll have to check for cleanup here.
+ * Currently it's not an issue because there are
+ * no dynamic config deletes...
+ */
+ if ( slapd_shutdown )
+ return SLAPD_ABANDON;
+
+ if ( !ldap_pvt_thread_pool_pausewait( &connection_pool ))
+ ldap_pvt_thread_yield();
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+
+ /* clean up if the caller is giving up */
+ if ( op->o_abandon ) {
+ modinst **m2;
+ slap_callback **sc;
+ for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) {
+ if ( *m2 == mi ) {
+ *m2 = mi->mi_next;
+ if ( mt->mt_tail == mi )
+ mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2;
+ break;
+ }
+ }
+ for (sc = &op->o_callback; ; sc = &(*sc)->sc_next) {
+ if ( *sc == cb ) {
+ *sc = cb->sc_next;
+ break;
+ }
+ }
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ return SLAPD_ABANDON;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ } else {
+ /* Record that we're modifying this entry now */
+ mt = ch_malloc( sizeof(modtarget) );
+ mt->mt_mods = mi;
+ mt->mt_tail = mi;
+ ber_dupbv( &mt->mt_dn, &mi->mi_op->o_req_ndn );
+ ldap_pvt_thread_mutex_init( &mt->mt_mutex );
+ ldap_avl_insert( &si->si_mods, mt, sp_avl_cmp, ldap_avl_dup_error );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ }
+ opc->smt = mt;
+ }
+
+ if (( have_psearches || si->si_logs ) && op->o_tag != LDAP_REQ_ADD )
+ syncprov_matchops( op, opc, 1 );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_op_extended( Operation *op, SlapReply *rs )
+{
+ if ( exop_is_write( op ))
+ return syncprov_op_mod( op, rs );
+
+ return SLAP_CB_CONTINUE;
+}
+
+typedef struct searchstate {
+ slap_overinst *ss_on;
+ syncops *ss_so;
+ BerVarray ss_ctxcsn;
+ int *ss_sids;
+ int ss_numcsns;
+#define SS_PRESENT 0x01
+#define SS_CHANGED 0x02
+ int ss_flags;
+} searchstate;
+
+typedef struct SyncOperationBuffer {
+ Operation sob_op;
+ Opheader sob_hdr;
+ OpExtra sob_oe;
+ AttributeName sob_extra; /* not always present */
+ /* Further data allocated here */
+} SyncOperationBuffer;
+
+static void
+syncprov_detach_op( Operation *op, syncops *so, slap_overinst *on )
+{
+ SyncOperationBuffer *sopbuf2;
+ Operation *op2;
+ int i, alen = 0;
+ size_t size;
+ char *ptr;
+ GroupAssertion *g1, *g2;
+
+ /* count the search attrs */
+ for (i=0; op->ors_attrs && !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) {
+ alen += op->ors_attrs[i].an_name.bv_len + 1;
+ }
+ /* Make a new copy of the operation */
+ size = offsetof( SyncOperationBuffer, sob_extra ) +
+ (i ? ( (i+1) * sizeof(AttributeName) + alen) : 0) +
+ op->o_req_dn.bv_len + 1 +
+ op->o_req_ndn.bv_len + 1 +
+ op->o_ndn.bv_len + 1 +
+ so->s_filterstr.bv_len + 1;
+ sopbuf2 = ch_calloc( 1, size );
+ op2 = &sopbuf2->sob_op;
+ op2->o_hdr = &sopbuf2->sob_hdr;
+ LDAP_SLIST_FIRST(&op2->o_extra) = &sopbuf2->sob_oe;
+
+ /* Copy the fields we care about explicitly, leave the rest alone */
+ *op2->o_hdr = *op->o_hdr;
+ op2->o_tag = op->o_tag;
+ op2->o_time = op->o_time;
+ op2->o_bd = on->on_info->oi_origdb;
+ op2->o_request = op->o_request;
+ op2->o_managedsait = op->o_managedsait;
+ LDAP_SLIST_FIRST(&op2->o_extra)->oe_key = on;
+ LDAP_SLIST_NEXT(LDAP_SLIST_FIRST(&op2->o_extra), oe_next) = NULL;
+
+ ptr = (char *) sopbuf2 + offsetof( SyncOperationBuffer, sob_extra );
+ if ( i ) {
+ op2->ors_attrs = (AttributeName *) ptr;
+ ptr = (char *) &op2->ors_attrs[i+1];
+ for (i=0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) {
+ op2->ors_attrs[i] = op->ors_attrs[i];
+ op2->ors_attrs[i].an_name.bv_val = ptr;
+ ptr = lutil_strcopy( ptr, op->ors_attrs[i].an_name.bv_val ) + 1;
+ }
+ BER_BVZERO( &op2->ors_attrs[i].an_name );
+ }
+
+ op2->o_authz = op->o_authz;
+ op2->o_ndn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_ndn.bv_val) + 1;
+ op2->o_dn = op2->o_ndn;
+ op2->o_req_dn.bv_len = op->o_req_dn.bv_len;
+ op2->o_req_dn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_req_dn.bv_val) + 1;
+ op2->o_req_ndn.bv_len = op->o_req_ndn.bv_len;
+ op2->o_req_ndn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_req_ndn.bv_val) + 1;
+ op2->ors_filterstr.bv_val = ptr;
+ strcpy( ptr, so->s_filterstr.bv_val );
+ op2->ors_filterstr.bv_len = so->s_filterstr.bv_len;
+
+ /* Skip the AND/GE clause that we stuck on in front */
+ if ( so->s_flags & PS_FIX_FILTER ) {
+ op2->ors_filter = op->ors_filter->f_and->f_next;
+ so->s_flags ^= PS_FIX_FILTER;
+ } else {
+ op2->ors_filter = op->ors_filter;
+ }
+ op2->ors_filter = filter_dup( op2->ors_filter, NULL );
+ so->s_op = op2;
+
+ /* Copy any cached group ACLs individually */
+ op2->o_groups = NULL;
+ for ( g1=op->o_groups; g1; g1=g1->ga_next ) {
+ g2 = ch_malloc( sizeof(GroupAssertion) + g1->ga_len );
+ *g2 = *g1;
+ strcpy( g2->ga_ndn, g1->ga_ndn );
+ g2->ga_next = op2->o_groups;
+ op2->o_groups = g2;
+ }
+ /* Don't allow any further group caching */
+ op2->o_do_not_cache = 1;
+
+ /* Add op2 to conn so abandon will find us */
+ op->o_conn->c_n_ops_executing++;
+ op->o_conn->c_n_ops_completed--;
+ LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op2, o_next );
+ so->s_flags |= PS_IS_DETACHED;
+}
+
+static int
+syncprov_search_response( Operation *op, SlapReply *rs )
+{
+ searchstate *ss = op->o_callback->sc_private;
+ slap_overinst *on = ss->ss_on;
+ syncops *so = ss->ss_so;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ sync_control *srs = op->o_controls[slap_cids.sc_LDAPsync];
+
+ if ( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF ) {
+ Attribute *a;
+ /* If we got a referral without a referral object, there's
+ * something missing that we cannot replicate. Just ignore it.
+ * The consumer will abort because we didn't send the expected
+ * control.
+ */
+ if ( !rs->sr_entry ) {
+ assert( rs->sr_entry != NULL );
+ Debug( LDAP_DEBUG_ANY, "%s syncprov_search_response: "
+ "bogus referral in context\n", op->o_log_prefix );
+ return SLAP_CB_CONTINUE;
+ }
+ if ( is_entry_glue( rs->sr_entry ) ) {
+ return LDAP_SUCCESS;
+ }
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a == NULL && rs->sr_operational_attrs != NULL ) {
+ a = attr_find( rs->sr_operational_attrs, slap_schema.si_ad_entryCSN );
+ }
+ if ( a ) {
+ int i, sid;
+ sid = slap_parse_csn_sid( &a->a_nvals[0] );
+
+ /* If not a persistent search */
+ if ( !so ) {
+ /* Make sure entry is less than the snapshot'd contextCSN */
+ for ( i=0; i<ss->ss_numcsns; i++ ) {
+ if ( sid == ss->ss_sids[i] && ber_bvcmp( &a->a_nvals[0],
+ &ss->ss_ctxcsn[i] ) > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: "
+ "Entry %s CSN %s greater than snapshot %s\n",
+ op->o_log_prefix,
+ rs->sr_entry->e_name.bv_val,
+ a->a_nvals[0].bv_val,
+ ss->ss_ctxcsn[i].bv_val );
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ /* Don't send old entries twice */
+ if ( srs->sr_state.ctxcsn ) {
+ for ( i=0; i<srs->sr_state.numcsns; i++ ) {
+ if ( sid == srs->sr_state.sids[i] &&
+ ber_bvcmp( &a->a_nvals[0],
+ &srs->sr_state.ctxcsn[i] )<= 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: "
+ "Entry %s CSN %s older or equal to ctx %s\n",
+ op->o_log_prefix,
+ rs->sr_entry->e_name.bv_val,
+ a->a_nvals[0].bv_val,
+ srs->sr_state.ctxcsn[i].bv_val );
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }
+ rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2,
+ op->o_tmpmemctx );
+ rs->sr_ctrls[1] = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+ /* If we're in delta-sync mode, always send a cookie */
+ if ( si->si_nopres && si->si_usehint && a ) {
+ struct berval cookie;
+ slap_compose_sync_cookie( op, &cookie, a->a_nvals, srs->sr_state.rid,
+ slap_serverID ? slap_serverID : -1, NULL );
+ rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry,
+ LDAP_SYNC_ADD, rs->sr_ctrls, 0, 1, &cookie );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ } else {
+ rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry,
+ LDAP_SYNC_ADD, rs->sr_ctrls, 0, 0, NULL );
+ }
+ } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) {
+ struct berval cookie = BER_BVNULL;
+
+ if ( ( ss->ss_flags & SS_CHANGED ) &&
+ ss->ss_ctxcsn && !BER_BVISNULL( &ss->ss_ctxcsn[0] )) {
+ slap_compose_sync_cookie( op, &cookie, ss->ss_ctxcsn,
+ srs->sr_state.rid,
+ slap_serverID ? slap_serverID : -1, NULL );
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: cookie=%s\n",
+ op->o_log_prefix, cookie.bv_val );
+ }
+
+ /* Is this a regular refresh?
+ * Note: refresh never gets here if there were no changes
+ */
+ if ( !so ) {
+ rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2,
+ op->o_tmpmemctx );
+ rs->sr_ctrls[1] = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+ rs->sr_err = syncprov_done_ctrl( op, rs, rs->sr_ctrls,
+ 0, 1, &cookie, ( ss->ss_flags & SS_PRESENT ) ? LDAP_SYNC_REFRESH_PRESENTS :
+ LDAP_SYNC_REFRESH_DELETES );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ } else {
+ /* It's RefreshAndPersist, transition to Persist phase */
+ rs->sr_err = SLAPD_NO_REPLY;
+ syncprov_sendinfo( op, rs, ( ss->ss_flags & SS_PRESENT ) ?
+ LDAP_TAG_SYNC_REFRESH_PRESENT : LDAP_TAG_SYNC_REFRESH_DELETE,
+ ( ss->ss_flags & SS_CHANGED ) ? &cookie : NULL,
+ 1, NULL, 0 );
+ if ( !BER_BVISNULL( &cookie ))
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+
+ /* Detach this Op from frontend control */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ /* But not if this connection was closed along the way */
+ if ( op->o_abandon ) {
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ /* syncprov_ab_cleanup will free this syncop */
+ return SLAPD_ABANDON;
+
+ } else {
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ /* Turn off the refreshing flag */
+ so->s_flags ^= PS_IS_REFRESHING;
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: "
+ "detaching op\n", op->o_log_prefix );
+ syncprov_detach_op( op, so, on );
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ /* If there are queued responses, fire them off */
+ if ( so->s_res )
+ syncprov_qstart( so );
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return rs->sr_err;
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_search_cb( Operation *op, SlapReply *rs )
+{
+ /*
+ * Prevent the glue overlay from processing subordinates when it is
+ * configured (explicitly or implicitly) below the syncprov overlay.
+ */
+ if ( rs->sr_type == REP_RESULT )
+ op->o_no_subordinate_glue = 1;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_search_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_INTERMEDIATE ||
+ rs->sr_err == SLAPD_ABANDON || op->o_abandon ) {
+ searchstate *ss = op->o_callback->sc_private;
+ if ( ss && ss->ss_numcsns ) {
+ ber_bvarray_free_x( ss->ss_ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( ss->ss_sids, op->o_tmpmemctx );
+ }
+ slap_freeself_cb( op, rs );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ slap_callback *cb;
+ int gotstate = 0, changed = 0, do_present = 0;
+ syncops *sop = NULL;
+ searchstate *ss;
+ sync_control *srs;
+ BerVarray ctxcsn;
+ int i, *sids, numcsns;
+ struct berval mincsn, maxcsn;
+ int minsid, maxsid;
+ int dirty = 0;
+
+ if ( op->o_sync > SLAP_CONTROL_IGNORED ) {
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_response = syncprov_search_cb;
+ cb->sc_cleanup = syncprov_search_cleanup;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+
+ if ( !(op->o_sync_mode & SLAP_SYNC_REFRESH) ) return SLAP_CB_CONTINUE;
+
+ if ( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "illegal value for derefAliases" );
+ return rs->sr_err;
+ }
+
+ srs = op->o_controls[slap_cids.sc_LDAPsync];
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "got a %ssearch with a cookie=%s\n",
+ op->o_log_prefix,
+ op->o_sync_mode & SLAP_SYNC_PERSIST ? "persistent ": "",
+ srs->sr_state.octet_str.bv_val );
+
+ /* If this is a persistent search, set it up right away */
+ if ( op->o_sync_mode & SLAP_SYNC_PERSIST ) {
+ syncops so = {0};
+ fbase_cookie fc;
+ opcookie opc;
+ slap_callback sc = {0};
+
+ fc.fss = &so;
+ fc.fbase = 0;
+ so.s_eid = NOID;
+ so.s_op = op;
+ so.s_flags = PS_IS_REFRESHING | PS_FIND_BASE;
+ /* syncprov_findbase expects to be called as a callback... */
+ sc.sc_private = &opc;
+ opc.son = on;
+ ldap_pvt_thread_mutex_init( &so.s_mutex );
+ cb = op->o_callback;
+ op->o_callback = &sc;
+ rs->sr_err = syncprov_findbase( op, &fc );
+ op->o_callback = cb;
+ ldap_pvt_thread_mutex_destroy( &so.s_mutex );
+
+ /* Special case, if client knows nothing, nor do we, keep going */
+ if ( srs->sr_state.numcsns == 0 && rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "both our DB and client empty, ignoring NO_SUCH_OBJECT\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ sop = ch_malloc( sizeof( syncops ));
+ *sop = so;
+ sop->s_rid = srs->sr_state.rid;
+ sop->s_sid = srs->sr_state.sid;
+ /* set refcount=2 to prevent being freed out from under us
+ * by abandons that occur while we're running here
+ */
+ sop->s_inuse = 2;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ while ( si->si_active ) {
+ /* Wait for active mods to finish before proceeding, as they
+ * may already have inspected the si_ops list looking for
+ * consumers to replicate the change to. Using the log
+ * doesn't help, as we may finish playing it before the
+ * active mods gets added to it.
+ */
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( slapd_shutdown ) {
+aband:
+ ch_free( sop->s_base.bv_val );
+ ch_free( sop );
+ return SLAPD_ABANDON;
+ }
+ if ( !ldap_pvt_thread_pool_pausewait( &connection_pool ))
+ ldap_pvt_thread_yield();
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ }
+ if ( op->o_abandon ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ goto aband;
+ }
+ ldap_pvt_thread_mutex_init( &sop->s_mutex );
+ sop->s_next = si->si_ops;
+ sop->s_si = si;
+ si->si_ops = sop;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "registered persistent search\n", op->o_log_prefix );
+ }
+
+ /* snapshot the ctxcsn
+ * Note: this must not be done before the psearch setup. (ITS#8365)
+ */
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+ numcsns = si->si_numcsns;
+ if ( numcsns ) {
+ ber_bvarray_dup_x( &ctxcsn, si->si_ctxcsn, op->o_tmpmemctx );
+ sids = op->o_tmpalloc( numcsns * sizeof(int), op->o_tmpmemctx );
+ for ( i=0; i<numcsns; i++ )
+ sids[i] = si->si_sids[i];
+ } else {
+ ctxcsn = NULL;
+ sids = NULL;
+ }
+ dirty = si->si_dirty;
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+
+ /* If we have a cookie, handle the PRESENT lookups */
+ if ( srs->sr_state.ctxcsn ) {
+ sessionlog *sl;
+ int i, j;
+
+ /* If we don't have any CSN of our own yet, bail out.
+ */
+ if ( !numcsns ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "consumer has state info but provider doesn't!";
+ goto bailout;
+ }
+
+ if ( !si->si_nopres )
+ do_present = SS_PRESENT;
+
+ /* If there are SIDs we don't recognize in the cookie, drop them */
+ for (i=0; i<srs->sr_state.numcsns; ) {
+ for (j=i; j<numcsns; j++) {
+ if ( srs->sr_state.sids[i] <= sids[j] ) {
+ break;
+ }
+ }
+ /* not found */
+ if ( j == numcsns || srs->sr_state.sids[i] != sids[j] ) {
+ char *tmp = srs->sr_state.ctxcsn[i].bv_val;
+ srs->sr_state.numcsns--;
+ for ( j=i; j<srs->sr_state.numcsns; j++ ) {
+ srs->sr_state.ctxcsn[j] = srs->sr_state.ctxcsn[j+1];
+ srs->sr_state.sids[j] = srs->sr_state.sids[j+1];
+ }
+ srs->sr_state.ctxcsn[j].bv_val = tmp;
+ srs->sr_state.ctxcsn[j].bv_len = 0;
+ continue;
+ }
+ i++;
+ }
+
+ if (srs->sr_state.numcsns != numcsns) {
+ /* consumer doesn't have the right number of CSNs */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "consumer cookie is missing a csn we track\n",
+ op->o_log_prefix );
+
+ changed = SS_CHANGED;
+ if ( srs->sr_state.ctxcsn ) {
+ ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx );
+ srs->sr_state.ctxcsn = NULL;
+ }
+ if ( srs->sr_state.sids ) {
+ slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx );
+ srs->sr_state.sids = NULL;
+ }
+ srs->sr_state.numcsns = 0;
+ goto shortcut;
+ }
+
+ /* Find the smallest CSN which differs from contextCSN */
+ mincsn.bv_len = 0;
+ maxcsn.bv_len = 0;
+ for ( i=0,j=0; i<srs->sr_state.numcsns; i++ ) {
+ int newer;
+ while ( srs->sr_state.sids[i] != sids[j] ) j++;
+ if ( BER_BVISEMPTY( &maxcsn ) || ber_bvcmp( &maxcsn,
+ &srs->sr_state.ctxcsn[i] ) < 0 ) {
+ maxcsn = srs->sr_state.ctxcsn[i];
+ maxsid = sids[j];
+ }
+ newer = ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[j] );
+ /* If our state is newer, tell consumer about changes */
+ if ( newer < 0) {
+ changed = SS_CHANGED;
+ if ( BER_BVISEMPTY( &mincsn ) || ber_bvcmp( &mincsn,
+ &srs->sr_state.ctxcsn[i] ) > 0 ) {
+ mincsn = srs->sr_state.ctxcsn[i];
+ minsid = sids[j];
+ }
+ } else if ( newer > 0 && sids[j] == slap_serverID ) {
+ /* our state is older, complain to consumer */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "consumer state is newer than provider!";
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "consumer %d state %s is newer than provider %d state %s\n",
+ op->o_log_prefix, sids[i], srs->sr_state.ctxcsn[i].bv_val,
+ sids[j], /* == slap_serverID */
+ ctxcsn[j].bv_val);
+bailout:
+ if ( sop ) {
+ syncops **sp = &si->si_ops;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ while ( *sp != sop )
+ sp = &(*sp)->s_next;
+ *sp = sop->s_next;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ ch_free( sop->s_base.bv_val );
+ ch_free( sop );
+ }
+ rs->sr_ctrls = NULL;
+ send_ldap_result( op, rs );
+ if ( numcsns ) {
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+ }
+ }
+ if ( BER_BVISEMPTY( &mincsn )) {
+ mincsn = maxcsn;
+ minsid = maxsid;
+ }
+
+ /* If nothing has changed, shortcut it */
+ if ( !changed && !dirty ) {
+ do_present = 0;
+no_change: if ( !(op->o_sync_mode & SLAP_SYNC_PERSIST) ) {
+ LDAPControl *ctrls[2];
+
+ ctrls[0] = NULL;
+ ctrls[1] = NULL;
+ syncprov_done_ctrl( op, rs, ctrls, 0, 0,
+ NULL, LDAP_SYNC_REFRESH_DELETES );
+ rs->sr_ctrls = ctrls;
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+ rs->sr_ctrls = NULL;
+ if ( numcsns ) {
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "no change, skipping log replay\n",
+ op->o_log_prefix );
+ goto shortcut;
+ }
+
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ do_present = 0;
+ if ( syncprov_play_accesslog( op, rs, srs, ctxcsn,
+ numcsns, sids, &mincsn, minsid ) ) {
+ do_present = SS_PRESENT;
+ }
+ } else if ( si->si_logs ) {
+ do_present = 0;
+ if ( syncprov_play_sessionlog( op, rs, srs, ctxcsn,
+ numcsns, sids, &mincsn, minsid ) ) {
+ do_present = SS_PRESENT;
+ }
+ } else if ( ad_minCSN != NULL && si->si_nopres && si->si_usehint ) {
+ /* We are instructed to trust minCSN if it exists. */
+ Entry *e;
+ Attribute *a = NULL;
+ int rc;
+
+ /*
+ * ITS#9580 FIXME: when we've figured out and split the
+ * sessionlog/deltalog tracking, use the appropriate attribute
+ */
+ rc = overlay_entry_get_ov( op, &op->o_bd->be_nsuffix[0], NULL,
+ ad_minCSN, 0, &e, on );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ a = attr_find( e->e_attrs, ad_minCSN );
+ }
+
+ if ( a != NULL ) {
+ int *minsids;
+
+ minsids = slap_parse_csn_sids( a->a_vals, a->a_numvals, op->o_tmpmemctx );
+ slap_sort_csn_sids( a->a_vals, minsids, a->a_numvals, op->o_tmpmemctx );
+
+ for ( i=0, j=0; i < a->a_numvals; i++ ) {
+ while ( j < numcsns && minsids[i] > sids[j] ) j++;
+ if ( j < numcsns && minsids[i] == sids[j] &&
+ ber_bvcmp( &a->a_vals[i], &srs->sr_state.ctxcsn[j] ) <= 0 ) {
+ /* minCSN for this serverID is contained, keep going */
+ continue;
+ }
+ /*
+ * Log DB's minCSN claims we can only replay from a certain
+ * CSN for this serverID, but consumer's cookie hasn't met that
+ * threshold: they need to refresh
+ */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "consumer not within recorded mincsn for DB's mincsn=%s\n",
+ op->o_log_prefix, a->a_vals[i].bv_val );
+ rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED;
+ rs->sr_text = "sync cookie is stale";
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ overlay_entry_release_ov( op, e, 0, on );
+ goto bailout;
+ }
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ }
+ if ( e != NULL )
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ /*
+ * If sessionlog wasn't useful, see if we can find at least one entry
+ * that hasn't changed based on the cookie.
+ *
+ * TODO: Using mincsn only (rather than the whole cookie) will
+ * under-approximate the set of entries that haven't changed, but we
+ * can't look up CSNs by serverid with the current indexing support.
+ *
+ * As a result, dormant serverids in the cluster become mincsns and
+ * more likely to make syncprov_findcsn(,FIND_CSN,) fail -> triggering
+ * an expensive refresh...
+ */
+ if ( !do_present ) {
+ gotstate = 1;
+ } else if ( syncprov_findcsn( op, FIND_CSN, &mincsn ) != LDAP_SUCCESS ) {
+ /* No, so a reload is required */
+ /* the 2.2 consumer doesn't send this hint */
+ if ( si->si_usehint && srs->sr_rhint == 0 ) {
+ rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED;
+ rs->sr_text = "sync cookie is stale";
+ goto bailout;
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "failed to find entry with csn=%s, ignoring cookie\n",
+ op->o_log_prefix, mincsn.bv_val );
+ if ( srs->sr_state.ctxcsn ) {
+ ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx );
+ srs->sr_state.ctxcsn = NULL;
+ }
+ if ( srs->sr_state.sids ) {
+ slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx );
+ srs->sr_state.sids = NULL;
+ }
+ srs->sr_state.numcsns = 0;
+ } else {
+ gotstate = 1;
+ /* If changed and doing Present lookup, send Present UUIDs */
+ if ( syncprov_findcsn( op, FIND_PRESENT, 0 ) != LDAP_SUCCESS )
+ goto bailout;
+ }
+ } else {
+ /* The consumer knows nothing, we know nothing. OK. */
+ if (!numcsns)
+ goto no_change;
+ /* No consumer state, assume something has changed */
+ changed = SS_CHANGED;
+ }
+
+shortcut:
+ /* Append CSN range to search filter, save original filter
+ * for persistent search evaluation
+ */
+ if ( sop ) {
+ ldap_pvt_thread_mutex_lock( &sop->s_mutex );
+ sop->s_filterstr = op->ors_filterstr;
+ /* correct the refcount that was set to 2 before */
+ sop->s_inuse--;
+ }
+
+ /* If something changed, find the changes */
+ if ( gotstate && ( changed || dirty ) ) {
+ Filter *fand, *fava;
+
+ fand = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ fand->f_choice = LDAP_FILTER_AND;
+ fand->f_next = NULL;
+ fava = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ fand->f_and = fava;
+ fava->f_choice = LDAP_FILTER_GE;
+ fava->f_ava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx );
+ fava->f_ava->aa_desc = slap_schema.si_ad_entryCSN;
+#ifdef LDAP_COMP_MATCH
+ fava->f_ava->aa_cf = NULL;
+#endif
+ ber_dupbv_x( &fava->f_ava->aa_value, &mincsn, op->o_tmpmemctx );
+ fava->f_next = op->ors_filter;
+ op->ors_filter = fand;
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ if ( sop ) {
+ sop->s_flags |= PS_FIX_FILTER;
+ }
+ }
+ if ( sop ) {
+ ldap_pvt_thread_mutex_unlock( &sop->s_mutex );
+ }
+
+ /* Let our callback add needed info to returned entries */
+ cb = op->o_tmpcalloc(1, sizeof(slap_callback)+sizeof(searchstate), op->o_tmpmemctx);
+ ss = (searchstate *)(cb+1);
+ ss->ss_on = on;
+ ss->ss_so = sop;
+ ss->ss_flags = do_present | changed;
+ ss->ss_ctxcsn = ctxcsn;
+ ss->ss_numcsns = numcsns;
+ ss->ss_sids = sids;
+ cb->sc_response = syncprov_search_response;
+ cb->sc_cleanup = syncprov_search_cleanup;
+ cb->sc_private = ss;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ /* If this is a persistent search and no changes were reported during
+ * the refresh phase, just invoke the response callback to transition
+ * us into persist phase
+ */
+ if ( !changed && !dirty ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "nothing changed, finishing up initial search early\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_nentries = 0;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ /* This prevents generating unnecessarily; frontend will strip
+ * any statically stored copy.
+ */
+ if ( op->o_sync != SLAP_CONTROL_NONE )
+ return SLAP_CB_CONTINUE;
+
+ if ( rs->sr_entry &&
+ dn_match( &rs->sr_entry->e_nname, &si->si_contextdn )) {
+
+ if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( slap_schema.si_ad_contextCSN, rs->sr_attrs )) {
+ Attribute *a, **ap = NULL;
+
+ for ( a=rs->sr_entry->e_attrs; a; a=a->a_next ) {
+ if ( a->a_desc == slap_schema.si_ad_contextCSN )
+ break;
+ }
+
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+ if ( si->si_ctxcsn ) {
+ if ( !a ) {
+ for ( ap = &rs->sr_operational_attrs; *ap;
+ ap=&(*ap)->a_next );
+
+ a = attr_alloc( slap_schema.si_ad_contextCSN );
+ *ap = a;
+ }
+
+ if ( !ap ) {
+ if ( rs_entry2modifiable( op, rs, on )) {
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_contextCSN );
+ }
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvarray_free( a->a_nvals );
+ }
+ a->a_nvals = NULL;
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_numvals = 0;
+ }
+ attr_valadd( a, si->si_ctxcsn, si->si_ctxcsn, si->si_numcsns );
+ }
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_setup_accesslog(void)
+{
+ const char *text;
+ int rc = -1;
+
+ if ( !ad_reqType ) {
+ if ( slap_str2ad( "reqType", &ad_reqType, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqType, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqResult ) {
+ if ( slap_str2ad( "reqResult", &ad_reqResult, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqResult, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqDN ) {
+ if ( slap_str2ad( "reqDN", &ad_reqDN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqDN, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqEntryUUID ) {
+ if ( slap_str2ad( "reqEntryUUID", &ad_reqEntryUUID, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqEntryUUID, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqNewDN ) {
+ if ( slap_str2ad( "reqNewDN", &ad_reqNewDN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqNewDN, "
+ "is accessslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_minCSN ) {
+ if ( slap_str2ad( "minCSN", &ad_minCSN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute minCSN, "
+ "is accessslog configured?\n" );
+ return rc;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+enum {
+ SP_CHKPT = 1,
+ SP_SESSL,
+ SP_NOPRES,
+ SP_USEHINT,
+ SP_LOGDB
+};
+
+static ConfigDriver sp_cf_gen;
+
+static ConfigTable spcfg[] = {
+ { "syncprov-checkpoint", "ops> <minutes", 3, 3, 0, ARG_MAGIC|SP_CHKPT,
+ sp_cf_gen, "( OLcfgOvAt:1.1 NAME 'olcSpCheckpoint' "
+ "DESC 'ContextCSN checkpoint interval in ops and minutes' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-sessionlog", "ops", 2, 2, 0, ARG_INT|ARG_MAGIC|SP_SESSL,
+ sp_cf_gen, "( OLcfgOvAt:1.2 NAME 'olcSpSessionlog' "
+ "DESC 'Session log size in ops' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-nopresent", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_NOPRES,
+ sp_cf_gen, "( OLcfgOvAt:1.3 NAME 'olcSpNoPresent' "
+ "DESC 'Omit Present phase processing' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-reloadhint", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_USEHINT,
+ sp_cf_gen, "( OLcfgOvAt:1.4 NAME 'olcSpReloadHint' "
+ "DESC 'Observe Reload Hint in Request control' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-sessionlog-source", NULL, 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|SP_LOGDB,
+ sp_cf_gen, "( OLcfgOvAt:1.5 NAME 'olcSpSessionlogSource' "
+ "DESC 'On startup, try loading sessionlog from this subtree' "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs spocs[] = {
+ { "( OLcfgOvOc:1.1 "
+ "NAME 'olcSyncProvConfig' "
+ "DESC 'SyncRepl Provider configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSpCheckpoint "
+ "$ olcSpSessionlog "
+ "$ olcSpNoPresent "
+ "$ olcSpReloadHint "
+ "$ olcSpSessionlogSource "
+ ") )",
+ Cft_Overlay, spcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+sp_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch ( c->type ) {
+ case SP_CHKPT:
+ if ( si->si_chkops || si->si_chktime ) {
+ struct berval bv;
+ /* we assume si_chktime is a multiple of 60
+ * because the parsed value was originally
+ * multiplied by 60 */
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%d %d", si->si_chkops, si->si_chktime/60 );
+ if ( bv.bv_len >= sizeof( c->cr_msg ) ) {
+ rc = 1;
+ } else {
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_SESSL:
+ if ( si->si_logs ) {
+ c->value_int = si->si_logs->sl_size;
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_NOPRES:
+ if ( si->si_nopres ) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_USEHINT:
+ if ( si->si_usehint ) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_LOGDB:
+ if ( BER_BVISEMPTY( &si->si_logbase ) ) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &si->si_logbase );
+ value_add_one( &c->rvalue_nvals, &si->si_logbase );
+ }
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch ( c->type ) {
+ case SP_CHKPT:
+ si->si_chkops = 0;
+ si->si_chktime = 0;
+ break;
+ case SP_SESSL:
+ if ( si->si_logs )
+ si->si_logs->sl_size = 0;
+ break;
+ case SP_NOPRES:
+ si->si_nopres = 0;
+ break;
+ case SP_USEHINT:
+ si->si_usehint = 0;
+ break;
+ case SP_LOGDB:
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ ch_free( si->si_logbase.bv_val );
+ BER_BVZERO( &si->si_logbase );
+ }
+ break;
+ }
+ return rc;
+ }
+ switch ( c->type ) {
+ case SP_CHKPT:
+ if ( lutil_atoi( &si->si_chkops, c->argv[1] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint ops # \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( si->si_chkops <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint ops # \"%d\"",
+ c->argv[0], si->si_chkops );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( lutil_atoi( &si->si_chktime, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint time \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( si->si_chktime <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint time \"%d\"",
+ c->argv[0], si->si_chkops );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ si->si_chktime *= 60;
+ break;
+ case SP_SESSL: {
+ sessionlog *sl;
+ int size = c->value_int;
+
+ if ( size < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s size %d is negative",
+ c->argv[0], size );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ if ( size && !BER_BVISNULL( &si->si_logbase ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring "
+ "internal sessionlog, accesslog source has already been "
+ "configured, this results in wasteful operation\n" );
+ }
+ sl = si->si_logs;
+ if ( !sl ) {
+ if ( !size ) break;
+ sl = ch_calloc( 1, sizeof( sessionlog ));
+ ldap_pvt_thread_rdwr_init( &sl->sl_mutex );
+ si->si_logs = sl;
+ }
+ sl->sl_size = size;
+ }
+ break;
+ case SP_NOPRES:
+ si->si_nopres = c->value_int;
+ break;
+ case SP_USEHINT:
+ si->si_usehint = c->value_int;
+ if ( si->si_usehint ) {
+ /* Consider we might be a delta provider, but it's ok if not */
+ (void)syncprov_setup_accesslog();
+ }
+ break;
+ case SP_LOGDB:
+ if ( si->si_logs ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring "
+ "accesslog source, internal sessionlog has already been "
+ "configured, this results in wasteful operation\n" );
+ }
+ if ( CONFIG_ONLINE_ADD( c ) ) {
+ if ( !select_backend( &c->value_ndn, 0 ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> no matching backend found for suffix",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ break;
+ }
+ ch_free( c->value_ndn.bv_val );
+ }
+ si->si_logbase = c->value_ndn;
+ rc = syncprov_setup_accesslog();
+ ch_free( c->value_dn.bv_val );
+ break;
+ }
+ return rc;
+}
+
+/* ITS#3456 we cannot run this search on the main thread, must use a
+ * child thread in order to insure we have a big enough stack.
+ */
+static void *
+syncprov_db_otask(
+ void *ptr
+)
+{
+ syncprov_findcsn( ptr, FIND_MAXCSN, 0 );
+ return NULL;
+}
+
+static int
+syncprov_db_ocallback(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
+ if ( rs->sr_entry->e_name.bv_len )
+ op->o_callback->sc_private = (void *)1;
+ }
+ return LDAP_SUCCESS;
+}
+
+/* ITS#9015 see if the DB is really empty */
+static void *
+syncprov_db_otask2(
+ void *ptr
+)
+{
+ Operation *op = ptr;
+ SlapReply rs = {REP_RESULT};
+ slap_callback cb = {0};
+ int rc;
+
+ cb.sc_response = syncprov_db_ocallback;
+
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_callback = &cb;
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_limit = NULL;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->ors_attrsonly = 1;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_filter = &generic_filter;
+ op->ors_filterstr = generic_filterstr;
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc == LDAP_SIZELIMIT_EXCEEDED || cb.sc_private )
+ op->ors_slimit = 2;
+ return NULL;
+}
+
+/* Read any existing contextCSN from the underlying db.
+ * Then search for any entries newer than that. If no value exists,
+ * just generate it. Cache whatever result.
+ */
+static int
+syncprov_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc;
+ void *thrctx = NULL;
+
+ if ( !SLAP_LASTMOD( be )) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov_db_open: invalid config, lastmod must be enabled\n" );
+ return -1;
+ }
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ rc = overlay_register_control( be, LDAP_CONTROL_SYNC );
+ if ( rc ) {
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: "
+ "starting syncprov for suffix %s\n",
+ be->be_suffix[0].bv_val );
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = be;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+
+ if ( SLAP_SYNC_SUBENTRY( be )) {
+ build_new_dn( &si->si_contextdn, be->be_nsuffix,
+ (struct berval *)&slap_ldapsync_cn_bv, NULL );
+ } else {
+ si->si_contextdn = be->be_nsuffix[0];
+ }
+ rc = overlay_entry_get_ov( op, &si->si_contextdn, NULL,
+ slap_schema.si_ad_contextCSN, 0, &e, on );
+
+ if ( e ) {
+ ldap_pvt_thread_t tid;
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ ber_bvarray_dup_x( &si->si_ctxcsn, a->a_vals, NULL );
+ si->si_numcsns = a->a_numvals;
+ si->si_sids = slap_parse_csn_sids( si->si_ctxcsn, a->a_numvals, NULL );
+ slap_sort_csn_sids( si->si_ctxcsn, si->si_sids, si->si_numcsns, NULL );
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ if ( si->si_ctxcsn && !SLAP_DBCLEAN( be )) {
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_req_dn = be->be_suffix[0];
+ op->o_req_ndn = be->be_nsuffix[0];
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ ldap_pvt_thread_create( &tid, 0, syncprov_db_otask, op );
+ ldap_pvt_thread_join( tid, NULL );
+ }
+ }
+
+ /* Didn't find a contextCSN, should we generate one? */
+ if ( !si->si_ctxcsn ) {
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ struct berval csn;
+
+ if ( SLAP_SINGLE_SHADOW( op->o_bd ) ) {
+ /* Not in charge of this serverID, don't generate anything. */
+ goto out;
+ }
+ if ( !SLAP_SYNC_SUBENTRY( be ) && rc != LDAP_SUCCESS
+ && rc != LDAP_NO_SUCH_ATTRIBUTE ) {
+ /* If the DB is genuinely empty, don't generate one either. */
+ goto out;
+ }
+ if ( !si->si_contextdn.bv_len ) {
+ ldap_pvt_thread_t tid;
+ /* a glue entry here with no contextCSN might mean an empty DB.
+ * we need to search for children, to be sure.
+ */
+ op->o_req_dn = be->be_suffix[0];
+ op->o_req_ndn = be->be_nsuffix[0];
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ ldap_pvt_thread_create( &tid, 0, syncprov_db_otask2, op );
+ ldap_pvt_thread_join( tid, NULL );
+ if ( op->ors_slimit == 1 )
+ goto out;
+ }
+
+ csn.bv_val = csnbuf;
+ csn.bv_len = sizeof( csnbuf );
+ slap_get_csn( op, &csn, 0 );
+ value_add_one( &si->si_ctxcsn, &csn );
+ si->si_numcsns = 1;
+ si->si_sids = ch_malloc( sizeof(int) );
+ si->si_sids[0] = slap_serverID;
+ Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: "
+ "generated a new ctxcsn=%s for suffix %s\n",
+ csn.bv_val, be->be_suffix[0].bv_val );
+
+ /* make sure we do a checkpoint on close */
+ si->si_numops++;
+ }
+
+ /* Initialize the sessionlog mincsn */
+ if ( si->si_logs && si->si_numcsns ) {
+ sessionlog *sl = si->si_logs;
+ int i;
+ ber_bvarray_dup_x( &sl->sl_mincsn, si->si_ctxcsn, NULL );
+ sl->sl_numcsns = si->si_numcsns;
+ sl->sl_sids = ch_malloc( si->si_numcsns * sizeof(int) );
+ for ( i=0; i < si->si_numcsns; i++ )
+ sl->sl_sids[i] = si->si_sids[i];
+ }
+
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ BackendDB *db = select_backend( &si->si_logbase, 0 );
+ if ( !db ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_db_open: "
+ "configured accesslog database dn='%s' not present\n",
+ si->si_logbase.bv_val );
+ return -1;
+ }
+ }
+
+out:
+ op->o_bd->bd_info = (BackendInfo *)on;
+ return 0;
+}
+
+/* Write the current contextCSN into the underlying db.
+ */
+static int
+syncprov_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+#ifdef SLAP_CONFIG_DELETE
+ syncops *so, *sonext;
+#endif /* SLAP_CONFIG_DELETE */
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+ if ( si->si_numops ) {
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = be;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+ syncprov_checkpoint( op, on );
+ }
+
+#ifdef SLAP_CONFIG_DELETE
+ if ( !slapd_shutdown ) {
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( so=si->si_ops, sonext=so; so; so=sonext ) {
+ SlapReply rs = {REP_RESULT};
+ rs.sr_err = LDAP_UNAVAILABLE;
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ send_ldap_result( so->s_op, &rs );
+ sonext=so->s_next;
+ if ( so->s_flags & PS_TASK_QUEUED )
+ ldap_pvt_thread_pool_retract( so->s_pool_cookie );
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ if ( !syncprov_drop_psearch( so, 0 ))
+ so->s_si = NULL;
+ }
+ si->si_ops=NULL;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ }
+ overlay_unregister_control( be, LDAP_CONTROL_SYNC );
+#endif /* SLAP_CONFIG_DELETE */
+
+ return 0;
+}
+
+static int
+syncprov_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ syncprov_info_t *si;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov must be instantiated within a database.\n" );
+ return 1;
+ }
+
+ si = ch_calloc(1, sizeof(syncprov_info_t));
+ on->on_bi.bi_private = si;
+ ldap_pvt_thread_rdwr_init( &si->si_csn_rwlock );
+ ldap_pvt_thread_mutex_init( &si->si_ops_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_resp_mutex );
+
+ csn_anlist[0].an_desc = slap_schema.si_ad_entryCSN;
+ csn_anlist[0].an_name = slap_schema.si_ad_entryCSN->ad_cname;
+ csn_anlist[1].an_desc = slap_schema.si_ad_entryUUID;
+ csn_anlist[1].an_name = slap_schema.si_ad_entryUUID->ad_cname;
+
+ uuid_anlist[0].an_desc = slap_schema.si_ad_entryUUID;
+ uuid_anlist[0].an_name = slap_schema.si_ad_entryUUID->ad_cname;
+
+ return 0;
+}
+
+static int
+syncprov_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ if ( si ) {
+ if ( si->si_logs ) {
+ sessionlog *sl = si->si_logs;
+
+ ldap_tavl_free( sl->sl_entries, (AVL_FREE)ch_free );
+ if ( sl->sl_mincsn )
+ ber_bvarray_free( sl->sl_mincsn );
+ if ( sl->sl_sids )
+ ch_free( sl->sl_sids );
+
+ ldap_pvt_thread_rdwr_destroy(&si->si_logs->sl_mutex);
+ ch_free( si->si_logs );
+ }
+ if ( si->si_ctxcsn )
+ ber_bvarray_free( si->si_ctxcsn );
+ if ( si->si_sids )
+ ch_free( si->si_sids );
+ if ( si->si_logbase.bv_val )
+ ch_free( si->si_logbase.bv_val );
+ ldap_pvt_thread_mutex_destroy( &si->si_resp_mutex );
+ ldap_pvt_thread_mutex_destroy( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_destroy( &si->si_ops_mutex );
+ ldap_pvt_thread_rdwr_destroy( &si->si_csn_rwlock );
+ ch_free( si );
+ }
+
+ return 0;
+}
+
+static int syncprov_parseCtrl (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_int_t mode;
+ ber_len_t len;
+ struct berval cookie = BER_BVNULL;
+ sync_control *sr;
+ int rhint = 0;
+
+ if ( op->o_sync != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Sync control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Sync control specified with pagedResults control";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Sync control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Sync control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* Parse the control value
+ * syncRequestValue ::= SEQUENCE {
+ * mode ENUMERATED {
+ * -- 0 unused
+ * refreshOnly (1),
+ * -- 2 reserved
+ * refreshAndPersist (3)
+ * },
+ * cookie syncCookie OPTIONAL
+ * }
+ */
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ if ( (tag = ber_scanf( ber, "{i" /*}*/, &mode )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : mode decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ switch( mode ) {
+ case LDAP_SYNC_REFRESH_ONLY:
+ mode = SLAP_SYNC_REFRESH;
+ break;
+ case LDAP_SYNC_REFRESH_AND_PERSIST:
+ mode = SLAP_SYNC_REFRESH_AND_PERSIST;
+ break;
+ default:
+ rs->sr_text = "Sync control : unknown update mode";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag == LDAP_TAG_SYNC_COOKIE ) {
+ if (( ber_scanf( ber, /*{*/ "m", &cookie )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : cookie decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+ if ( tag == LDAP_TAG_RELOAD_HINT ) {
+ if (( ber_scanf( ber, /*{*/ "b", &rhint )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : rhint decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+ if (( ber_scanf( ber, /*{*/ "}")) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ sr = op->o_tmpcalloc( 1, sizeof(struct sync_control), op->o_tmpmemctx );
+ sr->sr_rhint = rhint;
+ if (!BER_BVISNULL(&cookie)) {
+ ber_dupbv_x( &sr->sr_state.octet_str, &cookie, op->o_tmpmemctx );
+ /* If parse fails, pretend no cookie was sent */
+ if ( slap_parse_sync_cookie( &sr->sr_state, op->o_tmpmemctx ) ||
+ sr->sr_state.rid == -1 ) {
+ if ( sr->sr_state.ctxcsn ) {
+ ber_bvarray_free_x( sr->sr_state.ctxcsn, op->o_tmpmemctx );
+ sr->sr_state.ctxcsn = NULL;
+ }
+ sr->sr_state.numcsns = 0;
+ }
+ }
+
+ op->o_controls[slap_cids.sc_LDAPsync] = sr;
+
+ op->o_sync = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ op->o_sync_mode |= mode; /* o_sync_mode shares o_sync */
+
+ return LDAP_SUCCESS;
+}
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+static slap_overinst syncprov;
+
+int
+syncprov_initialize()
+{
+ int rc;
+
+ rc = register_supported_control( LDAP_CONTROL_SYNC,
+ SLAP_CTRL_SEARCH, NULL,
+ syncprov_parseCtrl, &slap_cids.sc_LDAPsync );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov_init: Failed to register control %d\n", rc );
+ return rc;
+ }
+
+ syncprov.on_bi.bi_type = "syncprov";
+ syncprov.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ syncprov.on_bi.bi_db_init = syncprov_db_init;
+ syncprov.on_bi.bi_db_destroy = syncprov_db_destroy;
+ syncprov.on_bi.bi_db_open = syncprov_db_open;
+ syncprov.on_bi.bi_db_close = syncprov_db_close;
+
+ syncprov.on_bi.bi_op_abandon = syncprov_op_abandon;
+ syncprov.on_bi.bi_op_cancel = syncprov_op_abandon;
+
+ syncprov.on_bi.bi_op_add = syncprov_op_mod;
+ syncprov.on_bi.bi_op_compare = syncprov_op_compare;
+ syncprov.on_bi.bi_op_delete = syncprov_op_mod;
+ syncprov.on_bi.bi_op_modify = syncprov_op_mod;
+ syncprov.on_bi.bi_op_modrdn = syncprov_op_mod;
+ syncprov.on_bi.bi_op_search = syncprov_op_search;
+ syncprov.on_bi.bi_extended = syncprov_op_extended;
+ syncprov.on_bi.bi_operational = syncprov_operational;
+
+ syncprov.on_bi.bi_cf_ocs = spocs;
+
+ generic_filter.f_desc = slap_schema.si_ad_objectClass;
+
+ rc = config_register_schema( spcfg, spocs );
+ if ( rc ) return rc;
+
+ return overlay_register( &syncprov );
+}
+
+#if SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return syncprov_initialize();
+}
+#endif /* SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_SYNCPROV) */
diff --git a/servers/slapd/overlays/translucent.c b/servers/slapd/overlays/translucent.c
new file mode 100644
index 0000000..09b12dc
--- /dev/null
+++ b/servers/slapd/overlays/translucent.c
@@ -0,0 +1,1497 @@
+/* translucent.c - translucent proxy module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_TRANSLUCENT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+#include "slap-config.h"
+
+/* config block */
+typedef struct translucent_info {
+ BackendDB db; /* captive backend */
+ AttributeName *local; /* valid attrs for local filters */
+ AttributeName *remote; /* valid attrs for remote filters */
+ int strict;
+ int no_glue;
+ int defer_db_open;
+ int bind_local;
+ int pwmod_local;
+} translucent_info;
+
+static ConfigLDAPadd translucent_ldadd;
+static ConfigCfAdd translucent_cfadd;
+
+static ConfigDriver translucent_cf_gen;
+
+enum {
+ TRANS_LOCAL = 1,
+ TRANS_REMOTE
+};
+
+static ConfigTable translucentcfg[] = {
+ { "translucent_strict", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, strict),
+ "( OLcfgOvAt:14.1 NAME 'olcTranslucentStrict' "
+ "DESC 'Reveal attribute deletion constraint violations' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "translucent_no_glue", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, no_glue),
+ "( OLcfgOvAt:14.2 NAME 'olcTranslucentNoGlue' "
+ "DESC 'Disable automatic glue records for ADD and MODRDN' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "translucent_local", "attr[,attr...]", 1, 2, 0,
+ ARG_MAGIC|TRANS_LOCAL,
+ translucent_cf_gen,
+ "( OLcfgOvAt:14.3 NAME 'olcTranslucentLocal' "
+ "DESC 'Attributes to use in local search filter' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "translucent_remote", "attr[,attr...]", 1, 2, 0,
+ ARG_MAGIC|TRANS_REMOTE,
+ translucent_cf_gen,
+ "( OLcfgOvAt:14.4 NAME 'olcTranslucentRemote' "
+ "DESC 'Attributes to use in remote search filter' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "translucent_bind_local", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, bind_local),
+ "( OLcfgOvAt:14.5 NAME 'olcTranslucentBindLocal' "
+ "DESC 'Enable local bind' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL },
+ { "translucent_pwmod_local", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, pwmod_local),
+ "( OLcfgOvAt:14.6 NAME 'olcTranslucentPwModLocal' "
+ "DESC 'Enable local RFC 3062 Password Modify extended operation' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs translucentocs[] = {
+ { "( OLcfgOvOc:14.1 "
+ "NAME 'olcTranslucentConfig' "
+ "DESC 'Translucent configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcTranslucentStrict $ olcTranslucentNoGlue $"
+ " olcTranslucentLocal $ olcTranslucentRemote $"
+ " olcTranslucentBindLocal $ olcTranslucentPwModLocal ) )",
+ Cft_Overlay, translucentcfg, NULL, translucent_cfadd },
+ { "( OLcfgOvOc:14.2 "
+ "NAME 'olcTranslucentDatabase' "
+ "DESC 'Translucent target database configuration' "
+ /* co_table is initialized in translucent_initialize() */
+ "AUXILIARY )", Cft_Misc, NULL, translucent_ldadd },
+ { NULL, 0, NULL }
+};
+/* for translucent_init() */
+
+static int
+translucent_ldadd_cleanup( ConfigArgs *ca )
+{
+ slap_overinst *on = ca->ca_private;
+ translucent_info *ov = on->on_bi.bi_private;
+
+ ov->defer_db_open = 0;
+ return backend_startup_one( ca->be, &ca->reply );
+}
+
+static int
+translucent_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ translucent_info *ov;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_ldadd\n" );
+
+ if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
+ cei->ce_bi->bi_cf_ocs != translucentocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ on = (slap_overinst *)cei->ce_bi;
+ ov = on->on_bi.bi_private;
+ ca->be = &ov->db;
+ ca->ca_private = on;
+ if ( CONFIG_ONLINE_ADD( ca ))
+ config_push_cleanup( ca, translucent_ldadd_cleanup );
+ else
+ ov->defer_db_open = 0;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+translucent_cfadd( Operation *op, SlapReply *rs, Entry *e, ConfigArgs *ca )
+{
+ CfEntryInfo *cei = e->e_private;
+ slap_overinst *on = (slap_overinst *)cei->ce_bi;
+ translucent_info *ov = on->on_bi.bi_private;
+ struct berval bv;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_cfadd\n" );
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase=" SLAP_X_ORDERED_FMT "%s",
+ 0, ov->db.bd_info->bi_type );
+ if ( bv.bv_len >= sizeof( ca->cr_msg ) ) {
+ return -1;
+ }
+ bv.bv_val = ca->cr_msg;
+ ca->be = &ov->db;
+ ov->defer_db_open = 0;
+
+ /* We can only create this entry if the database is table-driven
+ */
+ if ( ov->db.be_cf_ocs )
+ config_build_entry( op, rs, cei, ca, &bv,
+ ov->db.be_cf_ocs,
+ &translucentocs[1] );
+
+ return 0;
+}
+
+static int
+translucent_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ translucent_info *ov = on->on_bi.bi_private;
+ AttributeName **an, *a2;
+ int i;
+
+ if ( c->type == TRANS_LOCAL )
+ an = &ov->local;
+ else
+ an = &ov->remote;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( !*an )
+ return 1;
+ for ( i = 0; !BER_BVISNULL(&(*an)[i].an_name); i++ ) {
+ value_add_one( &c->rvalue_vals, &(*an)[i].an_name );
+ }
+ return ( i < 1 );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ anlist_free( *an, 1, NULL );
+ *an = NULL;
+ } else {
+ i = c->valx;
+ ch_free( (*an)[i].an_name.bv_val );
+ do {
+ (*an)[i] = (*an)[i+1];
+ i++;
+ } while ( !BER_BVISNULL( &(*an)[i].an_name ));
+ }
+ return 0;
+ }
+
+ /* cn=config values could be deleted later, we only want one name
+ * per value for valx to match. */
+ if ( c->op != SLAP_CONFIG_ADD && strchr( c->argv[1], ',' ) ) {
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s: "
+ "Supplying multiple attribute names in a single value is "
+ "unsupported and will be disallowed in a future version\n",
+ c->log, c->argv[0] );
+ }
+
+ a2 = str2anlist( *an, c->argv[1], "," );
+ if ( !a2 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse attribute %s",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ return ARG_BAD_CONF;
+ }
+ *an = a2;
+ return 0;
+}
+
+static slap_overinst translucent;
+
+/*
+** glue_parent()
+** call syncrepl_add_glue() with the parent suffix;
+**
+*/
+
+static struct berval glue[] = { BER_BVC("top"), BER_BVC("glue"), BER_BVNULL };
+
+void glue_parent(Operation *op) {
+ Operation nop = *op;
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct berval ndn = BER_BVNULL;
+ Attribute *a;
+ Entry *e;
+ struct berval pdn;
+
+ dnParent( &op->o_req_ndn, &pdn );
+ ber_dupbv_x( &ndn, &pdn, op->o_tmpmemctx );
+
+ Debug(LDAP_DEBUG_TRACE, "=> glue_parent: fabricating glue for <%s>\n", ndn.bv_val );
+
+ e = entry_alloc();
+ e->e_id = NOID;
+ ber_dupbv(&e->e_name, &ndn);
+ ber_dupbv(&e->e_nname, &ndn);
+
+ a = attr_alloc( slap_schema.si_ad_objectClass );
+ a->a_numvals = 2;
+ a->a_vals = ch_malloc(sizeof(struct berval) * 3);
+ ber_dupbv(&a->a_vals[0], &glue[0]);
+ ber_dupbv(&a->a_vals[1], &glue[1]);
+ ber_dupbv(&a->a_vals[2], &glue[2]);
+ a->a_nvals = a->a_vals;
+ a->a_next = e->e_attrs;
+ e->e_attrs = a;
+
+ a = attr_alloc( slap_schema.si_ad_structuralObjectClass );
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc(sizeof(struct berval) * 2);
+ ber_dupbv(&a->a_vals[0], &glue[1]);
+ ber_dupbv(&a->a_vals[1], &glue[2]);
+ a->a_nvals = a->a_vals;
+ a->a_next = e->e_attrs;
+ e->e_attrs = a;
+
+ nop.o_req_dn = ndn;
+ nop.o_req_ndn = ndn;
+ nop.ora_e = e;
+
+ nop.o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ syncrepl_add_glue(&nop, e);
+ nop.o_bd->bd_info = (BackendInfo *) on;
+
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+
+ return;
+}
+
+/*
+** free_attr_chain()
+** free only the Attribute*, not the contents;
+**
+*/
+void free_attr_chain(Attribute *b) {
+ Attribute *a;
+ for(a=b; a; a=a->a_next) {
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ }
+ attrs_free( b );
+ return;
+}
+
+/*
+** translucent_add()
+** if not bound as root, send ACCESS error;
+** if glue, glue_parent();
+** return CONTINUE;
+**
+*/
+
+static int translucent_add(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_add: %s\n",
+ op->o_req_dn.bv_val );
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ if(!ov->no_glue) glue_parent(op);
+ return(SLAP_CB_CONTINUE);
+}
+
+/*
+** translucent_modrdn()
+** if not bound as root, send ACCESS error;
+** if !glue, glue_parent();
+** else return CONTINUE;
+**
+*/
+
+static int translucent_modrdn(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_modrdn: %s -> %s\n",
+ op->o_req_dn.bv_val, op->orr_newrdn.bv_val );
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ if(!ov->no_glue) {
+ op->o_tag = LDAP_REQ_ADD;
+ glue_parent(op);
+ op->o_tag = LDAP_REQ_MODRDN;
+ }
+ return(SLAP_CB_CONTINUE);
+}
+
+/*
+** translucent_delete()
+** if not bound as root, send ACCESS error;
+** else return CONTINUE;
+**
+*/
+
+static int translucent_delete(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_delete: %s\n",
+ op->o_req_dn.bv_val );
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ return(SLAP_CB_CONTINUE);
+}
+
+static int
+translucent_tag_cb( Operation *op, SlapReply *rs )
+{
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = op->o_callback->sc_private;
+ rs->sr_tag = slap_req2res( op->o_tag );
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** translucent_modify()
+** modify in local backend if exists in both;
+** otherwise, add to local backend;
+** fail if not defined in captive backend;
+**
+*/
+
+static int translucent_modify(Operation *op, SlapReply *rs) {
+ SlapReply nrs = { REP_RESULT };
+
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *re = NULL;
+ Attribute *a, *ax;
+ Modifications *m, **mm;
+ BackendDB *db;
+ int del, rc, erc = 0;
+ slap_callback cb = { 0 };
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_modify: %s\n",
+ op->o_req_dn.bv_val );
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+/*
+** fetch entry from the captive backend;
+** if it did not exist, fail;
+** release it, if captive backend supports this;
+**
+*/
+
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re);
+ op->o_bd = db;
+ if(rc != LDAP_SUCCESS || re == NULL ) {
+ send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT,
+ "attempt to modify nonexistent local record");
+ return(rs->sr_err);
+ }
+/*
+** fetch entry from local backend;
+** if it exists:
+** foreach Modification:
+** if attr not present in local:
+** if Mod == LDAP_MOD_DELETE:
+** if remote attr not present, return NO_SUCH;
+** if remote attr present, drop this Mod;
+** else force this Mod to LDAP_MOD_ADD;
+** return CONTINUE;
+**
+*/
+
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+
+ if(e && rc == LDAP_SUCCESS) {
+ Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: found local entry\n" );
+ for(mm = &op->orm_modlist; *mm; ) {
+ m = *mm;
+ for(a = e->e_attrs; a; a = a->a_next)
+ if(a->a_desc == m->sml_desc) break;
+ if(a) {
+ mm = &m->sml_next;
+ continue; /* found local attr */
+ }
+ if(m->sml_op == LDAP_MOD_DELETE) {
+ for(a = re->e_attrs; a; a = a->a_next)
+ if(a->a_desc == m->sml_desc) break;
+ /* not found remote attr */
+ if(!a) {
+ erc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto release;
+ }
+ if(ov->strict) {
+ erc = LDAP_CONSTRAINT_VIOLATION;
+ goto release;
+ }
+ Debug(LDAP_DEBUG_TRACE,
+ "=> translucent_modify: silently dropping delete: %s\n",
+ m->sml_desc->ad_cname.bv_val );
+ *mm = m->sml_next;
+ m->sml_next = NULL;
+ slap_mods_free(m, 1);
+ continue;
+ }
+ m->sml_op = LDAP_MOD_ADD;
+ mm = &m->sml_next;
+ }
+ erc = SLAP_CB_CONTINUE;
+release:
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else
+ entry_free(re);
+ }
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ be_entry_release_r(op, e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+ if(erc == SLAP_CB_CONTINUE) {
+ return(erc);
+ } else if(erc) {
+ send_ldap_error(op, rs, erc,
+ "attempt to delete nonexistent attribute");
+ return(erc);
+ }
+ }
+
+ /* don't leak remote entry copy */
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else
+ entry_free(re);
+ }
+/*
+** foreach Modification:
+** if MOD_ADD or MOD_REPLACE, add Attribute;
+** if no Modifications were suitable:
+** if strict, throw CONSTRAINT_VIOLATION;
+** else, return early SUCCESS;
+** fabricate Entry with new Attribute chain;
+** glue_parent() for this Entry;
+** call bi_op_add() in local backend;
+**
+*/
+
+ Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: fabricating local add\n" );
+ a = NULL;
+ for(del = 0, ax = NULL, m = op->orm_modlist; m; m = m->sml_next) {
+ Attribute atmp;
+ if(((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_ADD) &&
+ ((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_REPLACE)) {
+ Debug(LDAP_DEBUG_ANY,
+ "=> translucent_modify: silently dropped modification(%d): %s\n",
+ m->sml_op, m->sml_desc->ad_cname.bv_val );
+ if((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) del++;
+ continue;
+ }
+ atmp.a_desc = m->sml_desc;
+ atmp.a_vals = m->sml_values;
+ atmp.a_nvals = m->sml_nvalues ? m->sml_nvalues : atmp.a_vals;
+ atmp.a_numvals = m->sml_numvals;
+ atmp.a_flags = 0;
+ a = attr_dup( &atmp );
+ a->a_next = ax;
+ ax = a;
+ }
+
+ if(del && ov->strict) {
+ attrs_free( a );
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "attempt to delete attributes from local database");
+ return(rs->sr_err);
+ }
+
+ if(!ax) {
+ if(ov->strict) {
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "modification contained other than ADD or REPLACE");
+ return(rs->sr_err);
+ }
+ /* rs->sr_text = "no valid modification found"; */
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result(op, rs);
+ return(rs->sr_err);
+ }
+
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &op->o_req_dn );
+ ber_dupbv( &e->e_nname, &op->o_req_ndn );
+ e->e_attrs = a;
+
+ op->o_tag = LDAP_REQ_ADD;
+ cb.sc_response = translucent_tag_cb;
+ cb.sc_private = op->orm_modlist;
+ op->oq_add.rs_e = e;
+
+ glue_parent(op);
+
+ cb.sc_next = op->o_callback;
+ op->o_callback = &cb;
+ rc = on->on_info->oi_orig->bi_op_add(op, &nrs);
+ if ( op->ora_e == e )
+ entry_free( e );
+ op->o_callback = cb.sc_next;
+
+ return(rc);
+}
+
+static int translucent_compare(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ AttributeAssertion *ava = op->orc_ava;
+ Entry *e = NULL;
+ BackendDB *db;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_compare: <%s> %s:%s\n",
+ op->o_req_dn.bv_val, ava->aa_desc->ad_cname.bv_val, ava->aa_value.bv_val);
+
+/*
+** if the local backend has an entry for this attribute:
+** CONTINUE and let it do the compare;
+**
+*/
+ rc = overlay_entry_get_ov(op, &op->o_req_ndn, NULL, ava->aa_desc, 0, &e, on);
+ if(rc == LDAP_SUCCESS && e) {
+ overlay_entry_release_ov(op, e, 0, on);
+ return(SLAP_CB_CONTINUE);
+ }
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+/*
+** call compare() in the captive backend;
+** return the result;
+**
+*/
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_op_compare(op, rs);
+ op->o_bd = db;
+
+ return(rc);
+}
+
+static int translucent_pwmod(Operation *op, SlapReply *rs) {
+ SlapReply nrs = { REP_RESULT };
+ Operation nop;
+
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *re = NULL;
+ BackendDB *db;
+ int rc = 0;
+ slap_callback cb = { 0 };
+
+ if (!ov->pwmod_local) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION,
+ rs->sr_text = "attempt to modify password in local database";
+ return rs->sr_err;
+ }
+
+/*
+** fetch entry from the captive backend;
+** if it did not exist, fail;
+** release it, if captive backend supports this;
+**
+*/
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re);
+ if(rc != LDAP_SUCCESS || re == NULL ) {
+ send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT,
+ "attempt to modify nonexistent local record");
+ return(rs->sr_err);
+ }
+ op->o_bd = db;
+/*
+** fetch entry from local backend;
+** if it exists:
+** return CONTINUE;
+*/
+
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+
+ if(e && rc == LDAP_SUCCESS) {
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else {
+ entry_free(re);
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ be_entry_release_r(op, e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* don't leak remote entry copy */
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else {
+ entry_free(re);
+ }
+ }
+/*
+** glue_parent() for this Entry;
+** call bi_op_add() in local backend;
+**
+*/
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &op->o_req_dn );
+ ber_dupbv( &e->e_nname, &op->o_req_ndn );
+ e->e_attrs = NULL;
+
+ nop = *op;
+ nop.o_tag = LDAP_REQ_ADD;
+ cb.sc_response = slap_null_cb;
+ nop.oq_add.rs_e = e;
+
+ glue_parent(&nop);
+
+ nop.o_callback = &cb;
+ rc = on->on_info->oi_orig->bi_op_add(&nop, &nrs);
+ if ( nop.ora_e == e ) {
+ entry_free( e );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ return rc;
+}
+
+static int translucent_exop(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ const struct berval bv_exop_pwmod = BER_BVC(LDAP_EXOP_MODIFY_PASSWD);
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_exop: %s\n",
+ op->o_req_dn.bv_val );
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ if ( bvmatch( &bv_exop_pwmod, &op->ore_reqoid ) ) {
+ return translucent_pwmod( op, rs );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** translucent_search_cb()
+** merge local data with remote data
+**
+** Four cases:
+** 1: remote search, no local filter
+** merge data and send immediately
+** 2: remote search, with local filter
+** merge data and save
+** 3: local search, no remote filter
+** merge data and send immediately
+** 4: local search, with remote filter
+** check list, merge, send, delete
+*/
+
+#define RMT_SIDE 0
+#define LCL_SIDE 1
+#define USE_LIST 2
+
+typedef struct trans_ctx {
+ BackendDB *db;
+ slap_overinst *on;
+ Filter *orig;
+ TAvlnode *list;
+ int step;
+ int slimit;
+ AttributeName *attrs;
+} trans_ctx;
+
+static int translucent_search_cb(Operation *op, SlapReply *rs) {
+ trans_ctx *tc;
+ BackendDB *db;
+ slap_overinst *on;
+ translucent_info *ov;
+ Entry *le, *re;
+ Attribute *a, *ax, *an, *as = NULL;
+ int rc;
+ int test_f = 0;
+
+ tc = op->o_callback->sc_private;
+
+ /* Don't let the op complete while we're gathering data */
+ if ( rs->sr_type == REP_RESULT && ( tc->step & USE_LIST ))
+ return 0;
+
+ if(rs->sr_type != REP_SEARCH || !rs->sr_entry)
+ return(SLAP_CB_CONTINUE);
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_search_cb: %s\n",
+ rs->sr_entry->e_name.bv_val );
+
+ op->ors_slimit = tc->slimit + ( tc->slimit > 0 ? 1 : 0 );
+ if ( op->ors_attrs == slap_anlist_all_attributes ) {
+ op->ors_attrs = tc->attrs;
+ rs->sr_attrs = tc->attrs;
+ rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs );
+ }
+
+ on = tc->on;
+ ov = on->on_bi.bi_private;
+
+ db = op->o_bd;
+ re = NULL;
+
+ /* If we have local, get remote */
+ if ( tc->step & LCL_SIDE ) {
+ le = rs->sr_entry;
+ /* If entry is already on list, use it */
+ if ( tc->step & USE_LIST ) {
+ re = ldap_tavl_delete( &tc->list, le, entry_dn_cmp );
+ if ( re ) {
+ rs_flush_entry( op, rs, on );
+ rc = test_filter( op, re, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_flags |= REP_ENTRY_MUSTBEFREED;
+ rs->sr_entry = re;
+
+ if ( tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) {
+ return LDAP_SIZELIMIT_EXCEEDED;
+ }
+
+ return SLAP_CB_CONTINUE;
+ } else {
+ entry_free( re );
+ rs->sr_entry = NULL;
+ return 0;
+ }
+ }
+ }
+ op->o_bd = &ov->db;
+ rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &re );
+ if ( rc == LDAP_SUCCESS && re ) {
+ Entry *tmp = entry_dup( re );
+ be_entry_release_r( op, re );
+ re = tmp;
+ test_f = 1;
+ }
+ } else {
+ /* Else we have remote, get local */
+ op->o_bd = tc->db;
+ le = NULL;
+ rc = overlay_entry_get_ov(op, &rs->sr_entry->e_nname, NULL, NULL, 0, &le, on);
+ if ( rc == LDAP_SUCCESS && le ) {
+ re = entry_dup( rs->sr_entry );
+ rs_flush_entry( op, rs, on );
+ } else {
+ le = NULL;
+ }
+ }
+
+/*
+** if we got remote and local entry:
+** foreach local attr:
+** foreach remote attr:
+** if match, remote attr with local attr;
+** if new local, add to list;
+** append new local attrs to remote;
+**
+*/
+
+ if ( re && le ) {
+ for(ax = le->e_attrs; ax; ax = ax->a_next) {
+ for(a = re->e_attrs; a; a = a->a_next) {
+ if(a->a_desc == ax->a_desc) {
+ test_f = 1;
+ if(a->a_vals != a->a_nvals)
+ ber_bvarray_free(a->a_nvals);
+ ber_bvarray_free(a->a_vals);
+ ber_bvarray_dup_x( &a->a_vals, ax->a_vals, NULL );
+ if ( ax->a_vals == ax->a_nvals ) {
+ a->a_nvals = a->a_vals;
+ } else {
+ ber_bvarray_dup_x( &a->a_nvals, ax->a_nvals, NULL );
+ }
+ break;
+ }
+ }
+ if(a) continue;
+ an = attr_dup(ax);
+ an->a_next = as;
+ as = an;
+ }
+ /* Dispose of local entry */
+ if ( tc->step & LCL_SIDE ) {
+ rs_flush_entry(op, rs, on);
+ } else {
+ overlay_entry_release_ov(op, le, 0, on);
+ }
+
+ /* literally append, so locals are always last */
+ if(as) {
+ if(re->e_attrs) {
+ for(ax = re->e_attrs; ax->a_next; ax = ax->a_next);
+ ax->a_next = as;
+ } else {
+ re->e_attrs = as;
+ }
+ }
+ /* If both filters, save entry for later */
+ if ( tc->step == (USE_LIST|RMT_SIDE) ) {
+ ldap_tavl_insert( &tc->list, re, entry_dn_cmp, ldap_avl_dup_error );
+ rs->sr_entry = NULL;
+ rc = 0;
+ } else {
+ /* send it now */
+ rs->sr_entry = re;
+ rs->sr_flags |= REP_ENTRY_MUSTBEFREED;
+ if ( test_f ) {
+ rc = test_filter( op, rs->sr_entry, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rc = SLAP_CB_CONTINUE;
+ } else {
+ rc = 0;
+ }
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+ } else if ( le ) {
+ /* Only a local entry: remote was deleted
+ * Ought to delete the local too...
+ */
+ rc = 0;
+ } else if ( tc->step & USE_LIST ) {
+ /* Only a remote entry, but both filters:
+ * Test the complete filter
+ */
+ rc = test_filter( op, rs->sr_entry, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rc = SLAP_CB_CONTINUE;
+ } else {
+ rc = 0;
+ }
+ } else {
+ /* Only a remote entry, only remote filter:
+ * just pass thru
+ */
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ op->o_bd = db;
+
+ if ( rc == SLAP_CB_CONTINUE && tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) {
+ return LDAP_SIZELIMIT_EXCEEDED;
+ }
+
+ return rc;
+}
+
+/* Dup the filter, excluding invalid elements */
+static Filter *
+trans_filter_dup(Operation *op, Filter *f, AttributeName *an)
+{
+ Filter *n = NULL;
+
+ if ( !f )
+ return NULL;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case SLAPD_FILTER_COMPUTED:
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_result = f->f_result;
+ n->f_next = NULL;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ if ( ad_inlist( f->f_desc, an )) {
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_desc = f->f_desc;
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_EXT:
+ if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) {
+ AttributeAssertion *nava;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+
+ nava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx );
+ *nava = *f->f_ava;
+ n->f_ava = nava;
+
+ ber_dupbv_x( &n->f_av_value, &f->f_av_value, op->o_tmpmemctx );
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) {
+ SubstringsAssertion *nsub;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+
+ nsub = op->o_tmpalloc( sizeof(SubstringsAssertion), op->o_tmpmemctx );
+ *nsub = *f->f_sub;
+ n->f_sub = nsub;
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ))
+ ber_dupbv_x( &n->f_sub_initial, &f->f_sub_initial, op->o_tmpmemctx );
+
+ ber_bvarray_dup_x( &n->f_sub_any, f->f_sub_any, op->o_tmpmemctx );
+
+ if ( !BER_BVISNULL( &f->f_sub_final ))
+ ber_dupbv_x( &n->f_sub_final, &f->f_sub_final, op->o_tmpmemctx );
+
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ Filter **p;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_next = NULL;
+
+ for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) {
+ *p = trans_filter_dup( op, f, an );
+ if ( !*p )
+ continue;
+ p = &(*p)->f_next;
+ }
+ /* nothing valid in this list */
+ if ( !n->f_list ) {
+ op->o_tmpfree( n, op->o_tmpmemctx );
+ return NULL;
+ }
+ /* Only 1 element in this list */
+ if ((n->f_choice & SLAPD_FILTER_MASK) != LDAP_FILTER_NOT &&
+ !n->f_list->f_next ) {
+ f = n->f_list;
+ *n = *f;
+ op->o_tmpfree( f, op->o_tmpmemctx );
+ }
+ break;
+ }
+ }
+ return n;
+}
+
+static void
+trans_filter_free( Operation *op, Filter *f )
+{
+ Filter *n, *p, *next;
+
+ f->f_choice &= SLAPD_FILTER_MASK;
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ /* Free in reverse order */
+ n = NULL;
+ for ( p = f->f_list; p; p = next ) {
+ next = p->f_next;
+ p->f_next = n;
+ n = p;
+ }
+ for ( p = n; p; p = next ) {
+ next = p->f_next;
+ trans_filter_free( op, p );
+ }
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ op->o_tmpfree( f->f_av_value.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( f->f_ava, op->o_tmpmemctx );
+ break;
+ default:
+ break;
+ }
+ op->o_tmpfree( f, op->o_tmpmemctx );
+}
+
+static int
+translucent_search_cleanup( Operation *op, SlapReply *rs )
+{
+ trans_ctx *tc = op->o_callback->sc_private;
+
+ op->ors_filter = tc->orig;
+ return LDAP_SUCCESS;
+}
+
+/*
+** translucent_search()
+** search via captive backend;
+** override results with any local data;
+**
+*/
+
+static int translucent_search(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ trans_ctx tc;
+ Filter *fl, *fr;
+ struct berval fbv;
+ int rc = 0;
+
+ if ( op->o_managedsait > SLAP_CONTROL_IGNORED )
+ return SLAP_CB_CONTINUE;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_search: <%s> %s\n",
+ op->o_req_dn.bv_val, op->ors_filterstr.bv_val );
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ fr = ov->remote ? trans_filter_dup( op, op->ors_filter, ov->remote ) : NULL;
+ fl = ov->local ? trans_filter_dup( op, op->ors_filter, ov->local ) : NULL;
+ cb.sc_response = (slap_response *) translucent_search_cb;
+ cb.sc_cleanup = (slap_response *) translucent_search_cleanup;
+ cb.sc_private = &tc;
+
+ ov->db.be_acl = op->o_bd->be_acl;
+ tc.db = op->o_bd;
+ tc.on = on;
+ tc.orig = op->ors_filter;
+ tc.list = NULL;
+ tc.step = 0;
+ tc.slimit = op->ors_slimit;
+ tc.attrs = NULL;
+ fbv = op->ors_filterstr;
+
+ if ( fr || !fl ) {
+ Operation op2;
+ Opheader oh;
+
+ op2 = *op;
+ oh = *op->o_hdr;
+ oh.oh_conn = op->o_conn;
+ oh.oh_connid = op->o_connid;
+ op2.o_bd = &ov->db;
+ op2.o_hdr = &oh;
+ op2.o_extra = op->o_extra;
+ op2.o_callback = &cb;
+
+ tc.attrs = op->ors_attrs;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_all_attributes;
+ tc.step |= RMT_SIDE;
+ if ( fl ) {
+ tc.step |= USE_LIST;
+ op->ors_filter = fr;
+ filter2bv_x( op, fr, &op2.ors_filterstr );
+ }
+ rc = ov->db.bd_info->bi_op_search( &op2, rs );
+ if ( op->ors_attrs == slap_anlist_all_attributes )
+ op->ors_attrs = tc.attrs;
+ if ( fl ) {
+ op->o_tmpfree( op2.ors_filterstr.bv_val, op2.o_tmpmemctx );
+ }
+ }
+
+ cb.sc_next = op->o_callback;
+ op->o_callback = &cb;
+
+ if ( fl && !rc ) {
+ tc.step |= LCL_SIDE;
+ op->ors_filter = fl;
+ filter2bv_x( op, fl, &op->ors_filterstr );
+ rc = overlay_op_walk( op, rs, op_search, on->on_info, on->on_next );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ op->ors_filterstr = fbv;
+ op->o_callback = cb.sc_next;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs );
+
+ /* Send out anything remaining on the list and finish */
+ if ( tc.step & USE_LIST ) {
+ if ( tc.list ) {
+ TAvlnode *av;
+
+ av = ldap_tavl_end( tc.list, TAVL_DIR_LEFT );
+ while ( av ) {
+ rs->sr_entry = av->avl_data;
+ if ( rc == LDAP_SUCCESS && LDAP_COMPARE_TRUE ==
+ test_filter( op, rs->sr_entry, op->ors_filter ))
+ {
+ rs->sr_flags = REP_ENTRY_MUSTBEFREED;
+ rc = send_search_entry( op, rs );
+ } else {
+ entry_free( rs->sr_entry );
+ }
+ av = ldap_tavl_next( av, TAVL_DIR_RIGHT );
+ }
+ ldap_tavl_free( tc.list, NULL );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ }
+ send_ldap_result( op, rs );
+ }
+
+ op->ors_slimit = tc.slimit;
+
+ /* Free in reverse order */
+ if ( fl )
+ trans_filter_free( op, fl );
+ if ( fr )
+ trans_filter_free( op, fr );
+
+ return rc;
+}
+
+
+/*
+** translucent_bind()
+** pass bind request to captive backend;
+**
+*/
+
+static int translucent_bind(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ BackendDB *db;
+ slap_callback sc = { 0 }, *save_cb;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "translucent_bind: <%s> method %d\n",
+ op->o_req_dn.bv_val, op->orb_method );
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ if (ov->bind_local) {
+ sc.sc_response = slap_null_cb;
+ save_cb = op->o_callback;
+ op->o_callback = &sc;
+ }
+
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_op_bind(op, rs);
+ op->o_bd = db;
+
+ if (ov->bind_local) {
+ op->o_callback = save_cb;
+ if (rc != LDAP_SUCCESS) {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** translucent_connection_destroy()
+** pass disconnect notification to captive backend;
+**
+*/
+
+static int translucent_connection_destroy(BackendDB *be, Connection *conn) {
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "translucent_connection_destroy\n" );
+
+ rc = ov->db.bd_info->bi_connection_destroy(&ov->db, conn);
+
+ return(rc);
+}
+
+/*
+** translucent_db_config()
+** pass config directives to captive backend;
+** parse unrecognized directives ourselves;
+**
+*/
+
+static int translucent_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_config: %s\n",
+ argc ? argv[0] : "" );
+
+ /* Something for the captive database? */
+ if ( ov->db.bd_info && ov->db.bd_info->bi_db_config )
+ return ov->db.bd_info->bi_db_config( &ov->db, fname, lineno,
+ argc, argv );
+ return SLAP_CONF_UNKNOWN;
+}
+
+/*
+** translucent_db_init()
+** initialize the captive backend;
+**
+*/
+
+static int translucent_db_init(BackendDB *be, ConfigReply *cr) {
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_init\n" );
+
+ ov = ch_calloc(1, sizeof(translucent_info));
+ on->on_bi.bi_private = ov;
+ ov->db = *be;
+ ov->db.be_private = NULL;
+ ov->defer_db_open = 1;
+
+ if ( !backend_db_init( "ldap", &ov->db, -1, NULL )) {
+ Debug( LDAP_DEBUG_CONFIG, "translucent: unable to open captive back-ldap\n" );
+ return 1;
+ }
+ SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NO_SCHEMA_CHECK;
+ SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NOLASTMOD;
+
+ return 0;
+}
+
+/*
+** translucent_db_open()
+** if the captive backend has an open() method, call it;
+**
+*/
+
+static int translucent_db_open(BackendDB *be, ConfigReply *cr) {
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_open\n" );
+
+ /* need to inherit something from the original database... */
+ ov->db.be_def_limit = be->be_def_limit;
+ ov->db.be_limits = be->be_limits;
+ ov->db.be_acl = be->be_acl;
+ ov->db.be_dfltaccess = be->be_dfltaccess;
+
+ if ( ov->defer_db_open )
+ return 0;
+
+ rc = backend_startup_one( &ov->db, cr );
+
+ if(rc) Debug(LDAP_DEBUG_TRACE,
+ "translucent: bi_db_open() returned error %d\n", rc );
+
+ return(rc);
+}
+
+/*
+** translucent_db_close()
+** if the captive backend has a close() method, call it
+**
+*/
+
+static int
+translucent_db_close( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_close\n" );
+
+ if ( ov && ov->db.bd_info && ov->db.bd_info->bi_db_close ) {
+ rc = ov->db.bd_info->bi_db_close(&ov->db, NULL);
+ }
+
+ return(rc);
+}
+
+/*
+** translucent_db_destroy()
+** if the captive backend has a db_destroy() method, call it;
+** free any config data
+**
+*/
+
+static int
+translucent_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_destroy\n" );
+
+ if ( ov ) {
+ if ( ov->remote )
+ anlist_free( ov->remote, 1, NULL );
+ if ( ov->local )
+ anlist_free( ov->local, 1, NULL );
+ if ( ov->db.be_private != NULL ) {
+ backend_stopdown_one( &ov->db );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &ov->db.be_pcsn_st.be_pcsn_mutex );
+ ch_free(ov);
+ on->on_bi.bi_private = NULL;
+ }
+
+ return(rc);
+}
+
+/*
+** translucent_initialize()
+** initialize the slap_overinst with our entry points;
+**
+*/
+
+int translucent_initialize() {
+
+ int rc;
+
+ /* olcDatabaseDummy is defined in slapd, and Windows
+ will not let us initialize a struct element with a data pointer
+ from another library, so we have to initialize this element
+ "by hand". */
+ translucentocs[1].co_table = olcDatabaseDummy;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_initialize\n" );
+
+ translucent.on_bi.bi_type = "translucent";
+ translucent.on_bi.bi_db_init = translucent_db_init;
+ translucent.on_bi.bi_db_config = translucent_db_config;
+ translucent.on_bi.bi_db_open = translucent_db_open;
+ translucent.on_bi.bi_db_close = translucent_db_close;
+ translucent.on_bi.bi_db_destroy = translucent_db_destroy;
+ translucent.on_bi.bi_op_bind = translucent_bind;
+ translucent.on_bi.bi_op_add = translucent_add;
+ translucent.on_bi.bi_op_modify = translucent_modify;
+ translucent.on_bi.bi_op_modrdn = translucent_modrdn;
+ translucent.on_bi.bi_op_delete = translucent_delete;
+ translucent.on_bi.bi_op_search = translucent_search;
+ translucent.on_bi.bi_op_compare = translucent_compare;
+ translucent.on_bi.bi_connection_destroy = translucent_connection_destroy;
+ translucent.on_bi.bi_extended = translucent_exop;
+
+ translucent.on_bi.bi_cf_ocs = translucentocs;
+ rc = config_register_schema ( translucentcfg, translucentocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&translucent));
+}
+
+#if SLAPD_OVER_TRANSLUCENT == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return translucent_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_TRANSLUCENT */
diff --git a/servers/slapd/overlays/unique.c b/servers/slapd/overlays/unique.c
new file mode 100644
index 0000000..561d86d
--- /dev/null
+++ b/servers/slapd/overlays/unique.c
@@ -0,0 +1,1549 @@
+/* unique.c - attribute uniqueness module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004,2006-2007 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corporation for
+ * inclusion in OpenLDAP Software, with subsequent enhancements by
+ * Emily Backes at Symas Corporation. This work was sponsored by
+ * Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_UNIQUE
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+
+#define UNIQUE_DEFAULT_URI ("ldap:///??sub")
+
+static slap_overinst unique;
+
+typedef struct unique_attrs_s {
+ struct unique_attrs_s *next; /* list of attrs */
+ AttributeDescription *attr;
+} unique_attrs;
+
+typedef struct unique_domain_uri_s {
+ struct unique_domain_uri_s *next;
+ struct berval dn;
+ struct berval ndn;
+ struct berval filter;
+ Filter *f;
+ struct unique_attrs_s *attrs;
+ int scope;
+} unique_domain_uri;
+
+typedef struct unique_domain_s {
+ struct unique_domain_s *next;
+ struct berval domain_spec;
+ struct unique_domain_uri_s *uri;
+ char ignore; /* polarity of attributes */
+ char strict; /* null considered unique too */
+ char serial; /* serialize execution */
+} unique_domain;
+
+typedef struct unique_data_s {
+ struct unique_domain_s *domains;
+ struct unique_domain_s *legacy;
+ char legacy_strict_set;
+ ldap_pvt_thread_mutex_t serial_mutex;
+} unique_data;
+
+typedef struct unique_counter_s {
+ struct berval *ndn;
+ int count;
+} unique_counter;
+
+enum {
+ UNIQUE_BASE = 1,
+ UNIQUE_IGNORE,
+ UNIQUE_ATTR,
+ UNIQUE_STRICT,
+ UNIQUE_URI,
+};
+
+static ConfigDriver unique_cf_base;
+static ConfigDriver unique_cf_attrs;
+static ConfigDriver unique_cf_strict;
+static ConfigDriver unique_cf_uri;
+
+static ConfigTable uniquecfg[] = {
+ { "unique_base", "basedn", 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|UNIQUE_BASE,
+ unique_cf_base, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' "
+ "DESC 'Subtree for uniqueness searches' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "unique_ignore", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_IGNORE,
+ unique_cf_attrs, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' "
+ "DESC 'Attributes for which uniqueness shall not be enforced' "
+ "EQUALITY caseIgnoreMatch "
+ "ORDERING caseIgnoreOrderingMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "unique_attributes", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_ATTR,
+ unique_cf_attrs, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' "
+ "DESC 'Attributes for which uniqueness shall be enforced' "
+ "EQUALITY caseIgnoreMatch "
+ "ORDERING caseIgnoreOrderingMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "unique_strict", "on|off", 1, 2, 0, ARG_MAGIC|UNIQUE_STRICT,
+ unique_cf_strict, "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' "
+ "DESC 'Enforce uniqueness of null values' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "unique_uri", "ldapuri", 2, 3, 0, ARG_MAGIC|UNIQUE_URI,
+ unique_cf_uri, "( OLcfgOvAt:10.5 NAME 'olcUniqueURI' "
+ "DESC 'List of keywords and LDAP URIs for a uniqueness domain' "
+ "EQUALITY caseExactMatch "
+ "ORDERING caseExactOrderingMatch "
+ "SUBSTR caseExactSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs uniqueocs[] = {
+ { "( OLcfgOvOc:10.1 "
+ "NAME 'olcUniqueConfig' "
+ "DESC 'Attribute value uniqueness configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcUniqueBase $ olcUniqueIgnore $ "
+ "olcUniqueAttribute $ olcUniqueStrict $ "
+ "olcUniqueURI ) )",
+ Cft_Overlay, uniquecfg },
+ { NULL, 0, NULL }
+};
+
+static void
+unique_free_domain_uri ( unique_domain_uri *uri )
+{
+ unique_domain_uri *next_uri = NULL;
+ unique_attrs *attr, *next_attr = NULL;
+
+ while ( uri ) {
+ next_uri = uri->next;
+ ch_free ( uri->dn.bv_val );
+ ch_free ( uri->ndn.bv_val );
+ ch_free ( uri->filter.bv_val );
+ filter_free( uri->f );
+ attr = uri->attrs;
+ while ( attr ) {
+ next_attr = attr->next;
+ ch_free (attr);
+ attr = next_attr;
+ }
+ ch_free ( uri );
+ uri = next_uri;
+ }
+}
+
+/* free an entire stack of domains */
+static void
+unique_free_domain ( unique_domain *domain )
+{
+ unique_domain *next_domain = NULL;
+
+ while ( domain ) {
+ next_domain = domain->next;
+ ch_free ( domain->domain_spec.bv_val );
+ unique_free_domain_uri ( domain->uri );
+ ch_free ( domain );
+ domain = next_domain;
+ }
+}
+
+static int
+unique_new_domain_uri ( unique_domain_uri **urip,
+ const LDAPURLDesc *url_desc,
+ ConfigArgs *c )
+{
+ int i, rc = LDAP_SUCCESS;
+ unique_domain_uri *uri;
+ struct berval bv = {0, NULL};
+ BackendDB *be = (BackendDB *)c->be;
+ char ** attr_str;
+ AttributeDescription * ad;
+ const char * text;
+
+ uri = ch_calloc ( 1, sizeof ( unique_domain_uri ) );
+
+ if ( url_desc->lud_host && url_desc->lud_host[0] ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "host <%s> not allowed in URI",
+ url_desc->lud_host );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( url_desc->lud_dn && url_desc->lud_dn[0] ) {
+ ber_str2bv( url_desc->lud_dn, 0, 0, &bv );
+ rc = dnPrettyNormal( NULL,
+ &bv,
+ &uri->dn,
+ &uri->ndn,
+ NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid DN %d (%s)",
+ url_desc->lud_dn, rc, ldap_err2string( rc ));
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( !dnIsSuffix ( &uri->ndn, &be->be_nsuffix[0] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn <%s> is not a suffix of backend base dn <%s>",
+ uri->dn.bv_val,
+ be->be_nsuffix[0].bv_val );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( BER_BVISNULL( &be->be_rootndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slapo-unique needs a rootdn; "
+ "backend <%s> has none, YMMV.\n",
+ be->be_nsuffix[0].bv_val );
+ }
+ }
+
+ attr_str = url_desc->lud_attrs;
+ if ( attr_str ) {
+ for ( i=0; attr_str[i]; ++i ) {
+ unique_attrs * attr;
+ ad = NULL;
+ if ( slap_str2ad ( attr_str[i], &ad, &text )
+ == LDAP_SUCCESS) {
+ attr = ch_calloc ( 1,
+ sizeof ( unique_attrs ) );
+ attr->attr = ad;
+ attr->next = uri->attrs;
+ uri->attrs = attr;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: attribute: %s: %s",
+ attr_str[i], text );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ }
+ }
+
+ uri->scope = url_desc->lud_scope;
+ if ( !uri->scope ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: uri with base scope will always be unique");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if (url_desc->lud_filter) {
+ char *ptr;
+ uri->f = str2filter( url_desc->lud_filter );
+ if ( !uri->f ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: bad filter");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ /* make sure the strfilter is in normal form (ITS#5581) */
+ filter2bv( uri->f, &uri->filter );
+ ptr = strstr( uri->filter.bv_val, "(?=" /*)*/ );
+ if ( ptr != NULL && ptr <= ( uri->filter.bv_val - STRLENOF( "(?=" /*)*/ ) + uri->filter.bv_len ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: bad filter");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ }
+exit:
+ uri->next = *urip;
+ *urip = uri;
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ unique_free_domain_uri ( uri );
+ *urip = NULL;
+ }
+ return rc;
+}
+
+static int
+unique_new_domain_uri_basic ( unique_domain_uri **urip,
+ ConfigArgs *c )
+{
+ LDAPURLDesc *url_desc = NULL;
+ int rc;
+
+ rc = ldap_url_parse ( UNIQUE_DEFAULT_URI, &url_desc );
+ if ( rc ) return rc;
+ rc = unique_new_domain_uri ( urip, url_desc, c );
+ ldap_free_urldesc ( url_desc );
+ return rc;
+}
+
+/* if *domain is non-null, it's pushed down the stack.
+ * note that the entire stack is freed if there is an error,
+ * so build added domains in a separate stack before adding them
+ *
+ * domain_specs look like
+ *
+ * [strict ][ignore ][serialize ]uri[[ uri]...]
+ * e.g. "ldap:///ou=foo,o=bar?uid?sub ldap:///ou=baz,o=bar?uid?sub"
+ * "strict ldap:///ou=accounts,o=bar?uid,uidNumber?one"
+ * etc
+ *
+ * so finally strictness is per-domain
+ * but so is ignore-state, and that would be better as a per-url thing
+ */
+static int
+unique_new_domain ( unique_domain **domainp,
+ char *domain_spec,
+ ConfigArgs *c )
+{
+ char *uri_start;
+ int rc = LDAP_SUCCESS;
+ int uri_err = 0;
+ unique_domain * domain;
+ LDAPURLDesc *url_desc, *url_descs = NULL;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_new_domain <%s>\n",
+ domain_spec );
+
+ domain = ch_calloc ( 1, sizeof (unique_domain) );
+ ber_str2bv( domain_spec, 0, 1, &domain->domain_spec );
+
+ uri_start = domain_spec;
+ if ( strncasecmp ( uri_start, "ignore ",
+ STRLENOF( "ignore " ) ) == 0 ) {
+ domain->ignore = 1;
+ uri_start += STRLENOF( "ignore " );
+ }
+ if ( strncasecmp ( uri_start, "serialize ",
+ STRLENOF( "serialize " ) ) == 0 ) {
+ domain->serial = 1;
+ uri_start += STRLENOF( "serialize " );
+ }
+ if ( strncasecmp ( uri_start, "strict ",
+ STRLENOF( "strict " ) ) == 0 ) {
+ domain->strict = 1;
+ uri_start += STRLENOF( "strict " );
+ if ( !domain->ignore
+ && strncasecmp ( uri_start, "ignore ",
+ STRLENOF( "ignore " ) ) == 0 ) {
+ domain->ignore = 1;
+ uri_start += STRLENOF( "ignore " );
+ }
+ }
+ rc = ldap_url_parselist_ext ( &url_descs, uri_start, " ", 0 );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid ldap urilist",
+ uri_start );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ for ( url_desc = url_descs;
+ url_desc;
+ url_desc = url_desc->lud_next ) {
+ rc = unique_new_domain_uri ( &domain->uri,
+ url_desc,
+ c );
+ if ( rc ) {
+ rc = ARG_BAD_CONF;
+ uri_err = 1;
+ goto exit;
+ }
+ }
+
+exit:
+ if ( url_descs ) ldap_free_urldesc ( url_descs );
+ domain->next = *domainp;
+ *domainp = domain;
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ unique_free_domain ( domain );
+ *domainp = NULL;
+ }
+ return rc;
+}
+
+static int
+unique_cf_base( ConfigArgs *c )
+{
+ BackendDB *be = (BackendDB *)c->be;
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ int rc = ARG_BAD_CONF;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ rc = 0;
+ if ( legacy && legacy->uri && legacy->uri->dn.bv_val ) {
+ rc = value_add_one ( &c->rvalue_vals,
+ &legacy->uri->dn );
+ if ( rc ) return rc;
+ rc = value_add_one ( &c->rvalue_nvals,
+ &legacy->uri->ndn );
+ if ( rc ) return rc;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ assert ( legacy && legacy->uri && legacy->uri->dn.bv_val );
+ rc = 0;
+ ch_free ( legacy->uri->dn.bv_val );
+ ch_free ( legacy->uri->ndn.bv_val );
+ BER_BVZERO( &legacy->uri->dn );
+ BER_BVZERO( &legacy->uri->ndn );
+ if ( !legacy->uri->attrs ) {
+ unique_free_domain_uri ( legacy->uri );
+ legacy->uri = NULL;
+ }
+ if ( !legacy->uri && !private->legacy_strict_set ) {
+ unique_free_domain ( legacy );
+ private->legacy = legacy = NULL;
+ }
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !dnIsSuffix ( &c->value_ndn,
+ &be->be_nsuffix[0] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn is not a suffix of backend base" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ if ( !legacy->uri )
+ unique_new_domain_uri_basic ( &legacy->uri, c );
+ ch_free ( legacy->uri->dn.bv_val );
+ ch_free ( legacy->uri->ndn.bv_val );
+ legacy->uri->dn = c->value_dn;
+ legacy->uri->ndn = c->value_ndn;
+ rc = 0;
+ break;
+ default:
+ abort();
+ }
+
+ if ( rc ) {
+ ch_free( c->value_dn.bv_val );
+ BER_BVZERO( &c->value_dn );
+ ch_free( c->value_ndn.bv_val );
+ BER_BVZERO( &c->value_ndn );
+ }
+
+ return rc;
+}
+
+static int
+unique_cf_attrs( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_attrs *new_attrs = NULL;
+ unique_attrs *attr, *next_attr, *reverse_attrs;
+ unique_attrs **attrp;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( legacy
+ && (c->type == UNIQUE_IGNORE) == legacy->ignore
+ && legacy->uri )
+ for ( attr = legacy->uri->attrs;
+ attr;
+ attr = attr->next )
+ value_add_one( &c->rvalue_vals,
+ &attr->attr->ad_cname );
+ rc = 0;
+ break;
+ case LDAP_MOD_DELETE:
+ if ( legacy
+ && (c->type == UNIQUE_IGNORE) == legacy->ignore
+ && legacy->uri
+ && legacy->uri->attrs) {
+ if ( c->valx < 0 ) { /* delete all */
+ for ( attr = legacy->uri->attrs;
+ attr;
+ attr = next_attr ) {
+ next_attr = attr->next;
+ ch_free ( attr );
+ }
+ legacy->uri->attrs = NULL;
+ } else { /* delete by index */
+ attrp = &legacy->uri->attrs;
+ for ( i=0; i < c->valx; ++i )
+ attrp = &(*attrp)->next;
+ attr = *attrp;
+ *attrp = attr->next;
+ ch_free (attr);
+ }
+ if ( !legacy->uri->attrs
+ && !legacy->uri->dn.bv_val ) {
+ unique_free_domain_uri ( legacy->uri );
+ legacy->uri = NULL;
+ }
+ if ( !legacy->uri && !private->legacy_strict_set ) {
+ unique_free_domain ( legacy );
+ private->legacy = legacy = NULL;
+ }
+ }
+ rc = 0;
+ break;
+ case LDAP_MOD_ADD:
+ if ( c->argc > 2 ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "unique config: "
+ "Supplying multiple names in a single %s value is unsupported "
+ "and will be disallowed in a future version\n",
+ c->argv[0] );
+ }
+ /* FALLTHRU */
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( legacy
+ && legacy->uri
+ && legacy->uri->attrs
+ && (c->type == UNIQUE_IGNORE) != legacy->ignore ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set both attrs and ignore-attrs" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ if ( !legacy->uri )
+ unique_new_domain_uri_basic ( &legacy->uri, c );
+ rc = 0;
+ for ( i=1; c->argv[i]; ++i ) {
+ AttributeDescription * ad = NULL;
+ const char * text;
+ if ( slap_str2ad ( c->argv[i], &ad, &text )
+ == LDAP_SUCCESS) {
+
+ attr = ch_calloc ( 1,
+ sizeof ( unique_attrs ) );
+ attr->attr = ad;
+ attr->next = new_attrs;
+ new_attrs = attr;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: attribute: %s: %s",
+ c->argv[i], text );
+ for ( attr = new_attrs;
+ attr;
+ attr=next_attr ) {
+ next_attr = attr->next;
+ ch_free ( attr );
+ }
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ }
+ if ( rc ) break;
+
+ /* (nconc legacy->uri->attrs (nreverse new_attrs)) */
+ reverse_attrs = NULL;
+ for ( attr = new_attrs;
+ attr;
+ attr = next_attr ) {
+ next_attr = attr->next;
+ attr->next = reverse_attrs;
+ reverse_attrs = attr;
+ }
+ for ( attrp = &legacy->uri->attrs;
+ *attrp;
+ attrp = &(*attrp)->next ) ;
+ *attrp = reverse_attrs;
+
+ legacy->ignore = ( c->type == UNIQUE_IGNORE );
+ break;
+ default:
+ abort();
+ }
+
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ }
+ return rc;
+}
+
+static int
+unique_cf_strict( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ int rc = ARG_BAD_CONF;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ /* We process the boolean manually instead of using
+ * ARG_ON_OFF so that we can three-state it;
+ * olcUniqueStrict is either TRUE, FALSE, or missing,
+ * and missing is necessary to add olcUniqueURIs...
+ */
+ if ( private->legacy_strict_set ) {
+ struct berval bv = legacy->strict ? slap_true_bv : slap_false_bv;
+ value_add_one ( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ break;
+ case LDAP_MOD_DELETE:
+ if ( legacy ) {
+ legacy->strict = 0;
+ if ( ! legacy->uri ) {
+ unique_free_domain ( legacy );
+ private->legacy = NULL;
+ }
+ }
+ private->legacy_strict_set = 0;
+ rc = 0;
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( ! legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ /* ... not using ARG_ON_OFF makes this necessary too */
+ assert ( c->argc == 2 );
+ legacy->strict = (strcasecmp ( c->argv[1], "TRUE" ) == 0);
+ private->legacy_strict_set = 1;
+ rc = 0;
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+unique_cf_uri( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain = NULL, **domainp = NULL;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ for ( domain = domains;
+ domain;
+ domain = domain->next ) {
+ rc = value_add_one ( &c->rvalue_vals,
+ &domain->domain_spec );
+ if ( rc ) break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx < 0 ) { /* delete them all! */
+ unique_free_domain ( domains );
+ private->domains = NULL;
+ } else { /* delete just one */
+ domainp = &private->domains;
+ for ( i=0; i < c->valx && *domainp; ++i )
+ domainp = &(*domainp)->next;
+
+ /* If *domainp is null, we walked off the end
+ * of the list. This happens when back-config
+ * and the overlay are out-of-sync, like when
+ * rejecting changes before ITS#4752 gets
+ * fixed.
+ *
+ * This should never happen, but will appear
+ * if you backport this version of
+ * slapo-unique without the config-undo fixes
+ *
+ * test024 Will hit this case in such a
+ * situation.
+ */
+ assert (*domainp != NULL);
+
+ domain = *domainp;
+ *domainp = domain->next;
+ domain->next = NULL;
+ unique_free_domain ( domain );
+ }
+ rc = 0;
+ break;
+
+ case SLAP_CONFIG_ADD: /* fallthru */
+ case LDAP_MOD_ADD:
+ if ( legacy ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set Uri when legacy attrs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ rc = 0;
+ if ( c->line ) rc = unique_new_domain ( &domain, c->line, c );
+ else rc = unique_new_domain ( &domain, c->argv[1], c );
+ if ( rc ) break;
+ assert ( domain->next == NULL );
+ for ( domainp = &private->domains;
+ *domainp;
+ domainp = &(*domainp)->next ) ;
+ *domainp = domain;
+
+ break;
+
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+/*
+** allocate new unique_data;
+** initialize, copy basedn;
+** store in on_bi.bi_private;
+**
+*/
+
+static int
+unique_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ unique_data *private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n" );
+
+ private = ch_calloc ( 1, sizeof ( unique_data ) );
+ ldap_pvt_thread_mutex_init( &private->serial_mutex );
+ on->on_bi.bi_private = private;
+
+ return 0;
+}
+
+static int
+unique_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ unique_data *private = on->on_bi.bi_private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n" );
+
+ if ( private ) {
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+
+ unique_free_domain ( domains );
+ unique_free_domain ( legacy );
+ ldap_pvt_thread_mutex_destroy( &private->serial_mutex );
+ ch_free ( private );
+ on->on_bi.bi_private = NULL;
+ }
+
+ return 0;
+}
+
+
+/*
+** search callback
+** if this is a REP_SEARCH, count++;
+**
+*/
+
+static int count_attr_cb(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ unique_counter *uc;
+
+ /* because you never know */
+ if(!op || !rs) return(0);
+
+ /* Only search entries are interesting */
+ if(rs->sr_type != REP_SEARCH) return(0);
+
+ uc = op->o_callback->sc_private;
+
+ /* Ignore the current entry */
+ if ( dn_match( uc->ndn, &rs->sr_entry->e_nname )) return(0);
+
+ Debug(LDAP_DEBUG_TRACE, "==> count_attr_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN" );
+
+ uc->count++;
+
+ return(0);
+}
+
+/* count the length of one attribute ad
+ * (and all of its values b)
+ * in the proposed filter
+ */
+static int
+count_filter_len(
+ unique_domain *domain,
+ unique_domain_uri *uri,
+ AttributeDescription *ad,
+ BerVarray b
+)
+{
+ unique_attrs *attr;
+ int i;
+ int ks = 0;
+
+ while ( !is_at_operational( ad->ad_type ) ) {
+ if ( uri->attrs ) {
+ for ( attr = uri->attrs; attr; attr = attr->next ) {
+ if ( ad == attr->attr ) {
+ break;
+ }
+ }
+ if ( ( domain->ignore && attr )
+ || (!domain->ignore && !attr )) {
+ break;
+ }
+ }
+ if ( b && b[0].bv_val ) {
+ for (i = 0; b[i].bv_val; i++ ) {
+ /* note: make room for filter escaping... */
+ ks += ( 3 * b[i].bv_len ) + ad->ad_cname.bv_len + STRLENOF( "(=)" );
+ }
+ } else if ( domain->strict ) {
+ ks += ad->ad_cname.bv_len + STRLENOF( "(=*)" ); /* (attr=*) */
+ }
+ break;
+ }
+
+ return ks;
+}
+
+static char *
+build_filter(
+ unique_domain *domain,
+ unique_domain_uri *uri,
+ AttributeDescription *ad,
+ BerVarray b,
+ char *kp,
+ int ks,
+ void *ctx
+)
+{
+ unique_attrs *attr;
+ int i;
+
+ while ( !is_at_operational( ad->ad_type ) ) {
+ if ( uri->attrs ) {
+ for ( attr = uri->attrs; attr; attr = attr->next ) {
+ if ( ad == attr->attr ) {
+ break;
+ }
+ }
+ if ( ( domain->ignore && attr )
+ || (!domain->ignore && !attr )) {
+ break;
+ }
+ }
+ if ( b && b[0].bv_val ) {
+ for ( i = 0; b[i].bv_val; i++ ) {
+ struct berval bv;
+ int len;
+
+ ldap_bv2escaped_filter_value_x( &b[i], &bv, 1, ctx );
+ if (!b[i].bv_len)
+ bv.bv_val = b[i].bv_val;
+ len = snprintf( kp, ks, "(%s=%s)", ad->ad_cname.bv_val, bv.bv_val );
+ assert( len >= 0 && len < ks );
+ kp += len;
+ if ( bv.bv_val != b[i].bv_val ) {
+ ber_memfree_x( bv.bv_val, ctx );
+ }
+ }
+ } else if ( domain->strict ) {
+ int len;
+ len = snprintf( kp, ks, "(%s=*)", ad->ad_cname.bv_val );
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ break;
+ }
+ return kp;
+}
+
+static int
+unique_search(
+ Operation *op,
+ Operation *nop,
+ struct berval * dn,
+ int scope,
+ SlapReply *rs,
+ struct berval *key
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ SlapReply nrs = { REP_RESULT };
+ slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */
+ unique_counter uq = { NULL, 0 };
+ int rc;
+ char *errmsg;
+ int errmsgsize;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key->bv_val );
+
+ nop->ors_filter = str2filter_x(nop, key->bv_val);
+ if(nop->ors_filter == NULL) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_OTHER,
+ "unique_search invalid filter");
+ return(rs->sr_err);
+ }
+
+ nop->ors_filterstr = *key;
+
+ cb.sc_response = (slap_response*)count_attr_cb;
+ cb.sc_private = &uq;
+ nop->o_callback = &cb;
+ nop->o_tag = LDAP_REQ_SEARCH;
+ nop->ors_scope = scope;
+ nop->ors_deref = LDAP_DEREF_NEVER;
+ nop->ors_limit = NULL;
+ nop->ors_slimit = SLAP_NO_LIMIT;
+ nop->ors_tlimit = SLAP_NO_LIMIT;
+ nop->ors_attrs = slap_anlist_no_attrs;
+ nop->ors_attrsonly = 1;
+ memset( nop->o_ctrlflag, 0, sizeof( nop->o_ctrlflag ));
+
+ uq.ndn = &op->o_req_ndn;
+
+ nop->o_req_ndn = *dn;
+ nop->o_ndn = op->o_bd->be_rootndn;
+
+ nop->o_bd = on->on_info->oi_origdb;
+ rc = nop->o_bd->be_search(nop, &nrs);
+ filter_free_x(nop, nop->ors_filter, 1);
+
+ if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, rc, "unique_search failed");
+ rc = rs->sr_err;
+ } else if(uq.count) {
+ Debug(LDAP_DEBUG_TRACE, "=> unique_search found %d records\n", uq.count );
+
+ errmsgsize = sizeof("non-unique attributes found with ") + key->bv_len;
+ errmsg = op->o_tmpalloc(errmsgsize, op->o_tmpmemctx);
+ snprintf( errmsg, errmsgsize, "non-unique attributes found with %s", key->bv_val );
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, errmsg);
+ op->o_tmpfree(errmsg, op->o_tmpmemctx);
+ rc = rs->sr_err;
+ } else {
+ Debug(LDAP_DEBUG_TRACE, "=> unique_search found no records\n" );
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ op->o_tmpfree( key->bv_val, op->o_tmpmemctx );
+
+ return(rc);
+}
+
+static int
+unique_unlock(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_callback *sc = op->o_callback;
+ unique_data *private = sc->sc_private;
+
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+static int
+unique_add(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Attribute *a;
+ char *key, *kp;
+ struct berval bvkey;
+ int rc = SLAP_CB_CONTINUE;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n",
+ op->o_req_dn.bv_val );
+
+ if ( be_shadow_update( op ) || (
+ get_relax(op) > SLAP_CONTROL_IGNORED
+ && access_allowed( op, op->ora_e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) ) {
+ return rc;
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ))
+ continue;
+
+ if ( uri->f ) {
+ if ( test_filter( NULL, op->ora_e, uri->f )
+ == LDAP_COMPARE_FALSE )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "==> unique_add_skip<%s>\n",
+ op->o_req_dn.bv_val );
+ continue;
+ }
+ }
+
+ if(!(a = op->ora_e->e_attrs)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "unique_add() got null op.ora_e.e_attrs");
+ rc = rs->sr_err;
+ break;
+
+ } else {
+ for(; a; a = a->a_next) {
+ ks += count_filter_len ( domain,
+ uri,
+ a->a_desc,
+ a->a_vals);
+ }
+ }
+
+ /* skip this domain-uri if it isn't involved */
+ if ( !ks ) continue;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ kp = build_filter(domain,
+ uri,
+ a->a_desc,
+ a->a_vals,
+ kp,
+ ks - ( kp - key ),
+ op->o_tmpmemctx);
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ return rc;
+}
+
+
+static int
+unique_modify(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Modifications *m;
+ Entry *e = NULL;
+ char *key, *kp;
+ struct berval bvkey;
+ int rc = SLAP_CB_CONTINUE;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n",
+ op->o_req_dn.bv_val );
+
+ if ( !op->orm_modlist ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_modify: got empty modify op\n" );
+ return rc;
+ }
+
+ if ( be_shadow_update( op ) ) {
+ return rc;
+ }
+ if ( get_relax(op) > SLAP_CONTROL_IGNORED
+ && overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) == LDAP_SUCCESS
+ && e
+ && access_allowed( op, e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ return rc;
+ }
+ if ( e ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ))
+ continue;
+
+ for ( m = op->orm_modlist; m; m = m->sml_next)
+ if ( (m->sml_op & LDAP_MOD_OP)
+ != LDAP_MOD_DELETE )
+ ks += count_filter_len
+ ( domain,
+ uri,
+ m->sml_desc,
+ m->sml_values);
+
+ /* skip this domain-uri if it isn't involved */
+ if ( !ks ) continue;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for(m = op->orm_modlist; m; m = m->sml_next)
+ if ( (m->sml_op & LDAP_MOD_OP)
+ != LDAP_MOD_DELETE )
+ kp = build_filter ( domain,
+ uri,
+ m->sml_desc,
+ m->sml_values,
+ kp,
+ ks - (kp - key),
+ op->o_tmpmemctx );
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ return rc;
+}
+
+
+static int
+unique_modrdn(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Entry *e = NULL;
+ char *key, *kp;
+ struct berval bvkey;
+ LDAPRDN newrdn;
+ struct berval bv[2];
+ int rc = SLAP_CB_CONTINUE;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n",
+ op->o_req_dn.bv_val, op->orr_newrdn.bv_val );
+
+ if ( be_shadow_update( op ) ) {
+ return rc;
+ }
+ if ( get_relax(op) > SLAP_CONTROL_IGNORED
+ && overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) == LDAP_SUCCESS
+ && e
+ && access_allowed( op, e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ return rc;
+ }
+ if ( e ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int i, len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )
+ && (!op->orr_nnewSup
+ || !dnIsSuffix( op->orr_nnewSup, &uri->ndn )))
+ continue;
+
+ if ( ldap_bv2rdn_x ( &op->oq_modrdn.rs_newrdn,
+ &newrdn,
+ (char **)&rs->sr_text,
+ LDAP_DN_FORMAT_LDAP,
+ op->o_tmpmemctx ) ) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "unknown type(s) used in RDN");
+ rc = rs->sr_err;
+ break;
+ }
+
+ rc = SLAP_CB_CONTINUE;
+ for ( i=0; newrdn[i]; i++) {
+ AttributeDescription *ad = NULL;
+ if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) {
+ ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
+ rs->sr_err = LDAP_INVALID_SYNTAX;
+ send_ldap_result( op, rs );
+ rc = rs->sr_err;
+ break;
+ }
+ newrdn[i]->la_private = ad;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+
+ bv[1].bv_val = NULL;
+ bv[1].bv_len = 0;
+
+ for ( i=0; newrdn[i]; i++ ) {
+ bv[0] = newrdn[i]->la_value;
+ ks += count_filter_len ( domain,
+ uri,
+ newrdn[i]->la_private,
+ bv);
+ }
+
+ /* skip this domain if it isn't involved */
+ if ( !ks ) continue;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for ( i=0; newrdn[i]; i++) {
+ bv[0] = newrdn[i]->la_value;
+ kp = build_filter ( domain,
+ uri,
+ newrdn[i]->la_private,
+ bv,
+ kp,
+ ks - (kp - key ),
+ op->o_tmpmemctx);
+ }
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ return rc;
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int
+unique_initialize()
+{
+ int rc;
+
+ /* statically declared just after the #includes at top */
+ memset (&unique, 0, sizeof(unique));
+
+ unique.on_bi.bi_type = "unique";
+ unique.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ unique.on_bi.bi_db_init = unique_db_init;
+ unique.on_bi.bi_db_destroy = unique_db_destroy;
+ unique.on_bi.bi_op_add = unique_add;
+ unique.on_bi.bi_op_modify = unique_modify;
+ unique.on_bi.bi_op_modrdn = unique_modrdn;
+
+ unique.on_bi.bi_cf_ocs = uniqueocs;
+ rc = config_register_schema( uniquecfg, uniqueocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&unique));
+}
+
+#if SLAPD_OVER_UNIQUE == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return unique_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_UNIQUE */
diff --git a/servers/slapd/overlays/valsort.c b/servers/slapd/overlays/valsort.c
new file mode 100644
index 0000000..3d998e2
--- /dev/null
+++ b/servers/slapd/overlays/valsort.c
@@ -0,0 +1,585 @@
+/* valsort.c - sort attribute values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * Portions copyright 2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software. This work was sponsored by Stanford University.
+ */
+
+/*
+ * This overlay sorts the values of multi-valued attributes when returning
+ * them in a search response.
+ */
+#include "portable.h"
+
+#ifdef SLAPD_OVER_VALSORT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+
+#define VALSORT_ASCEND 0
+#define VALSORT_DESCEND 1
+
+#define VALSORT_ALPHA 2
+#define VALSORT_NUMERIC 4
+
+#define VALSORT_WEIGHTED 8
+
+typedef struct valsort_info {
+ struct valsort_info *vi_next;
+ struct berval vi_dn;
+ AttributeDescription *vi_ad;
+ slap_mask_t vi_sort;
+} valsort_info;
+
+static int valsort_cid;
+
+static ConfigDriver valsort_cf_func;
+
+static ConfigTable valsort_cfats[] = {
+ { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC,
+ valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' "
+ "DESC 'Sorting rule for attribute under given DN' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs valsort_cfocs[] = {
+ { "( OLcfgOvOc:5.1 "
+ "NAME 'olcValSortConfig' "
+ "DESC 'Value Sorting configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcValSortAttr )",
+ Cft_Overlay, valsort_cfats },
+ { NULL }
+};
+
+static slap_verbmasks sorts[] = {
+ { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA },
+ { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA },
+ { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC },
+ { BER_BVC("numeric-descend"), VALSORT_DESCEND|VALSORT_NUMERIC },
+ { BER_BVC("weighted"), VALSORT_WEIGHTED },
+ { BER_BVNULL, 0 }
+};
+
+static Syntax *syn_numericString;
+
+static int
+valsort_cf_func(ConfigArgs *c) {
+ slap_overinst *on = (slap_overinst *)c->bi;
+ valsort_info vitmp, *vi, **vip;
+ const char *text = NULL;
+ int i, is_numeric;
+ struct berval bv = BER_BVNULL;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) {
+ struct berval bv2 = BER_BVNULL, bvret;
+ char *ptr;
+ int len;
+
+ len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 2;
+ i = vi->vi_sort;
+ if ( i & VALSORT_WEIGHTED ) {
+ enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 );
+ len += bv2.bv_len + 1;
+ i ^= VALSORT_WEIGHTED;
+ }
+ if ( i ) {
+ enum_to_verb( sorts, i, &bv );
+ len += bv.bv_len + 1;
+ }
+ bvret.bv_val = ch_malloc( len+1 );
+ bvret.bv_len = len;
+
+ ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val );
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val );
+ *ptr++ = '"';
+ if ( vi->vi_sort & VALSORT_WEIGHTED ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, bv2.bv_val );
+ }
+ if ( i ) {
+ *ptr++ = ' ';
+ strcpy( ptr, bv.bv_val );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bvret );
+ }
+ i = ( c->rvalue_vals != NULL ) ? 0 : 1;
+ return i;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ for ( vi = on->on_bi.bi_private; vi; vi = on->on_bi.bi_private ) {
+ on->on_bi.bi_private = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+ } else {
+ valsort_info **prev;
+
+ for (i=0, prev = (valsort_info **)&on->on_bi.bi_private,
+ vi = *prev; vi && i<c->valx;
+ prev = &vi->vi_next, vi = vi->vi_next, i++ );
+ (*prev)->vi_next = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+ return 0;
+ }
+ vitmp.vi_ad = NULL;
+ i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg), "<%s> %s", c->argv[0], text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ if ( is_at_single_value( vitmp.vi_ad->ad_type )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> %s is single-valued, ignoring", c->argv[0],
+ vitmp.vi_ad->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(0);
+ }
+ is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString ||
+ vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1
+ : 0;
+ ber_str2bv( c->argv[2], 0, 0, &bv );
+ i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to normalize DN", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[2] );
+ return(1);
+ }
+ i = verb_to_mask( c->argv[3], sorts );
+ if ( BER_BVISNULL( &sorts[i].word )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[3] );
+ return(1);
+ }
+ vitmp.vi_sort = sorts[i].mask;
+ if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) {
+ i = verb_to_mask( c->argv[4], sorts );
+ if ( BER_BVISNULL( &sorts[i].word )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[4] );
+ return(1);
+ }
+ vitmp.vi_sort |= sorts[i].mask;
+ }
+ if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> numeric sort specified for non-numeric syntax",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+
+ for ( vip = &on->on_bi.bi_private; *vip; vip = &(*vip)->vi_next )
+ /* Get to the end */ ;
+
+ vi = ch_malloc( sizeof(valsort_info) );
+ *vi = vitmp;
+ vi->vi_next = *vip;
+ *vip = vi;
+ return 0;
+}
+
+/* Use Insertion Sort algorithm on selected values */
+static void
+do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort )
+{
+ int i, j, gotnvals;
+ struct berval tmp, ntmp, *vals = NULL, *nvals;
+
+ gotnvals = (a->a_vals != a->a_nvals );
+
+ nvals = a->a_nvals + beg;
+ if ( gotnvals )
+ vals = a->a_vals + beg;
+
+ if ( sort & VALSORT_NUMERIC ) {
+ long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ),
+ idx;
+ for (i=0; i<num; i++)
+ numbers[i] = strtol( nvals[i].bv_val, NULL, 0 );
+
+ for (i=1; i<num; i++) {
+ idx = numbers[i];
+ ntmp = nvals[i];
+ if ( gotnvals ) tmp = vals[i];
+ j = i;
+ while ( j>0 ) {
+ int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx :
+ numbers[j-1] > idx;
+ if ( !cmp ) break;
+ numbers[j] = numbers[j-1];
+ nvals[j] = nvals[j-1];
+ if ( gotnvals ) vals[j] = vals[j-1];
+ j--;
+ }
+ numbers[j] = idx;
+ nvals[j] = ntmp;
+ if ( gotnvals ) vals[j] = tmp;
+ }
+ op->o_tmpfree( numbers, op->o_tmpmemctx );
+ } else {
+ for (i=1; i<num; i++) {
+ ntmp = nvals[i];
+ if ( gotnvals ) tmp = vals[i];
+ j = i;
+ while ( j>0 ) {
+ int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val );
+ cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0);
+ if ( !cmp ) break;
+
+ nvals[j] = nvals[j-1];
+ if ( gotnvals ) vals[j] = vals[j-1];
+ j--;
+ }
+ nvals[j] = ntmp;
+ if ( gotnvals ) vals[j] = tmp;
+ }
+ }
+}
+
+static int
+valsort_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on;
+ valsort_info *vi;
+ Attribute *a;
+
+ /* If this is not a search response, or it is a syncrepl response,
+ * or the valsort control wants raw results, pass thru unmodified.
+ */
+ if ( rs->sr_type != REP_SEARCH ||
+ ( _SCM(op->o_sync) > SLAP_CONTROL_IGNORED ) ||
+ ( op->o_ctrlflag[valsort_cid] & SLAP_CONTROL_DATA0))
+ return SLAP_CB_CONTINUE;
+
+ on = (slap_overinst *) op->o_bd->bd_info;
+ vi = on->on_bi.bi_private;
+
+ /* And we must have something configured */
+ if ( !vi ) return SLAP_CB_CONTINUE;
+
+ /* Find a rule whose baseDN matches this entry */
+ for (; vi; vi = vi->vi_next ) {
+ int i, n;
+
+ if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn ))
+ continue;
+
+ /* Find attr that this rule affects */
+ a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
+ if ( !a ) continue;
+
+ if ( rs_entry2modifiable( op, rs, on )) {
+ a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
+ }
+
+ n = a->a_numvals;
+ if ( vi->vi_sort & VALSORT_WEIGHTED ) {
+ int j, gotnvals;
+ long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx );
+
+ gotnvals = (a->a_vals != a->a_nvals );
+
+ for (i=0; i<n; i++) {
+ char *ptr = ber_bvchr( &a->a_nvals[i], '{' );
+ char *end = NULL;
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s "
+ "in entry %s\n", vi->vi_ad->ad_cname.bv_val,
+ rs->sr_entry->e_name.bv_val );
+ break;
+ }
+ index[i] = strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weights misformatted "
+ "in entry %s\n",
+ rs->sr_entry->e_name.bv_val );
+ break;
+ }
+ /* Strip out weights */
+ ptr = a->a_nvals[i].bv_val;
+ end++;
+ for (;*end;)
+ *ptr++ = *end++;
+ *ptr = '\0';
+ a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val;
+
+ if ( a->a_vals != a->a_nvals ) {
+ ptr = a->a_vals[i].bv_val;
+ end = ber_bvchr( &a->a_vals[i], '}' );
+ assert( end != NULL );
+ end++;
+ for (;*end;)
+ *ptr++ = *end++;
+ *ptr = '\0';
+ a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val;
+ }
+ }
+ /* An attr was missing weights here, ignore it */
+ if ( i<n ) {
+ op->o_tmpfree( index, op->o_tmpmemctx );
+ continue;
+ }
+ /* Insertion sort */
+ for ( i=1; i<n; i++) {
+ long idx = index[i];
+ struct berval tmp = a->a_vals[i], ntmp;
+ if ( gotnvals ) ntmp = a->a_nvals[i];
+ j = i;
+ while (( j>0 ) && (index[j-1] > idx )) {
+ index[j] = index[j-1];
+ a->a_vals[j] = a->a_vals[j-1];
+ if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
+ j--;
+ }
+ index[j] = idx;
+ a->a_vals[j] = tmp;
+ if ( gotnvals ) a->a_nvals[j] = ntmp;
+ }
+ /* Check for secondary sort */
+ if ( vi->vi_sort ^ VALSORT_WEIGHTED ) {
+ for ( i=0; i<n;) {
+ for (j=i+1; j<n; j++) {
+ if (index[i] != index[j])
+ break;
+ }
+ if( j-i > 1 )
+ do_sort( op, a, i, j-i, vi->vi_sort );
+ i = j;
+ }
+ }
+ op->o_tmpfree( index, op->o_tmpmemctx );
+ } else {
+ do_sort( op, a, 0, n, vi->vi_sort );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ valsort_info *vi = on->on_bi.bi_private;
+
+ Attribute *a;
+ int i;
+ char *ptr, *end;
+
+ /* See if any weighted sorting applies to this entry */
+ for ( ;vi;vi=vi->vi_next ) {
+ if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
+ continue;
+ if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
+ continue;
+ a = attr_find( op->ora_e->e_attrs, vi->vi_ad );
+ if ( !a )
+ continue;
+ for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) {
+ ptr = ber_bvchr(&a->a_vals[i], '{' );
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
+ vi->vi_ad->ad_cname.bv_val );
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight missing from attribute" );
+ return rs->sr_err;
+ }
+ strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
+ vi->vi_ad->ad_cname.bv_val );
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight is misformatted" );
+ return rs->sr_err;
+ }
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ valsort_info *vi = on->on_bi.bi_private;
+
+ Modifications *ml;
+ int i;
+ char *ptr, *end;
+
+ /* See if any weighted sorting applies to this entry */
+ for ( ;vi;vi=vi->vi_next ) {
+ if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
+ continue;
+ if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
+ continue;
+ for (ml = op->orm_modlist; ml; ml=ml->sml_next ) {
+ /* Must be a Delete Attr op, so no values to consider */
+ if ( !ml->sml_values )
+ continue;
+ if ( ml->sml_desc == vi->vi_ad )
+ break;
+ }
+ if ( !ml )
+ continue;
+ for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) {
+ ptr = ber_bvchr(&ml->sml_values[i], '{' );
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
+ vi->vi_ad->ad_cname.bv_val );
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight missing from attribute" );
+ return rs->sr_err;
+ }
+ strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
+ vi->vi_ad->ad_cname.bv_val );
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight is misformatted" );
+ return rs->sr_err;
+ }
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ return overlay_register_control( be, LDAP_CONTROL_VALSORT );
+}
+
+static int
+valsort_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ valsort_info *vi = on->on_bi.bi_private, *next;
+
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_VALSORT );
+#endif /* SLAP_CONFIG_DELETE */
+
+ for (; vi; vi = next) {
+ next = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+
+ return 0;
+}
+
+static int
+valsort_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_int_t flag = 0;
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "valSort control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "valSort control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+ if (( tag = ber_scanf( ber, "{b}", &flag )) == LBER_ERROR ) {
+ rs->sr_text = "valSort control: flag decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+ if ( flag )
+ op->o_ctrlflag[valsort_cid] |= SLAP_CONTROL_DATA0;
+
+ return LDAP_SUCCESS;
+}
+
+static slap_overinst valsort;
+
+int valsort_initialize( void )
+{
+ int rc;
+
+ valsort.on_bi.bi_type = "valsort";
+ valsort.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ valsort.on_bi.bi_db_destroy = valsort_destroy;
+ valsort.on_bi.bi_db_open = valsort_db_open;
+
+ valsort.on_bi.bi_op_add = valsort_add;
+ valsort.on_bi.bi_op_modify = valsort_modify;
+
+ valsort.on_response = valsort_response;
+
+ valsort.on_bi.bi_cf_ocs = valsort_cfocs;
+
+ rc = register_supported_control( LDAP_CONTROL_VALSORT,
+ SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl,
+ &valsort_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", rc );
+ return rc;
+ }
+
+ syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" );
+
+ rc = config_register_schema( valsort_cfats, valsort_cfocs );
+ if ( rc ) return rc;
+
+ return overlay_register(&valsort);
+}
+
+#if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
+int init_module( int argc, char *argv[]) {
+ return valsort_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_VALSORT */
diff --git a/servers/slapd/passwd.c b/servers/slapd/passwd.c
new file mode 100644
index 0000000..b6b1ddb
--- /dev/null
+++ b/servers/slapd/passwd.c
@@ -0,0 +1,659 @@
+/* passwd.c - password extended operation routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#ifdef SLAPD_CRYPT
+#ifdef HAVE_CRYPT_R
+#define __USE_GNU
+#endif /* HAVE_CRYPT_R */
+#include <ac/crypt.h>
+#endif /* SLAPD_CRYPT */
+
+#include "slap.h"
+
+#include <lber_pvt.h>
+#include <lutil.h>
+#include <lutil_sha1.h>
+
+const struct berval slap_EXOP_MODIFY_PASSWD = BER_BVC(LDAP_EXOP_MODIFY_PASSWD);
+
+static const char *defhash[] = {
+#ifdef LUTIL_SHA1_BYTES
+ "{SSHA}",
+#else
+ "{SMD5}",
+#endif
+ NULL
+};
+
+int passwd_extop(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct berval id = {0, NULL}, hash, *rsp = NULL;
+ req_pwdexop_s *qpw = &op->oq_pwdexop;
+ req_extended_s qext = op->oq_extended;
+ Modifications *ml;
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ int i, nhash;
+ char **hashes, idNul;
+ int rc;
+ BackendDB *op_be;
+ int freenewpw = 0;
+ struct berval dn = BER_BVNULL, ndn = BER_BVNULL;
+
+ assert( ber_bvcmp( &slap_EXOP_MODIFY_PASSWD, &op->ore_reqoid ) == 0 );
+
+ if( op->o_dn.bv_len == 0 ) {
+ Debug( LDAP_DEBUG_STATS, "%s PASSMOD\n",
+ op->o_log_prefix );
+ rs->sr_text = "only authenticated users may change passwords";
+ return LDAP_STRONG_AUTH_REQUIRED;
+ }
+
+ qpw->rs_old.bv_len = 0;
+ qpw->rs_old.bv_val = NULL;
+ qpw->rs_new.bv_len = 0;
+ qpw->rs_new.bv_val = NULL;
+ qpw->rs_mods = NULL;
+ qpw->rs_modtail = NULL;
+
+ rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id,
+ &qpw->rs_old, &qpw->rs_new, &rs->sr_text );
+
+ if ( !BER_BVISNULL( &id )) {
+ idNul = id.bv_val[id.bv_len];
+ id.bv_val[id.bv_len] = '\0';
+ }
+ if ( rs->sr_err == LDAP_SUCCESS && !BER_BVISEMPTY( &id ) ) {
+ Debug( LDAP_DEBUG_STATS, "%s PASSMOD id=\"%s\"%s%s\n",
+ op->o_log_prefix, id.bv_val,
+ qpw->rs_old.bv_val ? " old" : "",
+ qpw->rs_new.bv_val ? " new" : "" );
+ } else {
+ Debug( LDAP_DEBUG_STATS, "%s PASSMOD%s%s\n",
+ op->o_log_prefix,
+ qpw->rs_old.bv_val ? " old" : "",
+ qpw->rs_new.bv_val ? " new" : "" );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( !BER_BVISNULL( &id ))
+ id.bv_val[id.bv_len] = idNul;
+ return rs->sr_err;
+ }
+
+ if ( !BER_BVISEMPTY( &id ) ) {
+ rs->sr_err = dnPrettyNormal( NULL, &id, &dn, &ndn, op->o_tmpmemctx );
+ id.bv_val[id.bv_len] = idNul;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = "Invalid DN";
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ op->o_req_dn = dn;
+ op->o_req_ndn = ndn;
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+
+ } else {
+ ber_dupbv_x( &dn, &op->o_dn, op->o_tmpmemctx );
+ ber_dupbv_x( &ndn, &op->o_ndn, op->o_tmpmemctx );
+ op->o_req_dn = dn;
+ op->o_req_ndn = ndn;
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ op->o_bd = op->o_conn->c_authz_backend;
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ }
+
+ if( op->o_bd == NULL ) {
+ if ( qpw->rs_old.bv_val != NULL ) {
+ rs->sr_text = "unwilling to verify old password";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto error_return;
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ rc = slap_sasl_setpass( op, rs );
+#else
+ rs->sr_text = "no authz backend";
+ rc = LDAP_OTHER;
+#endif
+ goto error_return;
+ }
+
+ if ( op->o_req_ndn.bv_len == 0 ) {
+ rs->sr_text = "no password is associated with the Root DSE";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto error_return;
+ }
+
+ /* If we've got a glued backend, check the real backend */
+ op_be = op->o_bd;
+ if ( SLAP_GLUE_INSTANCE( op->o_bd )) {
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ }
+
+ if (backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_MODIFY_PASSWD ) != LDAP_SUCCESS) {
+ rc = rs->sr_err;
+ goto error_return;
+ }
+
+ /* check for referrals */
+ if ( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ rc = rs->sr_err;
+ goto error_return;
+ }
+
+ /* This does not apply to multi-provider case */
+ if(!( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ))) {
+ /* we SHOULD return a referral in this case */
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs,
+ NULL, NULL, LDAP_SCOPE_DEFAULT );
+ if(rs->sr_ref) {
+ rs->sr_flags |= REP_REF_MUSTBEFREED;
+ } else {
+ rs->sr_ref = defref;
+ }
+ rc = LDAP_REFERRAL;
+ goto error_return;
+
+ }
+
+ rs->sr_text = "shadow context; no update referral";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto error_return;
+ }
+
+ /* generate a new password if none was provided */
+ if ( qpw->rs_new.bv_len == 0 ) {
+ slap_passwd_generate( &qpw->rs_new );
+ if ( qpw->rs_new.bv_len ) {
+ rsp = slap_passwd_return( &qpw->rs_new );
+ freenewpw = 1;
+ }
+ }
+ if ( qpw->rs_new.bv_len == 0 ) {
+ rs->sr_text = "password generation failed";
+ rc = LDAP_OTHER;
+ goto error_return;
+ }
+
+ if ( op->o_txnSpec ) {
+ rc = txn_preop( op, rs );
+ goto error_return;
+ }
+
+ op->o_bd = op_be;
+
+ /* Give the backend a chance to handle this itself */
+ if ( op->o_bd->be_extended ) {
+ rs->sr_err = op->o_bd->be_extended( op, rs );
+ if ( rs->sr_err != LDAP_UNWILLING_TO_PERFORM &&
+ rs->sr_err != SLAP_CB_CONTINUE )
+ {
+ rc = rs->sr_err;
+ if ( rsp ) {
+ rs->sr_rspdata = rsp;
+ rsp = NULL;
+ }
+ goto error_return;
+ }
+ }
+
+ /* The backend didn't handle it, so try it here */
+ if( op->o_bd && !op->o_bd->be_modify ) {
+ rs->sr_text = "operation not supported for current user";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto error_return;
+ }
+
+ if ( qpw->rs_old.bv_val != NULL ) {
+ Entry *e = NULL;
+
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL,
+ slap_schema.si_ad_userPassword, 0, &e );
+ if ( rc == LDAP_SUCCESS && e ) {
+ Attribute *a = attr_find( e->e_attrs,
+ slap_schema.si_ad_userPassword );
+ if ( a )
+ rc = slap_passwd_check( op, e, a, &qpw->rs_old, &rs->sr_text );
+ else
+ rc = 1;
+ be_entry_release_r( op, e );
+ if ( rc == LDAP_SUCCESS )
+ goto old_good;
+ }
+ rs->sr_text = "unwilling to verify old password";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto error_return;
+ }
+
+old_good:
+ ml = ch_malloc( sizeof(Modifications) );
+ if ( !qpw->rs_modtail ) qpw->rs_modtail = &ml->sml_next;
+
+ if ( default_passwd_hash ) {
+ for ( nhash = 0; default_passwd_hash[nhash]; nhash++ );
+ hashes = default_passwd_hash;
+ } else {
+ nhash = 1;
+ hashes = (char **)defhash;
+ }
+ ml->sml_numvals = nhash;
+ ml->sml_values = ch_malloc( (nhash+1)*sizeof(struct berval) );
+ for ( i=0; hashes[i]; i++ ) {
+ slap_passwd_hash_type( &qpw->rs_new, &hash, hashes[i], &rs->sr_text );
+ if ( hash.bv_len == 0 ) {
+ if ( !rs->sr_text ) {
+ rs->sr_text = "password hash failed";
+ }
+ break;
+ }
+ ml->sml_values[i] = hash;
+ }
+ ml->sml_values[i].bv_val = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_desc = slap_schema.si_ad_userPassword;
+ ml->sml_type = ml->sml_desc->ad_cname;
+ ml->sml_op = LDAP_MOD_REPLACE;
+ ml->sml_flags = 0;
+ ml->sml_next = qpw->rs_mods;
+ qpw->rs_mods = ml;
+
+ if ( hashes[i] ) {
+ rs->sr_err = LDAP_OTHER;
+
+ } else {
+ slap_callback **sc;
+
+ cb.sc_next = op->o_callback;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = qpw->rs_mods;
+ op->orm_no_opattrs = 0;
+
+ cb.sc_private = qpw; /* let Modify know this was pwdMod,
+ * if it cares... */
+
+ rs->sr_err = op->o_bd->be_modify( op, rs );
+
+ /* be_modify() might have shuffled modifications */
+ qpw->rs_mods = op->orm_modlist;
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_rspdata = rsp;
+
+ } else if ( rsp ) {
+ ber_bvfree( rsp );
+ rsp = NULL;
+ }
+ op->o_tag = LDAP_REQ_EXTENDED;
+ for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) {
+ if ( *sc == &cb ) {
+ *sc = cb.sc_next;
+ break;
+ }
+ }
+ }
+
+ rc = rs->sr_err;
+ op->oq_extended = qext;
+
+error_return:;
+ if ( qpw->rs_mods ) {
+ slap_mods_free( qpw->rs_mods, 1 );
+ }
+ if ( freenewpw ) {
+ free( qpw->rs_new.bv_val );
+ }
+ if ( !BER_BVISNULL( &dn ) ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_dn );
+ }
+ if ( !BER_BVISNULL( &ndn ) ) {
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ }
+
+ return rc;
+}
+
+/* NOTE: The DN in *id is NOT NUL-terminated here. dnNormalize will
+ * reject it in this condition, the caller must NUL-terminate it.
+ * FIXME: should dnNormalize still be complaining about that?
+ */
+int slap_passwd_parse( struct berval *reqdata,
+ struct berval *id,
+ struct berval *oldpass,
+ struct berval *newpass,
+ const char **text )
+{
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ if( reqdata == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ if( reqdata->bv_len == 0 ) {
+ *text = "empty request data field";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, reqdata, 0 );
+
+ tag = ber_skip_tag( ber, &len );
+
+ if( tag != LBER_SEQUENCE ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_passwd_parse: decoding error\n" );
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_ID ) {
+ if( id == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: ID not allowed.\n" );
+
+ *text = "user must change own password";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ tag = ber_get_stringbv( ber, id, LBER_BV_NOTERM );
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: ID parse failed.\n" );
+
+ goto decoding_error;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_OLD ) {
+ if( oldpass == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD not allowed.\n" );
+
+ *text = "use bind to verify old password";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ tag = ber_get_stringbv( ber, oldpass, LBER_BV_NOTERM );
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD parse failed.\n" );
+
+ goto decoding_error;
+ }
+
+ if( oldpass->bv_len == 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: OLD empty.\n" );
+
+ *text = "old password value is empty";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if( tag == LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ) {
+ if( newpass == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW not allowed.\n" );
+
+ *text = "user specified passwords disallowed";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ tag = ber_get_stringbv( ber, newpass, LBER_BV_NOTERM );
+
+ if( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW parse failed.\n" );
+
+ goto decoding_error;
+ }
+
+ if( newpass->bv_len == 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_parse: NEW empty.\n" );
+
+ *text = "new password value is empty";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if( len != 0 ) {
+decoding_error:
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_passwd_parse: decoding error, len=%ld\n",
+ (long) len );
+
+ *text = "data decoding error";
+ rc = LDAP_PROTOCOL_ERROR;
+ }
+
+done:
+ return rc;
+}
+
+struct berval * slap_passwd_return(
+ struct berval *cred )
+{
+ int rc;
+ struct berval *bv = NULL;
+ BerElementBuffer berbuf;
+ /* opaque structure, size unknown but smaller than berbuf */
+ BerElement *ber = (BerElement *)&berbuf;
+
+ assert( cred != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_return: %ld\n",
+ (long) cred->bv_len );
+
+ ber_init_w_nullc( ber, LBER_USE_DER );
+
+ rc = ber_printf( ber, "{tON}",
+ LDAP_TAG_EXOP_MODIFY_PASSWD_GEN, cred );
+
+ if( rc >= 0 ) {
+ (void) ber_flatten( ber, &bv );
+ }
+
+ ber_free_buf( ber );
+
+ return bv;
+}
+
+/*
+ * if "e" is provided, access to each value of the password is checked first
+ */
+int
+slap_passwd_check(
+ Operation *op,
+ Entry *e,
+ Attribute *a,
+ struct berval *cred,
+ const char **text )
+{
+ int result = 1;
+ struct berval *bv;
+ AccessControlState acl_state = ACL_STATE_INIT;
+ char credNul = cred->bv_val[cred->bv_len];
+
+#ifdef SLAPD_SPASSWD
+ void *old_authctx = NULL;
+
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind,
+ op->o_conn->c_sasl_authctx, 0, &old_authctx, NULL );
+#endif
+
+ if ( credNul ) cred->bv_val[cred->bv_len] = 0;
+
+ for ( bv = a->a_vals; bv->bv_val != NULL; bv++ ) {
+ /* if e is provided, check access */
+ if ( e && access_allowed( op, e, a->a_desc, bv,
+ ACL_AUTH, &acl_state ) == 0 )
+ {
+ continue;
+ }
+
+ if ( !lutil_passwd( bv, cred, NULL, text ) ) {
+ result = 0;
+ break;
+ }
+ }
+
+ if ( credNul ) cred->bv_val[cred->bv_len] = credNul;
+
+#ifdef SLAPD_SPASSWD
+ ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)slap_sasl_bind,
+ old_authctx, 0, NULL, NULL );
+#endif
+
+ return result;
+}
+
+void
+slap_passwd_generate( struct berval *pass )
+{
+ Debug( LDAP_DEBUG_TRACE, "slap_passwd_generate\n" );
+ BER_BVZERO( pass );
+
+ /*
+ * generate passwords of only 8 characters as some getpass(3)
+ * implementations truncate at 8 characters.
+ */
+ lutil_passwd_generate( pass, 8 );
+}
+
+void
+slap_passwd_hash_type(
+ struct berval * cred,
+ struct berval * new,
+ char *hash,
+ const char **text )
+{
+ new->bv_len = 0;
+ new->bv_val = NULL;
+
+ assert( hash != NULL );
+
+ lutil_passwd_hash( cred , hash, new, text );
+}
+void
+slap_passwd_hash(
+ struct berval * cred,
+ struct berval * new,
+ const char **text )
+{
+ char *hash = NULL;
+ if ( default_passwd_hash ) {
+ hash = default_passwd_hash[0];
+ }
+ if ( !hash ) {
+ hash = (char *)defhash[0];
+ }
+
+ slap_passwd_hash_type( cred, new, hash, text );
+}
+
+#ifdef SLAPD_CRYPT
+static ldap_pvt_thread_mutex_t passwd_mutex;
+static lutil_cryptfunc slapd_crypt;
+
+#ifdef HAVE_CRYPT_R
+static int slapd_crypt( const char *key, const char *salt, char **hash )
+{
+ char *cr;
+ int rc;
+ struct crypt_data data;
+
+ data.initialized = 0;
+ cr = crypt_r( key, salt, &data );
+ if ( cr == NULL || cr[0] == '\0' ) {
+ /* salt must have been invalid */
+ rc = LUTIL_PASSWD_ERR;
+ } else {
+ if ( hash ) {
+ *hash = ber_strdup( cr );
+ rc = LUTIL_PASSWD_OK;
+ } else {
+ rc = strcmp( salt, cr ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK;
+ }
+ }
+
+ return rc;
+}
+#else
+static int slapd_crypt( const char *key, const char *salt, char **hash )
+{
+ char *cr;
+ int rc;
+
+ ldap_pvt_thread_mutex_lock( &passwd_mutex );
+
+ cr = crypt( key, salt );
+ if ( cr == NULL || cr[0] == '\0' ) {
+ /* salt must have been invalid */
+ rc = LUTIL_PASSWD_ERR;
+ } else {
+ if ( hash ) {
+ *hash = ber_strdup( cr );
+ rc = LUTIL_PASSWD_OK;
+
+ } else {
+ rc = strcmp( salt, cr ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK;
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &passwd_mutex );
+ return rc;
+}
+#endif /* HAVE_CRYPT_R */
+
+#endif /* SLAPD_CRYPT */
+
+void slap_passwd_init()
+{
+#ifdef SLAPD_CRYPT
+ ldap_pvt_thread_mutex_init( &passwd_mutex );
+ lutil_cryptptr = slapd_crypt;
+#endif
+}
+
diff --git a/servers/slapd/phonetic.c b/servers/slapd/phonetic.c
new file mode 100644
index 0000000..ed3641c
--- /dev/null
+++ b/servers/slapd/phonetic.c
@@ -0,0 +1,459 @@
+/* phonetic.c - routines to do phonetic matching */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/time.h>
+
+#include "slap.h"
+
+#if !defined(SLAPD_METAPHONE) && !defined(SLAPD_PHONETIC)
+#define SLAPD_METAPHONE
+#endif
+
+#define iswordbreak(x) (!isascii(x) || isspace((unsigned char) (x)) || \
+ ispunct((unsigned char) (x)) || \
+ isdigit((unsigned char) (x)) || (x) == '\0')
+
+#if 0
+static char *
+first_word( char *s )
+{
+ if ( s == NULL ) {
+ return( NULL );
+ }
+
+ while ( iswordbreak( *s ) ) {
+ if ( *s == '\0' ) {
+ return( NULL );
+ } else {
+ s++;
+ }
+ }
+
+ return( s );
+}
+
+static char *
+next_word( char *s )
+{
+ if ( s == NULL ) {
+ return( NULL );
+ }
+
+ while ( ! iswordbreak( *s ) ) {
+ s++;
+ }
+
+ while ( iswordbreak( *s ) ) {
+ if ( *s == '\0' ) {
+ return( NULL );
+ } else {
+ s++;
+ }
+ }
+
+ return( s );
+}
+
+static char *
+word_dup( char *w )
+{
+ char *s, *ret;
+ char save;
+
+ for ( s = w; !iswordbreak( *s ); s++ )
+ ; /* NULL */
+ save = *s;
+ *s = '\0';
+ ret = ch_strdup( w );
+ *s = save;
+
+ return( ret );
+}
+#endif /* 0 */
+
+#ifndef MAXPHONEMELEN
+#define MAXPHONEMELEN 4
+#endif
+
+#if defined(SLAPD_PHONETIC)
+
+/* lifted from isode-8.0 */
+char *
+phonetic( char *s )
+{
+ char code, adjacent, ch;
+ char *p;
+ int i;
+ char phoneme[MAXPHONEMELEN + 1];
+
+ p = s;
+ if ( p == NULL || *p == '\0' ) {
+ return( NULL );
+ }
+
+ adjacent = '0';
+ phoneme[0] = TOUPPER((unsigned char)*p);
+
+ phoneme[1] = '\0';
+ for ( i = 0; i < 99 && (! iswordbreak(*p)); p++ ) {
+ ch = TOUPPER ((unsigned char)*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( ch_strdup( phoneme ) );
+}
+
+#elif defined(SLAPD_METAPHONE)
+
+/*
+ * Metaphone was originally developed by Lawrence Philips and
+ * published in the "Computer Language" magazine in 1990.
+ */
+/*
+ * 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 const 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[40]; /* 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
+ */
+
+ for (n = ntrans + 4, n_end = ntrans + 35; !iswordbreak( *Word ) &&
+ n < n_end; Word++) {
+ if (isalpha((unsigned char)*Word))
+ *n++ = TOUPPER((unsigned char)*Word);
+ }
+ Metaph = buf;
+ *Metaph = '\0';
+ if (n == ntrans + 4) {
+ return( 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 {
+ /* 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( ch_strdup( buf ) );
+}
+
+#endif /* SLAPD_METAPHONE */
diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h
new file mode 100644
index 0000000..4b223ec
--- /dev/null
+++ b/servers/slapd/proto-slap.h
@@ -0,0 +1,2269 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#ifndef PROTO_SLAP_H
+#define PROTO_SLAP_H
+
+#include <ldap_cdefs.h>
+#include "ldap_pvt.h"
+
+LDAP_BEGIN_DECL
+
+struct config_args_s; /* slap-config.h */
+struct config_reply_s; /* slap-config.h */
+
+/*
+ * aci.c
+ */
+#ifdef SLAP_DYNACL
+#ifdef SLAPD_ACI_ENABLED
+LDAP_SLAPD_F (int) dynacl_aci_init LDAP_P(( void ));
+#endif /* SLAPD_ACI_ENABLED */
+#endif /* SLAP_DYNACL */
+
+/*
+ * acl.c
+ */
+LDAP_SLAPD_F (int) access_allowed_mask LDAP_P((
+ Operation *op,
+ Entry *e, AttributeDescription *desc, struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *mask ));
+#define access_allowed(op,e,desc,val,access,state) access_allowed_mask(op,e,desc,val,access,state,NULL)
+LDAP_SLAPD_F (int) slap_access_allowed LDAP_P((
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp ));
+LDAP_SLAPD_F (int) slap_access_always_allowed LDAP_P((
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp ));
+
+LDAP_SLAPD_F (int) acl_check_modlist LDAP_P((
+ Operation *op, Entry *e, Modifications *ml ));
+
+LDAP_SLAPD_F (void) acl_append( AccessControl **l, AccessControl *a, int pos );
+
+#ifdef SLAP_DYNACL
+LDAP_SLAPD_F (int) slap_dynacl_register LDAP_P(( slap_dynacl_t *da ));
+LDAP_SLAPD_F (slap_dynacl_t *) slap_dynacl_get LDAP_P(( const char *name ));
+#endif /* SLAP_DYNACL */
+LDAP_SLAPD_F (int) acl_init LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) acl_get_part LDAP_P((
+ struct berval *list,
+ int ix,
+ char sep,
+ struct berval *bv ));
+LDAP_SLAPD_F (int) acl_match_set LDAP_P((
+ struct berval *subj,
+ Operation *op,
+ Entry *e,
+ struct berval *default_set_attribute ));
+LDAP_SLAPD_F (int) acl_string_expand LDAP_P((
+ struct berval *newbuf, struct berval *pattern,
+ struct berval *dnmatch, struct berval *valmatch, AclRegexMatches *matches ));
+
+/*
+ * aclparse.c
+ */
+LDAP_SLAPD_V (LDAP_CONST char *) style_strings[];
+
+LDAP_SLAPD_F (int) parse_acl LDAP_P(( struct config_args_s *ca, int pos ));
+
+LDAP_SLAPD_F (char *) access2str LDAP_P(( slap_access_t access ));
+LDAP_SLAPD_F (slap_access_t) str2access LDAP_P(( const char *str ));
+
+#define ACCESSMASK_MAXLEN sizeof("unknown (+wrscan)")
+LDAP_SLAPD_F (char *) accessmask2str LDAP_P(( slap_mask_t mask, char*, int debug ));
+LDAP_SLAPD_F (slap_mask_t) str2accessmask LDAP_P(( const char *str ));
+LDAP_SLAPD_F (void) acl_unparse LDAP_P(( AccessControl*, struct berval* ));
+LDAP_SLAPD_F (void) acl_destroy LDAP_P(( AccessControl* ));
+LDAP_SLAPD_F (void) acl_free LDAP_P(( AccessControl *a ));
+
+
+/*
+ * ad.c
+ */
+LDAP_SLAPD_F (int) slap_str2ad LDAP_P((
+ const char *,
+ AttributeDescription **ad,
+ const char **text ));
+
+LDAP_SLAPD_F (int) slap_bv2ad LDAP_P((
+ struct berval *bv,
+ AttributeDescription **ad,
+ const char **text ));
+
+LDAP_SLAPD_F (void) ad_destroy LDAP_P(( AttributeDescription * ));
+LDAP_SLAPD_F (int) ad_keystring LDAP_P(( struct berval *bv ));
+
+#define ad_cmp(l,r) (((l)->ad_cname.bv_len < (r)->ad_cname.bv_len) \
+ ? -1 : (((l)->ad_cname.bv_len > (r)->ad_cname.bv_len) \
+ ? 1 : strcasecmp((l)->ad_cname.bv_val, (r)->ad_cname.bv_val )))
+
+LDAP_SLAPD_F (int) is_ad_subtype LDAP_P((
+ AttributeDescription *sub,
+ AttributeDescription *super ));
+
+LDAP_SLAPD_F (int) ad_inlist LDAP_P((
+ AttributeDescription *desc,
+ AttributeName *attrs ));
+
+LDAP_SLAPD_F (int) slap_str2undef_ad LDAP_P((
+ const char *,
+ AttributeDescription **ad,
+ const char **text,
+ unsigned proxied ));
+
+LDAP_SLAPD_F (int) slap_bv2undef_ad LDAP_P((
+ struct berval *bv,
+ AttributeDescription **ad,
+ const char **text,
+ unsigned proxied ));
+
+LDAP_SLAPD_F (AttributeDescription *) slap_bv2tmp_ad LDAP_P((
+ struct berval *bv,
+ void *memctx ));
+
+LDAP_SLAPD_F (int) slap_ad_undef_promote LDAP_P((
+ char *name,
+ AttributeType *nat ));
+
+LDAP_SLAPD_F (AttributeDescription *) ad_find_tags LDAP_P((
+ AttributeType *type,
+ struct berval *tags ));
+
+LDAP_SLAPD_F (AttributeName *) str2anlist LDAP_P(( AttributeName *an,
+ char *str, const char *brkstr ));
+LDAP_SLAPD_F (void) anlist_free LDAP_P(( AttributeName *an,
+ int freename, void *ctx ));
+
+LDAP_SLAPD_F (char **) anlist2charray_x LDAP_P((
+ AttributeName *an, int dup, void *ctx ));
+LDAP_SLAPD_F (char **) anlist2charray LDAP_P(( AttributeName *an, int dup ));
+LDAP_SLAPD_F (char **) anlist2attrs LDAP_P(( AttributeName *anlist ));
+LDAP_SLAPD_F (AttributeName *) file2anlist LDAP_P((
+ AttributeName *, const char *, const char * ));
+LDAP_SLAPD_F (int) an_find LDAP_P(( AttributeName *a, struct berval *s ));
+LDAP_SLAPD_F (int) ad_define_option LDAP_P(( const char *name,
+ const char *fname, int lineno ));
+LDAP_SLAPD_F (void) ad_unparse_options LDAP_P(( BerVarray *res ));
+
+LDAP_SLAPD_F (MatchingRule *) ad_mr(
+ AttributeDescription *ad,
+ unsigned usage );
+
+LDAP_SLAPD_V( AttributeName * ) slap_anlist_no_attrs;
+LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_user_attributes;
+LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_operational_attributes;
+LDAP_SLAPD_V( AttributeName * ) slap_anlist_all_attributes;
+
+LDAP_SLAPD_V( struct berval * ) slap_bv_no_attrs;
+LDAP_SLAPD_V( struct berval * ) slap_bv_all_user_attrs;
+LDAP_SLAPD_V( struct berval * ) slap_bv_all_operational_attrs;
+
+/* deprecated; only defined for backward compatibility */
+#define NoAttrs (*slap_bv_no_attrs)
+#define AllUser (*slap_bv_all_user_attrs)
+#define AllOper (*slap_bv_all_operational_attrs)
+
+/*
+ * add.c
+ */
+LDAP_SLAPD_F (int) slap_mods2entry LDAP_P(( Modifications *mods, Entry **e,
+ int initial, int dup, const char **text, char *textbuf, size_t textlen ));
+
+LDAP_SLAPD_F (int) slap_entry2mods LDAP_P(( Entry *e,
+ Modifications **mods, const char **text,
+ char *textbuf, size_t textlen ));
+LDAP_SLAPD_F( int ) slap_add_opattrs(
+ Operation *op,
+ const char **text,
+ char *textbuf, size_t textlen,
+ int manage_ctxcsn );
+
+
+/*
+ * at.c
+ */
+LDAP_SLAPD_V(int) at_oc_cache;
+LDAP_SLAPD_F (void) at_config LDAP_P((
+ const char *fname, int lineno,
+ int argc, char **argv ));
+LDAP_SLAPD_F (AttributeType *) at_find LDAP_P((
+ const char *name ));
+LDAP_SLAPD_F (AttributeType *) at_bvfind LDAP_P((
+ struct berval *name ));
+LDAP_SLAPD_F (int) at_find_in_list LDAP_P((
+ AttributeType *sat, AttributeType **list ));
+LDAP_SLAPD_F (int) at_append_to_list LDAP_P((
+ AttributeType *sat, AttributeType ***listp ));
+LDAP_SLAPD_F (int) at_delete_from_list LDAP_P((
+ int pos, AttributeType ***listp ));
+LDAP_SLAPD_F (int) at_schema_info LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (int) at_add LDAP_P((
+ LDAPAttributeType *at, int user,
+ AttributeType **sat, AttributeType *prev, const char **err ));
+LDAP_SLAPD_F (void) at_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) is_at_subtype LDAP_P((
+ AttributeType *sub,
+ AttributeType *super ));
+
+LDAP_SLAPD_F (const char *) at_syntax LDAP_P((
+ AttributeType *at ));
+LDAP_SLAPD_F (int) is_at_syntax LDAP_P((
+ AttributeType *at,
+ const char *oid ));
+
+LDAP_SLAPD_F (int) at_start LDAP_P(( AttributeType **at ));
+LDAP_SLAPD_F (int) at_next LDAP_P(( AttributeType **at ));
+LDAP_SLAPD_F (void) at_delete LDAP_P(( AttributeType *at ));
+
+LDAP_SLAPD_F (void) at_unparse LDAP_P((
+ BerVarray *bva, AttributeType *start, AttributeType *end, int system ));
+
+LDAP_SLAPD_F (int) register_at LDAP_P((
+ const char *at,
+ AttributeDescription **ad,
+ int dupok ));
+
+/*
+ * attr.c
+ */
+LDAP_SLAPD_F (void) attr_free LDAP_P(( Attribute *a ));
+LDAP_SLAPD_F (Attribute *) attr_dup LDAP_P(( Attribute *a ));
+
+#ifdef LDAP_COMP_MATCH
+LDAP_SLAPD_F (void) comp_tree_free LDAP_P(( Attribute *a ));
+#endif
+
+#define attr_mergeit( e, d, v ) attr_merge( e, d, v, NULL /* FIXME */ )
+#define attr_mergeit_one( e, d, v ) attr_merge_one( e, d, v, NULL /* FIXME */ )
+
+LDAP_SLAPD_F (Attribute *) attr_alloc LDAP_P(( AttributeDescription *ad ));
+LDAP_SLAPD_F (Attribute *) attrs_alloc LDAP_P(( int num ));
+LDAP_SLAPD_F (int) attr_prealloc LDAP_P(( int num ));
+LDAP_SLAPD_F (int) attr_valfind LDAP_P(( Attribute *a,
+ unsigned flags,
+ struct berval *val,
+ unsigned *slot,
+ void *ctx ));
+LDAP_SLAPD_F (int) attr_valadd LDAP_P(( Attribute *a,
+ BerVarray vals,
+ BerVarray nvals,
+ int num ));
+LDAP_SLAPD_F (int) attr_merge LDAP_P(( Entry *e,
+ AttributeDescription *desc,
+ BerVarray vals,
+ BerVarray nvals ));
+LDAP_SLAPD_F (int) attr_merge_one LDAP_P(( Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ struct berval *nval ));
+LDAP_SLAPD_F (int) attr_normalize LDAP_P(( AttributeDescription *desc,
+ BerVarray vals, BerVarray *nvalsp, void *memctx ));
+LDAP_SLAPD_F (int) attr_normalize_one LDAP_P(( AttributeDescription *desc,
+ struct berval *val, struct berval *nval, void *memctx ));
+LDAP_SLAPD_F (int) attr_merge_normalize LDAP_P(( Entry *e,
+ AttributeDescription *desc,
+ BerVarray vals, void *memctx ));
+LDAP_SLAPD_F (int) attr_merge_normalize_one LDAP_P(( Entry *e,
+ AttributeDescription *desc,
+ struct berval *val, void *memctx ));
+LDAP_SLAPD_F (Attribute *) attrs_find LDAP_P((
+ Attribute *a, AttributeDescription *desc ));
+LDAP_SLAPD_F (Attribute *) attr_find LDAP_P((
+ Attribute *a, AttributeDescription *desc ));
+LDAP_SLAPD_F (int) attr_delete LDAP_P((
+ Attribute **attrs, AttributeDescription *desc ));
+
+LDAP_SLAPD_F (void) attrs_free LDAP_P(( Attribute *a ));
+LDAP_SLAPD_F (Attribute *) attrs_dup LDAP_P(( Attribute *a ));
+LDAP_SLAPD_F (int) attr_init LDAP_P(( void ));
+LDAP_SLAPD_F (int) attr_destroy LDAP_P(( void ));
+
+
+/*
+ * ava.c
+ */
+LDAP_SLAPD_F (int) get_ava LDAP_P((
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ unsigned usage,
+ const char **text ));
+LDAP_SLAPD_F (void) ava_free LDAP_P((
+ Operation *op,
+ AttributeAssertion *ava,
+ int freeit ));
+LDAP_SLAPD_F (AttributeAssertion *) ava_dup LDAP_P((
+ AttributeAssertion *ava,
+ void *memctx ));
+
+/*
+ * backend.c
+ */
+
+#define be_match( be1, be2 ) ( (be1) == (be2) || \
+ ( (be1) && (be2) && (be1)->be_nsuffix == (be2)->be_nsuffix ) )
+
+LDAP_SLAPD_F (int) backend_init LDAP_P((void));
+LDAP_SLAPD_F (int) backend_add LDAP_P((BackendInfo *aBackendInfo));
+LDAP_SLAPD_F (int) backend_num LDAP_P((Backend *be));
+LDAP_SLAPD_F (int) backend_startup LDAP_P((Backend *be));
+LDAP_SLAPD_F (int) backend_startup_one LDAP_P((Backend *be, struct config_reply_s *cr));
+LDAP_SLAPD_F (int) backend_sync LDAP_P((Backend *be));
+LDAP_SLAPD_F (int) backend_shutdown LDAP_P((Backend *be));
+LDAP_SLAPD_F (int) backend_destroy LDAP_P((void));
+LDAP_SLAPD_F (void) backend_stopdown_one LDAP_P((BackendDB *bd ));
+LDAP_SLAPD_F (void) backend_destroy_one LDAP_P((BackendDB *bd, int dynamic));
+
+LDAP_SLAPD_F (BackendInfo *) backend_info LDAP_P(( const char *type ));
+LDAP_SLAPD_F (BackendDB *) backend_db_init LDAP_P(( const char *type,
+ BackendDB *be, int idx, struct config_reply_s *cr ));
+LDAP_SLAPD_F (void) backend_db_insert LDAP_P((BackendDB *bd, int idx));
+LDAP_SLAPD_F (void) backend_db_move LDAP_P((BackendDB *bd, int idx));
+
+LDAP_SLAPD_F (BackendDB *) select_backend LDAP_P((
+ struct berval * dn,
+ int noSubordinates ));
+
+LDAP_SLAPD_F (int) be_issuffix LDAP_P(( Backend *be,
+ struct berval *suffix ));
+LDAP_SLAPD_F (int) be_issubordinate LDAP_P(( Backend *be,
+ struct berval *subordinate ));
+LDAP_SLAPD_F (int) be_isroot LDAP_P(( Operation *op ));
+LDAP_SLAPD_F (int) be_isroot_dn LDAP_P(( Backend *be, struct berval *ndn ));
+LDAP_SLAPD_F (int) be_isroot_pw LDAP_P(( Operation *op ));
+LDAP_SLAPD_F (int) be_rootdn_bind LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) be_slurp_update LDAP_P(( Operation *op ));
+#define be_isupdate( op ) be_slurp_update( (op) )
+LDAP_SLAPD_F (int) be_shadow_update LDAP_P(( Operation *op ));
+LDAP_SLAPD_F (int) be_isupdate_dn LDAP_P(( Backend *be, struct berval *ndn ));
+LDAP_SLAPD_F (struct berval *) be_root_dn LDAP_P(( Backend *be ));
+LDAP_SLAPD_F (int) be_entry_get_rw LDAP_P(( Operation *o,
+ struct berval *ndn, ObjectClass *oc,
+ AttributeDescription *at, int rw, Entry **e ));
+
+/* "backend->ophandler(op,rs)" wrappers, applied by contrib:wrap_slap_ops */
+#define SLAP_OP(which, op, rs) slap_bi_op((op)->o_bd->bd_info, which, op, rs)
+#define slap_be_op(be, which, op, rs) slap_bi_op((be)->bd_info, which, op, rs)
+#if !(defined(USE_RS_ASSERT) && (USE_RS_ASSERT))
+#define slap_bi_op(bi, which, op, rs) ((&(bi)->bi_op_bind)[which](op, rs))
+#endif
+LDAP_SLAPD_F (int) (slap_bi_op) LDAP_P(( BackendInfo *bi,
+ slap_operation_t which, Operation *op, SlapReply *rs ));
+
+LDAP_SLAPD_F (int) be_entry_release_rw LDAP_P((
+ Operation *o, Entry *e, int rw ));
+#define be_entry_release_r( o, e ) be_entry_release_rw( o, e, 0 )
+#define be_entry_release_w( o, e ) be_entry_release_rw( o, e, 1 )
+
+LDAP_SLAPD_F (int) backend_unbind LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) backend_connection_init LDAP_P((Connection *conn));
+LDAP_SLAPD_F (int) backend_connection_destroy LDAP_P((Connection *conn));
+
+LDAP_SLAPD_F( int ) backend_check_controls LDAP_P((
+ Operation *op,
+ SlapReply *rs ));
+LDAP_SLAPD_F( int ) backend_check_restrictions LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ struct berval *opdata ));
+
+LDAP_SLAPD_F( int ) backend_check_referrals LDAP_P((
+ Operation *op,
+ SlapReply *rs ));
+
+LDAP_SLAPD_F (int) backend_group LDAP_P((
+ Operation *op,
+ Entry *target,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at
+));
+
+LDAP_SLAPD_F (int) backend_attribute LDAP_P((
+ Operation *op,
+ Entry *target,
+ struct berval *entry_ndn,
+ AttributeDescription *entry_at,
+ BerVarray *vals,
+ slap_access_t access
+));
+
+LDAP_SLAPD_F (int) backend_access LDAP_P((
+ Operation *op,
+ Entry *target,
+ struct berval *edn,
+ AttributeDescription *entry_at,
+ struct berval *nval,
+ slap_access_t access,
+ slap_mask_t *mask ));
+
+LDAP_SLAPD_F (int) backend_operational LDAP_P((
+ Operation *op,
+ SlapReply *rs
+));
+
+LDAP_SLAPD_F (ID) backend_tool_entry_first LDAP_P(( BackendDB *be ));
+
+LDAP_SLAPD_V(BackendInfo) slap_binfo[];
+
+/*
+ * backglue.c
+ */
+
+LDAP_SLAPD_F (int) glue_sub_init( void );
+LDAP_SLAPD_F (int) glue_sub_attach( int online );
+LDAP_SLAPD_F (int) glue_sub_add( BackendDB *be, int advert, int online );
+LDAP_SLAPD_F (int) glue_sub_del( BackendDB *be );
+
+/*
+ * backover.c
+ */
+LDAP_SLAPD_F (int) overlay_register LDAP_P(( slap_overinst *on ));
+LDAP_SLAPD_F (int) overlay_config LDAP_P(( BackendDB *be, const char *ov,
+ int idx, BackendInfo **res, ConfigReply *cr ));
+LDAP_SLAPD_F (void) overlay_destroy_one LDAP_P((
+ BackendDB *be,
+ slap_overinst *on ));
+LDAP_SLAPD_F (slap_overinst *) overlay_next LDAP_P(( slap_overinst *on ));
+LDAP_SLAPD_F (slap_overinst *) overlay_find LDAP_P(( const char *name ));
+LDAP_SLAPD_F (int) overlay_is_over LDAP_P(( BackendDB *be ));
+LDAP_SLAPD_F (int) overlay_is_inst LDAP_P(( BackendDB *be, const char *name ));
+LDAP_SLAPD_F (int) overlay_register_control LDAP_P((
+ BackendDB *be,
+ const char *oid ));
+LDAP_SLAPD_F (int) overlay_op_walk LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ slap_operation_t which,
+ slap_overinfo *oi,
+ slap_overinst *on ));
+LDAP_SLAPD_F (int) overlay_entry_get_ov LDAP_P((
+ Operation *op,
+ struct berval *dn,
+ ObjectClass *oc,
+ AttributeDescription *ad,
+ int rw,
+ Entry **e,
+ slap_overinst *ov ));
+LDAP_SLAPD_F (int) overlay_entry_release_ov LDAP_P((
+ Operation *op,
+ Entry *e,
+ int rw,
+ slap_overinst *ov ));
+LDAP_SLAPD_F (void) overlay_insert LDAP_P((
+ BackendDB *be, slap_overinst *on, slap_overinst ***prev, int idx ));
+LDAP_SLAPD_F (void) overlay_move LDAP_P((
+ BackendDB *be, slap_overinst *on, int idx ));
+#ifdef SLAP_CONFIG_DELETE
+LDAP_SLAPD_F (void) overlay_remove LDAP_P((
+ BackendDB *be, slap_overinst *on, Operation *op ));
+LDAP_SLAPD_F (void) overlay_unregister_control LDAP_P((
+ BackendDB *be,
+ const char *oid ));
+#endif /* SLAP_CONFIG_DELETE */
+LDAP_SLAPD_F (int) overlay_callback_after_backover LDAP_P((
+ Operation *op, slap_callback *sc, int append ));
+
+/*
+ * bconfig.c
+ */
+LDAP_SLAPD_F (int) slap_cf_aux_table_parse LDAP_P(( const char *word, void *bc, slap_cf_aux_table *tab0, LDAP_CONST char *tabmsg ));
+LDAP_SLAPD_F (int) slap_cf_aux_table_unparse LDAP_P(( void *bc, struct berval *bv, slap_cf_aux_table *tab0 ));
+
+/*
+ * ch_malloc.c
+ */
+LDAP_SLAPD_V (BerMemoryFunctions) ch_mfuncs;
+LDAP_SLAPD_F (void *) ch_malloc LDAP_P(( ber_len_t size ));
+LDAP_SLAPD_F (void *) ch_realloc LDAP_P(( void *block, ber_len_t size ));
+LDAP_SLAPD_F (void *) ch_calloc LDAP_P(( ber_len_t nelem, ber_len_t size ));
+LDAP_SLAPD_F (char *) ch_strdup LDAP_P(( const char *string ));
+LDAP_SLAPD_F (void) ch_free LDAP_P(( void * ));
+
+#ifndef CH_FREE
+#undef free
+#define free ch_free
+#endif
+
+/*
+ * compare.c
+ */
+
+LDAP_SLAPD_F (int) slap_compare_entry LDAP_P((
+ Operation *op,
+ Entry *e,
+ AttributeAssertion *ava ));
+
+/*
+ * component.c
+ */
+#ifdef LDAP_COMP_MATCH
+struct comp_attribute_aliasing;
+
+LDAP_SLAPD_F (int) test_comp_filter_entry LDAP_P((
+ Operation* op,
+ Entry* e,
+ MatchingRuleAssertion* mr));
+
+LDAP_SLAPD_F (int) dup_comp_filter LDAP_P((
+ Operation* op,
+ struct berval *bv,
+ ComponentFilter *in_f,
+ ComponentFilter **out_f ));
+
+LDAP_SLAPD_F (int) get_aliased_filter_aa LDAP_P((
+ Operation* op,
+ AttributeAssertion* a_assert,
+ struct comp_attribute_aliasing* aa,
+ const char** text ));
+
+LDAP_SLAPD_F (int) get_aliased_filter LDAP_P((
+ Operation* op,
+ MatchingRuleAssertion* ma,
+ struct comp_attribute_aliasing* aa,
+ const char** text ));
+
+LDAP_SLAPD_F (int) get_comp_filter LDAP_P((
+ Operation* op,
+ BerValue* bv,
+ ComponentFilter** filt,
+ const char **text ));
+
+LDAP_SLAPD_F (int) insert_component_reference LDAP_P((
+ ComponentReference *cr,
+ ComponentReference** cr_list ));
+
+LDAP_SLAPD_F (int) is_component_reference LDAP_P((
+ char *attr ));
+
+LDAP_SLAPD_F (int) extract_component_reference LDAP_P((
+ char* attr,
+ ComponentReference** cr ));
+
+LDAP_SLAPD_F (int) componentFilterMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+LDAP_SLAPD_F (int) directoryComponentsMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+LDAP_SLAPD_F (int) allComponentsMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+LDAP_SLAPD_F (ComponentReference*) dup_comp_ref LDAP_P((
+ Operation *op,
+ ComponentReference *cr ));
+
+LDAP_SLAPD_F (int) componentFilterValidate LDAP_P((
+ Syntax *syntax,
+ struct berval* bv ));
+
+LDAP_SLAPD_F (int) allComponentsValidate LDAP_P((
+ Syntax *syntax,
+ struct berval* bv ));
+
+LDAP_SLAPD_F (void) component_free LDAP_P((
+ ComponentFilter *f ));
+
+LDAP_SLAPD_F (void) free_ComponentData LDAP_P((
+ Attribute *a ));
+
+LDAP_SLAPD_V (test_membership_func*) is_aliased_attribute;
+
+LDAP_SLAPD_V (free_component_func*) component_destructor;
+
+LDAP_SLAPD_V (get_component_info_func*) get_component_description;
+
+LDAP_SLAPD_V (component_encoder_func*) component_encoder;
+
+LDAP_SLAPD_V (convert_attr_to_comp_func*) attr_converter;
+
+LDAP_SLAPD_V (alloc_nibble_func*) nibble_mem_allocator;
+
+LDAP_SLAPD_V (free_nibble_func*) nibble_mem_free;
+#endif
+
+/*
+ * controls.c
+ */
+LDAP_SLAPD_V( struct slap_control_ids ) slap_cids;
+LDAP_SLAPD_F (void) slap_free_ctrls LDAP_P((
+ Operation *op,
+ LDAPControl **ctrls ));
+LDAP_SLAPD_F (int) slap_add_ctrls LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrls ));
+LDAP_SLAPD_F (int) slap_parse_ctrl LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *control,
+ const char **text ));
+LDAP_SLAPD_F (int) get_ctrls LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ int senderrors ));
+LDAP_SLAPD_F (int) get_ctrls2 LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ int senderrors,
+ ber_tag_t ctag ));
+LDAP_SLAPD_F (int) register_supported_control2 LDAP_P((
+ const char *controloid,
+ slap_mask_t controlmask,
+ char **controlexops,
+ SLAP_CTRL_PARSE_FN *controlparsefn,
+ unsigned flags,
+ int *controlcid ));
+#define register_supported_control(oid, mask, exops, fn, cid) \
+ register_supported_control2((oid), (mask), (exops), (fn), 0, (cid))
+#ifdef SLAP_CONFIG_DELETE
+LDAP_SLAPD_F (int) unregister_supported_control LDAP_P((
+ const char* controloid ));
+#endif /* SLAP_CONFIG_DELETE */
+LDAP_SLAPD_F (int) register_control_exop LDAP_P (( const char *controloid, char *exopoid ));
+LDAP_SLAPD_F (int) slap_controls_init LDAP_P ((void));
+LDAP_SLAPD_F (void) controls_destroy LDAP_P ((void));
+LDAP_SLAPD_F (int) controls_root_dse_info LDAP_P ((Entry *e));
+LDAP_SLAPD_F (int) get_supported_controls LDAP_P ((
+ char ***ctrloidsp, slap_mask_t **ctrlmasks ));
+LDAP_SLAPD_F (int) slap_find_control_id LDAP_P ((
+ const char *oid, int *cid ));
+LDAP_SLAPD_F (int) slap_global_control LDAP_P ((
+ Operation *op, const char *oid, int *cid ));
+LDAP_SLAPD_F (int) slap_remove_control LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ int ctrl,
+ BI_chk_controls fnc ));
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+LDAP_SLAPD_F (int)
+slap_ctrl_session_tracking_add LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ip,
+ struct berval *name,
+ struct berval *id,
+ LDAPControl *ctrl ));
+LDAP_SLAPD_F (int)
+slap_ctrl_session_tracking_request_add LDAP_P((
+ Operation *op, SlapReply *rs, LDAPControl *ctrl ));
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+#ifdef SLAP_CONTROL_X_WHATFAILED
+LDAP_SLAPD_F (int)
+slap_ctrl_whatFailed_add LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ char **oids ));
+#endif /* SLAP_CONTROL_X_WHATFAILED */
+
+/*
+ * config.c
+ */
+LDAP_SLAPD_F (int) read_config LDAP_P(( const char *fname, const char *dir ));
+LDAP_SLAPD_F (void) config_destroy LDAP_P ((void));
+LDAP_SLAPD_F (char **) slap_str2clist LDAP_P((
+ char ***, char *, const char * ));
+LDAP_SLAPD_F (int) slap_tls_get_config LDAP_P((
+ LDAP *ld, int opt, char **val ));
+LDAP_SLAPD_F (void) bindconf_tls_defaults LDAP_P(( slap_bindconf *bc ));
+LDAP_SLAPD_F (int) bindconf_tls_parse LDAP_P((
+ const char *word, slap_bindconf *bc ));
+LDAP_SLAPD_F (int) bindconf_tls_unparse LDAP_P((
+ slap_bindconf *bc, struct berval *bv ));
+LDAP_SLAPD_F (int) bindconf_parse LDAP_P((
+ const char *word, slap_bindconf *bc ));
+LDAP_SLAPD_F (int) bindconf_unparse LDAP_P((
+ slap_bindconf *bc, struct berval *bv ));
+LDAP_SLAPD_F (int) bindconf_tls_set LDAP_P((
+ slap_bindconf *bc, LDAP *ld ));
+LDAP_SLAPD_F (void) bindconf_free LDAP_P(( slap_bindconf *bc ));
+LDAP_SLAPD_F (void) slap_client_keepalive LDAP_P(( LDAP *ld, slap_keepalive *sk ));
+LDAP_SLAPD_F (int) slap_client_connect LDAP_P(( LDAP **ldp, slap_bindconf *sb ));
+LDAP_SLAPD_F (int) config_generic_wrapper LDAP_P(( Backend *be,
+ const char *fname, int lineno, int argc, char **argv ));
+LDAP_SLAPD_F (char *) anlist_unparse LDAP_P(( AttributeName *, char *, ber_len_t buflen ));
+LDAP_SLAPD_F (int) slap_bv_x_ordered_unparse LDAP_P(( BerVarray in, BerVarray *out ));
+LDAP_SLAPD_F (int) slap_keepalive_parse( struct berval *val, void *bc,
+ slap_cf_aux_table *tab0, const char *tabmsg, int unparse );
+
+#ifdef LDAP_SLAPI
+LDAP_SLAPD_V (int) slapi_plugins_used;
+#endif
+
+/*
+ * connection.c
+ */
+LDAP_SLAPD_F (int) connections_init LDAP_P((void));
+LDAP_SLAPD_F (int) connections_shutdown LDAP_P((void));
+LDAP_SLAPD_F (int) connections_destroy LDAP_P((void));
+LDAP_SLAPD_F (int) connections_timeout_idle LDAP_P((time_t));
+LDAP_SLAPD_F (void) connections_drop LDAP_P((void));
+
+LDAP_SLAPD_F (Connection *) connection_client_setup LDAP_P((
+ ber_socket_t s,
+ ldap_pvt_thread_start_t *func,
+ void *arg ));
+LDAP_SLAPD_F (void) connection_client_enable LDAP_P(( Connection *c ));
+LDAP_SLAPD_F (void) connection_client_stop LDAP_P(( Connection *c ));
+
+#ifdef LDAP_PF_LOCAL_SENDMSG
+#define LDAP_PF_LOCAL_SENDMSG_ARG(arg) , arg
+#else
+#define LDAP_PF_LOCAL_SENDMSG_ARG(arg)
+#endif
+
+LDAP_SLAPD_F (Connection *) connection_init LDAP_P((
+ ber_socket_t s,
+ Listener* url,
+ const char* dnsname,
+ const char* peername,
+ int use_tls,
+ slap_ssf_t ssf,
+ struct berval *id
+ LDAP_PF_LOCAL_SENDMSG_ARG(struct berval *peerbv)));
+
+LDAP_SLAPD_F (void) operation_counter_init LDAP_P(( Operation *op, void *threadctx ));
+LDAP_SLAPD_F (void) connection_closing LDAP_P((
+ Connection *c, const char *why ));
+LDAP_SLAPD_F (int) connection_is_active LDAP_P(( ber_socket_t s ));
+LDAP_SLAPD_F (int) connection_valid LDAP_P(( Connection *c ));
+LDAP_SLAPD_F (const char *) connection_state2str LDAP_P(( int state ))
+ LDAP_GCCATTR((const));
+
+LDAP_SLAPD_F (int) connection_read_activate LDAP_P((ber_socket_t s));
+LDAP_SLAPD_F (int) connection_write LDAP_P((ber_socket_t s));
+LDAP_SLAPD_F (void) connection_write_resume LDAP_P((Connection *c));
+
+LDAP_SLAPD_F (void) connection_op_finish LDAP_P((
+ Operation *op, int lock ));
+
+LDAP_SLAPD_F (unsigned long) connections_nextid(void);
+
+LDAP_SLAPD_F (Connection *) connection_first LDAP_P(( ber_socket_t * ));
+LDAP_SLAPD_F (Connection *) connection_next LDAP_P((
+ Connection *, ber_socket_t *));
+LDAP_SLAPD_F (void) connection_done LDAP_P((Connection *));
+
+LDAP_SLAPD_F (void) connection2anonymous LDAP_P((Connection *));
+LDAP_SLAPD_F (void) connection_fake_init LDAP_P((
+ Connection *conn,
+ OperationBuffer *opbuf,
+ void *threadctx ));
+LDAP_SLAPD_F (void) connection_fake_init2 LDAP_P((
+ Connection *conn,
+ OperationBuffer *opbuf,
+ void *threadctx,
+ int newmem ));
+LDAP_SLAPD_F (void) operation_fake_init LDAP_P((
+ Connection *conn,
+ Operation *op,
+ void *threadctx,
+ int newmem ));
+LDAP_SLAPD_F (void) connection_assign_nextid LDAP_P((Connection *));
+
+/*
+ * cr.c
+ */
+LDAP_SLAPD_F (int) cr_schema_info( Entry *e );
+LDAP_SLAPD_F (void) cr_unparse LDAP_P((
+ BerVarray *bva, ContentRule *start, ContentRule *end, int system ));
+
+LDAP_SLAPD_F (int) cr_add LDAP_P((
+ LDAPContentRule *oc,
+ int user,
+ ContentRule **scr,
+ const char **err));
+
+LDAP_SLAPD_F (void) cr_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (ContentRule *) cr_find LDAP_P((
+ const char *crname));
+LDAP_SLAPD_F (ContentRule *) cr_bvfind LDAP_P((
+ struct berval *crname));
+
+/*
+ * ctxcsn.c
+ */
+
+LDAP_SLAPD_V( int ) slap_serverID;
+LDAP_SLAPD_V( const struct berval ) slap_ldapsync_bv;
+LDAP_SLAPD_V( const struct berval ) slap_ldapsync_cn_bv;
+LDAP_SLAPD_F (void) slap_get_commit_csn LDAP_P((
+ Operation *, struct berval *maxcsn, int *foundit ));
+LDAP_SLAPD_F (void) slap_rewind_commit_csn LDAP_P(( Operation * ));
+LDAP_SLAPD_F (void) slap_graduate_commit_csn LDAP_P(( Operation * ));
+LDAP_SLAPD_F (Entry *) slap_create_context_csn_entry LDAP_P(( Backend *, struct berval *));
+LDAP_SLAPD_F (int) slap_get_csn LDAP_P(( Operation *, struct berval *, int ));
+LDAP_SLAPD_F (void) slap_queue_csn LDAP_P(( Operation *, struct berval * ));
+
+/*
+ * daemon.c
+ */
+LDAP_SLAPD_F (void) slapd_add_internal(ber_socket_t s, int isactive);
+LDAP_SLAPD_F (int) slapd_daemon_init( const char *urls );
+LDAP_SLAPD_F (int) slapd_daemon_resize( int newnum );
+LDAP_SLAPD_F (int) slapd_daemon_destroy(void);
+LDAP_SLAPD_F (int) slapd_daemon(void);
+LDAP_SLAPD_F (Listener **) slapd_get_listeners LDAP_P((void));
+LDAP_SLAPD_F (void) slapd_remove LDAP_P((ber_socket_t s, Sockbuf *sb,
+ int wasactive, int wake, int locked ));
+
+LDAP_SLAPD_F (RETSIGTYPE) slap_sig_shutdown LDAP_P((int sig));
+LDAP_SLAPD_F (RETSIGTYPE) slap_sig_wake LDAP_P((int sig));
+LDAP_SLAPD_F (void) slap_wake_listener LDAP_P((void));
+
+LDAP_SLAPD_F (void) slap_suspend_listeners LDAP_P((void));
+LDAP_SLAPD_F (void) slap_resume_listeners LDAP_P((void));
+
+LDAP_SLAPD_F (int) slap_pause_server LDAP_P((void));
+LDAP_SLAPD_F (int) slap_unpause_server LDAP_P((void));
+
+LDAP_SLAPD_F (void) slapd_set_write LDAP_P((ber_socket_t s, int wake));
+LDAP_SLAPD_F (void) slapd_clr_write LDAP_P((ber_socket_t s, int wake));
+LDAP_SLAPD_F (void) slapd_set_read LDAP_P((ber_socket_t s, int wake));
+LDAP_SLAPD_F (int) slapd_clr_read LDAP_P((ber_socket_t s, int wake));
+LDAP_SLAPD_F (int) slapd_wait_writer( ber_socket_t sd );
+LDAP_SLAPD_F (void) slapd_shutsock( ber_socket_t sd );
+
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_abrupt_shutdown;
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_shutdown;
+LDAP_SLAPD_V (int) slapd_register_slp;
+LDAP_SLAPD_V (const char *) slapd_slp_attrs;
+LDAP_SLAPD_V (slap_ssf_t) local_ssf;
+LDAP_SLAPD_V (struct runqueue_s) slapd_rq;
+LDAP_SLAPD_V (int) slapd_daemon_threads;
+LDAP_SLAPD_V (int) slapd_daemon_mask;
+#ifdef LDAP_TCP_BUFFER
+LDAP_SLAPD_V (int) slapd_tcp_rmem;
+LDAP_SLAPD_V (int) slapd_tcp_wmem;
+#endif /* LDAP_TCP_BUFFER */
+
+#ifdef HAVE_WINSOCK
+LDAP_SLAPD_F (ber_socket_t) slapd_socknew(ber_socket_t s);
+LDAP_SLAPD_F (ber_socket_t) slapd_sock2fd(ber_socket_t s);
+LDAP_SLAPD_V (SOCKET *) slapd_ws_sockets;
+#define SLAP_FD2SOCK(s) slapd_ws_sockets[s]
+#define SLAP_SOCK2FD(s) slapd_sock2fd(s)
+#define SLAP_SOCKNEW(s) slapd_socknew(s)
+#else
+#define SLAP_FD2SOCK(s) s
+#define SLAP_SOCK2FD(s) s
+#define SLAP_SOCKNEW(s) s
+#endif
+
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) slapd_init_mutex;
+LDAP_SLAPD_V (ldap_pvt_thread_cond_t) slapd_init_cond;
+
+/*
+ * dn.c
+ */
+
+#define dn_match(dn1, dn2) ( ber_bvcmp((dn1), (dn2)) == 0 )
+#define bvmatch(bv1, bv2) ( ((bv1)->bv_len == (bv2)->bv_len) && (memcmp((bv1)->bv_val, (bv2)->bv_val, (bv1)->bv_len) == 0) )
+
+LDAP_SLAPD_F (int) dnValidate LDAP_P((
+ Syntax *syntax,
+ struct berval *val ));
+LDAP_SLAPD_F (int) rdnValidate LDAP_P((
+ Syntax *syntax,
+ struct berval *val ));
+
+LDAP_SLAPD_F (slap_mr_normalize_func) dnNormalize;
+
+LDAP_SLAPD_F (slap_mr_normalize_func) rdnNormalize;
+
+LDAP_SLAPD_F (slap_syntax_transform_func) dnPretty;
+
+LDAP_SLAPD_F (slap_syntax_transform_func) rdnPretty;
+
+LDAP_SLAPD_F (int) dnPrettyNormal LDAP_P((
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *pretty,
+ struct berval *normal,
+ void *ctx ));
+
+LDAP_SLAPD_F (int) dnMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+LDAP_SLAPD_F (int) dnRelativeMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+LDAP_SLAPD_F (int) rdnMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+
+LDAP_SLAPD_F (int) dnIsSuffix LDAP_P((
+ const struct berval *dn, const struct berval *suffix ));
+
+LDAP_SLAPD_F (int) dnIsWithinScope LDAP_P((
+ struct berval *ndn, struct berval *nbase, int scope ));
+
+LDAP_SLAPD_F (int) dnIsSuffixScope LDAP_P((
+ struct berval *ndn, struct berval *nbase, int scope ));
+
+LDAP_SLAPD_F (int) dnIsOneLevelRDN LDAP_P(( struct berval *rdn ));
+
+LDAP_SLAPD_F (int) dnExtractRdn LDAP_P((
+ struct berval *dn, struct berval *rdn, void *ctx ));
+
+LDAP_SLAPD_F (int) rdn_validate LDAP_P(( struct berval * rdn ));
+
+LDAP_SLAPD_F (ber_len_t) dn_rdnlen LDAP_P(( Backend *be, struct berval *dn ));
+
+LDAP_SLAPD_F (void) build_new_dn LDAP_P((
+ struct berval * new_dn,
+ struct berval * parent_dn,
+ struct berval * newrdn,
+ void *memctx ));
+
+LDAP_SLAPD_F (void) dnParent LDAP_P(( struct berval *dn, struct berval *pdn ));
+LDAP_SLAPD_F (void) dnRdn LDAP_P(( struct berval *dn, struct berval *rdn ));
+
+LDAP_SLAPD_F (int) dnX509normalize LDAP_P(( void *x509_name, struct berval *out ));
+
+LDAP_SLAPD_F (int) dnX509peerNormalize LDAP_P(( void *ssl, struct berval *dn ));
+
+LDAP_SLAPD_F (int) dnPrettyNormalDN LDAP_P(( Syntax *syntax, struct berval *val, LDAPDN *dn, int flags, void *ctx ));
+#define dnPrettyDN(syntax, val, dn, ctx) \
+ dnPrettyNormalDN((syntax),(val),(dn), SLAP_LDAPDN_PRETTY, ctx)
+#define dnNormalDN(syntax, val, dn, ctx) \
+ dnPrettyNormalDN((syntax),(val),(dn), 0, ctx)
+
+typedef int (SLAP_CERT_MAP_FN) LDAP_P(( void *ssl, struct berval *dn ));
+LDAP_SLAPD_F (int) register_certificate_map_function LDAP_P(( SLAP_CERT_MAP_FN *fn ));
+
+/*
+ * entry.c
+ */
+LDAP_SLAPD_V (const Entry) slap_entry_root;
+
+LDAP_SLAPD_F (int) entry_init LDAP_P((void));
+LDAP_SLAPD_F (int) entry_destroy LDAP_P((void));
+
+LDAP_SLAPD_F (Entry *) str2entry LDAP_P(( char *s ));
+LDAP_SLAPD_F (Entry *) str2entry2 LDAP_P(( char *s, int checkvals ));
+LDAP_SLAPD_F (char *) entry2str LDAP_P(( Entry *e, int *len ));
+LDAP_SLAPD_F (char *) entry2str_wrap LDAP_P(( Entry *e, int *len, ber_len_t wrap ));
+
+LDAP_SLAPD_F (ber_len_t) entry_flatsize LDAP_P(( Entry *e, int norm ));
+LDAP_SLAPD_F (void) entry_partsize LDAP_P(( Entry *e, ber_len_t *len,
+ int *nattrs, int *nvals, int norm ));
+
+LDAP_SLAPD_F (int) entry_header LDAP_P(( EntryHeader *eh ));
+LDAP_SLAPD_F (int) entry_decode_dn LDAP_P((
+ EntryHeader *eh, struct berval *dn, struct berval *ndn ));
+#ifdef SLAP_ZONE_ALLOC
+LDAP_SLAPD_F (int) entry_decode LDAP_P((
+ EntryHeader *eh, Entry **e, void *ctx ));
+#else
+LDAP_SLAPD_F (int) entry_decode LDAP_P((
+ EntryHeader *eh, Entry **e ));
+#endif
+LDAP_SLAPD_F (int) entry_encode LDAP_P(( Entry *e, struct berval *bv ));
+
+LDAP_SLAPD_F (void) entry_clean LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (void) entry_free LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (int) entry_cmp LDAP_P(( Entry *a, Entry *b ));
+LDAP_SLAPD_F (int) entry_dn_cmp LDAP_P(( const void *v_a, const void *v_b ));
+LDAP_SLAPD_F (int) entry_id_cmp LDAP_P(( const void *v_a, const void *v_b ));
+LDAP_SLAPD_F (Entry *) entry_dup LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (Entry *) entry_dup2 LDAP_P(( Entry *dest, Entry *src ));
+LDAP_SLAPD_F (Entry *) entry_dup_bv LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (Entry *) entry_alloc LDAP_P((void));
+LDAP_SLAPD_F (int) entry_prealloc LDAP_P((int num));
+
+/*
+ * extended.c
+ */
+LDAP_SLAPD_F (int) exop_root_dse_info LDAP_P ((Entry *e));
+
+#define exop_is_write( op ) ((op->ore_flags & SLAP_EXOP_WRITES) != 0)
+
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_CANCEL;
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_WHOAMI;
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_MODIFY_PASSWD;
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_START_TLS;
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_TXN_START;
+LDAP_SLAPD_V( const struct berval ) slap_EXOP_TXN_END;
+
+typedef int (SLAP_EXTOP_MAIN_FN) LDAP_P(( Operation *op, SlapReply *rs ));
+
+typedef int (SLAP_EXTOP_GETOID_FN) LDAP_P((
+ int index, struct berval *oid, int blen ));
+
+LDAP_SLAPD_F (int) load_extop2 LDAP_P((
+ const struct berval *ext_oid,
+ slap_mask_t flags,
+ SLAP_EXTOP_MAIN_FN *ext_main,
+ unsigned tmpflags ));
+#define load_extop(ext_oid, flags, ext_main) \
+ load_extop2((ext_oid), (flags), (ext_main), 0)
+LDAP_SLAPD_F (int) unload_extop LDAP_P((
+ const struct berval *ext_oid,
+ SLAP_EXTOP_MAIN_FN *ext_main,
+ unsigned tmpflags ));
+
+LDAP_SLAPD_F (int) extops_init LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) extops_kill LDAP_P(( void ));
+
+LDAP_SLAPD_F (struct berval *) get_supported_extop LDAP_P((int index));
+
+/*
+ * txn.c
+ */
+LDAP_SLAPD_F ( SLAP_CTRL_PARSE_FN ) txn_spec_ctrl;
+LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) txn_start_extop;
+LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) txn_end_extop;
+LDAP_SLAPD_F ( int ) txn_preop LDAP_P(( Operation *op, SlapReply *rs ));
+
+/*
+ * cancel.c
+ */
+LDAP_SLAPD_F ( SLAP_EXTOP_MAIN_FN ) cancel_extop;
+
+/*
+ * filter.c
+ */
+LDAP_SLAPD_F (int) get_filter LDAP_P((
+ Operation *op,
+ BerElement *ber,
+ Filter **filt,
+ const char **text ));
+
+LDAP_SLAPD_F (void) filter_free LDAP_P(( Filter *f ));
+LDAP_SLAPD_F (void) filter_free_x LDAP_P(( Operation *op, Filter *f, int freeme ));
+LDAP_SLAPD_F (void) filter2bv LDAP_P(( Filter *f, struct berval *bv ));
+LDAP_SLAPD_F (void) filter2bv_x LDAP_P(( Operation *op, Filter *f, struct berval *bv ));
+LDAP_SLAPD_F (void) filter2bv_undef LDAP_P(( Filter *f, int noundef, struct berval *bv ));
+LDAP_SLAPD_F (void) filter2bv_undef_x LDAP_P(( Operation *op, Filter *f, int noundef, struct berval *bv ));
+LDAP_SLAPD_F (Filter *) filter_dup LDAP_P(( Filter *f, void *memctx ));
+
+LDAP_SLAPD_F (int) get_vrFilter LDAP_P(( Operation *op, BerElement *ber,
+ ValuesReturnFilter **f,
+ const char **text ));
+
+LDAP_SLAPD_F (void) vrFilter_free LDAP_P(( Operation *op, ValuesReturnFilter *f ));
+LDAP_SLAPD_F (void) vrFilter2bv LDAP_P(( Operation *op, ValuesReturnFilter *f, struct berval *fstr ));
+
+LDAP_SLAPD_F (int) filter_has_subordinates LDAP_P(( Filter *filter ));
+#define filter_escape_value( in, out ) ldap_bv2escaped_filter_value_x( (in), (out), 0, NULL )
+#define filter_escape_value_x( in, out, ctx ) ldap_bv2escaped_filter_value_x( (in), (out), 0, ctx )
+
+LDAP_SLAPD_V (const Filter *) slap_filter_objectClass_pres;
+LDAP_SLAPD_V (const struct berval *) slap_filterstr_objectClass_pres;
+
+LDAP_SLAPD_F (int) filter_init LDAP_P(( void ));
+LDAP_SLAPD_F (void) filter_destroy LDAP_P(( void ));
+/*
+ * filterentry.c
+ */
+
+LDAP_SLAPD_F (int) test_filter LDAP_P(( Operation *op, Entry *e, Filter *f ));
+
+/*
+ * frontend.c
+ */
+LDAP_SLAPD_F (int) frontend_init LDAP_P(( void ));
+
+/*
+ * globals.c
+ */
+
+LDAP_SLAPD_V( const struct berval ) slap_empty_bv;
+LDAP_SLAPD_V( const struct berval ) slap_unknown_bv;
+LDAP_SLAPD_V( const struct berval ) slap_true_bv;
+LDAP_SLAPD_V( const struct berval ) slap_false_bv;
+LDAP_SLAPD_V( struct slap_sync_cookie_s ) slap_sync_cookie;
+LDAP_SLAPD_V( void * ) slap_tls_ctx;
+LDAP_SLAPD_V( LDAP * ) slap_tls_ld;
+
+/*
+ * index.c
+ */
+LDAP_SLAPD_F (int) slap_str2index LDAP_P(( const char *str, slap_mask_t *idx ));
+LDAP_SLAPD_F (void) slap_index2bvlen LDAP_P(( slap_mask_t idx, struct berval *bv ));
+LDAP_SLAPD_F (void) slap_index2bv LDAP_P(( slap_mask_t idx, struct berval *bv ));
+
+/*
+ * init.c
+ */
+LDAP_SLAPD_F (int) slap_init LDAP_P((int mode, const char* name));
+LDAP_SLAPD_F (int) slap_startup LDAP_P(( Backend *be ));
+LDAP_SLAPD_F (int) slap_shutdown LDAP_P(( Backend *be ));
+LDAP_SLAPD_F (int) slap_destroy LDAP_P((void));
+LDAP_SLAPD_F (void) slap_counters_init LDAP_P((slap_counters_t *sc));
+LDAP_SLAPD_F (void) slap_counters_destroy LDAP_P((slap_counters_t *sc));
+
+LDAP_SLAPD_V (char *) slap_known_controls[];
+
+/*
+ * ldapsync.c
+ */
+LDAP_SLAPD_F (void) slap_compose_sync_cookie LDAP_P((
+ Operation *, struct berval *, BerVarray, int, int, struct berval * ));
+LDAP_SLAPD_F (void) slap_sync_cookie_free LDAP_P((
+ struct sync_cookie *, int free_cookie ));
+LDAP_SLAPD_F (int) slap_parse_csn_sid LDAP_P((
+ struct berval * ));
+LDAP_SLAPD_F (int *) slap_parse_csn_sids LDAP_P((
+ BerVarray, int, void *memctx ));
+LDAP_SLAPD_F (int) slap_sort_csn_sids LDAP_P((
+ BerVarray, int *, int, void *memctx ));
+LDAP_SLAPD_F (void) slap_insert_csn_sids LDAP_P((
+ struct sync_cookie *ck, int, int, struct berval * ));
+LDAP_SLAPD_F (int) slap_parse_sync_cookie LDAP_P((
+ struct sync_cookie *, void *memctx ));
+LDAP_SLAPD_F (void) slap_reparse_sync_cookie LDAP_P((
+ struct sync_cookie *, void *memctx ));
+LDAP_SLAPD_F (int) slap_init_sync_cookie_ctxcsn LDAP_P((
+ struct sync_cookie * ));
+LDAP_SLAPD_F (struct sync_cookie *) slap_dup_sync_cookie LDAP_P((
+ struct sync_cookie *, struct sync_cookie * ));
+LDAP_SLAPD_F (int) slap_build_syncUUID_set LDAP_P((
+ Operation *, BerVarray *, Entry * ));
+
+/*
+ * limits.c
+ */
+LDAP_SLAPD_F (int) limits_parse LDAP_P((
+ Backend *be, const char *fname, int lineno,
+ int argc, char **argv ));
+LDAP_SLAPD_F (int) limits_parse_one LDAP_P(( const char *arg,
+ struct slap_limits_set *limit ));
+LDAP_SLAPD_F (int) limits_check LDAP_P((
+ Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) limits_unparse_one LDAP_P((
+ struct slap_limits_set *limit, int which, struct berval *bv, ber_len_t buflen ));
+LDAP_SLAPD_F (int) limits_unparse LDAP_P((
+ struct slap_limits *limit, struct berval *bv, ber_len_t buflen ));
+LDAP_SLAPD_F (void) limits_free_one LDAP_P((
+ struct slap_limits *lm ));
+LDAP_SLAPD_F (void) limits_destroy LDAP_P(( struct slap_limits **lm ));
+
+/*
+ * lock.c
+ */
+LDAP_SLAPD_F (FILE *) lock_fopen LDAP_P(( const char *fname,
+ const char *type, FILE **lfp ));
+LDAP_SLAPD_F (int) lock_fclose LDAP_P(( FILE *fp, FILE *lfp ));
+
+/*
+ * logging.c
+ */
+LDAP_SLAPD_F (int) slap_loglevel_get LDAP_P(( struct berval *s, int *l ));
+LDAP_SLAPD_F (void) slap_loglevel_destroy LDAP_P(( void ));
+LDAP_SLAPD_F (int) str2loglevel LDAP_P(( const char *s, int *l ));
+LDAP_SLAPD_F (int) loglevel2bvarray LDAP_P(( int l, BerVarray *bva ));
+LDAP_SLAPD_F (const char *) loglevel2str LDAP_P(( int l ));
+LDAP_SLAPD_F (int) loglevel2bv LDAP_P(( int l, struct berval *bv ));
+LDAP_SLAPD_F (int) loglevel_print LDAP_P(( FILE *out ));
+LDAP_SLAPD_F (void) slap_debug_print LDAP_P(( const char *data ));
+LDAP_SLAPD_F (int) logfile_open LDAP_P(( const char *path ));
+LDAP_SLAPD_F (void) logfile_close LDAP_P(( void ));
+LDAP_SLAPD_F (void) slap_syslog_set LDAP_P(( int l ));
+LDAP_SLAPD_F (int) slap_syslog_get LDAP_P(( void ));
+LDAP_SLAPD_F (void) slap_debug_set LDAP_P(( int l ));
+LDAP_SLAPD_F (int) slap_debug_get LDAP_P(( void ));
+LDAP_SLAPD_F (const char *) logfile_name LDAP_P(( void ));
+LDAP_SLAPD_F (int)
+slap_parse_syslog_level LDAP_P(( const char *arg, int *levelp ));
+LDAP_SLAPD_F (int)
+slap_parse_syslog_user LDAP_P(( const char *arg, int *syslogUser ));
+LDAP_SLAPD_F (int)
+slap_parse_debug_level LDAP_P(( const char *arg, int *levelp, int which ));
+LDAP_SLAPD_F (int)
+slap_parse_debug_unknowns LDAP_P(( void ));
+LDAP_SLAPD_F (void)
+slap_check_unknown_level LDAP_P(( char *levelstr, int level ));
+LDAP_SLAPD_V(ldap_pvt_thread_mutex_t) logfile_mutex;
+LDAP_SLAPD_V(int) slap_debug_orig;
+LDAP_SLAPD_V (char *) serverName;
+
+/*
+ * main.c
+ */
+
+/*
+ * matchedValues.c
+ */
+LDAP_SLAPD_F (int) filter_matched_values(
+ Operation *op,
+ Attribute *a,
+ char ***e_flags );
+
+/*
+ * modrdn.c
+ */
+LDAP_SLAPD_F (int) slap_modrdn2mods LDAP_P((
+ Operation *op,
+ SlapReply *rs ));
+
+/*
+ * modify.c
+ */
+LDAP_SLAPD_F( int ) slap_mods_obsolete_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf, size_t textlen );
+
+LDAP_SLAPD_F( int ) slap_mods_no_user_mod_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf, size_t textlen );
+
+LDAP_SLAPD_F ( int ) slap_mods_no_repl_user_mod_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf,
+ size_t textlen );
+
+LDAP_SLAPD_F( int ) slap_mods_check(
+ Operation *op,
+ Modifications *ml,
+ const char **text,
+ char *textbuf, size_t textlen, void *ctx );
+
+LDAP_SLAPD_F( int ) slap_sort_vals(
+ Modifications *ml,
+ const char **text,
+ int *dup,
+ void *ctx );
+
+LDAP_SLAPD_F( void ) slap_timestamp(
+ time_t *tm,
+ struct berval *bv );
+
+LDAP_SLAPD_F( void ) slap_mods_opattrs(
+ Operation *op,
+ Modifications **modsp,
+ int manage_ctxcsn );
+
+LDAP_SLAPD_F( int ) slap_parse_modlist(
+ Operation *op,
+ SlapReply *rs,
+ BerElement *ber,
+ req_modify_s *ms );
+
+/*
+ * mods.c
+ */
+LDAP_SLAPD_F( int ) modify_add_values( Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text, char *textbuf, size_t textlen );
+LDAP_SLAPD_F( int ) modify_delete_values( Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text, char *textbuf, size_t textlen );
+LDAP_SLAPD_F( int ) modify_delete_vindex( Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text, char *textbuf, size_t textlen, int *idx );
+LDAP_SLAPD_F( int ) modify_replace_values( Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text, char *textbuf, size_t textlen );
+LDAP_SLAPD_F( int ) modify_increment_values( Entry *e,
+ Modification *mod,
+ int permissive,
+ const char **text, char *textbuf, size_t textlen );
+
+LDAP_SLAPD_F( void ) slap_mod_free( Modification *mod, int freeit );
+LDAP_SLAPD_F( void ) slap_mods_free( Modifications *mods, int freevals );
+LDAP_SLAPD_F( void ) slap_modlist_free( LDAPModList *ml );
+
+/*
+ * module.c
+ */
+#ifdef SLAPD_MODULES
+
+LDAP_SLAPD_F (int) module_init LDAP_P(( void ));
+LDAP_SLAPD_F (int) module_kill LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) load_null_module(
+ const void *module, const char *file_name);
+LDAP_SLAPD_F (int) load_extop_module(
+ const void *module, const char *file_name);
+
+LDAP_SLAPD_F (int) module_load LDAP_P((
+ const char* file_name,
+ int argc, char *argv[] ));
+LDAP_SLAPD_F (int) module_path LDAP_P(( const char* path ));
+LDAP_SLAPD_F (int) module_unload LDAP_P(( const char* file_name ));
+
+LDAP_SLAPD_F (void *) module_handle LDAP_P(( const char* file_name ));
+
+LDAP_SLAPD_F (void *) module_resolve LDAP_P((
+ const void *module, const char *name));
+
+#endif /* SLAPD_MODULES */
+
+/* mr.c */
+LDAP_SLAPD_F (MatchingRule *) mr_bvfind LDAP_P((struct berval *mrname));
+LDAP_SLAPD_F (MatchingRule *) mr_find LDAP_P((const char *mrname));
+LDAP_SLAPD_F (int) mr_add LDAP_P(( LDAPMatchingRule *mr,
+ slap_mrule_defs_rec *def,
+ MatchingRule * associated,
+ const char **err ));
+LDAP_SLAPD_F (void) mr_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) register_matching_rule LDAP_P((
+ slap_mrule_defs_rec *def ));
+
+LDAP_SLAPD_F (void) mru_destroy LDAP_P(( void ));
+LDAP_SLAPD_F (int) matching_rule_use_init LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) mr_schema_info LDAP_P(( Entry *e ));
+LDAP_SLAPD_F (int) mru_schema_info LDAP_P(( Entry *e ));
+
+LDAP_SLAPD_F (int) mr_usable_with_at LDAP_P(( MatchingRule *mr,
+ AttributeType *at ));
+LDAP_SLAPD_F (int) mr_make_syntax_compat_with_mr LDAP_P((
+ Syntax *syn,
+ MatchingRule *mr ));
+LDAP_SLAPD_F (int) mr_make_syntax_compat_with_mrs LDAP_P((
+ const char *syntax,
+ char *const *mrs ));
+
+/*
+ * mra.c
+ */
+LDAP_SLAPD_F (int) get_mra LDAP_P((
+ Operation *op,
+ BerElement *ber,
+ Filter *f,
+ const char **text ));
+LDAP_SLAPD_F (void) mra_free LDAP_P((
+ Operation *op,
+ MatchingRuleAssertion *mra,
+ int freeit ));
+
+/* oc.c */
+LDAP_SLAPD_F (int) oc_add LDAP_P((
+ LDAPObjectClass *oc,
+ int user,
+ ObjectClass **soc,
+ ObjectClass *prev,
+ const char **err));
+LDAP_SLAPD_F (void) oc_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (ObjectClass *) oc_find LDAP_P((
+ const char *ocname));
+LDAP_SLAPD_F (ObjectClass *) oc_bvfind LDAP_P((
+ struct berval *ocname));
+LDAP_SLAPD_F (ObjectClass *) oc_bvfind_undef LDAP_P((
+ struct berval *ocname));
+LDAP_SLAPD_F (int) is_object_subclass LDAP_P((
+ ObjectClass *sup,
+ ObjectClass *sub ));
+
+LDAP_SLAPD_F (int) is_entry_objectclass LDAP_P((
+ Entry *, ObjectClass *oc, unsigned flags ));
+#define is_entry_objectclass_or_sub(e,oc) \
+ (is_entry_objectclass((e),(oc),SLAP_OCF_CHECK_SUP))
+#define is_entry_alias(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_ALIAS) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_alias, SLAP_OCF_SET_FLAGS))
+#define is_entry_referral(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_REFERRAL) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_referral, SLAP_OCF_SET_FLAGS))
+#define is_entry_subentry(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_SUBENTRY) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_subentry, SLAP_OCF_SET_FLAGS))
+#define is_entry_collectiveAttributeSubentry(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_collectiveAttributeSubentry, SLAP_OCF_SET_FLAGS))
+#define is_entry_dynamicObject(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_DYNAMICOBJECT) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_dynamicObject, SLAP_OCF_SET_FLAGS))
+#define is_entry_glue(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_GLUE) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_glue, SLAP_OCF_SET_FLAGS))
+#define is_entry_syncProviderSubentry(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_SYNCPROVIDERSUBENTRY) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_syncProviderSubentry, SLAP_OCF_SET_FLAGS))
+#define is_entry_syncConsumerSubentry(e) \
+ (((e)->e_ocflags & SLAP_OC__END) \
+ ? (((e)->e_ocflags & SLAP_OC_SYNCCONSUMERSUBENTRY) != 0) \
+ : is_entry_objectclass((e), slap_schema.si_oc_syncConsumerSubentry, SLAP_OCF_SET_FLAGS))
+
+LDAP_SLAPD_F (int) oc_schema_info( Entry *e );
+
+LDAP_SLAPD_F (int) oc_start LDAP_P(( ObjectClass **oc ));
+LDAP_SLAPD_F (int) oc_next LDAP_P(( ObjectClass **oc ));
+LDAP_SLAPD_F (void) oc_delete LDAP_P(( ObjectClass *oc ));
+
+LDAP_SLAPD_F (void) oc_unparse LDAP_P((
+ BerVarray *bva, ObjectClass *start, ObjectClass *end, int system ));
+
+LDAP_SLAPD_F (int) register_oc LDAP_P((
+ const char *desc,
+ ObjectClass **oc,
+ int dupok ));
+
+/*
+ * oidm.c
+ */
+LDAP_SLAPD_F(char *) oidm_find(char *oid);
+LDAP_SLAPD_F (void) oidm_destroy LDAP_P(( void ));
+LDAP_SLAPD_F (void) oidm_unparse LDAP_P((
+ BerVarray *bva, OidMacro *start, OidMacro *end, int system ));
+LDAP_SLAPD_F (int) parse_oidm LDAP_P((
+ struct config_args_s *ca, int user, OidMacro **om ));
+
+/*
+ * operation.c
+ */
+LDAP_SLAPD_F (void) slap_op_init LDAP_P(( void ));
+LDAP_SLAPD_F (void) slap_op_destroy LDAP_P(( void ));
+LDAP_SLAPD_F (void) slap_op_groups_free LDAP_P(( Operation *op ));
+LDAP_SLAPD_F (void) slap_op_free LDAP_P(( Operation *op, void *ctx ));
+LDAP_SLAPD_F (void) slap_op_time LDAP_P(( time_t *t, int *n ));
+LDAP_SLAPD_F (Operation *) slap_op_alloc LDAP_P((
+ BerElement *ber, ber_int_t msgid,
+ ber_tag_t tag, ber_int_t id, void *ctx ));
+
+LDAP_SLAPD_F (slap_op_t) slap_req2op LDAP_P(( ber_tag_t tag ));
+
+/*
+ * operational.c
+ */
+LDAP_SLAPD_F (Attribute *) slap_operational_subschemaSubentry( Backend *be );
+LDAP_SLAPD_F (Attribute *) slap_operational_entryDN( Entry *e );
+LDAP_SLAPD_F (Attribute *) slap_operational_hasSubordinate( int has );
+
+/*
+ * overlays.c
+ */
+LDAP_SLAPD_F (int) overlay_init( void );
+
+/*
+ * passwd.c
+ */
+LDAP_SLAPD_F (SLAP_EXTOP_MAIN_FN) passwd_extop;
+
+LDAP_SLAPD_F (int) slap_passwd_check(
+ Operation *op,
+ Entry *e,
+ Attribute *a,
+ struct berval *cred,
+ const char **text );
+
+LDAP_SLAPD_F (void) slap_passwd_generate( struct berval * );
+
+LDAP_SLAPD_F (void) slap_passwd_hash(
+ struct berval *cred,
+ struct berval *hash,
+ const char **text );
+
+LDAP_SLAPD_F (void) slap_passwd_hash_type(
+ struct berval *cred,
+ struct berval *hash,
+ char *htype,
+ const char **text );
+
+LDAP_SLAPD_F (struct berval *) slap_passwd_return(
+ struct berval *cred );
+
+LDAP_SLAPD_F (int) slap_passwd_parse(
+ struct berval *reqdata,
+ struct berval *id,
+ struct berval *oldpass,
+ struct berval *newpass,
+ const char **text );
+
+LDAP_SLAPD_F (void) slap_passwd_init (void);
+
+/*
+ * phonetic.c
+ */
+LDAP_SLAPD_F (char *) phonetic LDAP_P(( char *s ));
+
+/*
+ * proxyp.c
+ */
+LDAP_SLAPD_F (int) proxyp LDAP_P((ber_socket_t sfd, Sockaddr *from));
+
+/*
+ * referral.c
+ */
+LDAP_SLAPD_F (int) validate_global_referral LDAP_P((
+ const char *url ));
+
+LDAP_SLAPD_F (BerVarray) get_entry_referrals LDAP_P((
+ Operation *op, Entry *e ));
+
+LDAP_SLAPD_F (BerVarray) referral_rewrite LDAP_P((
+ BerVarray refs,
+ struct berval *base,
+ struct berval *target,
+ int scope ));
+
+LDAP_SLAPD_F (int) get_alias_dn LDAP_P((
+ Entry *e,
+ struct berval *ndn,
+ int *err,
+ const char **text ));
+
+/*
+ * result.c
+ */
+#if USE_RS_ASSERT /*defined(USE_RS_ASSERT)?(USE_RS_ASSERT):defined(LDAP_TEST)*/
+#ifdef __GNUC__
+# define RS_FUNC_ __FUNCTION__
+#elif defined(__STDC_VERSION__) && (__STDC_VERSION__) >= 199901L
+# define RS_FUNC_ __func__
+#else
+# define rs_assert_(file, line, func, cond) rs_assert__(file, line, cond)
+#endif
+LDAP_SLAPD_V(int) rs_suppress_assert;
+LDAP_SLAPD_F(void) rs_assert_(const char*, unsigned, const char*, const char*);
+# define RS_ASSERT(cond) ((rs_suppress_assert > 0 || (cond)) \
+ ? (void) 0 : rs_assert_(__FILE__, __LINE__, RS_FUNC_, #cond))
+#else
+# define RS_ASSERT(cond) ((void) 0)
+# define rs_assert_ok(rs) ((void) (rs))
+# define rs_assert_ready(rs) ((void) (rs))
+# define rs_assert_done(rs) ((void) (rs))
+#endif
+LDAP_SLAPD_F (void) (rs_assert_ok) LDAP_P(( const SlapReply *rs ));
+LDAP_SLAPD_F (void) (rs_assert_ready) LDAP_P(( const SlapReply *rs ));
+LDAP_SLAPD_F (void) (rs_assert_done) LDAP_P(( const SlapReply *rs ));
+
+#define rs_reinit(rs, type) do { \
+ SlapReply *const rsRI = (rs); \
+ rs_assert_done( rsRI ); \
+ rsRI->sr_type = (type); \
+ /* Got type before memset in case of rs_reinit(rs, rs->sr_type) */ \
+ assert( !offsetof( SlapReply, sr_type ) ); \
+ memset( (slap_reply_t *) rsRI + 1, 0, \
+ sizeof(*rsRI) - sizeof(slap_reply_t) ); \
+ } while ( 0 )
+LDAP_SLAPD_F (void) (rs_reinit) LDAP_P(( SlapReply *rs, slap_reply_t type ));
+LDAP_SLAPD_F (void) rs_flush_entry LDAP_P(( Operation *op,
+ SlapReply *rs, slap_overinst *on ));
+LDAP_SLAPD_F (void) rs_replace_entry LDAP_P(( Operation *op,
+ SlapReply *rs, slap_overinst *on, Entry *e ));
+LDAP_SLAPD_F (int) rs_entry2modifiable LDAP_P(( Operation *op,
+ SlapReply *rs, slap_overinst *on ));
+#define rs_ensure_entry_modifiable rs_entry2modifiable /* older name */
+LDAP_SLAPD_F (void) slap_send_ldap_result LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (void) send_ldap_sasl LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (void) send_ldap_disconnect LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (void) slap_send_ldap_extended LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (void) slap_send_ldap_intermediate LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (void) slap_send_search_result LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) slap_send_search_reference LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) slap_send_search_entry LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) slap_null_cb LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) slap_freeself_cb LDAP_P(( Operation *op, SlapReply *rs ));
+
+LDAP_SLAPD_V( const struct berval ) slap_pre_read_bv;
+LDAP_SLAPD_V( const struct berval ) slap_post_read_bv;
+LDAP_SLAPD_F (int) slap_read_controls LDAP_P(( Operation *op, SlapReply *rs,
+ Entry *e, const struct berval *oid, LDAPControl **ctrl ));
+
+LDAP_SLAPD_F (int) str2result LDAP_P(( char *s,
+ int *code, char **matched, char **info ));
+LDAP_SLAPD_F (int) slap_map_api2result LDAP_P(( SlapReply *rs ));
+LDAP_SLAPD_F (slap_mask_t) slap_attr_flags LDAP_P(( AttributeName *an ));
+LDAP_SLAPD_F (ber_tag_t) slap_req2res LDAP_P(( ber_tag_t tag ));
+
+LDAP_SLAPD_V( const struct berval ) slap_dummy_bv;
+
+/*
+ * root_dse.c
+ */
+LDAP_SLAPD_F (int) root_dse_init LDAP_P(( void ));
+LDAP_SLAPD_F (int) root_dse_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) root_dse_info LDAP_P((
+ Connection *conn,
+ Entry **e,
+ const char **text ));
+
+LDAP_SLAPD_F (int) root_dse_read_file LDAP_P((
+ const char *file));
+
+LDAP_SLAPD_F (int) slap_discover_feature LDAP_P((
+ slap_bindconf *sb,
+ const char *attr,
+ const char *val ));
+
+LDAP_SLAPD_F (int) supported_feature_load LDAP_P(( struct berval *f ));
+LDAP_SLAPD_F (int) supported_feature_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) entry_info_register LDAP_P(( SLAP_ENTRY_INFO_FN func, void *arg ));
+LDAP_SLAPD_F (int) entry_info_unregister LDAP_P(( SLAP_ENTRY_INFO_FN func, void *arg ));
+LDAP_SLAPD_F (void) entry_info_destroy LDAP_P(( void ));
+
+/*
+ * sasl.c
+ */
+LDAP_SLAPD_F (int) slap_sasl_init(void);
+LDAP_SLAPD_F (char *) slap_sasl_secprops( const char * );
+LDAP_SLAPD_F (void) slap_sasl_secprops_unparse( struct berval * );
+LDAP_SLAPD_F (int) slap_sasl_destroy(void);
+
+LDAP_SLAPD_F (int) slap_sasl_open( Connection *c, int reopen );
+LDAP_SLAPD_F (char **) slap_sasl_mechs( Connection *c );
+
+LDAP_SLAPD_F (int) slap_sasl_external( Connection *c,
+ slap_ssf_t ssf, /* relative strength of external security */
+ struct berval *authid ); /* asserted authentication id */
+
+LDAP_SLAPD_F (int) slap_sasl_cbinding( Connection *c, void *ssl );
+
+LDAP_SLAPD_F (int) slap_sasl_reset( Connection *c );
+LDAP_SLAPD_F (int) slap_sasl_close( Connection *c );
+
+LDAP_SLAPD_F (int) slap_sasl_bind LDAP_P(( Operation *op, SlapReply *rs ));
+
+LDAP_SLAPD_F (int) slap_sasl_setpass(
+ Operation *op,
+ SlapReply *rs );
+
+LDAP_SLAPD_F (int) slap_sasl_getdn( Connection *conn, Operation *op,
+ struct berval *id, char *user_realm, struct berval *dn, int flags );
+
+/*
+ * saslauthz.c
+ */
+LDAP_SLAPD_F (int) slap_parse_user LDAP_P((
+ struct berval *id, struct berval *user,
+ struct berval *realm, struct berval *mech ));
+LDAP_SLAPD_F (int) slap_sasl_matches LDAP_P((
+ Operation *op, BerVarray rules,
+ struct berval *assertDN, struct berval *authc ));
+LDAP_SLAPD_F (void) slap_sasl2dn LDAP_P((
+ Operation *op,
+ struct berval *saslname,
+ struct berval *dn,
+ int flags ));
+LDAP_SLAPD_F (int) slap_sasl_authorized LDAP_P((
+ Operation *op,
+ struct berval *authcid,
+ struct berval *authzid ));
+LDAP_SLAPD_F (int) slap_sasl_regexp_config LDAP_P((
+ const char *match, const char *replace, int valx ));
+LDAP_SLAPD_F (void) slap_sasl_regexp_unparse LDAP_P(( BerVarray *bva ));
+LDAP_SLAPD_F (int) slap_sasl_setpolicy LDAP_P(( const char * ));
+LDAP_SLAPD_F (const char *) slap_sasl_getpolicy LDAP_P(( void ));
+LDAP_SLAPD_F (int) slap_sasl_rewrite_config LDAP_P((
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ int valx ));
+LDAP_SLAPD_F (int) slap_sasl_rewrite_delete LDAP_P(( int valx ));
+LDAP_SLAPD_F (int) slap_sasl_rewrite_unparse LDAP_P(( BerVarray *bva ));
+LDAP_SLAPD_F (void) slap_sasl_regexp_destroy LDAP_P(( void ));
+LDAP_SLAPD_F (int) slap_sasl_regexp_delete LDAP_P(( int valx ));
+LDAP_SLAPD_F (int) authzValidate LDAP_P((
+ Syntax *syn, struct berval *in ));
+#if 0
+LDAP_SLAPD_F (int) authzMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+#endif
+LDAP_SLAPD_F (int) authzPretty LDAP_P((
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx ));
+LDAP_SLAPD_F (int) authzNormalize LDAP_P((
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx ));
+
+/*
+ * schema.c
+ */
+LDAP_SLAPD_F (int) schema_info LDAP_P(( Entry **entry, const char **text ));
+
+/*
+ * schema_check.c
+ */
+LDAP_SLAPD_F( int ) oc_check_allowed(
+ AttributeType *type,
+ ObjectClass **socs,
+ ObjectClass *sc );
+
+LDAP_SLAPD_F( int ) structural_class(
+ BerVarray ocs,
+ ObjectClass **sc,
+ ObjectClass ***socs,
+ const char **text,
+ char *textbuf, size_t textlen, void *ctx );
+
+LDAP_SLAPD_F( int ) entry_schema_check(
+ Operation *op,
+ Entry *e,
+ Attribute *attrs,
+ int manage,
+ int add,
+ Attribute **socp,
+ const char** text,
+ char *textbuf, size_t textlen );
+
+LDAP_SLAPD_F( int ) mods_structural_class(
+ Modifications *mods,
+ struct berval *oc,
+ const char** text,
+ char *textbuf, size_t textlen, void *ctx );
+
+/*
+ * schema_init.c
+ */
+LDAP_SLAPD_V( int ) schema_init_done;
+LDAP_SLAPD_F (int) slap_schema_init LDAP_P((void));
+LDAP_SLAPD_F (void) schema_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) slap_hash64 LDAP_P((int));
+
+LDAP_SLAPD_F( slap_mr_indexer_func ) octetStringIndexer;
+LDAP_SLAPD_F( slap_mr_filter_func ) octetStringFilter;
+
+LDAP_SLAPD_F( int ) numericoidValidate LDAP_P((
+ Syntax *syntax,
+ struct berval *in ));
+LDAP_SLAPD_F( int ) numericStringValidate LDAP_P((
+ Syntax *syntax,
+ struct berval *in ));
+LDAP_SLAPD_F( int ) octetStringMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+LDAP_SLAPD_F( int ) octetStringOrderingMatch LDAP_P((
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue ));
+
+/*
+ * schema_prep.c
+ */
+LDAP_SLAPD_V( struct slap_internal_schema ) slap_schema;
+LDAP_SLAPD_F (int) slap_schema_load LDAP_P((void));
+LDAP_SLAPD_F (int) slap_schema_check LDAP_P((void));
+
+/*
+ * schemaparse.c
+ */
+LDAP_SLAPD_F( int ) slap_valid_descr( const char * );
+
+LDAP_SLAPD_F (int) parse_cr LDAP_P((
+ struct config_args_s *ca, ContentRule **scr ));
+LDAP_SLAPD_F (int) parse_oc LDAP_P((
+ struct config_args_s *ca, ObjectClass **soc, ObjectClass *prev ));
+LDAP_SLAPD_F (int) parse_at LDAP_P((
+ struct config_args_s *ca, AttributeType **sat, AttributeType *prev ));
+LDAP_SLAPD_F (char *) scherr2str LDAP_P((int code)) LDAP_GCCATTR((const));
+LDAP_SLAPD_F (int) dscompare LDAP_P(( const char *s1, const char *s2del,
+ char delim ));
+LDAP_SLAPD_F (int) parse_syn LDAP_P((
+ struct config_args_s *ca, Syntax **sat, Syntax *prev ));
+
+/*
+ * sessionlog.c
+ */
+LDAP_SLAPD_F (int) slap_send_session_log LDAP_P((
+ Operation *, Operation *, SlapReply *));
+LDAP_SLAPD_F (int) slap_add_session_log LDAP_P((
+ Operation *, Operation *, Entry * ));
+
+/*
+ * sl_malloc.c
+ */
+LDAP_SLAPD_F (void *) slap_sl_malloc LDAP_P((
+ ber_len_t size, void *ctx ));
+LDAP_SLAPD_F (void *) slap_sl_realloc LDAP_P((
+ void *block, ber_len_t size, void *ctx ));
+LDAP_SLAPD_F (void *) slap_sl_calloc LDAP_P((
+ ber_len_t nelem, ber_len_t size, void *ctx ));
+LDAP_SLAPD_F (void) slap_sl_free LDAP_P((
+ void *, void *ctx ));
+LDAP_SLAPD_F (void) slap_sl_release LDAP_P((
+ void *, void *ctx ));
+LDAP_SLAPD_F (void *) slap_sl_mark LDAP_P((
+ void *ctx ));
+
+
+LDAP_SLAPD_V (BerMemoryFunctions) slap_sl_mfuncs;
+
+LDAP_SLAPD_F (void) slap_sl_mem_init LDAP_P(( void ));
+LDAP_SLAPD_F (void *) slap_sl_mem_create LDAP_P((
+ ber_len_t size, int stack, void *ctx, int flag ));
+LDAP_SLAPD_F (void) slap_sl_mem_setctx LDAP_P(( void *ctx, void *memctx ));
+LDAP_SLAPD_F (void) slap_sl_mem_destroy LDAP_P(( void *key, void *data ));
+LDAP_SLAPD_F (void *) slap_sl_context LDAP_P(( void *ptr ));
+
+/*
+ * starttls.c
+ */
+LDAP_SLAPD_F (SLAP_EXTOP_MAIN_FN) starttls_extop;
+
+/*
+ * str2filter.c
+ */
+LDAP_SLAPD_F (Filter *) str2filter LDAP_P(( const char *str ));
+LDAP_SLAPD_F (Filter *) str2filter_x LDAP_P(( Operation *op, const char *str ));
+
+/*
+ * syncrepl.c
+ */
+
+LDAP_SLAPD_F (int) syncrepl_add_glue LDAP_P((
+ Operation*, Entry* ));
+LDAP_SLAPD_F (void) syncrepl_diff_entry LDAP_P((
+ Operation *op, Attribute *old, Attribute *anew,
+ Modifications **mods, Modifications **ml, int is_ctx ));
+LDAP_SLAPD_F (void) syncinfo_free LDAP_P(( struct syncinfo_s *, int all ));
+
+/* syntax.c */
+LDAP_SLAPD_F (int) syn_is_sup LDAP_P((
+ Syntax *syn,
+ Syntax *sup ));
+LDAP_SLAPD_F (Syntax *) syn_find LDAP_P((
+ const char *synname ));
+LDAP_SLAPD_F (Syntax *) syn_find_desc LDAP_P((
+ const char *syndesc, int *slen ));
+LDAP_SLAPD_F (int) syn_add LDAP_P((
+ LDAPSyntax *syn,
+ int user,
+ slap_syntax_defs_rec *def,
+ Syntax **ssyn,
+ Syntax *prev,
+ const char **err ));
+LDAP_SLAPD_F (void) syn_destroy LDAP_P(( void ));
+
+LDAP_SLAPD_F (int) register_syntax LDAP_P((
+ slap_syntax_defs_rec *def ));
+
+LDAP_SLAPD_F (int) syn_schema_info( Entry *e );
+
+LDAP_SLAPD_F (int) syn_start LDAP_P(( Syntax **at ));
+LDAP_SLAPD_F (int) syn_next LDAP_P(( Syntax **at ));
+LDAP_SLAPD_F (void) syn_delete LDAP_P(( Syntax *at ));
+
+LDAP_SLAPD_F (void) syn_unparse LDAP_P((
+ BerVarray *bva, Syntax *start, Syntax *end, int system ));
+
+/*
+ * user.c
+ */
+#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
+LDAP_SLAPD_F (void) slap_init_user LDAP_P(( char *username, char *groupname ));
+#endif
+
+/*
+ * value.c
+ */
+LDAP_SLAPD_F (int) asserted_value_validate_normalize LDAP_P((
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned usage,
+ struct berval *in,
+ struct berval *out,
+ const char ** text,
+ void *ctx ));
+
+LDAP_SLAPD_F (int) value_match LDAP_P((
+ int *match,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned flags,
+ struct berval *v1,
+ void *v2,
+ const char ** text ));
+LDAP_SLAPD_F (int) value_find_ex LDAP_P((
+ AttributeDescription *ad,
+ unsigned flags,
+ BerVarray values,
+ struct berval *value,
+ void *ctx ));
+
+LDAP_SLAPD_F (int) ordered_value_add LDAP_P((
+ Entry *e,
+ AttributeDescription *ad,
+ Attribute *a,
+ BerVarray vals,
+ BerVarray nvals ));
+
+LDAP_SLAPD_F (int) ordered_value_validate LDAP_P((
+ AttributeDescription *ad,
+ struct berval *in,
+ int mop ));
+
+LDAP_SLAPD_F (int) ordered_value_pretty LDAP_P((
+ AttributeDescription *ad,
+ struct berval *val,
+ struct berval *out,
+ void *ctx ));
+
+LDAP_SLAPD_F (int) ordered_value_normalize LDAP_P((
+ slap_mask_t usage,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx ));
+
+LDAP_SLAPD_F (int) ordered_value_match LDAP_P((
+ int *match,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned flags,
+ struct berval *v1,
+ struct berval *v2,
+ const char ** text ));
+
+LDAP_SLAPD_F (void) ordered_value_renumber LDAP_P((
+ Attribute *a ));
+
+LDAP_SLAPD_F (int) ordered_value_sort LDAP_P((
+ Attribute *a,
+ int do_renumber ));
+
+LDAP_SLAPD_F (int) value_add LDAP_P((
+ BerVarray *vals,
+ BerVarray addvals ));
+LDAP_SLAPD_F (int) value_add_one LDAP_P((
+ BerVarray *vals,
+ struct berval *addval ));
+
+/* assumes (x) > (y) returns 1 if true, 0 otherwise */
+#define SLAP_PTRCMP(x, y) ((x) < (y) ? -1 : (x) > (y))
+
+/*
+ * verbs.c
+ */
+LDAP_SLAPD_F (int) bverb_to_mask LDAP_P((
+ struct berval *bword, slap_verbmasks *v ));
+LDAP_SLAPD_F (int) verb_to_mask LDAP_P((
+ const char *word, slap_verbmasks *v ));
+LDAP_SLAPD_F (int) verbs_to_mask LDAP_P((
+ int argc, char *argv[], slap_verbmasks *v, slap_mask_t *m ));
+LDAP_SLAPD_F (int) mask_to_verbs LDAP_P((
+ slap_verbmasks *v, slap_mask_t m, BerVarray *bva ));
+LDAP_SLAPD_F (int) mask_to_verbstring LDAP_P((
+ slap_verbmasks *v, slap_mask_t m, char delim, struct berval *bv ));
+LDAP_SLAPD_F (int) verbstring_to_mask LDAP_P((
+ slap_verbmasks *v, char *str, char delim, slap_mask_t *m ));
+LDAP_SLAPD_F (int) enum_to_verb LDAP_P((
+ slap_verbmasks *v, slap_mask_t m, struct berval *bv ));
+LDAP_SLAPD_F (int) slap_verbmasks_init LDAP_P(( slap_verbmasks **vp, slap_verbmasks *v ));
+LDAP_SLAPD_F (int) slap_verbmasks_destroy LDAP_P(( slap_verbmasks *v ));
+LDAP_SLAPD_F (int) slap_verbmasks_append LDAP_P(( slap_verbmasks **vp,
+ slap_mask_t m, struct berval *v, slap_mask_t *ignore ));
+LDAP_SLAPD_F (int) slap_verbmask_register LDAP_P(( slap_verbmasks *vm_,
+ slap_verbmasks **vmp, struct berval *bv, int mask ));
+
+#ifdef SLAP_ZONE_ALLOC
+/*
+ * zn_malloc.c
+ */
+LDAP_SLAPD_F (void *) slap_zn_malloc LDAP_P((ber_len_t, void *));
+LDAP_SLAPD_F (void *) slap_zn_realloc LDAP_P((void *, ber_len_t, void *));
+LDAP_SLAPD_F (void *) slap_zn_calloc LDAP_P((ber_len_t, ber_len_t, void *));
+LDAP_SLAPD_F (void) slap_zn_free LDAP_P((void *, void *));
+
+LDAP_SLAPD_F (void *) slap_zn_mem_create LDAP_P((
+ ber_len_t, ber_len_t, ber_len_t, ber_len_t));
+LDAP_SLAPD_F (void) slap_zn_mem_destroy LDAP_P((void *));
+LDAP_SLAPD_F (int) slap_zn_validate LDAP_P((void *, void *, int));
+LDAP_SLAPD_F (int) slap_zn_invalidate LDAP_P((void *, void *));
+LDAP_SLAPD_F (int) slap_zh_rlock LDAP_P((void*));
+LDAP_SLAPD_F (int) slap_zh_runlock LDAP_P((void*));
+LDAP_SLAPD_F (int) slap_zh_wlock LDAP_P((void*));
+LDAP_SLAPD_F (int) slap_zh_wunlock LDAP_P((void*));
+LDAP_SLAPD_F (int) slap_zn_rlock LDAP_P((void*, void*));
+LDAP_SLAPD_F (int) slap_zn_runlock LDAP_P((void*, void*));
+LDAP_SLAPD_F (int) slap_zn_wlock LDAP_P((void*, void*));
+LDAP_SLAPD_F (int) slap_zn_wunlock LDAP_P((void*, void*));
+#endif
+
+/*
+ * Other...
+ */
+LDAP_SLAPD_V (unsigned int) index_substr_if_minlen;
+LDAP_SLAPD_V (unsigned int) index_substr_if_maxlen;
+LDAP_SLAPD_V (unsigned int) index_substr_any_len;
+LDAP_SLAPD_V (unsigned int) index_substr_any_step;
+LDAP_SLAPD_V (unsigned int) index_intlen;
+/* all signed integers from strings of this size need more than intlen bytes */
+/* i.e. log(10)*(index_intlen_strlen-2) > log(2)*(8*(index_intlen)-1) */
+LDAP_SLAPD_V (unsigned int) index_intlen_strlen;
+#define SLAP_INDEX_INTLEN_STRLEN(intlen) ((8*(intlen)-1) * 146/485 + 3)
+
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming;
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_auth;
+LDAP_SLAPD_V (int) slap_conn_max_pending;
+LDAP_SLAPD_V (int) slap_conn_max_pending_auth;
+LDAP_SLAPD_V (int) slap_max_filter_depth;
+
+LDAP_SLAPD_V (slap_mask_t) global_allows;
+LDAP_SLAPD_V (slap_mask_t) global_disallows;
+
+LDAP_SLAPD_V (BerVarray) default_referral;
+LDAP_SLAPD_V (const char) Versionstr[];
+
+LDAP_SLAPD_V (int) global_gentlehup;
+LDAP_SLAPD_V (int) global_idletimeout;
+LDAP_SLAPD_V (int) global_writetimeout;
+LDAP_SLAPD_V (char *) global_host;
+LDAP_SLAPD_V (struct berval) global_host_bv;
+LDAP_SLAPD_V (char *) global_realm;
+LDAP_SLAPD_V (char *) sasl_host;
+LDAP_SLAPD_V (char *) sasl_cbinding;
+LDAP_SLAPD_V (char *) slap_sasl_auxprops;
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+LDAP_SLAPD_V (int) slap_dontUseCopy_ignore;
+LDAP_SLAPD_V (BerVarray) slap_dontUseCopy_propnames;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+LDAP_SLAPD_V (char **) default_passwd_hash;
+LDAP_SLAPD_V (int) lber_debug;
+LDAP_SLAPD_V (int) ldap_syslog;
+LDAP_SLAPD_V (struct berval) default_search_base;
+LDAP_SLAPD_V (struct berval) default_search_nbase;
+
+LDAP_SLAPD_V (slap_counters_t) slap_counters;
+
+LDAP_SLAPD_V (char *) slapd_pid_file;
+LDAP_SLAPD_V (char *) slapd_args_file;
+LDAP_SLAPD_V (time_t) starttime;
+
+/* use time(3) -- no mutex */
+#define slap_get_time() time( NULL )
+
+LDAP_SLAPD_V (ldap_pvt_thread_pool_t) connection_pool;
+LDAP_SLAPD_V (int) connection_pool_max;
+LDAP_SLAPD_V (int) connection_pool_queues;
+LDAP_SLAPD_V (int) slap_tool_thread_max;
+
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) entry2str_mutex;
+
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) ad_index_mutex;
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) ad_undef_mutex;
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) oc_undef_mutex;
+
+LDAP_SLAPD_V (ber_socket_t) dtblsize;
+
+LDAP_SLAPD_V (int) use_reverse_lookup;
+
+/*
+ * operations
+ */
+LDAP_SLAPD_F (int) do_abandon LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_add LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_bind LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_compare LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_delete LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_modify LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_modrdn LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_search LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_unbind LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) do_extended LDAP_P((Operation *op, SlapReply *rs));
+
+/*
+ * frontend operations
+ */
+LDAP_SLAPD_F (int) fe_op_abandon LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_add LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_bind LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_bind_success LDAP_P(( Operation *op, SlapReply *rs ));
+LDAP_SLAPD_F (int) fe_op_compare LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_delete LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_modify LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_modrdn LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_op_search LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_aux_operational LDAP_P((Operation *op, SlapReply *rs));
+#if 0
+LDAP_SLAPD_F (int) fe_op_unbind LDAP_P((Operation *op, SlapReply *rs));
+#endif
+LDAP_SLAPD_F (int) fe_extended LDAP_P((Operation *op, SlapReply *rs));
+LDAP_SLAPD_F (int) fe_acl_group LDAP_P((
+ Operation *op,
+ Entry *target,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at ));
+LDAP_SLAPD_F (int) fe_acl_attribute LDAP_P((
+ Operation *op,
+ Entry *target,
+ struct berval *edn,
+ AttributeDescription *entry_at,
+ BerVarray *vals,
+ slap_access_t access ));
+LDAP_SLAPD_F (int) fe_access_allowed LDAP_P((
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp ));
+
+/* NOTE: this macro assumes that bv has been allocated
+ * by ber_* malloc functions or is { 0L, NULL } */
+#ifdef USE_MP_BIGNUM
+# define UI2BVX(bv,ui,ctx) \
+ do { \
+ char *val; \
+ ber_len_t len; \
+ val = BN_bn2dec(ui); \
+ if (val) { \
+ len = strlen(val); \
+ if ( len > (bv)->bv_len ) { \
+ (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \
+ } \
+ AC_MEMCPY((bv)->bv_val, val, len + 1); \
+ (bv)->bv_len = len; \
+ OPENSSL_free(val); \
+ } else { \
+ ber_memfree_x( (bv)->bv_val, (ctx) ); \
+ BER_BVZERO( (bv) ); \
+ } \
+ } while ( 0 )
+
+#elif defined( USE_MP_GMP )
+/* NOTE: according to the documentation, the result
+ * of mpz_sizeinbase() can exceed the length of the
+ * string representation of the number by 1
+ */
+# define UI2BVX(bv,ui,ctx) \
+ do { \
+ ber_len_t len = mpz_sizeinbase( (ui), 10 ); \
+ if ( len > (bv)->bv_len ) { \
+ (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \
+ } \
+ (void)mpz_get_str( (bv)->bv_val, 10, (ui) ); \
+ if ( (bv)->bv_val[ len - 1 ] == '\0' ) { \
+ len--; \
+ } \
+ (bv)->bv_len = len; \
+ } while ( 0 )
+
+#else
+# ifdef USE_MP_LONG_LONG
+# define UI2BV_FORMAT "%llu"
+# elif defined USE_MP_LONG
+# define UI2BV_FORMAT "%lu"
+# elif defined HAVE_LONG_LONG
+# define UI2BV_FORMAT "%llu"
+# else
+# define UI2BV_FORMAT "%lu"
+# endif
+
+# define UI2BVX(bv,ui,ctx) \
+ do { \
+ char buf[LDAP_PVT_INTTYPE_CHARS(long)]; \
+ ber_len_t len; \
+ len = snprintf( buf, sizeof( buf ), UI2BV_FORMAT, (ui) ); \
+ if ( len > (bv)->bv_len ) { \
+ (bv)->bv_val = ber_memrealloc_x( (bv)->bv_val, len + 1, (ctx) ); \
+ } \
+ (bv)->bv_len = len; \
+ AC_MEMCPY( (bv)->bv_val, buf, len + 1 ); \
+ } while ( 0 )
+#endif
+
+#define UI2BV(bv,ui) UI2BVX(bv,ui,NULL)
+
+LDAP_END_DECL
+
+#endif /* PROTO_SLAP_H */
diff --git a/servers/slapd/proxyp.c b/servers/slapd/proxyp.c
new file mode 100644
index 0000000..c548bca
--- /dev/null
+++ b/servers/slapd/proxyp.c
@@ -0,0 +1,226 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#include "slap.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#include <lber_types.h>
+#include <ac/string.h>
+#include <ac/errno.h>
+
+typedef struct {
+ uint8_t sig[12]; /* hex 0d 0a 0d 0a 00 0d 0a 51 55 49 54 0a */
+ uint8_t ver_cmd; /* protocol version and command */
+ uint8_t fam; /* protocol family and address */
+ uint16_t len; /* length of address data */
+} proxyp_header;
+
+typedef union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unx;
+} proxyp_addr;
+
+static const uint8_t proxyp_sig[12] = {
+ 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a,
+};
+
+int
+proxyp( ber_socket_t sfd, Sockaddr *from ) {
+ proxyp_header pph;
+ proxyp_addr ppa;
+ char peername[LDAP_IPADDRLEN];
+ struct berval peerbv = BER_BVC(peername);
+ /* Maximum size of header minus static component size is max option size */
+ uint8_t proxyp_options[536 - 16];
+ int pph_len;
+ int ret;
+
+ peername[0] = '\0';
+
+ do {
+ ret = tcp_read( SLAP_FD2SOCK( sfd ), &pph, sizeof(pph) );
+ } while ( ret == -1 && errno == EINTR );
+
+ if ( ret == -1 ) {
+ char ebuf[128];
+ int save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "header read failed %d (%s)\n",
+ (long)sfd, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ return 0;
+ } else if ( ret != sizeof(pph) ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "header read insufficient data %d\n",
+ (long)sfd, ret );
+ return 0;
+ }
+
+ if ( memcmp( pph.sig, proxyp_sig, 12 ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "invalid header signature\n", (long)sfd );
+ return 0;
+ }
+
+ if ( ( pph.ver_cmd & 0xF0 ) != 0x20 ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "invalid header version %x\n",
+ (long)sfd, pph.ver_cmd & 0xF0 );
+ return 0;
+ }
+
+ pph_len = ntohs( pph.len );
+ if ( ( pph.ver_cmd & 0x0F ) == 0x01 ) { /* PROXY command */
+ int addr_len;
+ switch ( pph.fam ) {
+ case 0x11: /* TCPv4 */
+ addr_len = sizeof( ppa.ip4 );
+ break;
+ case 0x21: /* TCPv6 */
+ addr_len = sizeof( ppa.ip6 );
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "unsupported protocol %x\n",
+ (long)sfd, pph.fam );
+ return 0;
+ }
+
+ if ( pph_len < addr_len ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "address length %d too small, expecting %d\n",
+ (long)sfd, pph_len, addr_len );
+ return 0;
+ }
+
+ do {
+ ret = tcp_read( SLAP_FD2SOCK (sfd), &ppa, addr_len );
+ } while ( ret == -1 && errno == EINTR );
+
+ if ( ret == -1 ) {
+ char ebuf[128];
+ int save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "address read failed %d (%s)\n",
+ (long)sfd, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ return 0;
+ } else if ( ret != addr_len ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "address read insufficient data, expecting %d, read %d\n",
+ (long)sfd, addr_len, ret );
+ return 0;
+ }
+
+ pph_len -= addr_len;
+ }
+
+ switch ( pph.ver_cmd & 0x0F ) {
+ case 0x01: /* PROXY command */
+ switch ( pph.fam ) {
+ case 0x11: /* TCPv4 */
+ ldap_pvt_sockaddrstr( from, &peerbv );
+ Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
+ (long)sfd, peername );
+
+ from->sa_in_addr.sin_family = AF_INET;
+ from->sa_in_addr.sin_addr.s_addr = ppa.ip4.src_addr;
+ from->sa_in_addr.sin_port = ppa.ip4.src_port;
+ break;
+
+ case 0x21: /* TCPv6 */
+#ifdef LDAP_PF_INET6
+ ldap_pvt_sockaddrstr( from, &peerbv );
+ Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
+ (long)sfd, peername );
+ from->sa_in6_addr.sin6_family = AF_INET6;
+ memcpy( &from->sa_in6_addr.sin6_addr, ppa.ip6.src_addr,
+ sizeof(ppa.ip6.src_addr) );
+ from->sa_in6_addr.sin6_port = ppa.ip6.src_port;
+#else
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "IPv6 proxied addresses disabled\n",
+ (long)sfd );
+ return 0;
+#endif
+ break;
+ }
+
+ break;
+
+ case 0x00: /* LOCAL command */
+ Debug( LDAP_DEBUG_CONNS, "proxyp(%ld): "
+ "local connection, ignoring proxy data\n",
+ (long)sfd );
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): invalid command %x\n",
+ (long)sfd, pph.ver_cmd & 0x0F );
+ return 0;
+ }
+
+ /* Clear out any options left in proxy packet */
+ if ( pph_len > 0 ) {
+ if (pph_len > sizeof( proxyp_options ) ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "options size %d too big\n",
+ (long)sfd, pph_len );
+ return 0;
+ }
+
+ do {
+ ret = tcp_read( SLAP_FD2SOCK (sfd), &proxyp_options, pph_len );
+ } while ( ret == -1 && errno == EINTR );
+
+ if ( ret == -1 ) {
+ char ebuf[128];
+ int save_errno = errno;
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "options read failed %d (%s)\n",
+ (long)sfd, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ return 0;
+ } else if ( ret != pph_len ) {
+ Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
+ "options read insufficient data, expecting %d, read %d\n",
+ (long)sfd, pph_len, ret );
+ return 0;
+ }
+ }
+
+ return 1;
+}
diff --git a/servers/slapd/pwmods/Makefile.in b/servers/slapd/pwmods/Makefile.in
new file mode 100644
index 0000000..e8d76b8
--- /dev/null
+++ b/servers/slapd/pwmods/Makefile.in
@@ -0,0 +1,59 @@
+# Makefile.in for overlays
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2003-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = argon2.c
+
+LTONLY_MOD = $(LTONLY_mod)
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+MOD_DEFS = -DSLAPD_IMPORT
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBRARY = dummyvalue
+PROGRAMS = @SLAPD_DYNAMIC_PWMODS@
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+dynamic: $(PROGRAMS)
+
+argon2.la : argon2.lo version.lo
+ $(LTLINK_MOD) -module -o $@ argon2.lo version.lo $(ARGON2_LIBS) $(LINK_LIBS) $(MODULES_LIBS)
+
+install-local: $(PROGRAMS)
+ @if test -n "$?" ; then \
+ $(MKDIR) $(DESTDIR)$(moduledir); \
+ $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\
+ fi
+
+MKDEPFLAG = -l
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+ $(LTCOMPILE_MOD) $<
+
+# Must fixup depends for non-libtool objects
+depend-local: depend-common
+ @if test -n "$(OBJS)"; then \
+ OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \
+ SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \
+ mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \
+ $(RM) Makefile.bak; fi
+
diff --git a/servers/slapd/pwmods/README.argon2 b/servers/slapd/pwmods/README.argon2
new file mode 100644
index 0000000..33c562b
--- /dev/null
+++ b/servers/slapd/pwmods/README.argon2
@@ -0,0 +1,97 @@
+Argon2 OpenLDAP support
+----------------------
+
+argon2.c provides support for ARGON2 hashed passwords in OpenLDAP. For
+instance, one could have the LDAP attribute:
+
+userPassword: {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$DKlexoEJUoZTmkAAC3SaMWk30El9/RvVhlqGo6afIng
+
+or:
+
+userPassword: {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$qOCkx9nMeFlaGOO4DUmPDgrlUbgMMuO9T1+vQCFuyzw
+
+Both hash the password "secret", the first using the salt "saltsalt", the second using the salt "saltsaltsalt"
+
+Building
+--------
+
+This module is now part of regular OpenLDAP build process and enabled if
+libsodium or libargon2 library and the corresponding development headers are
+present. You can also choose the library to build against if you prefer by
+passing --enable-argon2=<libsodium|libargon2> to your configure invocation.
+
+Configuring
+-----------
+
+Load the argon2 module (e.g. with olcModuleLoad or moduleload) and the
+{ARGON2} password scheme should now be recognised.
+
+You can also tell OpenLDAP to use one of this scheme when processing LDAP
+Password Modify Extended Operations, thanks to the password-hash option in
+slapd.conf:
+
+password-hash {ARGON2}
+
+
+Testing
+-------
+
+A quick way to test whether it's working is to customize the rootdn and
+rootpw in slapd.conf, eg:
+
+rootdn "cn=admin,dc=example,dc=com"
+
+# This hashes the string 'secret', with a random salt
+rootpw {ARGON2}$argon2i$v=19$m=4096,t=3,p=1$uJyf0UfB25SQTfX7oCyK2w$U45DJqEFwD0yFaLvTVyACHLvGMwzNGf19dvzPR8XvGc
+
+
+Then to test, run something like:
+
+ldapsearch -b "dc=example,dc=com" -D "cn=admin,dc=example,dc=com" -x -w secret
+
+
+-- Test hashes:
+
+Test hashes can be generated with argon2:
+$ echo -n "secret" | argon2 "saltsalt" -e
+$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$DKlexoEJUoZTmkAAC3SaMWk30El9/RvVhlqGo6afIng
+
+$ echo -n "secret" | argon2 "saltsaltsalt" -e
+$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$qOCkx9nMeFlaGOO4DUmPDgrlUbgMMuO9T1+vQCFuyzw
+
+$ echo -n "secretsecret" | argon2 "saltsalt" -e
+$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHQ$U0Pd/wEsssZ9bHezDA8oxHnWe01xftykEy+7ehM2vic
+
+$ echo -n "secretsecret" | argon2 "saltsaltsalt" -e
+$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0$fkvoOwKgVtlX9ZDqcHFyyArBvqnAM0Igca8SScB4Jsc
+
+
+
+Alternatively we could modify an existing user's password with
+ldappasswd, and then test binding as that user:
+
+$ ldappasswd -D "cn=admin,dc=example,dc=com" -x -W -S uid=jturner,ou=People,dc=example,dc=com
+New password: secret
+Re-enter new password: secret
+Enter LDAP Password: <cn=admin's password>
+
+$ ldapsearch -b "dc=example,dc=com" -D "uid=jturner,ou=People,dc=example,dc=com" -x -w secret
+
+
+
+---
+
+This work is part of OpenLDAP Software <http://www.openldap.org/>.
+
+Copyright 2017-2022 The OpenLDAP Foundation.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted only as authorized by the OpenLDAP
+Public License.
+
+A copy of this license is available in the file LICENSE in the
+top-level directory of the distribution or, alternatively, at
+<http://www.OpenLDAP.org/license.html>.
+
+---
diff --git a/servers/slapd/pwmods/argon2.c b/servers/slapd/pwmods/argon2.c
new file mode 100644
index 0000000..f3f7b4c
--- /dev/null
+++ b/servers/slapd/pwmods/argon2.c
@@ -0,0 +1,240 @@
+/* argon2.c - Password module for argon2 */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2017-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#ifdef SLAPD_PWMOD_PW_ARGON2
+#include "ac/string.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+
+#include "slap.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef HAVE_LIBARGON2
+#include <argon2.h>
+
+/*
+ * For now, we hardcode the default values from the argon2 command line tool
+ * (as of argon2 release 20161029)
+ */
+#define SLAPD_ARGON2_ITERATIONS 3
+#define SLAPD_ARGON2_MEMORY (1 << 12)
+#define SLAPD_ARGON2_PARALLELISM 1
+#define SLAPD_ARGON2_SALT_LENGTH 16
+#define SLAPD_ARGON2_HASH_LENGTH 32
+
+#else /* !HAVE_LIBARGON2 */
+#include <sodium.h>
+
+/*
+ * Or libsodium interactive settings
+ */
+#define SLAPD_ARGON2_ITERATIONS crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE
+#define SLAPD_ARGON2_MEMORY (crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE / 1024)
+#define SLAPD_ARGON2_PARALLELISM 1
+#define SLAPD_ARGON2_SALT_LENGTH crypto_pwhash_argon2id_SALTBYTES
+#define SLAPD_ARGON2_HASH_LENGTH 32
+
+#endif
+
+static unsigned long iterations = SLAPD_ARGON2_ITERATIONS;
+static unsigned long memory = SLAPD_ARGON2_MEMORY;
+static unsigned long parallelism = SLAPD_ARGON2_PARALLELISM;
+
+const struct berval slapd_argon2_scheme = BER_BVC("{ARGON2}");
+
+static int
+slapd_argon2_hash(
+ const struct berval *scheme,
+ const struct berval *passwd,
+ struct berval *hash,
+ const char **text )
+{
+
+ /*
+ * Duplicate these values here so future code which allows
+ * configuration has an easier time.
+ */
+ uint32_t salt_length, hash_length;
+ char *p;
+ int rc = LUTIL_PASSWD_ERR;
+
+#ifdef HAVE_LIBARGON2
+ struct berval salt;
+ size_t encoded_length;
+
+ salt_length = SLAPD_ARGON2_SALT_LENGTH;
+ hash_length = SLAPD_ARGON2_HASH_LENGTH;
+
+ encoded_length = argon2_encodedlen( iterations, memory, parallelism,
+ salt_length, hash_length, Argon2_id );
+
+ salt.bv_len = salt_length;
+ salt.bv_val = ber_memalloc( salt.bv_len );
+
+ if ( salt.bv_val == NULL ) {
+ return LUTIL_PASSWD_ERR;
+ }
+
+ if ( lutil_entropy( (unsigned char*)salt.bv_val, salt.bv_len ) ) {
+ ber_memfree( salt.bv_val );
+ return LUTIL_PASSWD_ERR;
+ }
+
+ p = hash->bv_val = ber_memalloc( scheme->bv_len + encoded_length );
+ if ( p == NULL ) {
+ ber_memfree( salt.bv_val );
+ return LUTIL_PASSWD_ERR;
+ }
+
+ AC_MEMCPY( p, scheme->bv_val, scheme->bv_len );
+ p += scheme->bv_len;
+
+ /*
+ * Do the actual heavy lifting
+ */
+ if ( argon2i_hash_encoded( iterations, memory, parallelism,
+ passwd->bv_val, passwd->bv_len,
+ salt.bv_val, salt_length, hash_length,
+ p, encoded_length ) == 0 ) {
+ rc = LUTIL_PASSWD_OK;
+ }
+ hash->bv_len = scheme->bv_len + encoded_length;
+ ber_memfree( salt.bv_val );
+
+#else /* !HAVE_LIBARGON2 */
+ /* Not exposed by libsodium
+ salt_length = SLAPD_ARGON2_SALT_LENGTH;
+ hash_length = SLAPD_ARGON2_HASH_LENGTH;
+ */
+
+ p = hash->bv_val = ber_memalloc( scheme->bv_len + crypto_pwhash_STRBYTES );
+ if ( p == NULL ) {
+ return LUTIL_PASSWD_ERR;
+ }
+
+ AC_MEMCPY( hash->bv_val, scheme->bv_val, scheme->bv_len );
+ p += scheme->bv_len;
+
+ if ( crypto_pwhash_str_alg( p, passwd->bv_val, passwd->bv_len,
+ iterations, memory * 1024,
+ crypto_pwhash_ALG_ARGON2ID13 ) == 0 ) {
+ hash->bv_len = strlen( hash->bv_val );
+ rc = LUTIL_PASSWD_OK;
+ }
+#endif
+
+ if ( rc ) {
+ ber_memfree( hash->bv_val );
+ return LUTIL_PASSWD_ERR;
+ }
+
+ return LUTIL_PASSWD_OK;
+}
+
+static int
+slapd_argon2_verify(
+ const struct berval *scheme,
+ const struct berval *passwd,
+ const struct berval *cred,
+ const char **text )
+{
+ int rc = LUTIL_PASSWD_ERR;
+
+#ifdef HAVE_LIBARGON2
+ if ( strncmp( passwd->bv_val, "$argon2i$", STRLENOF("$argon2i$") ) == 0 ) {
+ rc = argon2i_verify( passwd->bv_val, cred->bv_val, cred->bv_len );
+ } else if ( strncmp( passwd->bv_val, "$argon2d$", STRLENOF("$argon2d$") ) == 0 ) {
+ rc = argon2d_verify( passwd->bv_val, cred->bv_val, cred->bv_len );
+ } else if ( strncmp( passwd->bv_val, "$argon2id$", STRLENOF("$argon2id$") ) == 0 ) {
+ rc = argon2id_verify( passwd->bv_val, cred->bv_val, cred->bv_len );
+ }
+#else /* !HAVE_LIBARGON2 */
+ rc = crypto_pwhash_str_verify( passwd->bv_val, cred->bv_val, cred->bv_len );
+#endif
+
+ if ( rc ) {
+ return LUTIL_PASSWD_ERR;
+ }
+ return LUTIL_PASSWD_OK;
+}
+
+int init_module( int argc, char *argv[] )
+{
+ int i;
+
+#ifdef HAVE_LIBSODIUM
+ if ( sodium_init() == -1 ) {
+ return -1;
+ }
+#endif
+
+ for ( i=0; i < argc; i++ ) {
+ char *p;
+ unsigned long value;
+
+ switch ( *argv[i] ) {
+ case 'm':
+ p = strchr( argv[i], '=' );
+ if ( !p || lutil_atoulx( &value, p+1, 0 ) ) {
+ return -1;
+ }
+ memory = value;
+ break;
+
+ case 't':
+ p = strchr( argv[i], '=' );
+ if ( !p || lutil_atoulx( &value, p+1, 0 ) ) {
+ return -1;
+ }
+ iterations = value;
+ break;
+
+ case 'p':
+ p = strchr( argv[i], '=' );
+ if ( !p || lutil_atoulx( &value, p+1, 0 ) ) {
+ return -1;
+ }
+ parallelism = value;
+ break;
+
+ default:
+ return -1;
+ }
+ }
+
+#ifndef HAVE_LIBARGON2
+ /* At the moment, we can only use libargon2 to set parallelism for new
+ * hashes */
+ if ( parallelism != SLAPD_ARGON2_PARALLELISM ) {
+ Debug( LDAP_DEBUG_ANY, "pw-argon2: "
+ "non-default parallelism only supported when linked with "
+ "libargon2, got p=%lu\n",
+ parallelism );
+
+ if ( (slapMode & SLAP_MODE) != SLAP_TOOL_MODE ||
+ slapTool == SLAPPASSWD || slapTool == SLAPTEST ) {
+ return 1;
+ }
+ }
+#endif
+
+ return lutil_passwd_add( (struct berval *)&slapd_argon2_scheme,
+ slapd_argon2_verify, slapd_argon2_hash );
+}
+#endif /* SLAPD_OVER_PW_ARGON2 */
diff --git a/servers/slapd/referral.c b/servers/slapd/referral.c
new file mode 100644
index 0000000..57465c0
--- /dev/null
+++ b/servers/slapd/referral.c
@@ -0,0 +1,363 @@
+/* referral.c - muck with referrals */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+
+/*
+ * This routine generates the DN appropriate to return in
+ * an LDAP referral.
+ */
+static char * referral_dn_muck(
+ const char * refDN,
+ struct berval * baseDN,
+ struct berval * targetDN )
+{
+ int rc;
+ struct berval bvin;
+ struct berval nrefDN = BER_BVNULL;
+ struct berval nbaseDN = BER_BVNULL;
+ struct berval ntargetDN = BER_BVNULL;
+
+ if( !baseDN ) {
+ /* no base, return target */
+ return targetDN ? ch_strdup( targetDN->bv_val ) : NULL;
+ }
+
+ if( refDN ) {
+ bvin.bv_val = (char *)refDN;
+ bvin.bv_len = strlen( refDN );
+
+ rc = dnPretty( NULL, &bvin, &nrefDN, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ /* Invalid refDN */
+ return NULL;
+ }
+ }
+
+ if( !targetDN ) {
+ /* continuation reference
+ * if refDN present return refDN
+ * else return baseDN
+ */
+ return nrefDN.bv_len ? nrefDN.bv_val : ch_strdup( baseDN->bv_val );
+ }
+
+ rc = dnPretty( NULL, targetDN, &ntargetDN, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ /* Invalid targetDN */
+ ch_free( nrefDN.bv_val );
+ return NULL;
+ }
+
+ if( nrefDN.bv_len ) {
+ rc = dnPretty( NULL, baseDN, &nbaseDN, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ /* Invalid baseDN */
+ ch_free( nrefDN.bv_val );
+ ch_free( ntargetDN.bv_val );
+ return NULL;
+ }
+
+ if( dn_match( &nbaseDN, &nrefDN ) ) {
+ ch_free( nrefDN.bv_val );
+ ch_free( nbaseDN.bv_val );
+ return ntargetDN.bv_val;
+ }
+
+ {
+ struct berval muck;
+
+ if( ntargetDN.bv_len < nbaseDN.bv_len ) {
+ ch_free( nrefDN.bv_val );
+ ch_free( nbaseDN.bv_val );
+ return ntargetDN.bv_val;
+ }
+
+ rc = strcasecmp(
+ &ntargetDN.bv_val[ntargetDN.bv_len-nbaseDN.bv_len],
+ nbaseDN.bv_val );
+ if( rc ) {
+ /* target not subordinate to base */
+ ch_free( nrefDN.bv_val );
+ ch_free( nbaseDN.bv_val );
+ return ntargetDN.bv_val;
+ }
+
+ muck.bv_len = ntargetDN.bv_len + nrefDN.bv_len - nbaseDN.bv_len;
+ muck.bv_val = ch_malloc( muck.bv_len + 1 );
+
+ strncpy( muck.bv_val, ntargetDN.bv_val,
+ ntargetDN.bv_len-nbaseDN.bv_len );
+ strcpy( &muck.bv_val[ntargetDN.bv_len-nbaseDN.bv_len],
+ nrefDN.bv_val );
+
+ ch_free( nrefDN.bv_val );
+ ch_free( nbaseDN.bv_val );
+ ch_free( ntargetDN.bv_val );
+
+ return muck.bv_val;
+ }
+ }
+
+ ch_free( nrefDN.bv_val );
+ return ntargetDN.bv_val;
+}
+
+
+/* validate URL for global referral use
+ * LDAP URLs must not have:
+ * DN, attrs, scope, nor filter
+ * Any non-LDAP URL is okay
+ *
+ * XXYYZ: should return an error string
+ */
+int validate_global_referral( const char *url )
+{
+ int rc;
+ LDAPURLDesc *lurl;
+
+ rc = ldap_url_parse_ext( url, &lurl, LDAP_PVT_URL_PARSE_NONE );
+
+ switch( rc ) {
+ case LDAP_URL_SUCCESS:
+ break;
+
+ case LDAP_URL_ERR_BADSCHEME:
+ /* not LDAP hence valid */
+ Debug( LDAP_DEBUG_CONFIG, "referral \"%s\": not LDAP.\n", url );
+ return 0;
+
+ default:
+ /* other error, bail */
+ Debug( LDAP_DEBUG_ANY,
+ "referral: invalid URL (%s): %s (%d)\n",
+ url, "" /* ldap_url_error2str(rc) */, rc );
+ return 1;
+ }
+
+ rc = 0;
+
+ if( lurl->lud_dn && *lurl->lud_dn ) {
+ Debug( LDAP_DEBUG_ANY,
+ "referral: URL (%s): contains DN\n",
+ url );
+ rc = 1;
+
+ } else if( lurl->lud_attrs ) {
+ Debug( LDAP_DEBUG_ANY,
+ "referral: URL (%s): requests attributes\n",
+ url );
+ rc = 1;
+
+ } else if( lurl->lud_scope != LDAP_SCOPE_DEFAULT ) {
+ Debug( LDAP_DEBUG_ANY,
+ "referral: URL (%s): contains explicit scope\n",
+ url );
+ rc = 1;
+
+ } else if( lurl->lud_filter ) {
+ Debug( LDAP_DEBUG_ANY,
+ "referral: URL (%s): contains explicit filter\n",
+ url );
+ rc = 1;
+ }
+
+ ldap_free_urldesc( lurl );
+ return rc;
+}
+
+BerVarray referral_rewrite(
+ BerVarray in,
+ struct berval *base,
+ struct berval *target,
+ int scope )
+{
+ int i;
+ BerVarray refs;
+ struct berval *iv, *jv;
+
+ if ( in == NULL ) {
+ return NULL;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+ /* just count them */
+ }
+
+ if ( i < 1 ) {
+ return NULL;
+ }
+
+ refs = ch_malloc( ( i + 1 ) * sizeof( struct berval ) );
+
+ for ( iv = in, jv = refs; !BER_BVISNULL( iv ); iv++ ) {
+ LDAPURLDesc *url;
+ char *dn;
+ int rc;
+
+ rc = ldap_url_parse_ext( iv->bv_val, &url, LDAP_PVT_URL_PARSE_NONE );
+ if ( rc == LDAP_URL_ERR_BADSCHEME ) {
+ ber_dupbv( jv++, iv );
+ continue;
+
+ } else if ( rc != LDAP_URL_SUCCESS ) {
+ continue;
+ }
+
+ dn = url->lud_dn;
+ url->lud_dn = referral_dn_muck( ( dn && *dn ) ? dn : NULL,
+ base, target );
+ ldap_memfree( dn );
+
+ if ( url->lud_scope == LDAP_SCOPE_DEFAULT ) {
+ url->lud_scope = scope;
+ }
+
+ jv->bv_val = ldap_url_desc2str( url );
+ if ( jv->bv_val != NULL ) {
+ jv->bv_len = strlen( jv->bv_val );
+
+ } else {
+ ber_dupbv( jv, iv );
+ }
+ jv++;
+
+ ldap_free_urldesc( url );
+ }
+
+ if ( jv == refs ) {
+ ch_free( refs );
+ refs = NULL;
+
+ } else {
+ BER_BVZERO( jv );
+ }
+
+ return refs;
+}
+
+
+BerVarray get_entry_referrals(
+ Operation *op,
+ Entry *e )
+{
+ Attribute *attr;
+ BerVarray refs;
+ unsigned i;
+ struct berval *iv, *jv;
+
+ AttributeDescription *ad_ref = slap_schema.si_ad_ref;
+
+ attr = attr_find( e->e_attrs, ad_ref );
+
+ if( attr == NULL ) return NULL;
+
+ for( i=0; attr->a_vals[i].bv_val != NULL; i++ ) {
+ /* count references */
+ }
+
+ if( i < 1 ) return NULL;
+
+ refs = ch_malloc( (i + 1) * sizeof(struct berval));
+
+ for( iv=attr->a_vals, jv=refs; iv->bv_val != NULL; iv++ ) {
+ unsigned k;
+ ber_dupbv( jv, iv );
+
+ /* trim the label */
+ for( k=0; k<jv->bv_len; k++ ) {
+ if( isspace( (unsigned char) jv->bv_val[k] ) ) {
+ jv->bv_val[k] = '\0';
+ jv->bv_len = k;
+ break;
+ }
+ }
+
+ if( jv->bv_len > 0 ) {
+ jv++;
+ } else {
+ free( jv->bv_val );
+ }
+ }
+
+ if( jv == refs ) {
+ free( refs );
+ refs = NULL;
+
+ } else {
+ jv->bv_val = NULL;
+ }
+
+ /* we should check that a referral value exists... */
+ return refs;
+}
+
+
+int get_alias_dn(
+ Entry *e,
+ struct berval *ndn,
+ int *err,
+ const char **text )
+{
+ Attribute *a;
+ AttributeDescription *aliasedObjectName
+ = slap_schema.si_ad_aliasedObjectName;
+
+ a = attr_find( e->e_attrs, aliasedObjectName );
+
+ if( a == NULL ) {
+ /*
+ * there was an aliasedobjectname defined but no data.
+ */
+ *err = LDAP_ALIAS_PROBLEM;
+ *text = "alias missing aliasedObjectName attribute";
+ return -1;
+ }
+
+ /*
+ * aliasedObjectName should be SINGLE-VALUED with a single value.
+ */
+ if ( a->a_vals[0].bv_val == NULL ) {
+ /*
+ * there was an aliasedobjectname defined but no data.
+ */
+ *err = LDAP_ALIAS_PROBLEM;
+ *text = "alias missing aliasedObjectName value";
+ return -1;
+ }
+
+ if( a->a_nvals[1].bv_val != NULL ) {
+ *err = LDAP_ALIAS_PROBLEM;
+ *text = "alias has multivalued aliasedObjectName";
+ return -1;
+ }
+
+ *ndn = a->a_nvals[0];
+
+ return 0;
+}
+
diff --git a/servers/slapd/result.c b/servers/slapd/result.c
new file mode 100644
index 0000000..a928e95
--- /dev/null
+++ b/servers/slapd/result.c
@@ -0,0 +1,1922 @@
+/* result.c - routines to send ldap results, errors, and referrals */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+
+#if SLAP_STATS_ETIME
+#define ETIME_SETUP \
+ struct timeval now; \
+ char timestr[64]; \
+ (void) gettimeofday( &now, NULL ); \
+ now.tv_sec -= op->o_time; \
+ now.tv_usec -= op->o_tusec; \
+ if ( now.tv_usec < 0 ) { \
+ --now.tv_sec; now.tv_usec += 1000000; \
+ } \
+ sprintf(timestr, "qtime=%d.%06d etime=%d.%06d", \
+ (int)op->o_qtime.tv_sec, (int)op->o_qtime.tv_usec, \
+ (int)now.tv_sec, (int)now.tv_usec);
+#define ETIME_LOGFMT "%s "
+#define StatslogEtime(lvl,fmt,pfx,tag,err,...) \
+ Debug(lvl,fmt,pfx,tag,err,timestr,__VA_ARGS__)
+#else
+#define ETIME_SETUP
+#define ETIME_LOGFMT ""
+#define StatslogEtime Debug
+#endif /* SLAP_STATS_ETIME */
+
+const struct berval slap_dummy_bv = BER_BVNULL;
+
+int slap_null_cb( Operation *op, SlapReply *rs )
+{
+ return 0;
+}
+
+int slap_freeself_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_callback != NULL );
+
+ op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static char *v2ref( BerVarray ref, const char *text )
+{
+ size_t len = 0, i = 0;
+ char *v2;
+
+ if(ref == NULL) {
+ if (text) {
+ return ch_strdup(text);
+ } else {
+ return NULL;
+ }
+ }
+
+ if ( text != NULL ) {
+ len = strlen( text );
+ if (text[len-1] != '\n') {
+ i = 1;
+ }
+ }
+
+ v2 = ch_malloc( len+i+sizeof("Referral:") );
+
+ if( text != NULL ) {
+ strcpy(v2, text);
+ if( i ) {
+ v2[len++] = '\n';
+ }
+ }
+ strcpy( v2+len, "Referral:" );
+ len += sizeof("Referral:");
+
+ for( i=0; ref[i].bv_val != NULL; i++ ) {
+ v2 = ch_realloc( v2, len + ref[i].bv_len + 1 );
+ v2[len-1] = '\n';
+ AC_MEMCPY(&v2[len], ref[i].bv_val, ref[i].bv_len );
+ len += ref[i].bv_len;
+ if (ref[i].bv_val[ref[i].bv_len-1] != '/') {
+ ++len;
+ }
+ }
+
+ v2[len-1] = '\0';
+ return v2;
+}
+
+ber_tag_t
+slap_req2res( ber_tag_t tag )
+{
+ switch( tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_EXTENDED:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ tag++;
+ break;
+
+ case LDAP_REQ_DELETE:
+ tag = LDAP_RES_DELETE;
+ break;
+
+ case LDAP_REQ_ABANDON:
+ case LDAP_REQ_UNBIND:
+ tag = LBER_SEQUENCE;
+ break;
+
+ case LDAP_REQ_SEARCH:
+ tag = LDAP_RES_SEARCH_RESULT;
+ break;
+
+ default:
+ tag = LBER_SEQUENCE;
+ }
+
+ return tag;
+}
+
+/*
+ * SlapReply debugging enabled by USE_RS_ASSERT.
+ *
+ * Disabled by default, but compiled in (but still unused) when
+ * LDAP_TEST. #define USE_RS_ASSERT as nonzero to enable some
+ * assertions which check the SlapReply. USE_RS_ASSERT = 2 or higher
+ * check aggressively, currently some code fail these tests.
+ *
+ * Environment variable $NO_RS_ASSERT controls how USE_RS_ASSERT handles
+ * errors. > 0: ignore errors, 0: abort (the default), < 0: just warn.
+ *
+ * Wrap LDAP operation calls in macros SLAP_OP() & co from proto-slap.h
+ * to check the SlapReply. contrib/slapd-tools/wrap_slap_ops converts
+ * source code to use the macros.
+ */
+#if defined(LDAP_TEST) || (defined(USE_RS_ASSERT) && (USE_RS_ASSERT))
+
+int rs_suppress_assert = 0;
+
+/* RS_ASSERT() helper function */
+void rs_assert_(const char*file, unsigned line, const char*fn, const char*cond)
+{
+ int no_assert = rs_suppress_assert, save_errno = errno;
+ const char *s;
+
+ if ( no_assert >= 0 ) {
+ if ( no_assert == 0 && (s = getenv( "NO_RS_ASSERT" )) && *s ) {
+ no_assert = rs_suppress_assert = atoi( s );
+ }
+ if ( no_assert > 0 ) {
+ errno = save_errno;
+ return;
+ }
+ }
+
+#ifdef rs_assert_ /* proto-slap.h #defined away the fn parameter */
+ fprintf( stderr,"%s:%u: " "RS_ASSERT(%s) failed.\n", file,line,cond );
+#else
+ fprintf( stderr,"%s:%u: %s: RS_ASSERT(%s) failed.\n", file,line,fn,cond );
+#endif
+ fflush( stderr );
+
+ errno = save_errno;
+ /* $NO_RS_ASSERT > 0: ignore rs_asserts, 0: abort, < 0: just warn */
+ if ( !no_assert /* from $NO_RS_ASSERT */ ) abort();
+}
+
+/* SlapReply is consistent */
+void
+(rs_assert_ok)( const SlapReply *rs )
+{
+ const slap_mask_t flags = rs->sr_flags;
+
+ if ( flags & REP_ENTRY_MASK ) {
+ RS_ASSERT( !(flags & REP_ENTRY_MUSTRELEASE)
+ || !(flags & (REP_ENTRY_MASK ^ REP_ENTRY_MUSTRELEASE)) );
+ RS_ASSERT( rs->sr_entry != NULL );
+ RS_ASSERT( (1 << rs->sr_type) &
+ ((1 << REP_SEARCH) | (1 << REP_SEARCHREF) |
+ (1 << REP_RESULT) | (1 << REP_GLUE_RESULT)) );
+ }
+#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */
+ if ( (flags & (REP_MATCHED_MASK | REP_REF_MASK | REP_CTRLS_MASK)) ) {
+ RS_ASSERT( !(flags & REP_MATCHED_MASK) || rs->sr_matched );
+ RS_ASSERT( !(flags & REP_CTRLS_MASK ) || rs->sr_ctrls );
+ /* Note: LDAP_REFERRAL + !sr_ref is OK, becomes LDAP_NO_SUCH_OBJECT */
+ }
+#if (USE_RS_ASSERT) > 2
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ RS_ASSERT( rs->sr_text == NULL );
+ RS_ASSERT( rs->sr_matched == NULL );
+ }
+#endif
+#endif
+}
+
+/* Ready for calling a new backend operation */
+void
+(rs_assert_ready)( const SlapReply *rs )
+{
+ RS_ASSERT( !rs->sr_entry );
+#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */
+ RS_ASSERT( !rs->sr_text );
+ RS_ASSERT( !rs->sr_ref );
+ RS_ASSERT( !rs->sr_matched );
+ RS_ASSERT( !rs->sr_ctrls );
+ RS_ASSERT( !rs->sr_flags );
+#if (USE_RS_ASSERT) > 2
+ RS_ASSERT( rs->sr_err == LDAP_SUCCESS );
+#endif
+#else
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+#endif
+}
+
+/* Backend operation done */
+void
+(rs_assert_done)( const SlapReply *rs )
+{
+#if defined(USE_RS_ASSERT) && (USE_RS_ASSERT) > 1 /* TODO: Enable when safe */
+ RS_ASSERT( !(rs->sr_flags & ~(REP_ENTRY_MODIFIABLE|REP_NO_OPERATIONALS)) );
+ rs_assert_ok( rs );
+#else
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MUSTFLUSH) );
+#endif
+}
+
+#endif /* LDAP_TEST || USE_RS_ASSERT */
+
+/* Reset a used SlapReply whose contents has been flushed (freed/released) */
+void
+(rs_reinit)( SlapReply *rs, slap_reply_t type )
+{
+ rs_reinit( rs, type ); /* proto-slap.h macro */
+}
+
+/* Obey and clear rs->sr_flags & REP_ENTRY_MASK. Clear sr_entry if freed. */
+void
+rs_flush_entry( Operation *op, SlapReply *rs, slap_overinst *on )
+{
+ rs_assert_ok( rs );
+
+ if ( (rs->sr_flags & REP_ENTRY_MUSTFLUSH) && rs->sr_entry != NULL ) {
+ if ( !(rs->sr_flags & REP_ENTRY_MUSTRELEASE) ) {
+ entry_free( rs->sr_entry );
+ } else if ( on != NULL ) {
+ overlay_entry_release_ov( op, rs->sr_entry, 0, on );
+ } else {
+ be_entry_release_rw( op, rs->sr_entry, 0 );
+ }
+ rs->sr_entry = NULL;
+ }
+
+ rs->sr_flags &= ~REP_ENTRY_MASK;
+}
+
+/* Set rs->sr_entry after obeying and clearing sr_flags & REP_ENTRY_MASK. */
+void
+rs_replace_entry( Operation *op, SlapReply *rs, slap_overinst *on, Entry *e )
+{
+ rs_flush_entry( op, rs, on );
+ rs->sr_entry = e;
+}
+
+/*
+ * Ensure rs->sr_entry is modifiable, by duplicating it if necessary.
+ * Obey sr_flags. Set REP_ENTRY_<MODIFIABLE, and MUSTBEFREED if duplicated>.
+ * Return nonzero if rs->sr_entry was replaced.
+ */
+int
+rs_entry2modifiable( Operation *op, SlapReply *rs, slap_overinst *on )
+{
+ if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) {
+ rs_assert_ok( rs );
+ return 0;
+ }
+ rs_replace_entry( op, rs, on, entry_dup( rs->sr_entry ));
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ return 1;
+}
+
+/* Check for any callbacks that want to be informed about being blocked
+ * on output. These callbacks are expected to leave the callback list
+ * unmodified. Their result is ignored.
+ */
+static void
+slap_writewait_play(
+ Operation *op )
+{
+ slap_callback *sc = op->o_callback;
+
+ for ( ; sc; sc = sc->sc_next ) {
+ if ( sc->sc_writewait )
+ sc->sc_writewait( op, sc );
+ }
+}
+
+static long send_ldap_ber(
+ Operation *op,
+ BerElement *ber )
+{
+ Connection *conn = op->o_conn;
+ ber_len_t bytes;
+ long ret = 0;
+ char *close_reason;
+ int do_resume = 0;
+
+ ber_get_option( ber, LBER_OPT_BER_BYTES_TO_WRITE, &bytes );
+
+ /* write only one pdu at a time - wait til it's our turn */
+ ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex );
+ if (( op->o_abandon && !op->o_cancel ) || !connection_valid( conn ) ||
+ conn->c_writers < 0 ) {
+ ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex );
+ return 0;
+ }
+
+ conn->c_writers++;
+
+ while ( conn->c_writers > 0 && conn->c_writing ) {
+ ldap_pvt_thread_cond_wait( &conn->c_write1_cv, &conn->c_write1_mutex );
+ }
+
+ /* connection was closed under us */
+ if ( conn->c_writers < 0 ) {
+ /* we're the last waiter, let the closer continue */
+ if ( conn->c_writers == -1 )
+ ldap_pvt_thread_cond_signal( &conn->c_write1_cv );
+ conn->c_writers++;
+ ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex );
+ return 0;
+ }
+
+ /* Our turn */
+ conn->c_writing = 1;
+
+ /* write the pdu */
+ while( 1 ) {
+ int err;
+ char ebuf[128];
+
+ if ( ber_flush2( conn->c_sb, ber, LBER_FLUSH_FREE_NEVER ) == 0 ) {
+ ret = bytes;
+ break;
+ }
+
+ err = sock_errno();
+
+ /*
+ * we got an error. if it's ewouldblock, we need to
+ * wait on the socket being writable. otherwise, figure
+ * it's a hard error and return.
+ */
+
+ Debug( LDAP_DEBUG_CONNS, "ber_flush2 failed errno=%d reason=\"%s\"\n",
+ err, sock_errstr(err, ebuf, sizeof(ebuf)) );
+
+ if ( err != EWOULDBLOCK && err != EAGAIN ) {
+ close_reason = "connection lost on write";
+fail:
+ conn->c_writers--;
+ conn->c_writing = 0;
+ ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex );
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+ /* conn may have been reused by the time we get the mutex */
+ if ( op->o_connid == conn->c_connid )
+ connection_closing( conn, close_reason );
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+ return -1;
+ }
+
+ /* if writer is blocked and we're waiting for a pool pause,
+ * just drop this connection.
+ */
+ if ( ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) {
+ close_reason = "writer blocked and pool pause pending";
+ goto fail;
+ }
+
+ /* wait for socket to be write-ready */
+ do_resume = 1;
+ conn->c_writewaiter = 1;
+ ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex );
+ slap_writewait_play( op );
+ err = slapd_wait_writer( conn->c_sd );
+ conn->c_writewaiter = 0;
+ ldap_pvt_thread_mutex_lock( &conn->c_write1_mutex );
+ /* 0 is timeout, so we close it.
+ * -1 is an error, close it.
+ */
+ if ( err <= 0 ) {
+ if ( err == 0 )
+ close_reason = "writetimeout";
+ else
+ close_reason = "connection lost on writewait";
+ goto fail;
+ }
+
+ if ( conn->c_writers < 0 ) {
+ ret = 0;
+ break;
+ }
+ }
+
+ conn->c_writing = 0;
+ if ( conn->c_writers < 0 ) {
+ /* shutting down, don't resume any ops */
+ do_resume = 0;
+ conn->c_writers++;
+ if ( !conn->c_writers )
+ ldap_pvt_thread_cond_signal( &conn->c_write1_cv );
+ } else {
+ conn->c_writers--;
+ /* other writers are waiting, don't resume any ops */
+ if ( conn->c_writers )
+ do_resume = 0;
+ ldap_pvt_thread_cond_signal( &conn->c_write1_cv );
+ }
+ ldap_pvt_thread_mutex_unlock( &conn->c_write1_mutex );
+
+ /* If there are no more writers, release a pending op */
+ if ( do_resume )
+ connection_write_resume( conn );
+
+ return ret;
+}
+
+static int
+send_ldap_control( BerElement *ber, LDAPControl *c )
+{
+ int rc;
+
+ assert( c != NULL );
+
+ rc = ber_printf( ber, "{s" /*}*/, c->ldctl_oid );
+
+ if( c->ldctl_iscritical ) {
+ rc = ber_printf( ber, "b",
+ (ber_int_t) c->ldctl_iscritical ) ;
+ if( rc == -1 ) return rc;
+ }
+
+ if( c->ldctl_value.bv_val != NULL ) {
+ rc = ber_printf( ber, "O", &c->ldctl_value );
+ if( rc == -1 ) return rc;
+ }
+
+ rc = ber_printf( ber, /*{*/"N}" );
+ if( rc == -1 ) return rc;
+
+ return 0;
+}
+
+static int
+send_ldap_controls( Operation *o, BerElement *ber, LDAPControl **c )
+{
+ int rc;
+
+ if( c == NULL )
+ return 0;
+
+ rc = ber_printf( ber, "t{"/*}*/, LDAP_TAG_CONTROLS );
+ if( rc == -1 ) return rc;
+
+ for( ; *c != NULL; c++) {
+ rc = send_ldap_control( ber, *c );
+ if( rc == -1 ) return rc;
+ }
+
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+ /* this is a hack to avoid having to modify op->s_ctrls */
+ if( o->o_sortedresults ) {
+ BerElementBuffer berbuf;
+ BerElement *sber = (BerElement *) &berbuf;
+ LDAPControl sorted;
+ BER_BVZERO( &sorted.ldctl_value );
+ sorted.ldctl_oid = LDAP_CONTROL_SORTRESPONSE;
+ sorted.ldctl_iscritical = 0;
+
+ ber_init2( sber, NULL, LBER_USE_DER );
+
+ ber_printf( sber, "{e}", LDAP_UNWILLING_TO_PERFORM );
+
+ if( ber_flatten2( sber, &sorted.ldctl_value, 0 ) == -1 ) {
+ return -1;
+ }
+
+ (void) ber_free_buf( sber );
+
+ rc = send_ldap_control( ber, &sorted );
+ if( rc == -1 ) return rc;
+ }
+#endif
+
+ rc = ber_printf( ber, /*{*/"N}" );
+
+ return rc;
+}
+
+/*
+ * slap_response_play()
+ *
+ * plays the callback list; rationale: a callback can
+ * - remove itself from the list, by setting op->o_callback = NULL;
+ * malloc()'ed callbacks should free themselves from inside the
+ * sc_response() function.
+ * - replace itself with another (list of) callback(s), by setting
+ * op->o_callback = a new (list of) callback(s); in this case, it
+ * is the callback's responsibility to to append existing subsequent
+ * callbacks to the end of the list that is passed to the sc_response()
+ * function.
+ * - modify the list of subsequent callbacks by modifying the value
+ * of the sc_next field from inside the sc_response() function; this
+ * case does not require any handling from inside slap_response_play()
+ *
+ * To stop execution of the playlist, the sc_response() function must return
+ * a value different from SLAP_SC_CONTINUE.
+ *
+ * The same applies to slap_cleanup_play(); only, there is no means to stop
+ * execution of the playlist, since all cleanup functions must be called.
+ */
+static int
+slap_response_play(
+ Operation *op,
+ SlapReply *rs )
+{
+ int rc;
+
+ slap_callback *sc = op->o_callback, **scp;
+
+ rc = SLAP_CB_CONTINUE;
+ for ( scp = &sc; *scp; ) {
+ slap_callback *sc_next = (*scp)->sc_next, **sc_nextp = &(*scp)->sc_next;
+
+ op->o_callback = *scp;
+ if ( op->o_callback->sc_response ) {
+ rc = op->o_callback->sc_response( op, rs );
+ if ( op->o_callback == NULL ) {
+ /* the callback has been removed;
+ * repair the list */
+ *scp = sc_next;
+ sc_nextp = scp;
+
+ } else if ( op->o_callback != *scp ) {
+ /* a new callback has been inserted
+ * in place of the existing one; repair the list */
+ *scp = op->o_callback;
+ sc_nextp = scp;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ scp = sc_nextp;
+ }
+
+ op->o_callback = sc;
+ return rc;
+}
+
+static int
+slap_cleanup_play(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback, **scp;
+
+ for ( scp = &sc; *scp; ) {
+ slap_callback *sc_next = (*scp)->sc_next, **sc_nextp = &(*scp)->sc_next;
+
+ op->o_callback = *scp;
+ if ( op->o_callback->sc_cleanup ) {
+ (void)op->o_callback->sc_cleanup( op, rs );
+ if ( op->o_callback == NULL ) {
+ /* the callback has been removed;
+ * repair the list */
+ *scp = sc_next;
+ sc_nextp = scp;
+
+ } else if ( op->o_callback != *scp ) {
+ /* a new callback has been inserted
+ * after the existing one; repair the list */
+ /* a new callback has been inserted
+ * in place of the existing one; repair the list */
+ *scp = op->o_callback;
+ sc_nextp = scp;
+ }
+ /* don't care about the result; do all cleanup */
+ }
+ scp = sc_nextp;
+ }
+
+ op->o_callback = sc;
+ return LDAP_SUCCESS;
+}
+
+static int
+send_ldap_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ int rc = LDAP_SUCCESS;
+ long bytes;
+
+ /* op was actually aborted, bypass everything if client didn't Cancel */
+ if (( rs->sr_err == SLAPD_ABANDON ) && !op->o_cancel ) {
+ rc = SLAPD_ABANDON;
+ goto clean2;
+ }
+
+ if ( op->o_callback ) {
+ rc = slap_response_play( op, rs );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ goto clean2;
+ }
+ }
+
+ /* op completed, connection aborted, bypass sending response */
+ if ( op->o_abandon && !op->o_cancel ) {
+ rc = SLAPD_ABANDON;
+ goto clean2;
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if (op->o_conn && op->o_conn->c_is_udp)
+ ber = op->o_res_ber;
+ else
+#endif
+ {
+ ber_init_w_nullc( ber, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+ }
+
+ rc = rs->sr_err;
+ if ( rc == SLAPD_ABANDON && op->o_cancel )
+ rc = LDAP_CANCELLED;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "send_ldap_response: msgid=%d tag=%lu err=%d\n",
+ rs->sr_msgid, rs->sr_tag, rc );
+
+ if( rs->sr_ref ) {
+ Debug( LDAP_DEBUG_ARGS, "send_ldap_response: ref=\"%s\"\n",
+ rs->sr_ref[0].bv_val ? rs->sr_ref[0].bv_val : "NULL" );
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if (op->o_conn && op->o_conn->c_is_udp &&
+ op->o_protocol == LDAP_VERSION2 )
+ {
+ rc = ber_printf( ber, "t{ess" /*"}"*/,
+ rs->sr_tag, rc,
+ rs->sr_matched == NULL ? "" : rs->sr_matched,
+ rs->sr_text == NULL ? "" : rs->sr_text );
+ } else
+#endif
+ if ( rs->sr_type == REP_INTERMEDIATE ) {
+ rc = ber_printf( ber, "{it{" /*"}}"*/,
+ rs->sr_msgid, rs->sr_tag );
+
+ } else {
+ rc = ber_printf( ber, "{it{ess" /*"}}"*/,
+ rs->sr_msgid, rs->sr_tag, rc,
+ rs->sr_matched == NULL ? "" : rs->sr_matched,
+ rs->sr_text == NULL ? "" : rs->sr_text );
+ }
+
+ if( rc != -1 ) {
+ if ( rs->sr_ref != NULL ) {
+ assert( rs->sr_err == LDAP_REFERRAL );
+ rc = ber_printf( ber, "t{W}",
+ LDAP_TAG_REFERRAL, rs->sr_ref );
+ } else {
+ assert( rs->sr_err != LDAP_REFERRAL );
+ }
+ }
+
+ if( rc != -1 && rs->sr_type == REP_SASL && rs->sr_sasldata != NULL ) {
+ rc = ber_printf( ber, "tO",
+ LDAP_TAG_SASL_RES_CREDS, rs->sr_sasldata );
+ }
+
+ if( rc != -1 &&
+ ( rs->sr_type == REP_EXTENDED || rs->sr_type == REP_INTERMEDIATE ))
+ {
+ if ( rs->sr_rspoid != NULL ) {
+ rc = ber_printf( ber, "ts",
+ rs->sr_type == REP_EXTENDED
+ ? LDAP_TAG_EXOP_RES_OID : LDAP_TAG_IM_RES_OID,
+ rs->sr_rspoid );
+ }
+ if( rc != -1 && rs->sr_rspdata != NULL ) {
+ rc = ber_printf( ber, "tO",
+ rs->sr_type == REP_EXTENDED
+ ? LDAP_TAG_EXOP_RES_VALUE : LDAP_TAG_IM_RES_VALUE,
+ rs->sr_rspdata );
+ }
+ }
+
+ if( rc != -1 ) {
+ rc = ber_printf( ber, /*"{"*/ "N}" );
+ }
+
+ if( rc != -1 ) {
+ rc = send_ldap_controls( op, ber, rs->sr_ctrls );
+ }
+
+ if( rc != -1 ) {
+ rc = ber_printf( ber, /*"{"*/ "N}" );
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if( op->o_conn && op->o_conn->c_is_udp && op->o_protocol == LDAP_VERSION2
+ && rc != -1 )
+ {
+ rc = ber_printf( ber, /*"{"*/ "N}" );
+ }
+#endif
+
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "ber_printf failed\n" );
+
+#ifdef LDAP_CONNECTIONLESS
+ if (!op->o_conn || op->o_conn->c_is_udp == 0)
+#endif
+ {
+ ber_free_buf( ber );
+ }
+ goto cleanup;
+ }
+
+ /* send BER */
+ bytes = send_ldap_ber( op, ber );
+#ifdef LDAP_CONNECTIONLESS
+ if (!op->o_conn || op->o_conn->c_is_udp == 0)
+#endif
+ {
+ ber_free_buf( ber );
+ }
+
+ if ( bytes < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_ldap_response: ber write failed\n" );
+
+ goto cleanup;
+ }
+
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes );
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex );
+
+cleanup:;
+ /* Tell caller that we did this for real, as opposed to being
+ * overridden by a callback
+ */
+ rc = SLAP_CB_CONTINUE;
+
+clean2:;
+ if ( op->o_callback ) {
+ (void)slap_cleanup_play( op, rs );
+ }
+
+ if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) {
+ rs->sr_flags ^= REP_MATCHED_MUSTBEFREED; /* paranoia */
+ if ( rs->sr_matched ) {
+ free( (char *)rs->sr_matched );
+ rs->sr_matched = NULL;
+ }
+ }
+
+ if ( rs->sr_flags & REP_REF_MUSTBEFREED ) {
+ rs->sr_flags ^= REP_REF_MUSTBEFREED; /* paranoia */
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) {
+ rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */
+ if ( rs->sr_ctrls ) {
+ slap_free_ctrls( op, rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ }
+
+ return rc;
+}
+
+
+void
+send_ldap_disconnect( Operation *op, SlapReply *rs )
+{
+#define LDAP_UNSOLICITED_ERROR(e) \
+ ( (e) == LDAP_PROTOCOL_ERROR \
+ || (e) == LDAP_STRONG_AUTH_REQUIRED \
+ || (e) == LDAP_UNAVAILABLE )
+
+ Debug( LDAP_DEBUG_TRACE,
+ "send_ldap_disconnect %d:%s\n",
+ rs->sr_err, rs->sr_text ? rs->sr_text : "" );
+ assert( LDAP_UNSOLICITED_ERROR( rs->sr_err ) );
+
+ /* TODO: Flush the entry if sr_type == REP_SEARCH/REP_SEARCHREF? */
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */
+
+ rs->sr_type = REP_EXTENDED;
+ rs->sr_rspdata = NULL;
+
+ if ( op->o_protocol < LDAP_VERSION3 ) {
+ rs->sr_rspoid = NULL;
+ rs->sr_tag = slap_req2res( op->o_tag );
+ rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0;
+
+ } else {
+ rs->sr_rspoid = LDAP_NOTICE_DISCONNECT;
+ rs->sr_tag = LDAP_RES_EXTENDED;
+ rs->sr_msgid = LDAP_RES_UNSOLICITED;
+ }
+
+ if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) {
+ ETIME_SETUP;
+ StatslogEtime( LDAP_DEBUG_STATS,
+ "%s DISCONNECT tag=%lu err=%d "ETIME_LOGFMT"text=%s\n",
+ op->o_log_prefix, rs->sr_tag, rs->sr_err,
+ rs->sr_text ? rs->sr_text : "" );
+ }
+}
+
+void
+slap_send_ldap_result( Operation *op, SlapReply *rs )
+{
+ char *tmp = NULL;
+ const char *otext = rs->sr_text;
+ BerVarray oref = rs->sr_ref;
+
+ rs->sr_type = REP_RESULT;
+
+ /* Propagate Abandons so that cleanup callbacks can be processed */
+ if ( rs->sr_err == SLAPD_ABANDON || op->o_abandon )
+ goto abandon;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "send_ldap_result: %s p=%d\n",
+ op->o_log_prefix, op->o_protocol );
+ Debug( LDAP_DEBUG_ARGS,
+ "send_ldap_result: err=%d matched=\"%s\" text=\"%s\"\n",
+ rs->sr_err, rs->sr_matched ? rs->sr_matched : "",
+ rs->sr_text ? rs->sr_text : "" );
+ if( rs->sr_ref ) {
+ Debug( LDAP_DEBUG_ARGS,
+ "send_ldap_result: referral=\"%s\"\n",
+ rs->sr_ref[0].bv_val ? rs->sr_ref[0].bv_val : "NULL" );
+ }
+ assert( !LDAP_API_ERROR( rs->sr_err ) );
+ assert( rs->sr_err != LDAP_PARTIAL_RESULTS );
+
+ if ( rs->sr_err == LDAP_REFERRAL ) {
+ if( op->o_domain_scope ) rs->sr_ref = NULL;
+
+ if( rs->sr_ref == NULL ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ } else if ( op->o_protocol < LDAP_VERSION3 ) {
+ rs->sr_err = LDAP_PARTIAL_RESULTS;
+ }
+ }
+
+ if ( op->o_protocol < LDAP_VERSION3 ) {
+ tmp = v2ref( rs->sr_ref, rs->sr_text );
+ rs->sr_text = tmp;
+ rs->sr_ref = NULL;
+ }
+
+abandon:
+ rs->sr_tag = slap_req2res( op->o_tag );
+ rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0;
+
+ if ( rs->sr_flags & REP_REF_MUSTBEFREED ) {
+ if ( rs->sr_ref == NULL ) {
+ rs->sr_flags ^= REP_REF_MUSTBEFREED;
+ ber_bvarray_free( oref );
+ }
+ oref = NULL; /* send_ldap_response() will free rs->sr_ref if != NULL */
+ }
+
+ if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) {
+ ETIME_SETUP;
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ StatslogEtime( LDAP_DEBUG_STATS,
+ "%s SEARCH RESULT tag=%lu err=%d "ETIME_LOGFMT"nentries=%d text=%s\n",
+ op->o_log_prefix, rs->sr_tag, rs->sr_err,
+ rs->sr_nentries, rs->sr_text ? rs->sr_text : "" );
+ } else {
+ StatslogEtime( LDAP_DEBUG_STATS,
+ "%s RESULT tag=%lu err=%d "ETIME_LOGFMT"text=%s\n",
+ op->o_log_prefix, rs->sr_tag, rs->sr_err,
+ rs->sr_text ? rs->sr_text : "" );
+ }
+ }
+
+ if( tmp != NULL ) ch_free(tmp);
+ rs->sr_text = otext;
+ rs->sr_ref = oref;
+}
+
+void
+send_ldap_sasl( Operation *op, SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_TRACE, "send_ldap_sasl: err=%d len=%ld\n",
+ rs->sr_err,
+ rs->sr_sasldata ? (long) rs->sr_sasldata->bv_len : -1 );
+
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */
+
+ rs->sr_type = REP_SASL;
+ rs->sr_tag = slap_req2res( op->o_tag );
+ rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0;
+
+ if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) {
+ ETIME_SETUP;
+ StatslogEtime( LDAP_DEBUG_STATS,
+ "%s RESULT tag=%lu err=%d "ETIME_LOGFMT"text=%s\n",
+ op->o_log_prefix, rs->sr_tag, rs->sr_err,
+ rs->sr_text ? rs->sr_text : "" );
+ }
+}
+
+void
+slap_send_ldap_extended( Operation *op, SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_TRACE,
+ "send_ldap_extended: err=%d oid=%s len=%ld\n",
+ rs->sr_err,
+ rs->sr_rspoid ? rs->sr_rspoid : "",
+ rs->sr_rspdata != NULL ? rs->sr_rspdata->bv_len : 0 );
+
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */
+
+ rs->sr_type = REP_EXTENDED;
+ rs->sr_tag = slap_req2res( op->o_tag );
+ rs->sr_msgid = (rs->sr_tag != LBER_SEQUENCE) ? op->o_msgid : 0;
+
+ if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) {
+ ETIME_SETUP;
+ StatslogEtime( LDAP_DEBUG_STATS,
+ "%s RESULT oid=%s err=%d "ETIME_LOGFMT"text=%s\n",
+ op->o_log_prefix, rs->sr_rspoid ? rs->sr_rspoid : "",
+ rs->sr_err, rs->sr_text ? rs->sr_text : "" );
+ }
+}
+
+void
+slap_send_ldap_intermediate( Operation *op, SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_TRACE,
+ "send_ldap_intermediate: err=%d oid=%s len=%ld\n",
+ rs->sr_err,
+ rs->sr_rspoid ? rs->sr_rspoid : "",
+ rs->sr_rspdata != NULL ? rs->sr_rspdata->bv_len : 0 );
+
+ RS_ASSERT( !(rs->sr_flags & REP_ENTRY_MASK) );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia */
+
+ rs->sr_type = REP_INTERMEDIATE;
+ rs->sr_tag = LDAP_RES_INTERMEDIATE;
+ rs->sr_msgid = op->o_msgid;
+ if ( send_ldap_response( op, rs ) == SLAP_CB_CONTINUE ) {
+ Debug( LDAP_DEBUG_STATS2,
+ "%s INTERMEDIATE oid=%s\n",
+ op->o_log_prefix,
+ rs->sr_rspoid ? rs->sr_rspoid : "" );
+ }
+}
+
+#define set_ldap_error( rs, err, text ) do { \
+ (rs)->sr_err = err; (rs)->sr_text = text; } while(0)
+
+/*
+ * returns:
+ *
+ * LDAP_SUCCESS entry sent
+ * LDAP_OTHER entry not sent (other)
+ * LDAP_INSUFFICIENT_ACCESS entry not sent (ACL)
+ * LDAP_UNAVAILABLE entry not sent (connection closed)
+ * LDAP_SIZELIMIT_EXCEEDED entry not sent (caller must send sizelimitExceeded)
+ */
+
+int
+slap_send_search_entry( Operation *op, SlapReply *rs )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ Attribute *a;
+ int i, j, rc = LDAP_UNAVAILABLE, bytes;
+ int userattrs;
+ AccessControlState acl_state = ACL_STATE_INIT;
+ int attrsonly;
+ AttributeDescription *ad_entry = slap_schema.si_ad_entry;
+
+ /* a_flags: array of flags telling if the i-th element will be
+ * returned or filtered out
+ * e_flags: array of a_flags
+ */
+ char **e_flags = NULL;
+
+ rs->sr_type = REP_SEARCH;
+
+ if ( op->ors_slimit >= 0 && rs->sr_nentries >= op->ors_slimit ) {
+ rc = LDAP_SIZELIMIT_EXCEEDED;
+ goto error_return;
+ }
+
+ /* Every 64 entries, check for thread pool pause */
+ if ( ( ( rs->sr_nentries & 0x3f ) == 0x3f ) &&
+ ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 )
+ {
+ rc = LDAP_BUSY;
+ goto error_return;
+ }
+
+ /* eventually will loop through generated operational attribute types
+ * currently implemented types include:
+ * entryDN, subschemaSubentry, and hasSubordinates */
+ /* NOTE: moved before overlays callback circling because
+ * they may modify entry and other stuff in rs */
+ /* check for special all operational attributes ("+") type */
+ /* FIXME: maybe we could set this flag at the operation level;
+ * however, in principle the caller of send_search_entry() may
+ * change the attribute list at each call */
+ rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs );
+
+ rc = backend_operational( op, rs );
+ if ( rc ) {
+ goto error_return;
+ }
+
+ if ( op->o_callback ) {
+ rc = slap_response_play( op, rs );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ goto error_return;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "=> send_search_entry: conn %lu dn=\"%s\"%s\n",
+ op->o_connid, rs->sr_entry->e_name.bv_val,
+ op->ors_attrsonly ? " (attrsOnly)" : "" );
+
+ attrsonly = op->ors_attrsonly;
+
+ if ( !access_allowed( op, rs->sr_entry, ad_entry, NULL, ACL_READ, NULL )) {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_entry: conn %lu access to entry (%s) not allowed\n",
+ op->o_connid, rs->sr_entry->e_name.bv_val );
+
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ goto error_return;
+ }
+
+ if ( op->o_res_ber ) {
+ /* read back control or LDAP_CONNECTIONLESS */
+ ber = op->o_res_ber;
+ } else {
+ struct berval bv;
+
+ bv.bv_len = entry_flatsize( rs->sr_entry, 0 );
+ bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx );
+
+ ber_init2( ber, &bv, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if ( op->o_conn && op->o_conn->c_is_udp ) {
+ /* CONNECTIONLESS */
+ if ( op->o_protocol == LDAP_VERSION2 ) {
+ rc = ber_printf(ber, "t{O{" /*}}*/,
+ LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name );
+ } else {
+ rc = ber_printf( ber, "{it{O{" /*}}}*/, op->o_msgid,
+ LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name );
+ }
+ } else
+#endif
+ if ( op->o_res_ber ) {
+ /* read back control */
+ rc = ber_printf( ber, "t{O{" /*}}*/,
+ LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name );
+ } else {
+ rc = ber_printf( ber, "{it{O{" /*}}}*/, op->o_msgid,
+ LDAP_RES_SEARCH_ENTRY, &rs->sr_entry->e_name );
+ }
+
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER, "encoding DN error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+
+ /* check for special all user attributes ("*") type */
+ userattrs = SLAP_USERATTRS( rs->sr_attr_flags );
+
+ /* create an array of arrays of flags. Each flag corresponds
+ * to particular value of attribute and equals 1 if value matches
+ * to ValuesReturnFilter or 0 if not
+ */
+ if ( op->o_vrFilter != NULL ) {
+ int k = 0;
+ size_t size;
+
+ for ( a = rs->sr_entry->e_attrs, i=0; a != NULL; a = a->a_next, i++ ) {
+ for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ) k++;
+ }
+
+ size = i * sizeof(char *) + k;
+ if ( size > 0 ) {
+ char *a_flags;
+ e_flags = slap_sl_calloc ( 1, i * sizeof(char *) + k, op->o_tmpmemctx );
+ if( e_flags == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu slap_sl_calloc failed\n",
+ op->o_connid );
+ ber_free( ber, 1 );
+
+ set_ldap_error( rs, LDAP_OTHER, "out of memory" );
+ goto error_return;
+ }
+ a_flags = (char *)(e_flags + i);
+ memset( a_flags, 0, k );
+ for ( a=rs->sr_entry->e_attrs, i=0; a != NULL; a=a->a_next, i++ ) {
+ for ( j = 0; a->a_vals[j].bv_val != NULL; j++ );
+ e_flags[i] = a_flags;
+ a_flags += j;
+ }
+
+ rc = filter_matched_values(op, rs->sr_entry->e_attrs, &e_flags) ;
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "send_search_entry: "
+ "conn %lu matched values filtering failed\n",
+ op->o_connid );
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "matched values filtering error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+ }
+
+ for ( a = rs->sr_entry->e_attrs, j = 0; a != NULL; a = a->a_next, j++ ) {
+ AttributeDescription *desc = a->a_desc;
+ int finish = 0;
+
+ if ( rs->sr_attrs == NULL ) {
+ /* all user attrs request, skip operational attributes */
+ if( is_at_operational( desc->ad_type ) ) {
+ continue;
+ }
+
+ } else {
+ /* specific attrs requested */
+ if ( is_at_operational( desc->ad_type ) ) {
+ /* if not explicitly requested */
+ if ( !ad_inlist( desc, rs->sr_attrs )) {
+ /* if not all op attrs requested, skip */
+ if ( !SLAP_OPATTRS( rs->sr_attr_flags ))
+ continue;
+ /* if DSA-specific and replicating, skip */
+ if ( op->o_sync != SLAP_CONTROL_NONE &&
+ desc->ad_type->sat_usage == LDAP_SCHEMA_DSA_OPERATION )
+ continue;
+ }
+ } else {
+ if ( !userattrs && !ad_inlist( desc, rs->sr_attrs ) ) {
+ continue;
+ }
+ }
+ }
+
+ if ( attrsonly ) {
+ if ( ! access_allowed( op, rs->sr_entry, desc, NULL,
+ ACL_READ, &acl_state ) )
+ {
+ Debug( LDAP_DEBUG_ACL, "send_search_entry: "
+ "conn %lu access to attribute %s not allowed\n",
+ op->o_connid, desc->ad_cname.bv_val );
+ continue;
+ }
+
+ if (( rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "encoding description error");
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ finish = 1;
+
+ } else {
+ int first = 1;
+ for ( i = 0; a->a_nvals[i].bv_val != NULL; i++ ) {
+ if ( ! access_allowed( op, rs->sr_entry,
+ desc, &a->a_nvals[i], ACL_READ, &acl_state ) )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_entry: conn %lu "
+ "access to attribute %s, value #%d not allowed\n",
+ op->o_connid, desc->ad_cname.bv_val, i );
+
+ continue;
+ }
+
+ if ( op->o_vrFilter && e_flags[j][i] == 0 ){
+ continue;
+ }
+
+ if ( first ) {
+ first = 0;
+ finish = 1;
+ if (( rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "encoding description error");
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+ if (( rc = ber_printf( ber, "O", &a->a_vals[i] )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu "
+ "ber_printf failed.\n", op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "encoding values error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+ }
+
+ if ( finish && ( rc = ber_printf( ber, /*{[*/ "]N}" )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER, "encode end error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+
+ /* NOTE: moved before overlays callback circling because
+ * they may modify entry and other stuff in rs */
+ if ( rs->sr_operational_attrs != NULL && op->o_vrFilter != NULL ) {
+ int k = 0;
+ size_t size;
+
+ for ( a = rs->sr_operational_attrs, i=0; a != NULL; a = a->a_next, i++ ) {
+ for ( j = 0; a->a_vals[j].bv_val != NULL; j++ ) k++;
+ }
+
+ size = i * sizeof(char *) + k;
+ if ( size > 0 ) {
+ char *a_flags, **tmp;
+
+ /*
+ * Reuse previous memory - we likely need less space
+ * for operational attributes
+ */
+ tmp = slap_sl_realloc( e_flags, i * sizeof(char *) + k,
+ op->o_tmpmemctx );
+ if ( tmp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu "
+ "not enough memory "
+ "for matched values filtering\n",
+ op->o_connid );
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "not enough memory for matched values filtering" );
+ goto error_return;
+ }
+ e_flags = tmp;
+ a_flags = (char *)(e_flags + i);
+ memset( a_flags, 0, k );
+ for ( a = rs->sr_operational_attrs, i=0; a != NULL; a = a->a_next, i++ ) {
+ for ( j = 0; a->a_vals[j].bv_val != NULL; j++ );
+ e_flags[i] = a_flags;
+ a_flags += j;
+ }
+ rc = filter_matched_values(op, rs->sr_operational_attrs, &e_flags) ;
+
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu "
+ "matched values filtering failed\n",
+ op->o_connid );
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "matched values filtering error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+ }
+
+ for (a = rs->sr_operational_attrs, j=0; a != NULL; a = a->a_next, j++ ) {
+ AttributeDescription *desc = a->a_desc;
+
+ if ( rs->sr_attrs == NULL ) {
+ /* all user attrs request, skip operational attributes */
+ if( is_at_operational( desc->ad_type ) ) {
+ continue;
+ }
+
+ } else {
+ /* specific attrs requested */
+ if( is_at_operational( desc->ad_type ) ) {
+ if ( !SLAP_OPATTRS( rs->sr_attr_flags ) &&
+ !ad_inlist( desc, rs->sr_attrs ) )
+ {
+ continue;
+ }
+ /* if DSA-specific and replicating, skip */
+ if ( op->o_sync != SLAP_CONTROL_NONE &&
+ desc->ad_type->sat_usage == LDAP_SCHEMA_DSA_OPERATION )
+ continue;
+ } else {
+ if ( !userattrs && !ad_inlist( desc, rs->sr_attrs ) ) {
+ continue;
+ }
+ }
+ }
+
+ if ( ! access_allowed( op, rs->sr_entry, desc, NULL,
+ ACL_READ, &acl_state ) )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_entry: conn %lu "
+ "access to attribute %s not allowed\n",
+ op->o_connid, desc->ad_cname.bv_val );
+
+ continue;
+ }
+
+ rc = ber_printf( ber, "{O[" /*]}*/ , &desc->ad_cname );
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu "
+ "ber_printf failed\n", op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "encoding description error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+
+ if ( ! attrsonly ) {
+ for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) {
+ if ( ! access_allowed( op, rs->sr_entry,
+ desc, &a->a_vals[i], ACL_READ, &acl_state ) )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_entry: conn %lu "
+ "access to %s, value %d not allowed\n",
+ op->o_connid, desc->ad_cname.bv_val, i );
+
+ continue;
+ }
+
+ if ( op->o_vrFilter && e_flags[j][i] == 0 ){
+ continue;
+ }
+
+ if (( rc = ber_printf( ber, "O", &a->a_vals[i] )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER,
+ "encoding values error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+ }
+
+ if (( rc = ber_printf( ber, /*{[*/ "]N}" )) == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber_printf failed\n",
+ op->o_connid );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER, "encode end error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+ }
+
+ /* free e_flags */
+ if ( e_flags ) {
+ slap_sl_free( e_flags, op->o_tmpmemctx );
+ e_flags = NULL;
+ }
+
+ rc = ber_printf( ber, /*{{*/ "}N}" );
+
+ if( rc != -1 ) {
+ rc = send_ldap_controls( op, ber, rs->sr_ctrls );
+ }
+
+ if( rc != -1 ) {
+#ifdef LDAP_CONNECTIONLESS
+ if( op->o_conn && op->o_conn->c_is_udp ) {
+ if ( op->o_protocol != LDAP_VERSION2 ) {
+ rc = ber_printf( ber, /*{*/ "N}" );
+ }
+ } else
+#endif
+ if ( op->o_res_ber == NULL ) {
+ rc = ber_printf( ber, /*{*/ "N}" );
+ }
+ }
+
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY, "ber_printf failed\n" );
+
+ if ( op->o_res_ber == NULL ) ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER, "encode entry end error" );
+ rc = rs->sr_err;
+ goto error_return;
+ }
+
+ Debug( LDAP_DEBUG_STATS2, "%s ENTRY dn=\"%s\"\n",
+ op->o_log_prefix, rs->sr_entry->e_nname.bv_val );
+
+ rs_flush_entry( op, rs, NULL );
+
+ if ( op->o_res_ber == NULL ) {
+ bytes = send_ldap_ber( op, ber );
+ ber_free_buf( ber );
+
+ if ( bytes < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_entry: conn %lu ber write failed.\n",
+ op->o_connid );
+
+ rc = LDAP_UNAVAILABLE;
+ goto error_return;
+ }
+ rs->sr_nentries++;
+
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_entries, 1 );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 );
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<= send_search_entry: conn %lu exit.\n", op->o_connid );
+
+ rc = LDAP_SUCCESS;
+
+error_return:;
+ if ( op->o_callback ) {
+ (void)slap_cleanup_play( op, rs );
+ }
+
+ if ( e_flags ) {
+ slap_sl_free( e_flags, op->o_tmpmemctx );
+ }
+
+ /* FIXME: Can break if rs now contains an extended response */
+ if ( rs->sr_operational_attrs ) {
+ attrs_free( rs->sr_operational_attrs );
+ rs->sr_operational_attrs = NULL;
+ }
+ rs->sr_attr_flags = SLAP_ATTRS_UNDEFINED;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) {
+ rs_flush_entry( op, rs, NULL );
+ } else {
+ RS_ASSERT( (rs->sr_flags & REP_ENTRY_MASK) == 0 );
+ }
+
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) {
+ rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */
+ if ( rs->sr_ctrls ) {
+ slap_free_ctrls( op, rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ }
+
+ return( rc );
+}
+
+int
+slap_send_search_reference( Operation *op, SlapReply *rs )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ int rc = 0;
+ int bytes;
+ char *edn = rs->sr_entry ? rs->sr_entry->e_name.bv_val : "(null)";
+
+ AttributeDescription *ad_ref = slap_schema.si_ad_ref;
+ AttributeDescription *ad_entry = slap_schema.si_ad_entry;
+
+ rs->sr_type = REP_SEARCHREF;
+ if ( op->o_callback ) {
+ rc = slap_response_play( op, rs );
+ if ( rc != SLAP_CB_CONTINUE ) {
+ goto rel;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "=> send_search_reference: dn=\"%s\"\n",
+ edn );
+
+ if ( rs->sr_entry && ! access_allowed( op, rs->sr_entry,
+ ad_entry, NULL, ACL_READ, NULL ) )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_reference: access to entry not allowed\n" );
+ rc = 1;
+ goto rel;
+ }
+
+ if ( rs->sr_entry && ! access_allowed( op, rs->sr_entry,
+ ad_ref, NULL, ACL_READ, NULL ) )
+ {
+ Debug( LDAP_DEBUG_ACL,
+ "send_search_reference: access "
+ "to reference not allowed\n" );
+ rc = 1;
+ goto rel;
+ }
+
+ if( op->o_domain_scope ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_reference: domainScope control in (%s)\n",
+ edn );
+ rc = 0;
+ goto rel;
+ }
+
+ if( rs->sr_ref == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_reference: null ref in (%s)\n",
+ edn );
+ rc = 1;
+ goto rel;
+ }
+
+ if( op->o_protocol < LDAP_VERSION3 ) {
+ rc = 0;
+ /* save the references for the result */
+ if( rs->sr_ref[0].bv_val != NULL ) {
+ if( value_add( &rs->sr_v2ref, rs->sr_ref ) )
+ rc = LDAP_OTHER;
+ }
+ goto rel;
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ if( op->o_conn && op->o_conn->c_is_udp ) {
+ ber = op->o_res_ber;
+ } else
+#endif
+ {
+ ber_init_w_nullc( ber, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+ }
+
+ rc = ber_printf( ber, "{it{W}" /*"}"*/ , op->o_msgid,
+ LDAP_RES_SEARCH_REFERENCE, rs->sr_ref );
+
+ if( rc != -1 ) {
+ rc = send_ldap_controls( op, ber, rs->sr_ctrls );
+ }
+
+ if( rc != -1 ) {
+ rc = ber_printf( ber, /*"{"*/ "N}" );
+ }
+
+ if ( rc == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "send_search_reference: ber_printf failed\n" );
+
+#ifdef LDAP_CONNECTIONLESS
+ if (!op->o_conn || op->o_conn->c_is_udp == 0)
+#endif
+ ber_free_buf( ber );
+ set_ldap_error( rs, LDAP_OTHER, "encode DN error" );
+ goto rel;
+ }
+
+ rc = 0;
+ rs_flush_entry( op, rs, NULL );
+
+#ifdef LDAP_CONNECTIONLESS
+ if (!op->o_conn || op->o_conn->c_is_udp == 0) {
+#endif
+ bytes = send_ldap_ber( op, ber );
+ ber_free_buf( ber );
+
+ if ( bytes < 0 ) {
+ rc = LDAP_UNAVAILABLE;
+ } else {
+ ldap_pvt_thread_mutex_lock( &op->o_counters->sc_mutex );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_bytes, (unsigned long)bytes );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_refs, 1 );
+ ldap_pvt_mp_add_ulong( op->o_counters->sc_pdu, 1 );
+ ldap_pvt_thread_mutex_unlock( &op->o_counters->sc_mutex );
+ }
+#ifdef LDAP_CONNECTIONLESS
+ }
+#endif
+ if ( rs->sr_ref != NULL ) {
+ int r;
+
+ for ( r = 0; !BER_BVISNULL( &rs->sr_ref[ r ] ); r++ ) {
+ Debug( LDAP_DEBUG_STATS2, "%s REF #%d \"%s\"\n",
+ op->o_log_prefix, r, rs->sr_ref[0].bv_val );
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_STATS2, "%s REF \"(null)\"\n",
+ op->o_log_prefix );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<= send_search_reference\n" );
+
+ if ( 0 ) {
+rel:
+ rs_flush_entry( op, rs, NULL );
+ }
+
+ if ( op->o_callback ) {
+ (void)slap_cleanup_play( op, rs );
+ }
+
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) {
+ rs->sr_flags ^= REP_CTRLS_MUSTBEFREED; /* paranoia */
+ if ( rs->sr_ctrls ) {
+ slap_free_ctrls( op, rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ }
+
+ return rc;
+}
+
+int
+str2result(
+ char *s,
+ int *code,
+ char **matched,
+ char **info )
+{
+ int rc;
+ char *c;
+
+ *code = LDAP_SUCCESS;
+ *matched = NULL;
+ *info = NULL;
+
+ if ( strncasecmp( s, "RESULT", STRLENOF( "RESULT" ) ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) expecting \"RESULT\"\n",
+ s );
+
+ return( -1 );
+ }
+
+ rc = 0;
+ while ( (s = strchr( s, '\n' )) != NULL ) {
+ *s++ = '\0';
+ if ( *s == '\0' ) {
+ break;
+ }
+ if ( (c = strchr( s, ':' )) != NULL ) {
+ c++;
+ }
+
+ if ( strncasecmp( s, "code", STRLENOF( "code" ) ) == 0 ) {
+ char *next = NULL;
+ long retcode;
+
+ if ( c == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) missing value\n",
+ s );
+ rc = -1;
+ continue;
+ }
+
+ while ( isspace( (unsigned char) c[ 0 ] ) ) c++;
+ if ( c[ 0 ] == '\0' ) {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) missing or empty value\n",
+ s );
+ rc = -1;
+ continue;
+ }
+
+ retcode = strtol( c, &next, 10 );
+ if ( next == NULL || next == c ) {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) unable to parse value\n",
+ s );
+ rc = -1;
+ continue;
+ }
+
+ while ( isspace( (unsigned char) next[ 0 ] ) && next[ 0 ] != '\n' )
+ next++;
+ if ( next[ 0 ] != '\0' && next[ 0 ] != '\n' ) {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) extra cruft after value\n",
+ s );
+ rc = -1;
+ continue;
+ }
+
+ /* FIXME: what if it's larger than max int? */
+ *code = (int)retcode;
+
+ } else if ( strncasecmp( s, "matched", STRLENOF( "matched" ) ) == 0 ) {
+ if ( c != NULL ) {
+ *matched = c;
+ }
+ } else if ( strncasecmp( s, "info", STRLENOF( "info" ) ) == 0 ) {
+ if ( c != NULL ) {
+ *info = c;
+ }
+ } else {
+ Debug( LDAP_DEBUG_ANY, "str2result (%s) unknown\n",
+ s );
+
+ rc = -1;
+ }
+ }
+
+ return( rc );
+}
+
+int slap_read_controls(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ const struct berval *oid,
+ LDAPControl **ctrl )
+{
+ int rc;
+ struct berval bv;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ LDAPControl c;
+ Operation myop;
+
+ Debug( LDAP_DEBUG_STATS, "%s slap_read_controls: (%s) %s\n",
+ op->o_log_prefix, oid->bv_val, e->e_dn );
+
+ rs->sr_entry = e;
+ rs->sr_attrs = ( oid == &slap_pre_read_bv ) ?
+ op->o_preread_attrs : op->o_postread_attrs;
+
+ bv.bv_len = entry_flatsize( rs->sr_entry, 0 );
+ bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx );
+
+ ber_init2( ber, &bv, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* create new operation */
+ myop = *op;
+ /* FIXME: o_bd needed for ACL */
+ myop.o_bd = op->o_bd;
+ myop.o_res_ber = ber;
+ myop.o_callback = NULL;
+ myop.ors_slimit = 1;
+ myop.ors_attrsonly = 0;
+
+ rc = slap_send_search_entry( &myop, rs );
+ if( rc ) return rc;
+
+ rc = ber_flatten2( ber, &c.ldctl_value, 0 );
+
+ if( rc == -1 ) return LDAP_OTHER;
+
+ c.ldctl_oid = oid->bv_val;
+ c.ldctl_iscritical = 0;
+
+ if ( *ctrl == NULL ) {
+ /* first try */
+ *ctrl = (LDAPControl *) slap_sl_calloc( 1, sizeof(LDAPControl), NULL );
+ } else {
+ /* retry: free previous try */
+ slap_sl_free( (*ctrl)->ldctl_value.bv_val, op->o_tmpmemctx );
+ }
+
+ **ctrl = c;
+ return LDAP_SUCCESS;
+}
+
+/* Map API errors to protocol errors... */
+int
+slap_map_api2result( SlapReply *rs )
+{
+ switch(rs->sr_err) {
+ case LDAP_SERVER_DOWN:
+ return LDAP_UNAVAILABLE;
+ case LDAP_LOCAL_ERROR:
+ return LDAP_OTHER;
+ case LDAP_ENCODING_ERROR:
+ case LDAP_DECODING_ERROR:
+ return LDAP_PROTOCOL_ERROR;
+ case LDAP_TIMEOUT:
+ return LDAP_UNAVAILABLE;
+ case LDAP_AUTH_UNKNOWN:
+ return LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ case LDAP_FILTER_ERROR:
+ rs->sr_text = "Filter error";
+ return LDAP_OTHER;
+ case LDAP_USER_CANCELLED:
+ rs->sr_text = "User cancelled";
+ return LDAP_OTHER;
+ case LDAP_PARAM_ERROR:
+ return LDAP_PROTOCOL_ERROR;
+ case LDAP_NO_MEMORY:
+ return LDAP_OTHER;
+ case LDAP_CONNECT_ERROR:
+ return LDAP_UNAVAILABLE;
+ case LDAP_NOT_SUPPORTED:
+ return LDAP_UNWILLING_TO_PERFORM;
+ case LDAP_CONTROL_NOT_FOUND:
+ return LDAP_PROTOCOL_ERROR;
+ case LDAP_NO_RESULTS_RETURNED:
+ return LDAP_NO_SUCH_OBJECT;
+ case LDAP_MORE_RESULTS_TO_RETURN:
+ rs->sr_text = "More results to return";
+ return LDAP_OTHER;
+ case LDAP_CLIENT_LOOP:
+ case LDAP_REFERRAL_LIMIT_EXCEEDED:
+ return LDAP_LOOP_DETECT;
+ default:
+ if ( LDAP_API_ERROR(rs->sr_err) ) return LDAP_OTHER;
+ return rs->sr_err;
+ }
+}
+
+
+slap_mask_t
+slap_attr_flags( AttributeName *an )
+{
+ slap_mask_t flags = SLAP_ATTRS_UNDEFINED;
+
+ if ( an == NULL ) {
+ flags |= ( SLAP_OPATTRS_NO | SLAP_USERATTRS_YES );
+
+ } else {
+ flags |= an_find( an, slap_bv_all_operational_attrs )
+ ? SLAP_OPATTRS_YES : SLAP_OPATTRS_NO;
+ flags |= an_find( an, slap_bv_all_user_attrs )
+ ? SLAP_USERATTRS_YES : SLAP_USERATTRS_NO;
+ }
+
+ return flags;
+}
diff --git a/servers/slapd/root_dse.c b/servers/slapd/root_dse.c
new file mode 100644
index 0000000..368bd9b
--- /dev/null
+++ b/servers/slapd/root_dse.c
@@ -0,0 +1,542 @@
+/* root_dse.c - Provides the Root DSA-Specific Entry */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include <ldif.h>
+#include "lber_pvt.h"
+
+#ifdef LDAP_SLAPI
+#include "slapi/slapi.h"
+#endif
+
+static struct berval builtin_supportedFeatures[] = {
+ BER_BVC(LDAP_FEATURE_MODIFY_INCREMENT), /* Modify/increment */
+ BER_BVC(LDAP_FEATURE_ALL_OP_ATTRS), /* All Op Attrs (+) */
+ BER_BVC(LDAP_FEATURE_OBJECTCLASS_ATTRS), /* OCs in Attrs List (@class) */
+ BER_BVC(LDAP_FEATURE_ABSOLUTE_FILTERS), /* (&) and (|) search filters */
+ BER_BVC(LDAP_FEATURE_LANGUAGE_TAG_OPTIONS), /* Language Tag Options */
+ BER_BVC(LDAP_FEATURE_LANGUAGE_RANGE_OPTIONS), /* Language Range Options */
+#ifdef LDAP_DEVEL
+ BER_BVC(LDAP_FEATURE_SUBORDINATE_SCOPE), /* "children" search scope */
+#endif
+ BER_BVNULL
+};
+static struct berval *supportedFeatures;
+
+static Entry *usr_attr = NULL;
+
+/*
+ * allow modules to register functions that muck with the root DSE entry
+ */
+
+typedef struct entry_info_t {
+ SLAP_ENTRY_INFO_FN func;
+ void *arg;
+ struct entry_info_t *next;
+} entry_info_t;
+
+static entry_info_t *extra_info;
+
+int
+entry_info_register( SLAP_ENTRY_INFO_FN func, void *arg )
+{
+ entry_info_t *ei = ch_calloc( 1, sizeof( entry_info_t ) );
+
+ ei->func = func;
+ ei->arg = arg;
+
+ ei->next = extra_info;
+ extra_info = ei;
+
+ return 0;
+}
+
+int
+entry_info_unregister( SLAP_ENTRY_INFO_FN func, void *arg )
+{
+ entry_info_t **eip;
+
+ for ( eip = &extra_info; *eip != NULL; eip = &(*eip)->next ) {
+ if ( (*eip)->func == func && (*eip)->arg == arg ) {
+ entry_info_t *ei = *eip;
+
+ *eip = ei->next;
+
+ ch_free( ei );
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+void
+entry_info_destroy( void )
+{
+ entry_info_t **eip;
+
+ for ( eip = &extra_info; *eip != NULL; ) {
+ entry_info_t *ei = *eip;
+
+ eip = &(*eip)->next;
+
+ ch_free( ei );
+ }
+}
+
+/*
+ * Allow modules to register supported features
+ */
+
+static int
+supported_feature_init( void )
+{
+ int i;
+
+ if ( supportedFeatures != NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &builtin_supportedFeatures[ i ] ); i++ )
+ ;
+
+ supportedFeatures = ch_calloc( sizeof( struct berval ), i + 1 );
+ if ( supportedFeatures == NULL ) {
+ return -1;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &builtin_supportedFeatures[ i ] ); i++ ) {
+ ber_dupbv( &supportedFeatures[ i ], &builtin_supportedFeatures[ i ] );
+ }
+ BER_BVZERO( &supportedFeatures[ i ] );
+
+ return 0;
+}
+
+int
+supported_feature_destroy( void )
+{
+ int i;
+
+ if ( supportedFeatures == NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &supportedFeatures[ i ] ); i++ ) {
+ ch_free( supportedFeatures[ i ].bv_val );
+ }
+
+ ch_free( supportedFeatures );
+ supportedFeatures = NULL;
+
+ return 0;
+}
+
+int
+supported_feature_load( struct berval *f )
+{
+ struct berval *tmp;
+ int i;
+
+ supported_feature_init();
+
+ for ( i = 0; !BER_BVISNULL( &supportedFeatures[ i ] ); i++ )
+ ;
+
+ tmp = ch_realloc( supportedFeatures, sizeof( struct berval ) * ( i + 2 ) );
+ if ( tmp == NULL ) {
+ return -1;
+ }
+ supportedFeatures = tmp;
+
+ ber_dupbv( &supportedFeatures[ i ], f );
+ BER_BVZERO( &supportedFeatures[ i + 1 ] );
+
+ return 0;
+}
+
+int
+root_dse_info(
+ Connection *conn,
+ Entry **entry,
+ const char **text )
+{
+ Entry *e;
+ struct berval val;
+#ifdef LDAP_SLAPI
+ struct berval *bv;
+#endif
+ int i, j;
+ char ** supportedSASLMechanisms;
+ BackendDB *be;
+
+ AttributeDescription *ad_structuralObjectClass
+ = slap_schema.si_ad_structuralObjectClass;
+ AttributeDescription *ad_objectClass
+ = slap_schema.si_ad_objectClass;
+ AttributeDescription *ad_namingContexts
+ = slap_schema.si_ad_namingContexts;
+#ifdef LDAP_SLAPI
+ AttributeDescription *ad_supportedExtension
+ = slap_schema.si_ad_supportedExtension;
+#endif
+ AttributeDescription *ad_supportedLDAPVersion
+ = slap_schema.si_ad_supportedLDAPVersion;
+ AttributeDescription *ad_supportedSASLMechanisms
+ = slap_schema.si_ad_supportedSASLMechanisms;
+ AttributeDescription *ad_supportedFeatures
+ = slap_schema.si_ad_supportedFeatures;
+ AttributeDescription *ad_monitorContext
+ = slap_schema.si_ad_monitorContext;
+ AttributeDescription *ad_configContext
+ = slap_schema.si_ad_configContext;
+ AttributeDescription *ad_ref
+ = slap_schema.si_ad_ref;
+
+ e = entry_alloc();
+ if( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "root_dse_info: entry_alloc failed" );
+ return LDAP_OTHER;
+ }
+
+ e->e_attrs = NULL;
+ e->e_name.bv_val = ch_strdup( LDAP_ROOT_DSE );
+ e->e_name.bv_len = sizeof( LDAP_ROOT_DSE )-1;
+ e->e_nname.bv_val = ch_strdup( LDAP_ROOT_DSE );
+ e->e_nname.bv_len = sizeof( LDAP_ROOT_DSE )-1;
+
+ /* the DN is an empty string so no pretty/normalization is needed */
+ assert( !e->e_name.bv_len );
+ assert( !e->e_nname.bv_len );
+
+ e->e_private = NULL;
+
+ /* FIXME: is this really needed? */
+ BER_BVSTR( &val, "top" );
+ if( attr_merge_one( e, ad_objectClass, &val, NULL ) ) {
+fail:
+ entry_free( e );
+ return LDAP_OTHER;
+ }
+
+ BER_BVSTR( &val, "OpenLDAProotDSE" );
+ if( attr_merge_one( e, ad_objectClass, &val, NULL ) ) {
+ goto fail;
+ }
+ if( attr_merge_one( e, ad_structuralObjectClass, &val, NULL ) ) {
+ goto fail;
+ }
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( be->be_suffix == NULL
+ || be->be_nsuffix == NULL ) {
+ /* no suffix! */
+ continue;
+ }
+ if ( SLAP_DBHIDDEN( be )) {
+ continue;
+ }
+ if ( SLAP_MONITOR( be )) {
+ if( attr_merge_one( e, ad_monitorContext,
+ &be->be_suffix[0],
+ &be->be_nsuffix[0] ) )
+ {
+ goto fail;
+ }
+ continue;
+ }
+ if ( SLAP_CONFIG( be )) {
+ if( attr_merge_one( e, ad_configContext,
+ &be->be_suffix[0],
+ & be->be_nsuffix[0] ) )
+ {
+ goto fail;
+ }
+ continue;
+ }
+ if ( SLAP_GLUE_SUBORDINATE( be ) && !SLAP_GLUE_ADVERTISE( be ) ) {
+ continue;
+ }
+ if ( attr_merge( e, ad_namingContexts,
+ be->be_suffix, be->be_nsuffix ) ) {
+ goto fail;
+ }
+ }
+
+ /* altServer unsupported */
+
+ /* supportedControl */
+ if ( controls_root_dse_info( e ) != 0 ) {
+ goto fail;
+ }
+
+ /* supportedExtension */
+ if ( exop_root_dse_info( e ) != 0 ) {
+ goto fail;
+ }
+
+#ifdef LDAP_SLAPI
+ /* netscape supportedExtension */
+ for ( i = 0; (bv = slapi_int_get_supported_extop(i)) != NULL; i++ ) {
+ if( attr_merge_one( e, ad_supportedExtension, bv, NULL ) ) {
+ goto fail;
+ }
+ }
+#endif /* LDAP_SLAPI */
+
+ /* supportedFeatures */
+ if ( supportedFeatures == NULL ) {
+ supported_feature_init();
+ }
+
+ if( attr_merge( e, ad_supportedFeatures, supportedFeatures, NULL ) ) {
+ goto fail;
+ }
+
+ /* supportedLDAPVersion */
+ /* don't publish version 2 as we don't really support it
+ * (even when configured to accept version 2 Bind requests)
+ * and the value would never be used by true LDAPv2 (or LDAPv3)
+ * clients.
+ */
+ for ( i=LDAP_VERSION3; i<=LDAP_VERSION_MAX; i++ ) {
+ char buf[sizeof("255")];
+ snprintf(buf, sizeof buf, "%d", i);
+ val.bv_val = buf;
+ val.bv_len = strlen( val.bv_val );
+ if( attr_merge_one( e, ad_supportedLDAPVersion, &val, NULL ) ) {
+ goto fail;
+ }
+ }
+
+ /* supportedSASLMechanism */
+ supportedSASLMechanisms = slap_sasl_mechs( conn );
+
+ if( supportedSASLMechanisms != NULL ) {
+ for ( i=0; supportedSASLMechanisms[i] != NULL; i++ ) {
+ val.bv_val = supportedSASLMechanisms[i];
+ val.bv_len = strlen( val.bv_val );
+ if( attr_merge_one( e, ad_supportedSASLMechanisms, &val, NULL ) ) {
+ ldap_charray_free( supportedSASLMechanisms );
+ goto fail;
+ }
+ }
+ ldap_charray_free( supportedSASLMechanisms );
+ }
+
+ if ( default_referral != NULL ) {
+ if( attr_merge( e, ad_ref, default_referral, NULL /* FIXME */ ) ) {
+ goto fail;
+ }
+ }
+
+ if( usr_attr != NULL) {
+ Attribute *a;
+ for( a = usr_attr->e_attrs; a != NULL; a = a->a_next ) {
+ if( attr_merge( e, a->a_desc, a->a_vals,
+ (a->a_nvals == a->a_vals) ? NULL : a->a_nvals ) )
+ {
+ goto fail;
+ }
+ }
+ }
+
+ if ( extra_info ) {
+ entry_info_t *ei = extra_info;
+
+ for ( ; ei; ei = ei->next ) {
+ ei->func( ei->arg, e );
+ }
+ }
+
+ *entry = e;
+ return LDAP_SUCCESS;
+}
+
+int
+root_dse_init( void )
+{
+ return 0;
+}
+
+int
+root_dse_destroy( void )
+{
+ if ( usr_attr ) {
+ entry_free( usr_attr );
+ usr_attr = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Read the entries specified in fname and merge the attributes
+ * to the user defined rootDSE. Note thaat if we find any errors
+ * what so ever, we will discard the entire entries, print an
+ * error message and return.
+ */
+int
+root_dse_read_file( const char *fname )
+{
+ struct LDIFFP *fp;
+ int rc = 0, lmax = 0, ldifrc;
+ unsigned long lineno = 0;
+ char *buf = NULL;
+
+ if ( (fp = ldif_open( fname, "r" )) == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "root_dse_read_file: could not open rootdse attr file \"%s\" - absolute path?\n",
+ fname );
+ perror( fname );
+ return EXIT_FAILURE;
+ }
+
+ usr_attr = entry_alloc();
+ if( usr_attr == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "root_dse_read_file: entry_alloc failed" );
+ ldif_close( fp );
+ return LDAP_OTHER;
+ }
+ usr_attr->e_attrs = NULL;
+
+ while(( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) {
+ Entry *e = str2entry( buf );
+ Attribute *a;
+
+ if( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "root_dse_read_file: "
+ "could not parse entry (file=\"%s\" line=%lu)\n",
+ fname, lineno );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /* make sure the DN is the empty DN */
+ if( e->e_nname.bv_len ) {
+ Debug( LDAP_DEBUG_ANY,
+ "root_dse_read_file: invalid rootDSE "
+ "- dn=\"%s\" (file=\"%s\" line=%lu)\n",
+ e->e_dn, fname, lineno );
+ entry_free( e );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /*
+ * we found a valid entry, so walk thru all the attributes in the
+ * entry, and add each attribute type and description to the
+ * usr_attr entry
+ */
+
+ for(a = e->e_attrs; a != NULL; a = a->a_next) {
+ if( attr_merge( usr_attr, a->a_desc, a->a_vals,
+ (a->a_nvals == a->a_vals) ? NULL : a->a_nvals ) )
+ {
+ rc = LDAP_OTHER;
+ break;
+ }
+ }
+
+ entry_free( e );
+ if (rc) break;
+ }
+
+ if ( ldifrc < 0 )
+ rc = LDAP_OTHER;
+
+ if (rc) {
+ entry_free( usr_attr );
+ usr_attr = NULL;
+ }
+
+ ch_free( buf );
+
+ ldif_close( fp );
+
+ Debug(LDAP_DEBUG_CONFIG, "rootDSE file=\"%s\" read.\n", fname );
+ return rc;
+}
+
+int
+slap_discover_feature(
+ slap_bindconf *sb,
+ const char *attr,
+ const char *val )
+{
+ LDAP *ld = NULL;
+ LDAPMessage *res = NULL, *entry;
+ int rc, i;
+ struct berval bv_val,
+ **values = NULL;
+ char *attrs[ 2 ] = { NULL, NULL };
+
+ rc = slap_client_connect( &ld, sb );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ attrs[ 0 ] = (char *) attr;
+ rc = ldap_search_ext_s( ld, "", LDAP_SCOPE_BASE, "(objectClass=*)",
+ attrs, 0, NULL, NULL, NULL, 0, &res );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ entry = ldap_first_entry( ld, res );
+ if ( entry == NULL ) {
+ goto done;
+ }
+
+ values = ldap_get_values_len( ld, entry, attrs[ 0 ] );
+ if ( values == NULL ) {
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ ber_str2bv( val, 0, 0, &bv_val );
+ for ( i = 0; values[ i ] != NULL; i++ ) {
+ if ( bvmatch( &bv_val, values[ i ] ) ) {
+ rc = LDAP_COMPARE_TRUE;
+ goto done;
+ }
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+
+done:;
+ if ( values != NULL ) {
+ ldap_value_free_len( values );
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+
+ ldap_unbind_ext( ld, NULL, NULL );
+
+ return rc;
+}
+
diff --git a/servers/slapd/sasl.c b/servers/slapd/sasl.c
new file mode 100644
index 0000000..1e17304
--- /dev/null
+++ b/servers/slapd/sasl.c
@@ -0,0 +1,2050 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/stdlib.h>
+#include <ac/string.h>
+
+#include <lber.h>
+#include <ldap_log.h>
+
+#include "slap.h"
+
+#include <rewrite.h>
+
+#ifdef HAVE_CYRUS_SASL
+# ifdef HAVE_SASL_SASL_H
+# include <sasl/sasl.h>
+# include <sasl/saslplug.h>
+# else
+# include <sasl.h>
+# include <saslplug.h>
+# endif
+
+# define SASL_CONST const
+
+#define SASL_VERSION_FULL ((SASL_VERSION_MAJOR << 16) |\
+ (SASL_VERSION_MINOR << 8) | SASL_VERSION_STEP)
+
+#if SASL_VERSION_FULL >= 0x020119 /* 2.1.25 */
+typedef sasl_callback_ft slap_sasl_cb_ft;
+#else
+typedef int (*slap_sasl_cb_ft)();
+#endif
+
+static sasl_security_properties_t sasl_secprops;
+#elif defined( SLAP_BUILTIN_SASL )
+/*
+ * built-in SASL implementation
+ * only supports EXTERNAL
+ */
+typedef struct sasl_ctx {
+ slap_ssf_t sc_external_ssf;
+ struct berval sc_external_id;
+} SASL_CTX;
+
+#endif
+
+#include <lutil.h>
+
+static struct berval ext_bv = BER_BVC( "EXTERNAL" );
+
+char *slap_sasl_auxprops;
+
+#ifdef HAVE_CYRUS_SASL
+
+/* Just use our internal auxprop by default */
+static int
+slap_sasl_getopt(
+ void *context,
+ const char *plugin_name,
+ const char *option,
+ const char **result,
+ unsigned *len)
+{
+ if ( strcmp( option, "auxprop_plugin" )) {
+ return SASL_FAIL;
+ }
+ if ( slap_sasl_auxprops )
+ *result = slap_sasl_auxprops;
+ else
+ *result = "slapd";
+ return SASL_OK;
+}
+
+int
+slap_sasl_log(
+ void *context,
+ int priority,
+ const char *message)
+{
+ Connection *conn = context;
+ int level;
+ const char * label;
+
+ if ( message == NULL ) {
+ return SASL_BADPARAM;
+ }
+
+ switch (priority) {
+ case SASL_LOG_NONE:
+ level = LDAP_DEBUG_NONE;
+ label = "None";
+ break;
+ case SASL_LOG_ERR:
+ level = LDAP_DEBUG_ANY;
+ label = "Error";
+ break;
+ case SASL_LOG_FAIL:
+ level = LDAP_DEBUG_ANY;
+ label = "Failure";
+ break;
+ case SASL_LOG_WARN:
+ level = LDAP_DEBUG_TRACE;
+ label = "Warning";
+ break;
+ case SASL_LOG_NOTE:
+ level = LDAP_DEBUG_TRACE;
+ label = "Notice";
+ break;
+ case SASL_LOG_DEBUG:
+ level = LDAP_DEBUG_TRACE;
+ label = "Debug";
+ break;
+ case SASL_LOG_TRACE:
+ level = LDAP_DEBUG_TRACE;
+ label = "Trace";
+ break;
+ case SASL_LOG_PASS:
+ level = LDAP_DEBUG_TRACE;
+ label = "Password Trace";
+ break;
+ default:
+ return SASL_BADPARAM;
+ }
+
+ Debug( level, "SASL [conn=%ld] %s: %s\n",
+ conn ? (long) conn->c_connid: -1L,
+ label, message );
+
+
+ return SASL_OK;
+}
+
+static const char *slap_propnames[] = {
+ "*slapConn", "*slapAuthcDNlen", "*slapAuthcDN",
+ "*slapAuthzDNlen", "*slapAuthzDN", NULL };
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+int slap_dontUseCopy_ignore;
+BerVarray slap_dontUseCopy_propnames;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL };
+static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
+
+#define SLAP_SASL_PROP_CONN 0
+#define SLAP_SASL_PROP_AUTHCLEN 1
+#define SLAP_SASL_PROP_AUTHC 2
+#define SLAP_SASL_PROP_AUTHZLEN 3
+#define SLAP_SASL_PROP_AUTHZ 4
+#define SLAP_SASL_PROP_COUNT 5 /* Number of properties we used */
+
+typedef struct lookup_info {
+ int flags;
+ const struct propval *list;
+ sasl_server_params_t *sparams;
+} lookup_info;
+
+static slap_response sasl_ap_lookup;
+
+static struct berval sc_cleartext = BER_BVC("{CLEARTEXT}");
+
+static int
+sasl_ap_lookup( Operation *op, SlapReply *rs )
+{
+ BerVarray bv;
+ AttributeDescription *ad;
+ Attribute *a;
+ const char *text;
+ int rc, i;
+ lookup_info *sl = (lookup_info *)op->o_callback->sc_private;
+
+ /* return the actual error code,
+ * to allow caller to handle specific errors
+ */
+ if (rs->sr_type != REP_SEARCH) return rs->sr_err;
+
+ for( i = 0; sl->list[i].name; i++ ) {
+ const char *name = sl->list[i].name;
+
+ if ( name[0] == '*' ) {
+ if ( sl->flags & SASL_AUXPROP_AUTHZID ) continue;
+ /* Skip our private properties */
+ if ( !strcmp( name, slap_propnames[0] )) {
+ i += SLAP_SASL_PROP_COUNT - 1;
+ continue;
+ }
+ name++;
+ } else if ( !(sl->flags & SASL_AUXPROP_AUTHZID ) )
+ continue;
+
+ if ( sl->list[i].values ) {
+ if ( !(sl->flags & SASL_AUXPROP_OVERRIDE) ) continue;
+ }
+ ad = NULL;
+ rc = slap_str2ad( name, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_ap_lookup: str2ad(%s): %s\n", name, text );
+ continue;
+ }
+
+ /* If it's the rootdn and a rootpw was present, we already set
+ * it so don't override it here.
+ */
+ if ( ad == slap_schema.si_ad_userPassword && sl->list[i].values &&
+ be_isroot_dn( op->o_bd, &op->o_req_ndn ))
+ continue;
+
+ a = attr_find( rs->sr_entry->e_attrs, ad );
+ if ( !a ) continue;
+ if ( ! access_allowed( op, rs->sr_entry, ad, NULL, ACL_AUTH, NULL ) ) {
+ continue;
+ }
+ if ( sl->list[i].values && ( sl->flags & SASL_AUXPROP_OVERRIDE ) ) {
+ sl->sparams->utils->prop_erase( sl->sparams->propctx,
+ sl->list[i].name );
+ }
+ for ( bv = a->a_vals; bv->bv_val; bv++ ) {
+ /* ITS#3846 don't give hashed passwords to SASL */
+ if ( ad == slap_schema.si_ad_userPassword &&
+ bv->bv_val[0] == '{' /*}*/ )
+ {
+ if ( lutil_passwd_scheme( bv->bv_val ) ) {
+ /* If it's not a recognized scheme, just assume it's
+ * a cleartext password that happened to include brackets.
+ *
+ * If it's a recognized scheme, skip this value, unless the
+ * scheme is {CLEARTEXT}. In that case, skip over the
+ * scheme name and use the remainder. If there is nothing
+ * past the scheme name, skip this value.
+ */
+#ifdef SLAPD_CLEARTEXT
+ if ( !strncasecmp( bv->bv_val, sc_cleartext.bv_val,
+ sc_cleartext.bv_len )) {
+ struct berval cbv;
+ cbv.bv_len = bv->bv_len - sc_cleartext.bv_len;
+ if ( cbv.bv_len > 0 ) {
+ cbv.bv_val = bv->bv_val + sc_cleartext.bv_len;
+ sl->sparams->utils->prop_set( sl->sparams->propctx,
+ sl->list[i].name, cbv.bv_val, cbv.bv_len );
+ }
+ }
+#endif
+ continue;
+ }
+ }
+ sl->sparams->utils->prop_set( sl->sparams->propctx,
+ sl->list[i].name, bv->bv_val, bv->bv_len );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+#if SASL_VERSION_FULL >= 0x020118
+static int
+#else
+static void
+#endif
+slap_auxprop_lookup(
+ void *glob_context,
+ sasl_server_params_t *sparams,
+ unsigned flags,
+ const char *user,
+ unsigned ulen)
+{
+ OperationBuffer opbuf = {{ NULL }};
+ Operation *op = (Operation *)&opbuf;
+ int i, doit = 0;
+ Connection *conn = NULL;
+ lookup_info sl;
+ int rc = LDAP_SUCCESS;
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ int dontUseCopy = 0;
+ BackendDB *dontUseCopy_bd = NULL;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ sl.list = sparams->utils->prop_get( sparams->propctx );
+ sl.sparams = sparams;
+ sl.flags = flags;
+
+ /* Find our DN and conn first */
+ for( i = 0; sl.list[i].name; i++ ) {
+ if ( sl.list[i].name[0] == '*' ) {
+ if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) {
+ if ( sl.list[i].values && sl.list[i].values[0] )
+ AC_MEMCPY( &conn, sl.list[i].values[0], sizeof( conn ) );
+ continue;
+ }
+ if ( flags & SASL_AUXPROP_AUTHZID ) {
+ if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZLEN] )) {
+ if ( sl.list[i].values && sl.list[i].values[0] )
+ AC_MEMCPY( &op->o_req_ndn.bv_len, sl.list[i].values[0],
+ sizeof( op->o_req_ndn.bv_len ) );
+ } else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHZ] )) {
+ if ( sl.list[i].values )
+ op->o_req_ndn.bv_val = (char *)sl.list[i].values[0];
+ break;
+ }
+ }
+
+ if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) {
+ if ( sl.list[i].values && sl.list[i].values[0] )
+ AC_MEMCPY( &op->o_req_ndn.bv_len, sl.list[i].values[0],
+ sizeof( op->o_req_ndn.bv_len ) );
+ } else if ( !strcmp( sl.list[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) {
+ if ( sl.list[i].values ) {
+ op->o_req_ndn.bv_val = (char *)sl.list[i].values[0];
+ if ( !(flags & SASL_AUXPROP_AUTHZID) )
+ break;
+ }
+ }
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( slap_dontUseCopy_propnames != NULL ) {
+ int j;
+ struct berval bv;
+ ber_str2bv( &sl.list[i].name[1], 0, 1, &bv );
+ for ( j = 0; !BER_BVISNULL( &slap_dontUseCopy_propnames[ j ]); j++ ) {
+ if ( bvmatch( &bv, &slap_dontUseCopy_propnames[ j ] ) ) {
+ dontUseCopy = 1;
+ break;
+ }
+ }
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+ }
+ }
+
+ /* we don't know anything about this, ignore it */
+ if ( !conn ) {
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ /* Now see what else needs to be fetched */
+ for( i = 0; sl.list[i].name; i++ ) {
+ const char *name = sl.list[i].name;
+
+ if ( name[0] == '*' ) {
+ if ( flags & SASL_AUXPROP_AUTHZID ) continue;
+ /* Skip our private properties */
+ if ( !strcmp( name, slap_propnames[0] )) {
+ i += SLAP_SASL_PROP_COUNT - 1;
+ continue;
+ }
+ name++;
+ } else if ( !(flags & SASL_AUXPROP_AUTHZID ) )
+ continue;
+
+ if ( sl.list[i].values ) {
+ if ( !(flags & SASL_AUXPROP_OVERRIDE) ) continue;
+ }
+ doit = 1;
+ break;
+ }
+
+ if (doit) {
+ slap_callback cb = { NULL, sasl_ap_lookup, NULL, NULL };
+
+ cb.sc_private = &sl;
+
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+
+ if ( op->o_bd ) {
+ /* For rootdn, see if we can use the rootpw */
+ if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) &&
+ !BER_BVISEMPTY( &op->o_bd->be_rootpw )) {
+ struct berval cbv = BER_BVNULL;
+
+ /* If there's a recognized scheme, see if it's CLEARTEXT */
+ if ( lutil_passwd_scheme( op->o_bd->be_rootpw.bv_val )) {
+ if ( !strncasecmp( op->o_bd->be_rootpw.bv_val,
+ sc_cleartext.bv_val, sc_cleartext.bv_len )) {
+
+ /* If it's CLEARTEXT, skip past scheme spec */
+ cbv.bv_len = op->o_bd->be_rootpw.bv_len -
+ sc_cleartext.bv_len;
+ if ( cbv.bv_len ) {
+ cbv.bv_val = op->o_bd->be_rootpw.bv_val +
+ sc_cleartext.bv_len;
+ }
+ }
+ /* No scheme, use the whole value */
+ } else {
+ cbv = op->o_bd->be_rootpw;
+ }
+ if ( !BER_BVISEMPTY( &cbv )) {
+ for( i = 0; sl.list[i].name; i++ ) {
+ const char *name = sl.list[i].name;
+
+ if ( name[0] == '*' ) {
+ if ( flags & SASL_AUXPROP_AUTHZID ) continue;
+ name++;
+ } else if ( !(flags & SASL_AUXPROP_AUTHZID ) )
+ continue;
+
+ if ( !strcasecmp(name,"userPassword") ) {
+ sl.sparams->utils->prop_set( sl.sparams->propctx,
+ sl.list[i].name, cbv.bv_val, cbv.bv_len );
+ break;
+ }
+ }
+ }
+ }
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( SLAP_SHADOW( op->o_bd ) && dontUseCopy ) {
+ dontUseCopy_bd = op->o_bd;
+ op->o_bd = frontendDB;
+ }
+
+retry_dontUseCopy:;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ if ( op->o_bd->be_search ) {
+ SlapReply rs = {REP_RESULT};
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ LDAPControl **save_ctrls = NULL, c;
+ int save_dontUseCopy;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ op->o_hdr = conn->c_sasl_bindop->o_hdr;
+ op->o_controls = opbuf.ob_controls;
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_dn = conn->c_ndn;
+ op->o_ndn = conn->c_ndn;
+ op->o_callback = &cb;
+ slap_op_time( &op->o_time, &op->o_tincr );
+ op->o_do_not_cache = 1;
+ op->o_is_auth_check = 1;
+ op->o_req_dn = op->o_req_ndn;
+ op->ors_scope = LDAP_SCOPE_BASE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = 1;
+ op->ors_filter = &generic_filter;
+ op->ors_filterstr = generic_filterstr;
+ op->o_authz = conn->c_authz;
+ /* FIXME: we want all attributes, right? */
+ op->ors_attrs = NULL;
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( dontUseCopy ) {
+ save_dontUseCopy = op->o_dontUseCopy;
+ if ( !op->o_dontUseCopy ) {
+ int cnt = 0;
+ save_ctrls = op->o_ctrls;
+ if ( op->o_ctrls ) {
+ for ( ; op->o_ctrls[ cnt ]; cnt++ )
+ ;
+ }
+ op->o_ctrls = op->o_tmpcalloc( sizeof(LDAPControl *), cnt + 2, op->o_tmpmemctx );
+ if ( cnt ) {
+ for ( cnt = 0; save_ctrls[ cnt ]; cnt++ ) {
+ op->o_ctrls[ cnt ] = save_ctrls[ cnt ];
+ }
+ }
+ c.ldctl_oid = LDAP_CONTROL_DONTUSECOPY;
+ c.ldctl_iscritical = 1;
+ BER_BVZERO( &c.ldctl_value );
+ op->o_ctrls[ cnt ] = &c;
+ }
+ op->o_dontUseCopy = SLAP_CONTROL_CRITICAL;
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ rc = op->o_bd->be_search( op, &rs );
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( dontUseCopy ) {
+ if ( save_ctrls != op->o_ctrls ) {
+ op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+ op->o_ctrls = save_ctrls;
+ op->o_dontUseCopy = save_dontUseCopy;
+ }
+
+ if ( rs.sr_err == LDAP_UNAVAILABLE && slap_dontUseCopy_ignore )
+ {
+ op->o_bd = dontUseCopy_bd;
+ dontUseCopy = 0;
+ goto retry_dontUseCopy;
+ }
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+ }
+ }
+ }
+done:;
+#if SASL_VERSION_FULL >= 0x020118
+ return rc != LDAP_SUCCESS ? SASL_FAIL : SASL_OK;
+#endif
+}
+
+#if SASL_VERSION_FULL >= 0x020110
+static int
+slap_auxprop_store(
+ void *glob_context,
+ sasl_server_params_t *sparams,
+ struct propctx *prctx,
+ const char *user,
+ unsigned ulen)
+{
+ Operation op = {0};
+ Opheader oph;
+ int rc, i;
+ unsigned j;
+ Connection *conn = NULL;
+ const struct propval *pr;
+ Modifications *modlist = NULL, **modtail = &modlist, *mod;
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ char textbuf[SLAP_TEXT_BUFLEN];
+ const char *text;
+ size_t textlen = sizeof(textbuf);
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ int dontUseCopy = 0;
+ BackendDB *dontUseCopy_bd = NULL;
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ /* just checking if we are enabled */
+ if (!prctx) return SASL_OK;
+
+ if (!sparams || !user) return SASL_BADPARAM;
+
+ pr = sparams->utils->prop_get( sparams->propctx );
+
+ /* Find our DN and conn first */
+ for( i = 0; pr[i].name; i++ ) {
+ if ( pr[i].name[0] == '*' ) {
+ if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_CONN] ) ) {
+ if ( pr[i].values && pr[i].values[0] )
+ AC_MEMCPY( &conn, pr[i].values[0], sizeof( conn ) );
+ continue;
+ }
+ if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHCLEN] )) {
+ if ( pr[i].values && pr[i].values[0] )
+ AC_MEMCPY( &op.o_req_ndn.bv_len, pr[i].values[0],
+ sizeof( op.o_req_ndn.bv_len ) );
+ } else if ( !strcmp( pr[i].name, slap_propnames[SLAP_SASL_PROP_AUTHC] ) ) {
+ if ( pr[i].values )
+ op.o_req_ndn.bv_val = (char *)pr[i].values[0];
+ }
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( slap_dontUseCopy_propnames != NULL ) {
+ struct berval bv;
+ ber_str2bv( &pr[i].name[1], 0, 1, &bv );
+ for ( j = 0; !BER_BVISNULL( &slap_dontUseCopy_propnames[ j ] ); j++ ) {
+ if ( bvmatch( &bv, &slap_dontUseCopy_propnames[ j ] ) ) {
+ dontUseCopy = 1;
+ break;
+ }
+ }
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+ }
+ }
+ if (!conn || !op.o_req_ndn.bv_val) return SASL_BADPARAM;
+
+ op.o_bd = select_backend( &op.o_req_ndn, 1 );
+
+ if ( !op.o_bd || !op.o_bd->be_modify ) return SASL_FAIL;
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( SLAP_SHADOW( op.o_bd ) && dontUseCopy ) {
+ dontUseCopy_bd = op.o_bd;
+ op.o_bd = frontendDB;
+ op.o_dontUseCopy = SLAP_CONTROL_CRITICAL;
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+
+ pr = sparams->utils->prop_get( prctx );
+ if (!pr) return SASL_BADPARAM;
+
+ for (i=0; pr[i].name; i++);
+ if (!i) return SASL_BADPARAM;
+
+ for (i=0; pr[i].name; i++) {
+ mod = (Modifications *)ch_malloc( sizeof(Modifications) );
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = 0;
+ ber_str2bv( pr[i].name, 0, 0, &mod->sml_type );
+ mod->sml_numvals = pr[i].nvalues;
+ mod->sml_values = (struct berval *)ch_malloc( (pr[i].nvalues + 1) *
+ sizeof(struct berval));
+ for (j=0; j<pr[i].nvalues; j++) {
+ ber_str2bv( pr[i].values[j], 0, 1, &mod->sml_values[j]);
+ }
+ BER_BVZERO( &mod->sml_values[j] );
+ mod->sml_nvalues = NULL;
+ mod->sml_desc = NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ *modtail = NULL;
+
+ rc = slap_mods_check( &op, modlist, &text, textbuf, textlen, NULL );
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = slap_mods_no_user_mod_check( &op, modlist,
+ &text, textbuf, textlen );
+
+ if ( rc == LDAP_SUCCESS ) {
+ if ( conn->c_sasl_bindop ) {
+ op.o_hdr = conn->c_sasl_bindop->o_hdr;
+ } else {
+ op.o_hdr = &oph;
+ memset( &oph, 0, sizeof(oph) );
+ operation_fake_init( conn, &op, ldap_pvt_thread_pool_context(), 0 );
+ }
+ op.o_tag = LDAP_REQ_MODIFY;
+ op.o_ndn = op.o_req_ndn;
+ op.o_callback = &cb;
+ slap_op_time( &op.o_time, &op.o_tincr );
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ op.o_req_dn = op.o_req_ndn;
+ op.orm_modlist = modlist;
+
+ for (;;) {
+ SlapReply rs = {REP_RESULT};
+ rc = op.o_bd->be_modify( &op, &rs );
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( dontUseCopy &&
+ rs.sr_err == LDAP_UNAVAILABLE &&
+ slap_dontUseCopy_ignore )
+ {
+ op.o_bd = dontUseCopy_bd;
+ op.o_dontUseCopy = SLAP_CONTROL_NONE;
+ dontUseCopy = 0;
+ continue;
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+ break;
+ }
+ }
+ }
+ slap_mods_free( modlist, 1 );
+ return rc != LDAP_SUCCESS ? SASL_FAIL : SASL_OK;
+}
+#endif /* SASL_VERSION_FULL >= 2.1.16 */
+
+static sasl_auxprop_plug_t slap_auxprop_plugin = {
+ 0, /* Features */
+ 0, /* spare */
+ NULL, /* glob_context */
+ NULL, /* auxprop_free */
+ slap_auxprop_lookup,
+ "slapd", /* name */
+#if SASL_VERSION_FULL >= 0x020110
+ slap_auxprop_store /* the declaration of this member changed
+ * in cyrus SASL from 2.1.15 to 2.1.16 */
+#else
+ NULL
+#endif
+};
+
+static int
+slap_auxprop_init(
+ const sasl_utils_t *utils,
+ int max_version,
+ int *out_version,
+ sasl_auxprop_plug_t **plug,
+ const char *plugname)
+{
+ if ( !out_version || !plug ) return SASL_BADPARAM;
+
+ if ( max_version < SASL_AUXPROP_PLUG_VERSION ) return SASL_BADVERS;
+
+ *out_version = SASL_AUXPROP_PLUG_VERSION;
+ *plug = &slap_auxprop_plugin;
+ return SASL_OK;
+}
+
+/* Convert a SASL authcid or authzid into a DN. Store the DN in an
+ * auxiliary property, so that we can refer to it in sasl_authorize
+ * without interfering with anything else. Also, the SASL username
+ * buffer is constrained to 256 characters, and our DNs could be
+ * much longer (SLAP_LDAPDN_MAXLEN, currently set to 8192)
+ */
+static int
+slap_sasl_canonicalize(
+ sasl_conn_t *sconn,
+ void *context,
+ const char *in,
+ unsigned inlen,
+ unsigned flags,
+ const char *user_realm,
+ char *out,
+ unsigned out_max,
+ unsigned *out_len)
+{
+ Connection *conn = (Connection *)context;
+ struct propctx *props = sasl_auxprop_getctx( sconn );
+ struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } };
+ struct berval dn;
+ int rc, which;
+ const char *names[2];
+ struct berval bvin;
+
+ *out_len = 0;
+
+ Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
+ conn ? (long) conn->c_connid : -1L,
+ (flags & SASL_CU_AUTHID) ? "authcid" : "authzid",
+ in ? in : "<empty>");
+
+ /* If name is too big, just truncate. We don't care, we're
+ * using DNs, not the usernames.
+ */
+ if ( inlen > out_max )
+ inlen = out_max-1;
+
+ /* This is a Simple Bind using SPASSWD. That means the in-directory
+ * userPassword of the Binding user already points at SASL, so it
+ * cannot be used to actually satisfy a password comparison. Just
+ * ignore it, some other mech will process it.
+ */
+ if ( !conn->c_sasl_bindop ||
+ conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) goto done;
+
+ /* See if we need to add request, can only do it once */
+ prop_getnames( props, slap_propnames, auxvals );
+ if ( !auxvals[0].name )
+ prop_request( props, slap_propnames );
+
+ if ( flags & SASL_CU_AUTHID )
+ which = SLAP_SASL_PROP_AUTHCLEN;
+ else
+ which = SLAP_SASL_PROP_AUTHZLEN;
+
+ /* Need to store the Connection for auxprop_lookup */
+ if ( !auxvals[SLAP_SASL_PROP_CONN].values ) {
+ names[0] = slap_propnames[SLAP_SASL_PROP_CONN];
+ names[1] = NULL;
+ prop_set( props, names[0], (char *)&conn, sizeof( conn ) );
+ }
+
+ /* Already been here? */
+ if ( auxvals[which].values )
+ goto done;
+
+ /* Normally we require an authzID to have a u: or dn: prefix.
+ * However, SASL frequently gives us an authzID that is just
+ * an exact copy of the authcID, without a prefix. We need to
+ * detect and allow this condition. If SASL calls canonicalize
+ * with SASL_CU_AUTHID|SASL_CU_AUTHZID this is a no-brainer.
+ * But if it's broken into two calls, we need to remember the
+ * authcID so that we can compare the authzID later. We store
+ * the authcID temporarily in conn->c_sasl_dn. We necessarily
+ * finish Canonicalizing before Authorizing, so there is no
+ * conflict with slap_sasl_authorize's use of this temp var.
+ *
+ * The SASL EXTERNAL mech is backwards from all the other mechs,
+ * it does authzID before the authcID. If we see that authzID
+ * has already been done, don't do anything special with authcID.
+ */
+ if ( flags == SASL_CU_AUTHID && !auxvals[SLAP_SASL_PROP_AUTHZ].values ) {
+ conn->c_sasl_dn.bv_val = (char *) in;
+ conn->c_sasl_dn.bv_len = 0;
+ } else if ( flags == SASL_CU_AUTHZID && conn->c_sasl_dn.bv_val ) {
+ rc = strcmp( in, conn->c_sasl_dn.bv_val );
+ conn->c_sasl_dn.bv_val = NULL;
+ /* They were equal, no work needed */
+ if ( !rc ) goto done;
+ }
+
+ bvin.bv_val = (char *)in;
+ bvin.bv_len = inlen;
+ rc = slap_sasl_getdn( conn, NULL, &bvin, (char *)user_realm, &dn,
+ (flags & SASL_CU_AUTHID) ? SLAP_GETDN_AUTHCID : SLAP_GETDN_AUTHZID );
+ if ( rc != LDAP_SUCCESS ) {
+ sasl_seterror( sconn, 0, ldap_err2string( rc ) );
+ return SASL_NOAUTHZ;
+ }
+
+ names[0] = slap_propnames[which];
+ names[1] = NULL;
+ prop_set( props, names[0], (char *)&dn.bv_len, sizeof( dn.bv_len ) );
+
+ which++;
+ names[0] = slap_propnames[which];
+ prop_set( props, names[0], dn.bv_val, dn.bv_len );
+
+ Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
+ conn ? (long) conn->c_connid : -1L, names[0]+1,
+ dn.bv_val ? dn.bv_val : "<EMPTY>" );
+
+ /* Not needed any more, SASL has copied it */
+ if ( conn && conn->c_sasl_bindop )
+ conn->c_sasl_bindop->o_tmpfree( dn.bv_val, conn->c_sasl_bindop->o_tmpmemctx );
+
+done:
+ AC_MEMCPY( out, in, inlen );
+ out[inlen] = '\0';
+
+ *out_len = inlen;
+
+ return SASL_OK;
+}
+
+static int
+slap_sasl_authorize(
+ sasl_conn_t *sconn,
+ void *context,
+ char *requested_user,
+ unsigned rlen,
+ char *auth_identity,
+ unsigned alen,
+ const char *def_realm,
+ unsigned urlen,
+ struct propctx *props)
+{
+ Connection *conn = (Connection *)context;
+ /* actually:
+ * (SLAP_SASL_PROP_COUNT - 1) because we skip "conn",
+ * + 1 for NULL termination?
+ */
+ struct propval auxvals[ SLAP_SASL_PROP_COUNT ] = { { 0 } };
+ struct berval authcDN, authzDN = BER_BVNULL;
+ int rc;
+
+ /* Simple Binds don't support proxy authorization, ignore it */
+ if ( !conn->c_sasl_bindop ||
+ conn->c_sasl_bindop->orb_method != LDAP_AUTH_SASL ) return SASL_OK;
+
+ Debug( LDAP_DEBUG_ARGS, "SASL proxy authorize [conn=%ld]: "
+ "authcid=\"%s\" authzid=\"%s\"\n",
+ conn ? (long) conn->c_connid : -1L, auth_identity, requested_user );
+ if ( conn->c_sasl_dn.bv_val ) {
+ BER_BVZERO( &conn->c_sasl_dn );
+ }
+
+ /* Skip SLAP_SASL_PROP_CONN */
+ prop_getnames( props, slap_propnames+1, auxvals );
+
+ /* Should not happen */
+ if ( !auxvals[0].values ) {
+ sasl_seterror( sconn, 0, "invalid authcid" );
+ return SASL_NOAUTHZ;
+ }
+
+ AC_MEMCPY( &authcDN.bv_len, auxvals[0].values[0], sizeof(authcDN.bv_len) );
+ authcDN.bv_val = auxvals[1].values ? (char *)auxvals[1].values[0] : NULL;
+ conn->c_sasl_dn = authcDN;
+
+ /* Nothing to do if no authzID was given */
+ if ( !auxvals[2].name || !auxvals[2].values ) {
+ goto ok;
+ }
+
+ AC_MEMCPY( &authzDN.bv_len, auxvals[2].values[0], sizeof(authzDN.bv_len) );
+ authzDN.bv_val = auxvals[3].values ? (char *)auxvals[3].values[0] : NULL;
+
+ rc = slap_sasl_authorized( conn->c_sasl_bindop, &authcDN, &authzDN );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "SASL Proxy Authorize [conn=%ld]: "
+ "proxy authorization disallowed (%d)\n",
+ conn ? (long) conn->c_connid : -1L, rc );
+
+ sasl_seterror( sconn, 0, "not authorized" );
+ return SASL_NOAUTHZ;
+ }
+
+ /* FIXME: we need yet another dup because slap_sasl_getdn()
+ * is using the bind operation slab */
+ ber_dupbv( &conn->c_sasl_authz_dn, &authzDN );
+
+ok:
+ if (conn->c_sasl_bindop) {
+ Debug( LDAP_DEBUG_STATS,
+ "%s BIND authcid=\"%s\" authzid=\"%s\"\n",
+ conn->c_sasl_bindop->o_log_prefix,
+ auth_identity, requested_user );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "SASL Authorize [conn=%ld]: "
+ " proxy authorization allowed authzDN=\"%s\"\n",
+ conn ? (long) conn->c_connid : -1L,
+ authzDN.bv_val ? authzDN.bv_val : "" );
+ return SASL_OK;
+}
+
+static int
+slap_sasl_err2ldap( int saslerr )
+{
+ int rc;
+
+ /* map SASL errors to LDAP resultCode returned by:
+ * sasl_server_new()
+ * SASL_OK, SASL_NOMEM
+ * sasl_server_step()
+ * SASL_OK, SASL_CONTINUE, SASL_TRANS, SASL_BADPARAM, SASL_BADPROT,
+ * ...
+ * sasl_server_start()
+ * + SASL_NOMECH
+ * sasl_setprop()
+ * SASL_OK, SASL_BADPARAM
+ */
+
+ switch (saslerr) {
+ case SASL_OK:
+ rc = LDAP_SUCCESS;
+ break;
+ case SASL_CONTINUE:
+ rc = LDAP_SASL_BIND_IN_PROGRESS;
+ break;
+ case SASL_FAIL:
+ case SASL_NOMEM:
+ rc = LDAP_OTHER;
+ break;
+ case SASL_NOMECH:
+ rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ break;
+ case SASL_BADAUTH:
+ case SASL_NOUSER:
+ case SASL_TRANS:
+ case SASL_EXPIRED:
+ rc = LDAP_INVALID_CREDENTIALS;
+ break;
+ case SASL_NOAUTHZ:
+ rc = LDAP_INSUFFICIENT_ACCESS;
+ break;
+ case SASL_TOOWEAK:
+ case SASL_ENCRYPT:
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ break;
+ case SASL_UNAVAIL:
+ case SASL_TRYAGAIN:
+ rc = LDAP_UNAVAILABLE;
+ break;
+ case SASL_DISABLED:
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+ default:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ return rc;
+}
+
+#ifdef SLAPD_SPASSWD
+
+static struct berval sasl_pwscheme = BER_BVC("{SASL}");
+
+static int chk_sasl(
+ const struct berval *sc,
+ const struct berval * passwd,
+ const struct berval * cred,
+ const char **text )
+{
+ unsigned int i;
+ int rtn;
+ void *ctx, *sconn = NULL;
+
+ for( i=0; i<cred->bv_len; i++) {
+ if(cred->bv_val[i] == '\0') {
+ return LUTIL_PASSWD_ERR; /* NUL character in password */
+ }
+ }
+
+ if( cred->bv_val[i] != '\0' ) {
+ return LUTIL_PASSWD_ERR; /* cred must behave like a string */
+ }
+
+ for( i=0; i<passwd->bv_len; i++) {
+ if(passwd->bv_val[i] == '\0') {
+ return LUTIL_PASSWD_ERR; /* NUL character in password */
+ }
+ }
+
+ if( passwd->bv_val[i] != '\0' ) {
+ return LUTIL_PASSWD_ERR; /* passwd must behave like a string */
+ }
+
+ rtn = LUTIL_PASSWD_ERR;
+
+ ctx = ldap_pvt_thread_pool_context();
+ ldap_pvt_thread_pool_getkey( ctx, (void *)slap_sasl_bind, &sconn, NULL );
+
+ if( sconn != NULL ) {
+ int sc;
+ sc = sasl_checkpass( sconn,
+ passwd->bv_val, passwd->bv_len,
+ cred->bv_val, cred->bv_len );
+ rtn = ( sc != SASL_OK ) ? LUTIL_PASSWD_ERR : LUTIL_PASSWD_OK;
+ }
+
+ return rtn;
+}
+#endif /* SLAPD_SPASSWD */
+
+#endif /* HAVE_CYRUS_SASL */
+
+typedef struct slapd_map_data {
+ struct berval base;
+ struct berval filter;
+ AttributeName attrs[2];
+ int scope;
+} slapd_map_data;
+
+static void *
+slapd_rw_config( const char *fname, int lineno, int argc, char **argv )
+{
+ slapd_map_data *ret = NULL;
+ LDAPURLDesc *lud = NULL;
+ char *uri;
+ AttributeDescription *ad = NULL;
+ int rc, flen = 0;
+ struct berval dn, ndn;
+
+ if ( argc != 1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "[%s:%d] slapd map needs URI\n",
+ fname, lineno );
+ return NULL;
+ }
+
+ uri = argv[0];
+ if ( strncasecmp( uri, "uri=", STRLENOF( "uri=" ) ) == 0 ) {
+ uri += STRLENOF( "uri=" );
+ }
+
+ if ( ldap_url_parse( uri, &lud ) != LDAP_URL_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "[%s:%d] illegal URI '%s'\n",
+ fname, lineno, uri );
+ return NULL;
+ }
+
+ if ( strcasecmp( lud->lud_scheme, "ldap" )) {
+ Debug( LDAP_DEBUG_ANY,
+ "[%s:%d] illegal URI scheme '%s'\n",
+ fname, lineno, lud->lud_scheme );
+ goto done;
+ }
+
+ if (( lud->lud_host && lud->lud_host[0] ) || lud->lud_exts
+ || !lud->lud_dn ) {
+ Debug( LDAP_DEBUG_ANY,
+ "[%s:%d] illegal URI '%s'\n",
+ fname, lineno, uri );
+ goto done;
+ }
+
+ if ( lud->lud_attrs ) {
+ if ( lud->lud_attrs[1] ) {
+ Debug( LDAP_DEBUG_ANY,
+ "[%s:%d] only one attribute allowed in URI\n",
+ fname, lineno );
+ goto done;
+ }
+ if ( strcasecmp( lud->lud_attrs[0], "dn" ) &&
+ strcasecmp( lud->lud_attrs[0], "entryDN" )) {
+ const char *text;
+ rc = slap_str2ad( lud->lud_attrs[0], &ad, &text );
+ if ( rc )
+ goto done;
+ }
+ }
+ ber_str2bv( lud->lud_dn, 0, 0, &dn );
+ if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ))
+ goto done;
+
+ if ( lud->lud_filter ) {
+ flen = strlen( lud->lud_filter ) + 1;
+ }
+ ret = ch_malloc( sizeof( slapd_map_data ) + flen );
+ ret->base = ndn;
+ if ( flen ) {
+ ret->filter.bv_val = (char *)(ret+1);
+ ret->filter.bv_len = flen - 1;
+ strcpy( ret->filter.bv_val, lud->lud_filter );
+ } else {
+ BER_BVZERO( &ret->filter );
+ }
+ ret->scope = lud->lud_scope;
+ if ( ad ) {
+ ret->attrs[0].an_name = ad->ad_cname;
+ } else {
+ BER_BVZERO( &ret->attrs[0].an_name );
+ }
+ ret->attrs[0].an_desc = ad;
+ BER_BVZERO( &ret->attrs[1].an_name );
+done:
+ ldap_free_urldesc( lud );
+ return ret;
+}
+
+struct slapd_rw_info {
+ slapd_map_data *si_data;
+ struct berval si_val;
+};
+
+static int
+slapd_rw_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ struct slapd_rw_info *si = op->o_callback->sc_private;
+
+ if ( si->si_data->attrs[0].an_desc ) {
+ Attribute *a;
+
+ a = attr_find( rs->sr_entry->e_attrs,
+ si->si_data->attrs[0].an_desc );
+ if ( a ) {
+ ber_dupbv( &si->si_val, a->a_vals );
+ }
+ } else {
+ ber_dupbv( &si->si_val, &rs->sr_entry->e_name );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+slapd_rw_apply( void *private, const char *filter, struct berval *val )
+{
+ slapd_map_data *sl = private;
+ slap_callback cb = { NULL };
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+ SlapReply rs = {REP_RESULT};
+ struct slapd_rw_info si;
+ char *ptr;
+ int rc;
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_req_dn = op->o_req_ndn = sl->base;
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+ if ( !op->o_bd ) {
+ return REWRITE_ERR;
+ }
+ si.si_data = sl;
+ BER_BVZERO( &si.si_val );
+ op->ors_scope = sl->scope;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ if ( sl->attrs[0].an_desc ) {
+ op->ors_attrs = sl->attrs;
+ } else {
+ op->ors_attrs = slap_anlist_no_attrs;
+ }
+ if ( filter ) {
+ rc = strlen( filter );
+ } else {
+ rc = 0;
+ }
+ rc += sl->filter.bv_len;
+ ptr = op->ors_filterstr.bv_val = op->o_tmpalloc( rc + 1, op->o_tmpmemctx );
+ if ( sl->filter.bv_len ) {
+ ptr = lutil_strcopy( ptr, sl->filter.bv_val );
+ } else {
+ *ptr = '\0';
+ }
+ if ( filter ) {
+ strcpy( ptr, filter );
+ }
+ op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+ if ( !op->ors_filter ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ return REWRITE_ERR;
+ }
+
+ op->ors_attrsonly = 0;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_do_not_cache = 1;
+
+ cb.sc_response = slapd_rw_cb;
+ cb.sc_private = &si;
+ op->o_callback = &cb;
+
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &si.si_val )) {
+ *val = si.si_val;
+ rc = REWRITE_SUCCESS;
+ } else {
+ if ( !BER_BVISNULL( &si.si_val )) {
+ ch_free( si.si_val.bv_val );
+ }
+ rc = REWRITE_ERR;
+ }
+ filter_free_x( op, op->ors_filter, 1 );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ return rc;
+}
+
+static int
+slapd_rw_destroy( void *private )
+{
+ slapd_map_data *md = private;
+
+ assert( private != NULL );
+
+ ch_free( md->base.bv_val );
+ ch_free( md );
+
+ return 0;
+}
+
+static const rewrite_mapper slapd_mapper = {
+ "slapd",
+ slapd_rw_config,
+ slapd_rw_apply,
+ slapd_rw_destroy
+};
+
+int slap_sasl_init( void )
+{
+#ifdef HAVE_CYRUS_SASL
+ int rc;
+ static sasl_callback_t server_callbacks[] = {
+ { SASL_CB_LOG, (slap_sasl_cb_ft)&slap_sasl_log, NULL },
+ { SASL_CB_GETOPT, (slap_sasl_cb_ft)&slap_sasl_getopt, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+ };
+#endif
+
+ rewrite_mapper_register( &slapd_mapper );
+
+#ifdef HAVE_CYRUS_SASL
+#ifdef HAVE_SASL_VERSION
+ /* stringify the version number, sasl.h doesn't do it for us */
+#define VSTR0(maj, min, pat) #maj "." #min "." #pat
+#define VSTR(maj, min, pat) VSTR0(maj, min, pat)
+#define SASL_VERSION_STRING VSTR(SASL_VERSION_MAJOR, SASL_VERSION_MINOR, \
+ SASL_VERSION_STEP)
+
+ sasl_version( NULL, &rc );
+ if ( ((rc >> 16) != ((SASL_VERSION_MAJOR << 8)|SASL_VERSION_MINOR)) ||
+ (rc & 0xffff) < SASL_VERSION_STEP)
+ {
+ char version[sizeof("xxx.xxx.xxxxx")];
+ sprintf( version, "%u.%d.%d", (unsigned)rc >> 24, (rc >> 16) & 0xff,
+ rc & 0xffff );
+ Debug( LDAP_DEBUG_ANY, "slap_sasl_init: SASL library version mismatch:"
+ " expected %s, got %s\n",
+ SASL_VERSION_STRING, version );
+ return -1;
+ }
+#endif
+
+ sasl_set_mutex(
+ ldap_pvt_sasl_mutex_new,
+ ldap_pvt_sasl_mutex_lock,
+ ldap_pvt_sasl_mutex_unlock,
+ ldap_pvt_sasl_mutex_dispose );
+
+ generic_filter.f_desc = slap_schema.si_ad_objectClass;
+
+ rc = sasl_auxprop_add_plugin( "slapd", slap_auxprop_init );
+ if( rc != SASL_OK ) {
+ Debug( LDAP_DEBUG_ANY, "slap_sasl_init: auxprop add plugin failed\n" );
+ return -1;
+ }
+
+ /* should provide callbacks for logging */
+ /* server name should be configurable */
+ rc = sasl_server_init( server_callbacks, "slapd" );
+
+ if( rc != SASL_OK ) {
+ Debug( LDAP_DEBUG_ANY, "slap_sasl_init: server init failed\n" );
+
+ return -1;
+ }
+
+#ifdef SLAPD_SPASSWD
+ lutil_passwd_add( &sasl_pwscheme, chk_sasl, NULL );
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, "slap_sasl_init: initialized!\n" );
+
+ /* default security properties */
+ memset( &sasl_secprops, '\0', sizeof(sasl_secprops) );
+ sasl_secprops.max_ssf = INT_MAX;
+ sasl_secprops.maxbufsize = 65536;
+ sasl_secprops.security_flags = SASL_SEC_NOPLAINTEXT|SASL_SEC_NOANONYMOUS;
+#endif
+
+ return 0;
+}
+
+int slap_sasl_destroy( void )
+{
+#ifdef HAVE_CYRUS_SASL
+ sasl_done();
+
+#ifdef SLAP_AUXPROP_DONTUSECOPY
+ if ( slap_dontUseCopy_propnames ) {
+ ber_bvarray_free( slap_dontUseCopy_propnames );
+ slap_dontUseCopy_propnames = NULL;
+ }
+#endif /* SLAP_AUXPROP_DONTUSECOPY */
+#endif
+ free( sasl_host );
+ sasl_host = NULL;
+ free( sasl_cbinding );
+ sasl_cbinding = NULL;
+
+ return 0;
+}
+
+static char *
+slap_sasl_peer2ipport( struct berval *peer )
+{
+ int isv6 = 0;
+ char *ipport, *p,
+ *addr = &peer->bv_val[ STRLENOF( "IP=" ) ];
+ ber_len_t plen = peer->bv_len - STRLENOF( "IP=" );
+
+ /* IPv6? */
+ if ( addr[0] == '[' ) {
+ isv6 = 1;
+ plen--;
+ }
+ ipport = ch_strdup( &addr[isv6] );
+
+ /* Convert IPv6/IPv4 addresses to address;port syntax. */
+ p = strrchr( ipport, ':' );
+ if ( p != NULL ) {
+ *p = ';';
+ if ( isv6 ) {
+ assert( p[-1] == ']' );
+ AC_MEMCPY( &p[-1], p, plen - ( p - ipport ) + 1 );
+ }
+
+ } else if ( isv6 ) {
+ /* trim ']' */
+ plen--;
+ assert( addr[plen] == ']' );
+ addr[plen] = '\0';
+ }
+
+ return ipport;
+}
+
+int slap_sasl_open( Connection *conn, int reopen )
+{
+ int sc = LDAP_SUCCESS;
+#ifdef HAVE_CYRUS_SASL
+ int cb;
+
+ sasl_conn_t *ctx = NULL;
+ sasl_callback_t *session_callbacks;
+ char *ipremoteport = NULL, *iplocalport = NULL;
+
+ assert( conn->c_sasl_authctx == NULL );
+
+ if ( !reopen ) {
+ assert( conn->c_sasl_extra == NULL );
+
+ session_callbacks =
+ SLAP_CALLOC( 5, sizeof(sasl_callback_t));
+ if( session_callbacks == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slap_sasl_open: SLAP_MALLOC failed" );
+ return -1;
+ }
+ conn->c_sasl_extra = session_callbacks;
+
+ session_callbacks[cb=0].id = SASL_CB_LOG;
+ session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_log;
+ session_callbacks[cb++].context = conn;
+
+ session_callbacks[cb].id = SASL_CB_PROXY_POLICY;
+ session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_authorize;
+ session_callbacks[cb++].context = conn;
+
+ session_callbacks[cb].id = SASL_CB_CANON_USER;
+ session_callbacks[cb].proc = (slap_sasl_cb_ft)&slap_sasl_canonicalize;
+ session_callbacks[cb++].context = conn;
+
+ session_callbacks[cb].id = SASL_CB_LIST_END;
+ session_callbacks[cb].proc = NULL;
+ session_callbacks[cb++].context = NULL;
+ } else {
+ session_callbacks = conn->c_sasl_extra;
+ }
+
+ conn->c_sasl_layers = 0;
+
+ /* create new SASL context */
+ if ( conn->c_sock_name.bv_len != 0 &&
+ strncmp( conn->c_sock_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 )
+ {
+ iplocalport = slap_sasl_peer2ipport( &conn->c_sock_name );
+ }
+
+ if ( conn->c_peer_name.bv_len != 0 &&
+ strncmp( conn->c_peer_name.bv_val, "IP=", STRLENOF( "IP=" ) ) == 0 )
+ {
+ ipremoteport = slap_sasl_peer2ipport( &conn->c_peer_name );
+ }
+
+ sc = sasl_server_new( "ldap", sasl_host, global_realm,
+ iplocalport, ipremoteport, session_callbacks, SASL_SUCCESS_DATA, &ctx );
+ if ( iplocalport != NULL ) {
+ ch_free( iplocalport );
+ }
+ if ( ipremoteport != NULL ) {
+ ch_free( ipremoteport );
+ }
+
+ if( sc != SASL_OK ) {
+ Debug( LDAP_DEBUG_ANY, "sasl_server_new failed: %d\n",
+ sc );
+
+ return -1;
+ }
+
+ conn->c_sasl_authctx = ctx;
+
+ if( sc == SASL_OK ) {
+ sc = sasl_setprop( ctx,
+ SASL_SEC_PROPS, &sasl_secprops );
+
+ if( sc != SASL_OK ) {
+ Debug( LDAP_DEBUG_ANY, "sasl_setprop failed: %d\n",
+ sc );
+
+ slap_sasl_close( conn );
+ return -1;
+ }
+ }
+
+ sc = slap_sasl_err2ldap( sc );
+
+#elif defined(SLAP_BUILTIN_SASL)
+ /* built-in SASL implementation */
+ SASL_CTX *ctx = (SASL_CTX *) SLAP_MALLOC(sizeof(SASL_CTX));
+ if( ctx == NULL ) return -1;
+
+ ctx->sc_external_ssf = 0;
+ BER_BVZERO( &ctx->sc_external_id );
+
+ conn->c_sasl_authctx = ctx;
+#endif
+
+ return sc;
+}
+
+int slap_sasl_external(
+ Connection *conn,
+ slap_ssf_t ssf,
+ struct berval *auth_id )
+{
+#ifdef HAVE_CYRUS_SASL
+ int sc;
+ sasl_conn_t *ctx = conn->c_sasl_authctx;
+ sasl_ssf_t sasl_ssf = ssf;
+
+ if ( ctx == NULL ) {
+ return LDAP_UNAVAILABLE;
+ }
+
+ sc = sasl_setprop( ctx, SASL_SSF_EXTERNAL, &sasl_ssf );
+
+ if ( sc != SASL_OK ) {
+ return LDAP_OTHER;
+ }
+
+ sc = sasl_setprop( ctx, SASL_AUTH_EXTERNAL,
+ auth_id ? auth_id->bv_val : NULL );
+
+ if ( sc != SASL_OK ) {
+ return LDAP_OTHER;
+ }
+#elif defined(SLAP_BUILTIN_SASL)
+ /* built-in SASL implementation */
+ SASL_CTX *ctx = conn->c_sasl_authctx;
+ if ( ctx == NULL ) return LDAP_UNAVAILABLE;
+
+ ctx->sc_external_ssf = ssf;
+ if( auth_id ) {
+ ctx->sc_external_id = *auth_id;
+ BER_BVZERO( auth_id );
+ } else {
+ BER_BVZERO( &ctx->sc_external_id );
+ }
+#endif
+
+ return LDAP_SUCCESS;
+}
+
+int slap_sasl_cbinding( Connection *conn, void *ssl )
+{
+#ifdef SASL_CHANNEL_BINDING
+ void *cb;
+ int i;
+
+ if ( sasl_cbinding == NULL )
+ return LDAP_SUCCESS;
+
+ i = ldap_pvt_sasl_cbinding_parse( sasl_cbinding );
+ if ( i < 0 )
+ return LDAP_SUCCESS;
+
+ cb = ldap_pvt_sasl_cbinding( ssl, i, 1 );
+ if ( cb != NULL ) {
+ sasl_setprop( conn->c_sasl_authctx, SASL_CHANNEL_BINDING, cb );
+ conn->c_sasl_cbind = cb;
+ }
+#endif
+ return LDAP_SUCCESS;
+}
+
+int slap_sasl_reset( Connection *conn )
+{
+ return LDAP_SUCCESS;
+}
+
+char ** slap_sasl_mechs( Connection *conn )
+{
+ char **mechs = NULL;
+
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *ctx = conn->c_sasl_authctx;
+
+ if( ctx == NULL ) ctx = conn->c_sasl_sockctx;
+
+ if( ctx != NULL ) {
+ int sc;
+ SASL_CONST char *mechstr;
+
+ sc = sasl_listmech( ctx,
+ NULL, NULL, ",", NULL,
+ &mechstr, NULL, NULL );
+
+ if( sc != SASL_OK ) {
+ Debug( LDAP_DEBUG_ANY, "slap_sasl_listmech failed: %d\n",
+ sc );
+
+ return NULL;
+ }
+
+ mechs = ldap_str2charray( mechstr, "," );
+ }
+#elif defined(SLAP_BUILTIN_SASL)
+ /* builtin SASL implementation */
+ SASL_CTX *ctx = conn->c_sasl_authctx;
+ if ( ctx != NULL && ctx->sc_external_id.bv_val ) {
+ /* should check ssf */
+ mechs = ldap_str2charray( "EXTERNAL", "," );
+ }
+#endif
+
+ return mechs;
+}
+
+int slap_sasl_close( Connection *conn )
+{
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *ctx = conn->c_sasl_authctx;
+
+ if( ctx != NULL ) {
+ sasl_dispose( &ctx );
+ }
+ if ( conn->c_sasl_sockctx &&
+ conn->c_sasl_authctx != conn->c_sasl_sockctx )
+ {
+ ctx = conn->c_sasl_sockctx;
+ sasl_dispose( &ctx );
+ }
+
+ conn->c_sasl_authctx = NULL;
+ conn->c_sasl_sockctx = NULL;
+ conn->c_sasl_done = 0;
+
+ free( conn->c_sasl_extra );
+ conn->c_sasl_extra = NULL;
+
+ free( conn->c_sasl_cbind );
+ conn->c_sasl_cbind = NULL;
+
+#elif defined(SLAP_BUILTIN_SASL)
+ SASL_CTX *ctx = conn->c_sasl_authctx;
+ if( ctx ) {
+ if( ctx->sc_external_id.bv_val ) {
+ free( ctx->sc_external_id.bv_val );
+ BER_BVZERO( &ctx->sc_external_id );
+ }
+ free( ctx );
+ conn->c_sasl_authctx = NULL;
+ }
+#endif
+
+ return LDAP_SUCCESS;
+}
+
+int slap_sasl_bind( Operation *op, SlapReply *rs )
+{
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *ctx = op->o_conn->c_sasl_authctx;
+ struct berval response;
+ unsigned reslen = 0;
+ int sc;
+
+ Debug(LDAP_DEBUG_ARGS,
+ "==> sasl_bind: dn=\"%s\" mech=%s datalen=%ld\n",
+ op->o_req_dn.bv_len ? op->o_req_dn.bv_val : "",
+ op->o_conn->c_sasl_bind_in_progress ? "<continuing>" :
+ op->o_conn->c_sasl_bind_mech.bv_val,
+ op->orb_cred.bv_len );
+
+ if( ctx == NULL ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+ "SASL unavailable on this session" );
+ return rs->sr_err;
+ }
+
+#define START( ctx, mech, cred, clen, resp, rlen, err ) \
+ sasl_server_start( ctx, mech, cred, clen, resp, rlen )
+#define STEP( ctx, cred, clen, resp, rlen, err ) \
+ sasl_server_step( ctx, cred, clen, resp, rlen )
+
+ if ( !op->o_conn->c_sasl_bind_in_progress ) {
+ /* If we already authenticated once, must use a new context */
+ if ( op->o_conn->c_sasl_done ) {
+ sasl_ssf_t ssf = 0;
+ sasl_ssf_t *ssfp = NULL;
+ const char *authid = NULL;
+
+ sasl_getprop( ctx, SASL_SSF_EXTERNAL, (void *)&ssfp );
+ if ( ssfp ) ssf = *ssfp;
+
+ sasl_getprop( ctx, SASL_AUTH_EXTERNAL, (void *)&authid );
+ if ( authid ) authid = ch_strdup( authid );
+
+ if ( ctx != op->o_conn->c_sasl_sockctx ) {
+ sasl_dispose( &ctx );
+ }
+ op->o_conn->c_sasl_authctx = NULL;
+
+ slap_sasl_open( op->o_conn, 1 );
+ ctx = op->o_conn->c_sasl_authctx;
+ sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf );
+ if ( authid ) {
+ sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid );
+ ch_free( (char *)authid );
+ }
+ }
+ sc = START( ctx,
+ op->o_conn->c_sasl_bind_mech.bv_val,
+ op->orb_cred.bv_val, op->orb_cred.bv_len,
+ (SASL_CONST char **)&response.bv_val, &reslen, &rs->sr_text );
+
+ } else {
+ sc = STEP( ctx,
+ op->orb_cred.bv_val, op->orb_cred.bv_len,
+ (SASL_CONST char **)&response.bv_val, &reslen, &rs->sr_text );
+ }
+
+ response.bv_len = reslen;
+
+ if ( sc == SASL_OK ) {
+ sasl_ssf_t *ssf = NULL;
+
+ ber_dupbv_x( &op->orb_edn, &op->o_conn->c_sasl_dn, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_conn->c_sasl_dn );
+ op->o_conn->c_sasl_done = 1;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ (void) sasl_getprop( ctx, SASL_SSF, (void *)&ssf );
+ op->orb_ssf = ssf ? *ssf : 0;
+
+ ctx = NULL;
+ if( op->orb_ssf ) {
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ op->o_conn->c_sasl_layers++;
+
+ /* If there's an old layer, set sockctx to NULL to
+ * tell connection_read() to wait for us to finish.
+ * Otherwise there is a race condition: we have to
+ * send the Bind response using the old security
+ * context and then remove it before reading any
+ * new messages.
+ */
+ if ( op->o_conn->c_sasl_sockctx ) {
+ ctx = op->o_conn->c_sasl_sockctx;
+ op->o_conn->c_sasl_sockctx = NULL;
+ } else {
+ op->o_conn->c_sasl_sockctx = op->o_conn->c_sasl_authctx;
+ }
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ }
+
+ /* Must send response using old security layer */
+ rs->sr_sasldata = (response.bv_len ? &response : NULL);
+ send_ldap_sasl( op, rs );
+
+ /* Now dispose of the old security layer.
+ */
+ if ( ctx ) {
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ ldap_pvt_sasl_remove( op->o_conn->c_sb );
+ op->o_conn->c_sasl_sockctx = op->o_conn->c_sasl_authctx;
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ sasl_dispose( &ctx );
+ }
+ } else if ( sc == SASL_CONTINUE ) {
+ rs->sr_err = LDAP_SASL_BIND_IN_PROGRESS,
+ rs->sr_text = sasl_errdetail( ctx );
+ rs->sr_sasldata = &response;
+ send_ldap_sasl( op, rs );
+
+ } else {
+ BER_BVZERO( &op->o_conn->c_sasl_dn );
+ rs->sr_text = sasl_errdetail( ctx );
+ rs->sr_err = slap_sasl_err2ldap( sc ),
+ send_ldap_result( op, rs );
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "<== slap_sasl_bind: rc=%d\n", rs->sr_err );
+
+#elif defined(SLAP_BUILTIN_SASL)
+ /* built-in SASL implementation */
+ SASL_CTX *ctx = op->o_conn->c_sasl_authctx;
+
+ if ( ctx == NULL ) {
+ send_ldap_error( op, rs, LDAP_OTHER,
+ "Internal SASL Error" );
+
+ } else if ( bvmatch( &ext_bv, &op->o_conn->c_sasl_bind_mech ) ) {
+ /* EXTERNAL */
+
+ if( op->orb_cred.bv_len ) {
+ rs->sr_text = "proxy authorization not supported";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ send_ldap_result( op, rs );
+
+ } else {
+ op->orb_edn = ctx->sc_external_id;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_sasldata = NULL;
+ send_ldap_sasl( op, rs );
+ }
+
+ } else {
+ send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED,
+ "requested SASL mechanism not supported" );
+ }
+#else
+ send_ldap_error( op, rs, LDAP_AUTH_METHOD_NOT_SUPPORTED,
+ "SASL not supported" );
+#endif
+
+ return rs->sr_err;
+}
+
+char* slap_sasl_secprops( const char *in )
+{
+#ifdef HAVE_CYRUS_SASL
+ int rc = ldap_pvt_sasl_secprops( in, &sasl_secprops );
+
+ return rc == LDAP_SUCCESS ? NULL : "Invalid security properties";
+#else
+ return "SASL not supported";
+#endif
+}
+
+void slap_sasl_secprops_unparse( struct berval *bv )
+{
+#ifdef HAVE_CYRUS_SASL
+ ldap_pvt_sasl_secprops_unparse( &sasl_secprops, bv );
+#endif
+}
+
+#ifdef HAVE_CYRUS_SASL
+int
+slap_sasl_setpass( Operation *op, SlapReply *rs )
+{
+ struct berval id = BER_BVNULL; /* needs to come from connection */
+ struct berval new = BER_BVNULL;
+ struct berval old = BER_BVNULL;
+
+ assert( ber_bvcmp( &slap_EXOP_MODIFY_PASSWD, &op->ore_reqoid ) == 0 );
+
+ rs->sr_err = sasl_getprop( op->o_conn->c_sasl_authctx, SASL_USERNAME,
+ (SASL_CONST void **)(char *)&id.bv_val );
+
+ if( rs->sr_err != SASL_OK ) {
+ rs->sr_text = "unable to retrieve SASL username";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "==> slap_sasl_setpass: \"%s\"\n",
+ id.bv_val ? id.bv_val : "" );
+
+ rs->sr_err = slap_passwd_parse( op->ore_reqdata,
+ NULL, &old, &new, &rs->sr_text );
+
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if( new.bv_len == 0 ) {
+ slap_passwd_generate(&new);
+
+ if( new.bv_len == 0 ) {
+ rs->sr_text = "password generation failed.";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ rs->sr_rspdata = slap_passwd_return( &new );
+ }
+
+ rs->sr_err = sasl_setpass( op->o_conn->c_sasl_authctx, id.bv_val,
+ new.bv_val, new.bv_len, old.bv_val, old.bv_len, 0 );
+ if( rs->sr_err != SASL_OK ) {
+ rs->sr_text = sasl_errdetail( op->o_conn->c_sasl_authctx );
+ }
+ switch(rs->sr_err) {
+ case SASL_OK:
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ case SASL_NOCHANGE:
+ case SASL_NOMECH:
+ case SASL_DISABLED:
+ case SASL_PWLOCK:
+ case SASL_FAIL:
+ case SASL_BADPARAM:
+ default:
+ rs->sr_err = LDAP_OTHER;
+ }
+
+done:
+ return rs->sr_err;
+}
+#endif /* HAVE_CYRUS_SASL */
+
+/* Take any sort of identity string and return a DN with the "dn:" prefix. The
+ * string returned in *dn is in its own allocated memory, and must be free'd
+ * by the calling process. -Mark Adamson, Carnegie Mellon
+ *
+ * The "dn:" prefix is no longer used anywhere inside slapd. It is only used
+ * on strings passed in directly from SASL. -Howard Chu, Symas Corp.
+ */
+
+#define SET_NONE 0
+#define SET_DN 1
+#define SET_U 2
+
+int slap_sasl_getdn( Connection *conn, Operation *op, struct berval *id,
+ char *user_realm, struct berval *dn, int flags )
+{
+ int rc, is_dn = SET_NONE, do_norm = 1;
+ struct berval dn2, *mech;
+
+ assert( conn != NULL );
+ assert( id != NULL );
+
+ Debug( LDAP_DEBUG_ARGS, "slap_sasl_getdn: conn %lu id=%s [len=%lu]\n",
+ conn->c_connid,
+ BER_BVISNULL( id ) ? "NULL" : ( BER_BVISEMPTY( id ) ? "<empty>" : id->bv_val ),
+ BER_BVISNULL( id ) ? 0 : ( BER_BVISEMPTY( id ) ? 0 :
+ (unsigned long) id->bv_len ) );
+
+ if ( !op ) {
+ op = conn->c_sasl_bindop;
+ }
+ assert( op != NULL );
+
+ BER_BVZERO( dn );
+
+ if ( !BER_BVISNULL( id ) ) {
+ /* Blatantly anonymous ID */
+ static struct berval bv_anonymous = BER_BVC( "anonymous" );
+
+ if ( ber_bvstrcasecmp( id, &bv_anonymous ) == 0 ) {
+ return( LDAP_SUCCESS );
+ }
+
+ } else {
+ /* FIXME: if empty, should we stop? */
+ BER_BVSTR( id, "" );
+ }
+
+ if ( !BER_BVISEMPTY( &conn->c_sasl_bind_mech ) ) {
+ mech = &conn->c_sasl_bind_mech;
+ } else {
+ mech = &conn->c_authmech;
+ }
+
+ /* An authcID needs to be converted to authzID form. Set the
+ * values directly into *dn; they will be normalized later. (and
+ * normalizing always makes a new copy.) An ID from a TLS certificate
+ * is already normalized, so copy it and skip normalization.
+ */
+ if( flags & SLAP_GETDN_AUTHCID ) {
+ if( bvmatch( mech, &ext_bv )) {
+ /* EXTERNAL DNs are already normalized */
+ assert( !BER_BVISNULL( id ) );
+
+ do_norm = 0;
+ is_dn = SET_DN;
+ ber_dupbv_x( dn, id, op->o_tmpmemctx );
+
+ } else {
+ /* convert to u:<username> form */
+ is_dn = SET_U;
+ *dn = *id;
+ }
+ }
+
+ if( is_dn == SET_NONE ) {
+ if( !strncasecmp( id->bv_val, "u:", STRLENOF( "u:" ) ) ) {
+ is_dn = SET_U;
+ dn->bv_val = id->bv_val + STRLENOF( "u:" );
+ dn->bv_len = id->bv_len - STRLENOF( "u:" );
+
+ } else if ( !strncasecmp( id->bv_val, "dn:", STRLENOF( "dn:" ) ) ) {
+ is_dn = SET_DN;
+ dn->bv_val = id->bv_val + STRLENOF( "dn:" );
+ dn->bv_len = id->bv_len - STRLENOF( "dn:" );
+ }
+ }
+
+ /* No other possibilities from here */
+ if( is_dn == SET_NONE ) {
+ BER_BVZERO( dn );
+ return( LDAP_INAPPROPRIATE_AUTH );
+ }
+
+ /* Username strings */
+ if( is_dn == SET_U ) {
+ /* ITS#3419: values may need escape */
+ LDAPRDN DN[ 5 ];
+ LDAPAVA *RDNs[ 4 ][ 2 ];
+ LDAPAVA AVAs[ 4 ];
+ int irdn;
+
+ irdn = 0;
+ DN[ irdn ] = RDNs[ irdn ];
+ RDNs[ irdn ][ 0 ] = &AVAs[ irdn ];
+ AVAs[ irdn ].la_attr = slap_schema.si_ad_uid->ad_cname;
+ AVAs[ irdn ].la_value = *dn;
+ AVAs[ irdn ].la_flags = LDAP_AVA_NULL;
+ AVAs[ irdn ].la_private = NULL;
+ RDNs[ irdn ][ 1 ] = NULL;
+
+ if ( user_realm && *user_realm ) {
+ irdn++;
+ DN[ irdn ] = RDNs[ irdn ];
+ RDNs[ irdn ][ 0 ] = &AVAs[ irdn ];
+ AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname;
+ ber_str2bv( user_realm, 0, 0, &AVAs[ irdn ].la_value );
+ AVAs[ irdn ].la_flags = LDAP_AVA_NULL;
+ AVAs[ irdn ].la_private = NULL;
+ RDNs[ irdn ][ 1 ] = NULL;
+ }
+
+ if ( !BER_BVISNULL( mech ) ) {
+ irdn++;
+ DN[ irdn ] = RDNs[ irdn ];
+ RDNs[ irdn ][ 0 ] = &AVAs[ irdn ];
+ AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname;
+ AVAs[ irdn ].la_value = *mech;
+ AVAs[ irdn ].la_flags = LDAP_AVA_NULL;
+ AVAs[ irdn ].la_private = NULL;
+ RDNs[ irdn ][ 1 ] = NULL;
+ }
+
+ irdn++;
+ DN[ irdn ] = RDNs[ irdn ];
+ RDNs[ irdn ][ 0 ] = &AVAs[ irdn ];
+ AVAs[ irdn ].la_attr = slap_schema.si_ad_cn->ad_cname;
+ BER_BVSTR( &AVAs[ irdn ].la_value, "auth" );
+ AVAs[ irdn ].la_flags = LDAP_AVA_NULL;
+ AVAs[ irdn ].la_private = NULL;
+ RDNs[ irdn ][ 1 ] = NULL;
+
+ irdn++;
+ DN[ irdn ] = NULL;
+
+ rc = ldap_dn2bv_x( DN, dn, LDAP_DN_FORMAT_LDAPV3,
+ op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ BER_BVZERO( dn );
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_sasl_getdn: u:id converted to %s\n",
+ dn->bv_val );
+
+ } else {
+
+ /* Dup the DN in any case, so we don't risk
+ * leaks or dangling pointers later,
+ * and the DN value is '\0' terminated */
+ ber_dupbv_x( &dn2, dn, op->o_tmpmemctx );
+ dn->bv_val = dn2.bv_val;
+ }
+
+ /* All strings are in DN form now. Normalize if needed. */
+ if ( do_norm ) {
+ rc = dnNormalize( 0, NULL, NULL, dn, &dn2, op->o_tmpmemctx );
+
+ /* User DNs were constructed above and must be freed now */
+ slap_sl_free( dn->bv_val, op->o_tmpmemctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ BER_BVZERO( dn );
+ return rc;
+ }
+ *dn = dn2;
+ }
+
+ /* Run thru regexp */
+ slap_sasl2dn( op, dn, &dn2, flags );
+ if( !BER_BVISNULL( &dn2 ) ) {
+ slap_sl_free( dn->bv_val, op->o_tmpmemctx );
+ *dn = dn2;
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_sasl_getdn: dn:id converted to %s\n",
+ dn->bv_val );
+ }
+
+ return( LDAP_SUCCESS );
+}
diff --git a/servers/slapd/saslauthz.c b/servers/slapd/saslauthz.c
new file mode 100644
index 0000000..181f40f
--- /dev/null
+++ b/servers/slapd/saslauthz.c
@@ -0,0 +1,2194 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2000 Mark Adamson, Carnegie Mellon.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/stdlib.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+
+#include "lutil.h"
+#include "slap-config.h"
+
+#define SASLREGEX_REPLACE 10
+
+#define LDAP_X_SCOPE_EXACT ((ber_int_t) 0x0010)
+#define LDAP_X_SCOPE_REGEX ((ber_int_t) 0x0020)
+#define LDAP_X_SCOPE_CHILDREN ((ber_int_t) 0x0030)
+#define LDAP_X_SCOPE_SUBTREE ((ber_int_t) 0x0040)
+#define LDAP_X_SCOPE_ONELEVEL ((ber_int_t) 0x0050)
+#define LDAP_X_SCOPE_GROUP ((ber_int_t) 0x0060)
+#define LDAP_X_SCOPE_USERS ((ber_int_t) 0x0070)
+
+/*
+ * IDs in DNauthzid form can now have a type specifier, that
+ * influences how they are used in related operations.
+ *
+ * syntax: dn[.{exact|regex}]:<val>
+ *
+ * dn.exact: the value must pass normalization and is used
+ * in exact DN match.
+ * dn.regex: the value is treated as a regular expression
+ * in matching DN values in authz{To|From}
+ * attributes.
+ * dn: for backwards compatibility reasons, the value
+ * is treated as a regular expression, and thus
+ * it is not normalized nor validated; it is used
+ * in exact or regex comparisons based on the
+ * context.
+ *
+ * IDs in DNauthzid form can now have a type specifier, that
+ * influences how they are used in related operations.
+ *
+ * syntax: u[.mech[/realm]]:<val>
+ *
+ * where mech is a SIMPLE, AUTHZ, or a SASL mechanism name
+ * and realm is mechanism specific realm (separate to those
+ * which are representable as part of the principal).
+ */
+
+typedef struct sasl_regexp {
+ char *sr_match; /* regexp match pattern */
+ char *sr_replace; /* regexp replace pattern */
+} SaslRegexp_t;
+
+static int nSaslRegexp = 0;
+static SaslRegexp_t *SaslRegexp = NULL;
+
+#include "rewrite.h"
+struct rewrite_info *sasl_rwinfo = NULL;
+#define AUTHID_CONTEXT "authid"
+static BerVarray authz_rewrites = NULL;
+
+/* What SASL proxy authorization policies are allowed? */
+#define SASL_AUTHZ_NONE 0x00
+#define SASL_AUTHZ_FROM 0x01
+#define SASL_AUTHZ_TO 0x02
+#define SASL_AUTHZ_AND 0x10
+
+static const char *policy_txt[] = {
+ "none", "from", "to", "any"
+};
+
+static int authz_policy = SASL_AUTHZ_NONE;
+
+static int
+slap_sasl_match( Operation *opx, struct berval *rule,
+ struct berval *assertDN, struct berval *authc );
+
+int slap_sasl_setpolicy( const char *arg )
+{
+ int rc = LDAP_SUCCESS;
+
+ if ( strcasecmp( arg, "none" ) == 0 ) {
+ authz_policy = SASL_AUTHZ_NONE;
+ } else if ( strcasecmp( arg, "from" ) == 0 ) {
+ authz_policy = SASL_AUTHZ_FROM;
+ } else if ( strcasecmp( arg, "to" ) == 0 ) {
+ authz_policy = SASL_AUTHZ_TO;
+ } else if ( strcasecmp( arg, "both" ) == 0 || strcasecmp( arg, "any" ) == 0 ) {
+ authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO;
+ } else if ( strcasecmp( arg, "all" ) == 0 ) {
+ authz_policy = SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND;
+ } else {
+ rc = LDAP_OTHER;
+ }
+ return rc;
+}
+
+const char * slap_sasl_getpolicy()
+{
+ if ( authz_policy == (SASL_AUTHZ_FROM | SASL_AUTHZ_TO | SASL_AUTHZ_AND) )
+ return "all";
+ else
+ return policy_txt[authz_policy];
+}
+
+int slap_parse_user( struct berval *id, struct berval *user,
+ struct berval *realm, struct berval *mech )
+{
+ char u;
+
+ assert( id != NULL );
+ assert( !BER_BVISNULL( id ) );
+ assert( user != NULL );
+ assert( realm != NULL );
+ assert( mech != NULL );
+
+ u = id->bv_val[ 0 ];
+
+ if ( u != 'u' && u != 'U' ) {
+ /* called with something other than u: */
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* uauthzid form:
+ * u[.mech[/realm]]:user
+ */
+
+ user->bv_val = ber_bvchr( id, ':' );
+ if ( BER_BVISNULL( user ) ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+ user->bv_val[ 0 ] = '\0';
+ user->bv_val++;
+ user->bv_len = id->bv_len - ( user->bv_val - id->bv_val );
+
+ if ( id->bv_val[1] == '.' ) {
+ id->bv_val[1] = '\0';
+ mech->bv_val = id->bv_val + 2;
+ mech->bv_len = user->bv_val - mech->bv_val - 1;
+
+ realm->bv_val = ber_bvchr( mech, '/' );
+
+ if ( !BER_BVISNULL( realm ) ) {
+ realm->bv_val[ 0 ] = '\0';
+ realm->bv_val++;
+ mech->bv_len = realm->bv_val - mech->bv_val - 1;
+ realm->bv_len = user->bv_val - realm->bv_val - 1;
+ }
+
+ } else {
+ BER_BVZERO( mech );
+ BER_BVZERO( realm );
+ }
+
+ if ( id->bv_val[ 1 ] != '\0' ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( mech ) ) {
+ if ( mech->bv_val != id->bv_val + 2 )
+ return LDAP_PROTOCOL_ERROR;
+
+ AC_MEMCPY( mech->bv_val - 2, mech->bv_val, mech->bv_len + 1 );
+ mech->bv_val -= 2;
+ }
+
+ if ( !BER_BVISNULL( realm ) ) {
+ if ( realm->bv_val < id->bv_val + 2 )
+ return LDAP_PROTOCOL_ERROR;
+
+ AC_MEMCPY( realm->bv_val - 2, realm->bv_val, realm->bv_len + 1 );
+ realm->bv_val -= 2;
+ }
+
+ /* leave "u:" before user */
+ user->bv_val -= 2;
+ user->bv_len += 2;
+ user->bv_val[ 0 ] = u;
+ user->bv_val[ 1 ] = ':';
+
+ return LDAP_SUCCESS;
+}
+
+int
+authzValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ struct berval bv;
+ int rc = LDAP_INVALID_SYNTAX;
+ LDAPURLDesc *ludp = NULL;
+ int scope = -1;
+
+ /*
+ * 1) <DN>
+ * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
+ * 3) dn.regex:<pattern>
+ * 4) u[.mech[/realm]]:<ID>
+ * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
+ * 6) <URL>
+ */
+
+ assert( in != NULL );
+ assert( !BER_BVISNULL( in ) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "authzValidate: parsing %s\n", in->bv_val );
+
+ /*
+ * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
+ * 3) dn.regex:<pattern>
+ *
+ * <DN> must pass DN normalization
+ */
+ if ( !strncasecmp( in->bv_val, "dn", STRLENOF( "dn" ) ) ) {
+ bv.bv_val = in->bv_val + STRLENOF( "dn" );
+
+ if ( bv.bv_val[ 0 ] == '.' ) {
+ bv.bv_val++;
+
+ if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
+ bv.bv_val += STRLENOF( "exact:" );
+ scope = LDAP_X_SCOPE_EXACT;
+
+ } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
+ bv.bv_val += STRLENOF( "regex:" );
+ scope = LDAP_X_SCOPE_REGEX;
+
+ } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
+ bv.bv_val += STRLENOF( "children:" );
+ scope = LDAP_X_SCOPE_CHILDREN;
+
+ } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
+ bv.bv_val += STRLENOF( "subtree:" );
+ scope = LDAP_X_SCOPE_SUBTREE;
+
+ } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
+ bv.bv_val += STRLENOF( "onelevel:" );
+ scope = LDAP_X_SCOPE_ONELEVEL;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else {
+ if ( bv.bv_val[ 0 ] != ':' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ scope = LDAP_X_SCOPE_EXACT;
+ bv.bv_val++;
+ }
+
+ bv.bv_val += strspn( bv.bv_val, " " );
+ /* jump here in case no type specification was present
+ * and uri was not an URI... HEADS-UP: assuming EXACT */
+is_dn: bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
+
+ /* a single '*' means any DN without using regexes */
+ if ( ber_bvccmp( &bv, '*' ) ) {
+ /* LDAP_X_SCOPE_USERS */
+ return LDAP_SUCCESS;
+ }
+
+ switch ( scope ) {
+ case LDAP_X_SCOPE_EXACT:
+ case LDAP_X_SCOPE_CHILDREN:
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_ONELEVEL:
+ return dnValidate( NULL, &bv );
+
+ case LDAP_X_SCOPE_REGEX:
+ return LDAP_SUCCESS;
+ }
+
+ return rc;
+
+ /*
+ * 4) u[.mech[/realm]]:<ID>
+ */
+ } else if ( ( in->bv_val[ 0 ] == 'u' || in->bv_val[ 0 ] == 'U' )
+ && ( in->bv_val[ 1 ] == ':'
+ || in->bv_val[ 1 ] == '/'
+ || in->bv_val[ 1 ] == '.' ) )
+ {
+ char buf[ SLAP_LDAPDN_MAXLEN ];
+ struct berval id,
+ user = BER_BVNULL,
+ realm = BER_BVNULL,
+ mech = BER_BVNULL;
+
+ if ( sizeof( buf ) <= in->bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ id.bv_len = in->bv_len;
+ id.bv_val = buf;
+ strncpy( buf, in->bv_val, sizeof( buf ) );
+
+ rc = slap_parse_user( &id, &user, &realm, &mech );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return rc;
+
+ /*
+ * 5) group[/groupClass[/memberAttr]]:<DN>
+ *
+ * <groupClass> defaults to "groupOfNames"
+ * <memberAttr> defaults to "member"
+ *
+ * <DN> must pass DN normalization
+ */
+ } else if ( strncasecmp( in->bv_val, "group", STRLENOF( "group" ) ) == 0 )
+ {
+ struct berval group_dn = BER_BVNULL,
+ group_oc = BER_BVNULL,
+ member_at = BER_BVNULL;
+
+ bv.bv_val = in->bv_val + STRLENOF( "group" );
+ bv.bv_len = in->bv_len - STRLENOF( "group" );
+ group_dn.bv_val = ber_bvchr( &bv, ':' );
+ if ( group_dn.bv_val == NULL ) {
+ /* last chance: assume it's a(n exact) DN ... */
+ bv.bv_val = in->bv_val;
+ scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+ }
+
+ /*
+ * FIXME: we assume that "member" and "groupOfNames"
+ * are present in schema...
+ */
+ if ( bv.bv_val[ 0 ] == '/' ) {
+ group_oc.bv_val = &bv.bv_val[ 1 ];
+ group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
+
+ member_at.bv_val = ber_bvchr( &group_oc, '/' );
+ if ( member_at.bv_val ) {
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
+ member_at.bv_val++;
+ member_at.bv_len = group_dn.bv_val - member_at.bv_val;
+ rc = slap_bv2ad( &member_at, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ if ( oc_bvfind( &group_oc ) == NULL ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ group_dn.bv_val++;
+ group_dn.bv_len = in->bv_len - ( group_dn.bv_val - in->bv_val );
+
+ rc = dnValidate( NULL, &group_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ return rc;
+ }
+
+ /*
+ * ldap:///<base>??<scope>?<filter>
+ * <scope> ::= {base|one|subtree}
+ *
+ * <scope> defaults to "base"
+ * <base> must pass DN normalization
+ * <filter> must pass str2filter()
+ */
+ rc = ldap_url_parse( in->bv_val, &ludp );
+ switch ( rc ) {
+ case LDAP_URL_SUCCESS:
+ /* FIXME: the check is pedantic, but I think it's necessary,
+ * because people tend to use things like ldaps:// which
+ * gives the idea SSL is being used. Maybe we could
+ * accept ldapi:// as well, but the point is that we use
+ * an URL as an easy means to define bits of a search with
+ * little parsing.
+ */
+ if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
+ /*
+ * must be ldap:///
+ */
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ break;
+
+ case LDAP_URL_ERR_BADSCHEME:
+ /*
+ * last chance: assume it's a(n exact) DN ...
+ *
+ * NOTE: must pass DN normalization
+ */
+ ldap_free_urldesc( ludp );
+ bv.bv_val = in->bv_val;
+ scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+
+ default:
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ if ( ( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs || ludp->lud_exts )
+ {
+ /* host part must be empty */
+ /* attrs and extensions parts must be empty */
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /* Grab the filter */
+ if ( ludp->lud_filter ) {
+ Filter *f = str2filter( ludp->lud_filter );
+ if ( f == NULL ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ filter_free( f );
+ }
+
+ /* Grab the searchbase */
+ if ( ludp->lud_dn != NULL ) {
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ rc = dnValidate( NULL, &bv );
+ } else {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+done:
+ ldap_free_urldesc( ludp );
+ return( rc );
+}
+
+static int
+authzPrettyNormal(
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx,
+ int normalize )
+{
+ struct berval bv;
+ int rc = LDAP_INVALID_SYNTAX;
+ LDAPURLDesc *ludp = NULL;
+ char *lud_dn = NULL,
+ *lud_filter = NULL;
+ int scope = -1;
+
+ /*
+ * 1) <DN>
+ * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
+ * 3) dn.regex:<pattern>
+ * 4) u[.mech[/realm]]:<ID>
+ * 5) group[/<groupClass>[/<memberAttr>]]:<DN>
+ * 6) <URL>
+ */
+
+ assert( val != NULL );
+ assert( !BER_BVISNULL( val ) );
+ BER_BVZERO( normalized );
+
+ /*
+ * 2) dn[.{exact|children|subtree|onelevel}]:{*|<DN>}
+ * 3) dn.regex:<pattern>
+ *
+ * <DN> must pass DN normalization
+ */
+ if ( !strncasecmp( val->bv_val, "dn", STRLENOF( "dn" ) ) ) {
+ struct berval out = BER_BVNULL,
+ prefix = BER_BVNULL;
+ char *ptr;
+
+ bv.bv_val = val->bv_val + STRLENOF( "dn" );
+
+ if ( bv.bv_val[ 0 ] == '.' ) {
+ bv.bv_val++;
+
+ if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
+ bv.bv_val += STRLENOF( "exact:" );
+ scope = LDAP_X_SCOPE_EXACT;
+
+ } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
+ bv.bv_val += STRLENOF( "regex:" );
+ scope = LDAP_X_SCOPE_REGEX;
+
+ } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
+ bv.bv_val += STRLENOF( "children:" );
+ scope = LDAP_X_SCOPE_CHILDREN;
+
+ } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
+ bv.bv_val += STRLENOF( "subtree:" );
+ scope = LDAP_X_SCOPE_SUBTREE;
+
+ } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
+ bv.bv_val += STRLENOF( "onelevel:" );
+ scope = LDAP_X_SCOPE_ONELEVEL;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else {
+ if ( bv.bv_val[ 0 ] != ':' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ scope = LDAP_X_SCOPE_EXACT;
+ bv.bv_val++;
+ }
+
+ bv.bv_val += strspn( bv.bv_val, " " );
+ /* jump here in case no type specification was present
+ * and uri was not an URI... HEADS-UP: assuming EXACT */
+is_dn: bv.bv_len = val->bv_len - ( bv.bv_val - val->bv_val );
+
+ /* a single '*' means any DN without using regexes */
+ if ( ber_bvccmp( &bv, '*' ) ) {
+ ber_str2bv_x( "dn:*", STRLENOF( "dn:*" ), 1, normalized, ctx );
+ return LDAP_SUCCESS;
+ }
+
+ switch ( scope ) {
+ case LDAP_X_SCOPE_EXACT:
+ case LDAP_X_SCOPE_CHILDREN:
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_ONELEVEL:
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
+ } else {
+ rc = dnPretty( NULL, &bv, &out, ctx );
+ }
+ if( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ break;
+
+ case LDAP_X_SCOPE_REGEX:
+ normalized->bv_len = STRLENOF( "dn.regex:" ) + bv.bv_len;
+ normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
+ ptr = lutil_strcopy( normalized->bv_val, "dn.regex:" );
+ ptr = lutil_strncopy( ptr, bv.bv_val, bv.bv_len );
+ ptr[ 0 ] = '\0';
+ return LDAP_SUCCESS;
+
+ default:
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* prepare prefix */
+ switch ( scope ) {
+ case LDAP_X_SCOPE_EXACT:
+ BER_BVSTR( &prefix, "dn:" );
+ break;
+
+ case LDAP_X_SCOPE_CHILDREN:
+ BER_BVSTR( &prefix, "dn.children:" );
+ break;
+
+ case LDAP_X_SCOPE_SUBTREE:
+ BER_BVSTR( &prefix, "dn.subtree:" );
+ break;
+
+ case LDAP_X_SCOPE_ONELEVEL:
+ BER_BVSTR( &prefix, "dn.onelevel:" );
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ normalized->bv_len = prefix.bv_len + out.bv_len;
+ normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
+
+ ptr = lutil_strcopy( normalized->bv_val, prefix.bv_val );
+ ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
+ ptr[ 0 ] = '\0';
+ ber_memfree_x( out.bv_val, ctx );
+
+ return LDAP_SUCCESS;
+
+ /*
+ * 4) u[.mech[/realm]]:<ID>
+ */
+ } else if ( ( val->bv_val[ 0 ] == 'u' || val->bv_val[ 0 ] == 'U' )
+ && ( val->bv_val[ 1 ] == ':'
+ || val->bv_val[ 1 ] == '/'
+ || val->bv_val[ 1 ] == '.' ) )
+ {
+ char buf[ SLAP_LDAPDN_MAXLEN ];
+ struct berval id,
+ user = BER_BVNULL,
+ realm = BER_BVNULL,
+ mech = BER_BVNULL;
+
+ if ( sizeof( buf ) <= val->bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ id.bv_len = val->bv_len;
+ id.bv_val = buf;
+ strncpy( buf, val->bv_val, sizeof( buf ) );
+
+ rc = slap_parse_user( &id, &user, &realm, &mech );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ber_dupbv_x( normalized, val, ctx );
+
+ return rc;
+
+ /*
+ * 5) group[/groupClass[/memberAttr]]:<DN>
+ *
+ * <groupClass> defaults to "groupOfNames"
+ * <memberAttr> defaults to "member"
+ *
+ * <DN> must pass DN normalization
+ */
+ } else if ( strncasecmp( val->bv_val, "group", STRLENOF( "group" ) ) == 0 )
+ {
+ struct berval group_dn = BER_BVNULL,
+ group_oc = BER_BVNULL,
+ member_at = BER_BVNULL,
+ out = BER_BVNULL;
+ char *ptr;
+
+ bv.bv_val = val->bv_val + STRLENOF( "group" );
+ bv.bv_len = val->bv_len - STRLENOF( "group" );
+ group_dn.bv_val = ber_bvchr( &bv, ':' );
+ if ( group_dn.bv_val == NULL ) {
+ /* last chance: assume it's a(n exact) DN ... */
+ bv.bv_val = val->bv_val;
+ scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+ }
+
+ /*
+ * FIXME: we assume that "member" and "groupOfNames"
+ * are present in schema...
+ */
+ if ( bv.bv_val[ 0 ] == '/' ) {
+ ObjectClass *oc = NULL;
+
+ group_oc.bv_val = &bv.bv_val[ 1 ];
+ group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
+
+ member_at.bv_val = ber_bvchr( &group_oc, '/' );
+ if ( member_at.bv_val ) {
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
+ member_at.bv_val++;
+ member_at.bv_len = group_dn.bv_val - member_at.bv_val;
+ rc = slap_bv2ad( &member_at, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ member_at = ad->ad_cname;
+
+ }
+
+ oc = oc_bvfind( &group_oc );
+ if ( oc == NULL ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ group_oc = oc->soc_cname;
+ }
+
+ group_dn.bv_val++;
+ group_dn.bv_len = val->bv_len - ( group_dn.bv_val - val->bv_val );
+
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, &group_dn, &out, ctx );
+ } else {
+ rc = dnPretty( NULL, &group_dn, &out, ctx );
+ }
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ normalized->bv_len = STRLENOF( "group" ":" ) + out.bv_len;
+ if ( !BER_BVISNULL( &group_oc ) ) {
+ normalized->bv_len += STRLENOF( "/" ) + group_oc.bv_len;
+ if ( !BER_BVISNULL( &member_at ) ) {
+ normalized->bv_len += STRLENOF( "/" ) + member_at.bv_len;
+ }
+ }
+
+ normalized->bv_val = ber_memalloc_x( normalized->bv_len + 1, ctx );
+ ptr = lutil_strcopy( normalized->bv_val, "group" );
+ if ( !BER_BVISNULL( &group_oc ) ) {
+ ptr[ 0 ] = '/';
+ ptr++;
+ ptr = lutil_strncopy( ptr, group_oc.bv_val, group_oc.bv_len );
+ if ( !BER_BVISNULL( &member_at ) ) {
+ ptr[ 0 ] = '/';
+ ptr++;
+ ptr = lutil_strncopy( ptr, member_at.bv_val, member_at.bv_len );
+ }
+ }
+ ptr[ 0 ] = ':';
+ ptr++;
+ ptr = lutil_strncopy( ptr, out.bv_val, out.bv_len );
+ ptr[ 0 ] = '\0';
+ ber_memfree_x( out.bv_val, ctx );
+
+ return rc;
+ }
+
+ /*
+ * ldap:///<base>??<scope>?<filter>
+ * <scope> ::= {base|one|subtree}
+ *
+ * <scope> defaults to "base"
+ * <base> must pass DN normalization
+ * <filter> must pass str2filter()
+ */
+ rc = ldap_url_parse( val->bv_val, &ludp );
+ switch ( rc ) {
+ case LDAP_URL_SUCCESS:
+ /* FIXME: the check is pedantic, but I think it's necessary,
+ * because people tend to use things like ldaps:// which
+ * gives the idea SSL is being used. Maybe we could
+ * accept ldapi:// as well, but the point is that we use
+ * an URL as an easy means to define bits of a search with
+ * little parsing.
+ */
+ if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
+ /*
+ * must be ldap:///
+ */
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ AC_MEMCPY( ludp->lud_scheme, "ldap", STRLENOF( "ldap" ) );
+ break;
+
+ case LDAP_URL_ERR_BADSCHEME:
+ /*
+ * last chance: assume it's a(n exact) DN ...
+ *
+ * NOTE: must pass DN normalization
+ */
+ ldap_free_urldesc( ludp );
+ bv.bv_val = val->bv_val;
+ scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+
+ default:
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ if ( ( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs || ludp->lud_exts )
+ {
+ /* host part must be empty */
+ /* attrs and extensions parts must be empty */
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /* Grab the filter */
+ if ( ludp->lud_filter ) {
+ struct berval filterstr;
+ Filter *f;
+
+ lud_filter = ludp->lud_filter;
+
+ f = str2filter( lud_filter );
+ if ( f == NULL ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ filter2bv( f, &filterstr );
+ filter_free( f );
+ if ( BER_BVISNULL( &filterstr ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ ludp->lud_filter = filterstr.bv_val;
+ }
+
+ /* Grab the searchbase */
+ if ( ludp->lud_dn ) {
+ struct berval out = BER_BVNULL;
+
+ lud_dn = ludp->lud_dn;
+
+ ber_str2bv( lud_dn, 0, 0, &bv );
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, &bv, &out, ctx );
+ } else {
+ rc = dnPretty( NULL, &bv, &out, ctx );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ ludp->lud_dn = out.bv_val;
+ } else {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ ludp->lud_port = 0;
+ normalized->bv_val = ldap_url_desc2str( ludp );
+ if ( normalized->bv_val ) {
+ normalized->bv_len = strlen( normalized->bv_val );
+
+ } else {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+done:
+ if ( lud_filter ) {
+ if ( ludp->lud_filter != lud_filter ) {
+ ber_memfree( ludp->lud_filter );
+ }
+ ludp->lud_filter = lud_filter;
+ }
+
+ if ( lud_dn ) {
+ if ( ludp->lud_dn != lud_dn ) {
+ slap_sl_free( ludp->lud_dn, ctx );
+ }
+ ludp->lud_dn = lud_dn;
+ }
+
+ ldap_free_urldesc( ludp );
+
+ return( rc );
+}
+
+int
+authzNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> authzNormalize: <%s>\n",
+ val->bv_val );
+
+ rc = authzPrettyNormal( val, normalized, ctx, 1 );
+
+ Debug( LDAP_DEBUG_TRACE, "<<< authzNormalize: <%s> (%d)\n",
+ normalized->bv_val, rc );
+
+ return rc;
+}
+
+int
+authzPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx)
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> authzPretty: <%s>\n",
+ val->bv_val );
+
+ rc = authzPrettyNormal( val, out, ctx, 0 );
+
+ Debug( LDAP_DEBUG_TRACE, "<<< authzPretty: <%s> (%d)\n",
+ out->bv_val ? out->bv_val : "(null)" , rc );
+
+ return rc;
+}
+
+
+static int
+slap_parseURI(
+ Operation *op,
+ struct berval *uri,
+ struct berval *base,
+ struct berval *nbase,
+ int *scope,
+ Filter **filter,
+ struct berval *fstr,
+ int normalize )
+{
+ struct berval bv;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ struct berval idx;
+
+ assert( uri != NULL && !BER_BVISNULL( uri ) );
+ BER_BVZERO( base );
+ BER_BVZERO( nbase );
+ BER_BVZERO( fstr );
+ *scope = -1;
+ *filter = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_parseURI: parsing %s\n", uri->bv_val );
+
+ rc = LDAP_PROTOCOL_ERROR;
+
+ idx = *uri;
+ if ( idx.bv_val[ 0 ] == '{' ) {
+ char *ptr;
+
+ ptr = ber_bvchr( &idx, '}' ) + 1;
+
+ assert( ptr != (void *)1 );
+
+ idx.bv_len -= ptr - idx.bv_val;
+ idx.bv_val = ptr;
+ uri = &idx;
+ }
+
+ /*
+ * dn[.<dnstyle>]:<dnpattern>
+ * <dnstyle> ::= {exact|regex|children|subtree|onelevel}
+ *
+ * <dnstyle> defaults to "exact"
+ * if <dnstyle> is not "regex", <dnpattern> must pass DN normalization
+ */
+ if ( !strncasecmp( uri->bv_val, "dn", STRLENOF( "dn" ) ) ) {
+ bv.bv_val = uri->bv_val + STRLENOF( "dn" );
+
+ if ( bv.bv_val[ 0 ] == '.' ) {
+ bv.bv_val++;
+
+ if ( !strncasecmp( bv.bv_val, "exact:", STRLENOF( "exact:" ) ) ) {
+ bv.bv_val += STRLENOF( "exact:" );
+ *scope = LDAP_X_SCOPE_EXACT;
+
+ } else if ( !strncasecmp( bv.bv_val, "regex:", STRLENOF( "regex:" ) ) ) {
+ bv.bv_val += STRLENOF( "regex:" );
+ *scope = LDAP_X_SCOPE_REGEX;
+
+ } else if ( !strncasecmp( bv.bv_val, "children:", STRLENOF( "children:" ) ) ) {
+ bv.bv_val += STRLENOF( "children:" );
+ *scope = LDAP_X_SCOPE_CHILDREN;
+
+ } else if ( !strncasecmp( bv.bv_val, "subtree:", STRLENOF( "subtree:" ) ) ) {
+ bv.bv_val += STRLENOF( "subtree:" );
+ *scope = LDAP_X_SCOPE_SUBTREE;
+
+ } else if ( !strncasecmp( bv.bv_val, "onelevel:", STRLENOF( "onelevel:" ) ) ) {
+ bv.bv_val += STRLENOF( "onelevel:" );
+ *scope = LDAP_X_SCOPE_ONELEVEL;
+
+ } else {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ } else {
+ if ( bv.bv_val[ 0 ] != ':' ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+ *scope = LDAP_X_SCOPE_EXACT;
+ bv.bv_val++;
+ }
+
+ bv.bv_val += strspn( bv.bv_val, " " );
+ /* jump here in case no type specification was present
+ * and uri was not an URI... HEADS-UP: assuming EXACT */
+is_dn: bv.bv_len = uri->bv_len - (bv.bv_val - uri->bv_val);
+
+ /* a single '*' means any DN without using regexes */
+ if ( ber_bvccmp( &bv, '*' ) ) {
+ *scope = LDAP_X_SCOPE_USERS;
+ }
+
+ switch ( *scope ) {
+ case LDAP_X_SCOPE_EXACT:
+ case LDAP_X_SCOPE_CHILDREN:
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_ONELEVEL:
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, &bv, nbase, op->o_tmpmemctx );
+ if( rc != LDAP_SUCCESS ) {
+ *scope = -1;
+ }
+ } else {
+ ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
+ rc = LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_X_SCOPE_REGEX:
+ ber_dupbv_x( nbase, &bv, op->o_tmpmemctx );
+
+ case LDAP_X_SCOPE_USERS:
+ rc = LDAP_SUCCESS;
+ break;
+
+ default:
+ *scope = -1;
+ break;
+ }
+
+ return rc;
+
+ /*
+ * u:<uid>
+ */
+ } else if ( ( uri->bv_val[ 0 ] == 'u' || uri->bv_val[ 0 ] == 'U' )
+ && ( uri->bv_val[ 1 ] == ':'
+ || uri->bv_val[ 1 ] == '/'
+ || uri->bv_val[ 1 ] == '.' ) )
+ {
+ Connection c = *op->o_conn;
+ char buf[ SLAP_LDAPDN_MAXLEN ];
+ struct berval id,
+ user = BER_BVNULL,
+ realm = BER_BVNULL,
+ mech = BER_BVNULL;
+
+ if ( sizeof( buf ) <= uri->bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ id.bv_len = uri->bv_len;
+ id.bv_val = buf;
+ strncpy( buf, uri->bv_val, sizeof( buf ) );
+
+ rc = slap_parse_user( &id, &user, &realm, &mech );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( !BER_BVISNULL( &mech ) ) {
+ c.c_sasl_bind_mech = mech;
+ } else {
+ BER_BVSTR( &c.c_sasl_bind_mech, "AUTHZ" );
+ }
+
+ rc = slap_sasl_getdn( &c, op, &user,
+ realm.bv_val, nbase, SLAP_GETDN_AUTHZID );
+
+ if ( rc == LDAP_SUCCESS ) {
+ *scope = LDAP_X_SCOPE_EXACT;
+ }
+
+ return rc;
+
+ /*
+ * group[/<groupoc>[/<groupat>]]:<groupdn>
+ *
+ * groupoc defaults to "groupOfNames"
+ * groupat defaults to "member"
+ *
+ * <groupdn> must pass DN normalization
+ */
+ } else if ( strncasecmp( uri->bv_val, "group", STRLENOF( "group" ) ) == 0 )
+ {
+ struct berval group_dn = BER_BVNULL,
+ group_oc = BER_BVNULL,
+ member_at = BER_BVNULL;
+ char *tmp;
+
+ bv.bv_val = uri->bv_val + STRLENOF( "group" );
+ bv.bv_len = uri->bv_len - STRLENOF( "group" );
+ group_dn.bv_val = ber_bvchr( &bv, ':' );
+ if ( group_dn.bv_val == NULL ) {
+ /* last chance: assume it's a(n exact) DN ... */
+ bv.bv_val = uri->bv_val;
+ *scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+ }
+
+ if ( bv.bv_val[ 0 ] == '/' ) {
+ group_oc.bv_val = &bv.bv_val[ 1 ];
+ group_oc.bv_len = group_dn.bv_val - group_oc.bv_val;
+
+ member_at.bv_val = ber_bvchr( &group_oc, '/' );
+ if ( member_at.bv_val ) {
+ group_oc.bv_len = member_at.bv_val - group_oc.bv_val;
+ member_at.bv_val++;
+ member_at.bv_len = group_dn.bv_val - member_at.bv_val;
+
+ } else {
+ BER_BVSTR( &member_at, SLAPD_GROUP_ATTR );
+ }
+
+ } else {
+ BER_BVSTR( &group_oc, SLAPD_GROUP_CLASS );
+ BER_BVSTR( &member_at, SLAPD_GROUP_ATTR );
+ }
+ group_dn.bv_val++;
+ group_dn.bv_len = uri->bv_len - ( group_dn.bv_val - uri->bv_val );
+
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, &group_dn, nbase, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ *scope = -1;
+ return rc;
+ }
+ } else {
+ ber_dupbv_x( nbase, &group_dn, op->o_tmpmemctx );
+ rc = LDAP_SUCCESS;
+ }
+ *scope = LDAP_X_SCOPE_GROUP;
+
+ /* FIXME: caller needs to add value of member attribute
+ * and close brackets twice */
+ fstr->bv_len = STRLENOF( "(&(objectClass=)(=" /* )) */ )
+ + group_oc.bv_len + member_at.bv_len;
+ fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
+
+ tmp = lutil_strncopy( fstr->bv_val, "(&(objectClass=" /* )) */ ,
+ STRLENOF( "(&(objectClass=" /* )) */ ) );
+ tmp = lutil_strncopy( tmp, group_oc.bv_val, group_oc.bv_len );
+ tmp = lutil_strncopy( tmp, /* ( */ ")(" /* ) */ ,
+ STRLENOF( /* ( */ ")(" /* ) */ ) );
+ tmp = lutil_strncopy( tmp, member_at.bv_val, member_at.bv_len );
+ tmp = lutil_strncopy( tmp, "=", STRLENOF( "=" ) );
+
+ return rc;
+ }
+
+ /*
+ * ldap:///<base>??<scope>?<filter>
+ * <scope> ::= {base|one|subtree}
+ *
+ * <scope> defaults to "base"
+ * <base> must pass DN normalization
+ * <filter> must pass str2filter()
+ */
+ rc = ldap_url_parse( uri->bv_val, &ludp );
+ switch ( rc ) {
+ case LDAP_URL_SUCCESS:
+ /* FIXME: the check is pedantic, but I think it's necessary,
+ * because people tend to use things like ldaps:// which
+ * gives the idea SSL is being used. Maybe we could
+ * accept ldapi:// as well, but the point is that we use
+ * an URL as an easy means to define bits of a search with
+ * little parsing.
+ */
+ if ( strcasecmp( ludp->lud_scheme, "ldap" ) != 0 ) {
+ /*
+ * must be ldap:///
+ */
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ break;
+
+ case LDAP_URL_ERR_BADSCHEME:
+ /*
+ * last chance: assume it's a(n exact) DN ...
+ *
+ * NOTE: must pass DN normalization
+ */
+ ldap_free_urldesc( ludp );
+ bv.bv_val = uri->bv_val;
+ *scope = LDAP_X_SCOPE_EXACT;
+ goto is_dn;
+
+ default:
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ if ( ( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs || ludp->lud_exts )
+ {
+ /* host part must be empty */
+ /* attrs and extensions parts must be empty */
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ /* Grab the scope */
+ *scope = ludp->lud_scope;
+
+ /* Grab the filter */
+ if ( ludp->lud_filter ) {
+ *filter = str2filter_x( op, ludp->lud_filter );
+ if ( *filter == NULL ) {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ ber_str2bv( ludp->lud_filter, 0, 0, fstr );
+ }
+
+ /* Grab the searchbase */
+ ber_str2bv( ludp->lud_dn, 0, 0, base );
+ if ( normalize ) {
+ rc = dnNormalize( 0, NULL, NULL, base, nbase, op->o_tmpmemctx );
+ } else {
+ ber_dupbv_x( nbase, base, op->o_tmpmemctx );
+ rc = LDAP_SUCCESS;
+ }
+
+done:
+ if( rc != LDAP_SUCCESS ) {
+ if( *filter ) {
+ filter_free_x( op, *filter, 1 );
+ *filter = NULL;
+ }
+ BER_BVZERO( base );
+ BER_BVZERO( fstr );
+ } else {
+ /* Don't free these, return them to caller */
+ ludp->lud_filter = NULL;
+ ludp->lud_dn = NULL;
+ }
+
+ ldap_free_urldesc( ludp );
+ return( rc );
+}
+
+static int slap_sasl_rewrite_config_argv(
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ int rc;
+ char *argv0 = NULL;
+
+ if ( strncasecmp( argv[0], "authid-", STRLENOF( "authid-" ) ) == 0 ) {
+ /* strip "authid-" prefix for parsing */
+ argv0 = argv[0];
+ argv[0] = &argv0[ STRLENOF( "authid-" ) ];
+ }
+
+ /* init at first call */
+ if ( sasl_rwinfo == NULL ) {
+ sasl_rwinfo = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+ }
+
+ rc = rewrite_parse( sasl_rwinfo, fname, lineno, argc, argv );
+
+ if ( argv0 )
+ argv[0] = argv0;
+
+ return rc;
+}
+
+static int slap_sasl_rewrite_config_bv(
+ const char *fname,
+ int lineno,
+ struct berval bv
+)
+{
+ int rc;
+ ConfigArgs ca = { 0 };
+
+ ca.line = bv.bv_val;
+ ca.argc = 0;
+ config_fp_parse_line( &ca );
+
+ rc = slap_sasl_rewrite_config_argv( fname, lineno, ca.argc, ca.argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ return rc;
+}
+
+static void
+slap_sasl_rewrite_bva_add(
+ BerVarray *bva,
+ int idx,
+ int argc,
+ char **argv
+)
+{
+ char *line, *s;
+ struct berval bv;
+
+ if ( argc > 1 ) {
+ /* quote all args but the first */
+ line = ldap_charray2str( argv, "\" \"" );
+ ber_str2bv( line, 0, 0, &bv );
+ s = ber_bvchr( &bv, '"' );
+ assert( s != NULL );
+
+ /* move the trailing quote of argv[0] to the end */
+ AC_MEMCPY( s, s + 1, bv.bv_len - ( s - bv.bv_val ) );
+ bv.bv_val[ bv.bv_len - 1 ] = '"';
+ } else {
+ ber_str2bv( argv[ 0 ], 0, 1, &bv );
+ }
+
+ if ( idx == -1 ) {
+ ber_bvarray_add( bva, &bv );
+ } else {
+ (*bva)[ idx ] = bv;
+ }
+}
+
+static int
+slap_sasl_rewrite_destroy( void )
+{
+ if ( sasl_rwinfo ) {
+ rewrite_info_delete( &sasl_rwinfo );
+ sasl_rwinfo = NULL;
+ }
+
+ return 0;
+}
+
+int slap_sasl_rewrite_config(
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv,
+ int valx
+)
+{
+ int rc, i, last;
+ char *line;
+ struct berval bv;
+ struct rewrite_info *rw = sasl_rwinfo;
+
+ for ( last = 0; authz_rewrites && !BER_BVISNULL( &authz_rewrites[ last ] ); last++ )
+ /* count'em */ ;
+
+ if ( valx == -1 || valx >= last ) {
+ valx = -1;
+ rc = slap_sasl_rewrite_config_argv( fname, lineno, argc, argv );
+ if ( rc == 0 ) {
+ slap_sasl_rewrite_bva_add( &authz_rewrites, valx, argc, argv );
+ }
+ return rc;
+ }
+
+ sasl_rwinfo = NULL;
+
+ for ( i = 0; i < valx; i++ )
+ {
+ rc = slap_sasl_rewrite_config_bv( fname, lineno, authz_rewrites[ i ] );
+ assert( rc == 0 );
+ }
+
+ rc = slap_sasl_rewrite_config_argv( fname, lineno, argc, argv );
+ if ( rc != 0 ) {
+ slap_sasl_rewrite_destroy();
+ sasl_rwinfo = rw;
+ return 1;
+ }
+
+ for ( i = valx; authz_rewrites && !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
+ {
+ rc = slap_sasl_rewrite_config_bv( fname, lineno, authz_rewrites[ i ] );
+ assert( rc == 0 );
+ }
+
+ authz_rewrites = ch_realloc( authz_rewrites,
+ ( last + 2 )*sizeof( struct berval ) );
+ BER_BVZERO( &authz_rewrites[ last + 1 ] );
+
+ for ( i = last - 1; i >= valx; i-- )
+ {
+ authz_rewrites[ i + 1 ] = authz_rewrites[ i ];
+ }
+
+ slap_sasl_rewrite_bva_add( &authz_rewrites, valx, argc, argv );
+
+ if ( rw )
+ rewrite_info_delete( &rw );
+
+ return rc;
+}
+
+int slap_sasl_rewrite_delete( int valx ) {
+ int rc, i;
+
+ if ( valx == -1 ) {
+ slap_sasl_rewrite_destroy();
+ if ( authz_rewrites ) {
+ ber_bvarray_free( authz_rewrites );
+ authz_rewrites = NULL;
+ }
+ return 0;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
+ /* count'em */ ;
+
+ if ( valx >= i ) {
+ return 1;
+ }
+
+ i = valx;
+ ber_memfree( authz_rewrites[ i ].bv_val );
+ for ( ; !BER_BVISNULL( &authz_rewrites[ i + 1 ] ); i++ )
+ {
+ authz_rewrites[ i ] = authz_rewrites[ i + 1 ];
+ }
+ BER_BVZERO( &authz_rewrites[ i ] );
+
+ slap_sasl_rewrite_destroy();
+
+ for ( i = 0; !BER_BVISNULL( &authz_rewrites[ i ] ); i++ )
+ {
+ rc = slap_sasl_rewrite_config_bv( "slapd", 0, authz_rewrites[ i ] );
+ assert( rc == 0 );
+ }
+
+ return rc;
+}
+
+int slap_sasl_rewrite_unparse( BerVarray *bva ) {
+ if ( authz_rewrites ) {
+ return slap_bv_x_ordered_unparse( authz_rewrites, bva );
+ }
+ return 0;
+}
+
+static int
+slap_sasl_regexp_rewrite_config(
+ struct rewrite_info **rwinfo,
+ const char *fname,
+ int lineno,
+ const char *match,
+ const char *replace,
+ const char *context )
+{
+ int rc;
+ char *argvRule[] = { "rewriteRule", NULL, NULL, ":@", NULL };
+ struct rewrite_info *rw = *rwinfo;
+
+ /* init at first call */
+ if ( rw == NULL ) {
+ char *argvEngine[] = { "rewriteEngine", "on", NULL };
+ char *argvContext[] = { "rewriteContext", NULL, NULL };
+
+ /* initialize rewrite engine */
+ rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+
+ /* switch on rewrite engine */
+ rc = rewrite_parse( rw, fname, lineno, 2, argvEngine );
+ if (rc != LDAP_SUCCESS) {
+ goto out;
+ }
+
+ /* create generic authid context */
+ argvContext[1] = AUTHID_CONTEXT;
+ rc = rewrite_parse( rw, fname, lineno, 2, argvContext );
+ if (rc != LDAP_SUCCESS) {
+ goto out;
+ }
+ }
+
+ argvRule[1] = (char *)match;
+ argvRule[2] = (char *)replace;
+ rc = rewrite_parse( rw, fname, lineno, 4, argvRule );
+out:
+ if (rc == LDAP_SUCCESS) {
+ *rwinfo = rw;
+ } else {
+ rewrite_info_delete( &rw );
+ }
+
+ return rc;
+}
+
+int slap_sasl_regexp_config( const char *match, const char *replace, int valx )
+{
+ int i, rc;
+ SaslRegexp_t sr;
+ struct rewrite_info *rw = NULL;
+
+ if ( valx < 0 || valx > nSaslRegexp )
+ valx = nSaslRegexp;
+
+ for ( i = 0; i < valx; i++) {
+ rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
+ SaslRegexp[i].sr_match,
+ SaslRegexp[i].sr_replace,
+ AUTHID_CONTEXT);
+ assert( rc == 0 );
+ }
+
+ rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
+ match, replace, AUTHID_CONTEXT );
+
+ if ( rc == LDAP_SUCCESS ) {
+ SaslRegexp = (SaslRegexp_t *) ch_realloc( (char *) SaslRegexp,
+ (nSaslRegexp + 1) * sizeof(SaslRegexp_t) );
+
+ for ( i = nSaslRegexp; i > valx; i-- ) {
+ SaslRegexp[i] = SaslRegexp[i - 1];
+ }
+
+ SaslRegexp[i] = sr;
+ SaslRegexp[i].sr_match = ch_strdup( match );
+ SaslRegexp[i].sr_replace = ch_strdup( replace );
+
+ nSaslRegexp++;
+
+ for ( i = valx + 1; i < nSaslRegexp; i++ ) {
+ rc = slap_sasl_regexp_rewrite_config( &rw, "sasl-regexp", 0,
+ SaslRegexp[i].sr_match,
+ SaslRegexp[i].sr_replace,
+ AUTHID_CONTEXT);
+ assert( rc == 0 );
+ }
+
+ slap_sasl_rewrite_destroy();
+ sasl_rwinfo = rw;
+ } else if ( rw ) {
+ rewrite_info_delete( &rw );
+ }
+
+ return rc;
+}
+
+static void
+slap_sasl_regexp_destroy_one( int n )
+{
+ ch_free( SaslRegexp[ n ].sr_match );
+ ch_free( SaslRegexp[ n ].sr_replace );
+}
+
+void
+slap_sasl_regexp_destroy( void )
+{
+ if ( SaslRegexp ) {
+ int n;
+
+ for ( n = 0; n < nSaslRegexp; n++ ) {
+ slap_sasl_regexp_destroy_one( n );
+ }
+
+ ch_free( SaslRegexp );
+ SaslRegexp = NULL;
+ nSaslRegexp = 0;
+ }
+
+ slap_sasl_rewrite_destroy();
+}
+
+int slap_sasl_regexp_delete( int valx )
+{
+ int rc = 0;
+
+ if ( valx >= nSaslRegexp ) {
+ rc = 1;
+ } else if ( valx < 0 || nSaslRegexp == 1 ) {
+ slap_sasl_regexp_destroy();
+ } else {
+ int i;
+
+ slap_sasl_regexp_destroy_one( valx );
+ nSaslRegexp--;
+
+ for ( i = valx; i < nSaslRegexp; i++ ) {
+ SaslRegexp[ i ] = SaslRegexp[ i + 1 ];
+ }
+
+ slap_sasl_rewrite_destroy();
+ for ( i = 0; i < nSaslRegexp; i++ ) {
+ rc = slap_sasl_regexp_rewrite_config( &sasl_rwinfo, "sasl-regexp", 0,
+ SaslRegexp[ i ].sr_match,
+ SaslRegexp[ i ].sr_replace,
+ AUTHID_CONTEXT );
+ assert( rc == 0 );
+ }
+ }
+
+ return rc;
+}
+
+void slap_sasl_regexp_unparse( BerVarray *out )
+{
+ int i;
+ BerVarray bva = NULL;
+ char ibuf[32], *ptr;
+ struct berval idx;
+
+ if ( !nSaslRegexp ) return;
+
+ idx.bv_val = ibuf;
+ bva = ch_malloc( (nSaslRegexp+1) * sizeof(struct berval) );
+ BER_BVZERO(bva+nSaslRegexp);
+ for ( i=0; i<nSaslRegexp; i++ ) {
+ idx.bv_len = sprintf( idx.bv_val, "{%d}", i);
+ bva[i].bv_len = idx.bv_len + strlen( SaslRegexp[i].sr_match ) +
+ strlen( SaslRegexp[i].sr_replace ) + 5;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len+1 );
+ ptr = lutil_strcopy( bva[i].bv_val, ibuf );
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_match );
+ ptr = lutil_strcopy( ptr, "\" \"" );
+ ptr = lutil_strcopy( ptr, SaslRegexp[i].sr_replace );
+ *ptr++ = '"';
+ *ptr = '\0';
+ }
+ *out = bva;
+}
+
+/* Take the passed in SASL name and attempt to convert it into an
+ LDAP URI to find the matching LDAP entry, using the pattern matching
+ strings given in the saslregexp config file directive(s) */
+
+static int slap_authz_regexp( struct berval *in, struct berval *out,
+ int flags, void *ctx )
+{
+ const char *context = AUTHID_CONTEXT;
+
+ if ( sasl_rwinfo == NULL || BER_BVISNULL( in ) ) {
+ return 0;
+ }
+
+ /* FIXME: if aware of authc/authz mapping,
+ * we could use different contexts ... */
+ switch ( rewrite_session( sasl_rwinfo, context, in->bv_val, NULL,
+ &out->bv_val ) )
+ {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( out ) ) {
+ char *val = out->bv_val;
+ ber_str2bv_x( val, 0, 1, out, ctx );
+ if ( val != in->bv_val ) {
+ free( val );
+ }
+ } else {
+ ber_dupbv_x( out, in, ctx );
+ }
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ context, in->bv_val, out->bv_val );
+ return 1;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ case REWRITE_REGEXEC_ERR:
+ default:
+ return 0;
+ }
+
+}
+
+/* This callback actually does some work...*/
+static int sasl_sc_sasl2dn( Operation *op, SlapReply *rs )
+{
+ struct berval *ndn = op->o_callback->sc_private;
+
+ if ( rs->sr_type != REP_SEARCH ) return LDAP_SUCCESS;
+
+ /* We only want to be called once */
+ if ( !BER_BVISNULL( ndn ) ) {
+ op->o_tmpfree( ndn->bv_val, op->o_tmpmemctx );
+ BER_BVZERO( ndn );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s: slap_sc_sasl2dn: search DN returned more than 1 entry\n",
+ op->o_log_prefix );
+ return LDAP_UNAVAILABLE; /* short-circuit the search */
+ }
+
+ ber_dupbv_x( ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ return LDAP_SUCCESS;
+}
+
+
+typedef struct smatch_info {
+ struct berval *dn;
+ int match;
+} smatch_info;
+
+static int sasl_sc_smatch( Operation *o, SlapReply *rs )
+{
+ smatch_info *sm = o->o_callback->sc_private;
+
+ if (rs->sr_type != REP_SEARCH) return 0;
+
+ if (dn_match(sm->dn, &rs->sr_entry->e_nname)) {
+ sm->match = 1;
+ return LDAP_UNAVAILABLE; /* short-circuit the search */
+ }
+
+ return 0;
+}
+
+int
+slap_sasl_matches( Operation *op, BerVarray rules,
+ struct berval *assertDN, struct berval *authc )
+{
+ int rc = LDAP_INAPPROPRIATE_AUTH;
+
+ if ( rules != NULL ) {
+ int i;
+
+ for( i = 0; !BER_BVISNULL( &rules[i] ); i++ ) {
+ rc = slap_sasl_match( op, &rules[i], assertDN, authc );
+ if ( rc == LDAP_SUCCESS ) break;
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Map a SASL regexp rule to a DN. If the rule is just a DN or a scope=base
+ * URI, just strcmp the rule (or its searchbase) to the *assertDN. Otherwise,
+ * the rule must be used as an internal search for entries. If that search
+ * returns the *assertDN entry, the match is successful.
+ *
+ * The assertDN should not have the dn: prefix
+ */
+
+static int
+slap_sasl_match( Operation *opx, struct berval *rule,
+ struct berval *assertDN, struct berval *authc )
+{
+ int rc;
+ regex_t reg;
+ smatch_info sm;
+ slap_callback cb = { NULL, sasl_sc_smatch, NULL, NULL };
+ Operation op = {0};
+ SlapReply rs = {REP_RESULT};
+ struct berval base = BER_BVNULL;
+
+ sm.dn = assertDN;
+ sm.match = 0;
+ cb.sc_private = &sm;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "===>slap_sasl_match: comparing DN %s to rule %s\n",
+ assertDN->bv_len ? assertDN->bv_val : "(null)", rule->bv_val );
+
+ /* NOTE: don't normalize rule if authz syntax is enabled */
+ rc = slap_parseURI( opx, rule, &base, &op.o_req_ndn,
+ &op.ors_scope, &op.ors_filter, &op.ors_filterstr, 0 );
+
+ if( rc != LDAP_SUCCESS ) goto CONCLUDED;
+
+ switch ( op.ors_scope ) {
+ case LDAP_X_SCOPE_EXACT:
+exact_match:
+ if ( dn_match( &op.o_req_ndn, assertDN ) ) {
+ rc = LDAP_SUCCESS;
+ } else {
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ }
+ goto CONCLUDED;
+
+ case LDAP_X_SCOPE_CHILDREN:
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_ONELEVEL:
+ {
+ int d = assertDN->bv_len - op.o_req_ndn.bv_len;
+
+ rc = LDAP_INAPPROPRIATE_AUTH;
+
+ if ( d == 0 && op.ors_scope == LDAP_X_SCOPE_SUBTREE ) {
+ goto exact_match;
+
+ } else if ( d > 0 ) {
+ struct berval bv;
+
+ /* leave room for at least one char of attributeType,
+ * one for '=' and one for ',' */
+ if ( d < (int) STRLENOF( "x=,") ) {
+ goto CONCLUDED;
+ }
+
+ bv.bv_len = op.o_req_ndn.bv_len;
+ bv.bv_val = assertDN->bv_val + d;
+
+ if ( bv.bv_val[ -1 ] == ',' && dn_match( &op.o_req_ndn, &bv ) ) {
+ switch ( op.ors_scope ) {
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_CHILDREN:
+ rc = LDAP_SUCCESS;
+ break;
+
+ case LDAP_X_SCOPE_ONELEVEL:
+ {
+ struct berval pdn;
+
+ dnParent( assertDN, &pdn );
+ /* the common portion of the DN
+ * already matches, so only check
+ * if parent DN of assertedDN
+ * is all the pattern */
+ if ( pdn.bv_len == op.o_req_ndn.bv_len ) {
+ rc = LDAP_SUCCESS;
+ }
+ break;
+ }
+ default:
+ /* at present, impossible */
+ assert( 0 );
+ }
+ }
+ }
+ goto CONCLUDED;
+ }
+
+ case LDAP_X_SCOPE_REGEX:
+ rc = regcomp(&reg, op.o_req_ndn.bv_val,
+ REG_EXTENDED|REG_ICASE|REG_NOSUB);
+ if ( rc == 0 ) {
+ rc = regexec(&reg, assertDN->bv_val, 0, NULL, 0);
+ regfree( &reg );
+ }
+ if ( rc == 0 ) {
+ rc = LDAP_SUCCESS;
+ } else {
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ }
+ goto CONCLUDED;
+
+ case LDAP_X_SCOPE_GROUP: {
+ char *tmp;
+
+ /* Now filterstr looks like "(&(objectClass=<group_oc>)(<member_at>="
+ * we need to append the <assertDN> so that the <group_dn> is searched
+ * with scope "base", and the filter ensures that <assertDN> is
+ * member of the group */
+ tmp = ch_realloc( op.ors_filterstr.bv_val, op.ors_filterstr.bv_len +
+ assertDN->bv_len + STRLENOF( /*"(("*/ "))" ) + 1 );
+ if ( tmp == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto CONCLUDED;
+ }
+ op.ors_filterstr.bv_val = tmp;
+
+ tmp = lutil_strcopy( &tmp[op.ors_filterstr.bv_len], assertDN->bv_val );
+ tmp = lutil_strcopy( tmp, /*"(("*/ "))" );
+
+ /* pass opx because str2filter_x may (and does) use o_tmpmfuncs */
+ op.ors_filter = str2filter_x( opx, op.ors_filterstr.bv_val );
+ if ( op.ors_filter == NULL ) {
+ rc = LDAP_PROTOCOL_ERROR;
+ goto CONCLUDED;
+ }
+ op.ors_scope = LDAP_SCOPE_BASE;
+
+ /* hijack match DN: use that of the group instead of the assertDN;
+ * assertDN is now in the filter */
+ sm.dn = &op.o_req_ndn;
+
+ /* do the search */
+ break;
+ }
+
+ case LDAP_X_SCOPE_USERS:
+ if ( !BER_BVISEMPTY( assertDN ) ) {
+ rc = LDAP_SUCCESS;
+ } else {
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ }
+ goto CONCLUDED;
+
+ default:
+ break;
+ }
+
+ /* Must run an internal search. */
+ if ( op.ors_filter == NULL ) {
+ rc = LDAP_FILTER_ERROR;
+ goto CONCLUDED;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_sasl_match: performing internal search (base=%s, scope=%d)\n",
+ op.o_req_ndn.bv_val, op.ors_scope );
+
+ op.o_bd = select_backend( &op.o_req_ndn, 1 );
+ if(( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL)) {
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ goto CONCLUDED;
+ }
+
+ op.o_hdr = opx->o_hdr;
+ op.o_tag = LDAP_REQ_SEARCH;
+ op.o_ndn = *authc;
+ op.o_callback = &cb;
+ slap_op_time( &op.o_time, &op.o_tincr );
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ /* use req_ndn as req_dn instead of non-pretty base of uri */
+ if( !BER_BVISNULL( &base ) ) {
+ ch_free( base.bv_val );
+ /* just in case... */
+ BER_BVZERO( &base );
+ }
+ ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
+ op.ors_deref = LDAP_DEREF_NEVER;
+ op.ors_slimit = 1;
+ op.ors_tlimit = SLAP_NO_LIMIT;
+ op.ors_attrs = slap_anlist_no_attrs;
+ op.ors_attrsonly = 1;
+
+ op.o_bd->be_search( &op, &rs );
+
+ if (sm.match) {
+ rc = LDAP_SUCCESS;
+ } else {
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ }
+
+CONCLUDED:
+ if( !BER_BVISNULL( &op.o_req_dn ) ) slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
+ if( !BER_BVISNULL( &op.o_req_ndn ) ) slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
+ if( op.ors_filter ) filter_free_x( opx, op.ors_filter, 1 );
+ if( !BER_BVISNULL( &op.ors_filterstr ) ) ch_free( op.ors_filterstr.bv_val );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<===slap_sasl_match: comparison returned %d\n", rc );
+
+ return( rc );
+}
+
+
+/*
+ * This function answers the question, "Can this ID authorize to that ID?",
+ * based on authorization rules. The rules are stored in the *searchDN, in the
+ * attribute named by *attr. If any of those rules map to the *assertDN, the
+ * authorization is approved.
+ *
+ * The DNs should not have the dn: prefix
+ */
+static int
+slap_sasl_check_authz( Operation *op,
+ struct berval *searchDN,
+ struct berval *assertDN,
+ AttributeDescription *ad,
+ struct berval *authc )
+{
+ int rc,
+ do_not_cache = op->o_do_not_cache;
+ BerVarray vals = NULL;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "==>slap_sasl_check_authz: does %s match %s rule in %s?\n",
+ assertDN->bv_val, ad->ad_cname.bv_val, searchDN->bv_val);
+
+ /* ITS#4760: don't cache group access */
+ op->o_do_not_cache = 1;
+ rc = backend_attribute( op, NULL, searchDN, ad, &vals, ACL_AUTH );
+ op->o_do_not_cache = do_not_cache;
+ if( rc != LDAP_SUCCESS ) goto COMPLETE;
+
+ /* Check if the *assertDN matches any *vals */
+ rc = slap_sasl_matches( op, vals, assertDN, authc );
+
+COMPLETE:
+ if( vals ) ber_bvarray_free_x( vals, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<==slap_sasl_check_authz: %s check returning %d\n",
+ ad->ad_cname.bv_val, rc );
+
+ return( rc );
+}
+
+/*
+ * Given a SASL name (e.g. "UID=name,cn=REALM,cn=MECH,cn=AUTH")
+ * return the LDAP DN to which it matches. The SASL regexp rules in the config
+ * file turn the SASL name into an LDAP URI. If the URI is just a DN (or a
+ * search with scope=base), just return the URI (or its searchbase). Otherwise
+ * an internal search must be done, and if that search returns exactly one
+ * entry, return the DN of that one entry.
+ */
+void
+slap_sasl2dn(
+ Operation *opx,
+ struct berval *saslname,
+ struct berval *sasldn,
+ int flags )
+{
+ int rc;
+ slap_callback cb = { NULL, sasl_sc_sasl2dn, NULL, NULL };
+ Operation op = {0};
+ SlapReply rs = {REP_RESULT};
+ struct berval regout = BER_BVNULL;
+ struct berval base = BER_BVNULL;
+
+ Debug( LDAP_DEBUG_TRACE, "==>slap_sasl2dn: "
+ "converting SASL name %s to a DN\n",
+ saslname->bv_val );
+
+ BER_BVZERO( sasldn );
+ cb.sc_private = sasldn;
+
+ /* Convert the SASL name into a minimal URI */
+ if( !slap_authz_regexp( saslname, &regout, flags, opx->o_tmpmemctx ) ) {
+ goto FINISHED;
+ }
+
+ /* NOTE: always normalize regout because it results
+ * from string submatch expansion */
+ rc = slap_parseURI( opx, &regout, &base, &op.o_req_ndn,
+ &op.ors_scope, &op.ors_filter, &op.ors_filterstr, 1 );
+ if ( !BER_BVISNULL( &regout ) ) slap_sl_free( regout.bv_val, opx->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ goto FINISHED;
+ }
+
+ /* Must do an internal search */
+ op.o_bd = select_backend( &op.o_req_ndn, 1 );
+
+ switch ( op.ors_scope ) {
+ case LDAP_X_SCOPE_EXACT:
+ *sasldn = op.o_req_ndn;
+ BER_BVZERO( &op.o_req_ndn );
+ /* intentionally continue to next case */
+
+ case LDAP_X_SCOPE_REGEX:
+ case LDAP_X_SCOPE_SUBTREE:
+ case LDAP_X_SCOPE_CHILDREN:
+ case LDAP_X_SCOPE_ONELEVEL:
+ case LDAP_X_SCOPE_GROUP:
+ case LDAP_X_SCOPE_USERS:
+ /* correctly parsed, but illegal */
+ goto FINISHED;
+
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ /* do a search */
+ break;
+
+ default:
+ /* catch unhandled cases (there shouldn't be) */
+ assert( 0 );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_sasl2dn: performing internal search (base=%s, scope=%d)\n",
+ op.o_req_ndn.bv_val, op.ors_scope );
+
+ if ( ( op.o_bd == NULL ) || ( op.o_bd->be_search == NULL) ) {
+ goto FINISHED;
+ }
+
+ /* Must run an internal search. */
+ if ( op.ors_filter == NULL ) {
+ rc = LDAP_FILTER_ERROR;
+ goto FINISHED;
+ }
+
+ op.o_hdr = opx->o_hdr;
+ op.o_tag = LDAP_REQ_SEARCH;
+ op.o_ndn = opx->o_conn->c_ndn;
+ op.o_callback = &cb;
+ slap_op_time( &op.o_time, &op.o_tincr );
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ op.ors_deref = LDAP_DEREF_NEVER;
+ op.ors_slimit = 1;
+ op.ors_tlimit = SLAP_NO_LIMIT;
+ op.ors_attrs = slap_anlist_no_attrs;
+ op.ors_attrsonly = 1;
+ /* use req_ndn as req_dn instead of non-pretty base of uri */
+ if( !BER_BVISNULL( &base ) ) {
+ ch_free( base.bv_val );
+ /* just in case... */
+ BER_BVZERO( &base );
+ }
+ ber_dupbv_x( &op.o_req_dn, &op.o_req_ndn, op.o_tmpmemctx );
+
+ op.o_bd->be_search( &op, &rs );
+
+FINISHED:
+ if( opx == opx->o_conn->c_sasl_bindop && !BER_BVISEMPTY( sasldn ) ) {
+ opx->o_conn->c_authz_backend = op.o_bd;
+ }
+ if( !BER_BVISNULL( &op.o_req_dn ) ) {
+ slap_sl_free( op.o_req_dn.bv_val, opx->o_tmpmemctx );
+ }
+ if( !BER_BVISNULL( &op.o_req_ndn ) ) {
+ slap_sl_free( op.o_req_ndn.bv_val, opx->o_tmpmemctx );
+ }
+ if( op.ors_filter ) {
+ filter_free_x( opx, op.ors_filter, 1 );
+ }
+ if( !BER_BVISNULL( &op.ors_filterstr ) ) {
+ ch_free( op.ors_filterstr.bv_val );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==slap_sasl2dn: Converted SASL name to %s\n",
+ !BER_BVISEMPTY( sasldn ) ? sasldn->bv_val : "<nothing>" );
+
+ return;
+}
+
+
+/* Check if a bind can SASL authorize to another identity.
+ * The DNs should not have the dn: prefix
+ */
+
+int slap_sasl_authorized( Operation *op,
+ struct berval *authcDN, struct berval *authzDN )
+{
+ int rc = LDAP_INAPPROPRIATE_AUTH;
+
+ /* User binding as anonymous */
+ if ( !authzDN || !authzDN->bv_len || !authzDN->bv_val ) {
+ rc = LDAP_SUCCESS;
+ goto DONE;
+ }
+
+ /* User is anonymous */
+ if ( !authcDN || !authcDN->bv_len || !authcDN->bv_val ) {
+ goto DONE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "==>slap_sasl_authorized: can %s become %s?\n",
+ authcDN->bv_len ? authcDN->bv_val : "(null)",
+ authzDN->bv_len ? authzDN->bv_val : "(null)" );
+
+ /* If person is authorizing to self, succeed */
+ if ( dn_match( authcDN, authzDN ) ) {
+ rc = LDAP_SUCCESS;
+ goto DONE;
+ }
+
+ /* Allow the manager to authorize as any DN in its own DBs. */
+ {
+ Backend *zbe = select_backend( authzDN, 1 );
+ if ( zbe && be_isroot_dn( zbe, authcDN )) {
+ rc = LDAP_SUCCESS;
+ goto DONE;
+ }
+ }
+
+ /* Check source rules */
+ if( authz_policy & SASL_AUTHZ_TO ) {
+ rc = slap_sasl_check_authz( op, authcDN, authzDN,
+ slap_schema.si_ad_saslAuthzTo, authcDN );
+ if(( rc == LDAP_SUCCESS ) ^ (( authz_policy & SASL_AUTHZ_AND) != 0)) {
+ if( rc != LDAP_SUCCESS )
+ rc = LDAP_INAPPROPRIATE_AUTH;
+ goto DONE;
+ }
+ }
+
+ /* Check destination rules */
+ if( authz_policy & SASL_AUTHZ_FROM ) {
+ rc = slap_sasl_check_authz( op, authzDN, authcDN,
+ slap_schema.si_ad_saslAuthzFrom, authcDN );
+ if( rc == LDAP_SUCCESS ) {
+ goto DONE;
+ }
+ }
+
+ rc = LDAP_INAPPROPRIATE_AUTH;
+
+DONE:
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<== slap_sasl_authorized: return %d\n", rc );
+
+ return( rc );
+}
diff --git a/servers/slapd/schema.c b/servers/slapd/schema.c
new file mode 100644
index 0000000..a98f0c6
--- /dev/null
+++ b/servers/slapd/schema.c
@@ -0,0 +1,167 @@
+/* schema.c - routines to manage schema definitions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+
+int
+schema_info( Entry **entry, const char **text )
+{
+ AttributeDescription *ad_structuralObjectClass
+ = slap_schema.si_ad_structuralObjectClass;
+ AttributeDescription *ad_objectClass
+ = slap_schema.si_ad_objectClass;
+ AttributeDescription *ad_createTimestamp
+ = slap_schema.si_ad_createTimestamp;
+ AttributeDescription *ad_modifyTimestamp
+ = slap_schema.si_ad_modifyTimestamp;
+
+ Entry *e;
+ struct berval vals[5];
+ struct berval nvals[5];
+
+ e = entry_alloc();
+ if( e == NULL ) {
+ /* Out of memory, do something about it */
+ Debug( LDAP_DEBUG_ANY,
+ "schema_info: entry_alloc failed - out of memory.\n" );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+
+ e->e_attrs = NULL;
+ /* backend-specific schema info should be created by the
+ * backend itself
+ */
+ ber_dupbv( &e->e_name, &frontendDB->be_schemadn );
+ ber_dupbv( &e->e_nname, &frontendDB->be_schemandn );
+ e->e_private = NULL;
+
+ BER_BVSTR( &vals[0], "subentry" );
+ if( attr_merge_one( e, ad_structuralObjectClass, vals, NULL ) ) {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+
+ BER_BVSTR( &vals[0], "top" );
+ BER_BVSTR( &vals[1], "subentry" );
+ BER_BVSTR( &vals[2], "subschema" );
+ BER_BVSTR( &vals[3], "extensibleObject" );
+ BER_BVZERO( &vals[4] );
+ if ( attr_merge( e, ad_objectClass, vals, NULL ) ) {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+
+ {
+ int rc;
+ AttributeDescription *desc = NULL;
+ struct berval rdn = frontendDB->be_schemadn;
+ vals[0].bv_val = ber_bvchr( &rdn, '=' );
+
+ if( vals[0].bv_val == NULL ) {
+ *text = "improperly configured subschema subentry";
+ return LDAP_OTHER;
+ }
+
+ vals[0].bv_val++;
+ vals[0].bv_len = rdn.bv_len - (vals[0].bv_val - rdn.bv_val);
+ rdn.bv_len -= vals[0].bv_len + 1;
+
+ rc = slap_bv2ad( &rdn, &desc, text );
+
+ if( rc != LDAP_SUCCESS ) {
+ entry_free( e );
+ *text = "improperly configured subschema subentry";
+ return LDAP_OTHER;
+ }
+
+ nvals[0].bv_val = ber_bvchr( &frontendDB->be_schemandn, '=' );
+ assert( nvals[0].bv_val != NULL );
+ nvals[0].bv_val++;
+ nvals[0].bv_len = frontendDB->be_schemandn.bv_len -
+ (nvals[0].bv_val - frontendDB->be_schemandn.bv_val);
+
+ if ( attr_merge_one( e, desc, vals, nvals ) ) {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+ }
+
+ {
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+
+ /*
+ * According to RFC 4512:
+
+ Servers SHOULD maintain the 'creatorsName', 'createTimestamp',
+ 'modifiersName', and 'modifyTimestamp' attributes for all entries of
+ the DIT.
+
+ * to be conservative, we declare schema created
+ * AND modified at server startup time ...
+ */
+
+ vals[0].bv_val = timebuf;
+ vals[0].bv_len = sizeof( timebuf );
+
+ slap_timestamp( &starttime, vals );
+
+ if( attr_merge_one( e, ad_createTimestamp, vals, NULL ) ) {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+ if( attr_merge_one( e, ad_modifyTimestamp, vals, NULL ) ) {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+ }
+
+ if ( syn_schema_info( e )
+ || mr_schema_info( e )
+ || mru_schema_info( e )
+ || at_schema_info( e )
+ || oc_schema_info( e )
+ || cr_schema_info( e ) )
+ {
+ /* Out of memory, do something about it */
+ entry_free( e );
+ *text = "out of memory";
+ return LDAP_OTHER;
+ }
+
+ *entry = e;
+ return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/schema/README b/servers/slapd/schema/README
new file mode 100644
index 0000000..9ae23ca
--- /dev/null
+++ b/servers/slapd/schema/README
@@ -0,0 +1,82 @@
+This directory contains user application schema definitions for use
+with slapd(8).
+
+File Description
+---- -----------
+collective.schema Collective attributes (experimental)
+corba.schema Corba Object
+core.schema OpenLDAP "core"
+cosine.schema COSINE Pilot
+dsee.schema Sun DSEE compatibility schema for replication
+duaconf.schema Client Configuration (work in progress)
+dyngroup.schema Dynamic Group (experimental)
+inetorgperson.schema InetOrgPerson
+java.schema Java Object
+misc.schema Miscellaneous Schema (experimental)
+msuser.schema Microsoft's Active Directory schema for replication
+namedobject.schema namedObject draft schema (work in progress)
+nis.schema Network Information Service (experimental)
+openldap.schema OpenLDAP Project (FYI)
+pmi.schema ITU X.509 PMI support (experimental)
+
+Additional "generally useful" schema definitions can be submitted
+using the OpenLDAP Issue Tracking System <http://www.openldap.org/its/>.
+Submissions should include a stable reference to a mature, open
+technical specification (e.g., an RFC) for the schema.
+
+The core.ldif and openldap.ldif files are equivalent to their
+corresponding .schema files. They have been provided as examples
+for use with the dynamic configuration backend. These example files
+are not actually necessary since slapd will automatically convert any
+included *.schema files into LDIF when converting a slapd.conf file
+to a configuration database, but they serve as a model of how to
+convert schema files in general.
+
+---
+
+This notice applies to all files in this directory.
+
+Copyright 1998-2022 The OpenLDAP Foundation, Redwood City, California, USA
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted only as authorized by the OpenLDAP
+Public License. A copy of this license is available at
+http://www.OpenLDAP.org/license.html or in file LICENSE in the
+top-level directory of the distribution.
+
+---
+
+This notice applies to all schema in this directory which are derived
+from RFCs and other IETF documents.
+
+Portions Copyright 1991-2004, The Internet Society. All Rights Reserved.
+
+This document and translations of it may be copied and furnished
+to others, and derivative works that comment on or otherwise explain
+it or assist in its implementation may be prepared, copied, published
+and distributed, in whole or in part, without restriction of any
+kind, provided that the above copyright notice and this paragraph
+are included on all such copies and derivative works. However,
+this document itself may not be modified in any way, such as by
+removing the copyright notice or references to the Internet Society
+or other Internet organizations, except as needed for the purpose
+of developing Internet standards in which case the procedures for
+copyrights defined in the Internet Standards process must be
+followed, or as required to translate it into languages other than
+English.
+
+The limited permissions granted above are perpetual and will not
+be revoked by the Internet Society or its successors or assigns.
+
+This document and the information contained herein is provided on
+an "AS IS" basis and THE AUTHORS, THE INTERNET SOCIETY, AND THE
+INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE
+OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY
+IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE.
+
+
+---
+$OpenLDAP$
diff --git a/servers/slapd/schema/collective.ldif b/servers/slapd/schema/collective.ldif
new file mode 100644
index 0000000..fa62326
--- /dev/null
+++ b/servers/slapd/schema/collective.ldif
@@ -0,0 +1,48 @@
+# collective.ldif -- Collective attribute schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+## Portions Copyright (C) The Internet Society (2003).
+## Please see full copyright statement below.
+#
+# From RFC 3671 [portions trimmed]:
+# Collective Attributes in LDAP
+#
+# This file was automatically generated from collective.schema; see that file
+# for complete references.
+#
+dn: cn=collective,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: collective
+olcAttributeTypes: {0}( 2.5.4.7.1 NAME 'c-l' SUP l COLLECTIVE )
+olcAttributeTypes: {1}( 2.5.4.8.1 NAME 'c-st' SUP st COLLECTIVE )
+olcAttributeTypes: {2}( 2.5.4.9.1 NAME 'c-street' SUP street COLLECTIVE )
+olcAttributeTypes: {3}( 2.5.4.10.1 NAME 'c-o' SUP o COLLECTIVE )
+olcAttributeTypes: {4}( 2.5.4.11.1 NAME 'c-ou' SUP ou COLLECTIVE )
+olcAttributeTypes: {5}( 2.5.4.16.1 NAME 'c-PostalAddress' SUP postalAddress CO
+ LLECTIVE )
+olcAttributeTypes: {6}( 2.5.4.17.1 NAME 'c-PostalCode' SUP postalCode COLLECTI
+ VE )
+olcAttributeTypes: {7}( 2.5.4.18.1 NAME 'c-PostOfficeBox' SUP postOfficeBox CO
+ LLECTIVE )
+olcAttributeTypes: {8}( 2.5.4.19.1 NAME 'c-PhysicalDeliveryOfficeName' SUP phy
+ sicalDeliveryOfficeName COLLECTIVE )
+olcAttributeTypes: {9}( 2.5.4.20.1 NAME 'c-TelephoneNumber' SUP telephoneNumbe
+ r COLLECTIVE )
+olcAttributeTypes: {10}( 2.5.4.21.1 NAME 'c-TelexNumber' SUP telexNumber COLLE
+ CTIVE )
+olcAttributeTypes: {11}( 2.5.4.23.1 NAME 'c-FacsimileTelephoneNumber' SUP facs
+ imileTelephoneNumber COLLECTIVE )
+olcAttributeTypes: {12}( 2.5.4.25.1 NAME 'c-InternationalISDNNumber' SUP inter
+ nationalISDNNumber COLLECTIVE )
diff --git a/servers/slapd/schema/corba.ldif b/servers/slapd/schema/corba.ldif
new file mode 100644
index 0000000..2933932
--- /dev/null
+++ b/servers/slapd/schema/corba.ldif
@@ -0,0 +1,42 @@
+# corba.ldif -- Corba Object Schema
+# depends upon core.ldif
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+## Portions Copyright (C) The Internet Society (1999).
+## Please see full copyright statement below.
+#
+# From RFC 2714 [portions trimmed]:
+# Schema for Representing CORBA Object References in an LDAP Directory
+#
+# This file was automatically generated from corba.schema; see that file
+# for complete references.
+#
+dn: cn=corba,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: corba
+olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.4.1.14 NAME 'corbaIor' DESC 'Strin
+ gified interoperable object reference of a CORBA object' EQUALITY caseIgnoreI
+ A5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.4.1.15 NAME 'corbaRepositoryId' DE
+ SC 'Repository ids of interfaces implemented by a CORBA object' EQUALITY case
+ ExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcObjectClasses: {0}( 1.3.6.1.4.1.42.2.27.4.2.10 NAME 'corbaContainer' DESC '
+ Container for a CORBA object' SUP top STRUCTURAL MUST cn )
+olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.4.2.9 NAME 'corbaObject' DESC 'CORB
+ A object representation' SUP top ABSTRACT MAY ( corbaRepositoryId $ descripti
+ on ) )
+olcObjectClasses: {2}( 1.3.6.1.4.1.42.2.27.4.2.11 NAME 'corbaObjectReference'
+ DESC 'CORBA interoperable object reference' SUP corbaObject AUXILIARY MUST co
+ rbaIor )
diff --git a/servers/slapd/schema/cosine.ldif b/servers/slapd/schema/cosine.ldif
new file mode 100644
index 0000000..311d9ce
--- /dev/null
+++ b/servers/slapd/schema/cosine.ldif
@@ -0,0 +1,200 @@
+# RFC1274: Cosine and Internet X.500 schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# RFC1274: Cosine and Internet X.500 schema
+#
+# This file contains LDAPv3 schema derived from X.500 COSINE "pilot"
+# schema. As this schema was defined for X.500(89), some
+# oddities were introduced in the mapping to LDAPv3. The
+# mappings were based upon: draft-ietf-asid-ldapv3-attributes-03.txt
+# (a work in progress)
+#
+# Note: It seems that the pilot schema evolved beyond what was
+# described in RFC1274. However, this document attempts to describes
+# RFC1274 as published.
+#
+# Depends on core.ldif
+#
+# This file was automatically generated from cosine.schema; see that
+# file for complete background.
+#
+dn: cn=cosine,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: cosine
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress'
+ EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.
+ 1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274: g
+ eneral information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteDri
+ nk' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR caseIgno
+ reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RFC1
+ 274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S
+ YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274:
+ photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC12
+ 74: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat
+ ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274: h
+ ost computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTA
+ X 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC127
+ 4: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115
+ .121.1.12 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' D
+ ESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUBSTR
+ caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC '
+ RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri
+ ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' DES
+ C 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu
+ bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DESC
+ 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNTAX 1
+ .3.6.1.4.1.1466.115.121.1.12 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' DE
+ SC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBSTR c
+ aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'homeTe
+ lephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephoneNumb
+ erMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121
+ .1.50 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'RFC
+ 1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.146
+ 6.115.121.1.12 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNTAX
+ 1.3.6.1.4.1.1466.115.121.1.39 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY ca
+ seIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY c
+ aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY c
+ aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY c
+ aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALITY
+ caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUALIT
+ Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DESC
+ 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedNameMatc
+ h SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' D
+ ESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR caseIg
+ noreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DESC
+ 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring
+ sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileTel
+ ephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephoneNum
+ berMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
+ 1.1.50 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTelep
+ honeNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNumber
+ Match SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1
+ .50 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCount
+ ryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch SUBS
+ TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' DE
+ SC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.14
+ 66.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus
+ ' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR caseI
+ gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC '
+ RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Subst
+ ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOption
+ ' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC '
+ RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstrin
+ gsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC 'RF
+ C1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality'
+ DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SIN
+ GLE-VALUE )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQualit
+ y' DESC 'RFC1274: Subtree Minimum Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
+ 13 SINGLE-VALUE )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQualit
+ y' DESC 'RFC1274: Subtree Maximum Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
+ 13 SINGLE-VALUE )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.53 NAME 'personalSignature' D
+ ESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.
+ 23 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC 'R
+ FC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466
+ .115.121.1.12 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC1274
+ : audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' D
+ ESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno
+ reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPilo
+ tPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rfc822
+ Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber $ hom
+ ePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod $ busine
+ ssCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $ pagerTelep
+ honeNumber $ organizationalStatus $ mailPreferenceOption $ personalSignature
+ ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCT
+ URAL MUST userid MAY ( description $ seeAlso $ localityName $ organizationNam
+ e $ organizationalUnitName $ host ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUC
+ TURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $ loca
+ lityName $ organizationName $ organizationalUnitName $ documentTitle $ docume
+ ntVersion $ documentAuthor $ documentLocation $ documentPublisher ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURA
+ L MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNumber
+ ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top
+ STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber $ l
+ ocalityName $ organizationName $ organizationalUnitName ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCT
+ URAL MUST domainComponent MAY ( associatedName $ organizationName $ descripti
+ on $ businessCategory $ seeAlso $ searchGuide $ userPassword $ localityName $
+ stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ postalAdd
+ ress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTelephoneNumber
+ $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIdentifier $ tel
+ exNumber $ preferredDeliveryMethod $ destinationIndicator $ registeredAddress
+ $ x121Address ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP d
+ omain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ telepho
+ neNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOffi
+ ceBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNNumber $
+ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferredDelivery
+ Method $ destinationIndicator $ registeredAddress $ x121Address ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domain
+ STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ CNAME
+ Record ) )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' D
+ ESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST associat
+ edDomain )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP c
+ ountry STRUCTURAL MUST friendlyCountryName )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization' SU
+ P ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa STR
+ UCTURAL MAY dSAQuality )
+olcObjectClasses: ( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData'
+ SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMaximu
+ mQuality ) )
diff --git a/servers/slapd/schema/dsee.ldif b/servers/slapd/schema/dsee.ldif
new file mode 100644
index 0000000..798fe51
--- /dev/null
+++ b/servers/slapd/schema/dsee.ldif
@@ -0,0 +1,113 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2019-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# This file is provided for informational purposes only.
+#
+# These definitions are from Sun DSEE 7's cn=schema subentry.
+# None of the attributes had matching rules defined; we've
+# inserted usable ones as needed.
+#
+# Some of these attributes are defined with NO-USER-MODIFICATION,
+# but slapd won't load such definitions from user-modifiable schema
+# files. So that designation has been removed, and commented accordingly.
+#
+dn: cn=dsee,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: dsee
+#
+olcObjectIdentifier: NetscapeRoot 2.16.840.1.113730
+olcObjectIdentifier: NetscapeDS NetscapeRoot:3
+olcObjectIdentifier: NSDSat NetscapeDS:1
+olcObjectIdentifier: NSDSoc NetscapeDS:2
+olcObjectIdentifier: SunRoot 1.3.6.1.4.1.42
+olcObjectIdentifier: SunDS SunRoot:2.27
+#
+olcAttributeTypes: ( NSDSat:5
+ NAME 'changeNumber'
+ DESC 'Changelog attribute type'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+olcAttributeTypes: ( NSDSat:6
+ NAME 'targetDn'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+olcAttributeTypes: ( NSDSat:7
+ NAME 'changeType'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+# They claim Binary syntax but it's really octetString
+olcAttributeTypes: ( NSDSat:8
+ NAME 'changes'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+olcAttributeTypes: ( NSDSat:9
+ NAME 'newRdn'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+olcAttributeTypes: ( NSDSat:10
+ NAME 'deleteOldRdn'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+olcAttributeTypes: ( NSDSat:11
+ NAME 'newSuperior'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+#
+# should be generalizedTime, but they used directoryString instead...
+olcAttributeTypes: ( NSDSat:77
+ NAME 'changeTime'
+ DESC 'Sun ONE defined attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ X-ORIGIN 'Sun ONE Directory Server' )
+#
+# These are UUIDs, but (of course) hyphenated differently than ours.
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( NSDSat:542
+ NAME 'nsUniqueId'
+ DESC 'Sun ONE defined attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'Sun ONE Directory Server' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( SunDS:9.1.596
+ NAME 'targetUniqueId'
+ DESC 'RetroChangelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'Sun Directory Server' )
+#
+olcObjectclasses: ( NSDSoc:1
+ NAME 'changeLogEntry'
+ DESC 'LDAP changelog objectclass'
+ SUP top STRUCTURAL
+ MUST ( targetDn $ changeTime $ changeNumber $ changeType )
+ MAY ( changes $ newRdn $ deleteOldRdn $ newSuperior )
+ X-ORIGIN 'Changelog Internet Draft' )
diff --git a/servers/slapd/schema/dsee.schema b/servers/slapd/schema/dsee.schema
new file mode 100644
index 0000000..18a9190
--- /dev/null
+++ b/servers/slapd/schema/dsee.schema
@@ -0,0 +1,109 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2019-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+# This file is provided for informational purposes only.
+
+# These definitions are from Sun DSEE 7's cn=schema subentry.
+# None of the attributes had matching rules defined; we've
+# inserted usable ones as needed.
+
+# Some of these attributes are defined with NO-USER-MODIFICATION,
+# but slapd won't load such definitions from user-modifiable schema
+# files. So that designation has been removed, and commented accordingly.
+
+objectidentifier NetscapeRoot 2.16.840.1.113730
+objectidentifier NetscapeDS NetscapeRoot:3
+objectidentifier NSDSat NetscapeDS:1
+objectidentifier NSDSoc NetscapeDS:2
+objectidentifier SunRoot 1.3.6.1.4.1.42
+objectidentifier SunDS SunRoot:2.27
+
+attributetype ( NSDSat:5
+ NAME 'changeNumber'
+ DESC 'Changelog attribute type'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ X-ORIGIN 'Changelog Internet Draft' )
+
+attributetype ( NSDSat:6
+ NAME 'targetDn'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+
+attributetype ( NSDSat:7
+ NAME 'changeType'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ X-ORIGIN 'Changelog Internet Draft' )
+
+# They claim Binary syntax but it's really octetString
+attributetype ( NSDSat:8
+ NAME 'changes'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5
+ X-ORIGIN 'Changelog Internet Draft' )
+
+attributetype ( NSDSat:9
+ NAME 'newRdn'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+
+attributetype ( NSDSat:10
+ NAME 'deleteOldRdn'
+ DESC 'Changelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ X-ORIGIN 'Changelog Internet Draft' )
+
+attributetype ( NSDSat:11
+ NAME 'newSuperior'
+ DESC 'Changelog attribute type'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ X-ORIGIN 'Changelog Internet Draft' )
+
+# should be generalizedTime, but they used directoryString instead...
+attributeType ( NSDSat:77
+ NAME 'changeTime'
+ DESC 'Sun ONE defined attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ X-ORIGIN 'Sun ONE Directory Server' )
+
+# These are UUIDs, but (of course) hyphenated differently than ours.
+# NO-USER-MODIFICATION
+attributetype ( NSDSat:542
+ NAME 'nsUniqueId'
+ DESC 'Sun ONE defined attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'Sun ONE Directory Server' )
+
+# NO-USER-MODIFICATION
+attributeype ( SunDS:9.1.596
+ NAME 'targetUniqueId'
+ DESC 'RetroChangelog attribute type'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'Sun Directory Server' )
+
+objectclass ( NSDSoc:1
+ NAME 'changeLogEntry'
+ DESC 'LDAP changelog objectclass'
+ SUP top STRUCTURAL
+ MUST ( targetDn $ changeTime $ changeNumber $ changeType )
+ MAY ( changes $ newRdn $ deleteOldRdn $ newSuperior )
+ X-ORIGIN 'Changelog Internet Draft' )
diff --git a/servers/slapd/schema/duaconf.ldif b/servers/slapd/schema/duaconf.ldif
new file mode 100644
index 0000000..2bfd5a0
--- /dev/null
+++ b/servers/slapd/schema/duaconf.ldif
@@ -0,0 +1,83 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# DUA schema from draft-joslin-config-schema (a work in progress)
+#
+# This file was automatically generated from duaconf.schema; see that file
+# for complete references.
+#
+dn: cn=duaconf,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: duaconf
+olcObjectIdentifier: {0}DUAConfSchemaOID 1.3.6.1.4.1.11.1.3.1
+olcAttributeTypes: {0}( DUAConfSchemaOID:1.0 NAME 'defaultServerList' DESC 'De
+ fault LDAP server host address used by a DUA' EQUALITY caseIgnoreMatch SYNTAX
+ 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: {1}( DUAConfSchemaOID:1.1 NAME 'defaultSearchBase' DESC 'De
+ fault LDAP base DN used by a DUA' EQUALITY distinguishedNameMatch SYNTAX 1.3.
+ 6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )
+olcAttributeTypes: {2}( DUAConfSchemaOID:1.2 NAME 'preferredServerList' DESC '
+ Preferred LDAP server host addresses to be used by a DUA' EQUALITY
+ caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: {3}( DUAConfSchemaOID:1.3 NAME 'searchTimeLimit' DESC 'Maxi
+ mum time in seconds a DUA should allow for a search to complete' E
+ QUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: {4}( DUAConfSchemaOID:1.4 NAME 'bindTimeLimit' DESC 'Maximu
+ m time in seconds a DUA should allow for the bind operation to com
+ plete' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALU
+ E )
+olcAttributeTypes: {5}( DUAConfSchemaOID:1.5 NAME 'followReferrals' DESC 'Tell
+ s DUA if it should follow referrals returned by a DSA search resul
+ t' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+olcAttributeTypes: {6}( DUAConfSchemaOID:1.16 NAME 'dereferenceAliases' DESC '
+ Tells DUA if it should dereference aliases' EQUALITY booleanMatch SYNTAX 1.3.
+ 6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+olcAttributeTypes: {7}( DUAConfSchemaOID:1.6 NAME 'authenticationMethod' DESC
+ 'A keystring which identifies the type of authentication method us
+ ed to contact the DSA' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.1
+ 21.1.15 SINGLE-VALUE )
+olcAttributeTypes: {8}( DUAConfSchemaOID:1.7 NAME 'profileTTL' DESC 'Time to l
+ ive, in seconds, before a client DUA should re-read this configura
+ tion profile' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SING
+ LE-VALUE )
+olcAttributeTypes: {9}( DUAConfSchemaOID:1.14 NAME 'serviceSearchDescriptor' D
+ ESC 'LDAP search descriptor list used by a DUA' EQUALITY caseExactMatch SYNTA
+ X 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: {10}( DUAConfSchemaOID:1.9 NAME 'attributeMap' DESC 'Attrib
+ ute mappings used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.14
+ 66.115.121.1.26 )
+olcAttributeTypes: {11}( DUAConfSchemaOID:1.10 NAME 'credentialLevel' DESC 'Id
+ entifies type of credentials a DUA should use when binding to the
+ LDAP server' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+olcAttributeTypes: {12}( DUAConfSchemaOID:1.11 NAME 'objectclassMap' DESC 'Obj
+ ectclass mappings used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4
+ .1.1466.115.121.1.26 )
+olcAttributeTypes: {13}( DUAConfSchemaOID:1.12 NAME 'defaultSearchScope' DESC
+ 'Default search scope used by a DUA' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6
+ .1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {14}( DUAConfSchemaOID:1.13 NAME 'serviceCredentialLevel' D
+ ESC 'Identifies type of credentials a DUA should use when binding
+ to the LDAP server for a specific service' EQUALITY caseIgnoreIA5M
+ atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {15}( DUAConfSchemaOID:1.15 NAME 'serviceAuthenticationMeth
+ od' DESC 'Authentication method used by a service of the DUA' EQUALITY caseIg
+ noreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcObjectClasses: {0}( DUAConfSchemaOID:2.5 NAME 'DUAConfigProfile' DESC 'Abst
+ raction of a base configuration for a DUA' SUP top STRUCTURAL MUST cn MAY ( d
+ efaultServerList $ preferredServerList $ defaultSearchBase $ defaultSearchSco
+ pe $ searchTimeLimit $ bindTimeLimit $ credentialLevel $ authenticationMethod
+ $ followReferrals $ dereferenceAliases $ serviceSearchDescriptor $ serviceCr
+ edentialLevel $ serviceAuthenticationMethod $ objectclassMap $ attributeMap $
+ profileTTL ) )
diff --git a/servers/slapd/schema/dyngroup.ldif b/servers/slapd/schema/dyngroup.ldif
new file mode 100644
index 0000000..7383091
--- /dev/null
+++ b/servers/slapd/schema/dyngroup.ldif
@@ -0,0 +1,74 @@
+# dyngroup.schema -- Dynamic Group schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Dynamic Group schema (experimental), as defined by Netscape. See
+# http://www.redhat.com/docs/manuals/ent-server/pdf/esadmin611.pdf
+# page 70 for details on how these groups were used.
+#
+# A description of the objectclass definition is available here:
+# http://www.redhat.com/docs/manuals/dir-server/schema/7.1/oc_dir.html#1303745
+#
+# depends upon:
+# core.schema
+#
+# These definitions are considered experimental due to the lack of
+# a formal specification (e.g., RFC).
+#
+# NOT RECOMMENDED FOR PRODUCTION USE! USE WITH CAUTION!
+#
+# The Netscape documentation describes this as an auxiliary objectclass
+# but their implementations have always defined it as a structural class.
+# The sloppiness here is because Netscape-derived servers don't actually
+# implement the X.500 data model, and they don't honor the distinction
+# between structural and auxiliary classes. This fact is noted here:
+# http://forum.java.sun.com/thread.jspa?threadID=5016864&messageID=9034636
+#
+# In accordance with other existing implementations, we define it as a
+# structural class.
+#
+# Our definition of memberURL also does not match theirs but again
+# their published definition and what works in practice do not agree.
+# In other words, the Netscape definitions are broken and interoperability
+# is not guaranteed.
+#
+# Also see the new DynGroup proposed spec at
+# http://tools.ietf.org/html/draft-haripriya-dynamicgroup-02
+dn: cn=dyngroup,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: dyngroup
+olcObjectIdentifier: {0}NetscapeRoot 2.16.840.1.113730
+olcObjectIdentifier: {1}NetscapeLDAP NetscapeRoot:3
+olcObjectIdentifier: {2}NetscapeLDAPattributeType NetscapeLDAP:1
+olcObjectIdentifier: {3}NetscapeLDAPobjectClass NetscapeLDAP:2
+olcObjectIdentifier: {4}OpenLDAPExp11 1.3.6.1.4.1.4203.666.11
+olcObjectIdentifier: {5}DynGroupBase OpenLDAPExp11:8
+olcObjectIdentifier: {6}DynGroupAttr DynGroupBase:1
+olcObjectIdentifier: {7}DynGroupOC DynGroupBase:2
+olcAttributeTypes: {0}( NetscapeLDAPattributeType:198 NAME 'memberURL' DESC 'I
+ dentifies an URL associated with each member of a group. Any type of labeled
+ URL can be used.' SUP labeledURI )
+olcAttributeTypes: {1}( DynGroupAttr:1 NAME 'dgIdentity' DESC 'Identity to use
+ when processing the memberURL' SUP distinguishedName SINGLE-VALUE )
+olcAttributeTypes: {2}( DynGroupAttr:2 NAME 'dgAuthz' DESC 'Optional authoriza
+ tion rules that determine who is allowed to assume the dgIdentity' EQUALITY a
+ uthzMatch SYNTAX 1.3.6.1.4.1.4203.666.2.7 X-ORDERED 'VALUES' )
+olcAttributeTypes: {3}( DynGroupAttr:3 NAME 'dgMemberOf' DESC 'Group that the
+ entry belongs to' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115
+ .121.1.12 )
+olcObjectClasses: {0}( NetscapeLDAPobjectClass:33 NAME 'groupOfURLs' SUP top S
+ TRUCTURAL MUST cn MAY ( memberURL $ businessCategory $ description $ o $ ou $
+ owner $ seeAlso ) )
+olcObjectClasses: {1}( DynGroupOC:1 NAME 'dgIdentityAux' SUP top AUXILIARY MAY
+ ( dgIdentity $ dgAuthz ) )
diff --git a/servers/slapd/schema/dyngroup.schema b/servers/slapd/schema/dyngroup.schema
new file mode 100644
index 0000000..f756c33
--- /dev/null
+++ b/servers/slapd/schema/dyngroup.schema
@@ -0,0 +1,98 @@
+# dyngroup.schema -- Dynamic Group schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Dynamic Group schema (experimental), as defined by Netscape. See
+# http://www.redhat.com/docs/manuals/ent-server/pdf/esadmin611.pdf
+# page 70 for details on how these groups were used.
+#
+# A description of the objectclass definition is available here:
+# http://www.redhat.com/docs/manuals/dir-server/schema/7.1/oc_dir.html#1303745
+#
+# depends upon:
+# core.schema
+#
+# These definitions are considered experimental due to the lack of
+# a formal specification (e.g., RFC).
+#
+# NOT RECOMMENDED FOR PRODUCTION USE! USE WITH CAUTION!
+#
+# The Netscape documentation describes this as an auxiliary objectclass
+# but their implementations have always defined it as a structural class.
+# The sloppiness here is because Netscape-derived servers don't actually
+# implement the X.500 data model, and they don't honor the distinction
+# between structural and auxiliary classes. This fact is noted here:
+# http://forum.java.sun.com/thread.jspa?threadID=5016864&messageID=9034636
+#
+# In accordance with other existing implementations, we define it as a
+# structural class.
+#
+# Our definition of memberURL also does not match theirs but again
+# their published definition and what works in practice do not agree.
+# In other words, the Netscape definitions are broken and interoperability
+# is not guaranteed.
+#
+# Also see the new DynGroup proposed spec at
+# http://tools.ietf.org/html/draft-haripriya-dynamicgroup-02
+
+objectIdentifier NetscapeRoot 2.16.840.1.113730
+
+objectIdentifier NetscapeLDAP NetscapeRoot:3
+objectIdentifier NetscapeLDAPattributeType NetscapeLDAP:1
+objectIdentifier NetscapeLDAPobjectClass NetscapeLDAP:2
+
+objectIdentifier OpenLDAPExp11 1.3.6.1.4.1.4203.666.11
+objectIdentifier DynGroupBase OpenLDAPExp11:8
+objectIdentifier DynGroupAttr DynGroupBase:1
+objectIdentifier DynGroupOC DynGroupBase:2
+
+attributetype ( NetscapeLDAPattributeType:198
+ NAME 'memberURL'
+ DESC 'Identifies an URL associated with each member of a group. Any type of labeled URL can be used.'
+ SUP labeledURI )
+
+attributetype ( DynGroupAttr:1
+ NAME 'dgIdentity'
+ DESC 'Identity to use when processing the memberURL'
+ SUP distinguishedName SINGLE-VALUE )
+
+attributeType ( DynGroupAttr:2
+ NAME 'dgAuthz'
+ DESC 'Optional authorization rules that determine who is allowed to assume the dgIdentity'
+ EQUALITY authzMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.2.7
+ X-ORDERED 'VALUES' )
+
+# Just for testing. The actual memberOf is an operational attribute
+attributeType ( DynGroupAttr:3
+ NAME 'dgMemberOf'
+ DESC 'Group that the entry belongs to'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ EQUALITY distinguishedNameMatch )
+
+objectClass ( NetscapeLDAPobjectClass:33
+ NAME 'groupOfURLs'
+ SUP top STRUCTURAL
+ MUST cn
+ MAY ( memberURL $ businessCategory $ description $ o $ ou $
+ owner $ seeAlso ) )
+
+# The Haripriya dyngroup schema still needs a lot of work.
+# We're just adding support for the dgIdentity attribute for now...
+objectClass ( DynGroupOC:1
+ NAME 'dgIdentityAux'
+ SUP top AUXILIARY
+ MAY ( dgIdentity $ dgAuthz ) )
+
+
diff --git a/servers/slapd/schema/inetorgperson.ldif b/servers/slapd/schema/inetorgperson.ldif
new file mode 100644
index 0000000..780f848
--- /dev/null
+++ b/servers/slapd/schema/inetorgperson.ldif
@@ -0,0 +1,69 @@
+# InetOrgPerson (RFC2798)
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# InetOrgPerson (RFC2798)
+#
+# Depends upon
+# Definition of an X.500 Attribute Type and an Object Class to Hold
+# Uniform Resource Identifiers (URIs) [RFC2079]
+# (core.ldif)
+#
+# A Summary of the X.500(96) User Schema for use with LDAPv3 [RFC2256]
+# (core.ldif)
+#
+# The COSINE and Internet X.500 Schema [RFC1274] (cosine.ldif)
+#
+# This file was automatically generated from inetorgperson.schema; see
+# that file for complete references.
+#
+dn: cn=inetorgperson,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: inetorgperson
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC279
+ 8: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR cas
+ eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC '
+ RFC2798: identifies a department within an organization' EQUALITY caseIgnoreM
+ atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'RFC
+ 2798: preferred name to be used when displaying entries' EQUALITY caseIgnoreM
+ atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SI
+ NGLE-VALUE )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'RF
+ C2798: numerically identifies an employee within an organization' EQUALITY ca
+ seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12
+ 1.1.15 SINGLE-VALUE )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RFC2
+ 798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgn
+ oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: ( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RFC2
+ 798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC
+ 'RFC2798: preferred written or spoken language for a person' EQUALITY caseIg
+ noreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.
+ 15 SINGLE-VALUE )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' D
+ ESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.1.14
+ 66.115.121.1.5 )
+olcAttributeTypes: ( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RFC2
+ 798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.1466.1
+ 15.121.1.5 )
+olcObjectClasses: ( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RFC2
+ 798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY
+ ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ em
+ ployeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ ini
+ tials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo
+ $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ pre
+ ferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
diff --git a/servers/slapd/schema/java.ldif b/servers/slapd/schema/java.ldif
new file mode 100644
index 0000000..74f202e
--- /dev/null
+++ b/servers/slapd/schema/java.ldif
@@ -0,0 +1,59 @@
+# java.ldif -- Java Object Schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Java Object Schema (defined in RFC 2713)
+# depends upon core.ldif
+#
+# This file was automatically generated from java.schema; see that file
+# for complete references.
+#
+dn: cn=java,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: java
+olcAttributeTypes: {0}( 1.3.6.1.4.1.42.2.27.4.1.6 NAME 'javaClassName' DESC 'F
+ ully qualified name of distinguished Java class or interface' EQUALITY caseEx
+ actMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: {1}( 1.3.6.1.4.1.42.2.27.4.1.7 NAME 'javaCodebase' DESC 'UR
+ L(s) specifying the location of class definition' EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {2}( 1.3.6.1.4.1.42.2.27.4.1.13 NAME 'javaClassNames' DESC
+ 'Fully qualified Java class or interface name' EQUALITY caseExactMatch SYNTAX
+ 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.4.1.8 NAME 'javaSerializedData' DE
+ SC 'Serialized form of a Java object' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SI
+ NGLE-VALUE )
+olcAttributeTypes: {4}( 1.3.6.1.4.1.42.2.27.4.1.10 NAME 'javaFactory' DESC 'Fu
+ lly qualified Java class name of a JNDI object factory' EQUALITY caseExactMat
+ ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
+olcAttributeTypes: {5}( 1.3.6.1.4.1.42.2.27.4.1.11 NAME 'javaReferenceAddress'
+ DESC 'Addresses associated with a JNDI Reference' EQUALITY caseExactMatch SY
+ NTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+olcAttributeTypes: {6}( 1.3.6.1.4.1.42.2.27.4.1.12 NAME 'javaDoc' DESC 'The Ja
+ va documentation for the class' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1
+ .1466.115.121.1.26 )
+olcObjectClasses: {0}( 1.3.6.1.4.1.42.2.27.4.2.1 NAME 'javaContainer' DESC 'Co
+ ntainer for a Java object' SUP top STRUCTURAL MUST cn )
+olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.4.2.4 NAME 'javaObject' DESC 'Java
+ object representation' SUP top ABSTRACT MUST javaClassName MAY ( javaClassNam
+ es $ javaCodebase $ javaDoc $ description ) )
+olcObjectClasses: {2}( 1.3.6.1.4.1.42.2.27.4.2.5 NAME 'javaSerializedObject' D
+ ESC 'Java serialized object' SUP javaObject AUXILIARY MUST javaSerializedData
+ )
+olcObjectClasses: {3}( 1.3.6.1.4.1.42.2.27.4.2.8 NAME 'javaMarshalledObject' D
+ ESC 'Java marshalled object' SUP javaObject AUXILIARY MUST javaSerializedData
+ )
+olcObjectClasses: {4}( 1.3.6.1.4.1.42.2.27.4.2.7 NAME 'javaNamingReference' DE
+ SC 'JNDI reference' SUP javaObject AUXILIARY MAY ( javaReferenceAddress $ jav
+ aFactory ) )
diff --git a/servers/slapd/schema/misc.ldif b/servers/slapd/schema/misc.ldif
new file mode 100644
index 0000000..a77a7e3
--- /dev/null
+++ b/servers/slapd/schema/misc.ldif
@@ -0,0 +1,45 @@
+# misc.ldif -- assorted schema definitions
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Assorted definitions from several sources, including
+# ''works in progress''. Contents of this file are
+# subject to change (including deletion) without notice.
+#
+# Not recommended for production use!
+# Use with extreme caution!
+#
+# This file was automatically generated from misc.schema; see that file
+# for complete references.
+#
+dn: cn=misc,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: misc
+olcAttributeTypes: {0}( 2.16.840.1.113730.3.1.13 NAME 'mailLocalAddress' DESC
+ 'RFC822 email address of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1
+ .3.6.1.4.1.1466.115.121.1.26{256} )
+olcAttributeTypes: {1}( 2.16.840.1.113730.3.1.18 NAME 'mailHost' DESC 'FQDN of
+ the SMTP/MTA of this recipient' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4
+ .1.1466.115.121.1.26{256} SINGLE-VALUE )
+olcAttributeTypes: {2}( 2.16.840.1.113730.3.1.47 NAME 'mailRoutingAddress' DES
+ C 'RFC822 routing address of this recipient' EQUALITY caseIgnoreIA5Match SYNT
+ AX 1.3.6.1.4.1.1466.115.121.1.26{256} SINGLE-VALUE )
+olcAttributeTypes: {3}( 1.3.6.1.4.1.42.2.27.2.1.15 NAME 'rfc822MailMember' DES
+ C 'rfc822 mail address of group member(s)' EQUALITY caseIgnoreIA5Match SYNTAX
+ 1.3.6.1.4.1.1466.115.121.1.26 )
+olcObjectClasses: {0}( 2.16.840.1.113730.3.2.147 NAME 'inetLocalMailRecipient'
+ DESC 'Internet local mail recipient' SUP top AUXILIARY MAY ( mailLocalAddres
+ s $ mailHost $ mailRoutingAddress ) )
+olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.1.2.5 NAME 'nisMailAlias' DESC 'NIS
+ mail alias' SUP top STRUCTURAL MUST cn MAY rfc822MailMember )
diff --git a/servers/slapd/schema/misc.schema b/servers/slapd/schema/misc.schema
new file mode 100644
index 0000000..150abbc
--- /dev/null
+++ b/servers/slapd/schema/misc.schema
@@ -0,0 +1,75 @@
+# misc.schema -- assorted schema definitions
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Assorted definitions from several sources, including
+# ''works in progress''. Contents of this file are
+# subject to change (including deletion) without notice.
+#
+# Not recommended for production use!
+# Use with extreme caution!
+
+#-----------------------------------------------------------
+# draft-lachman-laser-ldap-mail-routing-02.txt !!!EXPIRED!!!
+# (a work in progress)
+#
+attributetype ( 2.16.840.1.113730.3.1.13
+ NAME 'mailLocalAddress'
+ DESC 'RFC822 email address of this recipient'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+
+attributetype ( 2.16.840.1.113730.3.1.18
+ NAME 'mailHost'
+ DESC 'FQDN of the SMTP/MTA of this recipient'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+attributetype ( 2.16.840.1.113730.3.1.47
+ NAME 'mailRoutingAddress'
+ DESC 'RFC822 routing address of this recipient'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
+ SINGLE-VALUE )
+
+# I-D leaves this OID TBD.
+# iPlanet uses 2.16.840.1.113.730.3.2.147 but that is an
+# improperly delegated OID. A typo is likely.
+objectclass ( 2.16.840.1.113730.3.2.147
+ NAME 'inetLocalMailRecipient'
+ DESC 'Internet local mail recipient'
+ SUP top AUXILIARY
+ MAY ( mailLocalAddress $ mailHost $ mailRoutingAddress ) )
+
+#-----------------------------------------------------------
+# draft-srivastava-ldap-mail-00.txt !!!EXPIRED!!!
+# (a work in progress)
+#
+attributetype ( 1.3.6.1.4.1.42.2.27.2.1.15
+ NAME 'rfc822MailMember'
+ DESC 'rfc822 mail address of group member(s)'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+#-----------------------------------------------------------
+# !!!no I-D!!!
+# (a work in progress)
+#
+objectclass ( 1.3.6.1.4.1.42.2.27.1.2.5
+ NAME 'nisMailAlias'
+ DESC 'NIS mail alias'
+ SUP top STRUCTURAL
+ MUST cn
+ MAY rfc822MailMember )
diff --git a/servers/slapd/schema/msuser.ldif b/servers/slapd/schema/msuser.ldif
new file mode 100644
index 0000000..9211c91
--- /dev/null
+++ b/servers/slapd/schema/msuser.ldif
@@ -0,0 +1,4299 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2018-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# This file is provided for informational purposes only.
+#
+# These definitions are from Microsoft's Active Directory.
+# They were first gathered using the subschemaSubentry object of
+# Windows 2000 Advanced Server Release Candidate 2.
+# Additional definitions were gathered from Windows Server 2012
+# and adjusted to ease compatibility issues.
+#
+# These object classes and attributes are rooted at OID
+# 1.2.840.113556.1, Microsoft's Directory Enabled networks
+# OID. OID Macros are used throughout, to make mapping of
+# incompatible OIDs easier.
+#
+# A number of custom syntaxes have been replaced with generic
+# syntaxes. The original syntax OIDs are commented out below.
+#
+# Many of these attributes are defined with NO-USER-MODIFICATION,
+# but slapd won't load such definitions from user-modifiable schema
+# files. So that designation has been removed, and commented accordingly.
+#
+# Only the subset of Windows 2012 attributes needed to make the
+# user and group objectclasses work has been added to the previously
+# retrieved definitions.
+#
+# If an AD definition clashes with existing standardized schema,
+# the AD definition has been commented out.
+#
+dn: cn=msuser,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: msuser
+#
+olcObjectIdentifier: MicrosoftRoot 1.2.840.113556
+olcObjectIdentifier: MSAD MicrosoftRoot:1
+olcObjectIdentifier: MSADat2 MSAD:2
+olcObjectIdentifier: MSADat4 MSAD:4
+olcObjectIdentifier: MSADat6 MSAD:6
+olcObjectIdentifier: MSADoc3 MSAD:3
+olcObjectIdentifier: MSADoc5 MSAD:5
+olcObjectIdentifier: MSADdnWithOctetString 1.3.6.1.4.1.1466.115.121.1.40
+olcObjectIdentifier: MSADdnWithString 1.3.6.1.4.1.1466.115.121.1.40
+olcObjectIdentifier: MSADtelex 1.3.6.1.4.1.1466.115.121.1.44
+olcObjectIdentifier: MSADlargeInteger 1.3.6.1.4.1.1466.115.121.1.27
+olcObjectIdentifier: MSADcis 1.3.6.1.4.1.1466.115.121.1.44
+olcObjectIdentifier: MSADsecDesc 1.3.6.1.4.1.1466.115.121.1.40
+# original OIDs commented out below
+#olcObjectIdentifier: MSADdnWithOctetString MSADat4:903
+#olcObjectIdentifier: MSADdnWithString MSADat4:904
+#olcObjectIdentifier: MSADtelex MSADat4:905
+#olcObjectIdentifier: MSADlargeInteger MSADat4:906
+#olcObjectIdentifier: MSADcis MSADat4:907
+#olcObjectIdentifier: MSADsecDesc MSADat4:907
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:104
+ NAME 'ownerBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1424
+ NAME 'msCOM-PartitionSetLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1425
+ NAME 'msCOM-UserLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1669
+ NAME 'msDS-Approx-Immed-Subordinates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1704
+ NAME 'msDS-NCReplCursors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1705
+ NAME 'msDS-NCReplInboundNeighbors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1706
+ NAME 'msDS-NCReplOutboundNeighbors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1707
+ NAME 'msDS-ReplAttributeMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1708
+ NAME 'msDS-ReplValueMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1793
+ NAME 'msDS-NonMembers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1794
+ NAME 'msDS-NonMembersBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1806
+ NAME 'msDS-MembersForAzRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1807
+ NAME 'msDS-MembersForAzRoleBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1808 NAME 'msDS-OperationsForAzTask' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1809 NAME 'msDS-OperationsForAzTaskBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1810 NAME 'msDS-TasksForAzTask' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1811 NAME 'msDS-TasksForAzTaskBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1812 NAME 'msDS-OperationsForAzRole' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1813 NAME 'msDS-OperationsForAzRoleBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1814 NAME 'msDS-TasksForAzRole' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1815 NAME 'msDS-TasksForAzRoleBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1837 NAME 'msDs-masteredBy' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1840 NAME 'msDS-ObjectReference' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1841 NAME 'msDS-ObjectReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1865 NAME 'msDS-PrincipalName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1930 NAME 'msDS-RevealedDSAs' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1931 NAME 'msDS-KrbTgtLinkBl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1932 NAME 'msDS-IsFullReplicaFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1933 NAME 'msDS-IsDomainFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1934 NAME 'msDS-IsPartialReplicaFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1957 NAME 'msDS-AuthenticatedToAccountlist' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1958 NAME 'msDS-AuthenticatedAtDC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1975 NAME 'msDS-RevealedListBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1968 NAME 'msDS-NC-RO-Replica-Locations-BL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2021 NAME 'msDS-PSOApplied' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2024 NAME 'msDS-NcType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2052 NAME 'msDS-OIDToGroupLinkBl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2057 NAME 'msDS-HostServiceAccountBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2058 NAME 'isRecycled' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2059 NAME 'msDS-LocalEffectiveDeletionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2060 NAME 'msDS-LocalEffectiveRecycleTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2067 NAME 'msDS-LastKnownRDN' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2069 NAME 'msDS-EnabledFeatureBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2102 NAME 'msDS-ClaimSharesPossibleValuesWithBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2104 NAME 'msDS-MembersOfResourcePropertyListBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2168 NAME 'msDS-IsPrimaryComputerFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2188 NAME 'msDS-ValueTypeReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2193 NAME 'msDS-TDOIngressBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2194 NAME 'msDS-TDOEgressBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2203 NAME 'msDS-parentdistname' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2235 NAME 'msDS-ReplValueMetaDataExt' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2236 NAME 'msds-memberOfTransitive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2238 NAME 'msds-memberTransitive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat6:18.1.347 NAME 'msSFU30PosixMemberOf' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat6:13.3.102 NAME 'msDFSR-MemberReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat6:13.3.103 NAME 'msDFSR-ComputerReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+#
+olcAttributeTypes: ( MSADat4:1792 NAME 'msDS-AzLDAPQuery' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1801 NAME 'msDS-AzBizRule' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1802 NAME 'msDS-AzBizRuleLanguage' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1803 NAME 'msDS-AzLastImportedBizRulePath' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1819 NAME 'msDS-AzApplicationData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1949 NAME 'msDS-AzObjectGuid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1950 NAME 'msDS-AzGenericData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2167 NAME 'msDS-PrimaryComputer' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat6:18.1.309 NAME 'msSFU30Name' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat6:18.1.339 NAME 'msSFU30NisDomain' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat6:18.1.346 NAME 'msSFU30PosixMember' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1426 NAME 'msCOM-UserPartitionSetLink' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1441 NAME 'msDS-Cached-Membership' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1442 NAME 'msDS-Cached-Membership-Time-Stamp' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1443 NAME 'msDS-Site-Affinity' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+olcAttributeTypes: ( MSADat4:1460 NAME 'msDS-User-Account-Control-Computed' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1696 NAME 'lastLogonTimestamp' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1785 NAME 'msIIS-FTPRoot' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1786 NAME 'msIIS-FTPDir' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1843 NAME 'msDRM-IdentityCertificate' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+olcAttributeTypes: ( MSADat4:1879 NAME 'msDS-SourceObjectDN' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1892 NAME 'msPKIRoamingTimeStamp' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1893 NAME 'msPKIDPAPIMasterKeys' SYNTAX 'MSADdnWithOctetString' )
+olcAttributeTypes: ( MSADat4:1894 NAME 'msPKIAccountCredentials' SYNTAX 'MSADdnWithOctetString' )
+olcAttributeTypes: ( MSADat4:1913 NAME 'msRADIUS-FramedInterfaceId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1914 NAME 'msRADIUS-SavedFramedInterfaceId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1915 NAME 'msRADIUS-FramedIpv6Prefix' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1916 NAME 'msRADIUS-SavedFramedIpv6Prefix' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1917 NAME 'msRADIUS-FramedIpv6Route' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+olcAttributeTypes: ( MSADat4:1918 NAME 'msRADIUS-SavedFramedIpv6Route' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1929 NAME 'msDS-SecondaryKrbTgtNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1963 NAME 'msDS-SupportedEncryptionTypes' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1970 NAME 'msDS-LastSuccessfulInteractiveLogonTime' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1971 NAME 'msDS-LastFailedInteractiveLogonTime' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1972 NAME 'msDS-FailedInteractiveLogonCount' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1973 NAME 'msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1976 NAME 'msTSProfilePath' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1977 NAME 'msTSHomeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1978 NAME 'msTSHomeDrive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1979 NAME 'msTSAllowLogon' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1980 NAME 'msTSRemoteControl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1981 NAME 'msTSMaxDisconnectionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1982 NAME 'msTSMaxConnectionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1983 NAME 'msTSMaxIdleTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1984 NAME 'msTSReconnectionAction' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1985 NAME 'msTSBrokenConnectionAction' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1986 NAME 'msTSConnectClientDrives' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1987 NAME 'msTSConnectPrinterDrives' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1988 NAME 'msTSDefaultToMainPrinter' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1989 NAME 'msTSWorkDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1990 NAME 'msTSInitialProgram' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1991 NAME 'msTSProperty01' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: ( MSADat4:1992 NAME 'msTSProperty02' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: ( MSADat4:1993 NAME 'msTSExpireDate' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1994 NAME 'msTSLicenseVersion' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1995 NAME 'msTSManagingLS' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1996 NAME 'msDS-UserPasswordExpiryTimeComputed' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2008 NAME 'msTSManagingLS4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2005 NAME 'msTSManagingLS3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2002 NAME 'msTSManagingLS2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2006 NAME 'msTSExpireDate4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2003 NAME 'msTSExpireDate3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2000 NAME 'msTSExpireDate2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2004 NAME 'msTSLicenseVersion3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2001 NAME 'msTSLicenseVersion2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2007 NAME 'msTSLicenseVersion4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2009 NAME 'msTSLSProperty01' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: ( MSADat4:2010 NAME 'msTSLSProperty02' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2022 NAME 'msDS-ResultantPSO' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2050 NAME 'msPKI-CredentialRoamingTokens' SYNTAX 'MSADdnWithOctetString' )
+olcAttributeTypes: ( MSADat4:2073 NAME 'msTSPrimaryDesktop' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2075 NAME 'msTSSecondaryDesktops' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2276 NAME 'msDS-SyncServerUrl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: ( MSADat4:2285 NAME 'msDS-AssignedAuthNPolicySilo' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2288 NAME 'msDS-AuthNPolicySiloMembersBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2295 NAME 'msDS-AssignedAuthNPolicy' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+#
+## NO-USER-MODIFICATION
+#olcAttributeTypes: ( MSADat4:1459 NAME 'msDS-Behavior-Version' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#olcAttributeTypes: ( MSADat4:1788 NAME 'msDS-PerUserTrustQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#olcAttributeTypes: ( MSADat4:1789 NAME 'msDS-AllUsersTrustQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#olcAttributeTypes: ( MSADat4:1790 NAME 'msDS-PerUserTrustTombstonesQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1717 NAME 'msDS-AdditionalDnsHostName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+## NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1718 NAME 'msDS-AdditionalSamAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1783 NAME 'msDS-ExecuteScriptPassword' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1923 NAME 'msDS-KrbTgtLink' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1924 NAME 'msDS-RevealedUsers' SYNTAX 'MSADdnWithOctetString' )
+olcAttributeTypes: ( MSADat4:1926 NAME 'msDS-NeverRevealGroup' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:1928 NAME 'msDS-RevealOnDemandGroup' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1940 NAME 'msDS-RevealedList' SYNTAX 'MSADdnWithString' )
+olcAttributeTypes: ( MSADat4:1959 NAME 'msDS-isGC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1960 NAME 'msDS-isRODC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1961 NAME 'msDS-SiteName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1962 NAME 'msDS-PromotionSettings' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:1966 NAME 'msTPM-OwnerInformation' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2025 NAME 'msDS-IsUserCachableAtRodc' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2056 NAME 'msDS-HostServiceAccount' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2070 NAME 'msTSEndpointData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2071 NAME 'msTSEndpointType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2072 NAME 'msTSEndpointPlugin' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2074 NAME 'msTSPrimaryDesktopBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2078 NAME 'msTSSecondaryDesktopBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+olcAttributeTypes: ( MSADat4:2109 NAME 'msTPM-TpmInformationForComputer' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2166 NAME 'msDS-GenerationId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2180 NAME 'msImaging-ThumbprintHash' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2181 NAME 'msImaging-HashAlgorithm' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat4:2234 NAME 'netbootDUID' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+olcAttributeTypes: ( MSADat6:18.1.323 NAME 'msSFU30Aliases' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+#
+#
+olcAttributeTypes: ( MSADat4:856
+ NAME 'netbootNewMachineOU'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:13
+ NAME 'builtinCreationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1335
+ NAME 'pKIEnrollmentAccess'
+ SYNTAX 'MSADcis' )
+#
+olcAttributeTypes: ( MSADat4:1333
+ NAME 'pKIExtendedKeyUsage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1123
+ NAME 'msNPCalledStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#
+olcAttributeTypes: ( MSADat4:539
+ NAME 'initialAuthIncoming'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:370
+ NAME 'objectClassCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:41
+ NAME 'generatedConnection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:911
+ NAME 'allowedChildClasses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:68
+ NAME 'machineArchitecture'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:767
+ NAME 'aCSMaxPeakBandwidth'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:72
+ NAME 'marshalledInterface'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:368
+ NAME 'rIDManagerReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:770
+ NAME 'aCSEnableACSService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1237
+ NAME 'mSMQRoutingService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1405
+ NAME 'mS-SQL-AllowQueuedUpdatingSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:648
+ NAME 'primaryTelexNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:8
+ NAME 'userAccountControl'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:563
+ NAME 'shellPropertyPages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:4
+ NAME 'replUpToDateVector'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:484
+ NAME 'fRSDirectoryFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:230
+ NAME 'printSeparatorFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1329
+ NAME 'pKIMaxIssuingDepth'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1307
+ NAME 'accountNameHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1386
+ NAME 'mS-SQL-GPSLongitude'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:562
+ NAME 'adminPropertyPages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:121
+ NAME 'securityIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:166
+ NAME 'groupMembershipSAM'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:659
+ NAME 'serviceDNSNameType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:585
+ NAME 'meetingIsEncrypted'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1400
+ NAME 'mS-SQL-Applications'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:330
+ NAME 'lastUpdateSequence'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:50
+ NAME 'lastContentIndexed'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:567
+ NAME 'meetingDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat4:880
+# NAME 'fRSTimeLastCommand'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:83
+ NAME 'monikerDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:321
+ NAME 'requiredCategories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:813
+ NAME 'upgradeProductCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:774
+ NAME 'aCSMaxNoOfLogFiles'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1370
+ NAME 'mS-SQL-CharacterSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:578
+ NAME 'meetingContactInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1397
+ NAME 'mS-SQL-CreationDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:32
+ NAME 'domainPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:703
+ NAME 'dhcpObjDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:573
+ NAME 'meetingApplication'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:518
+ NAME 'defaultHidingValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:875
+ NAME 'fRSMemberReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:701
+ NAME 'dhcpIdentification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:135
+ NAME 'trustAuthOutgoing'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:197
+ NAME 'systemMustContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1412
+ NAME 'primaryGroupToken'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:118
+ NAME 'rpcNsProfileEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:129
+ NAME 'trustAuthIncoming'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1225
+ NAME 'mSMQPrevSiteGates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:607
+ NAME 'queryPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:712
+ NAME 'optionDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1314
+ NAME 'aCSMaximumSDUSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:530
+ NAME 'nonSecurityMember'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:31
+ NAME 'fRSReplicaSetType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:763
+ NAME 'aCSTotalNoOfFlows'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:915
+ NAME 'possibleInferiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:851
+ NAME 'netbootMaxClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1385
+ NAME 'mS-SQL-GPSLatitude'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:765
+ NAME 'aCSPermissionBits'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:926
+ NAME 'mSMQTransactional'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1390
+ NAME 'mS-SQL-Description'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:913
+ NAME 'allowedAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:491
+ NAME 'fRSFaultCondition'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:54
+ NAME 'tombstoneLifetime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:809
+ NAME 'remoteStorageGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:644
+ NAME 'showInAddressBook'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:213
+ NAME 'defaultClassStore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:577
+ NAME 'meetingOriginator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:656
+ NAME 'userPrincipalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1316
+ NAME 'aCSMinimumLatency'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat2:617
+# NAME 'homePostalAddress'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:638
+ NAME 'isPrivilegeHolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:533
+ NAME 'fRSReplicaSetGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:371
+ NAME 'rIDAllocationPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1327
+ NAME 'pKIDefaultKeySpec'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:537
+ NAME 'dynamicLDAPServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:516
+ NAME 'serverReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:500
+ NAME 'fRSServiceCommand'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1304
+ NAME 'sDRightsEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1249
+ NAME 'proxiedObjectName'
+ SYNTAX 'MSADdnWithOctetString'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:586
+ NAME 'meetingRecurrence'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:251
+ NAME 'cOMTreatAsClassId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1245
+ NAME 'globalAddressList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:908
+ NAME 'extendedClassInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:459
+ NAME 'machineWidePolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:356
+ NAME 'foreignIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1242
+ NAME 'dNReferenceUpdate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:134
+ NAME 'trustPosixOffset'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:36
+ NAME 'enabledConnection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:627
+ NAME 'ipsecNFAReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:86
+ NAME 'userWorkstations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:301
+ NAME 'garbageCollPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:933
+ NAME 'mSMQComputerType'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:65
+ NAME 'logonWorkstation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:921
+ NAME 'mSMQJournalQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:108
+ NAME 'remoteSourceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:95
+ NAME 'pwdHistoryLength'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:920
+ NAME 'mSMQBasePriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:196
+ NAME 'systemMayContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:1407
+ NAME 'mS-SQL-ThirdParty'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1243
+ NAME 'mSMQQueueNameExt'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:485
+ NAME 'fRSUpdateTimeout'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:924
+ NAME 'mSMQPrivacyLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:615
+ NAME 'shellContextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:618
+ NAME 'wellKnownObjects'
+ SYNTAX 'MSADdnWithOctetString' )
+#
+olcAttributeTypes: ( MSADat4:789
+ NAME 'transportDLLName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:458
+ NAME 'qualityOfService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:73
+ NAME 'lockoutThreshold'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:105
+ NAME 'remoteServerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:694
+ NAME 'previousParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1345
+ NAME 'dSUIShellMaximum'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:303
+ NAME 'notificationList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1244
+ NAME 'addressBookRoots'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:878
+ NAME 'fRSPrimaryMember'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat4:587
+# NAME 'meetingStartTime'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53' )
+#
+olcAttributeTypes: ( MSADat4:1310
+ NAME 'mSMQSiteGatesMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:709
+ NAME 'dhcpReservations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:614
+ NAME 'adminContextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1332
+ NAME 'pKIOverlapPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:142
+ NAME 'winsockAddresses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:923
+ NAME 'mSMQAuthenticate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1344
+ NAME 'dSUIAdminMaximum'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:848
+ NAME 'appSchemaVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:123
+ NAME 'serviceClassInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:769
+ NAME 'aCSEventLogLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:751
+ NAME 'userSharedFolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:421
+ NAME 'domainWidePolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:669
+ NAME 'rIDSetReferences'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:815
+ NAME 'canUpgradeScript'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:610
+ NAME 'classDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat2:226
+ NAME 'adminDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:67
+ NAME 'lSAModifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:509
+ NAME 'serviceClassName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:56
+ NAME 'localPolicyFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:115
+ NAME 'rpcNsInterfaceID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:194
+ NAME 'adminDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:753
+ NAME 'nameServiceFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:589
+ NAME 'meetingBandwidth'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:755
+ NAME 'domainIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:370
+ NAME 'rIDAvailablePool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:655
+ NAME 'legacyExchangeDN'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:470
+ NAME 'trustAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:535
+ NAME 'fRSRootSecurity'
+ SYNTAX 'MSADcis'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:532
+ NAME 'superiorDNSRoot'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:278
+ NAME 'printMaxYExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:277
+ NAME 'printMaxXExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:280
+ NAME 'printMinYExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:279
+ NAME 'printMinXExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:32
+ NAME 'attributeSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:247
+ NAME 'printAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:152
+ NAME 'groupAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:816
+ NAME 'fileExtPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:930
+ NAME 'mSMQServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:363
+ NAME 'operatingSystem'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1371
+ NAME 'mS-SQL-SortOrder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:329
+ NAME 'versionNumberLo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:884
+ NAME 'msRRASAttribute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:781
+ NAME 'lastKnownParent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1209
+ NAME 'shortServerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:60
+ NAME 'lockoutDuration'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:232
+ NAME 'defaultPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:754
+ NAME 'rpcNsEntryFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:713
+ NAME 'optionsLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:328
+ NAME 'versionNumberHi'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:366
+ NAME 'rpcNsAnnotation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:886
+ NAME 'purportedSearch'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:776
+ NAME 'aCSDSBMPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:961
+ NAME 'mSMQSiteForeign'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' )
+#
+olcAttributeTypes: ( MSADat4:335
+ NAME 'currentLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:570
+ NAME 'meetingProtocol'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:420
+ NAME 'publicKeyPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1402
+ NAME 'mS-SQL-Publisher'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:812
+ NAME 'createWizardExt'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1373
+ NAME 'mS-SQL-Clustered'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:334
+ NAME 'volTableIdxGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:696
+ NAME 'currentParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:504
+ NAME 'seqNotification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:515
+ NAME 'serverReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1119
+ NAME 'msNPAllowDialin'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1387
+ NAME 'mS-SQL-GPSHeight'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1378
+ NAME 'mS-SQL-AppleTalk'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:269
+ NAME 'linkTrackSecret'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:378
+ NAME 'dnsAllowDynamic'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:49
+ NAME 'badPasswordTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:637
+ NAME 'privilegeHolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:289
+ NAME 'printMediaReady'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:288
+ NAME 'printMACAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:66
+ NAME 'lSACreationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:569
+ NAME 'meetingLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:784
+ NAME 'aCSIdentityName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1410
+ NAME 'mS-DS-CreatorSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1374
+ NAME 'mS-SQL-NamedPipe'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:843
+ NAME 'lDAPAdminLimits'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat2:460
+ NAME 'lDAPDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:218
+ NAME 'applicationName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:695
+ NAME 'pendingParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:779
+ NAME 'aCSCacheTimeout'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:574
+ NAME 'meetingLanguage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:778
+ NAME 'aCSDSBMDeadTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:697
+ NAME 'cACertificateDN'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:138
+ NAME 'userParameters'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:132
+ NAME 'trustDirection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:962
+ NAME 'mSMQQueueQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:936
+ NAME 'mSMQEncryptKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:885
+ NAME 'terminalServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:233
+ NAME 'printStartTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:664
+ NAME 'syncWithObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:344
+ NAME 'groupsToIgnore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:665
+ NAME 'syncMembership'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:666
+ NAME 'syncAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:214
+ NAME 'nextLevelStore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:302
+ NAME 'sAMAccountType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1401
+ NAME 'mS-SQL-Keywords'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat2:210
+ NAME 'proxyAddresses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:284
+ NAME 'bytesPerMinute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:241
+ NAME 'printMaxCopies'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:98
+ NAME 'primaryGroupID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:89
+ NAME 'nTGroupMembers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:1228
+ NAME 'mSMQDsServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:43
+ NAME 'fRSVersionGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:486
+ NAME 'fRSWorkingPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:18
+ NAME 'otherTelephone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat2:277
+ NAME 'otherHomePhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:151
+ NAME 'oEMInformation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:459
+ NAME 'networkAddress'
+ SYNTAX 'MSADtelex' )
+#
+olcAttributeTypes: ( MSADat4:966
+ NAME 'mSMQDigestsMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:568
+ NAME 'meetingKeyword'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:844
+ NAME 'lDAPIPDenyList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:847
+ NAME 'installUiLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:894
+ NAME 'gPCFileSysPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:488
+ NAME 'fRSStagingPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:351
+ NAME 'auxiliaryClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:159
+ NAME 'accountExpires'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:718
+ NAME 'dhcpProperties'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:346
+ NAME 'desktopProfile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:762
+ NAME 'aCSServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat2:610
+# NAME 'employeeNumber'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1213
+ NAME 'assocNTAccount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:498
+ NAME 'creationWizard'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:253
+ NAME 'cOMOtherProgId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:202
+ NAME 'auditingPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:635
+ NAME 'privilegeValue'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1366
+ NAME 'mS-SQL-Location'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1334
+ NAME 'pKIDefaultCSPs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:270
+ NAME 'printShareName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:33
+ NAME 'isSingleValued'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:472
+ NAME 'domainCrossRef'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1240
+ NAME 'netbootSIFFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:250
+ NAME 'cOMUniqueLIBID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:657
+ NAME 'serviceDNSName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:782
+ NAME 'objectCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:122
+ NAME 'serviceClassID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:720
+ NAME 'dhcpUpdateTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:221
+ NAME 'sAMAccountName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat4:588
+# NAME 'meetingEndTime'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53' )
+#
+olcAttributeTypes: ( MSADat4:1389
+ NAME 'mS-SQL-Language'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:777
+ NAME 'aCSDSBMRefresh'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1393
+ NAME 'mS-SQL-Database'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:20
+ NAME 'cOMInterfaceID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1403
+ NAME 'mS-SQL-AllowKnownPullSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1394
+ NAME 'mS-SQL-AllowAnonymousSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:654
+ NAME 'managedObjects'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat2:8
+ NAME 'possSuperiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:791
+ NAME 'transportType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:345
+ NAME 'groupPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:117
+ NAME 'rpcNsPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:917
+ NAME 'mSMQQueueType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:141
+ NAME 'versionNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:121
+ NAME 'uSNLastObjRem'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1346
+ NAME 'templateRoots'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:93
+ NAME 'pwdProperties'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:290
+ NAME 'printNumberUp'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:536
+ NAME 'fRSExtensions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:286
+ NAME 'printRateUnit'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:846
+ NAME 'msiScriptSize'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:274
+ NAME 'printSpooling'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:608
+ NAME 'queryPolicyBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:103
+ NAME 'proxyLifetime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:144
+ NAME 'operatorCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:860
+ NAME 'netbootServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:369
+ NAME 'fSMORoleOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:276
+ NAME 'driverVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1388
+ NAME 'mS-SQL-Version'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:939
+ NAME 'mSMQNameStyle'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:471
+ NAME 'schemaVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:436
+ NAME 'directReports'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat2:255
+ NAME 'addressSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:235
+ NAME 'printFormName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:15
+ NAME 'msiScriptPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1312
+ NAME 'aCSServerList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+#olcAttributeTypes: ( MSADat2:615
+# NAME 'personalTitle'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1305
+ NAME 'moveTreeState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:945
+ NAME 'mSMQSiteGates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1238
+ NAME 'mSMQDsService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:76
+ NAME 'objectVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1414
+ NAME 'dNSTombstoned'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:941
+ NAME 'mSMQLongLived'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:534
+ NAME 'fRSLevelLimit'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:845
+ NAME 'msiScriptName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat4:44
+# NAME 'homeDirectory'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:698
+ NAME 'dhcpUniqueKey'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:227
+ NAME 'extensionName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+#olcAttributeTypes: ( MSADat2:256
+# NAME 'streetAddress'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:113
+ NAME 'rpcNsBindings'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:237
+ NAME 'printBinNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:109
+ NAME 'replicaSource'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:246
+ NAME 'printLanguage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1365
+ NAME 'mS-SQL-Contact'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:357
+ NAME 'nTMixedDomain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:483
+ NAME 'fRSFileFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:332
+ NAME 'birthLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:682
+ NAME 'friendlyNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:622
+ NAME 'ipsecDataType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:584
+ NAME 'meetingRating'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:681
+ NAME 'indexedScopes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:312
+ NAME 'rpcNsObjectID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:168
+ NAME 'modifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:218
+ NAME 'oMObjectClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:772
+ NAME 'aCSPolicyName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:502
+ NAME 'timeVolChange'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:337
+ NAME 'currMachineId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:120
+ NAME 'schemaFlagsEx'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1356
+ NAME 'validAccesses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:158
+ NAME 'domainReplica'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1309
+ NAME 'mSMQInterval2'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1308
+ NAME 'mSMQInterval1'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:916
+ NAME 'canonicalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:94
+ NAME 'ntPwdHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:133
+ NAME 'trustPartner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:160
+ NAME 'lmPwdHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:1380
+ NAME 'mS-SQL-Status'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:469
+ NAME 'USNIntersite'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:858
+ NAME 'netbootTools'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:99
+ NAME 'priorSetTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1367
+ NAME 'mS-SQL-Memory'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:950
+ NAME 'mSMQServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat2:613
+# NAME 'employeeType'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:27
+ NAME 'currentValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:822
+ NAME 'siteLinkList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:107
+ NAME 'remoteSource'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:325
+ NAME 'setupCommand'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:212
+ NAME 'dSHeuristics'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1336
+ NAME 'replInterval'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:234
+ NAME 'printEndTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:1
+ NAME 'instanceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:722
+ NAME 'otherIpPhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:965
+ NAME 'mSMQSiteName'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:579
+ NAME 'meetingOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:242
+ NAME 'printCollate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:480
+ NAME 'defaultGroup'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:79
+ NAME 'minPwdLength'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:864
+ NAME 'netbootSCPBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:650
+ NAME 'mhsORAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+#olcAttributeTypes: ( MSADat4:651
+# NAME 'otherMailbox'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:367
+ NAME 'rpcNsCodeset'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:14
+ NAME 'hasMasterNCs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:952
+ NAME 'mSMQMigrated'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:74
+ NAME 'dSASignature'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:115
+ NAME 'invocationId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:254
+ NAME 'cOMTypelibId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:26
+ NAME 'creationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:581
+ NAME 'meetingScope'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:336
+ NAME 'volTableGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:513
+ NAME 'siteObjectBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:756
+ NAME 'aCSTimeOfDay'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:757
+ NAME 'aCSDirection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:77
+ NAME 'maxTicketAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:481
+ NAME 'schemaUpdate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:80
+ NAME 'minTicketAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:628
+ NAME 'ipsecNegotiationPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:327
+ NAME 'helpFileName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:148
+ NAME 'schemaIDGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:810
+ NAME 'createDialog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:964
+ NAME 'mSMQNt4Flags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:327
+ NAME 'packageFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:464
+ NAME 'wWWHomePage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:507
+ NAME 'volumeCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:273
+ NAME 'printStatus'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:890
+ NAME 'uPNSuffixes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:471
+ NAME 'trustParent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1301
+ NAME 'tokenGroups'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:375
+ NAME 'systemFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:667
+ NAME 'syncWithSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1306
+ NAME 'dNSProperty'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:710
+ NAME 'superScopes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:1347
+ NAME 'sPNMappings'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:272
+ NAME 'printNotify'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:282
+ NAME 'printMemory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:154
+ NAME 'serverState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:942
+ NAME 'mSMQVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:373
+ NAME 'rIDUsedPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1355
+ NAME 'queryFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:300
+ NAME 'printerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:97
+ NAME 'preferredOU'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:649
+ NAME 'primaryInternationalISDNNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:333
+ NAME 'oMTIndxGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1337
+ NAME 'mSMQUserSid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:487
+ NAME 'fRSRootPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:918
+ NAME 'mSMQJournal'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:499
+ NAME 'contextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:764
+ NAME 'aCSPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:937
+ NAME 'mSMQSignKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:359
+ NAME 'netbootGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:925
+ NAME 'mSMQOwnerID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:24
+ NAME 'mustContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:379
+ NAME 'dnsAllowXFR'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1379
+ NAME 'mS-SQL-Vines'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:948
+ NAME 'mSMQDigests'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:662
+ NAME 'lockoutTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:53
+ NAME 'lastSetTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:25
+ NAME 'countryCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1377
+ NAME 'mS-SQL-TCPIP'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:934
+ NAME 'mSMQForeign'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:571
+ NAME 'meetingType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:714
+ NAME 'dhcpOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:704
+ NAME 'dhcpServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:283
+ NAME 'assetNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:350
+ NAME 'addressType'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:940
+ NAME 'mSMQCSPName'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:671
+ NAME 'msiFileList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:619
+ NAME 'dNSHostName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:705
+ NAME 'dhcpSubnets'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:1328
+ NAME 'pKIKeyUsage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:30
+ NAME 'attributeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:506
+ NAME 'objectCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:503
+ NAME 'timeRefresh'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:139
+ NAME 'profilePath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:818
+ NAME 'productCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:647
+ NAME 'otherMobile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:12
+ NAME 'badPwdCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1368
+ NAME 'mS-SQL-Build'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat2:13
+# NAME 'displayName'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:87
+ NAME 'nETBIOSName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1395
+ NAME 'mS-SQL-Alias'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:75
+ NAME 'maxRenewAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:806
+ NAME 'treatAsLeaf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:960
+ NAME 'mSMQNt4Stub'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:324
+ NAME 'packageType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1212
+ NAME 'isEphemeral'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:36
+ NAME 'dMDLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:715
+ NAME 'dhcpClasses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:39
+ NAME 'forceLogoff'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:2
+ NAME 'whenCreated'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:566
+ NAME 'meetingName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:786
+ NAME 'mailAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:590
+ NAME 'meetingBlob'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:71
+ NAME 'machineRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:334
+ NAME 'searchFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:3
+ NAME 'whenChanged'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:702
+ NAME 'dhcpObjName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:897
+ NAME 'aCSMaxAggregatePeakRatePerUser'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:326
+ NAME 'packageName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:170
+ NAME 'systemOnly'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:935
+ NAME 'mSMQOSType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:680
+ NAME 'queryPoint'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:271
+ NAME 'printOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:19
+ NAME 'uSNCreated'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:494
+ NAME 'siteServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:114
+ NAME 'rpcNsGroup'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:609
+ NAME 'sIDHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:882
+ NAME 'fRSVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:64
+ NAME 'logonHours'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:854
+ NAME 'netbootAnswerOnlyValidClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:96
+ NAME 'pwdLastSet'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:243
+ NAME 'printColor'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1391
+ NAME 'mS-SQL-Type'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:40
+ NAME 'fromServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:157
+ NAME 'serverRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:100
+ NAME 'priorValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:169
+ NAME 'logonCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:90
+ NAME 'unicodePwd'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:21
+ NAME 'subClassOf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1396
+ NAME 'mS-SQL-Size'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:101
+ NAME 'privateKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:512
+ NAME 'siteObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:62
+ NAME 'scriptPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:223
+ NAME 'serverName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:953
+ NAME 'mSMQSiteID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:340
+ NAME 'rightsGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:374
+ NAME 'rIDNextRID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:583
+ NAME 'meetingURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat2:400
+ NAME 'addressEntryDisplayTableMSDOS'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:76
+ NAME 'maxStorage'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:35
+ NAME 'rangeUpper'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:34
+ NAME 'rangeLower'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:118
+ NAME 'otherPager'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:639
+ NAME 'isMemberOfPartialAttributeSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1224
+ NAME 'parentGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:141
+ NAME 'department'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:25
+ NAME 'mayContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:150
+ NAME 'adminCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:51
+ NAME 'lastLogoff'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1409
+ NAME 'masteredBy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:35
+ NAME 'employeeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:719
+ NAME 'dhcpMaxKey'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:229
+ NAME 'driverName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1363
+ NAME 'mS-SQL-Name'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:322
+ NAME 'categoryId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:889
+ NAME 'additionalTrustedServiceNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1354
+ NAME 'scopeFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:672
+ NAME 'categories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:855
+ NAME 'netbootNewMachineNamingPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:19
+ NAME 'cOMClassID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:120
+ NAME 'uSNChanged'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:2
+ NAME 'objectGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:707
+ NAME 'dhcpRanges'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1358
+ NAME 'schemaInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:646
+ NAME 'otherFacsimileTelephoneNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:520
+ NAME 'machinePasswordChangeInterval'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:674
+ NAME 'rootTrust'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:136
+ NAME 'trustType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:750
+ NAME 'groupType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:896
+ NAME 'uSNSource'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:919
+ NAME 'mSMQQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:927
+ NAME 'mSMQSites'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:910
+ NAME 'fromEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' )
+#
+olcAttributeTypes: ( MSADat4:1376
+ NAME 'mS-SQL-SPX'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:892
+ NAME 'gPOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:814
+ NAME 'msiScript'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:285
+ NAME 'printRate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:683
+ NAME 'cRLPartitionedRevocationList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:652
+ NAME 'assistant'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:490
+ NAME 'fRSDSPoll'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:663
+ NAME 'partialAttributeDeletionList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:52
+ NAME 'lastLogon'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:22
+ NAME 'governsID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:341
+ NAME 'appliesTo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:268
+ NAME 'eFSPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:155
+ NAME 'uASCompat'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:538
+ NAME 'prefixMap'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:661
+ NAME 'isDefunct'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:708
+ NAME 'dhcpSites'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:888
+ NAME 'iPSECNegotiationPolicyAction'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:382
+ NAME 'dnsRecord'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:21
+ NAME 'cOMProgID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:45
+ NAME 'homeDrive'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:580
+ NAME 'meetingIP'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1321
+ NAME 'aCSNonReservedMinPolicedSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:717
+ NAME 'dhcpState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:922
+ NAME 'mSMQLabel'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:74
+ NAME 'maxPwdAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:78
+ NAME 'minPwdAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:689
+ NAME 'cRLObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:146
+ NAME 'objectSid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:565
+ NAME 'meetingID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:620
+ NAME 'ipsecName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:48
+ NAME 'isDeleted'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:760
+ NAME 'aCSAggregateTokenRatePerUser'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:623
+ NAME 'ipsecData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:668
+ NAME 'domainCAs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:687
+ NAME 'cAConnect'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:238
+ NAME 'printMaxResolutionSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:700
+ NAME 'dhcpFlags'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:402
+ NAME 'helpData16'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:653
+ NAME 'managedBy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:9
+ NAME 'helpData32'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:944
+ NAME 'mSMQSite2'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:943
+ NAME 'mSMQSite1'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:677
+ NAME 'replTopologyStayOfExecution'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:912
+ NAME 'allowedChildClassesEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:231
+ NAME 'oMSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:231
+ NAME 'priority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:48
+ NAME 'keywords'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:946
+ NAME 'mSMQCost'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:821
+ NAME 'siteList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:145
+ NAME 'revision'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:91
+ NAME 'repsFrom'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:645
+ NAME 'userCert'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:951
+ NAME 'mSMQQMID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:228
+ NAME 'portName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:859
+ NAME 'netbootLocallyInstalledOSes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:261
+ NAME 'division'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:902
+ NAME 'aCSMaxSizeOfRSVPAccountFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:699
+ NAME 'dhcpType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:301
+ NAME 'wbemPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:362
+ NAME 'siteGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:26
+ NAME 'rDNAttID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:900
+ NAME 'aCSRSVPAccountFilesLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1226
+ NAME 'mSMQDependentClientServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:222
+ NAME 'location'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:874
+ NAME 'fRSFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:219
+ NAME 'iconPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:688
+ NAME 'cAWEBURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:716
+ NAME 'mscopeId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:660
+ NAME 'treeName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:211
+ NAME 'schedule'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:557
+ NAME 'parentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:249
+ NAME 'cOMCLSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:675
+ NAME 'catalogs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:102
+ NAME 'memberOf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:690
+ NAME 'cAUsages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:706
+ NAME 'dhcpMask'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+#
+olcAttributeTypes: ( MSADat4:511
+ NAME 'flatName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:686
+ NAME 'domainID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:58
+ NAME 'localeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:16
+ NAME 'codePage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:768
+ NAME 'aCSEnableRSVPMessageLogging'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:240
+ NAME 'printOrientationsSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:883
+ NAME 'msRRASVendorAttributeEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1246
+ NAME 'interSiteTopologyGenerator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:307
+ NAME 'options'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:28
+ NAME 'dnsRoot'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:887
+ NAME 'iPSECNegotiationPolicyType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1392
+ NAME 'mS-SQL-InformationDirectory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:365
+ NAME 'operatingSystemServicePack'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:88
+ NAME 'nextRid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:865
+ NAME 'pekList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:7
+ NAME 'subRefs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:505
+ NAME 'oMTGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:205
+ NAME 'pKTGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:146
+ NAME 'company'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:82
+ NAME 'moniker'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:156
+ NAME 'comment'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:721
+ NAME 'ipPhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1361
+ NAME 'mS-DS-ConsistencyChildCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:679
+ NAME 'creator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:137
+ NAME 'uNCName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:55
+ NAME 'dBCSPwd'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1239
+ NAME 'mSMQDependentClientService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:684
+ NAME 'certificateAuthorityObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:621
+ NAME 'ipsecID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:914
+ NAME 'allowedAttributesEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+#olcAttributeTypes: ( MSADat2:598
+# NAME 'dmdName'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:759
+ NAME 'aCSMaxPeakBandwidthPerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:557
+ NAME 'Enabled'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:326
+ NAME 'perRecipDialogDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1248
+ NAME 'interSiteTopologyFailover'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:895
+ NAME 'transportAddressAttribute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:852
+ NAME 'netbootCurrentClientCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:372
+ NAME 'rIDPreviousAllocationPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:83
+ NAME 'repsTo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:224
+ NAME 'defaultSecurityDescriptor'
+ SYNTAX 'MSADsecDesc'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:519
+ NAME 'lastBackupRestorationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:873
+ NAME 'fRSControlOutboundBacklog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:255
+ NAME 'vendor'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:891
+ NAME 'gPLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:214
+ NAME 'originalDisplayTableMSDOS'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:50
+ NAME 'linkID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1130
+ NAME 'msNPSavedCallingStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:49
+ NAME 'mAPIID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:510
+ NAME 'serviceBindingInformation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:16
+ NAME 'nCName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1303
+ NAME 'tokenGroupsNoGCAcceptable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:1418
+ NAME 'tokenGroupsGlobalAndUniversal'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:1190
+ NAME 'msRASSavedFramedIPAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:766
+ NAME 'aCSAllocableRSVPBandwidth'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:61
+ NAME 'lockOutObservationWindow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:857
+ NAME 'netbootIntelliMirrorOSes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1320
+ NAME 'aCSNonReservedMaxSDUSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:265
+ NAME 'notes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:673
+ NAME 'retiredReplDSASignatures'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1313
+ NAME 'aCSMaxTokenBucketPerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:324
+ NAME 'addressEntryDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1317
+ NAME 'aCSMinimumDelayVariation'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:872
+ NAME 'fRSControlInboundBacklog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:38
+ NAME 'flags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1399
+ NAME 'mS-SQL-LastDiagnosticDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1348
+ NAME 'gPCMachineExtensionNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1411
+ NAME 'ms-DS-MachineAccountQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:325
+ NAME 'perMsgDialogDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:57
+ NAME 'defaultLocalPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1189
+ NAME 'msRASSavedCallbackNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:685
+ NAME 'parentCACertificateChain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:893
+ NAME 'gPCFunctionalityVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:879
+ NAME 'fRSServiceCommandStatus'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1319
+ NAME 'aCSNonReservedTokenSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:775
+ NAME 'aCSMaxSizeOfRSVPLogFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:135
+ NAME 'cost'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:81
+ NAME 'modifiedCountAtLastProm'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:773
+ NAME 'aCSRSVPLogFilesLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat2:81
+# NAME 'info'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:125
+ NAME 'supplementalCredentials'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:819
+ NAME 'bridgeheadTransportList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:967
+ NAME 'mSMQSignCertificatesMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+## NO-USER-MODIFICATION
+#olcAttributeTypes: ( MSADat4:1
+# NAME 'name'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1153
+ NAME 'msRADIUSFramedIPAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1408
+ NAME 'mS-DS-ReplicatesNCReason'
+ SYNTAX 'MSADdnWithOctetString' )
+#
+olcAttributeTypes: ( MSADat4:899
+ NAME 'aCSEnableRSVPAccounting'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+#olcAttributeTypes: ( MSADat4:881
+# NAME 'fRSTimeLastConfigChange'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53'
+# SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:281
+ NAME 'printStaplingSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1247
+ NAME 'interSiteTopologyRenew'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:364
+ NAME 'operatingSystemVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:91
+ NAME 'otherLoginWorkstations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:849
+ NAME 'netbootAllowNewClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1372
+ NAME 'mS-SQL-UnicodeSortOrder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:749
+ NAME 'url'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:206
+ NAME 'pKT'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:199
+ NAME 'serviceInstanceVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:169
+ NAME 'showInAdvancedViewOnly'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:758
+ NAME 'aCSMaxTokenRatePerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:868
+ NAME 'isCriticalSystemObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:576
+ NAME 'meetingMaxParticipants'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1208
+ NAME 'aNR'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:153
+ NAME 'rid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:523
+ NAME 'proxyGenerationEnabled'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:871
+ NAME 'fRSControlDataCreation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:692
+ NAME 'previousCACertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:24
+ NAME 'contentIndexingAllowed'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:633
+ NAME 'policyReplicationFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:870
+ NAME 'frsComputerReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1318
+ NAME 'aCSNonReservedPeakRate'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:901
+ NAME 'aCSMaxNoOfAccountFiles'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:514
+ NAME 'physicalLocationObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:928
+ NAME 'mSMQOutRoutingServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:820
+ NAME 'bridgeheadServerListBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1145
+ NAME 'msRADIUSCallbackNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:361
+ NAME 'netbootMachineFilePath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:963
+ NAME 'mSMQQueueJournalQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:853
+ NAME 'netbootAnswerRequests'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:415
+ NAME 'operatingSystemHotfix'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:149
+ NAME 'attributeSecurityGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:711
+ NAME 'superScopeDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1359
+ NAME 'otherWellKnownObjects'
+ SYNTAX 'MSADdnWithOctetString' )
+#
+olcAttributeTypes: ( MSADat4:780
+ NAME 'aCSNonReservedTxLimit'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:11
+ NAME 'authenticationOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:867
+ NAME 'altSecurityIdentities'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1349
+ NAME 'gPCUserExtensionNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:358
+ NAME 'netbootInitialization'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1364
+ NAME 'mS-SQL-RegisteredOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:761
+ NAME 'aCSMaxDurationPerFlow'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1330
+ NAME 'pKICriticalExtensions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:748
+ NAME 'attributeDisplayNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1404
+ NAME 'mS-SQL-AllowImmediateUpdatingSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1191
+ NAME 'msRASSavedFramedRoute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#
+olcAttributeTypes: ( MSADat4:752
+ NAME 'userSharedFolderOther'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+#olcAttributeTypes: ( MSADat2:131
+# NAME 'co'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:909
+ NAME 'extendedAttributeInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1241
+ NAME 'netbootMirrorDataFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1315
+ NAME 'aCSMinimumPolicedSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1353
+ NAME 'localizationDisplayId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:582
+ NAME 'meetingAdvertiseScope'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1343
+ NAME 'dSUIAdminNotification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:1381
+ NAME 'mS-SQL-LastUpdatedDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:1357
+ NAME 'dSCorePropagationData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' )
+#
+olcAttributeTypes: ( MSADat4:320
+ NAME 'implementedCategories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcAttributeTypes: ( MSADat4:783
+ NAME 'defaultObjectCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:422
+ NAME 'domainPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:929
+ NAME 'mSMQInRoutingServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:1311
+ NAME 'printDuplexSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:693
+ NAME 'pendingCACertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:281
+ NAME 'nTSecurityDescriptor'
+ SYNTAX 'MSADsecDesc'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:198
+ NAME 'systemAuxiliaryClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat4:898
+ NAME 'aCSNonReservedTxSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1382
+ NAME 'mS-SQL-InformationURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:3
+ NAME 'replPropertyMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1384
+ NAME 'mS-SQL-PublicationURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:275
+ NAME 'printKeepPrintedJobs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:267
+ NAME 'uSNDSALastObjRemoved'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:381
+ NAME 'dnsNotifySecondaries'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:1360
+ NAME 'mS-DS-ConsistencyGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:869
+ NAME 'frsComputerReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1369
+ NAME 'mS-SQL-ServiceAccount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1124
+ NAME 'msNPCallingStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#
+olcAttributeTypes: ( MSADat4:947
+ NAME 'mSMQSignCertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:624
+ NAME 'ipsecOwnersReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:14
+ NAME 'builtinModifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:634
+ NAME 'privilegeDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:380
+ NAME 'dnsSecureSecondaries'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+#
+olcAttributeTypes: ( MSADat4:817
+ NAME 'localizedDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:195
+ NAME 'systemPossSuperiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+#
+olcAttributeTypes: ( MSADat2:353
+ NAME 'displayNamePrintable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:771
+ NAME 'servicePrincipalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:866
+ NAME 'pekKeyChangeInterval'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat2:445
+ NAME 'originalDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1398
+ NAME 'mS-SQL-LastBackupDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:517
+ NAME 'ipsecPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:823
+ NAME 'certificateTemplates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:15
+ NAME 'hasPartialReplicaNCs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:457
+ NAME 'localPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat2:380
+ NAME 'extendedCharsAllowed'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:629
+ NAME 'ipsecFilterReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:626
+ NAME 'ipsecISAKMPReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:876
+ NAME 'fRSMemberReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:314
+ NAME 'rpcNsTransferSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1227
+ NAME 'mSMQRoutingServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1375
+ NAME 'mS-SQL-MultiProtocol'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:825
+ NAME 'enrollmentProviders'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:287
+ NAME 'printNetworkAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1171
+ NAME 'msRADIUSServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:631
+ NAME 'printPagesPerMinute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:299
+ NAME 'printMediaSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+#
+olcAttributeTypes: ( MSADat4:824
+ NAME 'signatureAlgorithms'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:877
+ NAME 'fRSPartnerAuthLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:636
+ NAME 'privilegeAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:640
+ NAME 'partialAttributeSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:850
+ NAME 'netbootLimitClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1383
+ NAME 'mS-SQL-ConnectionURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1406
+ NAME 'mS-SQL-AllowSnapshotFilesFTPDownloading'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1331
+ NAME 'pKIExpirationPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+#
+# NO-USER-MODIFICATION
+olcAttributeTypes: ( MSADat4:531
+ NAME 'nonSecurityMemberBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+#
+olcAttributeTypes: ( MSADat4:540
+ NAME 'initialAuthOutgoing'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+#
+olcAttributeTypes: ( MSADat4:1158
+ NAME 'msRADIUSFramedRoute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#
+olcAttributeTypes: ( MSADat4:200
+ NAME 'controlAccessRights'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+#
+olcObjectClasses: ( MSADoc5:0 NAME 'mstop' SUP top ABSTRACT
+ MUST (objectClass $ instanceType
+ $ nTSecurityDescriptor $ objectCategory ) MAY (cn $ description $ distinguishedName
+ $ whenCreated $ whenChanged $ subRefs $ displayName $ uSNCreated $ isDeleted
+ $ dSASignature $ objectVersion $ repsTo $ repsFrom $ memberOf $ ownerBL
+ $ uSNChanged $ uSNLastObjRem $ showInAdvancedViewOnly $ adminDisplayName
+ $ proxyAddresses $ adminDescription $ extensionName $ uSNDSALastObjRemoved
+ $ displayNamePrintable $ directReports $ wWWHomePage $ USNIntersite $ name
+ $ objectGUID $ replPropertyMetaData $ replUpToDateVector $ flags $ revision
+ $ wbemPath $ fSMORoleOwner $ systemFlags $ siteObjectBL $ serverReferenceBL
+ $ nonSecurityMemberBL $ queryPolicyBL $ wellKnownObjects $ isPrivilegeHolder
+ $ partialAttributeSet $ managedObjects $ partialAttributeDeletionList $ url
+ $ lastKnownParent $ bridgeheadServerListBL $ netbootSCPBL $ isCriticalSystemObject
+ $ frsComputerReferenceBL $ fRSMemberReferenceBL $ uSNSource $ fromEntry
+ $ allowedChildClasses $ allowedChildClassesEffective $ allowedAttributes
+ $ allowedAttributesEffective $ possibleInferiors $ canonicalName $ proxiedObjectName
+ $ sDRightsEffective $ dSCorePropagationData $ otherWellKnownObjects
+ $ mS-DS-ConsistencyGuid $ mS-DS-ConsistencyChildCount $ masteredBy
+ $ msCOM-PartitionSetLink $ msCOM-UserLink $ msDS-Approx-Immed-Subordinates
+ $ msDS-NCReplCursors $ msDS-NCReplInboundNeighbors $ msDS-NCReplOutboundNeighbors
+ $ msDS-ReplAttributeMetaData $ msDS-ReplValueMetaData $ msDS-NonMembersBL
+ $ msDS-MembersForAzRoleBL $ msDS-OperationsForAzTaskBL $ msDS-TasksForAzTaskBL
+ $ msDS-OperationsForAzRoleBL $ msDS-TasksForAzRoleBL $ msDs-masteredBy
+ $ msDS-ObjectReferenceBL $ msDS-PrincipalName $ msDS-RevealedDSAs $ msDS-KrbTgtLinkBl
+ $ msDS-IsFullReplicaFor $ msDS-IsDomainFor $ msDS-IsPartialReplicaFor
+ $ msDS-AuthenticatedToAccountlist $ msDS-NC-RO-Replica-Locations-BL $ msDS-RevealedListBL
+ $ msDS-PSOApplied $ msDS-NcType $ msDS-OIDToGroupLinkBl $ msDS-HostServiceAccountBL
+ $ isRecycled $ msDS-LocalEffectiveDeletionTime $ msDS-LocalEffectiveRecycleTime
+ $ msDS-LastKnownRDN $ msDS-EnabledFeatureBL $ msDS-ClaimSharesPossibleValuesWithBL
+ $ msDS-MembersOfResourcePropertyListBL $ msDS-IsPrimaryComputerFor
+ $ msDS-ValueTypeReferenceBL $ msDS-TDOIngressBL $ msDS-TDOEgressBL $ msDS-parentdistname
+ $ msDS-ReplValueMetaDataExt $ msds-memberOfTransitive $ msds-memberTransitive
+ $ msSFU30PosixMemberOf $ msDFSR-MemberReferenceBL $ msDFSR-ComputerReferenceBL ) )
+olcObjectClasses: ( MSADoc5:8 NAME 'group' SUP mstop STRUCTURAL
+ MUST (groupType ) MAY (member $ nTGroupMembers $ operatorCount $ adminCount
+ $ groupAttributes $ groupMembershipSAM $ controlAccessRights $ desktopProfile
+ $ nonSecurityMember $ managedBy $ primaryGroupToken $ msDS-AzLDAPQuery
+ $ msDS-NonMembers $ msDS-AzBizRule $ msDS-AzBizRuleLanguage $ msDS-AzLastImportedBizRulePath
+ $ msDS-AzApplicationData $ msDS-AzObjectGuid $ msDS-AzGenericData
+ $ msDS-PrimaryComputer $ mail $ msSFU30Name $ msSFU30NisDomain $ msSFU30PosixMember ) )
+olcObjectClasses: ( MSADoc5:9 NAME 'user'
+ SUP ( mstop $ organizationalPerson ) STRUCTURAL
+ MAY (o $ businessCategory $ userCertificate $ givenName $ initials
+ $ x500uniqueIdentifier $ displayName $ networkAddress $ employeeNumber
+ $ employeeType $ homePostalAddress $ userAccountControl $ badPwdCount
+ $ codePage $ homeDirectory $ homeDrive $ badPasswordTime $ lastLogoff
+ $ lastLogon $ dBCSPwd $ localeID $ scriptPath $ logonHours $ logonWorkstation
+ $ maxStorage $ userWorkstations $ unicodePwd $ otherLoginWorkstations
+ $ ntPwdHistory $ pwdLastSet $ preferredOU $ primaryGroupID $ userParameters
+ $ profilePath $ operatorCount $ adminCount $ accountExpires $ lmPwdHistory
+ $ groupMembershipSAM $ logonCount $ controlAccessRights $ defaultClassStore
+ $ groupsToIgnore $ groupPriority $ desktopProfile $ dynamicLDAPServer
+ $ userPrincipalName $ lockoutTime $ userSharedFolder $ userSharedFolderOther
+ $ servicePrincipalName $ aCSPolicyName $ terminalServer $ mSMQSignCertificates
+ $ mSMQDigests $ mSMQDigestsMig $ mSMQSignCertificatesMig $ msNPAllowDialin
+ $ msNPCallingStationID $ msNPSavedCallingStationID $ msRADIUSCallbackNumber
+ $ msRADIUSFramedIPAddress $ msRADIUSFramedRoute $ msRADIUSServiceType
+ $ msRASSavedCallbackNumber $ msRASSavedFramedIPAddress
+ $ msRASSavedFramedRoute $ mS-DS-CreatorSID $ msCOM-UserPartitionSetLink
+ $ msDS-Cached-Membership $ msDS-Cached-Membership-Time-Stamp
+ $ msDS-Site-Affinity $ msDS-User-Account-Control-Computed $ lastLogonTimestamp
+ $ msIIS-FTPRoot $ msIIS-FTPDir $ msDRM-IdentityCertificate $ msDS-SourceObjectDN
+ $ msPKIRoamingTimeStamp $ msPKIDPAPIMasterKeys $ msPKIAccountCredentials
+ $ msRADIUS-FramedInterfaceId $ msRADIUS-SavedFramedInterfaceId $ msRADIUS-FramedIpv6Prefix
+ $ msRADIUS-SavedFramedIpv6Prefix $ msRADIUS-FramedIpv6Route $ msRADIUS-SavedFramedIpv6Route
+ $ msDS-SecondaryKrbTgtNumber $ msDS-AuthenticatedAtDC $ msDS-SupportedEncryptionTypes
+ $ msDS-LastSuccessfulInteractiveLogonTime $ msDS-LastFailedInteractiveLogonTime
+ $ msDS-FailedInteractiveLogonCount $ msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon
+ $ msTSProfilePath $ msTSHomeDirectory $ msTSHomeDrive $ msTSAllowLogon
+ $ msTSRemoteControl $ msTSMaxDisconnectionTime $ msTSMaxConnectionTime
+ $ msTSMaxIdleTime $ msTSReconnectionAction $ msTSBrokenConnectionAction
+ $ msTSConnectClientDrives $ msTSConnectPrinterDrives $ msTSDefaultToMainPrinter
+ $ msTSWorkDirectory $ msTSInitialProgram $ msTSProperty01 $ msTSProperty02
+ $ msTSExpireDate $ msTSLicenseVersion $ msTSManagingLS
+ $ msDS-UserPasswordExpiryTimeComputed $ msTSExpireDate2 $ msTSLicenseVersion2
+ $ msTSManagingLS2 $ msTSExpireDate3 $ msTSLicenseVersion3 $ msTSManagingLS3
+ $ msTSExpireDate4 $ msTSLicenseVersion4 $ msTSManagingLS4 $ msTSLSProperty01
+ $ msTSLSProperty02 $ msDS-ResultantPSO $ msPKI-CredentialRoamingTokens
+ $ msTSPrimaryDesktop $ msTSSecondaryDesktops $ msDS-PrimaryComputer $ msDS-SyncServerUrl
+ $ msDS-AssignedAuthNPolicySilo $ msDS-AuthNPolicySiloMembersBL
+ $ msDS-AssignedAuthNPolicy $ userSMIMECertificate $ uid $ mail $ roomNumber
+ $ photo $ manager $ homePhone $ secretary $ mobile $ pager $ audio $ jpegPhoto
+ $ carLicense $ departmentNumber $ preferredLanguage $ userPKCS12 $ labeledURI
+ $ msSFU30Name $ msSFU30NisDomain ) )
+#objectclass ( MSADoc3:23 NAME 'container' SUP mstop STRUCTURAL
+# MUST (cn ) MAY (schemaVersion $ defaultClassStore $ msDS-ObjectReference ) )
+olcObjectClasses: ( 1.2.840.113556.1.3.30 NAME 'computer' SUP user STRUCTURAL
+ MAY (cn $ networkAddress $ localPolicyFlags $ defaultLocalPolicyObject $ machineRole
+ $ location $ netbootInitialization $ netbootGUID $ netbootMachineFilePath
+ $ siteGUID $ operatingSystem $ operatingSystemVersion $ operatingSystemServicePack
+ $ operatingSystemHotfix $ volumeCount $ physicalLocationObject $ dNSHostName
+ $ policyReplicationFlags $ managedBy $ rIDSetReferences $ catalogs
+ $ netbootSIFFile $ netbootMirrorDataFile $ msDS-AdditionalDnsHostName
+ $ msDS-AdditionalSamAccountName $ msDS-ExecuteScriptPassword $ msDS-KrbTgtLink
+ $ msDS-RevealedUsers $ msDS-NeverRevealGroup $ msDS-RevealOnDemandGroup
+ $ msDS-RevealedList $ msDS-AuthenticatedAtDC $ msDS-isGC $ msDS-isRODC $ msDS-SiteName
+ $ msDS-PromotionSettings $ msTPM-OwnerInformation $ msTSProperty01 $ msTSProperty02
+ $ msDS-IsUserCachableAtRodc $ msDS-HostServiceAccount $ msTSEndpointData
+ $ msTSEndpointType $ msTSEndpointPlugin $ msTSPrimaryDesktopBL $ msTSSecondaryDesktopBL
+ $ msTPM-TpmInformationForComputer $ msDS-GenerationId $ msImaging-ThumbprintHash
+ $ msImaging-HashAlgorithm $ netbootDUID $ msSFU30Name $ msSFU30Aliases $ msSFU30NisDomain $ nisMapName ) )
diff --git a/servers/slapd/schema/msuser.schema b/servers/slapd/schema/msuser.schema
new file mode 100644
index 0000000..4cce48f
--- /dev/null
+++ b/servers/slapd/schema/msuser.schema
@@ -0,0 +1,4295 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2018-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+# This file is provided for informational purposes only.
+
+# These definitions are from Microsoft's Active Directory.
+# They were first gathered using the subschemaSubentry object of
+# Windows 2000 Advanced Server Release Candidate 2.
+# Additional definitions were gathered from Windows Server 2012
+# and adjusted to ease compatibility issues.
+
+# These object classes and attributes are rooted at OID
+# 1.2.840.113556.1, Microsoft's Directory Enabled networks
+# OID. OID Macros are used throughout, to make mapping of
+# incompatible OIDs easier.
+
+# A number of custom syntaxes have been replaced with generic
+# syntaxes. The original syntax OIDs are commented out below.
+
+# Many of these attributes are defined with NO-USER-MODIFICATION,
+# but slapd won't load such definitions from user-modifiable schema
+# files. So that designation has been removed, and commented accordingly.
+
+# Only the subset of Windows 2012 attributes needed to make the
+# user and group objectclasses work has been added to the previously
+# retrieved definitions.
+
+# If an AD definition clashes with existing standardized schema,
+# the AD definition has been commented out.
+
+objectidentifier MicrosoftRoot 1.2.840.113556
+objectidentifier MSAD MicrosoftRoot:1
+objectidentifier MSADat2 MSAD:2
+objectidentifier MSADat4 MSAD:4
+objectidentifier MSADat6 MSAD:6
+objectidentifier MSADoc3 MSAD:3
+objectidentifier MSADoc5 MSAD:5
+
+#objectidentifier MSADdnWithOctetString MSADat4:903
+#objectidentifier MSADdnWithString MSADat4:904
+#objectidentifier MSADtelex MSADat4:905
+#objectidentifier MSADlargeInteger MSADat4:906
+#objectidentifier MSADcis MSADat4:907
+#objectidentifier MSADsecDesc MSADat4:907
+objectidentifier MSADdnWithOctetString 1.3.6.1.4.1.1466.115.121.1.40
+objectidentifier MSADdnWithString 1.3.6.1.4.1.1466.115.121.1.40
+objectidentifier MSADtelex 1.3.6.1.4.1.1466.115.121.1.44
+objectidentifier MSADlargeInteger 1.3.6.1.4.1.1466.115.121.1.27
+objectidentifier MSADcis 1.3.6.1.4.1.1466.115.121.1.44
+objectidentifier MSADsecDesc 1.3.6.1.4.1.1466.115.121.1.40
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:104
+ NAME 'ownerBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1424
+ NAME 'msCOM-PartitionSetLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1425
+ NAME 'msCOM-UserLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1669
+ NAME 'msDS-Approx-Immed-Subordinates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1704
+ NAME 'msDS-NCReplCursors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1705
+ NAME 'msDS-NCReplInboundNeighbors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1706
+ NAME 'msDS-NCReplOutboundNeighbors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1707
+ NAME 'msDS-ReplAttributeMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1708
+ NAME 'msDS-ReplValueMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1793
+ NAME 'msDS-NonMembers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1794
+ NAME 'msDS-NonMembersBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1806
+ NAME 'msDS-MembersForAzRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1807
+ NAME 'msDS-MembersForAzRoleBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1808 NAME 'msDS-OperationsForAzTask' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1809 NAME 'msDS-OperationsForAzTaskBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:1810 NAME 'msDS-TasksForAzTask' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1811 NAME 'msDS-TasksForAzTaskBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:1812 NAME 'msDS-OperationsForAzRole' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1813 NAME 'msDS-OperationsForAzRoleBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:1814 NAME 'msDS-TasksForAzRole' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1815 NAME 'msDS-TasksForAzRoleBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1837 NAME 'msDs-masteredBy' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributeType ( MSADat4:1840 NAME 'msDS-ObjectReference' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1841 NAME 'msDS-ObjectReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributeType ( MSADat4:1865 NAME 'msDS-PrincipalName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1930 NAME 'msDS-RevealedDSAs' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1931 NAME 'msDS-KrbTgtLinkBl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1932 NAME 'msDS-IsFullReplicaFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1933 NAME 'msDS-IsDomainFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1934 NAME 'msDS-IsPartialReplicaFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1957 NAME 'msDS-AuthenticatedToAccountlist' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributeType ( MSADat4:1958 NAME 'msDS-AuthenticatedAtDC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributeType ( MSADat4:1975 NAME 'msDS-RevealedListBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1968 NAME 'msDS-NC-RO-Replica-Locations-BL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2021 NAME 'msDS-PSOApplied' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2024 NAME 'msDS-NcType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2052 NAME 'msDS-OIDToGroupLinkBl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2057 NAME 'msDS-HostServiceAccountBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2058 NAME 'isRecycled' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2059 NAME 'msDS-LocalEffectiveDeletionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2060 NAME 'msDS-LocalEffectiveRecycleTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2067 NAME 'msDS-LastKnownRDN' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2069 NAME 'msDS-EnabledFeatureBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2102 NAME 'msDS-ClaimSharesPossibleValuesWithBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2104 NAME 'msDS-MembersOfResourcePropertyListBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2168 NAME 'msDS-IsPrimaryComputerFor' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2188 NAME 'msDS-ValueTypeReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2193 NAME 'msDS-TDOIngressBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2194 NAME 'msDS-TDOEgressBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2203 NAME 'msDS-parentdistname' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+attributetype ( MSADat4:2235 NAME 'msDS-ReplValueMetaDataExt' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2236 NAME 'msds-memberOfTransitive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2238 NAME 'msds-memberTransitive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat6:18.1.347 NAME 'msSFU30PosixMemberOf' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat6:13.3.102 NAME 'msDFSR-MemberReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat6:13.3.103 NAME 'msDFSR-ComputerReferenceBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+
+attributetype ( MSADat4:1792 NAME 'msDS-AzLDAPQuery' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1801 NAME 'msDS-AzBizRule' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1802 NAME 'msDS-AzBizRuleLanguage' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1803 NAME 'msDS-AzLastImportedBizRulePath' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1819 NAME 'msDS-AzApplicationData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1949 NAME 'msDS-AzObjectGuid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:1950 NAME 'msDS-AzGenericData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2167 NAME 'msDS-PrimaryComputer' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat6:18.1.309 NAME 'msSFU30Name' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat6:18.1.339 NAME 'msSFU30NisDomain' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat6:18.1.346 NAME 'msSFU30PosixMember' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1426 NAME 'msCOM-UserPartitionSetLink' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+attributetype ( MSADat4:1441 NAME 'msDS-Cached-Membership' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:1442 NAME 'msDS-Cached-Membership-Time-Stamp' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+attributetype ( MSADat4:1443 NAME 'msDS-Site-Affinity' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+attributetype ( MSADat4:1460 NAME 'msDS-User-Account-Control-Computed' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1696 NAME 'lastLogonTimestamp' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+attributetype ( MSADat4:1785 NAME 'msIIS-FTPRoot' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1786 NAME 'msIIS-FTPDir' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1843 NAME 'msDRM-IdentityCertificate' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+attributetype ( MSADat4:1879 NAME 'msDS-SourceObjectDN' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1892 NAME 'msPKIRoamingTimeStamp' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:1893 NAME 'msPKIDPAPIMasterKeys' SYNTAX 'MSADdnWithOctetString' )
+attributetype ( MSADat4:1894 NAME 'msPKIAccountCredentials' SYNTAX 'MSADdnWithOctetString' )
+attributetype ( MSADat4:1913 NAME 'msRADIUS-FramedInterfaceId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat4:1914 NAME 'msRADIUS-SavedFramedInterfaceId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat4:1915 NAME 'msRADIUS-FramedIpv6Prefix' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat4:1916 NAME 'msRADIUS-SavedFramedIpv6Prefix' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+attributetype ( MSADat4:1917 NAME 'msRADIUS-FramedIpv6Route' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+attributetype ( MSADat4:1918 NAME 'msRADIUS-SavedFramedIpv6Route' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1929 NAME 'msDS-SecondaryKrbTgtNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1963 NAME 'msDS-SupportedEncryptionTypes' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1970 NAME 'msDS-LastSuccessfulInteractiveLogonTime' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1971 NAME 'msDS-LastFailedInteractiveLogonTime' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1972 NAME 'msDS-FailedInteractiveLogonCount' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1973 NAME 'msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1976 NAME 'msTSProfilePath' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1977 NAME 'msTSHomeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1978 NAME 'msTSHomeDrive' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1979 NAME 'msTSAllowLogon' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1980 NAME 'msTSRemoteControl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1981 NAME 'msTSMaxDisconnectionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1982 NAME 'msTSMaxConnectionTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1983 NAME 'msTSMaxIdleTime' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:1984 NAME 'msTSReconnectionAction' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1985 NAME 'msTSBrokenConnectionAction' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1986 NAME 'msTSConnectClientDrives' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1987 NAME 'msTSConnectPrinterDrives' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1988 NAME 'msTSDefaultToMainPrinter' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1989 NAME 'msTSWorkDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1990 NAME 'msTSInitialProgram' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1991 NAME 'msTSProperty01' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+attributetype ( MSADat4:1992 NAME 'msTSProperty02' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+attributetype ( MSADat4:1993 NAME 'msTSExpireDate' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+attributetype ( MSADat4:1994 NAME 'msTSLicenseVersion' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1995 NAME 'msTSManagingLS' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1996 NAME 'msDS-UserPasswordExpiryTimeComputed' SYNTAX 'MSADlargeInteger' SINGLE-VALUE )
+attributetype ( MSADat4:2008 NAME 'msTSManagingLS4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2005 NAME 'msTSManagingLS3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2002 NAME 'msTSManagingLS2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2006 NAME 'msTSExpireDate4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+attributetype ( MSADat4:2003 NAME 'msTSExpireDate3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+attributetype ( MSADat4:2000 NAME 'msTSExpireDate2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' SINGLE-VALUE )
+attributetype ( MSADat4:2004 NAME 'msTSLicenseVersion3' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2001 NAME 'msTSLicenseVersion2' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2007 NAME 'msTSLicenseVersion4' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2009 NAME 'msTSLSProperty01' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+attributetype ( MSADat4:2010 NAME 'msTSLSProperty02' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2022 NAME 'msDS-ResultantPSO' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+attributetype ( MSADat4:2050 NAME 'msPKI-CredentialRoamingTokens' SYNTAX 'MSADdnWithOctetString' )
+attributetype ( MSADat4:2073 NAME 'msTSPrimaryDesktop' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+attributetype ( MSADat4:2075 NAME 'msTSSecondaryDesktops' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2276 NAME 'msDS-SyncServerUrl' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+attributetype ( MSADat4:2285 NAME 'msDS-AssignedAuthNPolicySilo' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2288 NAME 'msDS-AuthNPolicySiloMembersBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2295 NAME 'msDS-AssignedAuthNPolicy' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+
+## NO-USER-MODIFICATION
+#attributetype ( MSADat4:1459 NAME 'msDS-Behavior-Version' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#attributetype ( MSADat4:1788 NAME 'msDS-PerUserTrustQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#attributetype ( MSADat4:1789 NAME 'msDS-AllUsersTrustQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+#attributetype ( MSADat4:1790 NAME 'msDS-PerUserTrustTombstonesQuota' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1717 NAME 'msDS-AdditionalDnsHostName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+## NO-USER-MODIFICATION
+attributetype ( MSADat4:1718 NAME 'msDS-AdditionalSamAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1783 NAME 'msDS-ExecuteScriptPassword' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:1923 NAME 'msDS-KrbTgtLink' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1924 NAME 'msDS-RevealedUsers' SYNTAX 'MSADdnWithOctetString' )
+attributetype ( MSADat4:1926 NAME 'msDS-NeverRevealGroup' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:1928 NAME 'msDS-RevealOnDemandGroup' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1940 NAME 'msDS-RevealedList' SYNTAX 'MSADdnWithString' )
+attributetype ( MSADat4:1959 NAME 'msDS-isGC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1960 NAME 'msDS-isRODC' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )
+attributetype ( MSADat4:1961 NAME 'msDS-SiteName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1962 NAME 'msDS-PromotionSettings' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:1966 NAME 'msTPM-OwnerInformation' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2025 NAME 'msDS-IsUserCachableAtRodc' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:2056 NAME 'msDS-HostServiceAccount' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2070 NAME 'msTSEndpointData' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2071 NAME 'msTSEndpointType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )
+attributetype ( MSADat4:2072 NAME 'msTSEndpointPlugin' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2074 NAME 'msTSPrimaryDesktopBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2078 NAME 'msTSSecondaryDesktopBL' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+attributetype ( MSADat4:2109 NAME 'msTPM-TpmInformationForComputer' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2166 NAME 'msDS-GenerationId' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:2180 NAME 'msImaging-ThumbprintHash' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat4:2181 NAME 'msImaging-HashAlgorithm' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )
+attributetype ( MSADat4:2234 NAME 'netbootDUID' SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' SINGLE-VALUE )
+attributetype ( MSADat6:18.1.323 NAME 'msSFU30Aliases' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+#attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' SINGLE-VALUE )
+
+
+attributetype ( MSADat4:856
+ NAME 'netbootNewMachineOU'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:13
+ NAME 'builtinCreationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1335
+ NAME 'pKIEnrollmentAccess'
+ SYNTAX 'MSADcis' )
+
+attributetype ( MSADat4:1333
+ NAME 'pKIExtendedKeyUsage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1123
+ NAME 'msNPCalledStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+
+attributetype ( MSADat4:539
+ NAME 'initialAuthIncoming'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:370
+ NAME 'objectClassCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:41
+ NAME 'generatedConnection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:911
+ NAME 'allowedChildClasses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:68
+ NAME 'machineArchitecture'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:767
+ NAME 'aCSMaxPeakBandwidth'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:72
+ NAME 'marshalledInterface'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:368
+ NAME 'rIDManagerReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:770
+ NAME 'aCSEnableACSService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1237
+ NAME 'mSMQRoutingService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1405
+ NAME 'mS-SQL-AllowQueuedUpdatingSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:648
+ NAME 'primaryTelexNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:8
+ NAME 'userAccountControl'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:563
+ NAME 'shellPropertyPages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:4
+ NAME 'replUpToDateVector'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:484
+ NAME 'fRSDirectoryFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:230
+ NAME 'printSeparatorFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1329
+ NAME 'pKIMaxIssuingDepth'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1307
+ NAME 'accountNameHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1386
+ NAME 'mS-SQL-GPSLongitude'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:562
+ NAME 'adminPropertyPages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:121
+ NAME 'securityIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:166
+ NAME 'groupMembershipSAM'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:659
+ NAME 'serviceDNSNameType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:585
+ NAME 'meetingIsEncrypted'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1400
+ NAME 'mS-SQL-Applications'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:330
+ NAME 'lastUpdateSequence'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:50
+ NAME 'lastContentIndexed'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:567
+ NAME 'meetingDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat4:880
+# NAME 'fRSTimeLastCommand'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:83
+ NAME 'monikerDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:321
+ NAME 'requiredCategories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:813
+ NAME 'upgradeProductCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:774
+ NAME 'aCSMaxNoOfLogFiles'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1370
+ NAME 'mS-SQL-CharacterSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:578
+ NAME 'meetingContactInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1397
+ NAME 'mS-SQL-CreationDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:32
+ NAME 'domainPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:703
+ NAME 'dhcpObjDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:573
+ NAME 'meetingApplication'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:518
+ NAME 'defaultHidingValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:875
+ NAME 'fRSMemberReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:701
+ NAME 'dhcpIdentification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:135
+ NAME 'trustAuthOutgoing'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:197
+ NAME 'systemMustContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1412
+ NAME 'primaryGroupToken'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:118
+ NAME 'rpcNsProfileEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:129
+ NAME 'trustAuthIncoming'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1225
+ NAME 'mSMQPrevSiteGates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:607
+ NAME 'queryPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:712
+ NAME 'optionDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1314
+ NAME 'aCSMaximumSDUSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:530
+ NAME 'nonSecurityMember'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:31
+ NAME 'fRSReplicaSetType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:763
+ NAME 'aCSTotalNoOfFlows'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:915
+ NAME 'possibleInferiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:851
+ NAME 'netbootMaxClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1385
+ NAME 'mS-SQL-GPSLatitude'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:765
+ NAME 'aCSPermissionBits'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:926
+ NAME 'mSMQTransactional'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1390
+ NAME 'mS-SQL-Description'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:913
+ NAME 'allowedAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:491
+ NAME 'fRSFaultCondition'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:54
+ NAME 'tombstoneLifetime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:809
+ NAME 'remoteStorageGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:644
+ NAME 'showInAddressBook'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:213
+ NAME 'defaultClassStore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:577
+ NAME 'meetingOriginator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:656
+ NAME 'userPrincipalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1316
+ NAME 'aCSMinimumLatency'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat2:617
+# NAME 'homePostalAddress'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:638
+ NAME 'isPrivilegeHolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:533
+ NAME 'fRSReplicaSetGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:371
+ NAME 'rIDAllocationPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1327
+ NAME 'pKIDefaultKeySpec'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:537
+ NAME 'dynamicLDAPServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:516
+ NAME 'serverReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:500
+ NAME 'fRSServiceCommand'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1304
+ NAME 'sDRightsEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1249
+ NAME 'proxiedObjectName'
+ SYNTAX 'MSADdnWithOctetString'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:586
+ NAME 'meetingRecurrence'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:251
+ NAME 'cOMTreatAsClassId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1245
+ NAME 'globalAddressList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:908
+ NAME 'extendedClassInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:459
+ NAME 'machineWidePolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:356
+ NAME 'foreignIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1242
+ NAME 'dNReferenceUpdate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:134
+ NAME 'trustPosixOffset'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:36
+ NAME 'enabledConnection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:627
+ NAME 'ipsecNFAReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:86
+ NAME 'userWorkstations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:301
+ NAME 'garbageCollPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:933
+ NAME 'mSMQComputerType'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:65
+ NAME 'logonWorkstation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:921
+ NAME 'mSMQJournalQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:108
+ NAME 'remoteSourceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:95
+ NAME 'pwdHistoryLength'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:920
+ NAME 'mSMQBasePriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:196
+ NAME 'systemMayContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:1407
+ NAME 'mS-SQL-ThirdParty'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1243
+ NAME 'mSMQQueueNameExt'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:485
+ NAME 'fRSUpdateTimeout'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:924
+ NAME 'mSMQPrivacyLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:615
+ NAME 'shellContextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:618
+ NAME 'wellKnownObjects'
+ SYNTAX 'MSADdnWithOctetString' )
+
+attributetype ( MSADat4:789
+ NAME 'transportDLLName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:458
+ NAME 'qualityOfService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:73
+ NAME 'lockoutThreshold'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:105
+ NAME 'remoteServerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:694
+ NAME 'previousParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1345
+ NAME 'dSUIShellMaximum'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:303
+ NAME 'notificationList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1244
+ NAME 'addressBookRoots'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:878
+ NAME 'fRSPrimaryMember'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat4:587
+# NAME 'meetingStartTime'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53' )
+
+attributetype ( MSADat4:1310
+ NAME 'mSMQSiteGatesMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:709
+ NAME 'dhcpReservations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:614
+ NAME 'adminContextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1332
+ NAME 'pKIOverlapPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:142
+ NAME 'winsockAddresses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:923
+ NAME 'mSMQAuthenticate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1344
+ NAME 'dSUIAdminMaximum'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:848
+ NAME 'appSchemaVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:123
+ NAME 'serviceClassInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:769
+ NAME 'aCSEventLogLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:751
+ NAME 'userSharedFolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:421
+ NAME 'domainWidePolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:669
+ NAME 'rIDSetReferences'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:815
+ NAME 'canUpgradeScript'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:610
+ NAME 'classDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat2:226
+ NAME 'adminDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:67
+ NAME 'lSAModifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:509
+ NAME 'serviceClassName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:56
+ NAME 'localPolicyFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:115
+ NAME 'rpcNsInterfaceID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:194
+ NAME 'adminDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:753
+ NAME 'nameServiceFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:589
+ NAME 'meetingBandwidth'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:755
+ NAME 'domainIdentifier'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:370
+ NAME 'rIDAvailablePool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:655
+ NAME 'legacyExchangeDN'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:470
+ NAME 'trustAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:535
+ NAME 'fRSRootSecurity'
+ SYNTAX 'MSADcis'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:532
+ NAME 'superiorDNSRoot'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:278
+ NAME 'printMaxYExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:277
+ NAME 'printMaxXExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:280
+ NAME 'printMinYExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:279
+ NAME 'printMinXExtent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:32
+ NAME 'attributeSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:247
+ NAME 'printAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:152
+ NAME 'groupAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:816
+ NAME 'fileExtPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:930
+ NAME 'mSMQServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:363
+ NAME 'operatingSystem'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1371
+ NAME 'mS-SQL-SortOrder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:329
+ NAME 'versionNumberLo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:884
+ NAME 'msRRASAttribute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:781
+ NAME 'lastKnownParent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1209
+ NAME 'shortServerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:60
+ NAME 'lockoutDuration'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:232
+ NAME 'defaultPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:754
+ NAME 'rpcNsEntryFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:713
+ NAME 'optionsLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:328
+ NAME 'versionNumberHi'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:366
+ NAME 'rpcNsAnnotation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:886
+ NAME 'purportedSearch'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:776
+ NAME 'aCSDSBMPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:961
+ NAME 'mSMQSiteForeign'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' )
+
+attributetype ( MSADat4:335
+ NAME 'currentLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:570
+ NAME 'meetingProtocol'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:420
+ NAME 'publicKeyPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1402
+ NAME 'mS-SQL-Publisher'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:812
+ NAME 'createWizardExt'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1373
+ NAME 'mS-SQL-Clustered'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:334
+ NAME 'volTableIdxGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:696
+ NAME 'currentParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:504
+ NAME 'seqNotification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:515
+ NAME 'serverReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1119
+ NAME 'msNPAllowDialin'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1387
+ NAME 'mS-SQL-GPSHeight'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1378
+ NAME 'mS-SQL-AppleTalk'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:269
+ NAME 'linkTrackSecret'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:378
+ NAME 'dnsAllowDynamic'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:49
+ NAME 'badPasswordTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:637
+ NAME 'privilegeHolder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:289
+ NAME 'printMediaReady'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:288
+ NAME 'printMACAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:66
+ NAME 'lSACreationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:569
+ NAME 'meetingLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:784
+ NAME 'aCSIdentityName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1410
+ NAME 'mS-DS-CreatorSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1374
+ NAME 'mS-SQL-NamedPipe'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:843
+ NAME 'lDAPAdminLimits'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat2:460
+ NAME 'lDAPDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:218
+ NAME 'applicationName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:695
+ NAME 'pendingParentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:779
+ NAME 'aCSCacheTimeout'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:574
+ NAME 'meetingLanguage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:778
+ NAME 'aCSDSBMDeadTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:697
+ NAME 'cACertificateDN'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:138
+ NAME 'userParameters'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:132
+ NAME 'trustDirection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:962
+ NAME 'mSMQQueueQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:936
+ NAME 'mSMQEncryptKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:885
+ NAME 'terminalServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:233
+ NAME 'printStartTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:664
+ NAME 'syncWithObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:344
+ NAME 'groupsToIgnore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:665
+ NAME 'syncMembership'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:666
+ NAME 'syncAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:214
+ NAME 'nextLevelStore'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:302
+ NAME 'sAMAccountType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1401
+ NAME 'mS-SQL-Keywords'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat2:210
+ NAME 'proxyAddresses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:284
+ NAME 'bytesPerMinute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:241
+ NAME 'printMaxCopies'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:98
+ NAME 'primaryGroupID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:89
+ NAME 'nTGroupMembers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:1228
+ NAME 'mSMQDsServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:43
+ NAME 'fRSVersionGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:486
+ NAME 'fRSWorkingPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:18
+ NAME 'otherTelephone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat2:277
+ NAME 'otherHomePhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:151
+ NAME 'oEMInformation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:459
+ NAME 'networkAddress'
+ SYNTAX 'MSADtelex' )
+
+attributetype ( MSADat4:966
+ NAME 'mSMQDigestsMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:568
+ NAME 'meetingKeyword'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:844
+ NAME 'lDAPIPDenyList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:847
+ NAME 'installUiLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:894
+ NAME 'gPCFileSysPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:488
+ NAME 'fRSStagingPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:351
+ NAME 'auxiliaryClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:159
+ NAME 'accountExpires'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:718
+ NAME 'dhcpProperties'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:346
+ NAME 'desktopProfile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:762
+ NAME 'aCSServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat2:610
+# NAME 'employeeNumber'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:1213
+ NAME 'assocNTAccount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:498
+ NAME 'creationWizard'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:253
+ NAME 'cOMOtherProgId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:202
+ NAME 'auditingPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:635
+ NAME 'privilegeValue'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1366
+ NAME 'mS-SQL-Location'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1334
+ NAME 'pKIDefaultCSPs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:270
+ NAME 'printShareName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:33
+ NAME 'isSingleValued'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:472
+ NAME 'domainCrossRef'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1240
+ NAME 'netbootSIFFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:250
+ NAME 'cOMUniqueLIBID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:657
+ NAME 'serviceDNSName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:782
+ NAME 'objectCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:122
+ NAME 'serviceClassID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:720
+ NAME 'dhcpUpdateTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:221
+ NAME 'sAMAccountName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat4:588
+# NAME 'meetingEndTime'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53' )
+
+attributetype ( MSADat4:1389
+ NAME 'mS-SQL-Language'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:777
+ NAME 'aCSDSBMRefresh'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1393
+ NAME 'mS-SQL-Database'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:20
+ NAME 'cOMInterfaceID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1403
+ NAME 'mS-SQL-AllowKnownPullSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1394
+ NAME 'mS-SQL-AllowAnonymousSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:654
+ NAME 'managedObjects'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat2:8
+ NAME 'possSuperiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:791
+ NAME 'transportType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:345
+ NAME 'groupPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:117
+ NAME 'rpcNsPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:917
+ NAME 'mSMQQueueType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:141
+ NAME 'versionNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:121
+ NAME 'uSNLastObjRem'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1346
+ NAME 'templateRoots'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:93
+ NAME 'pwdProperties'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:290
+ NAME 'printNumberUp'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:536
+ NAME 'fRSExtensions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:286
+ NAME 'printRateUnit'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:846
+ NAME 'msiScriptSize'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:274
+ NAME 'printSpooling'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:608
+ NAME 'queryPolicyBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:103
+ NAME 'proxyLifetime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:144
+ NAME 'operatorCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:860
+ NAME 'netbootServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:369
+ NAME 'fSMORoleOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:276
+ NAME 'driverVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1388
+ NAME 'mS-SQL-Version'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:939
+ NAME 'mSMQNameStyle'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:471
+ NAME 'schemaVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:436
+ NAME 'directReports'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat2:255
+ NAME 'addressSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:235
+ NAME 'printFormName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:15
+ NAME 'msiScriptPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1312
+ NAME 'aCSServerList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+#attributetype ( MSADat2:615
+# NAME 'personalTitle'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:1305
+ NAME 'moveTreeState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:945
+ NAME 'mSMQSiteGates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1238
+ NAME 'mSMQDsService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:76
+ NAME 'objectVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1414
+ NAME 'dNSTombstoned'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:941
+ NAME 'mSMQLongLived'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:534
+ NAME 'fRSLevelLimit'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:845
+ NAME 'msiScriptName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat4:44
+# NAME 'homeDirectory'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:698
+ NAME 'dhcpUniqueKey'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:227
+ NAME 'extensionName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+#attributetype ( MSADat2:256
+# NAME 'streetAddress'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:113
+ NAME 'rpcNsBindings'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:237
+ NAME 'printBinNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:109
+ NAME 'replicaSource'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:246
+ NAME 'printLanguage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1365
+ NAME 'mS-SQL-Contact'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:357
+ NAME 'nTMixedDomain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:483
+ NAME 'fRSFileFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:332
+ NAME 'birthLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:682
+ NAME 'friendlyNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:622
+ NAME 'ipsecDataType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:584
+ NAME 'meetingRating'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:681
+ NAME 'indexedScopes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:312
+ NAME 'rpcNsObjectID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:168
+ NAME 'modifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:218
+ NAME 'oMObjectClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:772
+ NAME 'aCSPolicyName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:502
+ NAME 'timeVolChange'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:337
+ NAME 'currMachineId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:120
+ NAME 'schemaFlagsEx'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1356
+ NAME 'validAccesses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:158
+ NAME 'domainReplica'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1309
+ NAME 'mSMQInterval2'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1308
+ NAME 'mSMQInterval1'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:916
+ NAME 'canonicalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:94
+ NAME 'ntPwdHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:133
+ NAME 'trustPartner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:160
+ NAME 'lmPwdHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:1380
+ NAME 'mS-SQL-Status'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:469
+ NAME 'USNIntersite'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:858
+ NAME 'netbootTools'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:99
+ NAME 'priorSetTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1367
+ NAME 'mS-SQL-Memory'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:950
+ NAME 'mSMQServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat2:613
+# NAME 'employeeType'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:27
+ NAME 'currentValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:822
+ NAME 'siteLinkList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:107
+ NAME 'remoteSource'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:325
+ NAME 'setupCommand'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:212
+ NAME 'dSHeuristics'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1336
+ NAME 'replInterval'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:234
+ NAME 'printEndTime'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:1
+ NAME 'instanceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:722
+ NAME 'otherIpPhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:965
+ NAME 'mSMQSiteName'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:579
+ NAME 'meetingOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:242
+ NAME 'printCollate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:480
+ NAME 'defaultGroup'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:79
+ NAME 'minPwdLength'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:864
+ NAME 'netbootSCPBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:650
+ NAME 'mhsORAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+#attributetype ( MSADat4:651
+# NAME 'otherMailbox'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:367
+ NAME 'rpcNsCodeset'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:14
+ NAME 'hasMasterNCs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:952
+ NAME 'mSMQMigrated'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:74
+ NAME 'dSASignature'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:115
+ NAME 'invocationId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:254
+ NAME 'cOMTypelibId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:26
+ NAME 'creationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:581
+ NAME 'meetingScope'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:336
+ NAME 'volTableGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:513
+ NAME 'siteObjectBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:756
+ NAME 'aCSTimeOfDay'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:757
+ NAME 'aCSDirection'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:77
+ NAME 'maxTicketAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:481
+ NAME 'schemaUpdate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:80
+ NAME 'minTicketAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:628
+ NAME 'ipsecNegotiationPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:327
+ NAME 'helpFileName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:148
+ NAME 'schemaIDGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:810
+ NAME 'createDialog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:964
+ NAME 'mSMQNt4Flags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:327
+ NAME 'packageFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:464
+ NAME 'wWWHomePage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:507
+ NAME 'volumeCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:273
+ NAME 'printStatus'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:890
+ NAME 'uPNSuffixes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:471
+ NAME 'trustParent'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1301
+ NAME 'tokenGroups'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:375
+ NAME 'systemFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:667
+ NAME 'syncWithSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1306
+ NAME 'dNSProperty'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:710
+ NAME 'superScopes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:1347
+ NAME 'sPNMappings'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:272
+ NAME 'printNotify'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:282
+ NAME 'printMemory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:154
+ NAME 'serverState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:942
+ NAME 'mSMQVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:373
+ NAME 'rIDUsedPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1355
+ NAME 'queryFilter'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:300
+ NAME 'printerName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:97
+ NAME 'preferredOU'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:649
+ NAME 'primaryInternationalISDNNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:333
+ NAME 'oMTIndxGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1337
+ NAME 'mSMQUserSid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:487
+ NAME 'fRSRootPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:918
+ NAME 'mSMQJournal'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:499
+ NAME 'contextMenu'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:764
+ NAME 'aCSPriority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:937
+ NAME 'mSMQSignKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:359
+ NAME 'netbootGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:925
+ NAME 'mSMQOwnerID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:24
+ NAME 'mustContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:379
+ NAME 'dnsAllowXFR'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1379
+ NAME 'mS-SQL-Vines'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:948
+ NAME 'mSMQDigests'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:662
+ NAME 'lockoutTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:53
+ NAME 'lastSetTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:25
+ NAME 'countryCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1377
+ NAME 'mS-SQL-TCPIP'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:934
+ NAME 'mSMQForeign'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:571
+ NAME 'meetingType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:714
+ NAME 'dhcpOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:704
+ NAME 'dhcpServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:283
+ NAME 'assetNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:350
+ NAME 'addressType'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:940
+ NAME 'mSMQCSPName'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:671
+ NAME 'msiFileList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:619
+ NAME 'dNSHostName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:705
+ NAME 'dhcpSubnets'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:1328
+ NAME 'pKIKeyUsage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:30
+ NAME 'attributeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:506
+ NAME 'objectCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:503
+ NAME 'timeRefresh'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:139
+ NAME 'profilePath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:818
+ NAME 'productCode'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:647
+ NAME 'otherMobile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:12
+ NAME 'badPwdCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1368
+ NAME 'mS-SQL-Build'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat2:13
+# NAME 'displayName'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:87
+ NAME 'nETBIOSName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1395
+ NAME 'mS-SQL-Alias'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:75
+ NAME 'maxRenewAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:806
+ NAME 'treatAsLeaf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:960
+ NAME 'mSMQNt4Stub'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:324
+ NAME 'packageType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1212
+ NAME 'isEphemeral'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:36
+ NAME 'dMDLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:715
+ NAME 'dhcpClasses'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:39
+ NAME 'forceLogoff'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:2
+ NAME 'whenCreated'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:566
+ NAME 'meetingName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:786
+ NAME 'mailAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:590
+ NAME 'meetingBlob'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:71
+ NAME 'machineRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:334
+ NAME 'searchFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:3
+ NAME 'whenChanged'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:702
+ NAME 'dhcpObjName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:897
+ NAME 'aCSMaxAggregatePeakRatePerUser'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:326
+ NAME 'packageName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:170
+ NAME 'systemOnly'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:935
+ NAME 'mSMQOSType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:680
+ NAME 'queryPoint'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:271
+ NAME 'printOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:19
+ NAME 'uSNCreated'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:494
+ NAME 'siteServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:114
+ NAME 'rpcNsGroup'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:609
+ NAME 'sIDHistory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:882
+ NAME 'fRSVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:64
+ NAME 'logonHours'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:854
+ NAME 'netbootAnswerOnlyValidClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:96
+ NAME 'pwdLastSet'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:243
+ NAME 'printColor'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1391
+ NAME 'mS-SQL-Type'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:40
+ NAME 'fromServer'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:157
+ NAME 'serverRole'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:100
+ NAME 'priorValue'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:169
+ NAME 'logonCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:90
+ NAME 'unicodePwd'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:21
+ NAME 'subClassOf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1396
+ NAME 'mS-SQL-Size'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:101
+ NAME 'privateKey'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:512
+ NAME 'siteObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:62
+ NAME 'scriptPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:223
+ NAME 'serverName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:953
+ NAME 'mSMQSiteID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:340
+ NAME 'rightsGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:374
+ NAME 'rIDNextRID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:583
+ NAME 'meetingURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat2:400
+ NAME 'addressEntryDisplayTableMSDOS'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:76
+ NAME 'maxStorage'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:35
+ NAME 'rangeUpper'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:34
+ NAME 'rangeLower'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:118
+ NAME 'otherPager'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:639
+ NAME 'isMemberOfPartialAttributeSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1224
+ NAME 'parentGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:141
+ NAME 'department'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:25
+ NAME 'mayContain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:150
+ NAME 'adminCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:51
+ NAME 'lastLogoff'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1409
+ NAME 'masteredBy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:35
+ NAME 'employeeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:719
+ NAME 'dhcpMaxKey'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:229
+ NAME 'driverName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1363
+ NAME 'mS-SQL-Name'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:322
+ NAME 'categoryId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:889
+ NAME 'additionalTrustedServiceNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1354
+ NAME 'scopeFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:672
+ NAME 'categories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:855
+ NAME 'netbootNewMachineNamingPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:19
+ NAME 'cOMClassID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:120
+ NAME 'uSNChanged'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:2
+ NAME 'objectGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:707
+ NAME 'dhcpRanges'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1358
+ NAME 'schemaInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:646
+ NAME 'otherFacsimileTelephoneNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:520
+ NAME 'machinePasswordChangeInterval'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:674
+ NAME 'rootTrust'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:136
+ NAME 'trustType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:750
+ NAME 'groupType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:896
+ NAME 'uSNSource'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:919
+ NAME 'mSMQQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:927
+ NAME 'mSMQSites'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:910
+ NAME 'fromEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' )
+
+attributetype ( MSADat4:1376
+ NAME 'mS-SQL-SPX'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:892
+ NAME 'gPOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:814
+ NAME 'msiScript'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:285
+ NAME 'printRate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:683
+ NAME 'cRLPartitionedRevocationList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:652
+ NAME 'assistant'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:490
+ NAME 'fRSDSPoll'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:663
+ NAME 'partialAttributeDeletionList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:52
+ NAME 'lastLogon'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:22
+ NAME 'governsID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:341
+ NAME 'appliesTo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:268
+ NAME 'eFSPolicy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:155
+ NAME 'uASCompat'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:538
+ NAME 'prefixMap'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:661
+ NAME 'isDefunct'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:708
+ NAME 'dhcpSites'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:888
+ NAME 'iPSECNegotiationPolicyAction'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:382
+ NAME 'dnsRecord'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:21
+ NAME 'cOMProgID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:45
+ NAME 'homeDrive'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:580
+ NAME 'meetingIP'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1321
+ NAME 'aCSNonReservedMinPolicedSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:717
+ NAME 'dhcpState'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:922
+ NAME 'mSMQLabel'
+ SYNTAX 'MSADtelex'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:74
+ NAME 'maxPwdAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:78
+ NAME 'minPwdAge'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:689
+ NAME 'cRLObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:146
+ NAME 'objectSid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:565
+ NAME 'meetingID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:620
+ NAME 'ipsecName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:48
+ NAME 'isDeleted'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:760
+ NAME 'aCSAggregateTokenRatePerUser'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:623
+ NAME 'ipsecData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:668
+ NAME 'domainCAs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:687
+ NAME 'cAConnect'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:238
+ NAME 'printMaxResolutionSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:700
+ NAME 'dhcpFlags'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:402
+ NAME 'helpData16'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:653
+ NAME 'managedBy'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:9
+ NAME 'helpData32'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:944
+ NAME 'mSMQSite2'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:943
+ NAME 'mSMQSite1'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:677
+ NAME 'replTopologyStayOfExecution'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:912
+ NAME 'allowedChildClassesEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:231
+ NAME 'oMSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:231
+ NAME 'priority'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:48
+ NAME 'keywords'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:946
+ NAME 'mSMQCost'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:821
+ NAME 'siteList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:145
+ NAME 'revision'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:91
+ NAME 'repsFrom'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:645
+ NAME 'userCert'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:951
+ NAME 'mSMQQMID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:228
+ NAME 'portName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:859
+ NAME 'netbootLocallyInstalledOSes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:261
+ NAME 'division'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:902
+ NAME 'aCSMaxSizeOfRSVPAccountFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:699
+ NAME 'dhcpType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:301
+ NAME 'wbemPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:362
+ NAME 'siteGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:26
+ NAME 'rDNAttID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:900
+ NAME 'aCSRSVPAccountFilesLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1226
+ NAME 'mSMQDependentClientServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:222
+ NAME 'location'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:874
+ NAME 'fRSFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:219
+ NAME 'iconPath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:688
+ NAME 'cAWEBURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:716
+ NAME 'mscopeId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:660
+ NAME 'treeName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:211
+ NAME 'schedule'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:557
+ NAME 'parentCA'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:249
+ NAME 'cOMCLSID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:675
+ NAME 'catalogs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:102
+ NAME 'memberOf'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:690
+ NAME 'cAUsages'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:706
+ NAME 'dhcpMask'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44' )
+
+attributetype ( MSADat4:511
+ NAME 'flatName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:686
+ NAME 'domainID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:58
+ NAME 'localeID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:16
+ NAME 'codePage'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:768
+ NAME 'aCSEnableRSVPMessageLogging'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:240
+ NAME 'printOrientationsSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:883
+ NAME 'msRRASVendorAttributeEntry'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1246
+ NAME 'interSiteTopologyGenerator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:307
+ NAME 'options'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:28
+ NAME 'dnsRoot'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:887
+ NAME 'iPSECNegotiationPolicyType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1392
+ NAME 'mS-SQL-InformationDirectory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:365
+ NAME 'operatingSystemServicePack'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:88
+ NAME 'nextRid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:865
+ NAME 'pekList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:7
+ NAME 'subRefs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:505
+ NAME 'oMTGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:205
+ NAME 'pKTGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:146
+ NAME 'company'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:82
+ NAME 'moniker'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:156
+ NAME 'comment'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:721
+ NAME 'ipPhone'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1361
+ NAME 'mS-DS-ConsistencyChildCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:679
+ NAME 'creator'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:137
+ NAME 'uNCName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:55
+ NAME 'dBCSPwd'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1239
+ NAME 'mSMQDependentClientService'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:684
+ NAME 'certificateAuthorityObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:621
+ NAME 'ipsecID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:914
+ NAME 'allowedAttributesEffective'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+#attributetype ( MSADat2:598
+# NAME 'dmdName'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:759
+ NAME 'aCSMaxPeakBandwidthPerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:557
+ NAME 'Enabled'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:326
+ NAME 'perRecipDialogDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1248
+ NAME 'interSiteTopologyFailover'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:895
+ NAME 'transportAddressAttribute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:852
+ NAME 'netbootCurrentClientCount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:372
+ NAME 'rIDPreviousAllocationPool'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:83
+ NAME 'repsTo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:224
+ NAME 'defaultSecurityDescriptor'
+ SYNTAX 'MSADsecDesc'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:519
+ NAME 'lastBackupRestorationTime'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:873
+ NAME 'fRSControlOutboundBacklog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:255
+ NAME 'vendor'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:891
+ NAME 'gPLink'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:214
+ NAME 'originalDisplayTableMSDOS'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:50
+ NAME 'linkID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1130
+ NAME 'msNPSavedCallingStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:49
+ NAME 'mAPIID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:510
+ NAME 'serviceBindingInformation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:16
+ NAME 'nCName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1303
+ NAME 'tokenGroupsNoGCAcceptable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:1418
+ NAME 'tokenGroupsGlobalAndUniversal'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:1190
+ NAME 'msRASSavedFramedIPAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:766
+ NAME 'aCSAllocableRSVPBandwidth'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:61
+ NAME 'lockOutObservationWindow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:857
+ NAME 'netbootIntelliMirrorOSes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1320
+ NAME 'aCSNonReservedMaxSDUSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:265
+ NAME 'notes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:673
+ NAME 'retiredReplDSASignatures'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1313
+ NAME 'aCSMaxTokenBucketPerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:324
+ NAME 'addressEntryDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1317
+ NAME 'aCSMinimumDelayVariation'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:872
+ NAME 'fRSControlInboundBacklog'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:38
+ NAME 'flags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1399
+ NAME 'mS-SQL-LastDiagnosticDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1348
+ NAME 'gPCMachineExtensionNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1411
+ NAME 'ms-DS-MachineAccountQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:325
+ NAME 'perMsgDialogDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:57
+ NAME 'defaultLocalPolicyObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1189
+ NAME 'msRASSavedCallbackNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:685
+ NAME 'parentCACertificateChain'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:893
+ NAME 'gPCFunctionalityVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:879
+ NAME 'fRSServiceCommandStatus'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1319
+ NAME 'aCSNonReservedTokenSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:775
+ NAME 'aCSMaxSizeOfRSVPLogFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:135
+ NAME 'cost'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:81
+ NAME 'modifiedCountAtLastProm'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:773
+ NAME 'aCSRSVPLogFilesLocation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat2:81
+# NAME 'info'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:125
+ NAME 'supplementalCredentials'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:819
+ NAME 'bridgeheadTransportList'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:967
+ NAME 'mSMQSignCertificatesMig'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+## NO-USER-MODIFICATION
+#attributetype ( MSADat4:1
+# NAME 'name'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:1153
+ NAME 'msRADIUSFramedIPAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1408
+ NAME 'mS-DS-ReplicatesNCReason'
+ SYNTAX 'MSADdnWithOctetString' )
+
+attributetype ( MSADat4:899
+ NAME 'aCSEnableRSVPAccounting'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+#attributetype ( MSADat4:881
+# NAME 'fRSTimeLastConfigChange'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.53'
+# SINGLE-VALUE )
+
+attributetype ( MSADat4:281
+ NAME 'printStaplingSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1247
+ NAME 'interSiteTopologyRenew'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:364
+ NAME 'operatingSystemVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:91
+ NAME 'otherLoginWorkstations'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:849
+ NAME 'netbootAllowNewClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1372
+ NAME 'mS-SQL-UnicodeSortOrder'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:749
+ NAME 'url'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:206
+ NAME 'pKT'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:199
+ NAME 'serviceInstanceVersion'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:169
+ NAME 'showInAdvancedViewOnly'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:758
+ NAME 'aCSMaxTokenRatePerFlow'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:868
+ NAME 'isCriticalSystemObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:576
+ NAME 'meetingMaxParticipants'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1208
+ NAME 'aNR'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:153
+ NAME 'rid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:523
+ NAME 'proxyGenerationEnabled'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:871
+ NAME 'fRSControlDataCreation'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:692
+ NAME 'previousCACertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:24
+ NAME 'contentIndexingAllowed'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:633
+ NAME 'policyReplicationFlags'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:870
+ NAME 'frsComputerReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1318
+ NAME 'aCSNonReservedPeakRate'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:901
+ NAME 'aCSMaxNoOfAccountFiles'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:514
+ NAME 'physicalLocationObject'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:928
+ NAME 'mSMQOutRoutingServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:820
+ NAME 'bridgeheadServerListBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1145
+ NAME 'msRADIUSCallbackNumber'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:361
+ NAME 'netbootMachineFilePath'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:963
+ NAME 'mSMQQueueJournalQuota'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:853
+ NAME 'netbootAnswerRequests'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:415
+ NAME 'operatingSystemHotfix'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:149
+ NAME 'attributeSecurityGUID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:711
+ NAME 'superScopeDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1359
+ NAME 'otherWellKnownObjects'
+ SYNTAX 'MSADdnWithOctetString' )
+
+attributetype ( MSADat4:780
+ NAME 'aCSNonReservedTxLimit'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:11
+ NAME 'authenticationOptions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:867
+ NAME 'altSecurityIdentities'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1349
+ NAME 'gPCUserExtensionNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:358
+ NAME 'netbootInitialization'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1364
+ NAME 'mS-SQL-RegisteredOwner'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:761
+ NAME 'aCSMaxDurationPerFlow'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1330
+ NAME 'pKICriticalExtensions'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:748
+ NAME 'attributeDisplayNames'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1404
+ NAME 'mS-SQL-AllowImmediateUpdatingSubscription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1191
+ NAME 'msRASSavedFramedRoute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+
+attributetype ( MSADat4:752
+ NAME 'userSharedFolderOther'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+#attributetype ( MSADat2:131
+# NAME 'co'
+# SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+# SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:909
+ NAME 'extendedAttributeInfo'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1241
+ NAME 'netbootMirrorDataFile'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1315
+ NAME 'aCSMinimumPolicedSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1353
+ NAME 'localizationDisplayId'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:582
+ NAME 'meetingAdvertiseScope'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1343
+ NAME 'dSUIAdminNotification'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:1381
+ NAME 'mS-SQL-LastUpdatedDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:1357
+ NAME 'dSCorePropagationData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.24' )
+
+attributetype ( MSADat4:320
+ NAME 'implementedCategories'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+attributetype ( MSADat4:783
+ NAME 'defaultObjectCategory'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:422
+ NAME 'domainPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:929
+ NAME 'mSMQInRoutingServers'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:1311
+ NAME 'printDuplexSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:693
+ NAME 'pendingCACertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:281
+ NAME 'nTSecurityDescriptor'
+ SYNTAX 'MSADsecDesc'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:198
+ NAME 'systemAuxiliaryClass'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat4:898
+ NAME 'aCSNonReservedTxSize'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1382
+ NAME 'mS-SQL-InformationURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:3
+ NAME 'replPropertyMetaData'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1384
+ NAME 'mS-SQL-PublicationURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:275
+ NAME 'printKeepPrintedJobs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:267
+ NAME 'uSNDSALastObjRemoved'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:381
+ NAME 'dnsNotifySecondaries'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:1360
+ NAME 'mS-DS-ConsistencyGuid'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:869
+ NAME 'frsComputerReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1369
+ NAME 'mS-SQL-ServiceAccount'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1124
+ NAME 'msNPCallingStationID'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+
+attributetype ( MSADat4:947
+ NAME 'mSMQSignCertificates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:624
+ NAME 'ipsecOwnersReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:14
+ NAME 'builtinModifiedCount'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:634
+ NAME 'privilegeDisplayName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:380
+ NAME 'dnsSecureSecondaries'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' )
+
+attributetype ( MSADat4:817
+ NAME 'localizedDescription'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:195
+ NAME 'systemPossSuperiors'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' )
+
+attributetype ( MSADat2:353
+ NAME 'displayNamePrintable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.44'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:771
+ NAME 'servicePrincipalName'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:866
+ NAME 'pekKeyChangeInterval'
+ SYNTAX 'MSADlargeInteger'
+ SINGLE-VALUE )
+
+attributetype ( MSADat2:445
+ NAME 'originalDisplayTable'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1398
+ NAME 'mS-SQL-LastBackupDate'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:517
+ NAME 'ipsecPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:823
+ NAME 'certificateTemplates'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:15
+ NAME 'hasPartialReplicaNCs'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:457
+ NAME 'localPolicyReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat2:380
+ NAME 'extendedCharsAllowed'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:629
+ NAME 'ipsecFilterReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:626
+ NAME 'ipsecISAKMPReference'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:876
+ NAME 'fRSMemberReferenceBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:314
+ NAME 'rpcNsTransferSyntax'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1227
+ NAME 'mSMQRoutingServices'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1375
+ NAME 'mS-SQL-MultiProtocol'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:825
+ NAME 'enrollmentProviders'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:287
+ NAME 'printNetworkAddress'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1171
+ NAME 'msRADIUSServiceType'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:631
+ NAME 'printPagesPerMinute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:299
+ NAME 'printMediaSupported'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
+
+attributetype ( MSADat4:824
+ NAME 'signatureAlgorithms'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:877
+ NAME 'fRSPartnerAuthLevel'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:636
+ NAME 'privilegeAttributes'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.27'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:640
+ NAME 'partialAttributeSet'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:850
+ NAME 'netbootLimitClients'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1383
+ NAME 'mS-SQL-ConnectionURL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1406
+ NAME 'mS-SQL-AllowSnapshotFilesFTPDownloading'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.7'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1331
+ NAME 'pKIExpirationPeriod'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40'
+ SINGLE-VALUE )
+
+# NO-USER-MODIFICATION
+attributetype ( MSADat4:531
+ NAME 'nonSecurityMemberBL'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )
+
+attributetype ( MSADat4:540
+ NAME 'initialAuthOutgoing'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
+ SINGLE-VALUE )
+
+attributetype ( MSADat4:1158
+ NAME 'msRADIUSFramedRoute'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )
+
+attributetype ( MSADat4:200
+ NAME 'controlAccessRights'
+ SYNTAX '1.3.6.1.4.1.1466.115.121.1.40' )
+
+objectclass ( MSADoc5:0 NAME 'mstop' SUP top ABSTRACT
+ MUST (objectClass $ instanceType
+ $ nTSecurityDescriptor $ objectCategory ) MAY (cn $ description $ distinguishedName
+ $ whenCreated $ whenChanged $ subRefs $ displayName $ uSNCreated $ isDeleted
+ $ dSASignature $ objectVersion $ repsTo $ repsFrom $ memberOf $ ownerBL
+ $ uSNChanged $ uSNLastObjRem $ showInAdvancedViewOnly $ adminDisplayName
+ $ proxyAddresses $ adminDescription $ extensionName $ uSNDSALastObjRemoved
+ $ displayNamePrintable $ directReports $ wWWHomePage $ USNIntersite $ name
+ $ objectGUID $ replPropertyMetaData $ replUpToDateVector $ flags $ revision
+ $ wbemPath $ fSMORoleOwner $ systemFlags $ siteObjectBL $ serverReferenceBL
+ $ nonSecurityMemberBL $ queryPolicyBL $ wellKnownObjects $ isPrivilegeHolder
+ $ partialAttributeSet $ managedObjects $ partialAttributeDeletionList $ url
+ $ lastKnownParent $ bridgeheadServerListBL $ netbootSCPBL $ isCriticalSystemObject
+ $ frsComputerReferenceBL $ fRSMemberReferenceBL $ uSNSource $ fromEntry
+ $ allowedChildClasses $ allowedChildClassesEffective $ allowedAttributes
+ $ allowedAttributesEffective $ possibleInferiors $ canonicalName $ proxiedObjectName
+ $ sDRightsEffective $ dSCorePropagationData $ otherWellKnownObjects
+ $ mS-DS-ConsistencyGuid $ mS-DS-ConsistencyChildCount $ masteredBy
+ $ msCOM-PartitionSetLink $ msCOM-UserLink $ msDS-Approx-Immed-Subordinates
+ $ msDS-NCReplCursors $ msDS-NCReplInboundNeighbors $ msDS-NCReplOutboundNeighbors
+ $ msDS-ReplAttributeMetaData $ msDS-ReplValueMetaData $ msDS-NonMembersBL
+ $ msDS-MembersForAzRoleBL $ msDS-OperationsForAzTaskBL $ msDS-TasksForAzTaskBL
+ $ msDS-OperationsForAzRoleBL $ msDS-TasksForAzRoleBL $ msDs-masteredBy
+ $ msDS-ObjectReferenceBL $ msDS-PrincipalName $ msDS-RevealedDSAs $ msDS-KrbTgtLinkBl
+ $ msDS-IsFullReplicaFor $ msDS-IsDomainFor $ msDS-IsPartialReplicaFor
+ $ msDS-AuthenticatedToAccountlist $ msDS-NC-RO-Replica-Locations-BL $ msDS-RevealedListBL
+ $ msDS-PSOApplied $ msDS-NcType $ msDS-OIDToGroupLinkBl $ msDS-HostServiceAccountBL
+ $ isRecycled $ msDS-LocalEffectiveDeletionTime $ msDS-LocalEffectiveRecycleTime
+ $ msDS-LastKnownRDN $ msDS-EnabledFeatureBL $ msDS-ClaimSharesPossibleValuesWithBL
+ $ msDS-MembersOfResourcePropertyListBL $ msDS-IsPrimaryComputerFor
+ $ msDS-ValueTypeReferenceBL $ msDS-TDOIngressBL $ msDS-TDOEgressBL $ msDS-parentdistname
+ $ msDS-ReplValueMetaDataExt $ msds-memberOfTransitive $ msds-memberTransitive
+ $ msSFU30PosixMemberOf $ msDFSR-MemberReferenceBL $ msDFSR-ComputerReferenceBL ) )
+objectclass ( MSADoc5:8 NAME 'group' SUP mstop STRUCTURAL
+ MUST (groupType ) MAY (member $ nTGroupMembers $ operatorCount $ adminCount
+ $ groupAttributes $ groupMembershipSAM $ controlAccessRights $ desktopProfile
+ $ nonSecurityMember $ managedBy $ primaryGroupToken $ msDS-AzLDAPQuery
+ $ msDS-NonMembers $ msDS-AzBizRule $ msDS-AzBizRuleLanguage $ msDS-AzLastImportedBizRulePath
+ $ msDS-AzApplicationData $ msDS-AzObjectGuid $ msDS-AzGenericData
+ $ msDS-PrimaryComputer $ mail $ msSFU30Name $ msSFU30NisDomain $ msSFU30PosixMember ) )
+objectclass ( MSADoc5:9 NAME 'user'
+ SUP ( mstop $ organizationalPerson ) STRUCTURAL
+ MAY (o $ businessCategory $ userCertificate $ givenName $ initials
+ $ x500uniqueIdentifier $ displayName $ networkAddress $ employeeNumber
+ $ employeeType $ homePostalAddress $ userAccountControl $ badPwdCount
+ $ codePage $ homeDirectory $ homeDrive $ badPasswordTime $ lastLogoff
+ $ lastLogon $ dBCSPwd $ localeID $ scriptPath $ logonHours $ logonWorkstation
+ $ maxStorage $ userWorkstations $ unicodePwd $ otherLoginWorkstations
+ $ ntPwdHistory $ pwdLastSet $ preferredOU $ primaryGroupID $ userParameters
+ $ profilePath $ operatorCount $ adminCount $ accountExpires $ lmPwdHistory
+ $ groupMembershipSAM $ logonCount $ controlAccessRights $ defaultClassStore
+ $ groupsToIgnore $ groupPriority $ desktopProfile $ dynamicLDAPServer
+ $ userPrincipalName $ lockoutTime $ userSharedFolder $ userSharedFolderOther
+ $ servicePrincipalName $ aCSPolicyName $ terminalServer $ mSMQSignCertificates
+ $ mSMQDigests $ mSMQDigestsMig $ mSMQSignCertificatesMig $ msNPAllowDialin
+ $ msNPCallingStationID $ msNPSavedCallingStationID $ msRADIUSCallbackNumber
+ $ msRADIUSFramedIPAddress $ msRADIUSFramedRoute $ msRADIUSServiceType
+ $ msRASSavedCallbackNumber $ msRASSavedFramedIPAddress
+ $ msRASSavedFramedRoute $ mS-DS-CreatorSID $ msCOM-UserPartitionSetLink
+ $ msDS-Cached-Membership $ msDS-Cached-Membership-Time-Stamp
+ $ msDS-Site-Affinity $ msDS-User-Account-Control-Computed $ lastLogonTimestamp
+ $ msIIS-FTPRoot $ msIIS-FTPDir $ msDRM-IdentityCertificate $ msDS-SourceObjectDN
+ $ msPKIRoamingTimeStamp $ msPKIDPAPIMasterKeys $ msPKIAccountCredentials
+ $ msRADIUS-FramedInterfaceId $ msRADIUS-SavedFramedInterfaceId $ msRADIUS-FramedIpv6Prefix
+ $ msRADIUS-SavedFramedIpv6Prefix $ msRADIUS-FramedIpv6Route $ msRADIUS-SavedFramedIpv6Route
+ $ msDS-SecondaryKrbTgtNumber $ msDS-AuthenticatedAtDC $ msDS-SupportedEncryptionTypes
+ $ msDS-LastSuccessfulInteractiveLogonTime $ msDS-LastFailedInteractiveLogonTime
+ $ msDS-FailedInteractiveLogonCount $ msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon
+ $ msTSProfilePath $ msTSHomeDirectory $ msTSHomeDrive $ msTSAllowLogon
+ $ msTSRemoteControl $ msTSMaxDisconnectionTime $ msTSMaxConnectionTime
+ $ msTSMaxIdleTime $ msTSReconnectionAction $ msTSBrokenConnectionAction
+ $ msTSConnectClientDrives $ msTSConnectPrinterDrives $ msTSDefaultToMainPrinter
+ $ msTSWorkDirectory $ msTSInitialProgram $ msTSProperty01 $ msTSProperty02
+ $ msTSExpireDate $ msTSLicenseVersion $ msTSManagingLS
+ $ msDS-UserPasswordExpiryTimeComputed $ msTSExpireDate2 $ msTSLicenseVersion2
+ $ msTSManagingLS2 $ msTSExpireDate3 $ msTSLicenseVersion3 $ msTSManagingLS3
+ $ msTSExpireDate4 $ msTSLicenseVersion4 $ msTSManagingLS4 $ msTSLSProperty01
+ $ msTSLSProperty02 $ msDS-ResultantPSO $ msPKI-CredentialRoamingTokens
+ $ msTSPrimaryDesktop $ msTSSecondaryDesktops $ msDS-PrimaryComputer $ msDS-SyncServerUrl
+ $ msDS-AssignedAuthNPolicySilo $ msDS-AuthNPolicySiloMembersBL
+ $ msDS-AssignedAuthNPolicy $ userSMIMECertificate $ uid $ mail $ roomNumber
+ $ photo $ manager $ homePhone $ secretary $ mobile $ pager $ audio $ jpegPhoto
+ $ carLicense $ departmentNumber $ preferredLanguage $ userPKCS12 $ labeledURI
+ $ msSFU30Name $ msSFU30NisDomain ) )
+#objectclass ( MSADoc3:23 NAME 'container' SUP mstop STRUCTURAL
+# MUST (cn ) MAY (schemaVersion $ defaultClassStore $ msDS-ObjectReference ) )
+objectclass ( 1.2.840.113556.1.3.30 NAME 'computer' SUP user STRUCTURAL
+ MAY (cn $ networkAddress $ localPolicyFlags $ defaultLocalPolicyObject $ machineRole
+ $ location $ netbootInitialization $ netbootGUID $ netbootMachineFilePath
+ $ siteGUID $ operatingSystem $ operatingSystemVersion $ operatingSystemServicePack
+ $ operatingSystemHotfix $ volumeCount $ physicalLocationObject $ dNSHostName
+ $ policyReplicationFlags $ managedBy $ rIDSetReferences $ catalogs
+ $ netbootSIFFile $ netbootMirrorDataFile $ msDS-AdditionalDnsHostName
+ $ msDS-AdditionalSamAccountName $ msDS-ExecuteScriptPassword $ msDS-KrbTgtLink
+ $ msDS-RevealedUsers $ msDS-NeverRevealGroup $ msDS-RevealOnDemandGroup
+ $ msDS-RevealedList $ msDS-AuthenticatedAtDC $ msDS-isGC $ msDS-isRODC $ msDS-SiteName
+ $ msDS-PromotionSettings $ msTPM-OwnerInformation $ msTSProperty01 $ msTSProperty02
+ $ msDS-IsUserCachableAtRodc $ msDS-HostServiceAccount $ msTSEndpointData
+ $ msTSEndpointType $ msTSEndpointPlugin $ msTSPrimaryDesktopBL $ msTSSecondaryDesktopBL
+ $ msTPM-TpmInformationForComputer $ msDS-GenerationId $ msImaging-ThumbprintHash
+ $ msImaging-HashAlgorithm $ netbootDUID $ msSFU30Name $ msSFU30Aliases $ msSFU30NisDomain $ nisMapName ) )
diff --git a/servers/slapd/schema/namedobject.ldif b/servers/slapd/schema/namedobject.ldif
new file mode 100644
index 0000000..d2c2e24
--- /dev/null
+++ b/servers/slapd/schema/namedobject.ldif
@@ -0,0 +1,32 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2021-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## Definitions from draft Structural Object Classes for Named Objects
+## https://tools.ietf.org/html/draft-stroeder-namedobject
+##
+## Portions Copyright (c) 2013 IETF Trust and the persons identified as the
+## document authors. All rights reserved.
+#
+# Depends upon core.ldif and cosine.ldif
+#
+# This file was automatically generated from namedobject.schema; see
+# that file for complete references.
+#
+dn: cn=namedobject,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: namedobject
+olcObjectClasses: ( 1.3.6.1.4.1.5427.1.389.6.20 NAME 'namedObject' SUP top
+ STRUCTURAL MUST cn MAY ( uniqueIdentifier $ description ) )
+olcObjectClasses: ( 1.3.6.1.4.1.5427.1.389.6.21 NAME 'namedPolicy' SUP name
+ dObject STRUCTURAL )
diff --git a/servers/slapd/schema/nis.ldif b/servers/slapd/schema/nis.ldif
new file mode 100644
index 0000000..f623e29
--- /dev/null
+++ b/servers/slapd/schema/nis.ldif
@@ -0,0 +1,120 @@
+# NIS (RFC2307)
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# Definitions from RFC2307 (Experimental)
+# An Approach for Using LDAP as a Network Information Service
+#
+# Depends upon core.ldif and cosine.ldif
+#
+# This file was automatically generated from nis.schema; see that file
+# for complete references.
+#
+dn: cn=nis,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: nis
+olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; th
+ e common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatc
+ h SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolut
+ e path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1
+ 466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to th
+ e login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.2
+ 6 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integ
+ erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerM
+ atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integer
+ Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerM
+ atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMat
+ ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactI
+ A5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.
+ 26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY ca
+ seExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11
+ 5.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgr
+ oup triple' SYNTAX 1.3.6.1.1.1.0.0 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY intege
+ rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY int
+ egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integer
+ Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP address
+ ' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP netw
+ ork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI
+ NGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netm
+ ask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI
+ NGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address'
+ EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootp
+ aramd parameter' SYNTAX 1.3.6.1.1.1.0.1 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image nam
+ e' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name )
+olcAttributeTypes: ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseExac
+ tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
+ 1.26{1024} SINGLE-VALUE )
+olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction o
+ f an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNu
+ mber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $
+ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional a
+ ttributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword
+ $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive
+ $ shadowExpire $ shadowFlag $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of
+ a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPas
+ sword $ memberUid $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an I
+ nternet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipSe
+ rviceProtocol ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of
+ an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ description
+ ) MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an O
+ NC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description ) M
+ AY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a ho
+ st, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $ desc
+ ription $ manager ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a
+ n IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNetmas
+ kNumber $ l $ description $ manager ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of
+ a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNe
+ tgroup $ description ) )
+olcObjectClasses: ( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstracti
+ on of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )
+olcObjectClasses: ( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a
+ NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY descri
+ ption )
+olcObjectClasses: ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device w
+ ith a MAC address' SUP top AUXILIARY MAY macAddress )
+olcObjectClasses: ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device
+ with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) )
diff --git a/servers/slapd/schema/nis.schema b/servers/slapd/schema/nis.schema
new file mode 100644
index 0000000..1322fe5
--- /dev/null
+++ b/servers/slapd/schema/nis.schema
@@ -0,0 +1,237 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+# Definitions from RFC2307 (Experimental)
+# An Approach for Using LDAP as a Network Information Service
+
+# Depends upon core.schema and cosine.schema
+
+# Note: The definitions in RFC2307 are given in syntaxes closely related
+# to those in RFC2252, however, some liberties are taken that are not
+# supported by RFC2252. This file has been written following RFC2252
+# strictly.
+
+# OID Base is iso(1) org(3) dod(6) internet(1) directory(1) nisSchema(1).
+# i.e. nisSchema in RFC2307 is 1.3.6.1.1.1
+#
+# Syntaxes are under 1.3.6.1.1.1.0 (two new syntaxes are defined)
+# validaters for these syntaxes are incomplete, they only
+# implement printable string validation (which is good as the
+# common use of these syntaxes violates the specification).
+# Attribute types are under 1.3.6.1.1.1.1
+# Object classes are under 1.3.6.1.1.1.2
+
+# Attribute Type Definitions
+
+# builtin
+#attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber'
+# DESC 'An integer uniquely identifying a user in an administrative domain'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+# builtin
+#attributetype ( 1.3.6.1.1.1.1.1 NAME 'gidNumber'
+# DESC 'An integer uniquely identifying a group in an administrative domain'
+# EQUALITY integerMatch
+# SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.2 NAME 'gecos'
+ DESC 'The GECOS field; the common name'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory'
+ DESC 'The absolute path to the home directory'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.4 NAME 'loginShell'
+ DESC 'The path to the login shell'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.6 NAME 'shadowMin'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.7 NAME 'shadowMax'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.12 NAME 'memberUid'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple'
+ DESC 'Netgroup triple'
+ SYNTAX 1.3.6.1.1.1.0.0 )
+
+attributetype ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol'
+ SUP name )
+
+attributetype ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber'
+ DESC 'IP address'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
+
+attributetype ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber'
+ DESC 'IP network'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber'
+ DESC 'IP netmask'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.1.1.1.22 NAME 'macAddress'
+ DESC 'MAC address'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )
+
+attributetype ( 1.3.6.1.1.1.1.23 NAME 'bootParameter'
+ DESC 'rpc.bootparamd parameter'
+ SYNTAX 1.3.6.1.1.1.0.1 )
+
+attributetype ( 1.3.6.1.1.1.1.24 NAME 'bootFile'
+ DESC 'Boot image name'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.1.1.1.26 NAME 'nisMapName'
+ SUP name )
+
+attributetype ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry'
+ EQUALITY caseExactIA5Match
+ SUBSTR caseExactIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} SINGLE-VALUE )
+
+# Object Class Definitions
+
+objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
+ DESC 'Abstraction of an account with POSIX attributes'
+ SUP top AUXILIARY
+ MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
+ MAY ( userPassword $ loginShell $ gecos $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount'
+ DESC 'Additional attributes for shadow passwords'
+ SUP top AUXILIARY
+ MUST uid
+ MAY ( userPassword $ shadowLastChange $ shadowMin $
+ shadowMax $ shadowWarning $ shadowInactive $
+ shadowExpire $ shadowFlag $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
+ DESC 'Abstraction of a group of accounts'
+ SUP top STRUCTURAL
+ MUST ( cn $ gidNumber )
+ MAY ( userPassword $ memberUid $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.3 NAME 'ipService'
+ DESC 'Abstraction an Internet Protocol service'
+ SUP top STRUCTURAL
+ MUST ( cn $ ipServicePort $ ipServiceProtocol )
+ MAY ( description ) )
+
+objectclass ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol'
+ DESC 'Abstraction of an IP protocol'
+ SUP top STRUCTURAL
+ MUST ( cn $ ipProtocolNumber $ description )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.5 NAME 'oncRpc'
+ DESC 'Abstraction of an ONC/RPC binding'
+ SUP top STRUCTURAL
+ MUST ( cn $ oncRpcNumber $ description )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.6 NAME 'ipHost'
+ DESC 'Abstraction of a host, an IP device'
+ SUP top AUXILIARY
+ MUST ( cn $ ipHostNumber )
+ MAY ( l $ description $ manager ) )
+
+objectclass ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork'
+ DESC 'Abstraction of an IP network'
+ SUP top STRUCTURAL
+ MUST ( cn $ ipNetworkNumber )
+ MAY ( ipNetmaskNumber $ l $ description $ manager ) )
+
+objectclass ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup'
+ DESC 'Abstraction of a netgroup'
+ SUP top STRUCTURAL
+ MUST cn
+ MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )
+
+objectclass ( 1.3.6.1.1.1.2.9 NAME 'nisMap'
+ DESC 'A generic abstraction of a NIS map'
+ SUP top STRUCTURAL
+ MUST nisMapName
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.10 NAME 'nisObject'
+ DESC 'An entry in a NIS map'
+ SUP top STRUCTURAL
+ MUST ( cn $ nisMapEntry $ nisMapName )
+ MAY description )
+
+objectclass ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device'
+ DESC 'A device with a MAC address'
+ SUP top AUXILIARY
+ MAY macAddress )
+
+objectclass ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice'
+ DESC 'A device with boot parameters'
+ SUP top AUXILIARY
+ MAY ( bootFile $ bootParameter ) )
diff --git a/servers/slapd/schema/openldap.ldif b/servers/slapd/schema/openldap.ldif
new file mode 100644
index 0000000..ef44da3
--- /dev/null
+++ b/servers/slapd/schema/openldap.ldif
@@ -0,0 +1,88 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+#
+# OpenLDAP Project's directory schema items
+#
+# depends upon:
+# core.schema
+# cosine.schema
+# inetorgperson.schema
+#
+# These are provided for informational purposes only.
+#
+# This openldap.ldif file is provided as a demonstration of how to
+# convert a *.schema file into *.ldif format. The key points:
+# In LDIF, a blank line terminates an entry. Blank lines in a *.schema
+# file should be replaced with a single '#' to turn them into
+# comments, or they should just be removed.
+# In addition to the actual schema directives, the file needs a small
+# header to make it a valid LDAP entry. This header must provide the
+# dn of the entry, the objectClass, and the cn, as shown here:
+#
+dn: cn=openldap,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: openldap
+#
+# The schema directives need to be changed to LDAP Attributes.
+# First a basic string substitution can be done on each of the keywords:
+# objectIdentifier -> olcObjectIdentifier:
+# objectClass -> olcObjectClasses:
+# attributeType -> olcAttributeTypes:
+# Then leading whitespace must be fixed. The slapd.conf format allows
+# tabs or spaces to denote line continuation, while LDIF only allows
+# the space character.
+# Also slapd.conf preserves the continuation character, while LDIF strips
+# it out. So a single TAB/SPACE in slapd.conf must be replaced with
+# two SPACEs in LDIF, otherwise the continued text may get joined as
+# a single word.
+# The directives must be listed in a proper sequence:
+# All olcObjectIdentifiers must be first, so they may be referenced by
+# any following definitions.
+# All olcAttributeTypes must be next, so they may be referenced by any
+# following objectClass definitions.
+# All olcObjectClasses must be after the olcAttributeTypes.
+# And of course, any superior must occur before anything that inherits
+# from it.
+#
+olcObjectIdentifier: OpenLDAProot 1.3.6.1.4.1.4203
+#
+olcObjectIdentifier: OpenLDAP OpenLDAProot:1
+olcObjectIdentifier: OpenLDAPattributeType OpenLDAP:3
+olcObjectIdentifier: OpenLDAPobjectClass OpenLDAP:4
+#
+olcObjectClasses: ( OpenLDAPobjectClass:3
+ NAME 'OpenLDAPorg'
+ DESC 'OpenLDAP Organizational Object'
+ SUP organization
+ MAY ( buildingName $ displayName $ labeledURI ) )
+#
+olcObjectClasses: ( OpenLDAPobjectClass:4
+ NAME 'OpenLDAPou'
+ DESC 'OpenLDAP Organizational Unit Object'
+ SUP organizationalUnit
+ MAY ( buildingName $ displayName $ labeledURI $ o ) )
+#
+olcObjectClasses: ( OpenLDAPobjectClass:5
+ NAME 'OpenLDAPperson'
+ DESC 'OpenLDAP Person'
+ SUP ( pilotPerson $ inetOrgPerson )
+ MUST ( uid $ cn )
+ MAY ( givenName $ labeledURI $ o ) )
+#
+olcObjectClasses: ( OpenLDAPobjectClass:6
+ NAME 'OpenLDAPdisplayableObject'
+ DESC 'OpenLDAP Displayable Object'
+ AUXILIARY
+ MAY displayName )
diff --git a/servers/slapd/schema/openldap.schema b/servers/slapd/schema/openldap.schema
new file mode 100644
index 0000000..ae27205
--- /dev/null
+++ b/servers/slapd/schema/openldap.schema
@@ -0,0 +1,54 @@
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+#
+# OpenLDAP Project's directory schema items
+#
+# depends upon:
+# core.schema
+# cosine.schema
+# inetorgperson.schema
+#
+# These are provided for informational purposes only.
+
+objectIdentifier OpenLDAProot 1.3.6.1.4.1.4203
+
+objectIdentifier OpenLDAP OpenLDAProot:1
+objectIdentifier OpenLDAPattributeType OpenLDAP:3
+objectIdentifier OpenLDAPobjectClass OpenLDAP:4
+
+objectClass ( OpenLDAPobjectClass:3
+ NAME 'OpenLDAPorg'
+ DESC 'OpenLDAP Organizational Object'
+ SUP organization
+ MAY ( buildingName $ displayName $ labeledURI ) )
+
+objectClass ( OpenLDAPobjectClass:4
+ NAME 'OpenLDAPou'
+ DESC 'OpenLDAP Organizational Unit Object'
+ SUP organizationalUnit
+ MAY ( buildingName $ displayName $ labeledURI $ o ) )
+
+objectClass ( OpenLDAPobjectClass:5
+ NAME 'OpenLDAPperson'
+ DESC 'OpenLDAP Person'
+ SUP ( pilotPerson $ inetOrgPerson )
+ MUST ( uid $ cn )
+ MAY ( givenName $ labeledURI $ o ) )
+
+objectClass ( OpenLDAPobjectClass:6
+ NAME 'OpenLDAPdisplayableObject'
+ DESC 'OpenLDAP Displayable Object'
+ AUXILIARY
+ MAY displayName )
diff --git a/servers/slapd/schema/pmi.ldif b/servers/slapd/schema/pmi.ldif
new file mode 100644
index 0000000..c6d9cc3
--- /dev/null
+++ b/servers/slapd/schema/pmi.ldif
@@ -0,0 +1,123 @@
+# OpenLDAP X.509 PMI schema
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+## Portions Copyright (C) The Internet Society (1997-2006).
+## All Rights Reserved.
+#
+# Includes LDAPv3 schema items from:
+# ITU X.509 (08/2005)
+#
+# This file was automatically generated from pmi.schema; see that file
+# for complete references.
+#
+dn: cn=pmi,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: pmi
+olcObjectIdentifier: {0}id-oc-pmiUser 2.5.6.24
+olcObjectIdentifier: {1}id-oc-pmiAA 2.5.6.25
+olcObjectIdentifier: {2}id-oc-pmiSOA 2.5.6.26
+olcObjectIdentifier: {3}id-oc-attCertCRLDistributionPts 2.5.6.27
+olcObjectIdentifier: {4}id-oc-privilegePolicy 2.5.6.32
+olcObjectIdentifier: {5}id-oc-pmiDelegationPath 2.5.6.33
+olcObjectIdentifier: {6}id-oc-protectedPrivilegePolicy 2.5.6.34
+olcObjectIdentifier: {7}id-at-attributeCertificate 2.5.4.58
+olcObjectIdentifier: {8}id-at-attributeCertificateRevocationList 2.5.4.59
+olcObjectIdentifier: {9}id-at-aACertificate 2.5.4.61
+olcObjectIdentifier: {10}id-at-attributeDescriptorCertificate 2.5.4.62
+olcObjectIdentifier: {11}id-at-attributeAuthorityRevocationList 2.5.4.63
+olcObjectIdentifier: {12}id-at-privPolicy 2.5.4.71
+olcObjectIdentifier: {13}id-at-role 2.5.4.72
+olcObjectIdentifier: {14}id-at-delegationPath 2.5.4.73
+olcObjectIdentifier: {15}id-at-protPrivPolicy 2.5.4.74
+olcObjectIdentifier: {16}id-at-xMLPrivilegeInfo 2.5.4.75
+olcObjectIdentifier: {17}id-at-xMLPprotPrivPolicy 2.5.4.76
+olcObjectIdentifier: {18}id-mr 2.5.13
+olcObjectIdentifier: {19}id-mr-attributeCertificateMatch id-mr:42
+olcObjectIdentifier: {20}id-mr-attributeCertificateExactMatch id-mr:45
+olcObjectIdentifier: {21}id-mr-holderIssuerMatch id-mr:46
+olcObjectIdentifier: {22}id-mr-authAttIdMatch id-mr:53
+olcObjectIdentifier: {23}id-mr-roleSpecCertIdMatch id-mr:54
+olcObjectIdentifier: {24}id-mr-basicAttConstraintsMatch id-mr:55
+olcObjectIdentifier: {25}id-mr-delegatedNameConstraintsMatch id-mr:56
+olcObjectIdentifier: {26}id-mr-timeSpecMatch id-mr:57
+olcObjectIdentifier: {27}id-mr-attDescriptorMatch id-mr:58
+olcObjectIdentifier: {28}id-mr-acceptableCertPoliciesMatch id-mr:59
+olcObjectIdentifier: {29}id-mr-delegationPathMatch id-mr:61
+olcObjectIdentifier: {30}id-mr-sOAIdentifierMatch id-mr:66
+olcObjectIdentifier: {31}id-mr-indirectIssuerMatch id-mr:67
+olcObjectIdentifier: {32}AttributeCertificate 1.3.6.1.4.1.4203.666.11.10.2.1
+olcObjectIdentifier: {33}CertificateList 1.3.6.1.4.1.1466.115.121.1.9
+olcObjectIdentifier: {34}AttCertPath 1.3.6.1.4.1.4203.666.11.10.2.4
+olcObjectIdentifier: {35}PolicySyntax 1.3.6.1.4.1.4203.666.11.10.2.5
+olcObjectIdentifier: {36}RoleSyntax 1.3.6.1.4.1.4203.666.11.10.2.6
+olcLdapSyntaxes: {0}( 1.3.6.1.4.1.4203.666.11.10.2.4 DESC 'X.509 PMI attribute
+ certificate path: SEQUENCE OF AttributeCertificate' X-SUBST '1.3.6.1.4.1.146
+ 6.115.121.1.15' )
+olcLdapSyntaxes: {1}( 1.3.6.1.4.1.4203.666.11.10.2.5 DESC 'X.509 PMI policy sy
+ ntax' X-SUBST '1.3.6.1.4.1.1466.115.121.1.15' )
+olcLdapSyntaxes: {2}( 1.3.6.1.4.1.4203.666.11.10.2.6 DESC 'X.509 PMI role synt
+ ax' X-SUBST '1.3.6.1.4.1.1466.115.121.1.15' )
+olcAttributeTypes: {0}( id-at-role NAME 'role' DESC 'X.509 Role attribute, use
+ ;binary' SYNTAX RoleSyntax )
+olcAttributeTypes: {1}( id-at-xMLPrivilegeInfo NAME 'xmlPrivilegeInfo' DESC 'X
+ .509 XML privilege information attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.1
+ 5 )
+olcAttributeTypes: {2}( id-at-attributeCertificate NAME 'attributeCertificateA
+ ttribute' DESC 'X.509 Attribute certificate attribute, use ;binary' EQUALITY
+ attributeCertificateExactMatch SYNTAX AttributeCertificate )
+olcAttributeTypes: {3}( id-at-aACertificate NAME 'aACertificate' DESC 'X.509 A
+ A certificate attribute, use ;binary' EQUALITY attributeCertificateExactMatch
+ SYNTAX AttributeCertificate )
+olcAttributeTypes: {4}( id-at-attributeDescriptorCertificate NAME 'attributeDe
+ scriptorCertificate' DESC 'X.509 Attribute descriptor certificate attribute,
+ use ;binary' EQUALITY attributeCertificateExactMatch SYNTAX AttributeCertific
+ ate )
+olcAttributeTypes: {5}( id-at-attributeCertificateRevocationList NAME 'attribu
+ teCertificateRevocationList' DESC 'X.509 Attribute certificate revocation lis
+ t attribute, use ;binary' SYNTAX CertificateList X-EQUALITY 'certificateListE
+ xactMatch, not implemented yet' )
+olcAttributeTypes: {6}( id-at-attributeAuthorityRevocationList NAME 'attribute
+ AuthorityRevocationList' DESC 'X.509 AA certificate revocation list attribute
+ , use ;binary' SYNTAX CertificateList X-EQUALITY 'certificateListExactMatch,
+ not implemented yet' )
+olcAttributeTypes: {7}( id-at-delegationPath NAME 'delegationPath' DESC 'X.509
+ Delegation path attribute, use ;binary' SYNTAX AttCertPath )
+olcAttributeTypes: {8}( id-at-privPolicy NAME 'privPolicy' DESC 'X.509 Privile
+ ge policy attribute, use ;binary' SYNTAX PolicySyntax )
+olcAttributeTypes: {9}( id-at-protPrivPolicy NAME 'protPrivPolicy' DESC 'X.509
+ Protected privilege policy attribute, use ;binary' EQUALITY attributeCertifi
+ cateExactMatch SYNTAX AttributeCertificate )
+olcAttributeTypes: {10}( id-at-xMLPprotPrivPolicy NAME 'xmlPrivPolicy' DESC 'X
+ .509 XML Protected privilege policy attribute' SYNTAX 1.3.6.1.4.1.1466.115.12
+ 1.1.15 )
+olcObjectClasses: {0}( id-oc-pmiUser NAME 'pmiUser' DESC 'X.509 PMI user objec
+ t class' SUP top AUXILIARY MAY attributeCertificateAttribute )
+olcObjectClasses: {1}( id-oc-pmiAA NAME 'pmiAA' DESC 'X.509 PMI AA object clas
+ s' SUP top AUXILIARY MAY ( aACertificate $ attributeCertificateRevocationList
+ $ attributeAuthorityRevocationList ) )
+olcObjectClasses: {2}( id-oc-pmiSOA NAME 'pmiSOA' DESC 'X.509 PMI SOA object c
+ lass' SUP top AUXILIARY MAY ( attributeCertificateRevocationList $ attributeA
+ uthorityRevocationList $ attributeDescriptorCertificate ) )
+olcObjectClasses: {3}( id-oc-attCertCRLDistributionPts NAME 'attCertCRLDistrib
+ utionPt' DESC 'X.509 Attribute certificate CRL distribution point object clas
+ s' SUP top AUXILIARY MAY ( attributeCertificateRevocationList $ attributeAuth
+ orityRevocationList ) )
+olcObjectClasses: {4}( id-oc-pmiDelegationPath NAME 'pmiDelegationPath' DESC '
+ X.509 PMI delegation path' SUP top AUXILIARY MAY delegationPath )
+olcObjectClasses: {5}( id-oc-privilegePolicy NAME 'privilegePolicy' DESC 'X.50
+ 9 Privilege policy object class' SUP top AUXILIARY MAY privPolicy )
+olcObjectClasses: {6}( id-oc-protectedPrivilegePolicy NAME 'protectedPrivilege
+ Policy' DESC 'X.509 Protected privilege policy object class' SUP top AUXILIAR
+ Y MAY protPrivPolicy )
diff --git a/servers/slapd/schema_check.c b/servers/slapd/schema_check.c
new file mode 100644
index 0000000..b6090be
--- /dev/null
+++ b/servers/slapd/schema_check.c
@@ -0,0 +1,938 @@
+/* schema_check.c - routines to enforce schema definitions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+static char * oc_check_required(
+ Entry *e,
+ ObjectClass *oc,
+ struct berval *ocname );
+
+static int entry_naming_check(
+ Entry *e,
+ int manage,
+ int add_naming,
+ const char** text,
+ char *textbuf, size_t textlen );
+/*
+ * entry_schema_check - check that entry e conforms to the schema required
+ * by its object class(es).
+ *
+ * returns 0 if so, non-zero otherwise.
+ */
+
+int
+entry_schema_check(
+ Operation *op,
+ Entry *e,
+ Attribute *oldattrs,
+ int manage,
+ int add,
+ Attribute **socp,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ Attribute *a, *asc = NULL, *aoc = NULL;
+ ObjectClass *sc, *oc, **socs = NULL;
+ AttributeType *at;
+ ContentRule *cr;
+ int rc, i;
+ AttributeDescription *ad_structuralObjectClass
+ = slap_schema.si_ad_structuralObjectClass;
+ AttributeDescription *ad_objectClass
+ = slap_schema.si_ad_objectClass;
+ int extensible = 0;
+ int subentry = is_entry_subentry( e );
+ int collectiveSubentry = 0;
+
+ if ( SLAP_NO_SCHEMA_CHECK( op->o_bd )) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( get_no_schema_check( op ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if( subentry ) {
+ collectiveSubentry = is_entry_collectiveAttributeSubentry( e );
+ }
+
+ *text = textbuf;
+
+ /* misc attribute checks */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ const char *type = a->a_desc->ad_cname.bv_val;
+
+ /* there should be at least one value */
+ assert( a->a_vals != NULL );
+ assert( a->a_vals[0].bv_val != NULL );
+
+ if( a->a_desc->ad_type->sat_check ) {
+ rc = (a->a_desc->ad_type->sat_check)(
+ op->o_bd, e, a, text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+
+ if( a->a_desc == ad_structuralObjectClass )
+ asc = a;
+ else if ( a->a_desc == ad_objectClass )
+ aoc = a;
+
+ if( !collectiveSubentry && is_at_collective( a->a_desc->ad_type ) ) {
+ snprintf( textbuf, textlen,
+ "'%s' can only appear in collectiveAttributeSubentry",
+ type );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* if single value type, check for multiple values */
+ if( is_at_single_value( a->a_desc->ad_type ) &&
+ a->a_vals[1].bv_val != NULL )
+ {
+ snprintf( textbuf, textlen,
+ "attribute '%s' cannot have multiple values",
+ type );
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s), %s\n",
+ e->e_dn, textbuf );
+
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ /* check the object class attribute */
+ if ( aoc == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "No objectClass for entry (%s)\n",
+ e->e_dn );
+
+ *text = "no objectClass attribute";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ assert( aoc->a_vals != NULL );
+ assert( aoc->a_vals[0].bv_val != NULL );
+
+ /* check the structural object class attribute */
+ if ( asc == NULL && !add ) {
+ Debug( LDAP_DEBUG_ANY,
+ "No structuralObjectClass for entry (%s)\n",
+ e->e_dn );
+
+ *text = "no structuralObjectClass operational attribute";
+ return LDAP_OTHER;
+ }
+
+ rc = structural_class( aoc->a_vals, &oc, &socs, text, textbuf, textlen,
+ op->o_tmpmemctx );
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( asc == NULL && add ) {
+ attr_merge_one( e, ad_structuralObjectClass, &oc->soc_cname, NULL );
+ asc = attr_find( e->e_attrs, ad_structuralObjectClass );
+ sc = oc;
+ goto got_soc;
+ }
+
+ assert( asc->a_vals != NULL );
+ assert( asc->a_vals[0].bv_val != NULL );
+ assert( asc->a_vals[1].bv_val == NULL );
+
+ sc = oc_bvfind( &asc->a_vals[0] );
+ if( sc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized structuralObjectClass '%s'",
+ asc->a_vals[0].bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+
+ if( sc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
+ snprintf( textbuf, textlen,
+ "structuralObjectClass '%s' is not STRUCTURAL",
+ asc->a_vals[0].bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+got_soc:
+ if( !manage && sc->soc_obsolete ) {
+ snprintf( textbuf, textlen,
+ "structuralObjectClass '%s' is OBSOLETE",
+ asc->a_vals[0].bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+
+ *text = textbuf;
+
+ if ( oc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ aoc->a_vals[0].bv_val );
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+
+ } else if ( sc != oc ) {
+ if ( !manage && sc != slap_schema.si_oc_glue ) {
+ snprintf( textbuf, textlen,
+ "structural object class modification "
+ "from '%s' to '%s' not allowed",
+ asc->a_vals[0].bv_val, oc->soc_cname.bv_val );
+ rc = LDAP_NO_OBJECT_CLASS_MODS;
+ goto done;
+ }
+
+ assert( asc->a_vals != NULL );
+ assert( !BER_BVISNULL( &asc->a_vals[0] ) );
+ assert( BER_BVISNULL( &asc->a_vals[1] ) );
+ assert( asc->a_nvals == asc->a_vals );
+
+ /* draft-zeilenga-ldap-relax: automatically modify
+ * structuralObjectClass if changed with relax */
+ sc = oc;
+ ber_bvreplace( &asc->a_vals[ 0 ], &sc->soc_cname );
+ if ( socp ) {
+ *socp = asc;
+ }
+ }
+
+ /* naming check */
+ if ( !is_entry_glue ( e ) ) {
+ rc = entry_naming_check( e, manage, add, text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+ } else {
+ /* Glue Entry */
+ }
+
+ /* find the content rule for the structural class */
+ cr = cr_find( sc->soc_oid );
+
+ /* the cr must be same as the structural class */
+ assert( !cr || !strcmp( cr->scr_oid, sc->soc_oid ) );
+
+ /* check that the entry has required attrs of the content rule */
+ if( cr ) {
+ if( !manage && cr->scr_obsolete ) {
+ snprintf( textbuf, textlen,
+ "content rule '%s' is obsolete",
+ ldap_contentrule2name( &cr->scr_crule ));
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+
+ if( cr->scr_required ) for( i=0; cr->scr_required[i]; i++ ) {
+ at = cr->scr_required[i];
+
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if( a->a_desc->ad_type == at ) {
+ break;
+ }
+ }
+
+ /* not there => schema violation */
+ if ( a == NULL ) {
+ snprintf( textbuf, textlen,
+ "content rule '%s' requires attribute '%s'",
+ ldap_contentrule2name( &cr->scr_crule ),
+ at->sat_cname.bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+ }
+
+ if( cr->scr_precluded ) for( i=0; cr->scr_precluded[i]; i++ ) {
+ at = cr->scr_precluded[i];
+
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if( a->a_desc->ad_type == at ) {
+ break;
+ }
+ }
+
+ /* there => schema violation */
+ if ( a != NULL ) {
+ snprintf( textbuf, textlen,
+ "content rule '%s' precluded attribute '%s'",
+ ldap_contentrule2name( &cr->scr_crule ),
+ at->sat_cname.bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+ }
+ }
+
+ /* check that the entry has required attrs for each oc */
+ for ( i = 0; socs[i]; i++ ) {
+ oc = socs[i];
+ if ( !manage && oc->soc_obsolete ) {
+ /* disallow obsolete classes */
+ snprintf( textbuf, textlen,
+ "objectClass '%s' is OBSOLETE",
+ aoc->a_vals[i].bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+
+ if ( oc->soc_check ) {
+ rc = (oc->soc_check)( op->o_bd, e, oc,
+ text, textbuf, textlen );
+ if( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+ if ( oc->soc_kind == LDAP_SCHEMA_ABSTRACT ) {
+ /* object class is abstract */
+ if ( oc != slap_schema.si_oc_top &&
+ !is_object_subclass( oc, sc ))
+ {
+ int j;
+ ObjectClass *xc = NULL;
+ for( j=0; socs[j]; j++ ) {
+ if( i != j ) {
+ xc = socs[j];
+
+ /* since we previous check against the
+ * structural object of this entry, the
+ * abstract class must be a (direct or indirect)
+ * superclass of one of the auxiliary classes of
+ * the entry.
+ */
+ if ( xc->soc_kind == LDAP_SCHEMA_AUXILIARY &&
+ is_object_subclass( oc, xc ) )
+ {
+ xc = NULL;
+ break;
+ }
+ }
+ }
+
+ if( xc != NULL ) {
+ snprintf( textbuf, textlen, "instantiation of "
+ "abstract objectClass '%s' not allowed",
+ aoc->a_vals[i].bv_val );
+
+ Debug( LDAP_DEBUG_ANY,
+ "entry_check_schema(%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+ }
+
+ } else if ( oc->soc_kind != LDAP_SCHEMA_STRUCTURAL || oc == sc ) {
+ char *s;
+
+ if( oc->soc_kind == LDAP_SCHEMA_AUXILIARY ) {
+ int k;
+
+ if( cr ) {
+ int j;
+
+ k = -1;
+ if( cr->scr_auxiliaries ) {
+ for( j = 0; cr->scr_auxiliaries[j]; j++ ) {
+ if( cr->scr_auxiliaries[j] == oc ) {
+ k = 0;
+ break;
+ }
+ }
+ }
+ if ( k ) {
+ snprintf( textbuf, textlen,
+ "class '%s' not allowed by content rule '%s'",
+ oc->soc_cname.bv_val,
+ ldap_contentrule2name( &cr->scr_crule ) );
+ }
+ } else if ( global_disallows & SLAP_DISALLOW_AUX_WO_CR ) {
+ k = -1;
+ snprintf( textbuf, textlen,
+ "class '%s' not allowed by any content rule",
+ oc->soc_cname.bv_val );
+ } else {
+ k = 0;
+ }
+
+ if( k == -1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+ }
+
+ s = oc_check_required( e, oc, &aoc->a_vals[i] );
+ if (s != NULL) {
+ snprintf( textbuf, textlen,
+ "object class '%s' requires attribute '%s'",
+ aoc->a_vals[i].bv_val, s );
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s): %s\n",
+ e->e_dn, textbuf );
+
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+ goto done;
+ }
+
+ if( oc == slap_schema.si_oc_extensibleObject ) {
+ extensible=1;
+ }
+ }
+ }
+
+ if( extensible ) {
+ *text = NULL;
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ /* check that each attr in the entry is allowed by some oc */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
+
+ if( cr && cr->scr_required ) {
+ for( i=0; cr->scr_required[i]; i++ ) {
+ if( cr->scr_required[i] == a->a_desc->ad_type ) {
+ rc = LDAP_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ if( rc != LDAP_SUCCESS && cr && cr->scr_allowed ) {
+ for( i=0; cr->scr_allowed[i]; i++ ) {
+ if( cr->scr_allowed[i] == a->a_desc->ad_type ) {
+ rc = LDAP_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ if( rc != LDAP_SUCCESS )
+ {
+ rc = oc_check_allowed( a->a_desc->ad_type, socs, sc );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ char *type = a->a_desc->ad_cname.bv_val;
+
+ snprintf( textbuf, textlen,
+ "attribute '%s' not allowed",
+ type );
+
+ Debug( LDAP_DEBUG_ANY,
+ "Entry (%s), %s\n",
+ e->e_dn, textbuf );
+
+ goto done;
+ }
+ }
+
+ *text = NULL;
+done:
+ slap_sl_free( socs, op->o_tmpmemctx );
+ return rc;
+}
+
+static char *
+oc_check_required(
+ Entry *e,
+ ObjectClass *oc,
+ struct berval *ocname )
+{
+ AttributeType *at;
+ int i;
+ Attribute *a;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "oc_check_required entry (%s), objectClass \"%s\"\n",
+ e->e_dn?e->e_dn:"(null)", ocname->bv_val );
+
+
+ /* check for empty oc_required */
+ if(oc->soc_required == NULL) {
+ return NULL;
+ }
+
+ /* for each required attribute */
+ for ( i = 0; oc->soc_required[i] != NULL; i++ ) {
+ at = oc->soc_required[i];
+ /* see if it's in the entry */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if( a->a_desc->ad_type == at ) {
+ break;
+ }
+ }
+ /* not there => schema violation */
+ if ( a == NULL ) {
+ return at->sat_cname.bv_val;
+ }
+ }
+
+ return( NULL );
+}
+
+int oc_check_allowed(
+ AttributeType *at,
+ ObjectClass **socs,
+ ObjectClass *sc )
+{
+ int i, j;
+
+ Debug( LDAP_DEBUG_TRACE,
+ "oc_check_allowed type \"%s\"\n",
+ at->sat_cname.bv_val );
+
+ /* always allow objectClass attribute */
+ if ( strcasecmp( at->sat_cname.bv_val, "objectClass" ) == 0 ) {
+ return LDAP_SUCCESS;
+ }
+
+ /*
+ * All operational attributions are allowed by schema rules.
+ */
+ if( is_at_operational(at) ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* check to see if its allowed by the structuralObjectClass */
+ if( sc ) {
+ /* does it require the type? */
+ for ( j = 0; sc->soc_required != NULL &&
+ sc->soc_required[j] != NULL; j++ )
+ {
+ if( at == sc->soc_required[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* does it allow the type? */
+ for ( j = 0; sc->soc_allowed != NULL &&
+ sc->soc_allowed[j] != NULL; j++ )
+ {
+ if( at == sc->soc_allowed[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ /* check that the type appears as req or opt in at least one oc */
+ for ( i = 0; socs[i]; i++ ) {
+ /* if we know about the oc */
+ ObjectClass *oc = socs[i];
+ /* extensibleObject allows all */
+ if ( oc == slap_schema.si_oc_extensibleObject ) {
+ return LDAP_SUCCESS;
+ }
+ if ( oc != NULL && oc->soc_kind != LDAP_SCHEMA_ABSTRACT &&
+ ( sc == NULL || oc->soc_kind == LDAP_SCHEMA_AUXILIARY ))
+ {
+ /* does it require the type? */
+ for ( j = 0; oc->soc_required != NULL &&
+ oc->soc_required[j] != NULL; j++ )
+ {
+ if( at == oc->soc_required[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ /* does it allow the type? */
+ for ( j = 0; oc->soc_allowed != NULL &&
+ oc->soc_allowed[j] != NULL; j++ )
+ {
+ if( at == oc->soc_allowed[j] ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /* not allowed by any oc */
+ return LDAP_OBJECT_CLASS_VIOLATION;
+}
+
+/*
+ * Determine the structural object class from a set of OIDs
+ */
+int structural_class(
+ BerVarray ocs,
+ ObjectClass **scp,
+ ObjectClass ***socsp,
+ const char **text,
+ char *textbuf, size_t textlen,
+ void *ctx )
+{
+ int i, nocs;
+ ObjectClass *oc, **socs;
+ ObjectClass *sc = NULL;
+ int scn = -1;
+
+ *text = "structural_class: internal error";
+
+ /* count them */
+ for( i=0; ocs[i].bv_val; i++ ) ;
+ nocs = i;
+
+ socs = slap_sl_malloc( (nocs+1) * sizeof(ObjectClass *), ctx );
+
+ for( i=0; ocs[i].bv_val; i++ ) {
+ socs[i] = oc_bvfind( &ocs[i] );
+
+ if( socs[i] == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ ocs[i].bv_val );
+ *text = textbuf;
+ goto fail;
+ }
+ }
+ socs[i] = NULL;
+
+ for( i=0; ocs[i].bv_val; i++ ) {
+ oc = socs[i];
+ if( oc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
+ if( sc == NULL || is_object_subclass( sc, oc ) ) {
+ sc = oc;
+ scn = i;
+
+ } else if ( !is_object_subclass( oc, sc ) ) {
+ int j;
+ ObjectClass *xc = NULL;
+
+ /* find common superior */
+ for( j=i+1; ocs[j].bv_val; j++ ) {
+ xc = socs[j];
+
+ if( xc == NULL ) {
+ snprintf( textbuf, textlen,
+ "unrecognized objectClass '%s'",
+ ocs[j].bv_val );
+ *text = textbuf;
+ goto fail;
+ }
+
+ if( xc->soc_kind != LDAP_SCHEMA_STRUCTURAL ) {
+ xc = NULL;
+ continue;
+ }
+
+ if( is_object_subclass( sc, xc ) &&
+ is_object_subclass( oc, xc ) )
+ {
+ /* found common subclass */
+ break;
+ }
+
+ xc = NULL;
+ }
+
+ if( xc == NULL ) {
+ /* no common subclass */
+ snprintf( textbuf, textlen,
+ "invalid structural object class chain (%s/%s)",
+ ocs[scn].bv_val, ocs[i].bv_val );
+ *text = textbuf;
+ goto fail;
+ }
+ }
+ }
+ }
+
+ if( scp ) {
+ *scp = sc;
+ }
+
+ if( sc == NULL ) {
+ *text = "no structural object class provided";
+ goto fail;
+ }
+
+ if( scn < 0 ) {
+ *text = "invalid structural object class";
+ goto fail;
+ }
+
+ if ( socsp ) {
+ *socsp = socs;
+ } else {
+ slap_sl_free( socs, ctx );
+ }
+ *text = NULL;
+
+ return LDAP_SUCCESS;
+
+fail:
+ slap_sl_free( socs, ctx );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+}
+
+/*
+ * Return structural object class from list of modifications
+ */
+int mods_structural_class(
+ Modifications *mods,
+ struct berval *sc,
+ const char **text,
+ char *textbuf, size_t textlen, void *ctx )
+{
+ Modifications *ocmod = NULL;
+ ObjectClass *ssc;
+ int rc;
+
+ for( ; mods != NULL; mods = mods->sml_next ) {
+ if( mods->sml_desc == slap_schema.si_ad_objectClass ) {
+ if( ocmod != NULL ) {
+ *text = "entry has multiple objectClass attributes";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ ocmod = mods;
+ }
+ }
+
+ if( ocmod == NULL ) {
+ *text = "entry has no objectClass attribute";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( ocmod->sml_values == NULL || ocmod->sml_values[0].bv_val == NULL ) {
+ *text = "objectClass attribute has no values";
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ rc = structural_class( ocmod->sml_values, &ssc, NULL,
+ text, textbuf, textlen, ctx );
+ if ( rc == LDAP_SUCCESS )
+ *sc = ssc->soc_cname;
+ return rc;
+}
+
+
+static int
+entry_naming_check(
+ Entry *e,
+ int manage,
+ int add_naming,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ /* naming check */
+ LDAPRDN rdn = NULL;
+ const char *p = NULL;
+ ber_len_t cnt;
+ int rc = LDAP_SUCCESS;
+
+ if ( BER_BVISEMPTY( &e->e_name )) {
+ return LDAP_SUCCESS;
+ }
+
+ /*
+ * Get attribute type(s) and attribute value(s) of our RDN
+ */
+ if ( ldap_bv2rdn( &e->e_name, &rdn, (char **)&p,
+ LDAP_DN_FORMAT_LDAP ) )
+ {
+ *text = "unrecognized attribute type(s) in RDN";
+ return LDAP_INVALID_DN_SYNTAX;
+ }
+
+ /* Check that each AVA of the RDN is present in the entry */
+ /* FIXME: Should also check that each AVA lists a distinct type */
+ for ( cnt = 0; rdn[cnt]; cnt++ ) {
+ LDAPAVA *ava = rdn[cnt];
+ AttributeDescription *desc = NULL;
+ Attribute *attr;
+ const char *errtext;
+ int add = 0;
+
+ if( ava->la_flags & LDAP_AVA_BINARY ) {
+ snprintf( textbuf, textlen,
+ "value of naming attribute '%s' in unsupported BER form",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ rc = slap_bv2ad( &ava->la_attr, &desc, &errtext );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( textbuf, textlen, "%s (in RDN)", errtext );
+ break;
+ }
+
+ if( desc->ad_type->sat_usage ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' is operational",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ if( desc->ad_type->sat_collective ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' is collective",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ if( !manage && desc->ad_type->sat_obsolete ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' is obsolete",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ if( !desc->ad_type->sat_equality ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' has no equality matching rule",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ if( !desc->ad_type->sat_equality->smr_match ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' has unsupported equality matching rule",
+ ava->la_attr.bv_val );
+ rc = LDAP_NAMING_VIOLATION;
+ break;
+ }
+
+ /* find the naming attribute */
+ attr = attr_find( e->e_attrs, desc );
+ if ( attr == NULL ) {
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' is not present in entry",
+ ava->la_attr.bv_val );
+ if ( add_naming ) {
+ add = 1;
+
+ } else {
+ rc = LDAP_NAMING_VIOLATION;
+ }
+
+ } else {
+ rc = attr_valfind( attr, SLAP_MR_VALUE_OF_ASSERTION_SYNTAX|
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &ava->la_value, NULL, NULL );
+
+ if ( rc != 0 ) {
+ switch( rc ) {
+ case LDAP_INAPPROPRIATE_MATCHING:
+ snprintf( textbuf, textlen,
+ "inappropriate matching for naming attribute '%s'",
+ ava->la_attr.bv_val );
+ break;
+ case LDAP_INVALID_SYNTAX:
+ snprintf( textbuf, textlen,
+ "value of naming attribute '%s' is invalid",
+ ava->la_attr.bv_val );
+ break;
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ if ( add_naming ) {
+ if ( is_at_single_value( desc->ad_type ) ) {
+ snprintf( textbuf, textlen,
+ "value of single-valued naming attribute '%s' conflicts with value present in entry",
+ ava->la_attr.bv_val );
+
+ } else {
+ add = 1;
+ rc = LDAP_SUCCESS;
+ }
+
+ } else {
+ snprintf( textbuf, textlen,
+ "value of naming attribute '%s' is not present in entry",
+ ava->la_attr.bv_val );
+ }
+ break;
+ default:
+ snprintf( textbuf, textlen,
+ "naming attribute '%s' is inappropriate",
+ ava->la_attr.bv_val );
+ }
+
+ if ( !add ) {
+ rc = LDAP_NAMING_VIOLATION;
+ }
+ }
+ }
+
+ if ( add ) {
+ attr_merge_normalize_one( e, desc, &ava->la_value, NULL );
+
+ } else if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ }
+
+ ldap_rdnfree( rdn );
+ return rc;
+}
+
diff --git a/servers/slapd/schema_init.c b/servers/slapd/schema_init.c
new file mode 100644
index 0000000..4564043
--- /dev/null
+++ b/servers/slapd/schema_init.c
@@ -0,0 +1,6980 @@
+/* schema_init.c - init builtin schema */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/*
+ * Syntaxes - implementation notes:
+ *
+ * Validate function(syntax, value):
+ * Called before the other functions here to check if the value
+ * is valid according to the syntax.
+ *
+ * Pretty function(syntax, input value, output prettified...):
+ * If it exists, maps different notations of the same value to a
+ * unique representation which can be stored in the directory and
+ * possibly be passed to the Match/Indexer/Filter() functions.
+ *
+ * E.g. DN "2.5.4.3 = foo\,bar, o = BAZ" -> "cn=foo\2Cbar,o=BAZ",
+ * but unlike DN normalization, "BAZ" is not mapped to "baz".
+ */
+
+/*
+ * Matching rules - implementation notes:
+ *
+ * Matching rules match an attribute value (often from the directory)
+ * against an asserted value (e.g. from a filter).
+ *
+ * Invoked with validated and commonly pretty/normalized arguments, thus
+ * a number of matching rules can simply use the octetString functions.
+ *
+ * Normalize function(...input value, output normalized...):
+ * If it exists, maps matching values to a unique representation
+ * which is passed to the Match/Indexer/Filter() functions.
+ *
+ * Different matching rules can normalize values of the same syntax
+ * differently. E.g. caseIgnore rules normalize to lowercase,
+ * caseExact rules do not.
+ *
+ * Match function(*output matchp, ...value, asserted value):
+ * On success, set *matchp. 0 means match. For ORDERING/most EQUALITY,
+ * less/greater than 0 means value less/greater than asserted. However:
+ *
+ * In extensible match filters, ORDERING rules match if value<asserted.
+ *
+ * EQUALITY rules may order values differently than ORDERING rules for
+ * speed, since EQUALITY ordering is only used for SLAP_AT_SORTED_VAL.
+ * Some EQUALITY rules do not order values (ITS#6722).
+ *
+ * Indexer function(...attribute values, *output keysp,...):
+ * Generates index keys for the attribute values. Backends can store
+ * them in an index, a {key->entry ID set} mapping, for the attribute.
+ *
+ * A search can look up the DN/scope and asserted values in the
+ * indexes, if any, to narrow down the number of entries to check
+ * against the search criteria.
+ *
+ * Filter function(...asserted value, *output keysp,...):
+ * Generates index key(s) for the asserted value, to be looked up in
+ * the index from the Indexer function. *keysp is an array because
+ * substring matching rules can generate multiple lookup keys.
+ *
+ * Index keys:
+ * A key is usually a hash of match type, attribute value and schema
+ * info, because one index can contain keys for many filtering types.
+ *
+ * Some indexes instead have EQUALITY keys ordered so that if
+ * key(val1) < key(val2), then val1 < val2 by the ORDERING rule.
+ * That way the ORDERING rule can use the EQUALITY index.
+ *
+ * Substring indexing:
+ * This chops the attribute values up in small chunks and indexes all
+ * possible chunks of certain sizes. Substring filtering looks up
+ * SOME of the asserted value's chunks, and the caller uses the
+ * intersection of the resulting entry ID sets.
+ * See the index_substr_* keywords in slapd.conf(5).
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../../libraries/liblber/lber-int.h" /* get ber_ptrlen() */
+
+#include "ldap_utf8.h"
+
+#include "lutil.h"
+#include "lutil_hash.h"
+
+#ifdef LUTIL_HASH64_BYTES
+#define HASH_BYTES LUTIL_HASH64_BYTES
+#define HASH_LEN hashlen
+static void (*hashinit)(lutil_HASH_CTX *ctx) = lutil_HASHInit;
+static void (*hashupdate)(lutil_HASH_CTX *ctx,unsigned char const *buf, ber_len_t len) = lutil_HASHUpdate;
+static void (*hashfinal)(unsigned char digest[HASH_BYTES], lutil_HASH_CTX *ctx) = lutil_HASHFinal;
+static int hashlen = LUTIL_HASH_BYTES;
+#define HASH_Init(c) hashinit(c)
+#define HASH_Update(c,buf,len) hashupdate(c,buf,len)
+#define HASH_Final(d,c) hashfinal(d,c)
+
+/* Toggle between 32 and 64 bit hashing, default to 32 for compatibility
+ -1 to query, returns 1 if 64 bit, 0 if 32.
+ 0/1 to set 32/64, returns 0 on success, -1 on failure */
+int slap_hash64( int onoff )
+{
+ if ( onoff < 0 ) {
+ return hashlen == LUTIL_HASH64_BYTES;
+ } else if ( onoff ) {
+ hashinit = lutil_HASH64Init;
+ hashupdate = lutil_HASH64Update;
+ hashfinal = lutil_HASH64Final;
+ hashlen = LUTIL_HASH64_BYTES;
+ } else {
+ hashinit = lutil_HASHInit;
+ hashupdate = lutil_HASHUpdate;
+ hashfinal = lutil_HASHFinal;
+ hashlen = LUTIL_HASH_BYTES;
+ }
+ return 0;
+}
+
+#else
+#define HASH_BYTES LUTIL_HASH_BYTES
+#define HASH_LEN HASH_BYTES
+#define HASH_Init(c) lutil_HASHInit(c)
+#define HASH_Update(c,buf,len) lutil_HASHUpdate(c,buf,len)
+#define HASH_Final(d,c) lutil_HASHFinal(d,c)
+
+int slap_has64( int onoff )
+{
+ if ( onoff < 0 )
+ return 0;
+ else
+ return onoff ? -1 : 0;
+}
+
+#endif
+#define HASH_CONTEXT lutil_HASH_CTX
+
+/* approx matching rules */
+#define directoryStringApproxMatchOID "1.3.6.1.4.1.4203.666.4.4"
+#define directoryStringApproxMatch approxMatch
+#define directoryStringApproxIndexer approxIndexer
+#define directoryStringApproxFilter approxFilter
+#define IA5StringApproxMatchOID "1.3.6.1.4.1.4203.666.4.5"
+#define IA5StringApproxMatch approxMatch
+#define IA5StringApproxIndexer approxIndexer
+#define IA5StringApproxFilter approxFilter
+
+/* Change Sequence Number (CSN) - much of this will change */
+#define csnMatch octetStringMatch
+#define csnOrderingMatch octetStringOrderingMatch
+#define csnIndexer generalizedTimeIndexer
+#define csnFilter generalizedTimeFilter
+
+#define authzMatch octetStringMatch
+
+/* X.509 PMI ldapSyntaxes */
+/* FIXME: need to create temporary OIDs under OpenLDAP's arc;
+ * these are currently hijacked
+ *
+ * 1.3.6.1.4.1.4203.666 OpenLDAP
+ * 1.3.6.1.4.1.4203.666.11 self-contained works
+ * 1.3.6.1.4.1.4203.666.11.10 X.509 PMI
+ * 1.3.6.1.4.1.4203.666.11.10.2 X.509 PMI ldapSyntaxes
+ * 1.3.6.1.4.1.4203.666.11.10.2.1 AttributeCertificate (supported)
+ * 1.3.6.1.4.1.4203.666.11.10.2.2 AttributeCertificateExactAssertion (supported)
+ * 1.3.6.1.4.1.4203.666.11.10.2.3 AttributeCertificateAssertion (not supported)
+ * 1.3.6.1.4.1.4203.666.11.10.2.4 AttCertPath (X-SUBST'ed right now in pmi.schema)
+ * 1.3.6.1.4.1.4203.666.11.10.2.5 PolicySyntax (X-SUBST'ed right now in pmi.schema)
+ * 1.3.6.1.4.1.4203.666.11.10.2.6 RoleSyntax (X-SUBST'ed right now in pmi.schema)
+ */
+#if 0 /* from <draft-ietf-pkix-ldap-schema-02.txt> (expired) */
+#define attributeCertificateSyntaxOID "1.2.826.0.1.3344810.7.5"
+#define attributeCertificateExactAssertionSyntaxOID "1.2.826.0.1.3344810.7.6"
+#define attributeCertificateAssertionSyntaxOID "1.2.826.0.1.3344810.7.7"
+#else /* from OpenLDAP's experimental oid arc */
+#define X509_PMI_SyntaxOID "1.3.6.1.4.1.4203.666.11.10.2"
+#define attributeCertificateSyntaxOID X509_PMI_SyntaxOID ".1"
+#define attributeCertificateExactAssertionSyntaxOID X509_PMI_SyntaxOID ".2"
+#define attributeCertificateAssertionSyntaxOID X509_PMI_SyntaxOID ".3"
+#endif
+
+unsigned int index_substr_if_minlen = SLAP_INDEX_SUBSTR_IF_MINLEN_DEFAULT;
+unsigned int index_substr_if_maxlen = SLAP_INDEX_SUBSTR_IF_MAXLEN_DEFAULT;
+unsigned int index_substr_any_len = SLAP_INDEX_SUBSTR_ANY_LEN_DEFAULT;
+unsigned int index_substr_any_step = SLAP_INDEX_SUBSTR_ANY_STEP_DEFAULT;
+
+unsigned int index_intlen = SLAP_INDEX_INTLEN_DEFAULT;
+unsigned int index_intlen_strlen = SLAP_INDEX_INTLEN_STRLEN(
+ SLAP_INDEX_INTLEN_DEFAULT );
+
+ldap_pvt_thread_mutex_t ad_index_mutex;
+ldap_pvt_thread_mutex_t ad_undef_mutex;
+ldap_pvt_thread_mutex_t oc_undef_mutex;
+
+static int
+generalizedTimeValidate(
+ Syntax *syntax,
+ struct berval *in );
+
+#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX
+static int
+utcTimeValidate(
+ Syntax *syntax,
+ struct berval *in );
+#endif /* SUPPORT_OBSOLETE_UTC_SYNTAX */
+
+static int
+inValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ /* no value allowed */
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+blobValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ /* any value allowed */
+ return LDAP_SUCCESS;
+}
+
+#define berValidate blobValidate
+
+static int
+sequenceValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ if ( in->bv_len < 2 ) return LDAP_INVALID_SYNTAX;
+ if ( in->bv_val[0] != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+
+ return LDAP_SUCCESS;
+}
+
+/* X.509 related stuff */
+
+enum {
+ SLAP_X509_V1 = 0,
+ SLAP_X509_V2 = 1,
+ SLAP_X509_V3 = 2
+};
+
+enum {
+ SLAP_TAG_UTCTIME = 0x17U,
+ SLAP_TAG_GENERALIZEDTIME = 0x18U
+};
+
+
+#define SLAP_X509_OPTION (LBER_CLASS_CONTEXT|LBER_CONSTRUCTED)
+
+enum {
+ SLAP_X509_OPT_C_VERSION = SLAP_X509_OPTION + 0,
+ SLAP_X509_OPT_C_ISSUERUNIQUEID = LBER_CLASS_CONTEXT + 1,
+ SLAP_X509_OPT_C_SUBJECTUNIQUEID = LBER_CLASS_CONTEXT + 2,
+ SLAP_X509_OPT_C_EXTENSIONS = SLAP_X509_OPTION + 3
+};
+
+enum {
+ SLAP_X509_OPT_CL_CRLEXTENSIONS = SLAP_X509_OPTION + 0
+};
+
+/*
+GeneralName ::= CHOICE {
+ otherName [0] INSTANCE OF OTHER-NAME,
+ rfc822Name [1] IA5String,
+ dNSName [2] IA5String,
+ x400Address [3] ORAddress,
+ directoryName [4] Name,
+ ediPartyName [5] EDIPartyName,
+ uniformResourceIdentifier [6] IA5String,
+ iPAddress [7] OCTET STRING,
+ registeredID [8] OBJECT IDENTIFIER }
+*/
+enum {
+ SLAP_X509_GN_OTHERNAME = SLAP_X509_OPTION + 0,
+ SLAP_X509_GN_RFC822NAME = SLAP_X509_OPTION + 1,
+ SLAP_X509_GN_DNSNAME = SLAP_X509_OPTION + 2,
+ SLAP_X509_GN_X400ADDRESS = SLAP_X509_OPTION + 3,
+ SLAP_X509_GN_DIRECTORYNAME = SLAP_X509_OPTION + 4,
+ SLAP_X509_GN_EDIPARTYNAME = SLAP_X509_OPTION + 5,
+ SLAP_X509_GN_URI = SLAP_X509_OPTION + 6,
+ SLAP_X509_GN_IPADDRESS = SLAP_X509_OPTION + 7,
+ SLAP_X509_GN_REGISTEREDID = SLAP_X509_OPTION + 8
+};
+
+/* X.509 PMI related stuff */
+enum {
+ SLAP_X509AC_V1 = 0,
+ SLAP_X509AC_V2 = 1
+};
+
+enum {
+ SLAP_X509AC_ISSUER = SLAP_X509_OPTION + 0
+};
+
+/* X.509 certificate validation */
+static int
+certificateValidate( Syntax *syntax, struct berval *in )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t version = SLAP_X509_V1;
+
+ if ( BER_BVISNULL( in ) || BER_BVISEMPTY( in ))
+ return LDAP_INVALID_SYNTAX;
+
+ ber_init2( ber, in, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &len ); /* Signed wrapper */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_peek_tag( ber, &len );
+ /* Optional version */
+ if ( tag == SLAP_X509_OPT_C_VERSION ) {
+ tag = ber_skip_tag( ber, &len );
+ tag = ber_get_int( ber, &version );
+ if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX;
+ }
+ /* NOTE: don't try to parse Serial, because it might be longer
+ * than sizeof(ber_int_t); deferred to certificateExactNormalize() */
+ tag = ber_skip_tag( ber, &len ); /* Serial */
+ if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Issuer DN */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Validity */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Subject DN */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Subject PublicKeyInfo */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ if ( tag == SLAP_X509_OPT_C_ISSUERUNIQUEID ) { /* issuerUniqueID */
+ if ( version < SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ if ( tag == SLAP_X509_OPT_C_SUBJECTUNIQUEID ) { /* subjectUniqueID */
+ if ( version < SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ if ( tag == SLAP_X509_OPT_C_EXTENSIONS ) { /* Extensions */
+ if ( version < SLAP_X509_V3 ) return LDAP_INVALID_SYNTAX;
+ tag = ber_skip_tag( ber, &len );
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ /* signatureAlgorithm */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ /* Signature */
+ if ( tag != LBER_BITSTRING ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ /* Must be at end now */
+ if ( len || tag != LBER_DEFAULT ) return LDAP_INVALID_SYNTAX;
+ return LDAP_SUCCESS;
+}
+
+/* X.509 certificate list validation */
+static int
+checkTime( struct berval *in, struct berval *out );
+
+static int
+certificateListValidate( Syntax *syntax, struct berval *in )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len, wrapper_len;
+ char *wrapper_start;
+ int wrapper_ok = 0;
+ ber_int_t version = SLAP_X509_V1;
+ struct berval bvdn, bvtu;
+
+ ber_init2( ber, in, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &wrapper_len ); /* Signed wrapper */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ wrapper_start = ber->ber_ptr;
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_peek_tag( ber, &len );
+ /* Optional version */
+ if ( tag == LBER_INTEGER ) {
+ tag = ber_get_int( ber, &version );
+ if ( tag != LBER_INTEGER || version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX;
+ }
+ tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_peek_tag( ber, &len ); /* Issuer DN */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ len = ber_ptrlen( ber );
+ bvdn.bv_val = in->bv_val + len;
+ bvdn.bv_len = in->bv_len - len;
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* thisUpdate */
+ /* Time is a CHOICE { UTCTime, GeneralizedTime } */
+ if ( tag != SLAP_TAG_UTCTIME && tag != SLAP_TAG_GENERALIZEDTIME ) return LDAP_INVALID_SYNTAX;
+ bvtu.bv_val = (char *)ber->ber_ptr;
+ bvtu.bv_len = len;
+ ber_skip_data( ber, len );
+ /* Optional nextUpdate */
+ tag = ber_skip_tag( ber, &len );
+ if ( tag == SLAP_TAG_UTCTIME || tag == SLAP_TAG_GENERALIZEDTIME ) {
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ /* revokedCertificates - Sequence of Sequence, Optional */
+ if ( tag == LBER_SEQUENCE ) {
+ ber_len_t seqlen;
+ ber_tag_t stag;
+ stag = ber_peek_tag( ber, &seqlen );
+ if ( stag == LBER_SEQUENCE || !len ) {
+ /* RFC5280 requires non-empty, but X.509(2005) allows empty. */
+ if ( len )
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ }
+ /* Optional Extensions - Sequence of Sequence */
+ if ( tag == SLAP_X509_OPT_CL_CRLEXTENSIONS ) { /* ? */
+ ber_len_t seqlen;
+ if ( version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX;
+ tag = ber_peek_tag( ber, &seqlen );
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ /* signatureAlgorithm */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ /* Signature */
+ if ( tag != LBER_BITSTRING ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ if ( ber->ber_ptr == wrapper_start + wrapper_len ) wrapper_ok = 1;
+ tag = ber_skip_tag( ber, &len );
+ /* Must be at end now */
+ /* NOTE: OpenSSL tolerates CL with garbage past the end */
+ if ( len || tag != LBER_DEFAULT ) {
+ struct berval issuer_dn = BER_BVNULL, thisUpdate;
+ char tubuf[STRLENOF("YYYYmmddHHMMSSZ") + 1];
+ int rc;
+
+ if ( ! wrapper_ok ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = dnX509normalize( &bvdn, &issuer_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ thisUpdate.bv_val = tubuf;
+ thisUpdate.bv_len = sizeof(tubuf);
+ if ( checkTime( &bvtu, &thisUpdate ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "certificateListValidate issuer=\"%s\", thisUpdate=%s: extra cruft past end of certificateList\n",
+ issuer_dn.bv_val, thisUpdate.bv_val );
+
+done:;
+ if ( ! BER_BVISNULL( &issuer_dn ) ) {
+ ber_memfree( issuer_dn.bv_val );
+ }
+
+ return rc;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* X.509 PMI Attribute Certificate Validate */
+static int
+attributeCertificateValidate( Syntax *syntax, struct berval *in )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t version;
+ int cont = 0;
+
+ ber_init2( ber, in, LBER_USE_DER );
+
+ tag = ber_skip_tag( ber, &len ); /* Signed wrapper */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+
+ tag = ber_peek_tag( ber, &len ); /* Version */
+ if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX;
+ tag = ber_get_int( ber, &version ); /* X.509 only allows v2 */
+ if ( version != SLAP_X509AC_V2 ) return LDAP_INVALID_SYNTAX;
+
+ tag = ber_skip_tag( ber, &len ); /* Holder */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* Issuer */
+ if ( tag != SLAP_X509AC_ISSUER ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* Signature */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* Serial number */
+ if ( tag != LBER_INTEGER ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* AttCertValidityPeriod */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* Attributes */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag == LBER_BITSTRING ) { /* issuerUniqueID */
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LBER_SEQUENCE ) { /* extensions or signatureAlgorithm */
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ cont++;
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LBER_SEQUENCE ) { /* signatureAlgorithm */
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ cont++;
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LBER_BITSTRING ) { /* Signature */
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ cont++;
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ /* Must be at end now */
+ if ( len != 0 || tag != LBER_DEFAULT || cont < 2 ) return LDAP_INVALID_SYNTAX;
+
+ return LDAP_SUCCESS;
+}
+
+/* accept a PKCS#8 private key */
+static int
+privateKeyValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t version;
+
+ ber_init2( ber, val, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_peek_tag( ber, &len );
+ if ( tag != LBER_INTEGER ) {
+ /* might be an encrypted key */
+ if ( tag == LBER_SEQUENCE ) { /* encryptionAlgorithm */
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* encryptedData */
+ if ( tag != LBER_OCTETSTRING ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ } else
+ return LDAP_INVALID_SYNTAX;
+ } else {
+ tag = ber_get_int( ber, &version );
+ tag = ber_skip_tag( ber, &len ); /* AlgorithmIdentifier */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* PrivateKey */
+ if ( tag != LBER_OCTETSTRING ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ if ( tag == LBER_SET ) { /* Optional Attributes */
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len );
+ }
+ }
+
+ /* Must be at end now */
+ if ( len || tag != LBER_DEFAULT ) return LDAP_INVALID_SYNTAX;
+ return LDAP_SUCCESS;
+}
+
+int
+octetStringMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *asserted = (struct berval *) assertedValue;
+ ber_slen_t d = (ber_slen_t) value->bv_len - (ber_slen_t) asserted->bv_len;
+
+ /* For speed, order first by length, then by contents */
+ *matchp = d ? (sizeof(d) == sizeof(int) ? d : d < 0 ? -1 : 1)
+ : memcmp( value->bv_val, asserted->bv_val, value->bv_len );
+
+ return LDAP_SUCCESS;
+}
+
+int
+octetStringOrderingMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *asserted = (struct berval *) assertedValue;
+ ber_len_t v_len = value->bv_len;
+ ber_len_t av_len = asserted->bv_len;
+
+ int match = memcmp( value->bv_val, asserted->bv_val,
+ (v_len < av_len ? v_len : av_len) );
+
+ if( match == 0 )
+ match = sizeof(v_len) == sizeof(int)
+ ? (int) v_len - (int) av_len
+ : v_len < av_len ? -1 : v_len > av_len;
+
+ /* If used in extensible match filter, match if value < asserted */
+ if ( flags & SLAP_MR_EXT )
+ match = (match >= 0);
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/* Initialize HASHcontext from match type and schema info */
+static void
+hashPreset(
+ HASH_CONTEXT *HASHcontext,
+ struct berval *prefix,
+ char pre,
+ Syntax *syntax,
+ MatchingRule *mr)
+{
+ HASH_Init(HASHcontext);
+ if(prefix && prefix->bv_len > 0) {
+ HASH_Update(HASHcontext,
+ (unsigned char *)prefix->bv_val, prefix->bv_len);
+ }
+ if(pre) HASH_Update(HASHcontext, (unsigned char*)&pre, sizeof(pre));
+ HASH_Update(HASHcontext, (unsigned char*)syntax->ssyn_oid, syntax->ssyn_oidlen);
+ HASH_Update(HASHcontext, (unsigned char*)mr->smr_oid, mr->smr_oidlen);
+ return;
+}
+
+/* Set HASHdigest from HASHcontext and value:len */
+static void
+hashIter(
+ HASH_CONTEXT *HASHcontext,
+ unsigned char *HASHdigest,
+ unsigned char *value,
+ int len)
+{
+ HASH_CONTEXT ctx = *HASHcontext;
+ HASH_Update( &ctx, value, len );
+ HASH_Final( HASHdigest, &ctx );
+}
+
+/* Index generation function: Attribute values -> index hash keys */
+int octetStringIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ int i;
+ BerVarray keys;
+ HASH_CONTEXT HASHcontext;
+ unsigned char HASHdigest[HASH_BYTES];
+ struct berval digest;
+ digest.bv_val = (char *)HASHdigest;
+ digest.bv_len = HASH_LEN;
+
+ for( i=0; !BER_BVISNULL( &values[i] ); i++ ) {
+ /* just count them */
+ }
+
+ /* we should have at least one value at this point */
+ assert( i > 0 );
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx );
+
+ hashPreset( &HASHcontext, prefix, 0, syntax, mr);
+ for( i=0; !BER_BVISNULL( &values[i] ); i++ ) {
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)values[i].bv_val, values[i].bv_len );
+ ber_dupbv_x( &keys[i], &digest, ctx );
+ }
+
+ BER_BVZERO( &keys[i] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Asserted value -> index hash key */
+int octetStringFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ BerVarray keys;
+ HASH_CONTEXT HASHcontext;
+ unsigned char HASHdigest[HASH_BYTES];
+ struct berval *value = (struct berval *) assertedValue;
+ struct berval digest;
+ digest.bv_val = (char *)HASHdigest;
+ digest.bv_len = HASH_LEN;
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx );
+
+ hashPreset( &HASHcontext, prefix, 0, syntax, mr );
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)value->bv_val, value->bv_len );
+
+ ber_dupbv_x( keys, &digest, ctx );
+ BER_BVZERO( &keys[1] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+octetStringSubstringsMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match = 0;
+ SubstringsAssertion *sub = assertedValue;
+ struct berval left = *value;
+ int i;
+ ber_len_t inlen = 0;
+
+ /* Add up asserted input length */
+ if ( !BER_BVISNULL( &sub->sa_initial ) ) {
+ inlen += sub->sa_initial.bv_len;
+ }
+ if ( sub->sa_any ) {
+ for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) {
+ inlen += sub->sa_any[i].bv_len;
+ }
+ }
+ if ( !BER_BVISNULL( &sub->sa_final ) ) {
+ inlen += sub->sa_final.bv_len;
+ }
+
+ if ( !BER_BVISNULL( &sub->sa_initial ) ) {
+ if ( inlen > left.bv_len ) {
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( sub->sa_initial.bv_val, left.bv_val,
+ sub->sa_initial.bv_len );
+
+ if ( match != 0 ) {
+ goto done;
+ }
+
+ left.bv_val += sub->sa_initial.bv_len;
+ left.bv_len -= sub->sa_initial.bv_len;
+ inlen -= sub->sa_initial.bv_len;
+ }
+
+ if ( !BER_BVISNULL( &sub->sa_final ) ) {
+ if ( inlen > left.bv_len ) {
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( sub->sa_final.bv_val,
+ &left.bv_val[left.bv_len - sub->sa_final.bv_len],
+ sub->sa_final.bv_len );
+
+ if ( match != 0 ) {
+ goto done;
+ }
+
+ left.bv_len -= sub->sa_final.bv_len;
+ inlen -= sub->sa_final.bv_len;
+ }
+
+ if ( sub->sa_any ) {
+ for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) {
+ ber_len_t idx;
+ char *p;
+
+retry:
+ if ( inlen > left.bv_len ) {
+ /* not enough length */
+ match = 1;
+ goto done;
+ }
+
+ if ( BER_BVISEMPTY( &sub->sa_any[i] ) ) {
+ continue;
+ }
+
+ p = memchr( left.bv_val, *sub->sa_any[i].bv_val, left.bv_len );
+
+ if( p == NULL ) {
+ match = 1;
+ goto done;
+ }
+
+ idx = p - left.bv_val;
+
+ if ( idx >= left.bv_len ) {
+ /* this shouldn't happen */
+ return LDAP_OTHER;
+ }
+
+ left.bv_val = p;
+ left.bv_len -= idx;
+
+ if ( sub->sa_any[i].bv_len > left.bv_len ) {
+ /* not enough left */
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( left.bv_val,
+ sub->sa_any[i].bv_val,
+ sub->sa_any[i].bv_len );
+
+ if ( match != 0 ) {
+ left.bv_val++;
+ left.bv_len--;
+ goto retry;
+ }
+
+ left.bv_val += sub->sa_any[i].bv_len;
+ left.bv_len -= sub->sa_any[i].bv_len;
+ inlen -= sub->sa_any[i].bv_len;
+ }
+ }
+
+done:
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/* Substring index generation function: Attribute values -> index hash keys */
+static int
+octetStringSubstringsIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ ber_len_t i, nkeys;
+ BerVarray keys;
+
+ HASH_CONTEXT HCany, HCini, HCfin;
+ unsigned char HASHdigest[HASH_BYTES];
+ struct berval digest;
+ digest.bv_val = (char *)HASHdigest;
+ digest.bv_len = HASH_LEN;
+
+ nkeys = 0;
+
+ for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) {
+ /* count number of indices to generate */
+ if( flags & SLAP_INDEX_SUBSTR_INITIAL ) {
+ if( values[i].bv_len >= index_substr_if_maxlen ) {
+ nkeys += index_substr_if_maxlen -
+ (index_substr_if_minlen - 1);
+ } else if( values[i].bv_len >= index_substr_if_minlen ) {
+ nkeys += values[i].bv_len - (index_substr_if_minlen - 1);
+ }
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_ANY ) {
+ if( values[i].bv_len >= index_substr_any_len ) {
+ nkeys += values[i].bv_len - (index_substr_any_len - 1);
+ }
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_FINAL ) {
+ if( values[i].bv_len >= index_substr_if_maxlen ) {
+ nkeys += index_substr_if_maxlen -
+ (index_substr_if_minlen - 1);
+ } else if( values[i].bv_len >= index_substr_if_minlen ) {
+ nkeys += values[i].bv_len - (index_substr_if_minlen - 1);
+ }
+ }
+ }
+
+ if( nkeys == 0 ) {
+ /* no keys to generate */
+ *keysp = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * (nkeys+1), ctx );
+
+ if ( flags & SLAP_INDEX_SUBSTR_ANY )
+ hashPreset( &HCany, prefix, SLAP_INDEX_SUBSTR_PREFIX, syntax, mr );
+ if( flags & SLAP_INDEX_SUBSTR_INITIAL )
+ hashPreset( &HCini, prefix, SLAP_INDEX_SUBSTR_INITIAL_PREFIX, syntax, mr );
+ if( flags & SLAP_INDEX_SUBSTR_FINAL )
+ hashPreset( &HCfin, prefix, SLAP_INDEX_SUBSTR_FINAL_PREFIX, syntax, mr );
+
+ nkeys = 0;
+ for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) {
+ ber_len_t j,max;
+
+ if( ( flags & SLAP_INDEX_SUBSTR_ANY ) &&
+ ( values[i].bv_len >= index_substr_any_len ) )
+ {
+ max = values[i].bv_len - (index_substr_any_len - 1);
+
+ for( j=0; j<max; j++ ) {
+ hashIter( &HCany, HASHdigest,
+ (unsigned char *)&values[i].bv_val[j],
+ index_substr_any_len );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+ }
+
+ /* skip if too short */
+ if( values[i].bv_len < index_substr_if_minlen ) continue;
+
+ max = index_substr_if_maxlen < values[i].bv_len
+ ? index_substr_if_maxlen : values[i].bv_len;
+
+ for( j=index_substr_if_minlen; j<=max; j++ ) {
+
+ if( flags & SLAP_INDEX_SUBSTR_INITIAL ) {
+ hashIter( &HCini, HASHdigest,
+ (unsigned char *)values[i].bv_val, j );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_FINAL ) {
+ hashIter( &HCfin, HASHdigest,
+ (unsigned char *)&values[i].bv_val[values[i].bv_len-j], j );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+
+ }
+ }
+
+ if( nkeys > 0 ) {
+ BER_BVZERO( &keys[nkeys] );
+ *keysp = keys;
+ } else {
+ ch_free( keys );
+ *keysp = NULL;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Substring index generation function: Assertion value -> index hash keys */
+static int
+octetStringSubstringsFilter (
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx)
+{
+ SubstringsAssertion *sa;
+ char pre;
+ ber_len_t nkeys = 0;
+ size_t klen;
+ BerVarray keys;
+ HASH_CONTEXT HASHcontext;
+ unsigned char HASHdigest[HASH_BYTES];
+ struct berval *value;
+ struct berval digest;
+
+ sa = (SubstringsAssertion *) assertedValue;
+
+ if( flags & SLAP_INDEX_SUBSTR_INITIAL &&
+ !BER_BVISNULL( &sa->sa_initial ) &&
+ sa->sa_initial.bv_len >= index_substr_if_minlen )
+ {
+ nkeys++;
+ if ( sa->sa_initial.bv_len > index_substr_if_maxlen &&
+ ( flags & SLAP_INDEX_SUBSTR_ANY ))
+ {
+ nkeys += 1 + (sa->sa_initial.bv_len - index_substr_if_maxlen) / index_substr_any_step;
+ }
+ }
+
+ if ( flags & SLAP_INDEX_SUBSTR_ANY && sa->sa_any != NULL ) {
+ ber_len_t i;
+ for( i=0; !BER_BVISNULL( &sa->sa_any[i] ); i++ ) {
+ if( sa->sa_any[i].bv_len >= index_substr_any_len ) {
+ /* don't bother accounting with stepping */
+ nkeys += sa->sa_any[i].bv_len -
+ ( index_substr_any_len - 1 );
+ }
+ }
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_FINAL &&
+ !BER_BVISNULL( &sa->sa_final ) &&
+ sa->sa_final.bv_len >= index_substr_if_minlen )
+ {
+ nkeys++;
+ if ( sa->sa_final.bv_len > index_substr_if_maxlen &&
+ ( flags & SLAP_INDEX_SUBSTR_ANY ))
+ {
+ nkeys += 1 + (sa->sa_final.bv_len - index_substr_if_maxlen) / index_substr_any_step;
+ }
+ }
+
+ if( nkeys == 0 ) {
+ *keysp = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ digest.bv_val = (char *)HASHdigest;
+ digest.bv_len = HASH_LEN;
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * (nkeys+1), ctx );
+ nkeys = 0;
+
+ if( flags & SLAP_INDEX_SUBSTR_INITIAL &&
+ !BER_BVISNULL( &sa->sa_initial ) &&
+ sa->sa_initial.bv_len >= index_substr_if_minlen )
+ {
+ pre = SLAP_INDEX_SUBSTR_INITIAL_PREFIX;
+ value = &sa->sa_initial;
+
+ klen = index_substr_if_maxlen < value->bv_len
+ ? index_substr_if_maxlen : value->bv_len;
+
+ hashPreset( &HASHcontext, prefix, pre, syntax, mr );
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)value->bv_val, klen );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+
+ /* If initial is too long and we have subany indexed, use it
+ * to match the excess...
+ */
+ if (value->bv_len > index_substr_if_maxlen && (flags & SLAP_INDEX_SUBSTR_ANY))
+ {
+ ber_len_t j;
+ pre = SLAP_INDEX_SUBSTR_PREFIX;
+ hashPreset( &HASHcontext, prefix, pre, syntax, mr);
+ for ( j=index_substr_if_maxlen-1; j <= value->bv_len - index_substr_any_len; j+=index_substr_any_step )
+ {
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)&value->bv_val[j], index_substr_any_len );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+ }
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_ANY && sa->sa_any != NULL ) {
+ ber_len_t i, j;
+ pre = SLAP_INDEX_SUBSTR_PREFIX;
+ klen = index_substr_any_len;
+
+ for( i=0; !BER_BVISNULL( &sa->sa_any[i] ); i++ ) {
+ if( sa->sa_any[i].bv_len < index_substr_any_len ) {
+ continue;
+ }
+
+ value = &sa->sa_any[i];
+
+ hashPreset( &HASHcontext, prefix, pre, syntax, mr);
+ for(j=0;
+ j <= value->bv_len - index_substr_any_len;
+ j += index_substr_any_step )
+ {
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)&value->bv_val[j], klen );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+ }
+ }
+
+ if( flags & SLAP_INDEX_SUBSTR_FINAL &&
+ !BER_BVISNULL( &sa->sa_final ) &&
+ sa->sa_final.bv_len >= index_substr_if_minlen )
+ {
+ pre = SLAP_INDEX_SUBSTR_FINAL_PREFIX;
+ value = &sa->sa_final;
+
+ klen = index_substr_if_maxlen < value->bv_len
+ ? index_substr_if_maxlen : value->bv_len;
+
+ hashPreset( &HASHcontext, prefix, pre, syntax, mr );
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)&value->bv_val[value->bv_len-klen], klen );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+
+ /* If final is too long and we have subany indexed, use it
+ * to match the excess...
+ */
+ if (value->bv_len > index_substr_if_maxlen && (flags & SLAP_INDEX_SUBSTR_ANY))
+ {
+ ber_len_t j;
+ pre = SLAP_INDEX_SUBSTR_PREFIX;
+ hashPreset( &HASHcontext, prefix, pre, syntax, mr);
+ for ( j=0; j <= value->bv_len - index_substr_if_maxlen; j+=index_substr_any_step )
+ {
+ hashIter( &HASHcontext, HASHdigest,
+ (unsigned char *)&value->bv_val[j], index_substr_any_len );
+ ber_dupbv_x( &keys[nkeys++], &digest, ctx );
+ }
+ }
+ }
+
+ if( nkeys > 0 ) {
+ BER_BVZERO( &keys[nkeys] );
+ *keysp = keys;
+ } else {
+ ch_free( keys );
+ *keysp = NULL;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+bitStringValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ ber_len_t i;
+
+ /* very unforgiving validation, requires no normalization
+ * before simplistic matching
+ */
+ if( in->bv_len < 3 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* RFC 4517 Section 3.3.2 Bit String:
+ * BitString = SQUOTE *binary-digit SQUOTE "B"
+ * binary-digit = "0" / "1"
+ *
+ * where SQUOTE [RFC4512] is
+ * SQUOTE = %x27 ; single quote ("'")
+ *
+ * Example: '0101111101'B
+ */
+
+ if( in->bv_val[0] != '\'' ||
+ in->bv_val[in->bv_len - 2] != '\'' ||
+ in->bv_val[in->bv_len - 1] != 'B' )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for( i = in->bv_len - 3; i > 0; i-- ) {
+ if( in->bv_val[i] != '0' && in->bv_val[i] != '1' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Syntaxes from RFC 4517
+ *
+
+3.3.2. Bit String
+
+ A value of the Bit String syntax is a sequence of binary digits. The
+ LDAP-specific encoding of a value of this syntax is defined by the
+ following ABNF:
+
+ BitString = SQUOTE *binary-digit SQUOTE "B"
+
+ binary-digit = "0" / "1"
+
+ The <SQUOTE> rule is defined in [MODELS].
+
+ Example:
+ '0101111101'B
+
+ The LDAP definition for the Bit String syntax is:
+
+ ( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' )
+
+ This syntax corresponds to the BIT STRING ASN.1 type from [ASN.1].
+
+ ...
+
+3.3.21. Name and Optional UID
+
+ A value of the Name and Optional UID syntax is the distinguished name
+ [MODELS] of an entity optionally accompanied by a unique identifier
+ that serves to differentiate the entity from others with an identical
+ distinguished name.
+
+ The LDAP-specific encoding of a value of this syntax is defined by
+ the following ABNF:
+
+ NameAndOptionalUID = distinguishedName [ SHARP BitString ]
+
+ The <BitString> rule is defined in Section 3.3.2. The
+ <distinguishedName> rule is defined in [LDAPDN]. The <SHARP> rule is
+ defined in [MODELS].
+
+ Note that although the '#' character may occur in the string
+ representation of a distinguished name, no additional escaping of
+ this character is performed when a <distinguishedName> is encoded in
+ a <NameAndOptionalUID>.
+
+ Example:
+ 1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB#'0101'B
+
+ The LDAP definition for the Name and Optional UID syntax is:
+
+ ( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' )
+
+ This syntax corresponds to the NameAndOptionalUID ASN.1 type from
+ [X.520].
+
+ *
+ * RFC 4512 says:
+ *
+
+1.4. Common ABNF Productions
+
+ ...
+ SHARP = %x23 ; octothorpe (or sharp sign) ("#")
+ ...
+ SQUOTE = %x27 ; single quote ("'")
+ ...
+
+ *
+ * Note:
+ * RFC 4514 clarifies that SHARP, i.e. "#", doesn't have to
+ * be escaped except when at the beginning of a value, the
+ * definition of Name and Optional UID appears to be flawed,
+ * because there is no clear means to determine whether the
+ * UID part is present or not.
+ *
+ * Example:
+ *
+ * cn=Someone,dc=example,dc=com#'1'B
+ *
+ * could be either a NameAndOptionalUID with trailing UID, i.e.
+ *
+ * DN = "cn=Someone,dc=example,dc=com"
+ * UID = "'1'B"
+ *
+ * or a NameAndOptionalUID with no trailing UID, and the AVA
+ * in the last RDN made of
+ *
+ * attributeType = dc
+ * attributeValue = com#'1'B
+ *
+ * in fact "com#'1'B" is a valid IA5 string.
+ *
+ * As a consequence, current slapd code takes the presence of
+ * #<valid BitString> at the end of the string representation
+ * of a NameAndOptionalUID to mean this is indeed a BitString.
+ * This is quite arbitrary - it has changed the past and might
+ * change in the future.
+ */
+
+
+static int
+nameUIDValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ struct berval dn, uid;
+
+ if( BER_BVISEMPTY( in ) ) return LDAP_SUCCESS;
+
+ ber_dupbv( &dn, in );
+ if( !dn.bv_val ) return LDAP_OTHER;
+
+ /* if there's a "#", try bitStringValidate()... */
+ uid.bv_val = strrchr( dn.bv_val, '#' );
+ if ( !BER_BVISNULL( &uid ) ) {
+ uid.bv_val++;
+ uid.bv_len = dn.bv_len - ( uid.bv_val - dn.bv_val );
+
+ rc = bitStringValidate( NULL, &uid );
+ if ( rc == LDAP_SUCCESS ) {
+ /* in case of success, trim the UID,
+ * otherwise treat it as part of the DN */
+ dn.bv_len -= uid.bv_len + 1;
+ uid.bv_val[-1] = '\0';
+ }
+ }
+
+ rc = dnValidate( NULL, &dn );
+
+ ber_memfree( dn.bv_val );
+ return rc;
+}
+
+int
+nameUIDPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ assert( val != NULL );
+ assert( out != NULL );
+
+
+ Debug( LDAP_DEBUG_TRACE, ">>> nameUIDPretty: <%s>\n", val->bv_val );
+
+ if( BER_BVISEMPTY( val ) ) {
+ ber_dupbv_x( out, val, ctx );
+
+ } else if ( val->bv_len > SLAP_LDAPDN_MAXLEN ) {
+ return LDAP_INVALID_SYNTAX;
+
+ } else {
+ int rc;
+ struct berval dnval = *val;
+ struct berval uidval = BER_BVNULL;
+
+ uidval.bv_val = strrchr( val->bv_val, '#' );
+ if ( !BER_BVISNULL( &uidval ) ) {
+ uidval.bv_val++;
+ uidval.bv_len = val->bv_len - ( uidval.bv_val - val->bv_val );
+
+ rc = bitStringValidate( NULL, &uidval );
+
+ if ( rc == LDAP_SUCCESS ) {
+ ber_dupbv_x( &dnval, val, ctx );
+ uidval.bv_val--;
+ dnval.bv_len -= ++uidval.bv_len;
+ dnval.bv_val[dnval.bv_len] = '\0';
+
+ } else {
+ BER_BVZERO( &uidval );
+ }
+ }
+
+ rc = dnPretty( syntax, &dnval, out, ctx );
+ if ( dnval.bv_val != val->bv_val ) {
+ slap_sl_free( dnval.bv_val, ctx );
+ }
+ if( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if( !BER_BVISNULL( &uidval ) ) {
+ char *tmp;
+
+ tmp = slap_sl_realloc( out->bv_val, out->bv_len
+ + uidval.bv_len + 1,
+ ctx );
+ if( tmp == NULL ) {
+ ber_memfree_x( out->bv_val, ctx );
+ return LDAP_OTHER;
+ }
+ out->bv_val = tmp;
+ memcpy( out->bv_val + out->bv_len, uidval.bv_val, uidval.bv_len );
+ out->bv_len += uidval.bv_len;
+ out->bv_val[out->bv_len] = '\0';
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< nameUIDPretty: <%s>\n", out->bv_val );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+uniqueMemberNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval out;
+ int rc;
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 );
+
+ ber_dupbv_x( &out, val, ctx );
+ if ( BER_BVISEMPTY( &out ) ) {
+ *normalized = out;
+
+ } else {
+ struct berval uid = BER_BVNULL;
+
+ uid.bv_val = strrchr( out.bv_val, '#' );
+ if ( !BER_BVISNULL( &uid ) ) {
+ uid.bv_val++;
+ uid.bv_len = out.bv_len - ( uid.bv_val - out.bv_val );
+
+ rc = bitStringValidate( NULL, &uid );
+ if ( rc == LDAP_SUCCESS ) {
+ uid.bv_val[-1] = '\0';
+ out.bv_len -= uid.bv_len + 1;
+ } else {
+ BER_BVZERO( &uid );
+ }
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &out, normalized, ctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ slap_sl_free( out.bv_val, ctx );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( !BER_BVISNULL( &uid ) ) {
+ char *tmp;
+
+ tmp = ch_realloc( normalized->bv_val,
+ normalized->bv_len + uid.bv_len
+ + STRLENOF("#") + 1 );
+ if ( tmp == NULL ) {
+ ber_memfree_x( normalized->bv_val, ctx );
+ return LDAP_OTHER;
+ }
+
+ normalized->bv_val = tmp;
+
+ /* insert the separator */
+ normalized->bv_val[normalized->bv_len++] = '#';
+
+ /* append the UID */
+ AC_MEMCPY( &normalized->bv_val[normalized->bv_len],
+ uid.bv_val, uid.bv_len );
+ normalized->bv_len += uid.bv_len;
+
+ /* terminate */
+ normalized->bv_val[normalized->bv_len] = '\0';
+ }
+
+ slap_sl_free( out.bv_val, ctx );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+uniqueMemberMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match;
+ struct berval *asserted = (struct berval *) assertedValue;
+ struct berval assertedDN = *asserted;
+ struct berval assertedUID = BER_BVNULL;
+ struct berval valueDN = *value;
+ struct berval valueUID = BER_BVNULL;
+ int approx = ((flags & SLAP_MR_EQUALITY_APPROX) == SLAP_MR_EQUALITY_APPROX);
+
+ if ( !BER_BVISEMPTY( asserted ) ) {
+ assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' );
+ if ( !BER_BVISNULL( &assertedUID ) ) {
+ assertedUID.bv_val++;
+ assertedUID.bv_len = assertedDN.bv_len
+ - ( assertedUID.bv_val - assertedDN.bv_val );
+
+ if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) {
+ assertedDN.bv_len -= assertedUID.bv_len + 1;
+
+ } else {
+ BER_BVZERO( &assertedUID );
+ }
+ }
+ }
+
+ if ( !BER_BVISEMPTY( value ) ) {
+
+ valueUID.bv_val = strrchr( valueDN.bv_val, '#' );
+ if ( !BER_BVISNULL( &valueUID ) ) {
+ valueUID.bv_val++;
+ valueUID.bv_len = valueDN.bv_len
+ - ( valueUID.bv_val - valueDN.bv_val );
+
+ if ( bitStringValidate( NULL, &valueUID ) == LDAP_SUCCESS ) {
+ valueDN.bv_len -= valueUID.bv_len + 1;
+
+ } else {
+ BER_BVZERO( &valueUID );
+ }
+ }
+ }
+
+ if( valueUID.bv_len && assertedUID.bv_len ) {
+ ber_slen_t d;
+ d = (ber_slen_t) valueUID.bv_len - (ber_slen_t) assertedUID.bv_len;
+ if ( d ) {
+ *matchp = sizeof(d) == sizeof(int) ? d : d < 0 ? -1 : 1;
+ return LDAP_SUCCESS;
+ }
+
+ match = memcmp( valueUID.bv_val, assertedUID.bv_val, valueUID.bv_len );
+ if( match ) {
+ *matchp = match;
+ return LDAP_SUCCESS;
+ }
+
+ } else if ( !approx && valueUID.bv_len ) {
+ match = -1;
+ *matchp = match;
+ return LDAP_SUCCESS;
+
+ } else if ( !approx && assertedUID.bv_len ) {
+ match = 1;
+ *matchp = match;
+ return LDAP_SUCCESS;
+ }
+
+ return dnMatch( matchp, flags, syntax, mr, &valueDN, &assertedDN );
+}
+
+static int
+uniqueMemberIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ BerVarray dnvalues;
+ int rc;
+ int i;
+ for( i=0; !BER_BVISNULL( &values[i] ); i++ ) {
+ /* just count them */
+ }
+ assert( i > 0 );
+
+ dnvalues = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx );
+
+ for( i=0; !BER_BVISNULL( &values[i] ); i++ ) {
+ struct berval assertedDN = values[i];
+ struct berval assertedUID = BER_BVNULL;
+
+ if ( !BER_BVISEMPTY( &assertedDN ) ) {
+ assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' );
+ if ( !BER_BVISNULL( &assertedUID ) ) {
+ assertedUID.bv_val++;
+ assertedUID.bv_len = assertedDN.bv_len
+ - ( assertedUID.bv_val - assertedDN.bv_val );
+
+ if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) {
+ assertedDN.bv_len -= assertedUID.bv_len + 1;
+
+ } else {
+ BER_BVZERO( &assertedUID );
+ }
+ }
+ }
+
+ dnvalues[i] = assertedDN;
+ }
+ BER_BVZERO( &dnvalues[i] );
+
+ rc = octetStringIndexer( use, flags, syntax, mr, prefix,
+ dnvalues, keysp, ctx );
+
+ slap_sl_free( dnvalues, ctx );
+ return rc;
+}
+
+static int
+uniqueMemberFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ struct berval *asserted = (struct berval *) assertedValue;
+ struct berval assertedDN = *asserted;
+ struct berval assertedUID = BER_BVNULL;
+
+ if ( !BER_BVISEMPTY( asserted ) ) {
+ assertedUID.bv_val = strrchr( assertedDN.bv_val, '#' );
+ if ( !BER_BVISNULL( &assertedUID ) ) {
+ assertedUID.bv_val++;
+ assertedUID.bv_len = assertedDN.bv_len
+ - ( assertedUID.bv_val - assertedDN.bv_val );
+
+ if ( bitStringValidate( NULL, &assertedUID ) == LDAP_SUCCESS ) {
+ assertedDN.bv_len -= assertedUID.bv_len + 1;
+
+ } else {
+ BER_BVZERO( &assertedUID );
+ }
+ }
+ }
+
+ return octetStringFilter( use, flags, syntax, mr, prefix,
+ &assertedDN, keysp, ctx );
+}
+
+
+/*
+ * Handling boolean syntax and matching is quite rigid.
+ * A more flexible approach would be to allow a variety
+ * of strings to be normalized and prettied into TRUE
+ * and FALSE.
+ */
+static int
+booleanValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ /* very unforgiving validation, requires no normalization
+ * before simplistic matching
+ */
+
+ if( in->bv_len == 4 ) {
+ if( bvmatch( in, &slap_true_bv ) ) {
+ return LDAP_SUCCESS;
+ }
+ } else if( in->bv_len == 5 ) {
+ if( bvmatch( in, &slap_false_bv ) ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+booleanMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ /* simplistic matching allowed by rigid validation */
+ struct berval *asserted = (struct berval *) assertedValue;
+ *matchp = (int) asserted->bv_len - (int) value->bv_len;
+ return LDAP_SUCCESS;
+}
+
+/*-------------------------------------------------------------------
+LDAP/X.500 string syntax / matching rules have a few oddities. This
+comment attempts to detail how slapd(8) treats them.
+
+Summary:
+ StringSyntax X.500 LDAP Matching/Comments
+ DirectoryString CHOICE UTF8 i/e + ignore insignificant spaces
+ PrintableString subset subset i/e + ignore insignificant spaces
+ PrintableString subset subset i/e + ignore insignificant spaces
+ NumericString subset subset ignore all spaces
+ IA5String ASCII ASCII i/e + ignore insignificant spaces
+ TeletexString T.61 T.61 i/e + ignore insignificant spaces
+
+ TelephoneNumber subset subset i + ignore all spaces and "-"
+
+ See RFC 4518 for details.
+
+
+Directory String -
+ In X.500(93), a directory string can be either a PrintableString,
+ a bmpString, or a UniversalString (e.g., UCS (a subset of Unicode)).
+ In later versions, more CHOICEs were added. In all cases the string
+ must be non-empty.
+
+ In LDAPv3, a directory string is a UTF-8 encoded UCS string.
+ A directory string cannot be zero length.
+
+ For matching, there are both case ignore and exact rules. Both
+ also require that "insignificant" spaces be ignored.
+ spaces before the first non-space are ignored;
+ spaces after the last non-space are ignored;
+ spaces after a space are ignored.
+ Note: by these rules (and as clarified in X.520), a string of only
+ spaces is to be treated as if held one space, not empty (which
+ would be a syntax error).
+
+NumericString
+ In ASN.1, numeric string is just a string of digits and spaces
+ and could be empty. However, in X.500, all attribute values of
+ numeric string carry a non-empty constraint. For example:
+
+ internationalISDNNumber ATTRIBUTE ::= {
+ WITH SYNTAX InternationalISDNNumber
+ EQUALITY MATCHING RULE numericStringMatch
+ SUBSTRINGS MATCHING RULE numericStringSubstringsMatch
+ ID id-at-internationalISDNNumber }
+ InternationalISDNNumber ::=
+ NumericString (SIZE(1..ub-international-isdn-number))
+
+ Unfortunately, some assertion values are don't carry the same
+ constraint (but its unclear how such an assertion could ever
+ be true). In LDAP, there is one syntax (numericString) not two
+ (numericString with constraint, numericString without constraint).
+ This should be treated as numericString with non-empty constraint.
+ Note that while someone may have no ISDN number, there are no ISDN
+ numbers which are zero length.
+
+ In matching, spaces are ignored.
+
+PrintableString
+ In ASN.1, Printable string is just a string of printable characters
+ and can be empty. In X.500, semantics much like NumericString (see
+ serialNumber for a like example) excepting uses insignificant space
+ handling instead of ignore all spaces. They must be non-empty.
+
+IA5String
+ Basically same as PrintableString. There are no examples in X.500,
+ but same logic applies. Empty strings are allowed.
+
+-------------------------------------------------------------------*/
+
+static int
+UTF8StringValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int len;
+ unsigned char *u = (unsigned char *)in->bv_val, *end = (unsigned char *)in->bv_val + in->bv_len;
+
+ if( BER_BVISEMPTY( in ) && syntax == slap_schema.si_syn_directoryString ) {
+ /* directory strings cannot be empty */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for( ; u < end; u += len ) {
+ /* get the length indicated by the first byte */
+ len = LDAP_UTF8_CHARLEN2( u, len );
+ if ( u + len > end )
+ return LDAP_INVALID_SYNTAX;
+
+ /* very basic checks */
+ switch( len ) {
+ case 6:
+ if( (u[5] & 0xC0) != 0x80 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ case 5:
+ if( (u[4] & 0xC0) != 0x80 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ case 4:
+ if( (u[3] & 0xC0) != 0x80 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ case 3:
+ if( (u[2] & 0xC0 )!= 0x80 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ case 2:
+ if( (u[1] & 0xC0) != 0x80 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ case 1:
+ /* CHARLEN already validated it */
+ break;
+ default:
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* make sure len corresponds with the offset
+ to the next character */
+ if( LDAP_UTF8_OFFSET( (char *)u ) != len ) return LDAP_INVALID_SYNTAX;
+ }
+
+ if( u > end ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+UTF8StringNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval tmp, nvalue;
+ int flags, wasspace;
+ ber_len_t i;
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( use ) != 0 );
+
+ if( BER_BVISNULL( val ) ) {
+ /* assume we're dealing with a syntax (e.g., UTF8String)
+ * which allows empty strings
+ */
+ BER_BVZERO( normalized );
+ return LDAP_SUCCESS;
+ }
+
+ flags = SLAP_MR_ASSOCIATED( mr, slap_schema.si_mr_caseExactMatch )
+ ? LDAP_UTF8_NOCASEFOLD : LDAP_UTF8_CASEFOLD;
+ flags |= ( ( use & SLAP_MR_EQUALITY_APPROX ) == SLAP_MR_EQUALITY_APPROX )
+ ? LDAP_UTF8_APPROX : 0;
+
+ val = UTF8bvnormalize( val, &tmp, flags, ctx );
+ /* out of memory or syntax error, the former is unlikely */
+ if( val == NULL ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* collapse spaces (in place) */
+ nvalue.bv_len = 0;
+ nvalue.bv_val = tmp.bv_val;
+
+ /* trim leading spaces? */
+ wasspace = !((( use & SLAP_MR_SUBSTR_ANY ) == SLAP_MR_SUBSTR_ANY ) ||
+ (( use & SLAP_MR_SUBSTR_FINAL ) == SLAP_MR_SUBSTR_FINAL ));
+
+ for( i = 0; i < tmp.bv_len; i++) {
+ if ( ASCII_SPACE( tmp.bv_val[i] )) {
+ if( wasspace++ == 0 ) {
+ /* trim repeated spaces */
+ nvalue.bv_val[nvalue.bv_len++] = tmp.bv_val[i];
+ }
+ } else {
+ wasspace = 0;
+ nvalue.bv_val[nvalue.bv_len++] = tmp.bv_val[i];
+ }
+ }
+
+ if( !BER_BVISEMPTY( &nvalue ) ) {
+ /* trim trailing space? */
+ if( wasspace && (
+ (( use & SLAP_MR_SUBSTR_INITIAL ) != SLAP_MR_SUBSTR_INITIAL ) &&
+ ( use & SLAP_MR_SUBSTR_ANY ) != SLAP_MR_SUBSTR_ANY ))
+ {
+ --nvalue.bv_len;
+ }
+ nvalue.bv_val[nvalue.bv_len] = '\0';
+
+ } else if ( tmp.bv_len ) {
+ /* string of all spaces is treated as one space */
+ nvalue.bv_val[0] = ' ';
+ nvalue.bv_val[1] = '\0';
+ nvalue.bv_len = 1;
+ } /* should never be entered with 0-length val */
+
+ *normalized = nvalue;
+ return LDAP_SUCCESS;
+}
+
+static int
+directoryStringSubstringsMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ int match = 0;
+ SubstringsAssertion *sub = assertedValue;
+ struct berval left = *value;
+ ber_len_t i;
+ int priorspace=0;
+
+ if ( !BER_BVISNULL( &sub->sa_initial ) ) {
+ if ( sub->sa_initial.bv_len > left.bv_len ) {
+ /* not enough left */
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( sub->sa_initial.bv_val, left.bv_val,
+ sub->sa_initial.bv_len );
+
+ if ( match != 0 ) {
+ goto done;
+ }
+
+ left.bv_val += sub->sa_initial.bv_len;
+ left.bv_len -= sub->sa_initial.bv_len;
+
+ priorspace = ASCII_SPACE(
+ sub->sa_initial.bv_val[sub->sa_initial.bv_len] );
+ }
+
+ if ( sub->sa_any ) {
+ for ( i = 0; !BER_BVISNULL( &sub->sa_any[i] ); i++ ) {
+ ber_len_t idx;
+ char *p;
+
+ if( priorspace && !BER_BVISEMPTY( &sub->sa_any[i] )
+ && ASCII_SPACE( sub->sa_any[i].bv_val[0] ))
+ {
+ /* allow next space to match */
+ left.bv_val--;
+ left.bv_len++;
+ }
+ priorspace=0;
+
+retry:
+ if ( BER_BVISEMPTY( &sub->sa_any[i] ) ) {
+ continue;
+ }
+
+ if ( sub->sa_any[i].bv_len > left.bv_len ) {
+ /* not enough left */
+ match = 1;
+ goto done;
+ }
+
+ p = memchr( left.bv_val, *sub->sa_any[i].bv_val, left.bv_len );
+
+ if( p == NULL ) {
+ match = 1;
+ goto done;
+ }
+
+ idx = p - left.bv_val;
+
+ if ( idx >= left.bv_len ) {
+ /* this shouldn't happen */
+ return LDAP_OTHER;
+ }
+
+ left.bv_val = p;
+ left.bv_len -= idx;
+
+ if ( sub->sa_any[i].bv_len > left.bv_len ) {
+ /* not enough left */
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( left.bv_val,
+ sub->sa_any[i].bv_val,
+ sub->sa_any[i].bv_len );
+
+ if ( match != 0 ) {
+ left.bv_val++;
+ left.bv_len--;
+ goto retry;
+ }
+
+ left.bv_val += sub->sa_any[i].bv_len;
+ left.bv_len -= sub->sa_any[i].bv_len;
+
+ priorspace = ASCII_SPACE(
+ sub->sa_any[i].bv_val[sub->sa_any[i].bv_len] );
+ }
+ }
+
+ if ( !BER_BVISNULL( &sub->sa_final ) ) {
+ if( priorspace && !BER_BVISEMPTY( &sub->sa_final )
+ && ASCII_SPACE( sub->sa_final.bv_val[0] ))
+ {
+ /* allow next space to match */
+ left.bv_val--;
+ left.bv_len++;
+ }
+
+ if ( sub->sa_final.bv_len > left.bv_len ) {
+ /* not enough left */
+ match = 1;
+ goto done;
+ }
+
+ match = memcmp( sub->sa_final.bv_val,
+ &left.bv_val[left.bv_len - sub->sa_final.bv_len],
+ sub->sa_final.bv_len );
+
+ if ( match != 0 ) {
+ goto done;
+ }
+ }
+
+done:
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+#if defined(SLAPD_APPROX_INITIALS)
+# define SLAPD_APPROX_DELIMITER "._ "
+# define SLAPD_APPROX_WORDLEN 2
+#else
+# define SLAPD_APPROX_DELIMITER " "
+# define SLAPD_APPROX_WORDLEN 1
+#endif
+
+static int
+approxMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *nval, *assertv;
+ char *val, **values, **words, *c;
+ int i, count, len, nextchunk=0, nextavail=0;
+
+ /* Yes, this is necessary */
+ nval = UTF8bvnormalize( value, NULL, LDAP_UTF8_APPROX, NULL );
+ if( nval == NULL ) {
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* Yes, this is necessary */
+ assertv = UTF8bvnormalize( ((struct berval *)assertedValue),
+ NULL, LDAP_UTF8_APPROX, NULL );
+ if( assertv == NULL ) {
+ ber_bvfree( nval );
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* Isolate how many words there are */
+ for ( c = nval->bv_val, count = 1; *c; c++ ) {
+ c = strpbrk( c, SLAPD_APPROX_DELIMITER );
+ if ( c == NULL ) break;
+ *c = '\0';
+ count++;
+ }
+
+ /* Get a phonetic copy of each word */
+ words = (char **)ch_malloc( count * sizeof(char *) );
+ values = (char **)ch_malloc( count * sizeof(char *) );
+ for ( c = nval->bv_val, i = 0; i < count; i++, c += strlen(c) + 1 ) {
+ words[i] = c;
+ values[i] = phonetic(c);
+ }
+
+ /* Work through the asserted value's words, to see if at least some
+ * of the words are there, in the same order. */
+ len = 0;
+ while ( (ber_len_t) nextchunk < assertv->bv_len ) {
+ len = strcspn( assertv->bv_val + nextchunk, SLAPD_APPROX_DELIMITER);
+ if( len == 0 ) {
+ nextchunk++;
+ continue;
+ }
+#if defined(SLAPD_APPROX_INITIALS)
+ else if( len == 1 ) {
+ /* Single letter words need to at least match one word's initial */
+ for( i=nextavail; i<count; i++ )
+ if( !strncasecmp( assertv->bv_val + nextchunk, words[i], 1 )) {
+ nextavail=i+1;
+ break;
+ }
+ }
+#endif
+ else {
+ /* Isolate the next word in the asserted value and phonetic it */
+ assertv->bv_val[nextchunk+len] = '\0';
+ val = phonetic( assertv->bv_val + nextchunk );
+
+ /* See if this phonetic chunk is in the remaining words of *value */
+ for( i=nextavail; i<count; i++ ){
+ if( !strcmp( val, values[i] ) ){
+ nextavail = i+1;
+ break;
+ }
+ }
+ ch_free( val );
+ }
+
+ /* This chunk in the asserted value was NOT within the *value. */
+ if( i >= count ) {
+ nextavail=-1;
+ break;
+ }
+
+ /* Go on to the next word in the asserted value */
+ nextchunk += len+1;
+ }
+
+ /* If some of the words were seen, call it a match */
+ if( nextavail > 0 ) {
+ *matchp = 0;
+ }
+ else {
+ *matchp = 1;
+ }
+
+ /* Cleanup allocs */
+ ber_bvfree( assertv );
+ for( i=0; i<count; i++ ) {
+ ch_free( values[i] );
+ }
+ ch_free( values );
+ ch_free( words );
+ ber_bvfree( nval );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+approxIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ char *c;
+ int i,j, len, wordcount, keycount=0;
+ struct berval *newkeys;
+ BerVarray keys=NULL;
+
+ for( j = 0; !BER_BVISNULL( &values[j] ); j++ ) {
+ struct berval val = BER_BVNULL;
+ /* Yes, this is necessary */
+ UTF8bvnormalize( &values[j], &val, LDAP_UTF8_APPROX, NULL );
+ assert( !BER_BVISNULL( &val ) );
+
+ /* Isolate how many words there are. There will be a key for each */
+ for( wordcount = 0, c = val.bv_val; *c; c++) {
+ len = strcspn(c, SLAPD_APPROX_DELIMITER);
+ if( len >= SLAPD_APPROX_WORDLEN ) wordcount++;
+ c+= len;
+ if (*c == '\0') break;
+ *c = '\0';
+ }
+
+ /* Allocate/increase storage to account for new keys */
+ newkeys = (struct berval *)ch_malloc( (keycount + wordcount + 1)
+ * sizeof(struct berval) );
+ AC_MEMCPY( newkeys, keys, keycount * sizeof(struct berval) );
+ if( keys ) ch_free( keys );
+ keys = newkeys;
+
+ /* Get a phonetic copy of each word */
+ for( c = val.bv_val, i = 0; i < wordcount; c += len + 1 ) {
+ len = strlen( c );
+ if( len < SLAPD_APPROX_WORDLEN ) continue;
+ ber_str2bv( phonetic( c ), 0, 0, &keys[keycount] );
+ if( keys[keycount].bv_len ) {
+ keycount++;
+ } else {
+ ch_free( keys[keycount].bv_val );
+ }
+ i++;
+ }
+
+ ber_memfree( val.bv_val );
+ }
+ BER_BVZERO( &keys[keycount] );
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+approxFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ char *c;
+ int i, count, len;
+ struct berval *val;
+ BerVarray keys;
+
+ /* Yes, this is necessary */
+ val = UTF8bvnormalize( ((struct berval *)assertedValue),
+ NULL, LDAP_UTF8_APPROX, NULL );
+ if( val == NULL || BER_BVISNULL( val ) ) {
+ keys = (struct berval *)ch_malloc( sizeof(struct berval) );
+ BER_BVZERO( &keys[0] );
+ *keysp = keys;
+ ber_bvfree( val );
+ return LDAP_SUCCESS;
+ }
+
+ /* Isolate how many words there are. There will be a key for each */
+ for( count = 0,c = val->bv_val; *c; c++) {
+ len = strcspn(c, SLAPD_APPROX_DELIMITER);
+ if( len >= SLAPD_APPROX_WORDLEN ) count++;
+ c+= len;
+ if (*c == '\0') break;
+ *c = '\0';
+ }
+
+ /* Allocate storage for new keys */
+ keys = (struct berval *)ch_malloc( (count + 1) * sizeof(struct berval) );
+
+ /* Get a phonetic copy of each word */
+ for( c = val->bv_val, i = 0; i < count; c += len + 1 ) {
+ len = strlen(c);
+ if( len < SLAPD_APPROX_WORDLEN ) continue;
+ ber_str2bv( phonetic( c ), 0, 0, &keys[i] );
+ i++;
+ }
+
+ ber_bvfree( val );
+
+ BER_BVZERO( &keys[count] );
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Remove all spaces and '-' characters, unless the result would be empty */
+static int
+telephoneNumberNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ char *q;
+ ber_len_t c;
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 );
+
+ /* Ensure q is big enough, though validator should have caught this */
+ if ( BER_BVISEMPTY( val )) {
+ BER_BVZERO( normalized );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ q = normalized->bv_val = slap_sl_malloc( val->bv_len + 1, ctx );
+
+ for( c = 0; c < val->bv_len; c++ ) {
+ if ( ! ( ASCII_SPACE( val->bv_val[c] ) || val->bv_val[c] == '-' )) {
+ *q++ = val->bv_val[c];
+ }
+ }
+ if ( q == normalized->bv_val ) {
+ *q++ = ' ';
+ }
+ *q = '\0';
+
+ normalized->bv_len = q - normalized->bv_val;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+postalAddressValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ struct berval bv = *in;
+ ber_len_t c;
+
+ for ( c = 0; c < in->bv_len; c++ ) {
+ if ( in->bv_val[c] == '\\' ) {
+ c++;
+ if ( strncasecmp( &in->bv_val[c], "24", STRLENOF( "24" ) ) != 0
+ && strncasecmp( &in->bv_val[c], "5C", STRLENOF( "5C" ) ) != 0 )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+ continue;
+ }
+
+ if ( in->bv_val[c] == '$' ) {
+ bv.bv_len = &in->bv_val[c] - bv.bv_val;
+ if ( UTF8StringValidate( NULL, &bv ) != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ bv.bv_val = &in->bv_val[c] + 1;
+ }
+ }
+
+ bv.bv_len = &in->bv_val[c] - bv.bv_val;
+ return UTF8StringValidate( NULL, &bv );
+}
+
+static int
+postalAddressNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ BerVarray lines = NULL, nlines = NULL;
+ ber_len_t l, c;
+ int rc = LDAP_SUCCESS;
+ MatchingRule *xmr = NULL;
+ char *p;
+
+ if ( SLAP_MR_ASSOCIATED( mr, slap_schema.si_mr_caseIgnoreListMatch ) ) {
+ xmr = slap_schema.si_mr_caseIgnoreMatch;
+
+ } else {
+ xmr = slap_schema.si_mr_caseExactMatch;
+ }
+
+ for ( l = 0, c = 0; c < val->bv_len; c++ ) {
+ if ( val->bv_val[c] == '$' ) {
+ l++;
+ }
+ }
+
+ lines = slap_sl_calloc( sizeof( struct berval ), 2 * ( l + 2 ), ctx );
+ nlines = &lines[l + 2];
+
+ lines[0].bv_val = val->bv_val;
+ for ( l = 0, c = 0; c < val->bv_len; c++ ) {
+ if ( val->bv_val[c] == '$' ) {
+ lines[l].bv_len = &val->bv_val[c] - lines[l].bv_val;
+ l++;
+ lines[l].bv_val = &val->bv_val[c + 1];
+ }
+ }
+ lines[l].bv_len = &val->bv_val[c] - lines[l].bv_val;
+
+ normalized->bv_len = c = l;
+
+ for ( l = 0; l <= c; l++ ) {
+ /* NOTE: we directly normalize each line,
+ * without unescaping the values, since the special
+ * values '\24' ('$') and '\5C' ('\') are not affected
+ * by normalization */
+ if ( !lines[l].bv_len ) {
+ nlines[l].bv_len = 0;
+ nlines[l].bv_val = NULL;
+ continue;
+ }
+ rc = UTF8StringNormalize( usage, NULL, xmr, &lines[l], &nlines[l], ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ normalized->bv_len += nlines[l].bv_len;
+ }
+
+ normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx );
+
+ p = normalized->bv_val;
+ for ( l = 0; l <= c ; l++ ) {
+ p = lutil_strbvcopy( p, &nlines[l] );
+ *p++ = '$';
+ }
+ *--p = '\0';
+
+ assert( p == &normalized->bv_val[normalized->bv_len] );
+
+done:;
+ if ( nlines != NULL ) {
+ for ( l = 0; !BER_BVISNULL( &nlines[ l ] ); l++ ) {
+ slap_sl_free( nlines[l].bv_val, ctx );
+ }
+
+ slap_sl_free( lines, ctx );
+ }
+
+ return rc;
+}
+
+int
+numericoidValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ struct berval val = *in;
+
+ if( BER_BVISEMPTY( &val ) ) {
+ /* disallow empty strings */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ while( OID_LEADCHAR( val.bv_val[0] ) ) {
+ if ( val.bv_len == 1 ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( val.bv_val[0] == '0' && !OID_SEPARATOR( val.bv_val[1] )) {
+ break;
+ }
+
+ val.bv_val++;
+ val.bv_len--;
+
+ while ( OID_LEADCHAR( val.bv_val[0] )) {
+ val.bv_val++;
+ val.bv_len--;
+
+ if ( val.bv_len == 0 ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ if( !OID_SEPARATOR( val.bv_val[0] )) {
+ break;
+ }
+
+ val.bv_val++;
+ val.bv_len--;
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+integerValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ ber_len_t i;
+ struct berval val = *in;
+
+ if ( BER_BVISEMPTY( &val ) ) return LDAP_INVALID_SYNTAX;
+
+ if ( val.bv_val[0] == '-' ) {
+ val.bv_len--;
+ val.bv_val++;
+
+ if( BER_BVISEMPTY( &val ) ) { /* bare "-" */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( val.bv_val[0] == '0' ) { /* "-0" */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else if ( val.bv_val[0] == '0' ) {
+ if( val.bv_len > 1 ) { /* "0<more>" */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ for( i=0; i < val.bv_len; i++ ) {
+ if( !ASCII_DIGIT(val.bv_val[i]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+integerMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *asserted = (struct berval *) assertedValue;
+ int vsign = 1, asign = 1; /* default sign = '+' */
+ struct berval v, a;
+ int match;
+
+ v = *value;
+ if( v.bv_val[0] == '-' ) {
+ vsign = -1;
+ v.bv_val++;
+ v.bv_len--;
+ }
+
+ if( BER_BVISEMPTY( &v ) ) vsign = 0;
+
+ a = *asserted;
+ if( a.bv_val[0] == '-' ) {
+ asign = -1;
+ a.bv_val++;
+ a.bv_len--;
+ }
+
+ if( BER_BVISEMPTY( &a ) ) vsign = 0;
+
+ match = vsign - asign;
+ if( match == 0 ) {
+ match = ( v.bv_len != a.bv_len
+ ? ( v.bv_len < a.bv_len ? -1 : 1 )
+ : memcmp( v.bv_val, a.bv_val, v.bv_len ));
+ if( vsign < 0 ) match = -match;
+ }
+
+ /* Ordering rule used in extensible match filter? */
+ if ( (flags & SLAP_MR_EXT) && (mr->smr_usage & SLAP_MR_ORDERING) )
+ match = (match >= 0);
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/* 10**Chop < 256**Chopbytes and Chop > Chopbytes<<1 (for sign bit and itmp) */
+#define INDEX_INTLEN_CHOP 7
+#define INDEX_INTLEN_CHOPBYTES 3
+
+static int
+integerVal2Key(
+ struct berval *in,
+ struct berval *key,
+ struct berval *tmp,
+ void *ctx )
+{
+ /* Integer index key format, designed for memcmp to collate correctly:
+ * if too large: one's complement sign*<approx exponent=chopped bytes>,
+ * two's complement value (sign-extended or chopped as needed),
+ * however in first byte above, the top <number of exponent-bytes + 1>
+ * bits are the inverse sign and next bit is the sign as delimiter.
+ */
+ ber_slen_t k = index_intlen_strlen;
+ ber_len_t chop = 0;
+ unsigned signmask = ~0x7fU;
+ unsigned char lenbuf[sizeof(k) + 2], *lenp, neg = 0xff;
+ struct berval val = *in, itmp = *tmp;
+
+ if ( val.bv_val[0] != '-' ) {
+ neg = 0;
+ --k;
+ }
+
+ /* Chop least significant digits, increase length instead */
+ if ( val.bv_len > (ber_len_t) k ) {
+ chop = (val.bv_len-k+2)/INDEX_INTLEN_CHOP; /* 2 fewer digits */
+ val.bv_len -= chop * INDEX_INTLEN_CHOP; /* #digits chopped */
+ chop *= INDEX_INTLEN_CHOPBYTES; /* #bytes added */
+ }
+
+ if ( lutil_str2bin( &val, &itmp, ctx )) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* Omit leading sign byte */
+ if ( itmp.bv_val[0] == neg ) {
+ itmp.bv_val++;
+ itmp.bv_len--;
+ }
+
+ k = (ber_slen_t) index_intlen - (ber_slen_t) (itmp.bv_len + chop);
+ if ( k > 0 ) {
+ assert( chop == 0 );
+ memset( key->bv_val, neg, k ); /* sign-extend */
+ } else if ( k != 0 || ((itmp.bv_val[0] ^ neg) & 0xc0) ) {
+ /* Got exponent -k, or no room for 2 sign bits */
+ lenp = lenbuf + sizeof(lenbuf);
+ chop = - (ber_len_t) k;
+ do {
+ *--lenp = ((unsigned char) chop & 0xff) ^ neg;
+ signmask >>= 1;
+ } while ( (chop >>= 8) != 0 || (signmask >> 1) & (*lenp ^ neg) );
+ /* With n bytes in lenbuf, the top n+1 bits of (signmask&0xff)
+ * are 1, and the top n+2 bits of lenp[0] are the sign bit. */
+ k = (lenbuf + sizeof(lenbuf)) - lenp;
+ if ( k > (ber_slen_t) index_intlen )
+ k = index_intlen;
+ memcpy( key->bv_val, lenp, k );
+ itmp.bv_len = index_intlen - k;
+ }
+ memcpy( key->bv_val + k, itmp.bv_val, itmp.bv_len );
+ key->bv_val[0] ^= (unsigned char) signmask & 0xff; /* invert sign */
+ return 0;
+}
+
+/* Index generation function: Ordered index */
+static int
+integerIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ char ibuf[64];
+ struct berval itmp;
+ BerVarray keys;
+ ber_len_t vlen;
+ int i, rc;
+ unsigned maxstrlen = index_intlen_strlen + INDEX_INTLEN_CHOP-1;
+
+ /* count the values and find max needed length */
+ vlen = 0;
+ for( i = 0; !BER_BVISNULL( &values[i] ); i++ ) {
+ if ( vlen < values[i].bv_len )
+ vlen = values[i].bv_len;
+ }
+ if ( vlen > maxstrlen )
+ vlen = maxstrlen;
+
+ /* we should have at least one value at this point */
+ assert( i > 0 );
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx );
+ for ( i = 0; !BER_BVISNULL( &values[i] ); i++ ) {
+ keys[i].bv_len = index_intlen;
+ keys[i].bv_val = slap_sl_malloc( index_intlen, ctx );
+ }
+ keys[i].bv_len = 0;
+ keys[i].bv_val = NULL;
+
+ if ( vlen > sizeof(ibuf) ) {
+ itmp.bv_val = slap_sl_malloc( vlen, ctx );
+ } else {
+ itmp.bv_val = ibuf;
+ }
+ itmp.bv_len = sizeof(ibuf);
+
+ for ( i=0; !BER_BVISNULL( &values[i] ); i++ ) {
+ if ( itmp.bv_val != ibuf ) {
+ itmp.bv_len = values[i].bv_len;
+ if ( itmp.bv_len <= sizeof(ibuf) )
+ itmp.bv_len = sizeof(ibuf);
+ else if ( itmp.bv_len > maxstrlen )
+ itmp.bv_len = maxstrlen;
+ }
+ rc = integerVal2Key( &values[i], &keys[i], &itmp, ctx );
+ if ( rc ) {
+ slap_sl_free( keys, ctx );
+ goto func_leave;
+ }
+ }
+ *keysp = keys;
+func_leave:
+ if ( itmp.bv_val != ibuf ) {
+ slap_sl_free( itmp.bv_val, ctx );
+ }
+ return rc;
+}
+
+/* Index generation function: Ordered index */
+static int
+integerFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ char ibuf[64];
+ struct berval iv;
+ BerVarray keys;
+ struct berval *value;
+ int rc;
+
+ value = (struct berval *) assertedValue;
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx );
+
+ keys[0].bv_len = index_intlen;
+ keys[0].bv_val = slap_sl_malloc( index_intlen, ctx );
+ keys[1].bv_len = 0;
+ keys[1].bv_val = NULL;
+
+ iv.bv_len = value->bv_len < index_intlen_strlen + INDEX_INTLEN_CHOP-1
+ ? value->bv_len : index_intlen_strlen + INDEX_INTLEN_CHOP-1;
+ if ( iv.bv_len > (int) sizeof(ibuf) ) {
+ iv.bv_val = slap_sl_malloc( iv.bv_len, ctx );
+ } else {
+ iv.bv_val = ibuf;
+ iv.bv_len = sizeof(ibuf);
+ }
+
+ rc = integerVal2Key( value, keys, &iv, ctx );
+
+ if ( iv.bv_val != ibuf ) {
+ slap_sl_free( iv.bv_val, ctx );
+ }
+
+ if ( rc == 0 )
+ *keysp = keys;
+ else
+ slap_sl_free( keys, ctx );
+
+ return rc;
+}
+
+static int
+countryStringValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ if( val->bv_len != 2 ) return LDAP_INVALID_SYNTAX;
+
+ if( !SLAP_PRINTABLE(val->bv_val[0]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ if( !SLAP_PRINTABLE(val->bv_val[1]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+printableStringValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ ber_len_t i;
+
+ if( BER_BVISEMPTY( val ) ) return LDAP_INVALID_SYNTAX;
+
+ for(i=0; i < val->bv_len; i++) {
+ if( !SLAP_PRINTABLE(val->bv_val[i]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+printablesStringValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ ber_len_t i, len;
+
+ if( BER_BVISEMPTY( val ) ) return LDAP_INVALID_SYNTAX;
+
+ for(i=0,len=0; i < val->bv_len; i++) {
+ int c = val->bv_val[i];
+
+ if( c == '$' ) {
+ if( len == 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ len = 0;
+
+ } else if ( SLAP_PRINTABLE(c) ) {
+ len++;
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if( len == 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+IA5StringValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ ber_len_t i;
+
+ for(i=0; i < val->bv_len; i++) {
+ if( !LDAP_ASCII(val->bv_val[i]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+IA5StringNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ char *p, *q, *end;
+ int casefold = !SLAP_MR_ASSOCIATED( mr,
+ slap_schema.si_mr_caseExactIA5Match );
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( use ) != 0 );
+
+ p = val->bv_val;
+ end = val->bv_val + val->bv_len;
+
+ /* Ignore initial whitespace */
+ while ( p < end && ASCII_SPACE( *p ) ) p++;
+
+ normalized->bv_len = p < end ? (val->bv_len - ( p - val->bv_val )) : 0;
+ normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx );
+ AC_MEMCPY( normalized->bv_val, p, normalized->bv_len );
+ normalized->bv_val[normalized->bv_len] = '\0';
+
+ p = q = normalized->bv_val;
+
+ while ( *p ) {
+ if ( ASCII_SPACE( *p ) ) {
+ *q++ = *p++;
+
+ /* Ignore the extra whitespace */
+ while ( ASCII_SPACE( *p ) ) {
+ p++;
+ }
+
+ } else if ( casefold ) {
+ /* Most IA5 rules require casefolding */
+ *q++ = TOLOWER(*p); p++;
+
+ } else {
+ *q++ = *p++;
+ }
+ }
+
+ assert( normalized->bv_val <= p );
+ assert( q <= p );
+
+ /*
+ * If the string ended in space, backup the pointer one
+ * position. One is enough because the above loop collapsed
+ * all whitespace to a single space.
+ */
+ if ( q > normalized->bv_val && ASCII_SPACE( q[-1] ) ) --q;
+
+ /* null terminate */
+ *q = '\0';
+
+ normalized->bv_len = q - normalized->bv_val;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+UUIDValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int i;
+ if( in->bv_len != 36 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for( i=0; i<36; i++ ) {
+ switch(i) {
+ case 8:
+ case 13:
+ case 18:
+ case 23:
+ if( in->bv_val[i] != '-' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ break;
+ default:
+ if( !ASCII_HEX( in->bv_val[i]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+UUIDPretty(
+ Syntax *syntax,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ int i;
+ int rc=LDAP_INVALID_SYNTAX;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ if( in->bv_len != 36 ) return LDAP_INVALID_SYNTAX;
+
+ out->bv_len = 36;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ for( i=0; i<36; i++ ) {
+ switch(i) {
+ case 8:
+ case 13:
+ case 18:
+ case 23:
+ if( in->bv_val[i] != '-' ) {
+ goto handle_error;
+ }
+ out->bv_val[i] = '-';
+ break;
+
+ default:
+ if( !ASCII_HEX( in->bv_val[i]) ) {
+ goto handle_error;
+ }
+ out->bv_val[i] = TOLOWER( in->bv_val[i] );
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+ out->bv_val[ out->bv_len ] = '\0';
+
+ if( 0 ) {
+handle_error:
+ slap_sl_free( out->bv_val, ctx );
+ out->bv_val = NULL;
+ }
+
+ return rc;
+}
+
+int
+UUIDNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ unsigned char octet = '\0';
+ int i;
+ int j;
+
+ if ( SLAP_MR_IS_DENORMALIZE( usage ) ) {
+ /* NOTE: must be a normalized UUID */
+ if( val->bv_len != 16 )
+ return LDAP_INVALID_SYNTAX;
+
+ normalized->bv_val = slap_sl_malloc( LDAP_LUTIL_UUIDSTR_BUFSIZE, ctx );
+ normalized->bv_len = lutil_uuidstr_from_normalized( val->bv_val,
+ val->bv_len, normalized->bv_val, LDAP_LUTIL_UUIDSTR_BUFSIZE );
+ if( normalized->bv_len != STRLENOF( "BADBADBA-DBAD-0123-4567-BADBADBADBAD" ) )
+ return LDAP_INVALID_SYNTAX;
+
+ return LDAP_SUCCESS;
+ }
+
+ normalized->bv_len = 16;
+ normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx );
+
+ for( i=0, j=0; i<36; i++ ) {
+ unsigned char nibble;
+ if( val->bv_val[i] == '-' ) {
+ continue;
+
+ } else if( ASCII_DIGIT( val->bv_val[i] ) ) {
+ nibble = val->bv_val[i] - '0';
+
+ } else if( ASCII_HEXLOWER( val->bv_val[i] ) ) {
+ nibble = val->bv_val[i] - ('a'-10);
+
+ } else if( ASCII_HEXUPPER( val->bv_val[i] ) ) {
+ nibble = val->bv_val[i] - ('A'-10);
+
+ } else {
+ slap_sl_free( normalized->bv_val, ctx );
+ BER_BVZERO( normalized );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( j & 1 ) {
+ octet |= nibble;
+ normalized->bv_val[j>>1] = octet;
+ } else {
+ octet = nibble << 4;
+ }
+ j++;
+ }
+
+ normalized->bv_val[normalized->bv_len] = 0;
+ return LDAP_SUCCESS;
+}
+
+
+
+int
+numericStringValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ ber_len_t i;
+
+ if( BER_BVISEMPTY( in ) ) return LDAP_INVALID_SYNTAX;
+
+ for(i=0; i < in->bv_len; i++) {
+ if( !SLAP_NUMERIC(in->bv_val[i]) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+numericStringNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ /* removal all spaces */
+ char *p, *q;
+
+ assert( !BER_BVISEMPTY( val ) );
+
+ normalized->bv_val = slap_sl_malloc( val->bv_len + 1, ctx );
+
+ p = val->bv_val;
+ q = normalized->bv_val;
+
+ while ( *p ) {
+ if ( ASCII_SPACE( *p ) ) {
+ /* Ignore whitespace */
+ p++;
+ } else {
+ *q++ = *p++;
+ }
+ }
+
+ /* we should have copied no more than is in val */
+ assert( (q - normalized->bv_val) <= (p - val->bv_val) );
+
+ /* null terminate */
+ *q = '\0';
+
+ normalized->bv_len = q - normalized->bv_val;
+
+ if( BER_BVISEMPTY( normalized ) ) {
+ normalized->bv_val = slap_sl_realloc( normalized->bv_val, 2, ctx );
+ normalized->bv_val[0] = ' ';
+ normalized->bv_val[1] = '\0';
+ normalized->bv_len = 1;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * Integer conversion macros that will use the largest available
+ * type.
+ */
+#if defined(HAVE_STRTOLL) && defined(HAVE_LONG_LONG)
+# define SLAP_STRTOL(n,e,b) strtoll(n,e,b)
+# define SLAP_LONG long long
+#else
+# define SLAP_STRTOL(n,e,b) strtol(n,e,b)
+# define SLAP_LONG long
+#endif /* HAVE_STRTOLL ... */
+
+static int
+integerBitAndMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ SLAP_LONG lValue, lAssertedValue;
+
+ errno = 0;
+ /* safe to assume integers are NUL terminated? */
+ lValue = SLAP_STRTOL(value->bv_val, NULL, 10);
+ if( errno == ERANGE )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ lAssertedValue = SLAP_STRTOL(((struct berval *)assertedValue)->bv_val,
+ NULL, 10);
+ if( errno == ERANGE )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ *matchp = ((lValue & lAssertedValue) == lAssertedValue) ? 0 : 1;
+ return LDAP_SUCCESS;
+}
+
+static int
+integerBitOrMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ SLAP_LONG lValue, lAssertedValue;
+
+ errno = 0;
+ /* safe to assume integers are NUL terminated? */
+ lValue = SLAP_STRTOL(value->bv_val, NULL, 10);
+ if( errno == ERANGE )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ lAssertedValue = SLAP_STRTOL( ((struct berval *)assertedValue)->bv_val,
+ NULL, 10);
+ if( errno == ERANGE )
+ {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ *matchp = ((lValue & lAssertedValue) != 0) ? 0 : -1;
+ return LDAP_SUCCESS;
+}
+
+static int
+checkNum( struct berval *in, struct berval *out )
+{
+ /* parse serialNumber */
+ ber_len_t neg = 0, extra = 0;
+ char first = '\0';
+
+ out->bv_val = in->bv_val;
+ out->bv_len = 0;
+
+ if ( out->bv_val[0] == '-' ) {
+ neg++;
+ out->bv_len++;
+ }
+
+ if ( strncasecmp( out->bv_val, "0x", STRLENOF("0x") ) == 0 ) {
+ first = out->bv_val[2];
+ extra = 2;
+
+ out->bv_len += STRLENOF("0x");
+ for ( ; out->bv_len < in->bv_len; out->bv_len++ ) {
+ if ( !ASCII_HEX( out->bv_val[out->bv_len] ) ) break;
+ }
+
+ } else if ( out->bv_val[0] == '\'' ) {
+ first = out->bv_val[1];
+ extra = 3;
+
+ out->bv_len += STRLENOF("'");
+
+ for ( ; out->bv_len < in->bv_len; out->bv_len++ ) {
+ if ( !ASCII_HEX( out->bv_val[out->bv_len] ) ) break;
+ }
+ if ( strncmp( &out->bv_val[out->bv_len], "'H", STRLENOF("'H") ) != 0 ) {
+ return -1;
+ }
+ out->bv_len += STRLENOF("'H");
+
+ } else {
+ first = out->bv_val[0];
+ for ( ; out->bv_len < in->bv_len; out->bv_len++ ) {
+ if ( !ASCII_DIGIT( out->bv_val[out->bv_len] ) ) break;
+ }
+ }
+
+ if ( !( out->bv_len > neg ) ) {
+ return -1;
+ }
+
+ if ( ( out->bv_len > extra + 1 + neg ) && ( first == '0' ) ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+serialNumberAndIssuerCheck(
+ struct berval *in,
+ struct berval *sn,
+ struct berval *is,
+ void *ctx )
+{
+ ber_len_t n;
+
+ if( in->bv_len < 3 ) return LDAP_INVALID_SYNTAX;
+
+ if( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) {
+ /* Parse old format */
+ is->bv_val = ber_bvchr( in, '$' );
+ if( BER_BVISNULL( is ) ) return LDAP_INVALID_SYNTAX;
+
+ sn->bv_val = in->bv_val;
+ sn->bv_len = is->bv_val - in->bv_val;
+
+ is->bv_val++;
+ is->bv_len = in->bv_len - (sn->bv_len + 1);
+
+ /* eat leading zeros */
+ for( n=0; n < (sn->bv_len-1); n++ ) {
+ if( sn->bv_val[n] != '0' ) break;
+ }
+ sn->bv_val += n;
+ sn->bv_len -= n;
+
+ for( n=0; n < sn->bv_len; n++ ) {
+ if( !ASCII_DIGIT(sn->bv_val[n]) ) return LDAP_INVALID_SYNTAX;
+ }
+
+ } else {
+ /* Parse GSER format */
+ enum {
+ HAVE_NONE = 0x0,
+ HAVE_ISSUER = 0x1,
+ HAVE_SN = 0x2,
+ HAVE_ALL = ( HAVE_ISSUER | HAVE_SN )
+ } have = HAVE_NONE;
+
+ int numdquotes = 0, gotquote;
+ struct berval x = *in;
+ struct berval ni;
+ x.bv_val++;
+ x.bv_len -= 2;
+
+ do {
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* should be at issuer or serialNumber NamedValue */
+ if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) {
+ if ( have & HAVE_ISSUER ) return LDAP_INVALID_SYNTAX;
+
+ /* parse issuer */
+ x.bv_val += STRLENOF("issuer");
+ x.bv_len -= STRLENOF("issuer");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* For backward compatibility, this part is optional */
+ if ( strncasecmp( x.bv_val, "rdnSequence:", STRLENOF("rdnSequence:") ) == 0 ) {
+ x.bv_val += STRLENOF("rdnSequence:");
+ x.bv_len -= STRLENOF("rdnSequence:");
+ }
+
+ if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ is->bv_val = x.bv_val;
+ is->bv_len = 0;
+
+ for ( gotquote=0; is->bv_len < x.bv_len; ) {
+ if ( is->bv_val[is->bv_len] != '"' ) {
+ is->bv_len++;
+ continue;
+ }
+ gotquote = 1;
+ if ( is->bv_val[is->bv_len+1] == '"' ) {
+ /* double dquote */
+ numdquotes++;
+ is->bv_len += 2;
+ continue;
+ }
+ break;
+ }
+ if ( !gotquote ) return LDAP_INVALID_SYNTAX;
+
+ x.bv_val += is->bv_len + 1;
+ x.bv_len -= is->bv_len + 1;
+
+ have |= HAVE_ISSUER;
+
+ } else if ( strncasecmp( x.bv_val, "serialNumber", STRLENOF("serialNumber") ) == 0 )
+ {
+ if ( have & HAVE_SN ) return LDAP_INVALID_SYNTAX;
+
+ /* parse serialNumber */
+ x.bv_val += STRLENOF("serialNumber");
+ x.bv_len -= STRLENOF("serialNumber");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( checkNum( &x, sn ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val += sn->bv_len;
+ x.bv_len -= sn->bv_len;
+
+ have |= HAVE_SN;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( have == HAVE_ALL ) {
+ break;
+ }
+
+ if ( x.bv_val[0] != ',' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val++;
+ x.bv_len--;
+ } while ( 1 );
+
+ /* should have no characters left... */
+ if ( x.bv_len ) return LDAP_INVALID_SYNTAX;
+
+ if ( numdquotes == 0 ) {
+ ber_dupbv_x( &ni, is, ctx );
+
+ } else {
+ ber_len_t src, dst;
+
+ ni.bv_len = is->bv_len - numdquotes;
+ ni.bv_val = slap_sl_malloc( ni.bv_len + 1, ctx );
+ for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) {
+ if ( is->bv_val[src] == '"' ) {
+ src++;
+ }
+ ni.bv_val[dst] = is->bv_val[src];
+ }
+ ni.bv_val[dst] = '\0';
+ }
+
+ *is = ni;
+ }
+
+ return 0;
+}
+
+static int
+serialNumberAndIssuerValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ struct berval sn, i;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerValidate: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerCheck( in, &sn, &i, NULL );
+ if ( rc ) {
+ goto done;
+ }
+
+ /* validate DN -- doesn't handle double dquote */
+ rc = dnValidate( NULL, &i );
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, NULL );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerValidate: <%s> err=%d\n",
+ in->bv_val, rc );
+
+done:;
+ return rc;
+}
+
+static int
+serialNumberAndIssuerPretty(
+ Syntax *syntax,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ int rc;
+ struct berval sn, i, ni = BER_BVNULL;
+ char *p;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ BER_BVZERO( out );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerPretty: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerCheck( in, &sn, &i, ctx );
+ if ( rc ) {
+ goto done;
+ }
+
+ rc = dnPretty( syntax, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /* make room from sn + "$" */
+ out->bv_len = STRLENOF("{ serialNumber , issuer rdnSequence:\"\" }")
+ + sn.bv_len + ni.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ p = out->bv_val;
+ p = lutil_strcopy( p, "{ serialNumber " /*}*/ );
+ p = lutil_strbvcopy( p, &sn );
+ p = lutil_strcopy( p, ", issuer rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerPretty: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+slap_bin2hex(
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+
+{
+ /* Use hex format. '123456789abcdef'H */
+ unsigned char *ptr, zero = '\0';
+ char *sptr;
+ int first;
+ ber_len_t i, len, nlen;
+
+ assert( in != NULL );
+ assert( !BER_BVISNULL( in ) );
+ assert( out != NULL );
+ assert( !BER_BVISNULL( out ) );
+
+ ptr = (unsigned char *)in->bv_val;
+ len = in->bv_len;
+
+ /* Check for minimal encodings */
+ if ( len > 1 ) {
+ if ( ptr[0] & 0x80 ) {
+ if ( ( ptr[0] == 0xff ) && ( ptr[1] & 0x80 ) ) {
+ return -1;
+ }
+
+ } else if ( ptr[0] == 0 ) {
+ if ( !( ptr[1] & 0x80 ) ) {
+ return -1;
+ }
+ len--;
+ ptr++;
+ }
+
+ } else if ( len == 0 ) {
+ /* FIXME: this should not be possible,
+ * since a value of zero would have length 1 */
+ len = 1;
+ ptr = &zero;
+ }
+
+ first = !( ptr[0] & 0xf0U );
+ nlen = len * 2 - first + STRLENOF("''H"); /* quotes, H */
+ if ( nlen >= out->bv_len ) {
+ out->bv_val = slap_sl_malloc( nlen + 1, ctx );
+ }
+ sptr = out->bv_val;
+ *sptr++ = '\'';
+ i = 0;
+ if ( first ) {
+ sprintf( sptr, "%01X", ( ptr[0] & 0x0fU ) );
+ sptr++;
+ i = 1;
+ }
+ for ( ; i < len; i++ ) {
+ sprintf( sptr, "%02X", ptr[i] );
+ sptr += 2;
+ }
+ *sptr++ = '\'';
+ *sptr++ = 'H';
+ *sptr = '\0';
+
+ assert( sptr == &out->bv_val[nlen] );
+
+ out->bv_len = nlen;
+
+ return 0;
+}
+
+#define SLAP_SN_BUFLEN (64)
+
+/*
+ * This routine is called by certificateExactNormalize when
+ * certificateExactNormalize receives a search string instead of
+ * a certificate. This routine checks if the search value is valid
+ * and then returns the normalized value
+ */
+static int
+serialNumberAndIssuerNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ struct berval sn, sn2, sn3, i, ni;
+ char sbuf2[SLAP_SN_BUFLEN];
+ char sbuf3[SLAP_SN_BUFLEN];
+ char *p;
+ int rc;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerNormalize: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerCheck( in, &sn, &i, ctx );
+ if ( rc ) {
+ return rc;
+ }
+
+ rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ if ( rc ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* Convert sn to canonical hex */
+ sn2.bv_val = sbuf2;
+ if ( sn.bv_len > sizeof( sbuf2 ) ) {
+ sn2.bv_val = slap_sl_malloc( sn.bv_len, ctx );
+ }
+ sn2.bv_len = sn.bv_len;
+ sn3.bv_val = sbuf3;
+ sn3.bv_len = sizeof(sbuf3);
+ if ( lutil_str2bin( &sn, &sn2, ctx ) || slap_bin2hex( &sn2, &sn3, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ out->bv_len = STRLENOF( "{ serialNumber , issuer rdnSequence:\"\" }" )
+ + sn3.bv_len + ni.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto func_leave;
+ }
+
+ p = out->bv_val;
+
+ p = lutil_strcopy( p, "{ serialNumber " /*}*/ );
+ p = lutil_strbvcopy( p, &sn3 );
+ p = lutil_strcopy( p, ", issuer rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+func_leave:
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerNormalize: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ if ( sn2.bv_val != sbuf2 ) {
+ slap_sl_free( sn2.bv_val, ctx );
+ }
+
+ if ( sn3.bv_val != sbuf3 ) {
+ slap_sl_free( sn3.bv_val, ctx );
+ }
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return rc;
+}
+
+static int
+certificateExactNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t i;
+ char serialbuf2[SLAP_SN_BUFLEN];
+ struct berval sn, sn2 = BER_BVNULL;
+ struct berval issuer_dn = BER_BVNULL, bvdn;
+ char *p;
+ int rc = LDAP_INVALID_SYNTAX;
+
+ assert( val != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> certificateExactNormalize: <%p, %lu>\n",
+ val->bv_val, val->bv_len );
+
+ if ( BER_BVISEMPTY( val ) ) goto done;
+
+ if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) {
+ return serialNumberAndIssuerNormalize( 0, NULL, NULL, val, normalized, ctx );
+ }
+
+ assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 );
+
+ ber_init2( ber, val, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &len ); /* Signed Sequence */
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ tag = ber_peek_tag( ber, &len ); /* Optional version? */
+ if ( tag == SLAP_X509_OPT_C_VERSION ) {
+ tag = ber_skip_tag( ber, &len );
+ tag = ber_get_int( ber, &i ); /* version */
+ }
+
+ /* NOTE: move the test here from certificateValidate,
+ * so that we can validate certs with serial longer
+ * than sizeof(ber_int_t) */
+ tag = ber_skip_tag( ber, &len ); /* serial */
+ sn.bv_len = len;
+ sn.bv_val = (char *)ber->ber_ptr;
+ sn2.bv_val = serialbuf2;
+ sn2.bv_len = sizeof(serialbuf2);
+ if ( slap_bin2hex( &sn, &sn2, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* SignatureAlg */
+ ber_skip_data( ber, len );
+ tag = ber_peek_tag( ber, &len ); /* IssuerDN */
+ if ( len ) {
+ len = ber_ptrlen( ber );
+ bvdn.bv_val = val->bv_val + len;
+ bvdn.bv_len = val->bv_len - len;
+
+ rc = dnX509normalize( &bvdn, &issuer_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ }
+
+ normalized->bv_len = STRLENOF( "{ serialNumber , issuer rdnSequence:\"\" }" )
+ + sn2.bv_len + issuer_dn.bv_len;
+ normalized->bv_val = ch_malloc( normalized->bv_len + 1 );
+
+ p = normalized->bv_val;
+
+ p = lutil_strcopy( p, "{ serialNumber " /*}*/ );
+ p = lutil_strbvcopy( p, &sn2 );
+ p = lutil_strcopy( p, ", issuer rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &issuer_dn );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ rc = LDAP_SUCCESS;
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "<<< certificateExactNormalize: <%p, %lu> => <%s>\n",
+ val->bv_val, val->bv_len, rc == LDAP_SUCCESS ? normalized->bv_val : "(err)" );
+
+ if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val );
+ if ( sn2.bv_val != serialbuf2 ) ber_memfree_x( sn2.bv_val, ctx );
+
+ return rc;
+}
+
+/* X.509 PKI certificateList stuff */
+static int
+checkTime( struct berval *in, struct berval *out )
+{
+ int rc;
+ ber_len_t i;
+ char buf[STRLENOF("YYYYmmddHHMMSSZ") + 1];
+ struct berval bv;
+
+ assert( in != NULL );
+ assert( !BER_BVISNULL( in ) );
+ assert( !BER_BVISEMPTY( in ) );
+
+ if ( in->bv_len < STRLENOF( "YYmmddHHMMSSZ" ) ) {
+ return -1;
+ }
+
+ if ( out != NULL ) {
+ assert( !BER_BVISNULL( out ) );
+ assert( out->bv_len >= sizeof( buf ) );
+ bv.bv_val = out->bv_val;
+
+ } else {
+ bv.bv_val = buf;
+ }
+
+ for ( i = 0; i < STRLENOF( "YYYYmmddHHMMSS" ); i++ ) {
+ if ( !ASCII_DIGIT( in->bv_val[i] ) ) break;
+ }
+
+ if ( in->bv_val[i] != 'Z' ) {
+ return -1;
+ }
+ i++;
+
+ if ( i != in->bv_len ) {
+ return -1;
+ }
+
+ if ( i == STRLENOF( "YYYYmmddHHMMSSZ" ) ) {
+ lutil_strncopy( bv.bv_val, in->bv_val, i );
+ bv.bv_len = i;
+
+ } else if ( i == STRLENOF( "YYmmddHHMMSSZ" ) ) {
+ char *p = bv.bv_val;
+ if ( in->bv_val[0] < '7' ) {
+ p = lutil_strcopy( p, "20" );
+
+ } else {
+ p = lutil_strcopy( p, "19" );
+ }
+ lutil_strncopy( p, in->bv_val, i );
+ bv.bv_len = 2 + i;
+
+ } else {
+ return -1;
+ }
+
+ rc = generalizedTimeValidate( NULL, &bv );
+ if ( rc == LDAP_SUCCESS && out != NULL ) {
+ if ( out->bv_len > bv.bv_len ) {
+ out->bv_val[ bv.bv_len ] = '\0';
+ }
+ out->bv_len = bv.bv_len;
+ }
+
+ return rc != LDAP_SUCCESS;
+}
+
+static int
+issuerAndThisUpdateCheck(
+ struct berval *in,
+ struct berval *is,
+ struct berval *tu,
+ void *ctx )
+{
+ int numdquotes = 0;
+ struct berval x = *in;
+ struct berval ni = BER_BVNULL;
+ /* Parse GSER format */
+ enum {
+ HAVE_NONE = 0x0,
+ HAVE_ISSUER = 0x1,
+ HAVE_THISUPDATE = 0x2,
+ HAVE_ALL = ( HAVE_ISSUER | HAVE_THISUPDATE )
+ } have = HAVE_NONE;
+
+
+ if ( in->bv_len < STRLENOF( "{issuer \"\",thisUpdate \"YYMMDDhhmmssZ\"}" ) ) return LDAP_INVALID_SYNTAX;
+
+ if ( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val++;
+ x.bv_len -= STRLENOF("{}");
+
+ do {
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* should be at issuer or thisUpdate */
+ if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) {
+ if ( have & HAVE_ISSUER ) return LDAP_INVALID_SYNTAX;
+
+ /* parse issuer */
+ x.bv_val += STRLENOF("issuer");
+ x.bv_len -= STRLENOF("issuer");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* For backward compatibility, this part is optional */
+ if ( strncasecmp( x.bv_val, "rdnSequence:", STRLENOF("rdnSequence:") ) != 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ x.bv_val += STRLENOF("rdnSequence:");
+ x.bv_len -= STRLENOF("rdnSequence:");
+
+ if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ is->bv_val = x.bv_val;
+ is->bv_len = 0;
+
+ for ( ; is->bv_len < x.bv_len; ) {
+ if ( is->bv_val[is->bv_len] != '"' ) {
+ is->bv_len++;
+ continue;
+ }
+ if ( is->bv_val[is->bv_len+1] == '"' ) {
+ /* double dquote */
+ numdquotes++;
+ is->bv_len += 2;
+ continue;
+ }
+ break;
+ }
+ x.bv_val += is->bv_len + 1;
+ x.bv_len -= is->bv_len + 1;
+
+ have |= HAVE_ISSUER;
+
+ } else if ( strncasecmp( x.bv_val, "thisUpdate", STRLENOF("thisUpdate") ) == 0 )
+ {
+ if ( have & HAVE_THISUPDATE ) return LDAP_INVALID_SYNTAX;
+
+ /* parse thisUpdate */
+ x.bv_val += STRLENOF("thisUpdate");
+ x.bv_len -= STRLENOF("thisUpdate");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( !x.bv_len || x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ tu->bv_val = x.bv_val;
+ tu->bv_len = 0;
+
+ for ( ; tu->bv_len < x.bv_len; tu->bv_len++ ) {
+ if ( tu->bv_val[tu->bv_len] == '"' ) {
+ break;
+ }
+ }
+ if ( tu->bv_len < STRLENOF("YYYYmmddHHmmssZ") ) return LDAP_INVALID_SYNTAX;
+
+ x.bv_val += tu->bv_len + 1;
+ x.bv_len -= tu->bv_len + 1;
+
+ have |= HAVE_THISUPDATE;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( have == HAVE_ALL ) {
+ break;
+ }
+
+ if ( x.bv_val[0] != ',' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val++;
+ x.bv_len--;
+ } while ( 1 );
+
+ /* should have no characters left... */
+ if ( x.bv_len ) return LDAP_INVALID_SYNTAX;
+
+ if ( numdquotes == 0 ) {
+ ber_dupbv_x( &ni, is, ctx );
+
+ } else {
+ ber_len_t src, dst;
+
+ ni.bv_len = is->bv_len - numdquotes;
+ ni.bv_val = slap_sl_malloc( ni.bv_len + 1, ctx );
+ for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) {
+ if ( is->bv_val[src] == '"' ) {
+ src++;
+ }
+ ni.bv_val[dst] = is->bv_val[src];
+ }
+ ni.bv_val[dst] = '\0';
+ }
+
+ *is = ni;
+
+ return 0;
+}
+
+static int
+issuerAndThisUpdateValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ struct berval i, tu;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdateValidate: <%s>\n",
+ in->bv_val );
+
+ rc = issuerAndThisUpdateCheck( in, &i, &tu, NULL );
+ if ( rc ) {
+ goto done;
+ }
+
+ /* validate DN -- doesn't handle double dquote */
+ rc = dnValidate( NULL, &i );
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+
+ } else if ( checkTime( &tu, NULL ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, NULL );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdateValidate: <%s> err=%d\n",
+ in->bv_val, rc );
+
+done:;
+ return rc;
+}
+
+static int
+issuerAndThisUpdatePretty(
+ Syntax *syntax,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ int rc;
+ struct berval i, tu, ni = BER_BVNULL;
+ char *p;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ BER_BVZERO( out );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdatePretty: <%s>\n",
+ in->bv_val );
+
+ rc = issuerAndThisUpdateCheck( in, &i, &tu, ctx );
+ if ( rc ) {
+ goto done;
+ }
+
+ rc = dnPretty( syntax, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ if ( rc || checkTime( &tu, NULL ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /* make room */
+ out->bv_len = STRLENOF("{ issuer rdnSequence:\"\", thisUpdate \"\" }")
+ + ni.bv_len + tu.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ p = out->bv_val;
+ p = lutil_strcopy( p, "{ issuer rdnSequence:\"" /*}*/ );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, "\", thisUpdate \"" );
+ p = lutil_strbvcopy( p, &tu );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdatePretty: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return rc;
+}
+
+static int
+issuerAndThisUpdateNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ struct berval i, ni, tu, tu2;
+ char sbuf[STRLENOF("YYYYmmddHHMMSSZ") + 1];
+ char *p;
+ int rc;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> issuerAndThisUpdateNormalize: <%s>\n",
+ in->bv_val );
+
+ rc = issuerAndThisUpdateCheck( in, &i, &tu, ctx );
+ if ( rc ) {
+ return rc;
+ }
+
+ rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ tu2.bv_val = sbuf;
+ tu2.bv_len = sizeof( sbuf );
+ if ( rc || checkTime( &tu, &tu2 ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ out->bv_len = STRLENOF( "{ issuer rdnSequence:\"\", thisUpdate \"\" }" )
+ + ni.bv_len + tu2.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto func_leave;
+ }
+
+ p = out->bv_val;
+
+ p = lutil_strcopy( p, "{ issuer rdnSequence:\"" /*}*/ );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, "\", thisUpdate \"" );
+ p = lutil_strbvcopy( p, &tu2 );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+func_leave:
+ Debug( LDAP_DEBUG_TRACE, "<<< issuerAndThisUpdateNormalize: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return rc;
+}
+
+static int
+certificateListExactNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t version;
+ struct berval issuer_dn = BER_BVNULL, bvdn,
+ thisUpdate, bvtu;
+ char *p, tubuf[STRLENOF("YYYYmmddHHMMSSZ") + 1];
+ int rc = LDAP_INVALID_SYNTAX;
+
+ assert( val != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> certificateListExactNormalize: <%p, %lu>\n",
+ val->bv_val, val->bv_len );
+
+ if ( BER_BVISEMPTY( val ) ) goto done;
+
+ if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) {
+ return issuerAndThisUpdateNormalize( 0, NULL, NULL, val, normalized, ctx );
+ }
+
+ assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 );
+
+ ber_init2( ber, val, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &len ); /* Signed wrapper */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ tag = ber_peek_tag( ber, &len );
+ /* Optional version */
+ if ( tag == LBER_INTEGER ) {
+ tag = ber_get_int( ber, &version );
+ assert( tag == LBER_INTEGER );
+ if ( version != SLAP_X509_V2 ) return LDAP_INVALID_SYNTAX;
+ }
+ tag = ber_skip_tag( ber, &len ); /* Signature Algorithm */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ ber_skip_data( ber, len );
+
+ tag = ber_peek_tag( ber, &len ); /* IssuerDN */
+ if ( tag != LBER_SEQUENCE ) return LDAP_INVALID_SYNTAX;
+ len = ber_ptrlen( ber );
+ bvdn.bv_val = val->bv_val + len;
+ bvdn.bv_len = val->bv_len - len;
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+
+ tag = ber_skip_tag( ber, &len ); /* thisUpdate */
+ /* Time is a CHOICE { UTCTime, GeneralizedTime } */
+ if ( tag != SLAP_TAG_UTCTIME && tag != SLAP_TAG_GENERALIZEDTIME ) return LDAP_INVALID_SYNTAX;
+ bvtu.bv_val = (char *)ber->ber_ptr;
+ bvtu.bv_len = len;
+
+ rc = dnX509normalize( &bvdn, &issuer_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ thisUpdate.bv_val = tubuf;
+ thisUpdate.bv_len = sizeof(tubuf);
+ if ( checkTime( &bvtu, &thisUpdate ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ normalized->bv_len = STRLENOF( "{ issuer rdnSequence:\"\", thisUpdate \"\" }" )
+ + issuer_dn.bv_len + thisUpdate.bv_len;
+ normalized->bv_val = ch_malloc( normalized->bv_len + 1 );
+
+ p = normalized->bv_val;
+
+ p = lutil_strcopy( p, "{ issuer rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &issuer_dn );
+ p = lutil_strcopy( p, "\", thisUpdate \"" );
+ p = lutil_strbvcopy( p, &thisUpdate );
+ p = lutil_strcopy( p, /*{*/ "\" }" );
+
+ rc = LDAP_SUCCESS;
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "<<< certificateListExactNormalize: <%p, %lu> => <%s>\n",
+ val->bv_val, val->bv_len, rc == LDAP_SUCCESS ? normalized->bv_val : "(err)" );
+
+ if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val );
+
+ return rc;
+}
+
+/* X.509 PMI serialNumberAndIssuerSerialCheck
+
+AttributeCertificateExactAssertion ::= SEQUENCE {
+ serialNumber CertificateSerialNumber,
+ issuer AttCertIssuer }
+
+CertificateSerialNumber ::= INTEGER
+
+AttCertIssuer ::= [0] SEQUENCE {
+issuerName GeneralNames OPTIONAL,
+baseCertificateID [0] IssuerSerial OPTIONAL,
+objectDigestInfo [1] ObjectDigestInfo OPTIONAL }
+-- At least one component shall be present
+
+GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+
+GeneralName ::= CHOICE {
+ otherName [0] INSTANCE OF OTHER-NAME,
+ rfc822Name [1] IA5String,
+ dNSName [2] IA5String,
+ x400Address [3] ORAddress,
+ directoryName [4] Name,
+ ediPartyName [5] EDIPartyName,
+ uniformResourceIdentifier [6] IA5String,
+ iPAddress [7] OCTET STRING,
+ registeredID [8] OBJECT IDENTIFIER }
+
+IssuerSerial ::= SEQUENCE {
+ issuer GeneralNames,
+ serial CertificateSerialNumber,
+ issuerUID UniqueIdentifier OPTIONAL }
+
+ObjectDigestInfo ::= SEQUENCE {
+ digestedObjectType ENUMERATED {
+ publicKey (0),
+ publicKeyCert (1),
+ otherObjectTypes (2) },
+ otherObjectTypeID OBJECT IDENTIFIER OPTIONAL,
+ digestAlgorithm AlgorithmIdentifier,
+ objectDigest BIT STRING }
+
+ * The way I interpret it, an assertion should look like
+
+ { serialNumber 'dd'H,
+ issuer { issuerName { directoryName:rdnSequence:"cn=yyy" }, -- optional
+ baseCertificateID { serial '1d'H,
+ issuer { directoryName:rdnSequence:"cn=zzz" },
+ issuerUID <value> -- optional
+ }, -- optional
+ objectDigestInfo { ... } -- optional
+ }
+ }
+
+ * with issuerName, baseCertificateID and objectDigestInfo optional,
+ * at least one present; the way it's currently implemented, it is
+
+ { serialNumber 'dd'H,
+ issuer { baseCertificateID { serial '1d'H,
+ issuer { directoryName:rdnSequence:"cn=zzz" }
+ }
+ }
+ }
+
+ * with all the above parts mandatory.
+ */
+static int
+serialNumberAndIssuerSerialCheck(
+ struct berval *in,
+ struct berval *sn,
+ struct berval *is,
+ struct berval *i_sn, /* contain serial of baseCertificateID */
+ void *ctx )
+{
+ /* Parse GSER format */
+ enum {
+ HAVE_NONE = 0x0,
+ HAVE_SN = 0x1,
+ HAVE_ISSUER = 0x2,
+ HAVE_ALL = ( HAVE_SN | HAVE_ISSUER )
+ } have = HAVE_NONE, have2 = HAVE_NONE;
+ int numdquotes = 0;
+ struct berval x = *in;
+ struct berval ni;
+
+ if ( in->bv_len < 3 ) return LDAP_INVALID_SYNTAX;
+
+ /* no old format */
+ if ( in->bv_val[0] != '{' || in->bv_val[in->bv_len-1] != '}' ) return LDAP_INVALID_SYNTAX;
+
+ x.bv_val++;
+ x.bv_len -= 2;
+
+ do {
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* should be at issuer or serialNumber NamedValue */
+ if ( strncasecmp( x.bv_val, "issuer", STRLENOF("issuer") ) == 0 ) {
+ if ( have & HAVE_ISSUER ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* parse IssuerSerial */
+ x.bv_val += STRLENOF("issuer");
+ x.bv_len -= STRLENOF("issuer");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( strncasecmp( x.bv_val, "baseCertificateID ", STRLENOF("baseCertificateID ") ) != 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ x.bv_val += STRLENOF("baseCertificateID ");
+ x.bv_len -= STRLENOF("baseCertificateID ");
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ do {
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ /* parse issuer of baseCertificateID */
+ if ( strncasecmp( x.bv_val, "issuer ", STRLENOF("issuer ") ) == 0 ) {
+ if ( have2 & HAVE_ISSUER ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val += STRLENOF("issuer ");
+ x.bv_len -= STRLENOF("issuer ");
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( x.bv_val[0] != '{' /*}*/ ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( strncasecmp( x.bv_val, "directoryName:rdnSequence:", STRLENOF("directoryName:rdnSequence:") ) != 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ x.bv_val += STRLENOF("directoryName:rdnSequence:");
+ x.bv_len -= STRLENOF("directoryName:rdnSequence:");
+
+ if ( x.bv_val[0] != '"' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ is->bv_val = x.bv_val;
+ is->bv_len = 0;
+
+ for ( ; is->bv_len < x.bv_len; ) {
+ if ( is->bv_val[is->bv_len] != '"' ) {
+ is->bv_len++;
+ continue;
+ }
+ if ( is->bv_val[is->bv_len + 1] == '"' ) {
+ /* double dquote */
+ numdquotes++;
+ is->bv_len += 2;
+ continue;
+ }
+ break;
+ }
+ x.bv_val += is->bv_len + 1;
+ x.bv_len -= is->bv_len + 1;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ have2 |= HAVE_ISSUER;
+
+ } else if ( strncasecmp( x.bv_val, "serial ", STRLENOF("serial ") ) == 0 ) {
+ if ( have2 & HAVE_SN ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val += STRLENOF("serial ");
+ x.bv_len -= STRLENOF("serial ");
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len--) {
+ /* empty */;
+ }
+
+ if ( checkNum( &x, i_sn ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val += i_sn->bv_len;
+ x.bv_len -= i_sn->bv_len;
+
+ have2 |= HAVE_SN;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( have2 == HAVE_ALL ) {
+ break;
+ }
+
+ if ( x.bv_val[0] != ',' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+ } while ( 1 );
+
+ if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( x.bv_val[0] != /*{*/ '}' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ have |= HAVE_ISSUER;
+
+ } else if ( strncasecmp( x.bv_val, "serialNumber", STRLENOF("serialNumber") ) == 0 ) {
+ if ( have & HAVE_SN ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* parse serialNumber */
+ x.bv_val += STRLENOF("serialNumber");
+ x.bv_len -= STRLENOF("serialNumber");
+
+ if ( x.bv_val[0] != ' ' ) return LDAP_INVALID_SYNTAX;
+ x.bv_val++;
+ x.bv_len--;
+
+ /* eat leading spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( checkNum( &x, sn ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ x.bv_val += sn->bv_len;
+ x.bv_len -= sn->bv_len;
+
+ have |= HAVE_SN;
+
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* eat spaces */
+ for ( ; (x.bv_val[0] == ' ') && x.bv_len; x.bv_val++, x.bv_len-- ) {
+ /* empty */;
+ }
+
+ if ( have == HAVE_ALL ) {
+ break;
+ }
+
+ if ( x.bv_val[0] != ',' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ x.bv_val++ ;
+ x.bv_len--;
+ } while ( 1 );
+
+ /* should have no characters left... */
+ if( x.bv_len ) return LDAP_INVALID_SYNTAX;
+
+ if ( numdquotes == 0 ) {
+ ber_dupbv_x( &ni, is, ctx );
+
+ } else {
+ ber_len_t src, dst;
+
+ ni.bv_len = is->bv_len - numdquotes;
+ ni.bv_val = slap_sl_malloc( ni.bv_len + 1, ctx );
+ for ( src = 0, dst = 0; src < is->bv_len; src++, dst++ ) {
+ if ( is->bv_val[src] == '"' ) {
+ src++;
+ }
+ ni.bv_val[dst] = is->bv_val[src];
+ }
+ ni.bv_val[dst] = '\0';
+ }
+
+ *is = ni;
+
+ /* need to handle double dquotes here */
+ return 0;
+}
+
+/* X.509 PMI serialNumberAndIssuerSerialValidate */
+static int
+serialNumberAndIssuerSerialValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int rc;
+ struct berval sn, i, i_sn;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialValidate: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, NULL );
+ if ( rc ) {
+ goto done;
+ }
+
+ /* validate DN -- doesn't handle double dquote */
+ rc = dnValidate( NULL, &i );
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, NULL );
+ }
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialValidate: <%s> err=%d\n",
+ in->bv_val, rc );
+
+ return rc;
+}
+
+/* X.509 PMI serialNumberAndIssuerSerialPretty */
+static int
+serialNumberAndIssuerSerialPretty(
+ Syntax *syntax,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ struct berval sn, i, i_sn, ni = BER_BVNULL;
+ char *p;
+ int rc;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialPretty: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, ctx );
+ if ( rc ) {
+ goto done;
+ }
+
+ rc = dnPretty( syntax, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /* make room from sn + "$" */
+ out->bv_len = STRLENOF("{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }")
+ + sn.bv_len + ni.bv_len + i_sn.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ p = out->bv_val;
+ p = lutil_strcopy( p, "{ serialNumber " );
+ p = lutil_strbvcopy( p, &sn );
+ p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, "\" }, serial " );
+ p = lutil_strbvcopy( p, &i_sn );
+ p = lutil_strcopy( p, " } } }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialPretty: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return rc;
+}
+
+/* X.509 PMI serialNumberAndIssuerSerialNormalize */
+/*
+ * This routine is called by attributeCertificateExactNormalize
+ * when attributeCertificateExactNormalize receives a search
+ * string instead of a attribute certificate. This routine
+ * checks if the search value is valid and then returns the
+ * normalized value
+ */
+static int
+serialNumberAndIssuerSerialNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ struct berval i, ni = BER_BVNULL,
+ sn, sn2 = BER_BVNULL, sn3 = BER_BVNULL,
+ i_sn, i_sn2 = BER_BVNULL, i_sn3 = BER_BVNULL;
+ char sbuf2[SLAP_SN_BUFLEN], i_sbuf2[SLAP_SN_BUFLEN],
+ sbuf3[SLAP_SN_BUFLEN], i_sbuf3[SLAP_SN_BUFLEN];
+ char *p;
+ int rc;
+
+ assert( in != NULL );
+ assert( out != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, ">>> serialNumberAndIssuerSerialNormalize: <%s>\n",
+ in->bv_val );
+
+ rc = serialNumberAndIssuerSerialCheck( in, &sn, &i, &i_sn, ctx );
+ if ( rc ) {
+ goto func_leave;
+ }
+
+ rc = dnNormalize( usage, syntax, mr, &i, &ni, ctx );
+
+ if ( in->bv_val[0] == '{' && in->bv_val[in->bv_len-1] == '}' ) {
+ slap_sl_free( i.bv_val, ctx );
+ }
+
+ if ( rc ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ /* Convert sn to canonical hex */
+ sn2.bv_val = sbuf2;
+ sn2.bv_len = sn.bv_len;
+ if ( sn.bv_len > sizeof( sbuf2 ) ) {
+ sn2.bv_val = slap_sl_malloc( sn.bv_len, ctx );
+ }
+ if ( lutil_str2bin( &sn, &sn2, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ /* Convert i_sn to canonical hex */
+ i_sn2.bv_val = i_sbuf2;
+ i_sn2.bv_len = i_sn.bv_len;
+ if ( i_sn.bv_len > sizeof( i_sbuf2 ) ) {
+ i_sn2.bv_val = slap_sl_malloc( i_sn.bv_len, ctx );
+ }
+ if ( lutil_str2bin( &i_sn, &i_sn2, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ sn3.bv_val = sbuf3;
+ sn3.bv_len = sizeof(sbuf3);
+ if ( slap_bin2hex( &sn2, &sn3, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ i_sn3.bv_val = i_sbuf3;
+ i_sn3.bv_len = sizeof(i_sbuf3);
+ if ( slap_bin2hex( &i_sn2, &i_sn3, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto func_leave;
+ }
+
+ out->bv_len = STRLENOF("{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }")
+ + sn3.bv_len + ni.bv_len + i_sn3.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ if ( out->bv_val == NULL ) {
+ out->bv_len = 0;
+ rc = LDAP_OTHER;
+ goto func_leave;
+ }
+
+ p = out->bv_val;
+
+ p = lutil_strcopy( p, "{ serialNumber " );
+ p = lutil_strbvcopy( p, &sn3 );
+ p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &ni );
+ p = lutil_strcopy( p, "\" }, serial " );
+ p = lutil_strbvcopy( p, &i_sn3 );
+ p = lutil_strcopy( p, " } } }" );
+
+ assert( p == &out->bv_val[out->bv_len] );
+
+func_leave:
+ Debug( LDAP_DEBUG_TRACE, "<<< serialNumberAndIssuerSerialNormalize: <%s> => <%s>\n",
+ in->bv_val, rc == LDAP_SUCCESS ? out->bv_val : "(err)" );
+
+ if ( sn2.bv_val != sbuf2 ) {
+ slap_sl_free( sn2.bv_val, ctx );
+ }
+
+ if ( i_sn2.bv_val != i_sbuf2 ) {
+ slap_sl_free( i_sn2.bv_val, ctx );
+ }
+
+ if ( sn3.bv_val != sbuf3 ) {
+ slap_sl_free( sn3.bv_val, ctx );
+ }
+
+ if ( i_sn3.bv_val != i_sbuf3 ) {
+ slap_sl_free( i_sn3.bv_val, ctx );
+ }
+
+ slap_sl_free( ni.bv_val, ctx );
+
+ return rc;
+}
+
+/* X.509 PMI attributeCertificateExactNormalize */
+static int
+attributeCertificateExactNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ char issuer_serialbuf[SLAP_SN_BUFLEN], serialbuf[SLAP_SN_BUFLEN];
+ struct berval sn, i_sn, sn2 = BER_BVNULL, i_sn2 = BER_BVNULL;
+ struct berval issuer_dn = BER_BVNULL, bvdn;
+ char *p;
+ int rc = LDAP_INVALID_SYNTAX;
+
+ if ( BER_BVISEMPTY( val ) ) {
+ return rc;
+ }
+
+ if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) {
+ return serialNumberAndIssuerSerialNormalize( 0, NULL, NULL, val, normalized, ctx );
+ }
+
+ assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 );
+
+ ber_init2( ber, val, LBER_USE_DER );
+ tag = ber_skip_tag( ber, &len ); /* Signed Sequence */
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ tag = ber_skip_tag( ber, &len ); /* (Mandatory) version; must be v2(1) */
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* Holder Sequence */
+ ber_skip_data( ber, len );
+
+ /* Issuer */
+ tag = ber_skip_tag( ber, &len ); /* Sequence */
+ /* issuerName (GeneralNames sequence; optional)? */
+ tag = ber_skip_tag( ber, &len ); /* baseCertificateID (sequence; optional)? */
+ tag = ber_skip_tag( ber, &len ); /* GeneralNames (sequence) */
+ tag = ber_skip_tag( ber, &len ); /* directoryName (we only accept this form of GeneralName) */
+ if ( tag != SLAP_X509_GN_DIRECTORYNAME ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ tag = ber_peek_tag( ber, &len ); /* sequence of RDN */
+ len = ber_ptrlen( ber );
+ bvdn.bv_val = val->bv_val + len;
+ bvdn.bv_len = val->bv_len - len;
+ rc = dnX509normalize( &bvdn, &issuer_dn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ tag = ber_skip_tag( ber, &len ); /* sequence of RDN */
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* serial number */
+ if ( tag != LBER_INTEGER ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ i_sn.bv_val = (char *)ber->ber_ptr;
+ i_sn.bv_len = len;
+ i_sn2.bv_val = issuer_serialbuf;
+ i_sn2.bv_len = sizeof(issuer_serialbuf);
+ if ( slap_bin2hex( &i_sn, &i_sn2, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ ber_skip_data( ber, len );
+
+ /* issuerUID (bitstring; optional)? */
+ /* objectDigestInfo (sequence; optional)? */
+
+ tag = ber_skip_tag( ber, &len ); /* Signature (sequence) */
+ ber_skip_data( ber, len );
+ tag = ber_skip_tag( ber, &len ); /* serial number */
+ if ( tag != LBER_INTEGER ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ sn.bv_val = (char *)ber->ber_ptr;
+ sn.bv_len = len;
+ sn2.bv_val = serialbuf;
+ sn2.bv_len = sizeof(serialbuf);
+ if ( slap_bin2hex( &sn, &sn2, ctx ) ) {
+ rc = LDAP_INVALID_SYNTAX;
+ goto done;
+ }
+ ber_skip_data( ber, len );
+
+ normalized->bv_len = STRLENOF( "{ serialNumber , issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"\" }, serial } } }" )
+ + sn2.bv_len + issuer_dn.bv_len + i_sn2.bv_len;
+ normalized->bv_val = ch_malloc( normalized->bv_len + 1 );
+
+ p = normalized->bv_val;
+
+ p = lutil_strcopy( p, "{ serialNumber " );
+ p = lutil_strbvcopy( p, &sn2 );
+ p = lutil_strcopy( p, ", issuer { baseCertificateID { issuer { directoryName:rdnSequence:\"" );
+ p = lutil_strbvcopy( p, &issuer_dn );
+ p = lutil_strcopy( p, "\" }, serial " );
+ p = lutil_strbvcopy( p, &i_sn2 );
+ p = lutil_strcopy( p, " } } }" );
+
+ Debug( LDAP_DEBUG_TRACE, "attributeCertificateExactNormalize: %s\n",
+ normalized->bv_val );
+
+ rc = LDAP_SUCCESS;
+
+done:
+ if ( issuer_dn.bv_val ) ber_memfree( issuer_dn.bv_val );
+ if ( i_sn2.bv_val != issuer_serialbuf ) ber_memfree_x( i_sn2.bv_val, ctx );
+ if ( sn2.bv_val != serialbuf ) ber_memfree_x( sn2.bv_val, ctx );
+
+ return rc;
+}
+
+
+static int
+hexValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ ber_len_t i;
+
+ assert( in != NULL );
+ assert( !BER_BVISNULL( in ) );
+
+ for ( i = 0; i < in->bv_len; i++ ) {
+ if ( !ASCII_HEX( in->bv_val[ i ] ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Normalize a SID as used inside a CSN:
+ * three-digit numeric string */
+static int
+hexNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ ber_len_t i;
+
+ assert( val != NULL );
+ assert( normalized != NULL );
+
+ ber_dupbv_x( normalized, val, ctx );
+
+ for ( i = 0; i < normalized->bv_len; i++ ) {
+ if ( !ASCII_HEX( normalized->bv_val[ i ] ) ) {
+ ber_memfree_x( normalized->bv_val, ctx );
+ BER_BVZERO( normalized );
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ normalized->bv_val[ i ] = TOLOWER( normalized->bv_val[ i ] );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+sidValidate (
+ Syntax *syntax,
+ struct berval *in )
+{
+ assert( in != NULL );
+ assert( !BER_BVISNULL( in ) );
+
+ if ( in->bv_len != 3 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return hexValidate( NULL, in );
+}
+
+/* Normalize a SID as used inside a CSN:
+ * three-digit numeric string */
+static int
+sidNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ if ( val->bv_len != 3 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return hexNormalize( 0, NULL, NULL, val, normalized, ctx );
+}
+
+static int
+sidPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ return sidNormalize( SLAP_MR_VALUE_OF_SYNTAX, NULL, NULL, val, out, ctx );
+}
+
+/* Normalize a SID as used inside a CSN, either as-is
+ * (assertion value) or extracted from the CSN
+ * (attribute value) */
+static int
+csnSidNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval bv;
+ char *ptr,
+ buf[ 4 ];
+
+
+ if ( BER_BVISEMPTY( val ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX(usage) ) {
+ return sidNormalize( 0, NULL, NULL, val, normalized, ctx );
+ }
+
+ assert( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) != 0 );
+
+ ptr = ber_bvchr( val, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_val = ptr + 1;
+ bv.bv_len = val->bv_len - ( ptr + 1 - val->bv_val );
+
+ ptr = ber_bvchr( &bv, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_val = ptr + 1;
+ bv.bv_len = val->bv_len - ( ptr + 1 - val->bv_val );
+
+ ptr = ber_bvchr( &bv, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = ptr - bv.bv_val;
+
+ if ( bv.bv_len == 2 ) {
+ /* OpenLDAP 2.3 SID */
+ buf[ 0 ] = '0';
+ buf[ 1 ] = bv.bv_val[ 0 ];
+ buf[ 2 ] = bv.bv_val[ 1 ];
+ buf[ 3 ] = '\0';
+
+ bv.bv_val = buf;
+ bv.bv_len = 3;
+ }
+
+ return sidNormalize( 0, NULL, NULL, &bv, normalized, ctx );
+}
+
+static int
+csnValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ struct berval bv;
+ char *ptr;
+ int rc;
+
+ assert( in != NULL );
+
+ if ( BER_BVISNULL( in ) || BER_BVISEMPTY( in ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv = *in;
+
+ ptr = ber_bvchr( &bv, '#' );
+ if ( ptr == NULL || ptr == &bv.bv_val[bv.bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = ptr - bv.bv_val;
+ if ( bv.bv_len != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ" ) &&
+ bv.bv_len != STRLENOF( "YYYYmmddHHMMSSZ" ) )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = generalizedTimeValidate( NULL, &bv );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ bv.bv_val = ptr + 1;
+ bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
+
+ ptr = ber_bvchr( &bv, '#' );
+ if ( ptr == NULL || ptr == &in->bv_val[in->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = ptr - bv.bv_val;
+ if ( bv.bv_len != 6 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ rc = hexValidate( NULL, &bv );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ bv.bv_val = ptr + 1;
+ bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
+
+ ptr = ber_bvchr( &bv, '#' );
+ if ( ptr == NULL || ptr == &in->bv_val[in->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = ptr - bv.bv_val;
+ if ( bv.bv_len == 2 ) {
+ /* tolerate old 2-digit replica-id */
+ rc = hexValidate( NULL, &bv );
+
+ } else {
+ rc = sidValidate( NULL, &bv );
+ }
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ bv.bv_val = ptr + 1;
+ bv.bv_len = in->bv_len - ( bv.bv_val - in->bv_val );
+
+ if ( bv.bv_len != 6 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return hexValidate( NULL, &bv );
+}
+
+/* Normalize a CSN in OpenLDAP 2.1 format */
+static int
+csnNormalize21(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval gt, cnt, sid, mod;
+ struct berval bv;
+ char buf[ STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) + 1 ];
+ char *ptr;
+ ber_len_t i;
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 );
+ assert( !BER_BVISEMPTY( val ) );
+
+ gt = *val;
+
+ ptr = ber_bvchr( &gt, '#' );
+ if ( ptr == NULL || ptr == &gt.bv_val[gt.bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ gt.bv_len = ptr - gt.bv_val;
+ if ( gt.bv_len != STRLENOF( "YYYYmmddHH:MM:SSZ" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( gt.bv_val[ 10 ] != ':' || gt.bv_val[ 13 ] != ':' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_val = ptr + 1;
+ cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &cnt, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_len = ptr - cnt.bv_val;
+ if ( cnt.bv_len != STRLENOF( "0x0000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( strncmp( cnt.bv_val, "0x", STRLENOF( "0x" ) ) != 0 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_val += STRLENOF( "0x" );
+ cnt.bv_len -= STRLENOF( "0x" );
+
+ sid.bv_val = ptr + 1;
+ sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &sid, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ sid.bv_len = ptr - sid.bv_val;
+ if ( sid.bv_len != STRLENOF( "0" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ mod.bv_val = ptr + 1;
+ mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val );
+ if ( mod.bv_len != STRLENOF( "0000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" );
+ bv.bv_val = buf;
+
+ ptr = bv.bv_val;
+ ptr = lutil_strncopy( ptr, gt.bv_val, STRLENOF( "YYYYmmddHH" ) );
+ ptr = lutil_strncopy( ptr, &gt.bv_val[ STRLENOF( "YYYYmmddHH:" ) ],
+ STRLENOF( "MM" ) );
+ ptr = lutil_strncopy( ptr, &gt.bv_val[ STRLENOF( "YYYYmmddHH:MM:" ) ],
+ STRLENOF( "SS" ) );
+ ptr = lutil_strcopy( ptr, ".000000Z#00" );
+ ptr = lutil_strbvcopy( ptr, &cnt );
+ *ptr++ = '#';
+ *ptr++ = '0';
+ *ptr++ = '0';
+ *ptr++ = sid.bv_val[ 0 ];
+ *ptr++ = '#';
+ *ptr++ = '0';
+ *ptr++ = '0';
+ for ( i = 0; i < mod.bv_len; i++ ) {
+ *ptr++ = TOLOWER( mod.bv_val[ i ] );
+ }
+ *ptr = '\0';
+
+ assert( ptr == &bv.bv_val[bv.bv_len] );
+
+ if ( csnValidate( syntax, &bv ) != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ber_dupbv_x( normalized, &bv, ctx );
+
+ return LDAP_SUCCESS;
+}
+
+/* Normalize a CSN in OpenLDAP 2.3 format */
+static int
+csnNormalize23(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval gt, cnt, sid, mod;
+ struct berval bv;
+ char buf[ STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) + 1 ];
+ char *ptr;
+ ber_len_t i;
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 );
+ assert( !BER_BVISEMPTY( val ) );
+
+ gt = *val;
+
+ ptr = ber_bvchr( &gt, '#' );
+ if ( ptr == NULL || ptr == &gt.bv_val[gt.bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ gt.bv_len = ptr - gt.bv_val;
+ if ( gt.bv_len != STRLENOF( "YYYYmmddHHMMSSZ" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_val = ptr + 1;
+ cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &cnt, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_len = ptr - cnt.bv_val;
+ if ( cnt.bv_len != STRLENOF( "000000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ sid.bv_val = ptr + 1;
+ sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &sid, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ sid.bv_len = ptr - sid.bv_val;
+ if ( sid.bv_len != STRLENOF( "00" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ mod.bv_val = ptr + 1;
+ mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val );
+ if ( mod.bv_len != STRLENOF( "000000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_len = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" );
+ bv.bv_val = buf;
+
+ ptr = bv.bv_val;
+ ptr = lutil_strncopy( ptr, gt.bv_val, gt.bv_len - 1 );
+ ptr = lutil_strcopy( ptr, ".000000Z#" );
+ ptr = lutil_strbvcopy( ptr, &cnt );
+ *ptr++ = '#';
+ *ptr++ = '0';
+ for ( i = 0; i < sid.bv_len; i++ ) {
+ *ptr++ = TOLOWER( sid.bv_val[ i ] );
+ }
+ *ptr++ = '#';
+ for ( i = 0; i < mod.bv_len; i++ ) {
+ *ptr++ = TOLOWER( mod.bv_val[ i ] );
+ }
+ *ptr = '\0';
+
+ if ( ptr != &bv.bv_val[bv.bv_len] ||
+ csnValidate( syntax, &bv ) != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ber_dupbv_x( normalized, &bv, ctx );
+
+ return LDAP_SUCCESS;
+}
+
+/* Normalize a CSN */
+static int
+csnNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval cnt, sid, mod;
+ char *ptr;
+ ber_len_t i;
+
+ assert( val != NULL );
+ assert( normalized != NULL );
+
+ assert( SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) != 0 );
+
+ if ( BER_BVISEMPTY( val ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( val->bv_len == STRLENOF( "YYYYmmddHHMMSSZ#SSSSSS#ID#ssssss" ) ) {
+ /* Openldap <= 2.3 */
+
+ return csnNormalize23( usage, syntax, mr, val, normalized, ctx );
+ }
+
+ if ( val->bv_len == STRLENOF( "YYYYmmddHH:MM:SSZ#0xSSSS#I#ssss" ) ) {
+ /* Openldap 2.1 */
+
+ return csnNormalize21( usage, syntax, mr, val, normalized, ctx );
+ }
+
+ if ( val->bv_len != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#SID#ssssss" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ptr = ber_bvchr( val, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( ptr - val->bv_val != STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ cnt.bv_val = ptr + 1;
+ cnt.bv_len = val->bv_len - ( cnt.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &cnt, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( ptr - cnt.bv_val != STRLENOF( "000000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ sid.bv_val = ptr + 1;
+ sid.bv_len = val->bv_len - ( sid.bv_val - val->bv_val );
+
+ ptr = ber_bvchr( &sid, '#' );
+ if ( ptr == NULL || ptr == &val->bv_val[val->bv_len] ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ sid.bv_len = ptr - sid.bv_val;
+ if ( sid.bv_len != STRLENOF( "000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ mod.bv_val = ptr + 1;
+ mod.bv_len = val->bv_len - ( mod.bv_val - val->bv_val );
+
+ if ( mod.bv_len != STRLENOF( "000000" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ ber_dupbv_x( normalized, val, ctx );
+
+ for ( i = STRLENOF( "YYYYmmddHHMMSS.uuuuuuZ#SSSSSS#" );
+ i < normalized->bv_len; i++ )
+ {
+ /* assume it's already validated that's all hex digits */
+ normalized->bv_val[ i ] = TOLOWER( normalized->bv_val[ i ] );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+csnPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ return csnNormalize( SLAP_MR_VALUE_OF_SYNTAX, NULL, NULL, val, out, ctx );
+}
+
+#ifndef SUPPORT_OBSOLETE_UTC_SYNTAX
+/* slight optimization - does not need the start parameter */
+#define check_time_syntax(v, start, p, f) (check_time_syntax)(v, p, f)
+enum { start = 0 };
+#endif
+
+static int
+check_time_syntax (struct berval *val,
+ int start,
+ int *parts,
+ struct berval *fraction)
+{
+ /*
+ * start=0 GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM])
+ * start=1 UTCTime YYmmddHHMM[SS][Z|(+/-)HHMM]
+ * GeneralizedTime supports leap seconds, UTCTime does not.
+ */
+ static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 };
+ static const int mdays[2][12] = {
+ /* non-leap years */
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ /* leap years */
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+ };
+ char *p, *e;
+ int part, c, c1, c2, tzoffset, leapyear = 0;
+
+ p = val->bv_val;
+ e = p + val->bv_len;
+
+#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX
+ parts[0] = 20; /* century - any multiple of 4 from 04 to 96 */
+#endif
+ for (part = start; part < 7 && p < e; part++) {
+ c1 = *p;
+ if (!ASCII_DIGIT(c1)) {
+ break;
+ }
+ p++;
+ if (p == e) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ c = *p++;
+ if (!ASCII_DIGIT(c)) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ c += c1 * 10 - '0' * 11;
+ if ((part | 1) == 3) {
+ --c;
+ if (c < 0) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ if (c >= ceiling[part]) {
+ if (! (c == 60 && part == 6 && start == 0))
+ return LDAP_INVALID_SYNTAX;
+ }
+ parts[part] = c;
+ }
+ if (part < 5 + start) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ for (; part < 9; part++) {
+ parts[part] = 0;
+ }
+
+ /* leapyear check for the Gregorian calendar (year>1581) */
+ if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) {
+ leapyear = 1;
+ }
+
+ if (parts[3] >= mdays[leapyear][parts[2]]) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if (start == 0) {
+ fraction->bv_val = p;
+ fraction->bv_len = 0;
+ if (p < e && (*p == '.' || *p == ',')) {
+ char *end_num;
+ while (++p < e && ASCII_DIGIT(*p)) {
+ /* EMPTY */;
+ }
+ if (p - fraction->bv_val == 1) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ for (end_num = p; end_num[-1] == '0'; --end_num) {
+ /* EMPTY */;
+ }
+ c = end_num - fraction->bv_val;
+ if (c != 1) fraction->bv_len = c;
+ }
+ }
+
+ if (p == e) {
+ /* no time zone */
+ return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS;
+ }
+
+ tzoffset = *p++;
+ switch (tzoffset) {
+ default:
+ return LDAP_INVALID_SYNTAX;
+ case 'Z':
+ /* UTC */
+ break;
+ case '+':
+ case '-':
+ for (part = 7; part < 9 && p < e; part++) {
+ c1 = *p;
+ if (!ASCII_DIGIT(c1)) {
+ break;
+ }
+ p++;
+ if (p == e) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ c2 = *p++;
+ if (!ASCII_DIGIT(c2)) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ parts[part] = c1 * 10 + c2 - '0' * 11;
+ if (parts[part] >= ceiling[part]) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ if (part < 8 + start) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if (tzoffset == '-') {
+ /* negative offset to UTC, ie west of Greenwich */
+ parts[4] += parts[7];
+ parts[5] += parts[8];
+ /* offset is just hhmm, no seconds */
+ for (part = 6; --part >= 0; ) {
+ if (part != 3) {
+ c = ceiling[part];
+ } else {
+ c = mdays[leapyear][parts[2]];
+ }
+ if (parts[part] >= c) {
+ if (part == 0) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ parts[part] -= c;
+ parts[part - 1]++;
+ continue;
+ } else if (part != 5) {
+ break;
+ }
+ }
+ } else {
+ /* positive offset to UTC, ie east of Greenwich */
+ parts[4] -= parts[7];
+ parts[5] -= parts[8];
+ for (part = 6; --part >= 0; ) {
+ if (parts[part] < 0) {
+ if (part == 0) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ if (part != 3) {
+ c = ceiling[part];
+ } else {
+ /* make first arg to % non-negative */
+ c = mdays[leapyear][(parts[2] - 1 + 12) % 12];
+ }
+ parts[part] += c;
+ parts[part - 1]--;
+ continue;
+ } else if (part != 5) {
+ break;
+ }
+ }
+ }
+ }
+
+ return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS;
+}
+
+#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX
+
+#if 0
+static int
+xutcTimeNormalize(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *normalized )
+{
+ int parts[9], rc;
+
+ rc = check_time_syntax(val, 1, parts, NULL);
+ if (rc != LDAP_SUCCESS) {
+ return rc;
+ }
+
+ normalized->bv_val = ch_malloc( 14 );
+ if ( normalized->bv_val == NULL ) {
+ return LBER_ERROR_MEMORY;
+ }
+
+ sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02dZ",
+ parts[1], parts[2] + 1, parts[3] + 1,
+ parts[4], parts[5], parts[6] );
+ normalized->bv_len = 13;
+
+ return LDAP_SUCCESS;
+}
+#endif /* 0 */
+
+static int
+utcTimeValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int parts[9];
+ return check_time_syntax(in, 1, parts, NULL);
+}
+
+#endif /* SUPPORT_OBSOLETE_UTC_SYNTAX */
+
+static int
+generalizedTimeValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ int parts[9];
+ struct berval fraction;
+ return check_time_syntax(in, 0, parts, &fraction);
+}
+
+static int
+generalizedTimeNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ int parts[9], rc;
+ unsigned int len;
+ struct berval fraction;
+
+ rc = check_time_syntax(val, 0, parts, &fraction);
+ if (rc != LDAP_SUCCESS) {
+ return rc;
+ }
+
+ len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len;
+ normalized->bv_val = slap_sl_malloc( len + 1, ctx );
+ if ( BER_BVISNULL( normalized ) ) {
+ return LBER_ERROR_MEMORY;
+ }
+
+ sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d",
+ parts[0], parts[1], parts[2] + 1, parts[3] + 1,
+ parts[4], parts[5], parts[6] );
+ if ( !BER_BVISEMPTY( &fraction ) ) {
+ memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1,
+ fraction.bv_val, fraction.bv_len );
+ normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.';
+ }
+ strcpy( normalized->bv_val + len-1, "Z" );
+ normalized->bv_len = len;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+generalizedTimeOrderingMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *asserted = (struct berval *) assertedValue;
+ ber_len_t v_len = value->bv_len;
+ ber_len_t av_len = asserted->bv_len;
+
+ /* ignore trailing 'Z' when comparing */
+ int match = memcmp( value->bv_val, asserted->bv_val,
+ (v_len < av_len ? v_len : av_len) - 1 );
+ if ( match == 0 ) match = v_len - av_len;
+
+ /* If used in extensible match filter, match if value < asserted */
+ if ( flags & SLAP_MR_EXT )
+ match = (match >= 0);
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int generalizedTimeIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ int i, j;
+ BerVarray keys;
+ char tmp[5];
+ BerValue bvtmp; /* 40 bit index */
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+
+ bvtmp.bv_len = sizeof(tmp);
+ bvtmp.bv_val = tmp;
+ for( i=0; values[i].bv_val != NULL; i++ ) {
+ /* just count them */
+ }
+
+ /* we should have at least one value at this point */
+ assert( i > 0 );
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * (i+1), ctx );
+
+ /* GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) */
+ for( i=0, j=0; values[i].bv_val != NULL; i++ ) {
+ assert(values[i].bv_val != NULL && values[i].bv_len >= 10);
+ /* Use 40 bits of time for key */
+ if ( lutil_parsetime( values[i].bv_val, &tm ) == 0 ) {
+ lutil_tm2gtime( &tm, &tt );
+ tmp[0] = tt.tt_gsec & 0xff;
+ tmp[4] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[3] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[2] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[1] = tt.tt_sec & 0xff;
+
+ ber_dupbv_x(&keys[j++], &bvtmp, ctx );
+ }
+ }
+
+ keys[j].bv_val = NULL;
+ keys[j].bv_len = 0;
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int generalizedTimeFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void * assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ BerVarray keys;
+ char tmp[5];
+ BerValue bvtmp; /* 40 bit index */
+ BerValue *value = (BerValue *) assertedValue;
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+
+ bvtmp.bv_len = sizeof(tmp);
+ bvtmp.bv_val = tmp;
+ /* GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) */
+ /* Use 40 bits of time for key */
+ if ( value->bv_val && value->bv_len >= 10 &&
+ lutil_parsetime( value->bv_val, &tm ) == 0 ) {
+
+ lutil_tm2gtime( &tm, &tt );
+ tmp[0] = tt.tt_gsec & 0xff;
+ tmp[4] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[3] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[2] = tt.tt_sec & 0xff;
+ tt.tt_sec >>= 8;
+ tmp[1] = tt.tt_sec & 0xff;
+
+ keys = slap_sl_malloc( sizeof( struct berval ) * 2, ctx );
+ ber_dupbv_x(keys, &bvtmp, ctx );
+ keys[1].bv_val = NULL;
+ keys[1].bv_len = 0;
+ } else {
+ keys = NULL;
+ }
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+deliveryMethodValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+#undef LENOF
+#define LENOF(s) (sizeof(s)-1)
+ struct berval tmp = *val;
+ /*
+ * DeliveryMethod = pdm *( WSP DOLLAR WSP DeliveryMethod )
+ * pdm = "any" / "mhs" / "physical" / "telex" / "teletex" /
+ * "g3fax" / "g4fax" / "ia5" / "videotex" / "telephone"
+ */
+again:
+ if( tmp.bv_len < 3 ) return LDAP_INVALID_SYNTAX;
+
+ switch( tmp.bv_val[0] ) {
+ case 'a':
+ case 'A':
+ if(( tmp.bv_len >= LENOF("any") ) &&
+ ( strncasecmp(tmp.bv_val, "any", LENOF("any")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("any");
+ tmp.bv_val += LENOF("any");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 'm':
+ case 'M':
+ if(( tmp.bv_len >= LENOF("mhs") ) &&
+ ( strncasecmp(tmp.bv_val, "mhs", LENOF("mhs")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("mhs");
+ tmp.bv_val += LENOF("mhs");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 'p':
+ case 'P':
+ if(( tmp.bv_len >= LENOF("physical") ) &&
+ ( strncasecmp(tmp.bv_val, "physical", LENOF("physical")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("physical");
+ tmp.bv_val += LENOF("physical");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 't':
+ case 'T': /* telex or teletex or telephone */
+ if(( tmp.bv_len >= LENOF("telex") ) &&
+ ( strncasecmp(tmp.bv_val, "telex", LENOF("telex")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("telex");
+ tmp.bv_val += LENOF("telex");
+ break;
+ }
+ if(( tmp.bv_len >= LENOF("teletex") ) &&
+ ( strncasecmp(tmp.bv_val, "teletex", LENOF("teletex")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("teletex");
+ tmp.bv_val += LENOF("teletex");
+ break;
+ }
+ if(( tmp.bv_len >= LENOF("telephone") ) &&
+ ( strncasecmp(tmp.bv_val, "telephone", LENOF("telephone")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("telephone");
+ tmp.bv_val += LENOF("telephone");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 'g':
+ case 'G': /* g3fax or g4fax */
+ if(( tmp.bv_len >= LENOF("g3fax") ) && (
+ ( strncasecmp(tmp.bv_val, "g3fax", LENOF("g3fax")) == 0 ) ||
+ ( strncasecmp(tmp.bv_val, "g4fax", LENOF("g4fax")) == 0 )))
+ {
+ tmp.bv_len -= LENOF("g3fax");
+ tmp.bv_val += LENOF("g3fax");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 'i':
+ case 'I':
+ if(( tmp.bv_len >= LENOF("ia5") ) &&
+ ( strncasecmp(tmp.bv_val, "ia5", LENOF("ia5")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("ia5");
+ tmp.bv_val += LENOF("ia5");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ case 'v':
+ case 'V':
+ if(( tmp.bv_len >= LENOF("videotex") ) &&
+ ( strncasecmp(tmp.bv_val, "videotex", LENOF("videotex")) == 0 ))
+ {
+ tmp.bv_len -= LENOF("videotex");
+ tmp.bv_val += LENOF("videotex");
+ break;
+ }
+ return LDAP_INVALID_SYNTAX;
+
+ default:
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( BER_BVISEMPTY( &tmp ) ) return LDAP_SUCCESS;
+
+ while( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == ' ' ) ) {
+ tmp.bv_len--;
+ tmp.bv_val++;
+ }
+ if( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == '$' ) ) {
+ tmp.bv_len--;
+ tmp.bv_val++;
+ } else {
+ return LDAP_INVALID_SYNTAX;
+ }
+ while( !BER_BVISEMPTY( &tmp ) && ( tmp.bv_val[0] == ' ' ) ) {
+ tmp.bv_len--;
+ tmp.bv_val++;
+ }
+
+ goto again;
+}
+
+static int
+nisNetgroupTripleValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ char *p, *e;
+ int commas = 0;
+
+ if ( BER_BVISEMPTY( val ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ p = (char *)val->bv_val;
+ e = p + val->bv_len;
+
+ if ( *p != '(' /*')'*/ ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( p++; ( p < e ) && ( *p != /*'('*/ ')' ); p++ ) {
+ if ( *p == ',' ) {
+ commas++;
+ if ( commas > 2 ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else if ( !AD_CHAR( *p ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( ( commas != 2 ) || ( *p != /*'('*/ ')' ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ p++;
+
+ if (p != e) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+bootParameterValidate(
+ Syntax *syntax,
+ struct berval *val )
+{
+ char *p, *e;
+
+ if ( BER_BVISEMPTY( val ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ p = (char *)val->bv_val;
+ e = p + val->bv_len;
+
+ /* key */
+ for (; ( p < e ) && ( *p != '=' ); p++ ) {
+ if ( !AD_CHAR( *p ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( *p != '=' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* server */
+ for ( p++; ( p < e ) && ( *p != ':' ); p++ ) {
+ if ( !AD_CHAR( *p ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( *p != ':' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* path */
+ for ( p++; p < e; p++ ) {
+ if ( !SLAP_PRINTABLE( *p ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+firstComponentNormalize(
+ slap_mask_t usage,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ int rc;
+ struct berval comp;
+ ber_len_t len;
+
+ if( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( usage )) {
+ ber_dupbv_x( normalized, val, ctx );
+ return LDAP_SUCCESS;
+ }
+
+ if( val->bv_len < 3 ) return LDAP_INVALID_SYNTAX;
+
+ if( ! ( val->bv_val[0] == '(' /*')'*/
+ && val->bv_val[val->bv_len - 1] == /*'('*/ ')' )
+ && ! ( val->bv_val[0] == '{' /*'}'*/
+ && val->bv_val[val->bv_len - 1] == /*'('*/ '}' ) )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* trim leading white space */
+ for( len=1;
+ len < val->bv_len && ASCII_SPACE(val->bv_val[len]);
+ len++ )
+ {
+ /* empty */
+ }
+
+ /* grab next word */
+ comp.bv_val = &val->bv_val[len];
+ len = val->bv_len - len - STRLENOF(/*"{"*/ "}");
+ for( comp.bv_len = 0;
+ !ASCII_SPACE(comp.bv_val[comp.bv_len]) && comp.bv_len < len;
+ comp.bv_len++ )
+ {
+ /* empty */
+ }
+
+ if( mr == slap_schema.si_mr_objectIdentifierFirstComponentMatch ) {
+ rc = numericoidValidate( NULL, &comp );
+ } else if( mr == slap_schema.si_mr_integerFirstComponentMatch ) {
+ rc = integerValidate( NULL, &comp );
+ } else {
+ rc = LDAP_INVALID_SYNTAX;
+ }
+
+
+ if( rc == LDAP_SUCCESS ) {
+ ber_dupbv_x( normalized, &comp, ctx );
+ }
+
+ return rc;
+}
+
+static char *country_gen_syn[] = {
+ "1.3.6.1.4.1.1466.115.121.1.15", /* Directory String */
+ "1.3.6.1.4.1.1466.115.121.1.26", /* IA5 String */
+ "1.3.6.1.4.1.1466.115.121.1.44", /* Printable String */
+ NULL
+};
+
+#define X_BINARY "X-BINARY-TRANSFER-REQUIRED 'TRUE' "
+#define X_NOT_H_R "X-NOT-HUMAN-READABLE 'TRUE' "
+
+static slap_syntax_defs_rec syntax_defs[] = {
+ {"( 1.3.6.1.4.1.1466.115.121.1.1 DESC 'ACI Item' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.2 DESC 'Access Point' " X_NOT_H_R ")",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.3 DESC 'Attribute Type Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.4 DESC 'Audio' "
+ X_NOT_H_R ")",
+ SLAP_SYNTAX_BLOB, NULL, blobValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.5 DESC 'Binary' "
+ X_NOT_H_R ")",
+ SLAP_SYNTAX_BER, NULL, berValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' )",
+ 0, NULL, bitStringValidate, NULL },
+ {"( 1.3.6.1.4.1.1466.115.121.1.7 DESC 'Boolean' )",
+ 0, NULL, booleanValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER,
+ NULL, certificateValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.9 DESC 'Certificate List' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER,
+ NULL, certificateListValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.10 DESC 'Certificate Pair' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER,
+ NULL, sequenceValidate, NULL},
+ {"( " attributeCertificateSyntaxOID " DESC 'X.509 AttributeCertificate' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER,
+ NULL, attributeCertificateValidate, NULL},
+#if 0 /* need to go __after__ printableString */
+ {"( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' )",
+ 0, "1.3.6.1.4.1.1466.115.121.1.44",
+ countryStringValidate, NULL},
+#endif
+ {"( 1.3.6.1.4.1.1466.115.121.1.12 DESC 'Distinguished Name' )",
+ SLAP_SYNTAX_DN, NULL, dnValidate, dnPretty},
+ {"( 1.2.36.79672281.1.5.0 DESC 'RDN' )",
+ 0, NULL, rdnValidate, rdnPretty},
+#ifdef LDAP_COMP_MATCH
+ {"( 1.2.36.79672281.1.5.3 DESC 'allComponents' )",
+ 0, NULL, allComponentsValidate, NULL},
+ {"( 1.2.36.79672281.1.5.2 DESC 'componentFilterMatch assertion') ",
+ 0, NULL, componentFilterValidate, NULL},
+#endif
+ {"( 1.3.6.1.4.1.1466.115.121.1.13 DESC 'Data Quality' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.14 DESC 'Delivery Method' )",
+ 0, NULL, deliveryMethodValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' )",
+ 0, NULL, UTF8StringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.16 DESC 'DIT Content Rule Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.17 DESC 'DIT Structure Rule Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.19 DESC 'DSA Quality' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.20 DESC 'DSE Type' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.21 DESC 'Enhanced Guide' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.22 DESC 'Facsimile Telephone Number' )",
+ 0, NULL, printablesStringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.23 DESC 'Fax' " X_NOT_H_R ")",
+ SLAP_SYNTAX_BLOB, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )",
+ 0, NULL, generalizedTimeValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.25 DESC 'Guide' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.26 DESC 'IA5 String' )",
+ 0, NULL, IA5StringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.27 DESC 'Integer' )",
+ 0, NULL, integerValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.28 DESC 'JPEG' " X_NOT_H_R ")",
+ SLAP_SYNTAX_BLOB, NULL, blobValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.29 DESC 'Master And Shadow Access Points' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.30 DESC 'Matching Rule Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.31 DESC 'Matching Rule Use Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.32 DESC 'Mail Preference' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.33 DESC 'MHS OR Address' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' )",
+ SLAP_SYNTAX_DN, NULL, nameUIDValidate, nameUIDPretty },
+ {"( 1.3.6.1.4.1.1466.115.121.1.35 DESC 'Name Form Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.36 DESC 'Numeric String' )",
+ 0, NULL, numericStringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.37 DESC 'Object Class Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.38 DESC 'OID' )",
+ 0, NULL, numericoidValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.39 DESC 'Other Mailbox' )",
+ 0, NULL, IA5StringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.40 DESC 'Octet String' )",
+ 0, NULL, blobValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' )",
+ 0, NULL, postalAddressValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.42 DESC 'Protocol Information' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.43 DESC 'Presentation Address' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.44 DESC 'Printable String' )",
+ 0, NULL, printableStringValidate, NULL},
+ /* moved here because now depends on Directory String, IA5 String
+ * and Printable String */
+ {"( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' )",
+ 0, country_gen_syn, countryStringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.45 DESC 'SubtreeSpecification' )",
+#define subtreeSpecificationValidate UTF8StringValidate /* FIXME */
+ 0, NULL, subtreeSpecificationValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.49 DESC 'Supported Algorithm' "
+ X_BINARY X_NOT_H_R ")",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, NULL, berValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.50 DESC 'Telephone Number' )",
+ 0, NULL, printableStringValidate, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.51 DESC 'Teletex Terminal Identifier' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.52 DESC 'Telex Number' )",
+ 0, NULL, printablesStringValidate, NULL},
+#ifdef SUPPORT_OBSOLETE_UTC_SYNTAX
+ {"( 1.3.6.1.4.1.1466.115.121.1.53 DESC 'UTC Time' )",
+ 0, NULL, utcTimeValidate, NULL},
+#endif
+ {"( 1.3.6.1.4.1.1466.115.121.1.54 DESC 'LDAP Syntax Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.55 DESC 'Modify Rights' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.56 DESC 'LDAP Schema Definition' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.57 DESC 'LDAP Schema Description' )",
+ 0, NULL, NULL, NULL},
+ {"( 1.3.6.1.4.1.1466.115.121.1.58 DESC 'Substring Assertion' )",
+ 0, NULL, NULL, NULL},
+
+ /* RFC 2307 NIS Syntaxes */
+ {"( 1.3.6.1.1.1.0.0 DESC 'RFC2307 NIS Netgroup Triple' )",
+ 0, NULL, nisNetgroupTripleValidate, NULL},
+ {"( 1.3.6.1.1.1.0.1 DESC 'RFC2307 Boot Parameter' )",
+ 0, NULL, bootParameterValidate, NULL},
+
+ /* draft-zeilenga-ldap-x509 */
+ {"( 1.3.6.1.1.15.1 DESC 'Certificate Exact Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL,
+ serialNumberAndIssuerValidate,
+ serialNumberAndIssuerPretty},
+ {"( 1.3.6.1.1.15.2 DESC 'Certificate Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+ {"( 1.3.6.1.1.15.3 DESC 'Certificate Pair Exact Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+ {"( 1.3.6.1.1.15.4 DESC 'Certificate Pair Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+ {"( 1.3.6.1.1.15.5 DESC 'Certificate List Exact Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL,
+ issuerAndThisUpdateValidate,
+ issuerAndThisUpdatePretty},
+ {"( 1.3.6.1.1.15.6 DESC 'Certificate List Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+ {"( 1.3.6.1.1.15.7 DESC 'Algorithm Identifier' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+ {"( " attributeCertificateExactAssertionSyntaxOID " DESC 'AttributeCertificate Exact Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL,
+ serialNumberAndIssuerSerialValidate,
+ serialNumberAndIssuerSerialPretty},
+ {"( " attributeCertificateAssertionSyntaxOID " DESC 'AttributeCertificate Assertion' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+
+#ifdef SLAPD_AUTHPASSWD
+ /* needs updating */
+ {"( 1.3.6.1.4.1.4203.666.2.2 DESC 'OpenLDAP authPassword' )",
+ SLAP_SYNTAX_HIDE, NULL, NULL, NULL},
+#endif
+
+ {"( 1.3.6.1.1.16.1 DESC 'UUID' )",
+ 0, NULL, UUIDValidate, UUIDPretty},
+
+ {"( 1.3.6.1.4.1.4203.666.11.2.1 DESC 'CSN' )",
+ SLAP_SYNTAX_HIDE, NULL, csnValidate, csnPretty },
+
+ {"( 1.3.6.1.4.1.4203.666.11.2.4 DESC 'CSN SID' )",
+ SLAP_SYNTAX_HIDE, NULL, sidValidate, sidPretty },
+
+ /* OpenLDAP Void Syntax */
+ {"( 1.3.6.1.4.1.4203.1.1.1 DESC 'OpenLDAP void' )" ,
+ SLAP_SYNTAX_HIDE, NULL, inValidate, NULL},
+
+ /* FIXME: OID is unused, but not registered yet */
+ {"( 1.3.6.1.4.1.4203.666.2.7 DESC 'OpenLDAP authz' )",
+ SLAP_SYNTAX_HIDE, NULL, authzValidate, authzPretty},
+
+ /* PKCS#8 Private Keys for X.509 certificates */
+ {"( 1.2.840.113549.1.8.1.1 DESC 'PKCS#8 PrivateKeyInfo' )",
+ SLAP_SYNTAX_BINARY|SLAP_SYNTAX_BER, NULL, privateKeyValidate, NULL},
+ {NULL, 0, NULL, NULL, NULL}
+};
+
+char *csnSIDMatchSyntaxes[] = {
+ "1.3.6.1.4.1.4203.666.11.2.1" /* csn */,
+ NULL
+};
+char *certificateExactMatchSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.8" /* certificate */,
+ NULL
+};
+char *certificateListExactMatchSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.9" /* certificateList */,
+ NULL
+};
+char *attributeCertificateExactMatchSyntaxes[] = {
+ attributeCertificateSyntaxOID /* attributeCertificate */,
+ NULL
+};
+
+#ifdef LDAP_COMP_MATCH
+char *componentFilterMatchSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.8" /* certificate */,
+ "1.3.6.1.4.1.1466.115.121.1.9" /* certificateList */,
+ attributeCertificateSyntaxOID /* attributeCertificate */,
+ NULL
+};
+#endif
+
+char *directoryStringSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.11" /* countryString */,
+ "1.3.6.1.4.1.1466.115.121.1.44" /* printableString */,
+ "1.3.6.1.4.1.1466.115.121.1.50" /* telephoneNumber */,
+ NULL
+};
+char *integerFirstComponentMatchSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.27" /* INTEGER */,
+ "1.3.6.1.4.1.1466.115.121.1.17" /* dITStructureRuleDescription */,
+ NULL
+};
+char *objectIdentifierFirstComponentMatchSyntaxes[] = {
+ "1.3.6.1.4.1.1466.115.121.1.38" /* OID */,
+ "1.3.6.1.4.1.1466.115.121.1.3" /* attributeTypeDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.16" /* dITContentRuleDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.54" /* ldapSyntaxDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.30" /* matchingRuleDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.31" /* matchingRuleUseDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.35" /* nameFormDescription */,
+ "1.3.6.1.4.1.1466.115.121.1.37" /* objectClassDescription */,
+ NULL
+};
+
+/*
+ * Other matching rules in X.520 that we do not use (yet):
+ *
+ * 2.5.13.25 uTCTimeMatch
+ * 2.5.13.26 uTCTimeOrderingMatch
+ * 2.5.13.31* directoryStringFirstComponentMatch
+ * 2.5.13.32* wordMatch
+ * 2.5.13.33* keywordMatch
+ * 2.5.13.36+ certificatePairExactMatch
+ * 2.5.13.37+ certificatePairMatch
+ * 2.5.13.40+ algorithmIdentifierMatch
+ * 2.5.13.41* storedPrefixMatch
+ * 2.5.13.42 attributeCertificateMatch
+ * 2.5.13.43 readerAndKeyIDMatch
+ * 2.5.13.44 attributeIntegrityMatch
+ *
+ * (*) described in RFC 3698 (LDAP: Additional Matching Rules)
+ * (+) described in draft-zeilenga-ldap-x509
+ */
+static slap_mrule_defs_rec mrule_defs[] = {
+ /*
+ * EQUALITY matching rules must be listed after associated APPROX
+ * matching rules. So, we list all APPROX matching rules first.
+ */
+ {"( " directoryStringApproxMatchOID " NAME 'directoryStringApproxMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY_APPROX | SLAP_MR_EXT, NULL,
+ NULL, NULL, directoryStringApproxMatch,
+ directoryStringApproxIndexer, directoryStringApproxFilter,
+ NULL},
+
+ {"( " IA5StringApproxMatchOID " NAME 'IA5StringApproxMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY_APPROX | SLAP_MR_EXT, NULL,
+ NULL, NULL, IA5StringApproxMatch,
+ IA5StringApproxIndexer, IA5StringApproxFilter,
+ NULL},
+
+ /*
+ * Other matching rules
+ */
+
+ {"( 2.5.13.0 NAME 'objectIdentifierMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.1 NAME 'distinguishedNameMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, dnNormalize, dnMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 1.3.6.1.4.1.4203.666.4.9 NAME 'dnSubtreeMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ SLAP_MR_HIDE | SLAP_MR_EXT, NULL,
+ NULL, dnNormalize, dnRelativeMatch,
+ NULL, NULL,
+ NULL },
+
+ {"( 1.3.6.1.4.1.4203.666.4.8 NAME 'dnOneLevelMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ SLAP_MR_HIDE | SLAP_MR_EXT, NULL,
+ NULL, dnNormalize, dnRelativeMatch,
+ NULL, NULL,
+ NULL },
+
+ {"( 1.3.6.1.4.1.4203.666.4.10 NAME 'dnSubordinateMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ SLAP_MR_HIDE | SLAP_MR_EXT, NULL,
+ NULL, dnNormalize, dnRelativeMatch,
+ NULL, NULL,
+ NULL },
+
+ {"( 1.3.6.1.4.1.4203.666.4.11 NAME 'dnSuperiorMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ SLAP_MR_HIDE | SLAP_MR_EXT, NULL,
+ NULL, dnNormalize, dnRelativeMatch,
+ NULL, NULL,
+ NULL },
+
+ {"( 1.2.36.79672281.1.13.3 NAME 'rdnMatch' "
+ "SYNTAX 1.2.36.79672281.1.5.0 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, rdnNormalize, rdnMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+#ifdef LDAP_COMP_MATCH
+ {"( 1.2.36.79672281.1.13.2 NAME 'componentFilterMatch' "
+ "SYNTAX 1.2.36.79672281.1.5.2 )", /* componentFilterMatch assertion */
+ SLAP_MR_EXT|SLAP_MR_COMPONENT, componentFilterMatchSyntaxes,
+ NULL, NULL , componentFilterMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 1.2.36.79672281.1.13.6 NAME 'allComponentsMatch' "
+ "SYNTAX 1.2.36.79672281.1.5.3 )", /* allComponents */
+ SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_COMPONENT, NULL,
+ NULL, NULL , allComponentsMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 1.2.36.79672281.1.13.7 NAME 'directoryComponentsMatch' "
+ "SYNTAX 1.2.36.79672281.1.5.3 )", /* allComponents */
+ SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_COMPONENT, NULL,
+ NULL, NULL , directoryComponentsMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+#endif
+
+ {"( 2.5.13.2 NAME 'caseIgnoreMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ directoryStringApproxMatchOID },
+
+ {"( 2.5.13.3 NAME 'caseIgnoreOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, octetStringOrderingMatch,
+ NULL, NULL,
+ "caseIgnoreMatch" },
+
+ {"( 2.5.13.4 NAME 'caseIgnoreSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */
+ SLAP_MR_SUBSTR, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, directoryStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "caseIgnoreMatch" },
+
+ {"( 2.5.13.5 NAME 'caseExactMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ directoryStringApproxMatchOID },
+
+ {"( 2.5.13.6 NAME 'caseExactOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, octetStringOrderingMatch,
+ NULL, NULL,
+ "caseExactMatch" },
+
+ {"( 2.5.13.7 NAME 'caseExactSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */
+ SLAP_MR_SUBSTR, directoryStringSyntaxes,
+ NULL, UTF8StringNormalize, directoryStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "caseExactMatch" },
+
+ {"( 2.5.13.8 NAME 'numericStringMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, numericStringNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.9 NAME 'numericStringOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT, NULL,
+ NULL, numericStringNormalize, octetStringOrderingMatch,
+ NULL, NULL,
+ "numericStringMatch" },
+
+ {"( 2.5.13.10 NAME 'numericStringSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */
+ SLAP_MR_SUBSTR, NULL,
+ NULL, numericStringNormalize, octetStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "numericStringMatch" },
+
+ {"( 2.5.13.11 NAME 'caseIgnoreListMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )", /* Postal Address */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, postalAddressNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.12 NAME 'caseIgnoreListSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */
+ SLAP_MR_SUBSTR, NULL,
+ NULL, postalAddressNormalize, directoryStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "caseIgnoreListMatch" },
+
+ {"( 2.5.13.13 NAME 'booleanMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, booleanMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.14 NAME 'integerMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, NULL, integerMatch,
+ integerIndexer, integerFilter,
+ NULL },
+
+ {"( 2.5.13.15 NAME 'integerOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, NULL, integerMatch,
+ NULL, NULL,
+ "integerMatch" },
+
+ {"( 2.5.13.16 NAME 'bitStringMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.17 NAME 'octetStringMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.18 NAME 'octetStringOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT, NULL,
+ NULL, NULL, octetStringOrderingMatch,
+ NULL, NULL,
+ "octetStringMatch" },
+
+ {"( 2.5.13.19 NAME 'octetStringSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
+ SLAP_MR_SUBSTR, NULL,
+ NULL, NULL, octetStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "octetStringMatch" },
+
+ {"( 2.5.13.20 NAME 'telephoneNumberMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL,
+ telephoneNumberNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.21 NAME 'telephoneNumberSubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )", /* Substring Assertion */
+ SLAP_MR_SUBSTR, NULL,
+ NULL, telephoneNumberNormalize, octetStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "telephoneNumberMatch" },
+
+ {"( 2.5.13.22 NAME 'presentationAddressMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.43 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL },
+
+ {"( 2.5.13.23 NAME 'uniqueMemberMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )", /* Name And Optional UID */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, uniqueMemberNormalize, uniqueMemberMatch,
+ uniqueMemberIndexer, uniqueMemberFilter,
+ NULL },
+
+ {"( 2.5.13.24 NAME 'protocolInformationMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.42 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL },
+
+ {"( 2.5.13.27 NAME 'generalizedTimeMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, generalizedTimeNormalize, octetStringMatch,
+ generalizedTimeIndexer, generalizedTimeFilter,
+ NULL },
+
+ {"( 2.5.13.28 NAME 'generalizedTimeOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )",
+ SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, generalizedTimeNormalize, generalizedTimeOrderingMatch,
+ NULL, NULL,
+ "generalizedTimeMatch" },
+
+ {"( 2.5.13.29 NAME 'integerFirstComponentMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT,
+ integerFirstComponentMatchSyntaxes,
+ NULL, firstComponentNormalize, integerMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.30 NAME 'objectIdentifierFirstComponentMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", /* OID */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT,
+ objectIdentifierFirstComponentMatchSyntaxes,
+ NULL, firstComponentNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.34 NAME 'certificateExactMatch' "
+ "SYNTAX 1.3.6.1.1.15.1 )", /* Certificate Exact Assertion */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, certificateExactMatchSyntaxes,
+ NULL, certificateExactNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.35 NAME 'certificateMatch' "
+ "SYNTAX 1.3.6.1.1.15.2 )", /* Certificate Assertion */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL },
+
+ {"( 2.5.13.38 NAME 'certificateListExactMatch' "
+ "SYNTAX 1.3.6.1.1.15.5 )", /* Certificate List Exact Assertion */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, certificateListExactMatchSyntaxes,
+ NULL, certificateListExactNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.39 NAME 'certificateListMatch' "
+ "SYNTAX 1.3.6.1.1.15.6 )", /* Certificate List Assertion */
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL },
+
+ {"( 2.5.13.45 NAME 'attributeCertificateExactMatch' "
+ "SYNTAX " attributeCertificateExactAssertionSyntaxOID " )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_HIDE, attributeCertificateExactMatchSyntaxes,
+ NULL, attributeCertificateExactNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ {"( 2.5.13.46 NAME 'attributeCertificateMatch' "
+ "SYNTAX " attributeCertificateAssertionSyntaxOID " )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT | SLAP_MR_HIDE, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL },
+
+ {"( 1.3.6.1.4.1.1466.109.114.1 NAME 'caseExactIA5Match' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, IA5StringNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ IA5StringApproxMatchOID },
+
+ {"( 1.3.6.1.4.1.1466.109.114.2 NAME 'caseIgnoreIA5Match' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
+ SLAP_MR_EQUALITY | SLAP_MR_EXT, NULL,
+ NULL, IA5StringNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ IA5StringApproxMatchOID },
+
+ {"( 1.3.6.1.4.1.1466.109.114.3 NAME 'caseIgnoreIA5SubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
+ SLAP_MR_SUBSTR, NULL,
+ NULL, IA5StringNormalize, directoryStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "caseIgnoreIA5Match" },
+
+ {"( 1.3.6.1.4.1.4203.1.2.1 NAME 'caseExactIA5SubstringsMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
+ SLAP_MR_SUBSTR, NULL,
+ NULL, IA5StringNormalize, directoryStringSubstringsMatch,
+ octetStringSubstringsIndexer, octetStringSubstringsFilter,
+ "caseExactIA5Match" },
+
+#ifdef SLAPD_AUTHPASSWD
+ /* needs updating */
+ {"( 1.3.6.1.4.1.4203.666.4.1 NAME 'authPasswordMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", /* Octet String */
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
+ NULL, NULL, authPasswordMatch,
+ NULL, NULL,
+ NULL},
+#endif
+
+ {"( 1.2.840.113556.1.4.803 NAME 'integerBitAndMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */
+ SLAP_MR_EXT, NULL,
+ NULL, NULL, integerBitAndMatch,
+ NULL, NULL,
+ "integerMatch" },
+
+ {"( 1.2.840.113556.1.4.804 NAME 'integerBitOrMatch' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", /* Integer */
+ SLAP_MR_EXT, NULL,
+ NULL, NULL, integerBitOrMatch,
+ NULL, NULL,
+ "integerMatch" },
+
+ {"( 1.3.6.1.1.16.2 NAME 'UUIDMatch' "
+ "SYNTAX 1.3.6.1.1.16.1 )",
+ SLAP_MR_EQUALITY | SLAP_MR_MUTATION_NORMALIZER, NULL,
+ NULL, UUIDNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL},
+
+ {"( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' "
+ "SYNTAX 1.3.6.1.1.16.1 )",
+ SLAP_MR_ORDERING | SLAP_MR_MUTATION_NORMALIZER, NULL,
+ NULL, UUIDNormalize, octetStringOrderingMatch,
+ octetStringIndexer, octetStringFilter,
+ "UUIDMatch"},
+
+ {"( 1.3.6.1.4.1.4203.666.11.2.2 NAME 'CSNMatch' "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1 )",
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, csnNormalize, csnMatch,
+ csnIndexer, csnFilter,
+ NULL},
+
+ {"( 1.3.6.1.4.1.4203.666.11.2.3 NAME 'CSNOrderingMatch' "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1 )",
+ SLAP_MR_HIDE | SLAP_MR_ORDERING | SLAP_MR_EXT | SLAP_MR_ORDERED_INDEX, NULL,
+ NULL, csnNormalize, csnOrderingMatch,
+ NULL, NULL,
+ "CSNMatch" },
+
+ {"( 1.3.6.1.4.1.4203.666.11.2.5 NAME 'CSNSIDMatch' "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.4 )",
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY | SLAP_MR_EXT, csnSIDMatchSyntaxes,
+ NULL, csnSidNormalize, octetStringMatch,
+ octetStringIndexer, octetStringFilter,
+ NULL },
+
+ /* FIXME: OID is unused, but not registered yet */
+ {"( 1.3.6.1.4.1.4203.666.4.12 NAME 'authzMatch' "
+ "SYNTAX 1.3.6.1.4.1.4203.666.2.7 )", /* OpenLDAP authz */
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
+ NULL, authzNormalize, authzMatch,
+ NULL, NULL,
+ NULL},
+
+ {"( 1.3.6.1.4.1.4203.666.4.13 NAME 'privateKeyMatch' "
+ "SYNTAX 1.2.840.113549.1.8.1.1 )", /* PKCS#8 privateKey */
+ SLAP_MR_HIDE | SLAP_MR_EQUALITY, NULL,
+ NULL, NULL, octetStringMatch,
+ NULL, NULL,
+ NULL},
+
+ {NULL, SLAP_MR_NONE, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL }
+};
+
+int
+slap_schema_init( void )
+{
+ int res;
+ int i;
+
+ /* we should only be called once (from main) */
+ assert( schema_init_done == 0 );
+
+ for ( i=0; syntax_defs[i].sd_desc != NULL; i++ ) {
+ res = register_syntax( &syntax_defs[i] );
+
+ if ( res ) {
+ fprintf( stderr, "slap_schema_init: Error registering syntax %s\n",
+ syntax_defs[i].sd_desc );
+ return LDAP_OTHER;
+ }
+ }
+
+ for ( i=0; mrule_defs[i].mrd_desc != NULL; i++ ) {
+ if( mrule_defs[i].mrd_usage == SLAP_MR_NONE &&
+ mrule_defs[i].mrd_compat_syntaxes == NULL )
+ {
+ fprintf( stderr,
+ "slap_schema_init: Ignoring unusable matching rule %s\n",
+ mrule_defs[i].mrd_desc );
+ continue;
+ }
+
+ res = register_matching_rule( &mrule_defs[i] );
+
+ if ( res ) {
+ fprintf( stderr,
+ "slap_schema_init: Error registering matching rule %s\n",
+ mrule_defs[i].mrd_desc );
+ return LDAP_OTHER;
+ }
+ }
+
+ res = slap_schema_load();
+ schema_init_done = 1;
+ return res;
+}
+
+void
+schema_destroy( void )
+{
+ oidm_destroy();
+ oc_destroy();
+ at_destroy();
+ mr_destroy();
+ mru_destroy();
+ syn_destroy();
+
+ if( schema_init_done ) {
+ ldap_pvt_thread_mutex_destroy( &ad_index_mutex );
+ ldap_pvt_thread_mutex_destroy( &ad_undef_mutex );
+ ldap_pvt_thread_mutex_destroy( &oc_undef_mutex );
+ }
+}
diff --git a/servers/slapd/schema_prep.c b/servers/slapd/schema_prep.c
new file mode 100644
index 0000000..b8793f5
--- /dev/null
+++ b/servers/slapd/schema_prep.c
@@ -0,0 +1,1638 @@
+/* schema_prep.c - load builtin schema */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+#define OCDEBUG 0
+
+int schema_init_done = 0;
+
+struct slap_internal_schema slap_schema;
+
+static int
+oidValidate(
+ Syntax *syntax,
+ struct berval *in )
+{
+ struct berval val = *in;
+
+ if( val.bv_len == 0 ) {
+ /* disallow empty strings */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( DESC_LEADCHAR( val.bv_val[0] ) ) {
+ val.bv_val++;
+ val.bv_len--;
+ if ( val.bv_len == 0 ) return LDAP_SUCCESS;
+
+ while( DESC_CHAR( val.bv_val[0] ) ) {
+ val.bv_val++;
+ val.bv_len--;
+
+ if ( val.bv_len == 0 ) return LDAP_SUCCESS;
+ }
+
+ } else {
+ int sep = 0;
+ while( OID_LEADCHAR( val.bv_val[0] ) ) {
+ val.bv_val++;
+ val.bv_len--;
+
+ if ( val.bv_val[-1] != '0' ) {
+ while ( OID_LEADCHAR( val.bv_val[0] )) {
+ val.bv_val++;
+ val.bv_len--;
+ }
+ }
+
+ if( val.bv_len == 0 ) {
+ if( sep == 0 ) break;
+ return LDAP_SUCCESS;
+ }
+
+ if( !OID_SEPARATOR( val.bv_val[0] )) break;
+
+ sep++;
+ val.bv_val++;
+ val.bv_len--;
+ }
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+
+static int objectClassPretty(
+ Syntax *syntax,
+ struct berval *in,
+ struct berval *out,
+ void *ctx )
+{
+ ObjectClass *oc;
+
+ if( oidValidate( NULL, in )) return LDAP_INVALID_SYNTAX;
+
+ oc = oc_bvfind( in );
+ if( oc == NULL ) return LDAP_INVALID_SYNTAX;
+
+ ber_dupbv_x( out, &oc->soc_cname, ctx );
+ return LDAP_SUCCESS;
+}
+
+static int
+attributeTypeMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *a = (struct berval *) assertedValue;
+ AttributeType *at = at_bvfind( value );
+ AttributeType *asserted = at_bvfind( a );
+
+ if( asserted == NULL ) {
+ if( OID_LEADCHAR( *a->bv_val ) ) {
+ /* OID form, return FALSE */
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* desc form, return undefined */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( at == NULL ) {
+ /* unrecognized stored value */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ *matchp = ( asserted != at );
+ return LDAP_SUCCESS;
+}
+
+static int
+matchingRuleMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *a = (struct berval *) assertedValue;
+ MatchingRule *mrv = mr_bvfind( value );
+ MatchingRule *asserted = mr_bvfind( a );
+
+ if( asserted == NULL ) {
+ if( OID_LEADCHAR( *a->bv_val ) ) {
+ /* OID form, return FALSE */
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* desc form, return undefined */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( mrv == NULL ) {
+ /* unrecognized stored value */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ *matchp = ( asserted != mrv );
+ return LDAP_SUCCESS;
+}
+
+static int
+objectClassMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *a = (struct berval *) assertedValue;
+ ObjectClass *oc = oc_bvfind( value );
+ ObjectClass *asserted = oc_bvfind( a );
+
+ if( asserted == NULL ) {
+ if( OID_LEADCHAR( *a->bv_val ) ) {
+ /* OID form, return FALSE */
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* desc form, return undefined */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( oc == NULL ) {
+ /* unrecognized stored value */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ *matchp = ( asserted != oc );
+ return LDAP_SUCCESS;
+}
+
+static int
+objectSubClassMatch(
+ int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *a = (struct berval *) assertedValue;
+ ObjectClass *oc = oc_bvfind( value );
+ ObjectClass *asserted = oc_bvfind( a );
+
+ if( asserted == NULL ) {
+ if( OID_LEADCHAR( *a->bv_val ) ) {
+ /* OID form, return FALSE */
+ *matchp = 1;
+ return LDAP_SUCCESS;
+ }
+
+ /* desc form, return undefined */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( oc == NULL ) {
+ /* unrecognized stored value */
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX( flags ) ) {
+ *matchp = ( asserted != oc );
+ } else {
+ *matchp = !is_object_subclass( asserted, oc );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int objectSubClassIndexer(
+ slap_mask_t use,
+ slap_mask_t mask,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ int rc, noc, i;
+ BerVarray ocvalues;
+ ObjectClass **socs;
+
+ for( noc=0; values[noc].bv_val != NULL; noc++ ) {
+ /* just count em */;
+ }
+
+ /* over allocate */
+ socs = slap_sl_malloc( (noc+16) * sizeof( ObjectClass * ), ctx );
+
+ /* initialize */
+ for( i=0; i<noc; i++ ) {
+ socs[i] = oc_bvfind( &values[i] );
+ }
+
+ /* expand values */
+ for( i=0; i<noc; i++ ) {
+ int j;
+ ObjectClass *oc = socs[i];
+ if( oc == NULL || oc->soc_sups == NULL ) continue;
+
+ for( j=0; oc->soc_sups[j] != NULL; j++ ) {
+ int found = 0;
+ ObjectClass *sup = oc->soc_sups[j];
+ int k;
+
+ for( k=0; k<noc; k++ ) {
+ if( sup == socs[k] ) {
+ found++;
+ break;
+ }
+ }
+
+ if( !found ) {
+ socs = slap_sl_realloc( socs,
+ sizeof( ObjectClass * ) * (noc+2), ctx );
+
+ assert( k == noc );
+ socs[noc++] = sup;
+ }
+ }
+ }
+
+ ocvalues = slap_sl_malloc( sizeof( struct berval ) * (noc+1), ctx );
+ /* copy values */
+ for( i=0; i<noc; i++ ) {
+ if ( socs[i] )
+ ocvalues[i] = socs[i]->soc_cname;
+ else
+ ocvalues[i] = values[i];
+ }
+ BER_BVZERO( &ocvalues[i] );
+
+ rc = octetStringIndexer( use, mask, syntax, mr,
+ prefix, ocvalues, keysp, ctx );
+
+ slap_sl_free( ocvalues, ctx );
+ slap_sl_free( socs, ctx );
+ return rc;
+}
+
+#define objectSubClassFilter octetStringFilter
+
+static ObjectClassSchemaCheckFN rootDseObjectClass;
+static ObjectClassSchemaCheckFN aliasObjectClass;
+static ObjectClassSchemaCheckFN referralObjectClass;
+static ObjectClassSchemaCheckFN subentryObjectClass;
+#ifdef LDAP_DYNAMIC_OBJECTS
+static ObjectClassSchemaCheckFN dynamicObjectClass;
+#endif
+
+static struct slap_schema_oc_map {
+ char *ssom_name;
+ char *ssom_defn;
+ ObjectClassSchemaCheckFN *ssom_check;
+ slap_mask_t ssom_flags;
+ size_t ssom_offset;
+} oc_map[] = {
+ { "top", "( 2.5.6.0 NAME 'top' "
+ "DESC 'top of the superclass chain' "
+ "ABSTRACT MUST objectClass )",
+ 0, 0, offsetof(struct slap_internal_schema, si_oc_top) },
+ { "extensibleObject", "( 1.3.6.1.4.1.1466.101.120.111 "
+ "NAME 'extensibleObject' "
+ "DESC 'RFC4512: extensible object' "
+ "SUP top AUXILIARY )",
+ 0, SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_extensibleObject) },
+ { "alias", "( 2.5.6.1 NAME 'alias' "
+ "DESC 'RFC4512: an alias' "
+ "SUP top STRUCTURAL "
+ "MUST aliasedObjectName )",
+ aliasObjectClass, SLAP_OC_ALIAS|SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_alias) },
+ { "referral", "( 2.16.840.1.113730.3.2.6 NAME 'referral' "
+ "DESC 'namedref: named subordinate referral' "
+ "SUP top STRUCTURAL MUST ref )",
+ referralObjectClass, SLAP_OC_REFERRAL|SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_referral) },
+ { "LDAProotDSE", "( 1.3.6.1.4.1.4203.1.4.1 "
+ "NAME ( 'OpenLDAProotDSE' 'LDAProotDSE' ) "
+ "DESC 'OpenLDAP Root DSE object' "
+ "SUP top STRUCTURAL MAY cn )",
+ rootDseObjectClass, SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_rootdse) },
+ { "subentry", "( 2.5.17.0 NAME 'subentry' "
+ "DESC 'RFC3672: subentry' "
+ "SUP top STRUCTURAL "
+ "MUST ( cn $ subtreeSpecification ) )",
+ subentryObjectClass, SLAP_OC_SUBENTRY|SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_subentry) },
+ { "subschema", "( 2.5.20.1 NAME 'subschema' "
+ "DESC 'RFC4512: controlling subschema (sub)entry' "
+ "AUXILIARY "
+ "MAY ( dITStructureRules $ nameForms $ dITContentRules $ "
+ "objectClasses $ attributeTypes $ matchingRules $ "
+ "matchingRuleUse ) )",
+ subentryObjectClass, SLAP_OC_OPERATIONAL,
+ offsetof(struct slap_internal_schema, si_oc_subschema) },
+#ifdef LDAP_COLLECTIVE_ATTRIBUTES
+ { "collectiveAttributeSubentry", "( 2.5.17.2 "
+ "NAME 'collectiveAttributeSubentry' "
+ "DESC 'RFC3671: collective attribute subentry' "
+ "AUXILIARY )",
+ subentryObjectClass,
+ SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof( struct slap_internal_schema,
+ si_oc_collectiveAttributeSubentry) },
+#endif
+#ifdef LDAP_DYNAMIC_OBJECTS
+ { "dynamicObject", "( 1.3.6.1.4.1.1466.101.119.2 "
+ "NAME 'dynamicObject' "
+ "DESC 'RFC2589: Dynamic Object' "
+ "SUP top AUXILIARY )",
+ dynamicObjectClass, SLAP_OC_DYNAMICOBJECT,
+ offsetof(struct slap_internal_schema, si_oc_dynamicObject) },
+#endif
+ { "glue", "( 1.3.6.1.4.1.4203.666.3.4 "
+ "NAME 'glue' "
+ "DESC 'Glue Entry' "
+ "SUP top STRUCTURAL )",
+ 0, SLAP_OC_GLUE|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(struct slap_internal_schema, si_oc_glue) },
+ { "syncConsumerSubentry", "( 1.3.6.1.4.1.4203.666.3.5 "
+ "NAME 'syncConsumerSubentry' "
+ "DESC 'Persistent Info for SyncRepl Consumer' "
+ "AUXILIARY "
+ "MAY syncreplCookie )",
+ 0, SLAP_OC_SYNCCONSUMERSUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(struct slap_internal_schema, si_oc_syncConsumerSubentry) },
+ { "syncProviderSubentry", "( 1.3.6.1.4.1.4203.666.3.6 "
+ "NAME 'syncProviderSubentry' "
+ "DESC 'Persistent Info for SyncRepl Producer' "
+ "AUXILIARY "
+ "MAY contextCSN )",
+ 0, SLAP_OC_SYNCPROVIDERSUBENTRY|SLAP_OC_OPERATIONAL|SLAP_OC_HIDE,
+ offsetof(struct slap_internal_schema, si_oc_syncProviderSubentry) },
+
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+static AttributeTypeSchemaCheckFN rootDseAttribute;
+static AttributeTypeSchemaCheckFN aliasAttribute;
+static AttributeTypeSchemaCheckFN referralAttribute;
+static AttributeTypeSchemaCheckFN subentryAttribute;
+static AttributeTypeSchemaCheckFN administrativeRoleAttribute;
+#ifdef LDAP_DYNAMIC_OBJECTS
+static AttributeTypeSchemaCheckFN dynamicAttribute;
+#endif
+
+static struct slap_schema_ad_map {
+ char *ssam_name;
+ char *ssam_defn;
+ AttributeTypeSchemaCheckFN *ssam_check;
+ slap_mask_t ssam_flags;
+ slap_syntax_validate_func *ssam_syn_validate;
+ slap_syntax_transform_func *ssam_syn_pretty;
+ slap_mr_convert_func *ssam_mr_convert;
+ slap_mr_normalize_func *ssam_mr_normalize;
+ slap_mr_match_func *ssam_mr_match;
+ slap_mr_indexer_func *ssam_mr_indexer;
+ slap_mr_filter_func *ssam_mr_filter;
+ size_t ssam_offset;
+} ad_map[] = {
+ { "objectClass", "( 2.5.4.0 NAME 'objectClass' "
+ "DESC 'RFC4512: object classes of the entity' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ NULL, SLAP_AT_FINAL,
+ oidValidate, objectClassPretty,
+ NULL, NULL, objectSubClassMatch,
+ objectSubClassIndexer, objectSubClassFilter,
+ offsetof(struct slap_internal_schema, si_ad_objectClass) },
+
+ /* user entry operational attributes */
+ { "structuralObjectClass", "( 2.5.21.9 NAME 'structuralObjectClass' "
+ "DESC 'RFC4512: structural object class of entry' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, 0,
+ oidValidate, objectClassPretty,
+ NULL, NULL, objectSubClassMatch,
+ objectSubClassIndexer, objectSubClassFilter,
+ offsetof(struct slap_internal_schema, si_ad_structuralObjectClass) },
+ { "createTimestamp", "( 2.5.18.1 NAME 'createTimestamp' "
+ "DESC 'RFC4512: time which object was created' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_createTimestamp) },
+ { "modifyTimestamp", "( 2.5.18.2 NAME 'modifyTimestamp' "
+ "DESC 'RFC4512: time which object was last modified' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_modifyTimestamp) },
+ { "creatorsName", "( 2.5.18.3 NAME 'creatorsName' "
+ "DESC 'RFC4512: name of creator' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_creatorsName) },
+ { "modifiersName", "( 2.5.18.4 NAME 'modifiersName' "
+ "DESC 'RFC4512: name of last modifier' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_modifiersName) },
+ { "hasSubordinates", "( 2.5.18.9 NAME 'hasSubordinates' "
+ "DESC 'X.501: entry has children' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_DYNAMIC,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_hasSubordinates) },
+ { "subschemaSubentry", "( 2.5.18.10 NAME 'subschemaSubentry' "
+ "DESC 'RFC4512: name of controlling subschema entry' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE "
+ "NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_DYNAMIC,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_subschemaSubentry) },
+#ifdef LDAP_COLLECTIVE_ATTRIBUTES
+ { "collectiveAttributeSubentries", "( 2.5.18.12 "
+ "NAME 'collectiveAttributeSubentries' "
+ "DESC 'RFC3671: collective attribute subentries' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_collectiveSubentries) },
+ { "collectiveExclusions", "( 2.5.18.7 NAME 'collectiveExclusions' "
+ "DESC 'RFC3671: collective attribute exclusions' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 "
+ "USAGE directoryOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_collectiveExclusions) },
+#endif
+
+ { "entryDN", "( 1.3.6.1.1.20 NAME 'entryDN' "
+ "DESC 'DN of the entry' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_DYNAMIC,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_entryDN) },
+ { "entryUUID", "( 1.3.6.1.1.16.4 NAME 'entryUUID' "
+ "DESC 'UUID of the entry' "
+ "EQUALITY UUIDMatch "
+ "ORDERING UUIDOrderingMatch "
+ "SYNTAX 1.3.6.1.1.16.1 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_entryUUID) },
+ { "entryCSN", "( 1.3.6.1.4.1.4203.666.1.7 NAME 'entryCSN' "
+ "DESC 'change sequence number of the entry content' "
+ "EQUALITY CSNMatch "
+ "ORDERING CSNOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_entryCSN) },
+ { "namingCSN", "( 1.3.6.1.4.1.4203.666.1.13 NAME 'namingCSN' "
+ "DESC 'change sequence number of the entry naming (RDN)' "
+ "EQUALITY CSNMatch "
+ "ORDERING CSNOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_namingCSN) },
+
+#ifdef LDAP_SUPERIOR_UUID
+ { "superiorUUID", "( 1.3.6.1.4.1.4203.666.1.11 NAME 'superiorUUID' "
+ "DESC 'UUID of the superior entry' "
+ "EQUALITY UUIDMatch "
+ "ORDERING UUIDOrderingMatch "
+ "SYNTAX 1.3.6.1.1.16.1 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_superiorUUID) },
+#endif
+
+ { "syncreplCookie", "( 1.3.6.1.4.1.4203.666.1.23 "
+ "NAME 'syncreplCookie' "
+ "DESC 'syncrepl Cookie for shadow copy' "
+ "EQUALITY octetStringMatch "
+ "ORDERING octetStringOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_syncreplCookie) },
+
+ { "contextCSN", "( 1.3.6.1.4.1.4203.666.1.25 "
+ "NAME 'contextCSN' "
+ "DESC 'the largest committed CSN of a context' "
+ "EQUALITY CSNMatch "
+ "ORDERING CSNOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} "
+ "NO-USER-MODIFICATION USAGE dSAOperation )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_contextCSN) },
+
+#ifdef LDAP_SYNC_TIMESTAMP
+ { "syncTimestamp", "( 1.3.6.1.4.1.4203.666.1.26 NAME 'syncTimestamp' "
+ "DESC 'Time which object was replicated' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_syncTimestamp) },
+#endif
+
+ /* root DSE attributes */
+ { "altServer", "( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer' "
+ "DESC 'RFC4512: alternative servers' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_altServer) },
+ { "namingContexts", "( 1.3.6.1.4.1.1466.101.120.5 "
+ "NAME 'namingContexts' "
+ "DESC 'RFC4512: naming contexts' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_namingContexts) },
+ { "supportedControl", "( 1.3.6.1.4.1.1466.101.120.13 "
+ "NAME 'supportedControl' "
+ "DESC 'RFC4512: supported controls' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_supportedControl) },
+ { "supportedExtension", "( 1.3.6.1.4.1.1466.101.120.7 "
+ "NAME 'supportedExtension' "
+ "DESC 'RFC4512: supported extended operations' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_supportedExtension) },
+ { "supportedLDAPVersion", "( 1.3.6.1.4.1.1466.101.120.15 "
+ "NAME 'supportedLDAPVersion' "
+ "DESC 'RFC4512: supported LDAP versions' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_supportedLDAPVersion) },
+ { "supportedSASLMechanisms", "( 1.3.6.1.4.1.1466.101.120.14 "
+ "NAME 'supportedSASLMechanisms' "
+ "DESC 'RFC4512: supported SASL mechanisms'"
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_supportedSASLMechanisms) },
+ { "supportedFeatures", "( 1.3.6.1.4.1.4203.1.3.5 "
+ "NAME 'supportedFeatures' "
+ "DESC 'RFC4512: features supported by the server' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 "
+ "USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_supportedFeatures) },
+ { "monitorContext", "( 1.3.6.1.4.1.4203.666.1.10 "
+ "NAME 'monitorContext' "
+ "DESC 'monitor context' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "EQUALITY distinguishedNameMatch "
+ "SINGLE-VALUE NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ rootDseAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_monitorContext) },
+ { "configContext", "( 1.3.6.1.4.1.4203.1.12.2.1 "
+ "NAME 'configContext' "
+ "DESC 'config context' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "EQUALITY distinguishedNameMatch "
+ "SINGLE-VALUE NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ rootDseAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_configContext) },
+ { "vendorName", "( 1.3.6.1.1.4 NAME 'vendorName' "
+ "DESC 'RFC3045: name of implementation vendor' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "SINGLE-VALUE NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_vendorName) },
+ { "vendorVersion", "( 1.3.6.1.1.5 NAME 'vendorVersion' "
+ "DESC 'RFC3045: version of implementation' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "SINGLE-VALUE NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_vendorVersion) },
+
+ /* subentry attributes */
+ { "administrativeRole", "( 2.5.18.5 NAME 'administrativeRole' "
+ "DESC 'RFC3672: administrative role' "
+ "EQUALITY objectIdentifierMatch "
+ "USAGE directoryOperation "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ administrativeRoleAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_administrativeRole) },
+ { "subtreeSpecification", "( 2.5.18.6 NAME 'subtreeSpecification' "
+ "DESC 'RFC3672: subtree specification' "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.45 )",
+ subentryAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_subtreeSpecification) },
+
+ /* subschema subentry attributes */
+ { "dITStructureRules", "( 2.5.21.1 NAME 'dITStructureRules' "
+ "DESC 'RFC4512: DIT structure rules' "
+ "EQUALITY integerFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 "
+ "USAGE directoryOperation ) ",
+ subentryAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_ditStructureRules) },
+ { "dITContentRules", "( 2.5.21.2 NAME 'dITContentRules' "
+ "DESC 'RFC4512: DIT content rules' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 USAGE directoryOperation )",
+ subentryAttribute, SLAP_AT_HIDE,
+ oidValidate, NULL,
+ NULL, NULL, objectClassMatch, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_ditContentRules) },
+ { "matchingRules", "( 2.5.21.4 NAME 'matchingRules' "
+ "DESC 'RFC4512: matching rules' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation )",
+ subentryAttribute, 0,
+ oidValidate, NULL,
+ NULL, NULL, matchingRuleMatch, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_matchingRules) },
+ { "attributeTypes", "( 2.5.21.5 NAME 'attributeTypes' "
+ "DESC 'RFC4512: attribute types' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation )",
+ subentryAttribute, 0,
+ oidValidate, NULL,
+ NULL, NULL, attributeTypeMatch, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_attributeTypes) },
+ { "objectClasses", "( 2.5.21.6 NAME 'objectClasses' "
+ "DESC 'RFC4512: object classes' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation )",
+ subentryAttribute, 0,
+ oidValidate, NULL,
+ NULL, NULL, objectClassMatch, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_objectClasses) },
+ { "nameForms", "( 2.5.21.7 NAME 'nameForms' "
+ "DESC 'RFC4512: name forms ' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 USAGE directoryOperation )",
+ subentryAttribute, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_nameForms) },
+ { "matchingRuleUse", "( 2.5.21.8 NAME 'matchingRuleUse' "
+ "DESC 'RFC4512: matching rule uses' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation )",
+ subentryAttribute, 0,
+ oidValidate, NULL,
+ NULL, NULL, matchingRuleMatch, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_matchingRuleUse) },
+
+ { "ldapSyntaxes", "( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes' "
+ "DESC 'RFC4512: LDAP syntaxes' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation )",
+ subentryAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_ldapSyntaxes) },
+
+ /* knowledge information */
+ { "aliasedObjectName", "( 2.5.4.1 "
+ "NAME ( 'aliasedObjectName' 'aliasedEntryName' ) "
+ "DESC 'RFC4512: name of aliased object' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
+ aliasAttribute, SLAP_AT_FINAL,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_aliasedObjectName) },
+ { "ref", "( 2.16.840.1.113730.3.1.34 NAME 'ref' "
+ "DESC 'RFC3296: subordinate referral URL' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "USAGE distributedOperation )",
+ referralAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_ref) },
+
+ /* access control internals */
+ { "entry", "( 1.3.6.1.4.1.4203.1.3.1 "
+ "NAME 'entry' "
+ "DESC 'OpenLDAP ACL entry pseudo-attribute' "
+ "SYNTAX 1.3.6.1.4.1.4203.1.1.1 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_entry) },
+ { "children", "( 1.3.6.1.4.1.4203.1.3.2 "
+ "NAME 'children' "
+ "DESC 'OpenLDAP ACL children pseudo-attribute' "
+ "SYNTAX 1.3.6.1.4.1.4203.1.1.1 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_children) },
+
+ /* access control externals */
+ { "authzTo", "( 1.3.6.1.4.1.4203.666.1.8 "
+ "NAME ( 'authzTo' 'saslAuthzTo' ) "
+ "DESC 'proxy authorization targets' "
+ "EQUALITY authzMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.2.7 "
+ "X-ORDERED 'VALUES' "
+ "USAGE distributedOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_saslAuthzTo) },
+ { "authzFrom", "( 1.3.6.1.4.1.4203.666.1.9 "
+ "NAME ( 'authzFrom' 'saslAuthzFrom' ) "
+ "DESC 'proxy authorization sources' "
+ "EQUALITY authzMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.2.7 "
+ "X-ORDERED 'VALUES' "
+ "USAGE distributedOperation )",
+ NULL, SLAP_AT_HIDE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_saslAuthzFrom) },
+
+#ifdef LDAP_DYNAMIC_OBJECTS
+ { "entryTtl", "( 1.3.6.1.4.1.1466.101.119.3 NAME 'entryTtl' "
+ "DESC 'RFC2589: entry time-to-live' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE "
+ "NO-USER-MODIFICATION USAGE dSAOperation )",
+ dynamicAttribute, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_entryTtl) },
+ { "dynamicSubtrees", "( 1.3.6.1.4.1.1466.101.119.4 "
+ "NAME 'dynamicSubtrees' "
+ "DESC 'RFC2589: dynamic subtrees' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ rootDseAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_dynamicSubtrees) },
+#endif
+
+ /* userApplication attributes (which system schema depends upon) */
+ { "distinguishedName", "( 2.5.4.49 NAME 'distinguishedName' "
+ "DESC 'RFC4519: common supertype of DN attributes' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
+ NULL, SLAP_AT_ABSTRACT,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_distinguishedName) },
+ { "name", "( 2.5.4.41 NAME 'name' "
+ "DESC 'RFC4519: common supertype of name attributes' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )",
+ NULL, SLAP_AT_ABSTRACT,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_name) },
+ { "cn", "( 2.5.4.3 NAME ( 'cn' 'commonName' ) "
+ "DESC 'RFC4519: common name(s) for which the entity is known by' "
+ "SUP name )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_cn) },
+ { "uid", "( 0.9.2342.19200300.100.1.1 NAME ( 'uid' 'userid' ) "
+ "DESC 'RFC4519: user identifier' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_uid) },
+ { "uidNumber", /* for ldapi:// */
+ "( 1.3.6.1.1.1.1.0 NAME 'uidNumber' "
+ "DESC 'RFC2307: An integer uniquely identifying a user "
+ "in an administrative domain' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_uidNumber) },
+ { "gidNumber", /* for ldapi:// */
+ "( 1.3.6.1.1.1.1.1 NAME 'gidNumber' "
+ "DESC 'RFC2307: An integer uniquely identifying a group "
+ "in an administrative domain' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_gidNumber) },
+ { "userPassword", "( 2.5.4.35 NAME 'userPassword' "
+ "DESC 'RFC4519/2307: password of user' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_userPassword) },
+
+ { "labeledURI", "( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' "
+ "DESC 'RFC2079: Uniform Resource Identifier with optional label' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_labeledURI) },
+
+#ifdef SLAPD_AUTHPASSWD
+ { "authPassword", "( 1.3.6.1.4.1.4203.1.3.4 "
+ "NAME 'authPassword' "
+ "DESC 'RFC3112: authentication password attribute' "
+ "EQUALITY 1.3.6.1.4.1.4203.1.2.2 "
+ "SYNTAX 1.3.6.1.4.1.4203.1.1.2 )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_authPassword) },
+ { "supportedAuthPasswordSchemes", "( 1.3.6.1.4.1.4203.1.3.3 "
+ "NAME 'supportedAuthPasswordSchemes' "
+ "DESC 'RFC3112: supported authPassword schemes' "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} "
+ "USAGE dSAOperation )",
+ subschemaAttribute, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_authPasswordSchemes) },
+#endif
+
+ { "description", "( 2.5.4.13 NAME 'description' "
+ "DESC 'RFC4519: descriptive information' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_description) },
+
+ { "seeAlso", "( 2.5.4.34 NAME 'seeAlso' "
+ "DESC 'RFC4519: DN of related object' "
+ "SUP distinguishedName )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_seeAlso) },
+
+ { "pKCS8PrivateKey", "( 1.3.6.1.4.1.4203.666.1.60 "
+ "NAME 'pKCS8PrivateKey' "
+ "DESC 'PKCS#8 PrivateKeyInfo, use ;binary' "
+ "EQUALITY privateKeyMatch "
+ "SYNTAX 1.2.840.113549.1.8.1.1 )",
+ NULL, 0,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_pKCS8PrivateKey) },
+
+ { "pwdLastSuccess", "( 1.3.6.1.4.1.42.2.27.8.1.29 NAME 'pwdLastSuccess' "
+ "DESC 'The timestamp of the last successful authentication' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ NULL, SLAP_AT_MANAGEABLE,
+ NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ offsetof(struct slap_internal_schema, si_ad_pwdLastSuccess) },
+
+ { NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0 }
+};
+
+static AttributeType slap_at_undefined = {
+ { "1.1.1", NULL, "Catchall for undefined attribute types", 1, NULL,
+ NULL, NULL, NULL, NULL,
+ 0, 0, 0, 1, LDAP_SCHEMA_DSA_OPERATION, NULL }, /* LDAPAttributeType */
+ BER_BVC("UNDEFINED"), /* cname */
+ NULL, /* sup */
+ NULL, /* subtypes */
+ NULL, NULL, NULL, NULL, /* matching rules routines */
+ NULL, /* syntax (will be set later to "octetString") */
+ NULL, /* schema check function */
+ NULL, /* oidmacro */
+ NULL, /* soidmacro */
+ SLAP_AT_ABSTRACT|SLAP_AT_FINAL, /* mask */
+ { NULL }, /* next */
+ NULL /* attribute description */
+ /* mutex (don't know how to initialize it :) */
+};
+
+static AttributeType slap_at_proxied = {
+ { "1.1.1", NULL, "Catchall for undefined proxied attribute types", 1, NULL,
+ NULL, NULL, NULL, NULL,
+ 0, 0, 0, 0, LDAP_SCHEMA_USER_APPLICATIONS, NULL }, /* LDAPAttributeType */
+ BER_BVC("PROXIED"), /* cname */
+ NULL, /* sup */
+ NULL, /* subtypes */
+ NULL, NULL, NULL, NULL, /* matching rules routines (will be set later) */
+ NULL, /* syntax (will be set later to "octetString") */
+ NULL, /* schema check function */
+ NULL, /* oidmacro */
+ NULL, /* soidmacro */
+ SLAP_AT_ABSTRACT|SLAP_AT_FINAL, /* mask */
+ { NULL }, /* next */
+ NULL /* attribute description */
+ /* mutex (don't know how to initialize it :) */
+};
+
+static struct slap_schema_mr_map {
+ char *ssmm_name;
+ size_t ssmm_offset;
+} mr_map[] = {
+ { "caseExactIA5Match",
+ offsetof(struct slap_internal_schema, si_mr_caseExactIA5Match) },
+ { "caseExactMatch",
+ offsetof(struct slap_internal_schema, si_mr_caseExactMatch) },
+ { "caseExactSubstringsMatch",
+ offsetof(struct slap_internal_schema, si_mr_caseExactSubstringsMatch) },
+ { "distinguishedNameMatch",
+ offsetof(struct slap_internal_schema, si_mr_distinguishedNameMatch) },
+ { "dnSubtreeMatch",
+ offsetof(struct slap_internal_schema, si_mr_dnSubtreeMatch) },
+ { "dnOneLevelMatch",
+ offsetof(struct slap_internal_schema, si_mr_dnOneLevelMatch) },
+ { "dnSubordinateMatch",
+ offsetof(struct slap_internal_schema, si_mr_dnSubordinateMatch) },
+ { "dnSuperiorMatch",
+ offsetof(struct slap_internal_schema, si_mr_dnSuperiorMatch) },
+ { "integerMatch",
+ offsetof(struct slap_internal_schema, si_mr_integerMatch) },
+ { "integerFirstComponentMatch",
+ offsetof(struct slap_internal_schema,
+ si_mr_integerFirstComponentMatch) },
+ { "objectIdentifierFirstComponentMatch",
+ offsetof(struct slap_internal_schema,
+ si_mr_objectIdentifierFirstComponentMatch) },
+ { "caseIgnoreMatch",
+ offsetof(struct slap_internal_schema, si_mr_caseIgnoreMatch) },
+ { "caseIgnoreListMatch",
+ offsetof(struct slap_internal_schema, si_mr_caseIgnoreListMatch) },
+ { NULL, 0 }
+};
+
+static struct slap_schema_syn_map {
+ char *sssm_name;
+ size_t sssm_offset;
+} syn_map[] = {
+ { "1.3.6.1.4.1.1466.115.121.1.15",
+ offsetof(struct slap_internal_schema, si_syn_directoryString) },
+ { "1.3.6.1.4.1.1466.115.121.1.12",
+ offsetof(struct slap_internal_schema, si_syn_distinguishedName) },
+ { "1.3.6.1.4.1.1466.115.121.1.27",
+ offsetof(struct slap_internal_schema, si_syn_integer) },
+ { "1.3.6.1.4.1.1466.115.121.1.40",
+ offsetof(struct slap_internal_schema, si_syn_octetString) },
+ { "1.3.6.1.4.1.1466.115.121.1.3",
+ offsetof(struct slap_internal_schema, si_syn_attributeTypeDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.16",
+ offsetof(struct slap_internal_schema, si_syn_ditContentRuleDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.54",
+ offsetof(struct slap_internal_schema, si_syn_ldapSyntaxDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.30",
+ offsetof(struct slap_internal_schema, si_syn_matchingRuleDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.31",
+ offsetof(struct slap_internal_schema, si_syn_matchingRuleUseDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.35",
+ offsetof(struct slap_internal_schema, si_syn_nameFormDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.37",
+ offsetof(struct slap_internal_schema, si_syn_objectClassDesc) },
+ { "1.3.6.1.4.1.1466.115.121.1.17",
+ offsetof(struct slap_internal_schema, si_syn_ditStructureRuleDesc) },
+ { NULL, 0 }
+};
+
+int
+slap_schema_load( void )
+{
+ int i;
+
+ for( i=0; syn_map[i].sssm_name; i++ ) {
+ Syntax ** synp = (Syntax **)
+ &(((char *) &slap_schema)[syn_map[i].sssm_offset]);
+
+ assert( *synp == NULL );
+
+ *synp = syn_find( syn_map[i].sssm_name );
+
+ if( *synp == NULL ) {
+ fprintf( stderr, "slap_schema_load: Syntax: "
+ "No syntax \"%s\" defined in schema\n",
+ syn_map[i].sssm_name );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ for( i=0; mr_map[i].ssmm_name; i++ ) {
+ MatchingRule ** mrp = (MatchingRule **)
+ &(((char *) &slap_schema)[mr_map[i].ssmm_offset]);
+
+ assert( *mrp == NULL );
+
+ *mrp = mr_find( mr_map[i].ssmm_name );
+
+ if( *mrp == NULL ) {
+ fprintf( stderr, "slap_schema_load: MatchingRule: "
+ "No matching rule \"%s\" defined in schema\n",
+ mr_map[i].ssmm_name );
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+ }
+
+ slap_at_undefined.sat_syntax = slap_schema.si_syn_octetString;
+ slap_schema.si_at_undefined = &slap_at_undefined;
+
+ slap_at_proxied.sat_equality = mr_find( "octetStringMatch" );
+ slap_at_proxied.sat_approx = mr_find( "octetStringMatch" );
+ slap_at_proxied.sat_ordering = mr_find( "octetStringOrderingMatch" );
+ slap_at_proxied.sat_substr = mr_find( "octetStringSubstringsMatch" );
+ slap_at_proxied.sat_syntax = slap_schema.si_syn_octetString;
+ slap_schema.si_at_proxied = &slap_at_proxied;
+
+ ldap_pvt_thread_mutex_init( &ad_index_mutex );
+ ldap_pvt_thread_mutex_init( &ad_undef_mutex );
+ ldap_pvt_thread_mutex_init( &oc_undef_mutex );
+
+ for( i=0; ad_map[i].ssam_name; i++ ) {
+ assert( ad_map[i].ssam_defn != NULL );
+ {
+ LDAPAttributeType *at;
+ int code;
+ const char *err;
+
+ at = ldap_str2attributetype( ad_map[i].ssam_defn,
+ &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !at ) {
+ fprintf( stderr,
+ "slap_schema_load: AttributeType \"%s\": %s before %s\n",
+ ad_map[i].ssam_name, ldap_scherr2str(code), err );
+ return code;
+ }
+
+ if ( at->at_oid == NULL ) {
+ fprintf( stderr, "slap_schema_load: "
+ "AttributeType \"%s\": no OID\n",
+ ad_map[i].ssam_name );
+ ldap_attributetype_free( at );
+ return LDAP_OTHER;
+ }
+
+ code = at_add( at, 0, NULL, NULL, &err );
+ if ( code ) {
+ ldap_attributetype_free( at );
+ fprintf( stderr, "slap_schema_load: AttributeType "
+ "\"%s\": %s: \"%s\"\n",
+ ad_map[i].ssam_name, scherr2str(code), err );
+ return code;
+ }
+ ldap_memfree( at );
+ }
+ {
+ int rc;
+ const char *text;
+ Syntax *syntax = NULL;
+
+ AttributeDescription ** adp = (AttributeDescription **)
+ &(((char *) &slap_schema)[ad_map[i].ssam_offset]);
+
+ assert( *adp == NULL );
+
+ rc = slap_str2ad( ad_map[i].ssam_name, adp, &text );
+ if( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "slap_schema_load: AttributeType \"%s\": "
+ "not defined in schema\n",
+ ad_map[i].ssam_name );
+ return rc;
+ }
+
+ if( ad_map[i].ssam_check ) {
+ /* install check routine */
+ (*adp)->ad_type->sat_check = ad_map[i].ssam_check;
+ }
+ /* install flags */
+ (*adp)->ad_type->sat_flags |= ad_map[i].ssam_flags;
+
+ /* install custom syntax routines */
+ if( ad_map[i].ssam_syn_validate ||
+ ad_map[i].ssam_syn_pretty )
+ {
+ Syntax *syn;
+
+ syntax = (*adp)->ad_type->sat_syntax;
+
+ syn = ch_malloc( sizeof( Syntax ) );
+ *syn = *syntax;
+
+ if( ad_map[i].ssam_syn_validate ) {
+ syn->ssyn_validate = ad_map[i].ssam_syn_validate;
+ }
+ if( ad_map[i].ssam_syn_pretty ) {
+ syn->ssyn_pretty = ad_map[i].ssam_syn_pretty;
+ }
+
+ (*adp)->ad_type->sat_syntax = syn;
+ }
+
+ /* install custom rule routines */
+ if( syntax != NULL ||
+ ad_map[i].ssam_mr_convert ||
+ ad_map[i].ssam_mr_normalize ||
+ ad_map[i].ssam_mr_match ||
+ ad_map[i].ssam_mr_indexer ||
+ ad_map[i].ssam_mr_filter )
+ {
+ MatchingRule *mr = ch_malloc( sizeof( MatchingRule ) );
+ *mr = *(*adp)->ad_type->sat_equality;
+
+ if ( syntax != NULL ) {
+ mr->smr_syntax = (*adp)->ad_type->sat_syntax;
+ }
+ if ( ad_map[i].ssam_mr_convert ) {
+ mr->smr_convert = ad_map[i].ssam_mr_convert;
+ }
+ if ( ad_map[i].ssam_mr_normalize ) {
+ mr->smr_normalize = ad_map[i].ssam_mr_normalize;
+ }
+ if ( ad_map[i].ssam_mr_match ) {
+ mr->smr_match = ad_map[i].ssam_mr_match;
+ }
+ if ( ad_map[i].ssam_mr_indexer ) {
+ mr->smr_indexer = ad_map[i].ssam_mr_indexer;
+ }
+ if ( ad_map[i].ssam_mr_filter ) {
+ mr->smr_filter = ad_map[i].ssam_mr_filter;
+ }
+
+ (*adp)->ad_type->sat_equality = mr;
+ }
+ }
+ }
+
+ for( i=0; oc_map[i].ssom_name; i++ ) {
+ assert( oc_map[i].ssom_defn != NULL );
+ {
+ LDAPObjectClass *oc;
+ int code;
+ const char *err;
+
+ oc = ldap_str2objectclass( oc_map[i].ssom_defn, &code, &err,
+ LDAP_SCHEMA_ALLOW_ALL );
+ if ( !oc ) {
+ fprintf( stderr, "slap_schema_load: ObjectClass "
+ "\"%s\": %s before %s\n",
+ oc_map[i].ssom_name, ldap_scherr2str(code), err );
+ return code;
+ }
+
+ if ( oc->oc_oid == NULL ) {
+ fprintf( stderr, "slap_schema_load: ObjectClass "
+ "\"%s\": no OID\n",
+ oc_map[i].ssom_name );
+ ldap_objectclass_free( oc );
+ return LDAP_OTHER;
+ }
+
+ code = oc_add(oc,0,NULL,NULL,&err);
+ if ( code ) {
+ ldap_objectclass_free( oc );
+ fprintf( stderr, "slap_schema_load: ObjectClass "
+ "\"%s\": %s: \"%s\"\n",
+ oc_map[i].ssom_name, scherr2str(code), err);
+ return code;
+ }
+ ldap_memfree(oc);
+
+ }
+ {
+ ObjectClass ** ocp = (ObjectClass **)
+ &(((char *) &slap_schema)[oc_map[i].ssom_offset]);
+
+ assert( *ocp == NULL );
+
+ *ocp = oc_find( oc_map[i].ssom_name );
+ if( *ocp == NULL ) {
+ fprintf( stderr, "slap_schema_load: "
+ "ObjectClass \"%s\": not defined in schema\n",
+ oc_map[i].ssom_name );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( oc_map[i].ssom_check ) {
+ /* install check routine */
+ (*ocp)->soc_check = oc_map[i].ssom_check;
+ }
+ /* install flags */
+ (*ocp)->soc_flags |= oc_map[i].ssom_flags;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+slap_schema_check( void )
+{
+ /* we should only be called once after schema_init() was called */
+ assert( schema_init_done == 1 );
+
+ /*
+ * cycle thru attributeTypes to build matchingRuleUse
+ */
+ if ( matching_rule_use_init() ) {
+ return LDAP_OTHER;
+ }
+
+ ++schema_init_done;
+ return LDAP_SUCCESS;
+}
+
+static int rootDseObjectClass (
+ Backend *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( e->e_nname.bv_len ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" only allowed in the root DSE",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* we should not be called for the root DSE */
+ assert( 0 );
+ return LDAP_SUCCESS;
+}
+
+static int aliasObjectClass (
+ Backend *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_ALIASES(be) ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" not supported in context",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int referralObjectClass (
+ Backend *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_REFERRALS(be) ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" not supported in context",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int subentryObjectClass (
+ Backend *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_SUBENTRIES(be) ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" not supported in context",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( oc != slap_schema.si_oc_subentry && !is_entry_subentry( e ) ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" only allowed in subentries",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+#ifdef LDAP_DYNAMIC_OBJECTS
+static int dynamicObjectClass (
+ Backend *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_DYNAMIC(be) ) {
+ snprintf( textbuf, textlen,
+ "objectClass \"%s\" not supported in context",
+ oc->soc_oid );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+#endif /* LDAP_DYNAMIC_OBJECTS */
+
+static int rootDseAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( e->e_nname.bv_len ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" only allowed in the root DSE",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* we should not be called for the root DSE */
+ assert( 0 );
+ return LDAP_SUCCESS;
+}
+
+static int aliasAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_ALIASES(be) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported in context",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( !is_entry_alias( e ) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" only allowed in the alias",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int referralAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_REFERRALS(be) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported in context",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( !is_entry_referral( e ) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" only allowed in the referral",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int subentryAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_SUBENTRIES(be) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported in context",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( !is_entry_subentry( e ) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" only allowed in the subentry",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int administrativeRoleAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_SUBENTRIES(be) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported in context",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported!",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+}
+
+#ifdef LDAP_DYNAMIC_OBJECTS
+static int dynamicAttribute (
+ Backend *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen )
+{
+ *text = textbuf;
+
+ if( !SLAP_DYNAMIC(be) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" not supported in context",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if( !is_entry_dynamicObject( e ) ) {
+ snprintf( textbuf, textlen,
+ "attribute \"%s\" only allowed in dynamic object",
+ attr->a_desc->ad_cname.bv_val );
+ return LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDAP_SUCCESS;
+}
+#endif /* LDAP_DYNAMIC_OBJECTS */
diff --git a/servers/slapd/schemaparse.c b/servers/slapd/schemaparse.c
new file mode 100644
index 0000000..a13c89c
--- /dev/null
+++ b/servers/slapd/schemaparse.c
@@ -0,0 +1,400 @@
+/* schemaparse.c - routines to parse config file objectclass definitions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "ldap_schema.h"
+#include "slap-config.h"
+
+static void oc_usage(void);
+static void at_usage(void);
+
+static char *const err2text[] = {
+ "Success",
+ "Out of memory",
+ "ObjectClass not found",
+ "user-defined ObjectClass includes operational attributes",
+ "user-defined ObjectClass has inappropriate SUPerior",
+ "Duplicate objectClass",
+ "Inconsistent duplicate objectClass",
+ "AttributeType not found",
+ "AttributeType inappropriate matching rule",
+ "AttributeType inappropriate USAGE",
+ "AttributeType inappropriate SUPerior",
+ "AttributeType SYNTAX or SUPerior required",
+ "Duplicate attributeType",
+ "Inconsistent duplicate attributeType",
+ "MatchingRule not found",
+ "MatchingRule incomplete",
+ "Duplicate matchingRule",
+ "Syntax not found",
+ "Duplicate ldapSyntax",
+ "Superior syntax not found",
+ "Substitute syntax not specified",
+ "Substitute syntax not found",
+ "OID or name required",
+ "Qualifier not supported",
+ "Invalid NAME",
+ "OID could not be expanded",
+ "Duplicate Content Rule",
+ "Content Rule not for STRUCTURAL object class",
+ "Content Rule AUX contains inappropriate object class",
+ "Content Rule attribute type list contains duplicate",
+ NULL
+};
+
+char *
+scherr2str(int code)
+{
+ if ( code < 0 || SLAP_SCHERR_LAST <= code ) {
+ return "Unknown error";
+ } else {
+ return err2text[code];
+ }
+}
+
+/* check schema descr validity */
+int slap_valid_descr( const char *descr )
+{
+ int i=0;
+
+ if( !DESC_LEADCHAR( descr[i] ) ) {
+ return 0;
+ }
+
+ while( descr[++i] ) {
+ if( !DESC_CHAR( descr[i] ) ) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+/* OID Macros */
+
+/* String compare with delimiter check. Return 0 if not
+ * matched, otherwise return length matched.
+ */
+int
+dscompare(const char *s1, const char *s2, char delim)
+{
+ const char *orig = s1;
+ while (*s1++ == *s2++)
+ if (!s1[-1]) break;
+ --s1;
+ --s2;
+ if (!*s1 && (!*s2 || *s2 == delim))
+ return s1 - orig;
+ return 0;
+}
+
+static void
+cr_usage( void )
+{
+ fprintf( stderr,
+ "DITContentRuleDescription = \"(\" whsp\n"
+ " numericoid whsp ; StructuralObjectClass identifier\n"
+ " [ \"NAME\" qdescrs ]\n"
+ " [ \"DESC\" qdstring ]\n"
+ " [ \"OBSOLETE\" whsp ]\n"
+ " [ \"AUX\" oids ] ; Auxiliary ObjectClasses\n"
+ " [ \"MUST\" oids ] ; AttributeTypes\n"
+ " [ \"MAY\" oids ] ; AttributeTypes\n"
+ " [ \"NOT\" oids ] ; AttributeTypes\n"
+ " whsp \")\"\n" );
+}
+
+int
+parse_cr(
+ struct config_args_s *c,
+ ContentRule **scr )
+{
+ LDAPContentRule *cr;
+ int code;
+ const char *err;
+ char *line = strchr( c->line, '(' );
+
+ cr = ldap_str2contentrule( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !cr ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s",
+ c->argv[0], ldap_scherr2str( code ), err );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ cr_usage();
+ return 1;
+ }
+
+ if ( cr->cr_oid == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ cr_usage();
+ code = 1;
+ goto done;
+ }
+
+ code = cr_add( cr, 1, scr, &err );
+ if ( code ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"",
+ c->argv[0], scherr2str(code), err);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ code = 1;
+ goto done;
+ }
+
+done:;
+ if ( code ) {
+ ldap_contentrule_free( cr );
+
+ } else {
+ ldap_memfree( cr );
+ }
+
+ return code;
+}
+
+int
+parse_oc(
+ struct config_args_s *c,
+ ObjectClass **soc,
+ ObjectClass *prev )
+{
+ LDAPObjectClass *oc;
+ int code;
+ const char *err;
+ char *line = strchr( c->line, '(' );
+
+ oc = ldap_str2objectclass(line, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !oc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s",
+ c->argv[0], ldap_scherr2str( code ), err );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ oc_usage();
+ return 1;
+ }
+
+ if ( oc->oc_oid == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ oc_usage();
+ code = 1;
+ goto done;
+ }
+
+ code = oc_add( oc, 1, soc, prev, &err );
+ if ( code ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"",
+ c->argv[0], scherr2str(code), err);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ code = 1;
+ goto done;
+ }
+
+done:;
+ if ( code ) {
+ ldap_objectclass_free( oc );
+
+ } else {
+ ldap_memfree( oc );
+ }
+
+ return code;
+}
+
+static void
+oc_usage( void )
+{
+ fprintf( stderr,
+ "ObjectClassDescription = \"(\" whsp\n"
+ " numericoid whsp ; ObjectClass identifier\n"
+ " [ \"NAME\" qdescrs ]\n"
+ " [ \"DESC\" qdstring ]\n"
+ " [ \"OBSOLETE\" whsp ]\n"
+ " [ \"SUP\" oids ] ; Superior ObjectClasses\n"
+ " [ ( \"ABSTRACT\" / \"STRUCTURAL\" / \"AUXILIARY\" ) whsp ]\n"
+ " ; default structural\n"
+ " [ \"MUST\" oids ] ; AttributeTypes\n"
+ " [ \"MAY\" oids ] ; AttributeTypes\n"
+ " whsp \")\"\n" );
+}
+
+static void
+at_usage( void )
+{
+ fprintf( stderr, "%s%s%s",
+ "AttributeTypeDescription = \"(\" whsp\n"
+ " numericoid whsp ; AttributeType identifier\n"
+ " [ \"NAME\" qdescrs ] ; name used in AttributeType\n"
+ " [ \"DESC\" qdstring ] ; description\n"
+ " [ \"OBSOLETE\" whsp ]\n"
+ " [ \"SUP\" woid ] ; derived from this other\n"
+ " ; AttributeType\n",
+ " [ \"EQUALITY\" woid ] ; Matching Rule name\n"
+ " [ \"ORDERING\" woid ] ; Matching Rule name\n"
+ " [ \"SUBSTR\" woid ] ; Matching Rule name\n"
+ " [ \"SYNTAX\" whsp noidlen whsp ] ; see section 4.3\n"
+ " [ \"SINGLE-VALUE\" whsp ] ; default multi-valued\n"
+ " [ \"COLLECTIVE\" whsp ] ; default not collective\n",
+ " [ \"NO-USER-MODIFICATION\" whsp ]; default user modifiable\n"
+ " [ \"USAGE\" whsp AttributeUsage ]; default userApplications\n"
+ " ; userApplications\n"
+ " ; directoryOperation\n"
+ " ; distributedOperation\n"
+ " ; dSAOperation\n"
+ " whsp \")\"\n");
+}
+
+int
+parse_at(
+ struct config_args_s *c,
+ AttributeType **sat,
+ AttributeType *prev )
+{
+ LDAPAttributeType *at;
+ int code;
+ const char *err;
+ char *line = strchr( c->line, '(' );
+
+ at = ldap_str2attributetype( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !at ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s",
+ c->argv[0], ldap_scherr2str(code), err );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ at_usage();
+ return 1;
+ }
+
+ if ( at->at_oid == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ at_usage();
+ code = 1;
+ goto done;
+ }
+
+ /* operational attributes should be defined internally */
+ if ( at->at_usage ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: \"%s\" is operational",
+ c->argv[0], at->at_oid );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ code = 1;
+ goto done;
+ }
+
+ code = at_add( at, 1, sat, prev, &err);
+ if ( code ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"",
+ c->argv[0], scherr2str(code), err);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ code = 1;
+ goto done;
+ }
+
+done:;
+ if ( code ) {
+ ldap_attributetype_free( at );
+
+ } else {
+ ldap_memfree( at );
+ }
+
+ return code;
+}
+
+static void
+syn_usage( void )
+{
+ fprintf( stderr, "%s",
+ "SyntaxDescription = \"(\" whsp\n"
+ " numericoid whsp ; object identifier\n"
+ " [ whsp \"DESC\" whsp qdstring ] ; description\n"
+ " extensions whsp \")\" ; extensions\n"
+ " whsp \")\"\n");
+}
+
+int
+parse_syn(
+ struct config_args_s *c,
+ Syntax **ssyn,
+ Syntax *prev )
+{
+ LDAPSyntax *syn;
+ slap_syntax_defs_rec def = { 0 };
+ int code;
+ const char *err;
+ char *line = strchr( c->line, '(' );
+
+ syn = ldap_str2syntax( line, &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+ if ( !syn ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s before %s",
+ c->argv[0], ldap_scherr2str(code), err );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ syn_usage();
+ return 1;
+ }
+
+ if ( syn->syn_oid == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: OID is missing",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ syn_usage();
+ code = 1;
+ goto done;
+ }
+
+ code = syn_add( syn, 1, &def, ssyn, prev, &err );
+ if ( code ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s: %s: \"%s\"",
+ c->argv[0], scherr2str(code), err);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s %s\n", c->log, c->cr_msg );
+ code = 1;
+ goto done;
+ }
+
+done:;
+ if ( code ) {
+ ldap_syntax_free( syn );
+
+ } else {
+ ldap_memfree( syn );
+ }
+
+ return code;
+}
+
diff --git a/servers/slapd/search.c b/servers/slapd/search.c
new file mode 100644
index 0000000..7846f0f
--- /dev/null
+++ b/servers/slapd/search.c
@@ -0,0 +1,439 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+
+int
+do_search(
+ Operation *op, /* info about the op to which we're responding */
+ SlapReply *rs /* all the response data we'll send */ )
+{
+ struct berval base = BER_BVNULL;
+ ber_len_t siz, off, i;
+
+ Debug( LDAP_DEBUG_TRACE, "%s do_search\n",
+ op->o_log_prefix );
+ /*
+ * Parse the search request. It looks like this:
+ *
+ * SearchRequest := [APPLICATION 3] SEQUENCE {
+ * baseObject DistinguishedName,
+ * scope ENUMERATED {
+ * baseObject (0),
+ * singleLevel (1),
+ * wholeSubtree (2),
+ * subordinate (3) -- OpenLDAP extension
+ * },
+ * derefAliases ENUMERATED {
+ * neverDerefaliases (0),
+ * derefInSearching (1),
+ * derefFindingBaseObj (2),
+ * alwaysDerefAliases (3)
+ * },
+ * sizelimit INTEGER (0 .. 65535),
+ * timelimit INTEGER (0 .. 65535),
+ * attrsOnly BOOLEAN,
+ * filter Filter,
+ * attributes SEQUENCE OF AttributeType
+ * }
+ */
+
+ /* baseObject, scope, derefAliases, sizelimit, timelimit, attrsOnly */
+ if ( ber_scanf( op->o_ber, "{miiiib" /*}*/,
+ &base, &op->ors_scope, &op->ors_deref, &op->ors_slimit,
+ &op->ors_tlimit, &op->ors_attrsonly ) == LBER_ERROR )
+ {
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto return_results;
+ }
+
+ if ( op->ors_tlimit < 0 || op->ors_tlimit > SLAP_MAX_LIMIT ) {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid time limit" );
+ goto return_results;
+ }
+
+ if ( op->ors_slimit < 0 || op->ors_slimit > SLAP_MAX_LIMIT ) {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid size limit" );
+ goto return_results;
+ }
+
+ switch( op->ors_scope ) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ break;
+ default:
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid scope" );
+ goto return_results;
+ }
+
+ switch( op->ors_deref ) {
+ case LDAP_DEREF_NEVER:
+ case LDAP_DEREF_FINDING:
+ case LDAP_DEREF_SEARCHING:
+ case LDAP_DEREF_ALWAYS:
+ break;
+ default:
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "invalid deref" );
+ goto return_results;
+ }
+
+ rs->sr_err = dnPrettyNormal( NULL, &base, &op->o_req_dn, &op->o_req_ndn, op->o_tmpmemctx );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_search: invalid dn: \"%s\"\n",
+ op->o_log_prefix, base.bv_val );
+ send_ldap_error( op, rs, LDAP_INVALID_DN_SYNTAX, "invalid DN" );
+ goto return_results;
+ }
+
+ Debug( LDAP_DEBUG_ARGS, "SRCH \"%s\" %d %d %d %d %d\n",
+ base.bv_val, op->ors_scope, op->ors_deref,
+ op->ors_slimit, op->ors_tlimit, op->ors_attrsonly);
+
+ /* filter - returns a "normalized" version */
+ rs->sr_err = get_filter( op, op->o_ber, &op->ors_filter, &rs->sr_text );
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ if( rs->sr_err == SLAPD_DISCONNECT ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ send_ldap_disconnect( op, rs );
+ rs->sr_err = SLAPD_DISCONNECT;
+ } else {
+ send_ldap_result( op, rs );
+ }
+ goto return_results;
+ }
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+
+ Debug( LDAP_DEBUG_ARGS, " filter: %s\n",
+ !BER_BVISEMPTY( &op->ors_filterstr ) ? op->ors_filterstr.bv_val : "empty" );
+
+ /* attributes */
+ siz = sizeof(AttributeName);
+ off = offsetof(AttributeName,an_name);
+ if ( ber_scanf( op->o_ber, "{M}}", &op->ors_attrs, &siz, off ) == LBER_ERROR ) {
+ send_ldap_discon( op, rs, LDAP_PROTOCOL_ERROR, "decoding attrs error" );
+ rs->sr_err = SLAPD_DISCONNECT;
+ goto return_results;
+ }
+ for ( i=0; i<siz; i++ ) {
+ const char *dummy; /* ignore msgs from bv2ad */
+ op->ors_attrs[i].an_desc = NULL;
+ op->ors_attrs[i].an_oc = NULL;
+ op->ors_attrs[i].an_flags = 0;
+ if ( slap_bv2ad( &op->ors_attrs[i].an_name,
+ &op->ors_attrs[i].an_desc, &dummy ) != LDAP_SUCCESS )
+ {
+ if ( slap_bv2undef_ad( &op->ors_attrs[i].an_name,
+ &op->ors_attrs[i].an_desc, &dummy,
+ SLAP_AD_PROXIED|SLAP_AD_NOINSERT ) )
+ {
+ struct berval *bv = &op->ors_attrs[i].an_name;
+
+ /* RFC 4511 LDAPv3: All User Attributes */
+ if ( bvmatch( bv, slap_bv_all_user_attrs ) ) {
+ continue;
+ }
+
+ /* RFC 3673 LDAPv3: All Operational Attributes */
+ if ( bvmatch( bv, slap_bv_all_operational_attrs ) ) {
+ continue;
+ }
+
+ /* RFC 4529 LDAP: Requesting Attributes by Object Class */
+ if ( bv->bv_len > 1 && bv->bv_val[0] == '@' ) {
+ /* FIXME: check if remaining is valid oc name? */
+ continue;
+ }
+
+ /* add more "exceptions" to RFC 4511 4.5.1.8. */
+
+ /* invalid attribute description? remove */
+ if ( ad_keystring( bv ) ) {
+ /* NOTE: parsed in-place, don't modify;
+ * rather add "1.1", which must be ignored */
+ BER_BVSTR( &op->ors_attrs[i].an_name, LDAP_NO_ATTRS );
+ }
+
+ /* otherwise leave in place... */
+ }
+ }
+ }
+
+ if( get_ctrls( op, rs, 1 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s do_search: get_ctrls failed\n",
+ op->o_log_prefix );
+ goto return_results;
+ }
+
+ if (LogTest( LDAP_DEBUG_ARGS ) ) {
+ char abuf[BUFSIZ/2], *ptr = abuf;
+ unsigned len = 0, alen;
+
+ if ( !siz ) {
+ len = 1;
+ abuf[0] = '\0';
+ }
+ for ( i = 0; i<siz; i++ ) {
+ alen = op->ors_attrs[i].an_name.bv_len;
+ if (alen >= sizeof(abuf)) {
+ alen = sizeof(abuf)-1;
+ }
+ if (len && (len + 1 + alen >= sizeof(abuf))) {
+ Debug( LDAP_DEBUG_ARGS, " attrs: %s\n", abuf );
+ len = 0;
+ ptr = abuf;
+ }
+ if (len) {
+ *ptr++ = ' ';
+ len++;
+ }
+ ptr = lutil_strncopy(ptr, op->ors_attrs[i].an_name.bv_val, alen);
+ len += alen;
+ *ptr = '\0';
+ }
+ if (len) {
+ Debug( LDAP_DEBUG_ARGS, " attrs: %s\n", abuf );
+ }
+ }
+
+ if (LogTest( LDAP_DEBUG_STATS ) ) {
+ char abuf[BUFSIZ/2], *ptr = abuf;
+ unsigned len = 0, alen;
+
+ sprintf(abuf, "scope=%d deref=%d", op->ors_scope, op->ors_deref);
+ Debug( LDAP_DEBUG_STATS,
+ "%s SRCH base=\"%s\" %s filter=\"%s\"\n",
+ op->o_log_prefix, op->o_req_dn.bv_val, abuf,
+ op->ors_filterstr.bv_val );
+
+ for ( i = 0; i<siz; i++ ) {
+ alen = op->ors_attrs[i].an_name.bv_len;
+ if (alen >= sizeof(abuf)) {
+ alen = sizeof(abuf)-1;
+ }
+ if (len && (len + 1 + alen >= sizeof(abuf))) {
+ Debug( LDAP_DEBUG_STATS, "%s SRCH attr=%s\n",
+ op->o_log_prefix, abuf );
+ len = 0;
+ ptr = abuf;
+ }
+ if (len) {
+ *ptr++ = ' ';
+ len++;
+ }
+ ptr = lutil_strncopy(ptr, op->ors_attrs[i].an_name.bv_val, alen);
+ len += alen;
+ *ptr = '\0';
+ }
+ if (len) {
+ Debug( LDAP_DEBUG_STATS, "%s SRCH attr=%s\n",
+ op->o_log_prefix, abuf );
+ }
+ }
+
+ op->o_bd = frontendDB;
+ rs->sr_err = frontendDB->be_search( op, rs );
+ if ( rs->sr_err == SLAPD_ASYNCOP ) {
+ /* skip cleanup */
+ return rs->sr_err;
+ }
+
+return_results:;
+ if ( !BER_BVISNULL( &op->o_req_dn ) ) {
+ slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op->ors_filterstr ) ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ if ( op->ors_filter != NULL) {
+ filter_free_x( op, op->ors_filter, 1 );
+ }
+ if ( op->ors_attrs != NULL ) {
+ op->o_tmpfree( op->ors_attrs, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+int
+fe_op_search( Operation *op, SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ if ( op->ors_scope == LDAP_SCOPE_BASE ) {
+ Entry *entry = NULL;
+
+ if ( BER_BVISEMPTY( &op->o_req_ndn ) ) {
+#ifdef LDAP_CONNECTIONLESS
+ /* Ignore LDAPv2 CLDAP Root DSE queries */
+ if (op->o_protocol == LDAP_VERSION2 && op->o_conn->c_is_udp) {
+ goto return_results;
+ }
+#endif
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto return_results;
+ }
+
+ rs->sr_err = root_dse_info( op->o_conn, &entry, &rs->sr_text );
+
+ } else if ( bvmatch( &op->o_req_ndn, &frontendDB->be_schemandn ) ) {
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto return_results;
+ }
+
+ rs->sr_err = schema_info( &entry, &rs->sr_text );
+ }
+
+ if( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto return_results;
+
+ } else if ( entry != NULL ) {
+ if ( get_assert( op ) &&
+ ( test_filter( op, entry, get_assertion( op )) != LDAP_COMPARE_TRUE )) {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto fail1;
+ }
+
+ rs->sr_err = test_filter( op, entry, op->ors_filter );
+
+ if( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ /* note: we set no limits because either
+ * no limit is specified, or at least 1
+ * is specified, and we're going to return
+ * at most one entry */
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+
+ rs->sr_entry = entry;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_flags = 0;
+ send_search_entry( op, rs );
+ rs->sr_entry = NULL;
+ rs->sr_operational_attrs = NULL;
+ }
+ rs->sr_err = LDAP_SUCCESS;
+fail1:
+ entry_free( entry );
+ send_ldap_result( op, rs );
+ goto return_results;
+ }
+ }
+
+ if( BER_BVISEMPTY( &op->o_req_ndn ) && !BER_BVISEMPTY( &default_search_nbase ) ) {
+ slap_sl_free( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ slap_sl_free( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+
+ ber_dupbv_x( &op->o_req_dn, &default_search_base, op->o_tmpmemctx );
+ ber_dupbv_x( &op->o_req_ndn, &default_search_nbase, op->o_tmpmemctx );
+ }
+
+ /*
+ * We could be serving multiple database backends. Select the
+ * appropriate one, or send a referral to our "referral server"
+ * if we don't hold it.
+ */
+
+ op->o_bd = select_backend( &op->o_req_ndn, 1 );
+ if ( op->o_bd == NULL ) {
+ rs->sr_ref = referral_rewrite( default_referral,
+ NULL, &op->o_req_dn, op->ors_scope );
+
+ if (!rs->sr_ref) rs->sr_ref = default_referral;
+ rs->sr_err = LDAP_REFERRAL;
+ op->o_bd = bd;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != default_referral)
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ goto return_results;
+ }
+
+ /* check restrictions */
+ if( backend_check_restrictions( op, rs, NULL ) != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ goto return_results;
+ }
+
+ /* check for referrals */
+ if( backend_check_referrals( op, rs ) != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ if ( SLAP_SHADOW(op->o_bd) && get_dontUseCopy(op) ) {
+ /* don't use shadow copy */
+ BerVarray defref = op->o_bd->be_update_refs
+ ? op->o_bd->be_update_refs : default_referral;
+
+ if( defref != NULL ) {
+ rs->sr_ref = referral_rewrite( defref,
+ NULL, &op->o_req_dn, op->ors_scope );
+ if( !rs->sr_ref) rs->sr_ref = defref;
+ rs->sr_err = LDAP_REFERRAL;
+ send_ldap_result( op, rs );
+
+ if (rs->sr_ref != defref) ber_bvarray_free( rs->sr_ref );
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "copy not used; no referral information available" );
+ }
+
+ } else if ( op->o_bd->be_search ) {
+ if ( limits_check( op, rs ) == 0 ) {
+ /* actually do the search and send the result(s) */
+ (op->o_bd->be_search)( op, rs );
+ }
+ /* else limits_check() sends error */
+
+ } else {
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported within namingContext" );
+ }
+
+return_results:;
+ op->o_bd = bd;
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/sets.c b/servers/slapd/sets.c
new file mode 100644
index 0000000..9ab2b36
--- /dev/null
+++ b/servers/slapd/sets.c
@@ -0,0 +1,832 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "sets.h"
+
+static BerVarray set_chase( SLAP_SET_GATHER gatherer,
+ SetCookie *cookie, BerVarray set, AttributeDescription *desc, int closure );
+
+/* Count the array members */
+static long
+slap_set_size( BerVarray set )
+{
+ long i = 0;
+
+ if ( set != NULL ) {
+ while ( !BER_BVISNULL( &set[ i ] ) ) {
+ i++;
+ }
+ }
+
+ return i;
+}
+
+/* Return 0 if there is at least one array member, non-zero otherwise */
+static int
+slap_set_isempty( BerVarray set )
+{
+ if ( set == NULL ) {
+ return 1;
+ }
+
+ if ( !BER_BVISNULL( &set[ 0 ] ) ) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Dispose of the contents of the array and the array itself according
+ * to the flags value. If SLAP_SET_REFVAL, don't dispose of values;
+ * if SLAP_SET_REFARR, don't dispose of the array itself. In case of
+ * binary operators, there are LEFT flags and RIGHT flags, referring to
+ * the first and the second operator arguments, respectively. In this
+ * case, flags must be transformed using macros SLAP_SET_LREF2REF() and
+ * SLAP_SET_RREF2REF() before calling this function.
+ */
+static void
+slap_set_dispose( SetCookie *cp, BerVarray set, unsigned flags )
+{
+ if ( flags & SLAP_SET_REFVAL ) {
+ if ( ! ( flags & SLAP_SET_REFARR ) ) {
+ cp->set_op->o_tmpfree( set, cp->set_op->o_tmpmemctx );
+ }
+
+ } else {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ }
+}
+
+/* Duplicate a set. If SLAP_SET_REFARR, is not set, the original array
+ * with the original values is returned, otherwise the array is duplicated;
+ * if SLAP_SET_REFVAL is set, also the values are duplicated.
+ */
+static BerVarray
+set_dup( SetCookie *cp, BerVarray set, unsigned flags )
+{
+ BerVarray newset = NULL;
+
+ if ( set == NULL ) {
+ return NULL;
+ }
+
+ if ( flags & SLAP_SET_REFARR ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ )
+ ;
+ newset = cp->set_op->o_tmpcalloc( i + 1,
+ sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( newset == NULL ) {
+ return NULL;
+ }
+
+ if ( flags & SLAP_SET_REFVAL ) {
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ ber_dupbv_x( &newset[ i ], &set[ i ],
+ cp->set_op->o_tmpmemctx );
+ }
+
+ } else {
+ AC_MEMCPY( newset, set, ( i + 1 ) * sizeof( struct berval ) );
+ }
+
+ } else {
+ newset = set;
+ }
+
+ return newset;
+}
+
+/* Join two sets according to operator op and flags op_flags.
+ * op can be:
+ * '|' (or): the union between the two sets is returned,
+ * eliminating duplicates
+ * '&' (and): the intersection between the two sets
+ * is returned
+ * '+' (add): the inner product of the two sets is returned,
+ * namely a set containing the concatenation of
+ * all combinations of the two sets members,
+ * except for duplicates.
+ * The two sets are disposed of according to the flags as described
+ * for slap_set_dispose().
+ */
+BerVarray
+slap_set_join(
+ SetCookie *cp,
+ BerVarray lset,
+ unsigned op_flags,
+ BerVarray rset )
+{
+ BerVarray set;
+ long i, j, last, rlast;
+ unsigned op = ( op_flags & SLAP_SET_OPMASK );
+
+ set = NULL;
+ switch ( op ) {
+ case '|': /* union */
+ if ( lset == NULL || BER_BVISNULL( &lset[ 0 ] ) ) {
+ if ( rset == NULL ) {
+ if ( lset == NULL ) {
+ set = cp->set_op->o_tmpcalloc( 1,
+ sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ BER_BVZERO( &set[ 0 ] );
+ goto done2;
+ }
+ set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) );
+ goto done2;
+ }
+ slap_set_dispose( cp, lset, SLAP_SET_LREF2REF( op_flags ) );
+ set = set_dup( cp, rset, SLAP_SET_RREF2REF( op_flags ) );
+ goto done2;
+ }
+ if ( rset == NULL || BER_BVISNULL( &rset[ 0 ] ) ) {
+ slap_set_dispose( cp, rset, SLAP_SET_RREF2REF( op_flags ) );
+ set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) );
+ goto done2;
+ }
+
+ /* worst scenario: no duplicates */
+ rlast = slap_set_size( rset );
+ i = slap_set_size( lset ) + rlast + 1;
+ set = cp->set_op->o_tmpcalloc( i, sizeof( struct berval ), cp->set_op->o_tmpmemctx );
+ if ( set != NULL ) {
+ /* set_chase() depends on this routine to
+ * keep the first elements of the result
+ * set the same (and in the same order)
+ * as the left-set.
+ */
+ for ( i = 0; !BER_BVISNULL( &lset[ i ] ); i++ ) {
+ if ( op_flags & SLAP_SET_LREFVAL ) {
+ ber_dupbv_x( &set[ i ], &lset[ i ], cp->set_op->o_tmpmemctx );
+
+ } else {
+ set[ i ] = lset[ i ];
+ }
+ }
+
+ /* pointers to values have been used in set - don't free twice */
+ op_flags |= SLAP_SET_LREFVAL;
+
+ last = i;
+
+ for ( i = 0; !BER_BVISNULL( &rset[ i ] ); i++ ) {
+ int exists = 0;
+
+ for ( j = 0; !BER_BVISNULL( &set[ j ] ); j++ ) {
+ if ( bvmatch( &rset[ i ], &set[ j ] ) )
+ {
+ if ( !( op_flags & SLAP_SET_RREFVAL ) ) {
+ cp->set_op->o_tmpfree( rset[ i ].bv_val, cp->set_op->o_tmpmemctx );
+ rset[ i ] = rset[ --rlast ];
+ BER_BVZERO( &rset[ rlast ] );
+ i--;
+ }
+ exists = 1;
+ break;
+ }
+ }
+
+ if ( !exists ) {
+ if ( op_flags & SLAP_SET_RREFVAL ) {
+ ber_dupbv_x( &set[ last ], &rset[ i ], cp->set_op->o_tmpmemctx );
+
+ } else {
+ set[ last ] = rset[ i ];
+ }
+ last++;
+ }
+ }
+
+ /* pointers to values have been used in set - don't free twice */
+ op_flags |= SLAP_SET_RREFVAL;
+
+ BER_BVZERO( &set[ last ] );
+ }
+ break;
+
+ case '&': /* intersection */
+ if ( lset == NULL || BER_BVISNULL( &lset[ 0 ] )
+ || rset == NULL || BER_BVISNULL( &rset[ 0 ] ) )
+ {
+ set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ BER_BVZERO( &set[ 0 ] );
+ break;
+
+ } else {
+ long llen, rlen;
+ BerVarray sset;
+
+ llen = slap_set_size( lset );
+ rlen = slap_set_size( rset );
+
+ /* dup the shortest */
+ if ( llen < rlen ) {
+ last = llen;
+ set = set_dup( cp, lset, SLAP_SET_LREF2REF( op_flags ) );
+ lset = NULL;
+ sset = rset;
+
+ } else {
+ last = rlen;
+ set = set_dup( cp, rset, SLAP_SET_RREF2REF( op_flags ) );
+ rset = NULL;
+ sset = lset;
+ }
+
+ if ( set == NULL ) {
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ for ( j = 0; !BER_BVISNULL( &sset[ j ] ); j++ ) {
+ if ( bvmatch( &set[ i ], &sset[ j ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &sset[ j ] ) ) {
+ cp->set_op->o_tmpfree( set[ i ].bv_val, cp->set_op->o_tmpmemctx );
+ set[ i ] = set[ --last ];
+ BER_BVZERO( &set[ last ] );
+ i--;
+ }
+ }
+ }
+ break;
+
+ case '+': /* string concatenation */
+ i = slap_set_size( rset );
+ j = slap_set_size( lset );
+
+ /* handle empty set cases */
+ if ( i == 0 || j == 0 ) {
+ set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set == NULL ) {
+ break;
+ }
+ BER_BVZERO( &set[ 0 ] );
+ break;
+ }
+
+ set = cp->set_op->o_tmpcalloc( i * j + 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set == NULL ) {
+ break;
+ }
+
+ for ( last = 0, i = 0; !BER_BVISNULL( &lset[ i ] ); i++ ) {
+ for ( j = 0; !BER_BVISNULL( &rset[ j ] ); j++ ) {
+ struct berval bv;
+ long k;
+
+ /* don't concatenate with the empty string */
+ if ( BER_BVISEMPTY( &lset[ i ] ) ) {
+ ber_dupbv_x( &bv, &rset[ j ], cp->set_op->o_tmpmemctx );
+ if ( bv.bv_val == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ set = NULL;
+ goto done;
+ }
+
+ } else if ( BER_BVISEMPTY( &rset[ j ] ) ) {
+ ber_dupbv_x( &bv, &lset[ i ], cp->set_op->o_tmpmemctx );
+ if ( bv.bv_val == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ set = NULL;
+ goto done;
+ }
+
+ } else {
+ bv.bv_len = lset[ i ].bv_len + rset[ j ].bv_len;
+ bv.bv_val = cp->set_op->o_tmpalloc( bv.bv_len + 1,
+ cp->set_op->o_tmpmemctx );
+ if ( bv.bv_val == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ set = NULL;
+ goto done;
+ }
+ AC_MEMCPY( bv.bv_val, lset[ i ].bv_val, lset[ i ].bv_len );
+ AC_MEMCPY( &bv.bv_val[ lset[ i ].bv_len ], rset[ j ].bv_val, rset[ j ].bv_len );
+ bv.bv_val[ bv.bv_len ] = '\0';
+ }
+
+ for ( k = 0; k < last; k++ ) {
+ if ( bvmatch( &set[ k ], &bv ) ) {
+ cp->set_op->o_tmpfree( bv.bv_val, cp->set_op->o_tmpmemctx );
+ break;
+ }
+ }
+
+ if ( k == last ) {
+ set[ last++ ] = bv;
+ }
+ }
+ }
+ BER_BVZERO( &set[ last ] );
+ break;
+
+ default:
+ break;
+ }
+
+done:;
+ if ( lset ) slap_set_dispose( cp, lset, SLAP_SET_LREF2REF( op_flags ) );
+ if ( rset ) slap_set_dispose( cp, rset, SLAP_SET_RREF2REF( op_flags ) );
+
+done2:;
+ if ( LogTest( LDAP_DEBUG_ACL ) ) {
+ if ( !set || BER_BVISNULL( set ) ) {
+ Debug( LDAP_DEBUG_ACL, " ACL set: empty\n" );
+
+ } else {
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ Debug( LDAP_DEBUG_ACL, " ACL set[%ld]=%s\n", i, set[i].bv_val );
+ }
+ }
+ }
+
+ return set;
+}
+
+static BerVarray
+set_chase( SLAP_SET_GATHER gatherer,
+ SetCookie *cp, BerVarray set, AttributeDescription *desc, int closure )
+{
+ BerVarray vals, nset;
+ int i;
+
+ if ( set == NULL ) {
+ set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set != NULL ) {
+ BER_BVZERO( &set[ 0 ] );
+ }
+ return set;
+ }
+
+ if ( BER_BVISNULL( set ) ) {
+ return set;
+ }
+
+ nset = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx );
+ if ( nset == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ return NULL;
+ }
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ vals = gatherer( cp, &set[ i ], desc );
+ if ( vals != NULL ) {
+ nset = slap_set_join( cp, nset, '|', vals );
+ }
+ }
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+
+ if ( closure ) {
+ for ( i = 0; !BER_BVISNULL( &nset[ i ] ); i++ ) {
+ vals = gatherer( cp, &nset[ i ], desc );
+ if ( vals != NULL ) {
+ nset = slap_set_join( cp, nset, '|', vals );
+ if ( nset == NULL ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return nset;
+}
+
+
+static BerVarray
+set_parents( SetCookie *cp, BerVarray set )
+{
+ int i, j, last;
+ struct berval bv, pbv;
+ BerVarray nset, vals;
+
+ if ( set == NULL ) {
+ set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set != NULL ) {
+ BER_BVZERO( &set[ 0 ] );
+ }
+ return set;
+ }
+
+ if ( BER_BVISNULL( &set[ 0 ] ) ) {
+ return set;
+ }
+
+ nset = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx );
+ if ( nset == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ return NULL;
+ }
+
+ BER_BVZERO( &nset[ 0 ] );
+
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ int level = 1;
+
+ pbv = bv = set[ i ];
+ for ( ; !BER_BVISEMPTY( &pbv ); dnParent( &bv, &pbv ) ) {
+ level++;
+ bv = pbv;
+ }
+
+ vals = cp->set_op->o_tmpcalloc( level + 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx );
+ if ( vals == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ ber_bvarray_free_x( nset, cp->set_op->o_tmpmemctx );
+ return NULL;
+ }
+ BER_BVZERO( &vals[ 0 ] );
+ last = 0;
+
+ bv = set[ i ];
+ for ( j = 0 ; j < level ; j++ ) {
+ ber_dupbv_x( &vals[ last ], &bv, cp->set_op->o_tmpmemctx );
+ last++;
+ dnParent( &bv, &bv );
+ }
+ BER_BVZERO( &vals[ last ] );
+
+ nset = slap_set_join( cp, nset, '|', vals );
+ }
+
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+
+ return nset;
+}
+
+
+
+static BerVarray
+set_parent( SetCookie *cp, BerVarray set, int level )
+{
+ int i, j, last;
+ struct berval bv;
+ BerVarray nset;
+
+ if ( set == NULL ) {
+ set = cp->set_op->o_tmpcalloc( 1, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set != NULL ) {
+ BER_BVZERO( &set[ 0 ] );
+ }
+ return set;
+ }
+
+ if ( BER_BVISNULL( &set[ 0 ] ) ) {
+ return set;
+ }
+
+ nset = cp->set_op->o_tmpcalloc( slap_set_size( set ) + 1, sizeof( struct berval ), cp->set_op->o_tmpmemctx );
+ if ( nset == NULL ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ return NULL;
+ }
+
+ BER_BVZERO( &nset[ 0 ] );
+ last = 0;
+
+ for ( i = 0; !BER_BVISNULL( &set[ i ] ); i++ ) {
+ bv = set[ i ];
+
+ for ( j = 0 ; j < level ; j++ ) {
+ dnParent( &bv, &bv );
+ }
+
+ for ( j = 0; !BER_BVISNULL( &nset[ j ] ); j++ ) {
+ if ( bvmatch( &bv, &nset[ j ] ) )
+ {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &nset[ j ] ) ) {
+ ber_dupbv_x( &nset[ last ], &bv, cp->set_op->o_tmpmemctx );
+ last++;
+ }
+ }
+
+ BER_BVZERO( &nset[ last ] );
+
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+
+ return nset;
+}
+
+int
+slap_set_filter( SLAP_SET_GATHER gatherer,
+ SetCookie *cp, struct berval *fbv,
+ struct berval *user, struct berval *target, BerVarray *results )
+{
+#define STACK_SIZE 64
+#define IS_SET(x) ( (unsigned long)(x) >= 256 )
+#define IS_OP(x) ( (unsigned long)(x) < 256 )
+#define SF_ERROR(x) do { rc = -1; goto _error; } while ( 0 )
+#define SF_TOP() ( (BerVarray)( ( stp < 0 ) ? 0 : stack[ stp ] ) )
+#define SF_POP() ( (BerVarray)( ( stp < 0 ) ? 0 : stack[ stp-- ] ) )
+#define SF_PUSH(x) do { \
+ if ( stp >= ( STACK_SIZE - 1 ) ) SF_ERROR( overflow ); \
+ stack[ ++stp ] = (BerVarray)(long)(x); \
+ } while ( 0 )
+
+ BerVarray set, lset;
+ BerVarray stack[ STACK_SIZE ] = { 0 };
+ int len, rc, stp;
+ unsigned long op;
+ char c, *filter = fbv->bv_val;
+
+ if ( results ) {
+ *results = NULL;
+ }
+
+ stp = -1;
+ while ( ( c = *filter++ ) ) {
+ set = NULL;
+ switch ( c ) {
+ case ' ':
+ case '\t':
+ case '\x0A':
+ case '\x0D':
+ break;
+
+ case '(' /* ) */ :
+ if ( IS_SET( SF_TOP() ) ) {
+ SF_ERROR( syntax );
+ }
+ SF_PUSH( c );
+ break;
+
+ case /* ( */ ')':
+ set = SF_POP();
+ if ( IS_OP( set ) ) {
+ SF_ERROR( syntax );
+ }
+ if ( SF_TOP() == (void *)'(' /* ) */ ) {
+ SF_POP();
+ SF_PUSH( set );
+ set = NULL;
+
+ } else if ( IS_OP( SF_TOP() ) ) {
+ op = (unsigned long)SF_POP();
+ lset = SF_POP();
+ SF_POP();
+ set = slap_set_join( cp, lset, op, set );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ SF_PUSH( set );
+ set = NULL;
+
+ } else {
+ SF_ERROR( syntax );
+ }
+ break;
+
+ case '|': /* union */
+ case '&': /* intersection */
+ case '+': /* string concatenation */
+ set = SF_POP();
+ if ( IS_OP( set ) ) {
+ SF_ERROR( syntax );
+ }
+ if ( SF_TOP() == 0 || SF_TOP() == (void *)'(' /* ) */ ) {
+ SF_PUSH( set );
+ set = NULL;
+
+ } else if ( IS_OP( SF_TOP() ) ) {
+ op = (unsigned long)SF_POP();
+ lset = SF_POP();
+ set = slap_set_join( cp, lset, op, set );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ SF_PUSH( set );
+ set = NULL;
+
+ } else {
+ SF_ERROR( syntax );
+ }
+ SF_PUSH( c );
+ break;
+
+ case '[' /* ] */:
+ if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) {
+ SF_ERROR( syntax );
+ }
+ for ( len = 0; ( c = *filter++ ) && ( c != /* [ */ ']' ); len++ )
+ ;
+ if ( c == 0 ) {
+ SF_ERROR( syntax );
+ }
+
+ set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ set->bv_val = cp->set_op->o_tmpcalloc( len + 1, sizeof( char ),
+ cp->set_op->o_tmpmemctx );
+ if ( BER_BVISNULL( set ) ) {
+ SF_ERROR( memory );
+ }
+ AC_MEMCPY( set->bv_val, &filter[ - len - 1 ], len );
+ set->bv_len = len;
+ SF_PUSH( set );
+ set = NULL;
+ break;
+
+ case '-':
+ if ( ( SF_TOP() == (void *)'/' )
+ && ( *filter == '*' || ASCII_DIGIT( *filter ) ) )
+ {
+ SF_POP();
+
+ if ( *filter == '*' ) {
+ set = set_parents( cp, SF_POP() );
+ filter++;
+
+ } else {
+ char *next = NULL;
+ long parent = strtol( filter, &next, 10 );
+
+ if ( next == filter ) {
+ SF_ERROR( syntax );
+ }
+
+ set = SF_POP();
+ if ( parent != 0 ) {
+ set = set_parent( cp, set, parent );
+ }
+ filter = next;
+ }
+
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+
+ SF_PUSH( set );
+ set = NULL;
+ break;
+ } else {
+ c = *filter++;
+ if ( c != '>' ) {
+ SF_ERROR( syntax );
+ }
+ /* fall through to next case */
+ }
+
+ case '/':
+ if ( IS_OP( SF_TOP() ) ) {
+ SF_ERROR( syntax );
+ }
+ SF_PUSH( '/' );
+ break;
+
+ default:
+ if ( !AD_LEADCHAR( c ) ) {
+ SF_ERROR( syntax );
+ }
+ filter--;
+ for ( len = 1;
+ ( c = filter[ len ] ) && AD_CHAR( c );
+ len++ )
+ {
+ /* count */
+ if ( c == '-' && !AD_CHAR( filter[ len + 1 ] ) ) {
+ break;
+ }
+ }
+ if ( len == 4
+ && memcmp( "this", filter, len ) == 0 )
+ {
+ assert( !BER_BVISNULL( target ) );
+ if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) {
+ SF_ERROR( syntax );
+ }
+ set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ ber_dupbv_x( set, target, cp->set_op->o_tmpmemctx );
+ if ( BER_BVISNULL( set ) ) {
+ SF_ERROR( memory );
+ }
+ BER_BVZERO( &set[ 1 ] );
+
+ } else if ( len == 4
+ && memcmp( "user", filter, len ) == 0 )
+ {
+ if ( ( SF_TOP() == (void *)'/' ) || IS_SET( SF_TOP() ) ) {
+ SF_ERROR( syntax );
+ }
+ if ( BER_BVISNULL( user ) ) {
+ SF_ERROR( memory );
+ }
+ set = cp->set_op->o_tmpcalloc( 2, sizeof( struct berval ),
+ cp->set_op->o_tmpmemctx );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ ber_dupbv_x( set, user, cp->set_op->o_tmpmemctx );
+ BER_BVZERO( &set[ 1 ] );
+
+ } else if ( SF_TOP() != (void *)'/' ) {
+ SF_ERROR( syntax );
+
+ } else {
+ struct berval fb2;
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+
+ SF_POP();
+ fb2.bv_val = filter;
+ fb2.bv_len = len;
+
+ if ( slap_bv2ad( &fb2, &ad, &text ) != LDAP_SUCCESS ) {
+ SF_ERROR( syntax );
+ }
+
+ /* NOTE: ad must have distinguishedName syntax
+ * or expand in an LDAP URI if c == '*'
+ */
+
+ set = set_chase( gatherer,
+ cp, SF_POP(), ad, c == '*' );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+ if ( c == '*' ) {
+ len++;
+ }
+ }
+ filter += len;
+ SF_PUSH( set );
+ set = NULL;
+ break;
+ }
+ }
+
+ set = SF_POP();
+ if ( IS_OP( set ) ) {
+ SF_ERROR( syntax );
+ }
+ if ( SF_TOP() == 0 ) {
+ /* FIXME: ok ? */ ;
+
+ } else if ( IS_OP( SF_TOP() ) ) {
+ op = (unsigned long)SF_POP();
+ lset = SF_POP();
+ set = slap_set_join( cp, lset, op, set );
+ if ( set == NULL ) {
+ SF_ERROR( memory );
+ }
+
+ } else {
+ SF_ERROR( syntax );
+ }
+
+ rc = slap_set_isempty( set ) ? 0 : 1;
+ if ( results ) {
+ *results = set;
+ set = NULL;
+ }
+
+_error:
+ if ( IS_SET( set ) ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ }
+ while ( ( set = SF_POP() ) ) {
+ if ( IS_SET( set ) ) {
+ ber_bvarray_free_x( set, cp->set_op->o_tmpmemctx );
+ }
+ }
+ return rc;
+}
diff --git a/servers/slapd/sets.h b/servers/slapd/sets.h
new file mode 100644
index 0000000..c2b7673
--- /dev/null
+++ b/servers/slapd/sets.h
@@ -0,0 +1,75 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef SLAP_SETS_H_
+#define SLAP_SETS_H_
+
+#include <ldap_cdefs.h>
+
+LDAP_BEGIN_DECL
+
+typedef struct slap_set_cookie {
+ Operation *set_op;
+} SetCookie;
+
+/* this routine needs to return the bervals instead of
+ * plain strings, since syntax is not known. It should
+ * also return the syntax or some "comparison cookie"
+ * that is used by set_filter.
+ */
+typedef BerVarray (SLAP_SET_GATHER)( SetCookie *cookie,
+ struct berval *name, AttributeDescription *ad);
+
+LDAP_SLAPD_F (int) slap_set_filter(
+ SLAP_SET_GATHER gatherer,
+ SetCookie *cookie, struct berval *filter,
+ struct berval *user, struct berval *target, BerVarray *results);
+
+LDAP_SLAPD_F (BerVarray) slap_set_join(SetCookie *cp,
+ BerVarray lset, unsigned op, BerVarray rset);
+
+#define SLAP_SET_OPMASK 0x00FF
+
+#define SLAP_SET_REFARR 0x0100
+#define SLAP_SET_REFVAL 0x0200
+#define SLAP_SET_REF (SLAP_SET_REFARR|SLAP_SET_REFVAL)
+
+/* The unsigned "op" can be ORed with the flags below;
+ * - if the rset's values must not be freed, or must be copied if kept,
+ * it is ORed with SLAP_SET_RREFVAL
+ * - if the rset array must not be freed, or must be copied if kept,
+ * it is ORed with SLAP_SET_RREFARR
+ * - the same applies to the lset with SLAP_SET_LREFVAL and SLAP_SET_LREFARR
+ * - it is assumed that SLAP_SET_REFVAL implies SLAP_SET_REFARR,
+ * i.e. the former is checked only if the latter is set.
+ */
+
+#define SLAP_SET_RREFARR SLAP_SET_REFARR
+#define SLAP_SET_RREFVAL SLAP_SET_REFVAL
+#define SLAP_SET_RREF SLAP_SET_REF
+#define SLAP_SET_RREFMASK 0x0F00
+
+#define SLAP_SET_RREF2REF(r) ((r) & SLAP_SET_RREFMASK)
+
+#define SLAP_SET_LREFARR 0x1000
+#define SLAP_SET_LREFVAL 0x2000
+#define SLAP_SET_LREF (SLAP_SET_LREFARR|SLAP_SET_LREFVAL)
+#define SLAP_SET_LREFMASK 0xF000
+
+#define SLAP_SET_LREF2REF(r) (((r) & SLAP_SET_LREFMASK) >> 4)
+
+LDAP_END_DECL
+
+#endif
diff --git a/servers/slapd/sl_malloc.c b/servers/slapd/sl_malloc.c
new file mode 100644
index 0000000..9765bc3
--- /dev/null
+++ b/servers/slapd/sl_malloc.c
@@ -0,0 +1,739 @@
+/* sl_malloc.c - malloc routines using a per-thread slab */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+
+#ifdef USE_VALGRIND
+/* Get debugging help from Valgrind */
+#include <valgrind/memcheck.h>
+#define VGMEMP_MARK(m,s) VALGRIND_MAKE_MEM_NOACCESS(m,s)
+#define VGMEMP_CREATE(h,r,z) VALGRIND_CREATE_MEMPOOL(h,r,z)
+#define VGMEMP_TRIM(h,a,s) VALGRIND_MEMPOOL_TRIM(h,a,s)
+#define VGMEMP_ALLOC(h,a,s) VALGRIND_MEMPOOL_ALLOC(h,a,s)
+#define VGMEMP_CHANGE(h,a,b,s) VALGRIND_MEMPOOL_CHANGE(h,a,b,s)
+#else
+#define VGMEMP_MARK(m,s)
+#define VGMEMP_CREATE(h,r,z)
+#define VGMEMP_TRIM(h,a,s)
+#define VGMEMP_ALLOC(h,a,s)
+#define VGMEMP_CHANGE(h,a,b,s)
+#endif
+
+/*
+ * This allocator returns temporary memory from a slab in a given memory
+ * context, aligned on a 2-int boundary. It cannot be used for data
+ * which will outlive the task allocating it.
+ *
+ * A new memory context attaches to the creator's thread context, if any.
+ * Threads cannot use other threads' memory contexts; there are no locks.
+ *
+ * The caller of slap_sl_malloc, usually a thread pool task, must
+ * slap_sl_free the memory before finishing: New tasks reuse the context
+ * and normally reset it, reclaiming memory left over from last task.
+ *
+ * The allocator helps memory fragmentation, speed and memory leaks.
+ * It is not (yet) reliable as a garbage collector:
+ *
+ * It falls back to context NULL - plain ber_memalloc() - when the
+ * context's slab is full. A reset does not reclaim such memory.
+ * Conversely, free/realloc of data not from the given context assumes
+ * context NULL. The data must not belong to another memory context.
+ *
+ * Code which has lost track of the current memory context can try
+ * slap_sl_context() or ch_malloc.c:ch_free/ch_realloc().
+ *
+ * Allocations cannot yet return failure. Like ch_malloc, they succeed
+ * or abort slapd. This will change, do fix code which assumes success.
+ */
+
+/*
+ * The stack-based allocator stores (ber_len_t)sizeof(head+block) at
+ * allocated blocks' head - and in freed blocks also at the tail, marked
+ * by ORing *next* block's head with 1. Freed blocks are only reclaimed
+ * from the last block forward. This is fast, but when a block is never
+ * freed, older blocks will not be reclaimed until the slab is reset...
+ */
+
+#ifdef SLAP_NO_SL_MALLOC /* Useful with memory debuggers like Valgrind */
+enum { No_sl_malloc = 1 };
+#else
+enum { No_sl_malloc = 0 };
+#endif
+
+#define SLAP_SLAB_SOBLOCK 64
+
+struct slab_object {
+ void *so_ptr;
+ int so_blockhead;
+ LDAP_LIST_ENTRY(slab_object) so_link;
+};
+
+struct slab_heap {
+ void *sh_base;
+ void *sh_last;
+ void *sh_end;
+ int sh_stack;
+ int sh_maxorder;
+ unsigned char **sh_map;
+ LDAP_LIST_HEAD(sh_freelist, slab_object) *sh_free;
+ LDAP_LIST_HEAD(sh_so, slab_object) sh_sopool;
+};
+
+enum {
+ Align = sizeof(ber_len_t) > 2*sizeof(int)
+ ? sizeof(ber_len_t) : 2*sizeof(int),
+ Align_log2 = 1 + (Align>2) + (Align>4) + (Align>8) + (Align>16),
+ order_start = Align_log2 - 1,
+ pad = Align - 1
+};
+
+static struct slab_object * slap_replenish_sopool(struct slab_heap* sh);
+#ifdef SLAPD_UNUSED
+static void print_slheap(int level, void *ctx);
+#endif
+
+/* Keep memory context in a thread-local var */
+# define memctx_key ((void *) slap_sl_mem_init)
+# define SET_MEMCTX(thrctx, memctx, kfree) \
+ ldap_pvt_thread_pool_setkey(thrctx,memctx_key, memctx,kfree, NULL,NULL)
+# define GET_MEMCTX(thrctx, memctxp) \
+ ((void) (*(memctxp) = NULL), \
+ (void) ldap_pvt_thread_pool_getkey(thrctx,memctx_key, memctxp,NULL), \
+ *(memctxp))
+
+/* Destroy the context, or if key==NULL clean it up for reuse. */
+void
+slap_sl_mem_destroy(
+ void *key,
+ void *data
+)
+{
+ struct slab_heap *sh = data;
+ struct slab_object *so;
+ int i;
+
+ if (!sh)
+ return;
+
+ if (!sh->sh_stack) {
+ for (i = 0; i <= sh->sh_maxorder - order_start; i++) {
+ so = LDAP_LIST_FIRST(&sh->sh_free[i]);
+ while (so) {
+ struct slab_object *so_tmp = so;
+ so = LDAP_LIST_NEXT(so, so_link);
+ LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_tmp, so_link);
+ }
+ ch_free(sh->sh_map[i]);
+ }
+ ch_free(sh->sh_free);
+ ch_free(sh->sh_map);
+
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ while (so) {
+ struct slab_object *so_tmp = so;
+ so = LDAP_LIST_NEXT(so, so_link);
+ if (!so_tmp->so_blockhead) {
+ LDAP_LIST_REMOVE(so_tmp, so_link);
+ }
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ while (so) {
+ struct slab_object *so_tmp = so;
+ so = LDAP_LIST_NEXT(so, so_link);
+ ch_free(so_tmp);
+ }
+ }
+
+ if (key != NULL) {
+ ber_memfree_x(sh->sh_base, NULL);
+ ber_memfree_x(sh, NULL);
+ }
+}
+
+BerMemoryFunctions slap_sl_mfuncs =
+ { slap_sl_malloc, slap_sl_calloc, slap_sl_realloc, slap_sl_free };
+
+void
+slap_sl_mem_init()
+{
+ assert( Align == 1 << Align_log2 );
+
+ ber_set_option( NULL, LBER_OPT_MEMORY_FNS, &slap_sl_mfuncs );
+}
+
+/* Create, reset or just return the memory context of the current thread. */
+void *
+slap_sl_mem_create(
+ ber_len_t size,
+ int stack,
+ void *thrctx,
+ int new
+)
+{
+ void *memctx;
+ struct slab_heap *sh;
+ ber_len_t size_shift;
+ struct slab_object *so;
+ char *base, *newptr;
+ enum { Base_offset = (unsigned) -sizeof(ber_len_t) % Align };
+
+ sh = GET_MEMCTX(thrctx, &memctx);
+ if ( sh && !new )
+ return sh;
+
+ /* Round up to doubleword boundary, then make room for initial
+ * padding, preserving expected available size for pool version */
+ size = ((size + Align-1) & -Align) + Base_offset;
+
+ if (!sh) {
+ sh = ch_malloc(sizeof(struct slab_heap));
+ base = ch_malloc(size);
+ SET_MEMCTX(thrctx, sh, slap_sl_mem_destroy);
+ VGMEMP_MARK(base, size);
+ VGMEMP_CREATE(sh, 0, 0);
+ } else {
+ slap_sl_mem_destroy(NULL, sh);
+ base = sh->sh_base;
+ if (size > (ber_len_t) ((char *) sh->sh_end - base)) {
+ newptr = ch_realloc(base, size);
+ if ( newptr == NULL ) return NULL;
+ VGMEMP_CHANGE(sh, base, newptr, size);
+ base = newptr;
+ }
+ VGMEMP_TRIM(sh, base, 0);
+ }
+ sh->sh_base = base;
+ sh->sh_end = base + size;
+
+ /* Align (base + head of first block) == first returned block */
+ base += Base_offset;
+ size -= Base_offset;
+
+ sh->sh_stack = stack;
+ if (stack) {
+ sh->sh_last = base;
+
+ } else {
+ int i, order = -1, order_end = -1;
+
+ size_shift = size - 1;
+ do {
+ order_end++;
+ } while (size_shift >>= 1);
+ order = order_end - order_start + 1;
+ sh->sh_maxorder = order_end;
+
+ sh->sh_free = (struct sh_freelist *)
+ ch_malloc(order * sizeof(struct sh_freelist));
+ for (i = 0; i < order; i++) {
+ LDAP_LIST_INIT(&sh->sh_free[i]);
+ }
+
+ LDAP_LIST_INIT(&sh->sh_sopool);
+
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so, so_link);
+ so->so_ptr = base;
+
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[order-1], so, so_link);
+
+ sh->sh_map = (unsigned char **)
+ ch_malloc(order * sizeof(unsigned char *));
+ for (i = 0; i < order; i++) {
+ int shiftamt = order_start + 1 + i;
+ int nummaps = size >> shiftamt;
+ assert(nummaps);
+ nummaps >>= 3;
+ if (!nummaps) nummaps = 1;
+ sh->sh_map[i] = (unsigned char *) ch_malloc(nummaps);
+ memset(sh->sh_map[i], 0, nummaps);
+ }
+ }
+
+ return sh;
+}
+
+/*
+ * Assign memory context to thread context. Use NULL to detach
+ * current memory context from thread. Future users must
+ * know the context, since ch_free/slap_sl_context() cannot find it.
+ */
+void
+slap_sl_mem_setctx(
+ void *thrctx,
+ void *memctx
+)
+{
+ SET_MEMCTX(thrctx, memctx, slap_sl_mem_destroy);
+}
+
+void *
+slap_sl_malloc(
+ ber_len_t size,
+ void *ctx
+)
+{
+ struct slab_heap *sh = ctx;
+ ber_len_t *ptr, *newptr;
+
+ /* ber_set_option calls us like this */
+ if (No_sl_malloc || !ctx) {
+ newptr = ber_memalloc_x( size, NULL );
+ if ( newptr ) return newptr;
+ Debug(LDAP_DEBUG_ANY, "slap_sl_malloc of %lu bytes failed\n",
+ (unsigned long) size );
+ assert( 0 );
+ exit( EXIT_FAILURE );
+ }
+
+ /* Add room for head, ensure room for tail when freed, and
+ * round up to doubleword boundary. */
+ size = (size + sizeof(ber_len_t) + Align-1 + !size) & -Align;
+
+ if (sh->sh_stack) {
+ if (size < (ber_len_t) ((char *) sh->sh_end - (char *) sh->sh_last)) {
+ newptr = sh->sh_last;
+ sh->sh_last = (char *) sh->sh_last + size;
+ VGMEMP_ALLOC(sh, newptr, size);
+ *newptr++ = size;
+ return( (void *)newptr );
+ }
+
+ size -= sizeof(ber_len_t);
+
+ } else {
+ struct slab_object *so_new, *so_left, *so_right;
+ ber_len_t size_shift;
+ unsigned long diff;
+ int i, j, order = -1;
+
+ size_shift = size - 1;
+ do {
+ order++;
+ } while (size_shift >>= 1);
+
+ size -= sizeof(ber_len_t);
+
+ for (i = order; i <= sh->sh_maxorder &&
+ LDAP_LIST_EMPTY(&sh->sh_free[i-order_start]); i++);
+
+ if (i == order) {
+ so_new = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]);
+ LDAP_LIST_REMOVE(so_new, so_link);
+ ptr = so_new->so_ptr;
+ diff = (unsigned long)((char*)ptr -
+ (char*)sh->sh_base) >> (order + 1);
+ sh->sh_map[order-order_start][diff>>3] |= (1 << (diff & 0x7));
+ *ptr++ = size;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_new, so_link);
+ return((void*)ptr);
+ } else if (i <= sh->sh_maxorder) {
+ for (j = i; j > order; j--) {
+ so_left = LDAP_LIST_FIRST(&sh->sh_free[j-order_start]);
+ LDAP_LIST_REMOVE(so_left, so_link);
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so_right = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so_right, so_link);
+ so_right->so_ptr = (void *)((char *)so_left->so_ptr + (1 << j));
+ if (j == order + 1) {
+ ptr = so_left->so_ptr;
+ diff = (unsigned long)((char*)ptr -
+ (char*)sh->sh_base) >> (order+1);
+ sh->sh_map[order-order_start][diff>>3] |=
+ (1 << (diff & 0x7));
+ *ptr++ = size;
+ LDAP_LIST_INSERT_HEAD(
+ &sh->sh_free[j-1-order_start], so_right, so_link);
+ LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, so_left, so_link);
+ return((void*)ptr);
+ } else {
+ LDAP_LIST_INSERT_HEAD(
+ &sh->sh_free[j-1-order_start], so_right, so_link);
+ LDAP_LIST_INSERT_HEAD(
+ &sh->sh_free[j-1-order_start], so_left, so_link);
+ }
+ }
+ }
+ /* FIXME: missing return; guessing we failed... */
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "sl_malloc %lu: ch_malloc\n",
+ (unsigned long) size );
+ return ch_malloc(size);
+}
+
+#define LIM_SQRT(t) /* some value < sqrt(max value of unsigned type t) */ \
+ ((0UL|(t)-1) >>31>>31 > 1 ? ((t)1 <<32) - 1 : \
+ (0UL|(t)-1) >>31 ? 65535U : (0UL|(t)-1) >>15 ? 255U : 15U)
+
+void *
+slap_sl_calloc( ber_len_t n, ber_len_t size, void *ctx )
+{
+ void *newptr;
+ ber_len_t total = n * size;
+
+ /* The sqrt test is a slight optimization: often avoids the division */
+ if ((n | size) <= LIM_SQRT(ber_len_t) || n == 0 || total/n == size) {
+ newptr = slap_sl_malloc( total, ctx );
+ memset( newptr, 0, n*size );
+ } else {
+ Debug(LDAP_DEBUG_ANY, "slap_sl_calloc(%lu,%lu) out of range\n",
+ (unsigned long) n, (unsigned long) size );
+ assert(0);
+ exit(EXIT_FAILURE);
+ }
+ return newptr;
+}
+
+void *
+slap_sl_realloc(void *ptr, ber_len_t size, void *ctx)
+{
+ struct slab_heap *sh = ctx;
+ ber_len_t oldsize, *p = (ber_len_t *) ptr, *nextp;
+ void *newptr;
+
+ if (ptr == NULL)
+ return slap_sl_malloc(size, ctx);
+
+ /* Not our memory? */
+ if (No_sl_malloc || !sh || ptr < sh->sh_base || ptr >= sh->sh_end) {
+ /* Like ch_realloc(), except not trying a new context */
+ newptr = ber_memrealloc_x(ptr, size, NULL);
+ if (newptr) {
+ return newptr;
+ }
+ Debug(LDAP_DEBUG_ANY, "slap_sl_realloc of %lu bytes failed\n",
+ (unsigned long) size );
+ assert(0);
+ exit( EXIT_FAILURE );
+ }
+
+ if (size == 0) {
+ slap_sl_free(ptr, ctx);
+ return NULL;
+ }
+
+ oldsize = p[-1];
+
+ if (sh->sh_stack) {
+ /* Add room for head, round up to doubleword boundary */
+ size = (size + sizeof(ber_len_t) + Align-1) & -Align;
+
+ p--;
+
+ /* Never shrink blocks */
+ if (size <= oldsize) {
+ return ptr;
+ }
+
+ oldsize &= -2;
+ nextp = (ber_len_t *) ((char *) p + oldsize);
+
+ /* If reallocing the last block, try to grow it */
+ if (nextp == sh->sh_last) {
+ if (size < (ber_len_t) ((char *) sh->sh_end - (char *) p)) {
+ sh->sh_last = (char *) p + size;
+ p[0] = (p[0] & 1) | size;
+ return ptr;
+ }
+
+ /* Nowhere to grow, need to alloc and copy */
+ } else {
+ /* Slight optimization of the final realloc variant */
+ newptr = slap_sl_malloc(size-sizeof(ber_len_t), ctx);
+ AC_MEMCPY(newptr, ptr, oldsize-sizeof(ber_len_t));
+ /* Not last block, can just mark old region as free */
+ nextp[-1] = oldsize;
+ nextp[0] |= 1;
+ return newptr;
+ }
+
+ size -= sizeof(ber_len_t);
+ oldsize -= sizeof(ber_len_t);
+
+ } else if (oldsize > size) {
+ oldsize = size;
+ }
+
+ newptr = slap_sl_malloc(size, ctx);
+ AC_MEMCPY(newptr, ptr, oldsize);
+ slap_sl_free(ptr, ctx);
+ return newptr;
+}
+
+void
+slap_sl_free(void *ptr, void *ctx)
+{
+ struct slab_heap *sh = ctx;
+ ber_len_t size;
+ ber_len_t *p = ptr, *nextp, *tmpp;
+
+ if (!ptr)
+ return;
+
+ if (No_sl_malloc || !sh || ptr < sh->sh_base || ptr >= sh->sh_end) {
+ ber_memfree_x(ptr, NULL);
+ return;
+ }
+
+ size = *(--p);
+
+ if (sh->sh_stack) {
+ size &= -2;
+ nextp = (ber_len_t *) ((char *) p + size);
+ if (sh->sh_last != nextp) {
+ /* Mark it free: tail = size, head of next block |= 1 */
+ nextp[-1] = size;
+ nextp[0] |= 1;
+ /* We can't tell Valgrind about it yet, because we
+ * still need read/write access to this block for
+ * when we eventually get to reclaim it.
+ */
+ } else {
+ /* Reclaim freed block(s) off tail */
+ while (*p & 1) {
+ p = (ber_len_t *) ((char *) p - p[-1]);
+ }
+ sh->sh_last = p;
+ VGMEMP_TRIM(sh, sh->sh_base,
+ (char *) sh->sh_last - (char *) sh->sh_base);
+ }
+
+ } else {
+ int size_shift, order_size;
+ struct slab_object *so;
+ unsigned long diff;
+ int i, inserted = 0, order = -1;
+
+ size_shift = size + sizeof(ber_len_t) - 1;
+ do {
+ order++;
+ } while (size_shift >>= 1);
+
+ for (i = order, tmpp = p; i <= sh->sh_maxorder; i++) {
+ order_size = 1 << (i+1);
+ diff = (unsigned long)((char*)tmpp - (char*)sh->sh_base) >> (i+1);
+ sh->sh_map[i-order_start][diff>>3] &= (~(1 << (diff & 0x7)));
+ if (diff == ((diff>>1)<<1)) {
+ if (!(sh->sh_map[i-order_start][(diff+1)>>3] &
+ (1<<((diff+1)&0x7)))) {
+ so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]);
+ while (so) {
+ if ((char*)so->so_ptr == (char*)tmpp) {
+ LDAP_LIST_REMOVE( so, so_link );
+ } else if ((char*)so->so_ptr ==
+ (char*)tmpp + order_size) {
+ LDAP_LIST_REMOVE(so, so_link);
+ break;
+ }
+ so = LDAP_LIST_NEXT(so, so_link);
+ }
+ if (so) {
+ if (i < sh->sh_maxorder) {
+ inserted = 1;
+ so->so_ptr = tmpp;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start+1],
+ so, so_link);
+ }
+ continue;
+ } else {
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so, so_link);
+ so->so_ptr = tmpp;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start],
+ so, so_link);
+ break;
+
+ Debug(LDAP_DEBUG_TRACE, "slap_sl_free: "
+ "free object not found while bit is clear.\n" );
+ assert(so != NULL);
+
+ }
+ } else {
+ if (!inserted) {
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so, so_link);
+ so->so_ptr = tmpp;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start],
+ so, so_link);
+ }
+ break;
+ }
+ } else {
+ if (!(sh->sh_map[i-order_start][(diff-1)>>3] &
+ (1<<((diff-1)&0x7)))) {
+ so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]);
+ while (so) {
+ if ((char*)so->so_ptr == (char*)tmpp) {
+ LDAP_LIST_REMOVE(so, so_link);
+ } else if ((char*)tmpp == (char *)so->so_ptr + order_size) {
+ LDAP_LIST_REMOVE(so, so_link);
+ tmpp = so->so_ptr;
+ break;
+ }
+ so = LDAP_LIST_NEXT(so, so_link);
+ }
+ if (so) {
+ if (i < sh->sh_maxorder) {
+ inserted = 1;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start+1], so, so_link);
+ continue;
+ }
+ } else {
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so, so_link);
+ so->so_ptr = tmpp;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start],
+ so, so_link);
+ break;
+
+ Debug(LDAP_DEBUG_TRACE, "slap_sl_free: "
+ "free object not found while bit is clear.\n" );
+ assert(so != NULL);
+
+ }
+ } else {
+ if ( !inserted ) {
+ if (LDAP_LIST_EMPTY(&sh->sh_sopool)) {
+ slap_replenish_sopool(sh);
+ }
+ so = LDAP_LIST_FIRST(&sh->sh_sopool);
+ LDAP_LIST_REMOVE(so, so_link);
+ so->so_ptr = tmpp;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_free[i-order_start],
+ so, so_link);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+slap_sl_release( void *ptr, void *ctx )
+{
+ struct slab_heap *sh = ctx;
+ if ( sh && ptr >= sh->sh_base && ptr <= sh->sh_end )
+ sh->sh_last = ptr;
+}
+
+void *
+slap_sl_mark( void *ctx )
+{
+ struct slab_heap *sh = ctx;
+ return sh->sh_last;
+}
+
+/*
+ * Return the memory context of the current thread if the given block of
+ * memory belongs to it, otherwise return NULL.
+ */
+void *
+slap_sl_context( void *ptr )
+{
+ void *memctx;
+ struct slab_heap *sh;
+
+ if ( slapMode & SLAP_TOOL_MODE ) return NULL;
+
+ sh = GET_MEMCTX(ldap_pvt_thread_pool_context(), &memctx);
+ if (sh && ptr >= sh->sh_base && ptr <= sh->sh_end) {
+ return sh;
+ }
+ return NULL;
+}
+
+static struct slab_object *
+slap_replenish_sopool(
+ struct slab_heap* sh
+)
+{
+ struct slab_object *so_block;
+ int i;
+
+ so_block = (struct slab_object *)ch_malloc(
+ SLAP_SLAB_SOBLOCK * sizeof(struct slab_object));
+
+ if ( so_block == NULL ) {
+ return NULL;
+ }
+
+ so_block[0].so_blockhead = 1;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, &so_block[0], so_link);
+ for (i = 1; i < SLAP_SLAB_SOBLOCK; i++) {
+ so_block[i].so_blockhead = 0;
+ LDAP_LIST_INSERT_HEAD(&sh->sh_sopool, &so_block[i], so_link );
+ }
+
+ return so_block;
+}
+
+#ifdef SLAPD_UNUSED
+static void
+print_slheap(int level, void *ctx)
+{
+ struct slab_heap *sh = ctx;
+ struct slab_object *so;
+ int i, j, once = 0;
+
+ if (!ctx) {
+ Debug(level, "NULL memctx\n" );
+ return;
+ }
+
+ Debug(level, "sh->sh_maxorder=%d\n", sh->sh_maxorder );
+
+ for (i = order_start; i <= sh->sh_maxorder; i++) {
+ once = 0;
+ Debug(level, "order=%d\n", i );
+ for (j = 0; j < (1<<(sh->sh_maxorder-i))/8; j++) {
+ Debug(level, "%02x ", sh->sh_map[i-order_start][j] );
+ once = 1;
+ }
+ if (!once) {
+ Debug(level, "%02x ", sh->sh_map[i-order_start][0] );
+ }
+ Debug(level, "\n" );
+ Debug(level, "free list:\n" );
+ so = LDAP_LIST_FIRST(&sh->sh_free[i-order_start]);
+ while (so) {
+ Debug(level, "%p\n", so->so_ptr );
+ so = LDAP_LIST_NEXT(so, so_link);
+ }
+ }
+}
+#endif
diff --git a/servers/slapd/slap-cfglog.h b/servers/slapd/slap-cfglog.h
new file mode 100644
index 0000000..1ad67ff
--- /dev/null
+++ b/servers/slapd/slap-cfglog.h
@@ -0,0 +1,30 @@
+/* slap-cfglog.h - logging configuration */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef CFGLOG_H
+#define CFGLOG_H
+
+enum {
+ CFG_LOGLEVEL = 1,
+ CFG_LOGFILE,
+ CFG_LOGFILE_ROTATE,
+ CFG_LOGFILE_ONLY,
+ CFG_LOGFILE_FORMAT
+};
+
+extern ConfigDriver config_logging;
+
+#endif /* CFGLOG_H */
diff --git a/servers/slapd/slap-config.h b/servers/slapd/slap-config.h
new file mode 100644
index 0000000..ed67474
--- /dev/null
+++ b/servers/slapd/slap-config.h
@@ -0,0 +1,241 @@
+/* slap-config.h - configuration abstraction structure */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include<ac/string.h>
+
+LDAP_BEGIN_DECL
+
+typedef union config_values_u {
+ /* Drop-in to make existing "notify" initialisers quietly work */
+ void *dummy;
+ int v_int;
+ unsigned v_uint;
+ long v_long;
+ size_t v_ulong;
+ ber_len_t v_ber_t;
+ char *v_string;
+ struct berval v_bv;
+ struct {
+ struct berval vdn_dn;
+ struct berval vdn_ndn;
+ } v_dn;
+ AttributeDescription *v_ad;
+} ConfigValues;
+
+typedef struct ConfigTable {
+ const char *name;
+ const char *what;
+ int min_args;
+ int max_args;
+ int length;
+ unsigned int arg_type;
+ void *arg_item;
+ const char *attribute;
+ AttributeDescription *ad;
+ ConfigValues arg_default;
+} ConfigTable;
+
+/* search entries are returned according to this order */
+typedef enum {
+ Cft_Abstract = 0,
+ Cft_Global,
+ Cft_Module,
+ Cft_Schema,
+ Cft_Backend,
+ Cft_Database,
+ Cft_Overlay,
+ Cft_Misc /* backend/overlay defined */
+} ConfigType;
+
+#define ARGS_USERLAND 0x00000fff
+
+/* types are enumerated, not a bitmask */
+#define ARGS_TYPES 0x0000f000
+#define ARG_INT 0x00001000
+#define ARG_LONG 0x00002000
+#define ARG_BER_LEN_T 0x00003000
+#define ARG_ON_OFF 0x00004000
+#define ARG_STRING 0x00005000
+#define ARG_BERVAL 0x00006000
+#define ARG_DN 0x00007000
+#define ARG_UINT 0x00008000
+#define ARG_ATDESC 0x00009000
+#define ARG_ULONG 0x0000a000
+#define ARG_BINARY 0x0000b000
+
+#define ARGS_SYNTAX 0xffff0000
+#define ARG_IGNORED 0x00080000
+#define ARG_PRE_BI 0x00100000
+#define ARG_PRE_DB 0x00200000
+#define ARG_DB 0x00400000 /* Only applies to DB */
+#define ARG_MAY_DB 0x00800000 /* May apply to DB */
+#define ARG_PAREN 0x01000000
+#define ARG_NONZERO 0x02000000
+#define ARG_NO_INSERT 0x04000000 /* no arbitrary inserting */
+#define ARG_NO_DELETE 0x08000000 /* no runtime deletes */
+#define ARG_UNIQUE 0x10000000
+#define ARG_QUOTE 0x20000000 /* wrap with quotes before parsing */
+#define ARG_OFFSET 0x40000000
+#define ARG_MAGIC 0x80000000
+
+#define ARG_BAD_CONF 0xdead0000 /* overload return values */
+
+/* This is a config entry's e_private data */
+typedef struct CfEntryInfo {
+ struct CfEntryInfo *ce_parent;
+ struct CfEntryInfo *ce_sibs;
+ struct CfEntryInfo *ce_kids;
+ Entry *ce_entry;
+ ConfigType ce_type;
+ BackendInfo *ce_bi;
+ BackendDB *ce_be;
+ void *ce_private;
+} CfEntryInfo;
+
+struct config_args_s;
+
+/* Check if the child is allowed to be LDAPAdd'd to the parent */
+typedef int (ConfigLDAPadd)(
+ CfEntryInfo *parent, Entry *child, struct config_args_s *ca);
+
+/* Let the object create children out of slapd.conf */
+typedef int (ConfigCfAdd)(
+ Operation *op, SlapReply *rs, Entry *parent, struct config_args_s *ca );
+
+#ifdef SLAP_CONFIG_DELETE
+/* Called when deleting a Cft_Misc Child object from cn=config */
+typedef int (ConfigLDAPdel)(
+ CfEntryInfo *ce, Operation *op );
+#endif
+
+typedef struct ConfigOCs {
+ const char *co_def;
+ ConfigType co_type;
+ ConfigTable *co_table;
+ ConfigLDAPadd *co_ldadd;
+ ConfigCfAdd *co_cfadd;
+#ifdef SLAP_CONFIG_DELETE
+ ConfigLDAPdel *co_lddel;
+#endif
+ ObjectClass *co_oc;
+ struct berval *co_name;
+} ConfigOCs;
+
+typedef int (ConfigDriver)(struct config_args_s *c);
+
+struct config_reply_s {
+ int err;
+ char msg[SLAP_TEXT_BUFLEN];
+};
+
+typedef struct config_args_s {
+ int argc;
+ char **argv;
+ int argv_size;
+ char *line;
+ char *tline;
+ const char *fname;
+ int lineno;
+ int linelen;
+ char log[MAXPATHLEN + STRLENOF(": line ") + LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+#define cr_msg reply.msg
+ ConfigReply reply;
+ int depth;
+ int valx; /* multi-valued value index */
+ /* parsed first val for simple cases */
+ ConfigValues values;
+ /* return values for emit mode */
+ BerVarray rvalue_vals;
+ BerVarray rvalue_nvals;
+#define SLAP_CONFIG_EMIT 0x2000 /* emit instead of set */
+#define SLAP_CONFIG_ADD 0x4000 /* config file add vs LDAP add */
+ int op;
+ int type; /* ConfigTable.arg_type & ARGS_USERLAND */
+ Operation *ca_op;
+ BackendDB *be;
+ BackendInfo *bi;
+ Entry *ca_entry; /* entry being modified */
+ void *ca_private; /* anything */
+#ifndef SLAP_CONFIG_CLEANUP_MAX
+#define SLAP_CONFIG_CLEANUP_MAX 16
+#endif
+ ConfigDriver *cleanups[SLAP_CONFIG_CLEANUP_MAX];
+ ConfigType table; /* what kind of config table did we come from */
+ ConfigTable *ca_desc;
+ int num_cleanups;
+} ConfigArgs;
+
+/* If lineno is zero, we have an actual LDAP Add request from a client.
+ * Otherwise, we're reading a config file or a config dir.
+ */
+#define CONFIG_ONLINE_ADD(ca) (!((ca)->lineno))
+
+#define value_int values.v_int
+#define value_uint values.v_uint
+#define value_long values.v_long
+#define value_ulong values.v_ulong
+#define value_ber_t values.v_ber_t
+#define value_string values.v_string
+#define value_bv values.v_bv
+#define value_dn values.v_dn.vdn_dn
+#define value_ndn values.v_dn.vdn_ndn
+#define value_ad values.v_ad
+
+LDAP_SLAPD_F (int) config_fp_parse_line(ConfigArgs *c);
+
+LDAP_SLAPD_F (int) config_register_schema(ConfigTable *ct, ConfigOCs *co);
+LDAP_SLAPD_F (int) config_del_vals(ConfigTable *cf, ConfigArgs *c);
+LDAP_SLAPD_F (int) config_get_vals(ConfigTable *ct, ConfigArgs *c);
+LDAP_SLAPD_F (int) config_add_vals(ConfigTable *ct, ConfigArgs *c);
+
+LDAP_SLAPD_F (int) config_push_cleanup(ConfigArgs *c, ConfigDriver *cleanup);
+LDAP_SLAPD_F (int) config_run_cleanup(ConfigArgs *c);
+
+LDAP_SLAPD_F (void) init_config_argv( ConfigArgs *c );
+LDAP_SLAPD_F (int) init_config_attrs(ConfigTable *ct);
+LDAP_SLAPD_F (int) init_config_ocs( ConfigOCs *ocs );
+LDAP_SLAPD_F (void) config_parse_ldif( ConfigArgs *c );
+LDAP_SLAPD_F (int) config_parse_vals(ConfigTable *ct, ConfigArgs *c, int valx);
+LDAP_SLAPD_F (int) config_parse_add(ConfigTable *ct, ConfigArgs *c, int valx);
+LDAP_SLAPD_F (int) read_config_file(const char *fname, int depth, ConfigArgs *cf,
+ ConfigTable *cft );
+
+LDAP_SLAPD_F (ConfigTable *) config_find_keyword(ConfigTable *ct, ConfigArgs *c);
+LDAP_SLAPD_F (Entry *) config_build_entry( Operation *op, SlapReply *rs, CfEntryInfo *parent,
+ ConfigArgs *c, struct berval *rdn, ConfigOCs *main, ConfigOCs *extra );
+
+LDAP_SLAPD_F (Listener *) config_check_my_url(const char *url, LDAPURLDesc *lud);
+LDAP_SLAPD_F (int) config_shadow( ConfigArgs *c, slap_mask_t flag );
+#define config_slurp_shadow(c) config_shadow((c), SLAP_DBFLAG_SLURP_SHADOW)
+#define config_sync_shadow(c) config_shadow((c), SLAP_DBFLAG_SYNC_SHADOW)
+
+ /* Make sure we don't exceed the bits reserved for userland */
+#define config_check_userland(last) \
+ assert( ( ( (last) - 1 ) & ARGS_USERLAND ) == ( (last) - 1 ) );
+
+#define SLAP_X_ORDERED_FMT "{%d}"
+
+LDAP_SLAPD_V (slap_verbmasks *) slap_ldap_response_code;
+LDAP_SLAPD_F (int) slap_ldap_response_code_register( struct berval *bv, int err );
+
+LDAP_SLAPD_V (ConfigTable) olcDatabaseDummy[];
+
+LDAP_END_DECL
+
+#endif /* CONFIG_H */
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
new file mode 100644
index 0000000..946e552
--- /dev/null
+++ b/servers/slapd/slap.h
@@ -0,0 +1,3389 @@
+/* slap.h - stand alone ldap server include file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#ifndef _SLAP_H_
+#define _SLAP_H_
+
+#include "ldap_defaults.h"
+
+#include <stdio.h>
+#include <ac/stdlib.h>
+
+#include <sys/types.h>
+#include <ac/syslog.h>
+#include <ac/regex.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/time.h>
+#include <ac/param.h>
+
+#include "ldap_avl.h"
+
+#ifndef ldap_debug
+#define ldap_debug slap_debug
+#endif
+
+#include "ldap_log.h"
+
+#include <ldap.h>
+#include <ldap_schema.h>
+
+#include "lber_pvt.h"
+#include "ldap_pvt.h"
+#include "ldap_pvt_thread.h"
+#include "ldap_queue.h"
+
+#include "lutil.h"
+
+LDAP_BEGIN_DECL
+
+#ifdef LDAP_DEVEL
+#define LDAP_COLLECTIVE_ATTRIBUTES
+#define LDAP_COMP_MATCH
+#define LDAP_SYNC_TIMESTAMP
+#define SLAP_CONTROL_X_WHATFAILED
+#ifndef SLAP_SCHEMA_EXPOSE
+#define SLAP_SCHEMA_EXPOSE
+#endif
+#endif
+
+#define SLAP_CONFIG_DELETE
+#define SLAP_AUXPROP_DONTUSECOPY
+#define LDAP_DYNAMIC_OBJECTS
+#define SLAP_CONTROL_X_TREE_DELETE LDAP_CONTROL_X_TREE_DELETE
+#define SLAP_CONTROL_X_SESSION_TRACKING
+#define SLAP_DISTPROC
+#define SLAP_CONTROL_X_LAZY_COMMIT
+
+#ifndef SLAP_STATS_ETIME
+#define SLAP_STATS_ETIME 1 /* microsecond op timing */
+#endif
+
+/*
+ * SLAPD Memory allocation macros
+ *
+ * Unlike ch_*() routines, these routines do not assert() upon
+ * allocation error. They are intended to be used instead of
+ * ch_*() routines where the caller has implemented proper
+ * checking for and handling of allocation errors.
+ *
+ * Patches to convert ch_*() calls to SLAP_*() calls welcomed.
+ */
+#define SLAP_MALLOC(s) ber_memalloc((s))
+#define SLAP_CALLOC(n,s) ber_memcalloc((n),(s))
+#define SLAP_REALLOC(p,s) ber_memrealloc((p),(s))
+#define SLAP_FREE(p) ber_memfree((p))
+#define SLAP_VFREE(v) ber_memvfree((void**)(v))
+#define SLAP_STRDUP(s) ber_strdup((s))
+#define SLAP_STRNDUP(s,l) ber_strndup((s),(l))
+
+#ifdef f_next
+#undef f_next /* name conflict between sys/file.h on SCO and struct filter */
+#endif
+
+#define SERVICE_NAME OPENLDAP_PACKAGE "-slapd"
+#define SLAPD_ANONYMOUS ""
+
+#ifdef HAVE_TCPD
+# include <tcpd.h>
+# define SLAP_STRING_UNKNOWN STRING_UNKNOWN
+#else /* ! TCP Wrappers */
+# define SLAP_STRING_UNKNOWN "unknown"
+#endif /* ! TCP Wrappers */
+
+/* LDAPMod.mod_op value ===> Must be kept in sync with ldap.h! */
+/* These values are used internally by the backends. */
+/* SLAP_MOD_SOFTADD allows adding values that already exist without getting
+ * an error as required by modrdn when the new rdn was already an attribute
+ * value itself.
+ */
+#define SLAP_MOD_SOFTADD 0x1000
+/* SLAP_MOD_SOFTDEL allows deleting values if they exist without getting
+ * an error otherwise.
+ */
+#define SLAP_MOD_SOFTDEL 0x1001
+/* SLAP_MOD_ADD_IF_NOT_PRESENT allows adding values unless the attribute
+ * is already present without getting an error.
+ */
+#define SLAP_MOD_ADD_IF_NOT_PRESENT 0x1002
+/* SLAP_MOD_DEL_IF_PRESENT allows deleting values if the attribute
+ * is present, without getting an error otherwise.
+ * The semantics can be obtained using SLAP_MOD_SOFTDEL with NULL values.
+ */
+
+#define MAXREMATCHES (100)
+
+#define SLAP_MAX_WORKER_THREADS (16)
+
+#define SLAP_SB_MAX_INCOMING_DEFAULT ((1<<18) - 1)
+#define SLAP_SB_MAX_INCOMING_AUTH ((1<<24) - 1)
+
+#define SLAP_CONN_MAX_PENDING_DEFAULT 100
+#define SLAP_CONN_MAX_PENDING_AUTH 1000
+#define SLAP_MAX_FILTER_DEPTH_DEFAULT 1000
+
+#define SLAP_TEXT_BUFLEN (256)
+
+/* pseudo error code indicating abandoned operation */
+#define SLAPD_ABANDON (-1024)
+
+/* pseudo error code indicating disconnect */
+#define SLAPD_DISCONNECT (-1025)
+
+/* unknown config file directive */
+#define SLAP_CONF_UNKNOWN (-1026)
+
+/* pseudo error code indicating async operation */
+#define SLAPD_ASYNCOP (-1027)
+
+/* pseudo error code to suppress frontend response */
+#define SLAPD_NO_REPLY (-1028)
+
+/* We assume "C" locale, that is US-ASCII */
+#define ASCII_SPACE(c) ( (c) == ' ' )
+#define ASCII_LOWER(c) ( (c) >= 'a' && (c) <= 'z' )
+#define ASCII_UPPER(c) ( (c) >= 'A' && (c) <= 'Z' )
+#define ASCII_ALPHA(c) ( ASCII_LOWER(c) || ASCII_UPPER(c) )
+#define ASCII_DIGIT(c) ( (c) >= '0' && (c) <= '9' )
+#define ASCII_HEXLOWER(c) ( (c) >= 'a' && (c) <= 'f' )
+#define ASCII_HEXUPPER(c) ( (c) >= 'A' && (c) <= 'F' )
+#define ASCII_HEX(c) ( ASCII_DIGIT(c) || \
+ ASCII_HEXLOWER(c) || ASCII_HEXUPPER(c) )
+#define ASCII_ALNUM(c) ( ASCII_ALPHA(c) || ASCII_DIGIT(c) )
+#define ASCII_PRINTABLE(c) ( (c) >= ' ' && (c) <= '~' )
+
+#define SLAP_NIBBLE(c) ((c)&0x0f)
+#define SLAP_ESCAPE_CHAR ('\\')
+#define SLAP_ESCAPE_LO(c) ( "0123456789ABCDEF"[SLAP_NIBBLE(c)] )
+#define SLAP_ESCAPE_HI(c) ( SLAP_ESCAPE_LO((c)>>4) )
+
+#define FILTER_ESCAPE(c) ( (c) == '*' || (c) == '\\' \
+ || (c) == '(' || (c) == ')' || !ASCII_PRINTABLE(c) )
+
+#define DN_ESCAPE(c) ((c) == SLAP_ESCAPE_CHAR)
+/* NOTE: for consistency, this macro must only operate
+ * on normalized/pretty DN, such that ';' is never used
+ * as RDN separator, and all occurrences of ';' must be escaped */
+#define DN_SEPARATOR(c) ((c) == ',')
+#define RDN_ATTRTYPEANDVALUE_SEPARATOR(c) ((c) == '+') /* RFC 4514 */
+#define RDN_SEPARATOR(c) (DN_SEPARATOR(c) || RDN_ATTRTYPEANDVALUE_SEPARATOR(c))
+#define RDN_NEEDSESCAPE(c) ((c) == '\\' || (c) == '"')
+
+#define DESC_LEADCHAR(c) ( ASCII_ALPHA(c) )
+#define DESC_CHAR(c) ( ASCII_ALNUM(c) || (c) == '-' )
+#define OID_LEADCHAR(c) ( ASCII_DIGIT(c) )
+#define OID_SEPARATOR(c) ( (c) == '.' )
+#define OID_CHAR(c) ( OID_LEADCHAR(c) || OID_SEPARATOR(c) )
+
+#define ATTR_LEADCHAR(c) ( DESC_LEADCHAR(c) || OID_LEADCHAR(c) )
+#define ATTR_CHAR(c) ( DESC_CHAR((c)) || OID_SEPARATOR(c) )
+
+#define AD_LEADCHAR(c) ( ATTR_LEADCHAR(c) )
+#define AD_CHAR(c) ( ATTR_CHAR(c) || (c) == ';' )
+
+#define SLAP_NUMERIC(c) ( ASCII_DIGIT(c) || ASCII_SPACE(c) )
+
+#define SLAP_PRINTABLE(c) ( ASCII_ALNUM(c) || (c) == '\'' || \
+ (c) == '(' || (c) == ')' || (c) == '+' || (c) == ',' || \
+ (c) == '-' || (c) == '.' || (c) == '/' || (c) == ':' || \
+ (c) == '?' || (c) == ' ' || (c) == '=' )
+#define SLAP_PRINTABLES(c) ( SLAP_PRINTABLE(c) || (c) == '$' )
+
+/* must match in schema_init.c */
+#define SLAPD_DN_SYNTAX "1.3.6.1.4.1.1466.115.121.1.12"
+#define SLAPD_NAMEUID_SYNTAX "1.3.6.1.4.1.1466.115.121.1.34"
+#define SLAPD_INTEGER_SYNTAX "1.3.6.1.4.1.1466.115.121.1.27"
+#define SLAPD_GROUP_ATTR "member"
+#define SLAPD_GROUP_CLASS "groupOfNames"
+#define SLAPD_ROLE_ATTR "roleOccupant"
+#define SLAPD_ROLE_CLASS "organizationalRole"
+
+#define SLAPD_TOP_OID "2.5.6.0"
+
+LDAP_SLAPD_V (int) slap_debug;
+
+typedef unsigned long slap_mask_t;
+
+/* Security Strength Factor */
+typedef unsigned slap_ssf_t;
+
+typedef struct slap_ssf_set {
+ slap_ssf_t sss_ssf;
+ slap_ssf_t sss_transport;
+ slap_ssf_t sss_tls;
+ slap_ssf_t sss_sasl;
+ slap_ssf_t sss_update_ssf;
+ slap_ssf_t sss_update_transport;
+ slap_ssf_t sss_update_tls;
+ slap_ssf_t sss_update_sasl;
+ slap_ssf_t sss_simple_bind;
+} slap_ssf_set_t;
+
+/* Flags for telling slap_sasl_getdn() what type of identity is being passed */
+#define SLAP_GETDN_AUTHCID 2
+#define SLAP_GETDN_AUTHZID 4
+
+/*
+ * Index types
+ */
+#define SLAP_INDEX_TYPE 0x00FFUL
+#define SLAP_INDEX_UNDEFINED 0x0001UL
+#define SLAP_INDEX_PRESENT 0x0002UL
+#define SLAP_INDEX_EQUALITY 0x0004UL
+#define SLAP_INDEX_APPROX 0x0008UL
+#define SLAP_INDEX_SUBSTR 0x0010UL
+#define SLAP_INDEX_EXTENDED 0x0020UL
+
+#define SLAP_INDEX_DEFAULT SLAP_INDEX_EQUALITY
+
+#define IS_SLAP_INDEX(mask, type) (((mask) & (type)) == (type))
+
+#define SLAP_INDEX_SUBSTR_TYPE 0x0F00UL
+
+#define SLAP_INDEX_SUBSTR_INITIAL ( SLAP_INDEX_SUBSTR | 0x0100UL )
+#define SLAP_INDEX_SUBSTR_ANY ( SLAP_INDEX_SUBSTR | 0x0200UL )
+#define SLAP_INDEX_SUBSTR_FINAL ( SLAP_INDEX_SUBSTR | 0x0400UL )
+#define SLAP_INDEX_SUBSTR_DEFAULT \
+ ( SLAP_INDEX_SUBSTR \
+ | SLAP_INDEX_SUBSTR_INITIAL \
+ | SLAP_INDEX_SUBSTR_ANY \
+ | SLAP_INDEX_SUBSTR_FINAL )
+
+/* defaults for initial/final substring indices */
+#define SLAP_INDEX_SUBSTR_IF_MINLEN_DEFAULT 2
+#define SLAP_INDEX_SUBSTR_IF_MAXLEN_DEFAULT 4
+
+/* defaults for any substring indices */
+#define SLAP_INDEX_SUBSTR_ANY_LEN_DEFAULT 4
+#define SLAP_INDEX_SUBSTR_ANY_STEP_DEFAULT 2
+
+/* default for ordered integer index keys */
+#define SLAP_INDEX_INTLEN_DEFAULT 4
+
+#define SLAP_INDEX_FLAGS 0xF000UL
+#define SLAP_INDEX_NOSUBTYPES 0x1000UL /* don't use index w/ subtypes */
+#define SLAP_INDEX_NOTAGS 0x2000UL /* don't use index w/ tags */
+
+/*
+ * there is a single index for each attribute. these prefixes ensure
+ * that there is no collision among keys.
+ */
+#define SLAP_INDEX_EQUALITY_PREFIX '=' /* prefix for equality keys */
+#define SLAP_INDEX_APPROX_PREFIX '~' /* prefix for approx keys */
+#define SLAP_INDEX_SUBSTR_PREFIX '*' /* prefix for substring keys */
+#define SLAP_INDEX_SUBSTR_INITIAL_PREFIX '^'
+#define SLAP_INDEX_SUBSTR_FINAL_PREFIX '$'
+#define SLAP_INDEX_CONT_PREFIX '.' /* prefix for continuation keys */
+
+#define SLAP_SYNTAX_MATCHINGRULES_OID "1.3.6.1.4.1.1466.115.121.1.30"
+#define SLAP_SYNTAX_ATTRIBUTETYPES_OID "1.3.6.1.4.1.1466.115.121.1.3"
+#define SLAP_SYNTAX_OBJECTCLASSES_OID "1.3.6.1.4.1.1466.115.121.1.37"
+#define SLAP_SYNTAX_MATCHINGRULEUSES_OID "1.3.6.1.4.1.1466.115.121.1.31"
+#define SLAP_SYNTAX_CONTENTRULE_OID "1.3.6.1.4.1.1466.115.121.1.16"
+
+/*
+ * represents schema information for a database
+ */
+enum {
+ SLAP_SCHERR_OUTOFMEM = 1,
+ SLAP_SCHERR_CLASS_NOT_FOUND,
+ SLAP_SCHERR_CLASS_BAD_USAGE,
+ SLAP_SCHERR_CLASS_BAD_SUP,
+ SLAP_SCHERR_CLASS_DUP,
+ SLAP_SCHERR_CLASS_INCONSISTENT,
+ SLAP_SCHERR_ATTR_NOT_FOUND,
+ SLAP_SCHERR_ATTR_BAD_MR,
+ SLAP_SCHERR_ATTR_BAD_USAGE,
+ SLAP_SCHERR_ATTR_BAD_SUP,
+ SLAP_SCHERR_ATTR_INCOMPLETE,
+ SLAP_SCHERR_ATTR_DUP,
+ SLAP_SCHERR_ATTR_INCONSISTENT,
+ SLAP_SCHERR_MR_NOT_FOUND,
+ SLAP_SCHERR_MR_INCOMPLETE,
+ SLAP_SCHERR_MR_DUP,
+ SLAP_SCHERR_SYN_NOT_FOUND,
+ SLAP_SCHERR_SYN_DUP,
+ SLAP_SCHERR_SYN_SUP_NOT_FOUND,
+ SLAP_SCHERR_SYN_SUBST_NOT_SPECIFIED,
+ SLAP_SCHERR_SYN_SUBST_NOT_FOUND,
+ SLAP_SCHERR_NO_NAME,
+ SLAP_SCHERR_NOT_SUPPORTED,
+ SLAP_SCHERR_BAD_DESCR,
+ SLAP_SCHERR_OIDM,
+ SLAP_SCHERR_CR_DUP,
+ SLAP_SCHERR_CR_BAD_STRUCT,
+ SLAP_SCHERR_CR_BAD_AUX,
+ SLAP_SCHERR_CR_BAD_AT,
+
+ SLAP_SCHERR_LAST
+};
+
+/* forward declarations */
+typedef struct Syntax Syntax;
+typedef struct MatchingRule MatchingRule;
+typedef struct MatchingRuleUse MatchingRuleUse;
+typedef struct MatchingRuleAssertion MatchingRuleAssertion;
+typedef struct OidMacro OidMacro;
+typedef struct ObjectClass ObjectClass;
+typedef struct AttributeType AttributeType;
+typedef struct AttributeDescription AttributeDescription;
+typedef struct AttributeName AttributeName;
+typedef struct ContentRule ContentRule;
+
+typedef struct AttributeAssertion AttributeAssertion;
+typedef struct SubstringsAssertion SubstringsAssertion;
+typedef struct Filter Filter;
+typedef struct ValuesReturnFilter ValuesReturnFilter;
+typedef struct Attribute Attribute;
+#ifdef LDAP_COMP_MATCH
+typedef struct ComponentData ComponentData;
+typedef struct ComponentFilter ComponentFilter;
+#endif
+
+typedef struct Entry Entry;
+typedef struct Modification Modification;
+typedef struct Modifications Modifications;
+typedef struct LDAPModList LDAPModList;
+
+typedef struct BackendInfo BackendInfo; /* per backend type */
+typedef struct BackendDB BackendDB; /* per backend database */
+
+typedef struct Connection Connection;
+typedef struct Operation Operation;
+typedef struct SlapReply SlapReply;
+/* end of forward declarations */
+
+extern int slap_inet4or6;
+
+struct OidMacro {
+ struct berval som_oid;
+ BerVarray som_names;
+ BerVarray som_subs;
+#define SLAP_OM_HARDCODE 0x10000U /* This is hardcoded schema */
+ int som_flags;
+ LDAP_STAILQ_ENTRY(OidMacro) som_next;
+};
+
+typedef int slap_syntax_validate_func LDAP_P((
+ Syntax *syntax,
+ struct berval * in));
+
+typedef int slap_syntax_transform_func LDAP_P((
+ Syntax *syntax,
+ struct berval * in,
+ struct berval * out,
+ void *memctx));
+
+#ifdef LDAP_COMP_MATCH
+typedef void* slap_component_transform_func LDAP_P((
+ struct berval * in ));
+struct ComponentDesc;
+#endif
+
+struct Syntax {
+ LDAPSyntax ssyn_syn;
+#define ssyn_oid ssyn_syn.syn_oid
+#define ssyn_desc ssyn_syn.syn_desc
+#define ssyn_extensions ssyn_syn.syn_extensions
+ /*
+ * Note: the former
+ ber_len_t ssyn_oidlen;
+ * has been replaced by a struct berval that uses the value
+ * provided by ssyn_syn.syn_oid; a macro that expands to
+ * the bv_len field of the berval is provided for backward
+ * compatibility. CAUTION: NEVER FREE THE BERVAL
+ */
+ struct berval ssyn_bvoid;
+#define ssyn_oidlen ssyn_bvoid.bv_len
+
+ unsigned int ssyn_flags;
+
+#define SLAP_SYNTAX_NONE 0x0000U
+#define SLAP_SYNTAX_BLOB 0x0001U /* syntax treated as blob (audio) */
+#define SLAP_SYNTAX_BINARY 0x0002U /* binary transfer required (certificate) */
+#define SLAP_SYNTAX_BER 0x0004U /* stored in BER encoding (certificate) */
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_SYNTAX_HIDE 0x0000U /* publish everything */
+#else
+#define SLAP_SYNTAX_HIDE 0x8000U /* hide (do not publish) */
+#endif
+#define SLAP_SYNTAX_HARDCODE 0x10000U /* This is hardcoded schema */
+#define SLAP_SYNTAX_DN 0x20000U /* Treat like a DN */
+
+ Syntax **ssyn_sups;
+
+ slap_syntax_validate_func *ssyn_validate;
+ slap_syntax_transform_func *ssyn_pretty;
+
+#ifdef SLAPD_BINARY_CONVERSION
+ /* convert to and from binary */
+ slap_syntax_transform_func *ssyn_ber2str;
+ slap_syntax_transform_func *ssyn_str2ber;
+#endif
+#ifdef LDAP_COMP_MATCH
+ slap_component_transform_func *ssyn_attr2comp;
+ struct ComponentDesc* ssync_comp_syntax;
+#endif
+
+ LDAP_STAILQ_ENTRY(Syntax) ssyn_next;
+};
+
+#define slap_syntax_is_flag(s,flag) ((int)((s)->ssyn_flags & (flag)) ? 1 : 0)
+#define slap_syntax_is_blob(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BLOB)
+#define slap_syntax_is_binary(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BINARY)
+#define slap_syntax_is_ber(s) slap_syntax_is_flag((s),SLAP_SYNTAX_BER)
+#define slap_syntax_is_hidden(s) slap_syntax_is_flag((s),SLAP_SYNTAX_HIDE)
+
+typedef struct slap_syntax_defs_rec {
+ char *sd_desc;
+ int sd_flags;
+ char **sd_sups;
+ slap_syntax_validate_func *sd_validate;
+ slap_syntax_transform_func *sd_pretty;
+#ifdef SLAPD_BINARY_CONVERSION
+ slap_syntax_transform_func *sd_ber2str;
+ slap_syntax_transform_func *sd_str2ber;
+#endif
+} slap_syntax_defs_rec;
+
+/* X -> Y Converter */
+typedef int slap_mr_convert_func LDAP_P((
+ struct berval * in,
+ struct berval * out,
+ void *memctx ));
+
+/* Normalizer */
+typedef int slap_mr_normalize_func LDAP_P((
+ slap_mask_t use,
+ Syntax *syntax, /* NULL if in is asserted value */
+ MatchingRule *mr,
+ struct berval *in,
+ struct berval *out,
+ void *memctx ));
+
+/* Match (compare) function */
+typedef int slap_mr_match_func LDAP_P((
+ int *match,
+ slap_mask_t use,
+ Syntax *syntax, /* syntax of stored value */
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertValue ));
+
+/* Index generation function */
+typedef int slap_mr_indexer_func LDAP_P((
+ slap_mask_t use,
+ slap_mask_t mask,
+ Syntax *syntax, /* syntax of stored value */
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keys,
+ void *memctx ));
+
+/* Filter index function */
+typedef int slap_mr_filter_func LDAP_P((
+ slap_mask_t use,
+ slap_mask_t mask,
+ Syntax *syntax, /* syntax of stored value */
+ MatchingRule *mr,
+ struct berval *prefix,
+ void *assertValue,
+ BerVarray *keys,
+ void *memctx ));
+
+struct MatchingRule {
+ LDAPMatchingRule smr_mrule;
+ MatchingRuleUse *smr_mru;
+ /* RFC 4512 string representation */
+ struct berval smr_str;
+ /*
+ * Note: the former
+ * ber_len_t smr_oidlen;
+ * has been replaced by a struct berval that uses the value
+ * provided by smr_mrule.mr_oid; a macro that expands to
+ * the bv_len field of the berval is provided for backward
+ * compatibility. CAUTION: NEVER FREE THE BERVAL
+ */
+ struct berval smr_bvoid;
+#define smr_oidlen smr_bvoid.bv_len
+
+ slap_mask_t smr_usage;
+
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_MR_HIDE 0x0000U
+#else
+#define SLAP_MR_HIDE 0x8000U
+#endif
+
+#define SLAP_MR_MUTATION_NORMALIZER 0x4000U
+
+#define SLAP_MR_TYPE_MASK 0x0F00U
+#define SLAP_MR_SUBTYPE_MASK 0x00F0U
+#define SLAP_MR_USAGE 0x000FU
+
+#define SLAP_MR_NONE 0x0000U
+#define SLAP_MR_EQUALITY 0x0100U
+#define SLAP_MR_ORDERING 0x0200U
+#define SLAP_MR_SUBSTR 0x0400U
+#define SLAP_MR_EXT 0x0800U /* implicitly extensible */
+#define SLAP_MR_ORDERED_INDEX 0x1000U
+#ifdef LDAP_COMP_MATCH
+#define SLAP_MR_COMPONENT 0x2000U
+#endif
+
+#define SLAP_MR_EQUALITY_APPROX ( SLAP_MR_EQUALITY | 0x0010U )
+
+#define SLAP_MR_SUBSTR_INITIAL ( SLAP_MR_SUBSTR | 0x0010U )
+#define SLAP_MR_SUBSTR_ANY ( SLAP_MR_SUBSTR | 0x0020U )
+#define SLAP_MR_SUBSTR_FINAL ( SLAP_MR_SUBSTR | 0x0040U )
+
+
+/*
+ * The asserted value, depending on the particular usage,
+ * is expected to conform to either the assertion syntax
+ * or the attribute syntax. In some cases, the syntax of
+ * the value is known. If so, these flags indicate which
+ * syntax the value is expected to conform to. If not,
+ * neither of these flags is set (until the syntax of the
+ * provided value is determined). If the value is of the
+ * attribute syntax, the flag is changed once a value of
+ * the assertion syntax is derived from the provided value.
+ */
+#define SLAP_MR_VALUE_OF_ASSERTION_SYNTAX 0x0001U
+#define SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX 0x0002U
+#define SLAP_MR_VALUE_OF_SYNTAX (SLAP_MR_VALUE_OF_ASSERTION_SYNTAX|SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX)
+#define SLAP_MR_DENORMALIZE (SLAP_MR_MUTATION_NORMALIZER)
+
+#define SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX( usage ) \
+ ((usage) & SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX )
+#define SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( usage ) \
+ ((usage) & SLAP_MR_VALUE_OF_ASSERTION_SYNTAX )
+#ifdef LDAP_DEBUG
+#define SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) \
+ ((usage) & SLAP_MR_VALUE_OF_SYNTAX)
+#else
+#define SLAP_MR_IS_VALUE_OF_SYNTAX( usage ) (1)
+#endif
+#define SLAP_MR_IS_DENORMALIZE( usage ) \
+ ((usage) & SLAP_MR_DENORMALIZE )
+
+/* either or both the asserted value or attribute value
+ * may be provided in normalized form
+ */
+#define SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH 0x0004U
+#define SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH 0x0008U
+
+#define SLAP_IS_MR_ASSERTION_SYNTAX_MATCH( usage ) \
+ (!((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_MATCH))
+#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_MATCH( usage ) \
+ ((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_MATCH)
+
+#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH( usage ) \
+ (((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH) \
+ == SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH)
+#define SLAP_IS_MR_ATTRIBUTE_SYNTAX_NONCONVERTED_MATCH( usage ) \
+ (((usage) & SLAP_MR_ATTRIBUTE_SYNTAX_CONVERTED_MATCH) \
+ == SLAP_MR_ATTRIBUTE_SYNTAX_MATCH)
+
+#define SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( usage ) \
+ ((usage) & SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH )
+#define SLAP_IS_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH( usage ) \
+ ((usage) & SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH )
+
+ Syntax *smr_syntax;
+ slap_mr_convert_func *smr_convert;
+ slap_mr_normalize_func *smr_normalize;
+ slap_mr_match_func *smr_match;
+ slap_mr_indexer_func *smr_indexer;
+ slap_mr_filter_func *smr_filter;
+
+ /*
+ * null terminated array of syntaxes compatible with this syntax
+ * note: when MS_EXT is set, this MUST NOT contain the assertion
+ * syntax of the rule. When MS_EXT is not set, it MAY.
+ */
+ Syntax **smr_compat_syntaxes;
+
+ /*
+ * For equality rules, refers to an associated approximate rule.
+ * For non-equality rules, refers to an associated equality rule.
+ */
+ MatchingRule *smr_associated;
+
+#define SLAP_MR_ASSOCIATED(mr,amr) \
+ (((mr) == (amr)) || ((mr)->smr_associated == (amr)))
+
+ LDAP_SLIST_ENTRY(MatchingRule) smr_next;
+
+#define smr_oid smr_mrule.mr_oid
+#define smr_names smr_mrule.mr_names
+#define smr_desc smr_mrule.mr_desc
+#define smr_obsolete smr_mrule.mr_obsolete
+#define smr_syntax_oid smr_mrule.mr_syntax_oid
+#define smr_extensions smr_mrule.mr_extensions
+};
+
+struct MatchingRuleUse {
+ LDAPMatchingRuleUse smru_mruleuse;
+ MatchingRule *smru_mr;
+ /* RFC 4512 string representation */
+ struct berval smru_str;
+
+ LDAP_SLIST_ENTRY(MatchingRuleUse) smru_next;
+
+#define smru_oid smru_mruleuse.mru_oid
+#define smru_names smru_mruleuse.mru_names
+#define smru_desc smru_mruleuse.mru_desc
+#define smru_obsolete smru_mruleuse.mru_obsolete
+#define smru_applies_oids smru_mruleuse.mru_applies_oids
+
+#define smru_usage smru_mr->smr_usage
+} /* MatchingRuleUse */ ;
+
+typedef struct slap_mrule_defs_rec {
+ char * mrd_desc;
+ slap_mask_t mrd_usage;
+ char ** mrd_compat_syntaxes;
+ slap_mr_convert_func * mrd_convert;
+ slap_mr_normalize_func * mrd_normalize;
+ slap_mr_match_func * mrd_match;
+ slap_mr_indexer_func * mrd_indexer;
+ slap_mr_filter_func * mrd_filter;
+
+ /* For equality rule, this may refer to an associated approximate rule */
+ /* For non-equality rule, this may refer to an associated equality rule */
+ char * mrd_associated;
+} slap_mrule_defs_rec;
+
+typedef int (AttributeTypeSchemaCheckFN)(
+ BackendDB *be,
+ Entry *e,
+ Attribute *attr,
+ const char** text,
+ char *textbuf, size_t textlen );
+
+struct AttributeType {
+ LDAPAttributeType sat_atype;
+ struct berval sat_cname;
+ AttributeType *sat_sup;
+ AttributeType **sat_subtypes;
+ MatchingRule *sat_equality;
+ MatchingRule *sat_approx;
+ MatchingRule *sat_ordering;
+ MatchingRule *sat_substr;
+ Syntax *sat_syntax;
+
+ AttributeTypeSchemaCheckFN *sat_check;
+ char *sat_oidmacro; /* attribute OID */
+ char *sat_soidmacro; /* syntax OID */
+
+#define SLAP_AT_NONE 0x0000U
+#define SLAP_AT_ABSTRACT 0x0100U /* cannot be instantiated */
+#define SLAP_AT_FINAL 0x0200U /* cannot be subtyped */
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_AT_HIDE 0x0000U /* publish everything */
+#else
+#define SLAP_AT_HIDE 0x8000U /* hide attribute */
+#endif
+#define SLAP_AT_DYNAMIC 0x0400U /* dynamically generated */
+
+#define SLAP_AT_MANAGEABLE 0x0800U /* no-user-mod can be by-passed */
+
+/* Note: ORDERED values have an ordering specifically set by the
+ * user, denoted by the {x} ordering prefix on the values.
+ *
+ * SORTED values are simply sorted by memcmp. SORTED values can
+ * be efficiently located by binary search. ORDERED values have no
+ * such advantage. An attribute cannot have both properties.
+ */
+#define SLAP_AT_ORDERED_VAL 0x0001U /* values are ordered */
+#define SLAP_AT_ORDERED_SIB 0x0002U /* siblings are ordered */
+#define SLAP_AT_ORDERED 0x0003U /* value has order index */
+
+#define SLAP_AT_SORTED_VAL 0x0010U /* values should be sorted */
+
+#define SLAP_AT_HARDCODE 0x10000U /* hardcoded schema */
+#define SLAP_AT_DELETED 0x20000U
+
+ slap_mask_t sat_flags;
+
+ LDAP_STAILQ_ENTRY(AttributeType) sat_next;
+
+#define sat_oid sat_atype.at_oid
+#define sat_names sat_atype.at_names
+#define sat_desc sat_atype.at_desc
+#define sat_obsolete sat_atype.at_obsolete
+#define sat_sup_oid sat_atype.at_sup_oid
+#define sat_equality_oid sat_atype.at_equality_oid
+#define sat_ordering_oid sat_atype.at_ordering_oid
+#define sat_substr_oid sat_atype.at_substr_oid
+#define sat_syntax_oid sat_atype.at_syntax_oid
+#define sat_single_value sat_atype.at_single_value
+#define sat_collective sat_atype.at_collective
+#define sat_no_user_mod sat_atype.at_no_user_mod
+#define sat_usage sat_atype.at_usage
+#define sat_extensions sat_atype.at_extensions
+
+ AttributeDescription *sat_ad;
+ ldap_pvt_thread_mutex_t sat_ad_mutex;
+};
+
+#define is_at_operational(at) ((at)->sat_usage)
+#define is_at_single_value(at) ((at)->sat_single_value)
+#define is_at_collective(at) ((at)->sat_collective)
+#define is_at_obsolete(at) ((at)->sat_obsolete)
+#define is_at_no_user_mod(at) ((at)->sat_no_user_mod)
+
+typedef int (ObjectClassSchemaCheckFN)(
+ BackendDB *be,
+ Entry *e,
+ ObjectClass *oc,
+ const char** text,
+ char *textbuf, size_t textlen );
+
+struct ObjectClass {
+ LDAPObjectClass soc_oclass;
+ struct berval soc_cname;
+ ObjectClass **soc_sups;
+ AttributeType **soc_required;
+ AttributeType **soc_allowed;
+ ObjectClassSchemaCheckFN *soc_check;
+ char *soc_oidmacro;
+ slap_mask_t soc_flags;
+#define soc_oid soc_oclass.oc_oid
+#define soc_names soc_oclass.oc_names
+#define soc_desc soc_oclass.oc_desc
+#define soc_obsolete soc_oclass.oc_obsolete
+#define soc_sup_oids soc_oclass.oc_sup_oids
+#define soc_kind soc_oclass.oc_kind
+#define soc_at_oids_must soc_oclass.oc_at_oids_must
+#define soc_at_oids_may soc_oclass.oc_at_oids_may
+#define soc_extensions soc_oclass.oc_extensions
+
+ LDAP_STAILQ_ENTRY(ObjectClass) soc_next;
+};
+
+#define SLAP_OCF_SET_FLAGS 0x1
+#define SLAP_OCF_CHECK_SUP 0x2
+#define SLAP_OCF_MASK (SLAP_OCF_SET_FLAGS|SLAP_OCF_CHECK_SUP)
+
+#define SLAP_OC_ALIAS 0x0001
+#define SLAP_OC_REFERRAL 0x0002
+#define SLAP_OC_SUBENTRY 0x0004
+#define SLAP_OC_DYNAMICOBJECT 0x0008
+#define SLAP_OC_COLLECTIVEATTRIBUTESUBENTRY 0x0010
+#define SLAP_OC_GLUE 0x0020
+#define SLAP_OC_SYNCPROVIDERSUBENTRY 0x0040
+#define SLAP_OC_SYNCCONSUMERSUBENTRY 0x0080
+#define SLAP_OC__MASK 0x00FF
+#define SLAP_OC__END 0x0100
+#define SLAP_OC_OPERATIONAL 0x4000
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_OC_HIDE 0x0000
+#else
+#define SLAP_OC_HIDE 0x8000
+#endif
+#define SLAP_OC_HARDCODE 0x10000U /* This is hardcoded schema */
+#define SLAP_OC_DELETED 0x20000U
+
+/*
+ * DIT content rule
+ */
+struct ContentRule {
+ LDAPContentRule scr_crule;
+ ObjectClass *scr_sclass;
+ ObjectClass **scr_auxiliaries; /* optional */
+ AttributeType **scr_required; /* optional */
+ AttributeType **scr_allowed; /* optional */
+ AttributeType **scr_precluded; /* optional */
+#define scr_oid scr_crule.cr_oid
+#define scr_names scr_crule.cr_names
+#define scr_desc scr_crule.cr_desc
+#define scr_obsolete scr_crule.cr_obsolete
+#define scr_oc_oids_aux scr_crule.cr_oc_oids_aux
+#define scr_at_oids_must scr_crule.cr_at_oids_must
+#define scr_at_oids_may scr_crule.cr_at_oids_may
+#define scr_at_oids_not scr_crule.cr_at_oids_not
+
+ char *scr_oidmacro;
+#define SLAP_CR_HARDCODE 0x10000U
+ int scr_flags;
+
+ LDAP_STAILQ_ENTRY( ContentRule ) scr_next;
+};
+
+/* Represents a recognized attribute description ( type + options ). */
+struct AttributeDescription {
+ AttributeDescription *ad_next;
+ AttributeType *ad_type; /* attribute type, must be specified */
+ struct berval ad_cname; /* canonical name, must be specified */
+ struct berval ad_tags; /* empty if no tagging options */
+ unsigned ad_flags;
+#define SLAP_DESC_NONE 0x00U
+#define SLAP_DESC_BINARY 0x01U
+#define SLAP_DESC_TAG_RANGE 0x80U
+#define SLAP_DESC_TEMPORARY 0x1000U
+ unsigned ad_index;
+};
+
+/* flags to slap_*2undef_ad to register undefined (0, the default)
+ * or proxied (SLAP_AD_PROXIED) AttributeDescriptions; the additional
+ * SLAP_AD_NOINSERT is to lookup without insert */
+#define SLAP_AD_UNDEF 0x00U
+#define SLAP_AD_PROXIED 0x01U
+#define SLAP_AD_NOINSERT 0x02U
+
+#define SLAP_AN_OCEXCLUDE 0x01
+#define SLAP_AN_OCINITED 0x02
+
+struct AttributeName {
+ struct berval an_name;
+ AttributeDescription *an_desc;
+ int an_flags;
+ ObjectClass *an_oc;
+};
+
+#define slap_ad_is_tagged(ad) ( (ad)->ad_tags.bv_len != 0 )
+#define slap_ad_is_tag_range(ad) \
+ ( ((ad)->ad_flags & SLAP_DESC_TAG_RANGE) ? 1 : 0 )
+#define slap_ad_is_binary(ad) \
+ ( ((ad)->ad_flags & SLAP_DESC_BINARY) ? 1 : 0 )
+
+/*
+ * pointers to schema elements used internally
+ */
+struct slap_internal_schema {
+ /* objectClass */
+ ObjectClass *si_oc_top;
+ ObjectClass *si_oc_extensibleObject;
+ ObjectClass *si_oc_alias;
+ ObjectClass *si_oc_referral;
+ ObjectClass *si_oc_rootdse;
+ ObjectClass *si_oc_subentry;
+ ObjectClass *si_oc_subschema;
+ ObjectClass *si_oc_collectiveAttributeSubentry;
+ ObjectClass *si_oc_dynamicObject;
+
+ ObjectClass *si_oc_glue;
+ ObjectClass *si_oc_syncConsumerSubentry;
+ ObjectClass *si_oc_syncProviderSubentry;
+
+ /* objectClass attribute descriptions */
+ AttributeDescription *si_ad_objectClass;
+
+ /* operational attribute descriptions */
+ AttributeDescription *si_ad_structuralObjectClass;
+ AttributeDescription *si_ad_creatorsName;
+ AttributeDescription *si_ad_createTimestamp;
+ AttributeDescription *si_ad_modifiersName;
+ AttributeDescription *si_ad_modifyTimestamp;
+ AttributeDescription *si_ad_hasSubordinates;
+ AttributeDescription *si_ad_subschemaSubentry;
+ AttributeDescription *si_ad_collectiveSubentries;
+ AttributeDescription *si_ad_collectiveExclusions;
+ AttributeDescription *si_ad_entryDN;
+ AttributeDescription *si_ad_entryUUID;
+ AttributeDescription *si_ad_entryCSN;
+ AttributeDescription *si_ad_namingCSN;
+
+ AttributeDescription *si_ad_dseType;
+ AttributeDescription *si_ad_syncreplCookie;
+ AttributeDescription *si_ad_syncTimestamp;
+ AttributeDescription *si_ad_contextCSN;
+
+ /* root DSE attribute descriptions */
+ AttributeDescription *si_ad_altServer;
+ AttributeDescription *si_ad_namingContexts;
+ AttributeDescription *si_ad_supportedControl;
+ AttributeDescription *si_ad_supportedExtension;
+ AttributeDescription *si_ad_supportedLDAPVersion;
+ AttributeDescription *si_ad_supportedSASLMechanisms;
+ AttributeDescription *si_ad_supportedFeatures;
+ AttributeDescription *si_ad_monitorContext;
+ AttributeDescription *si_ad_vendorName;
+ AttributeDescription *si_ad_vendorVersion;
+ AttributeDescription *si_ad_configContext;
+
+ /* subentry attribute descriptions */
+ AttributeDescription *si_ad_administrativeRole;
+ AttributeDescription *si_ad_subtreeSpecification;
+
+ /* subschema subentry attribute descriptions */
+ AttributeDescription *si_ad_attributeTypes;
+ AttributeDescription *si_ad_ditContentRules;
+ AttributeDescription *si_ad_ditStructureRules;
+ AttributeDescription *si_ad_ldapSyntaxes;
+ AttributeDescription *si_ad_matchingRules;
+ AttributeDescription *si_ad_matchingRuleUse;
+ AttributeDescription *si_ad_nameForms;
+ AttributeDescription *si_ad_objectClasses;
+
+ /* Aliases & Referrals */
+ AttributeDescription *si_ad_aliasedObjectName;
+ AttributeDescription *si_ad_ref;
+
+ /* Access Control Internals */
+ AttributeDescription *si_ad_entry;
+ AttributeDescription *si_ad_children;
+ AttributeDescription *si_ad_saslAuthzTo;
+ AttributeDescription *si_ad_saslAuthzFrom;
+
+ /* dynamic entries */
+ AttributeDescription *si_ad_entryTtl;
+ AttributeDescription *si_ad_dynamicSubtrees;
+
+ /* Other attributes descriptions */
+ AttributeDescription *si_ad_distinguishedName;
+ AttributeDescription *si_ad_name;
+ AttributeDescription *si_ad_cn;
+ AttributeDescription *si_ad_uid;
+ AttributeDescription *si_ad_uidNumber;
+ AttributeDescription *si_ad_gidNumber;
+ AttributeDescription *si_ad_userPassword;
+ AttributeDescription *si_ad_labeledURI;
+#ifdef SLAPD_AUTHPASSWD
+ AttributeDescription *si_ad_authPassword;
+ AttributeDescription *si_ad_authPasswordSchemes;
+#endif
+ AttributeDescription *si_ad_description;
+ AttributeDescription *si_ad_seeAlso;
+
+ /* privateKeys */
+ AttributeDescription *si_ad_pKCS8PrivateKey;
+
+ /* ppolicy lastbind equivalent */
+ AttributeDescription *si_ad_pwdLastSuccess;
+
+ /* Undefined Attribute Type */
+ AttributeType *si_at_undefined;
+
+ /* "Proxied" Attribute Type */
+ AttributeType *si_at_proxied;
+
+ /* Matching Rules */
+ MatchingRule *si_mr_distinguishedNameMatch;
+ MatchingRule *si_mr_dnSubtreeMatch;
+ MatchingRule *si_mr_dnOneLevelMatch;
+ MatchingRule *si_mr_dnSubordinateMatch;
+ MatchingRule *si_mr_dnSuperiorMatch;
+ MatchingRule *si_mr_caseExactMatch;
+ MatchingRule *si_mr_caseExactSubstringsMatch;
+ MatchingRule *si_mr_caseExactIA5Match;
+ MatchingRule *si_mr_integerMatch;
+ MatchingRule *si_mr_integerFirstComponentMatch;
+ MatchingRule *si_mr_objectIdentifierFirstComponentMatch;
+ MatchingRule *si_mr_caseIgnoreMatch;
+ MatchingRule *si_mr_caseIgnoreListMatch;
+
+ /* Syntaxes */
+ Syntax *si_syn_directoryString;
+ Syntax *si_syn_distinguishedName;
+ Syntax *si_syn_integer;
+ Syntax *si_syn_octetString;
+
+ /* Schema Syntaxes */
+ Syntax *si_syn_attributeTypeDesc;
+ Syntax *si_syn_ditContentRuleDesc;
+ Syntax *si_syn_ditStructureRuleDesc;
+ Syntax *si_syn_ldapSyntaxDesc;
+ Syntax *si_syn_matchingRuleDesc;
+ Syntax *si_syn_matchingRuleUseDesc;
+ Syntax *si_syn_nameFormDesc;
+ Syntax *si_syn_objectClassDesc;
+};
+
+struct AttributeAssertion {
+ AttributeDescription *aa_desc;
+ struct berval aa_value;
+#ifdef LDAP_COMP_MATCH
+ ComponentFilter *aa_cf; /* for attribute aliasing */
+#endif
+};
+#ifdef LDAP_COMP_MATCH
+#define ATTRIBUTEASSERTION_INIT { NULL, BER_BVNULL, NULL }
+#else
+#define ATTRIBUTEASSERTION_INIT { NULL, BER_BVNULL }
+#endif
+
+struct SubstringsAssertion {
+ AttributeDescription *sa_desc;
+ struct berval sa_initial;
+ struct berval *sa_any;
+ struct berval sa_final;
+};
+
+struct MatchingRuleAssertion {
+ AttributeDescription *ma_desc; /* optional */
+ struct berval ma_value; /* required */
+ MatchingRule *ma_rule; /* optional */
+ struct berval ma_rule_text; /* optional */
+ int ma_dnattrs; /* boolean */
+#ifdef LDAP_COMP_MATCH
+ ComponentFilter *ma_cf; /* component filter */
+#endif
+};
+
+/*
+ * represents a search filter
+ */
+struct Filter {
+ ber_tag_t f_choice; /* values taken from ldap.h, plus: */
+#define SLAPD_FILTER_COMPUTED 0
+#define SLAPD_FILTER_MASK 0x7fff
+#define SLAPD_FILTER_UNDEFINED 0x8000
+
+ union f_un_u {
+ /* precomputed result */
+ ber_int_t f_un_result;
+
+ /* present */
+ AttributeDescription *f_un_desc;
+
+ /* simple value assertion */
+ AttributeAssertion *f_un_ava;
+
+ /* substring assertion */
+ SubstringsAssertion *f_un_ssa;
+
+ /* matching rule assertion */
+ MatchingRuleAssertion *f_un_mra;
+
+#define f_desc f_un.f_un_desc
+#define f_ava f_un.f_un_ava
+#define f_av_desc f_un.f_un_ava->aa_desc
+#define f_av_value f_un.f_un_ava->aa_value
+#define f_sub f_un.f_un_ssa
+#define f_sub_desc f_un.f_un_ssa->sa_desc
+#define f_sub_initial f_un.f_un_ssa->sa_initial
+#define f_sub_any f_un.f_un_ssa->sa_any
+#define f_sub_final f_un.f_un_ssa->sa_final
+#define f_mra f_un.f_un_mra
+#define f_mr_rule f_un.f_un_mra->ma_rule
+#define f_mr_rule_text f_un.f_un_mra->ma_rule_text
+#define f_mr_desc f_un.f_un_mra->ma_desc
+#define f_mr_value f_un.f_un_mra->ma_value
+#define f_mr_dnattrs f_un.f_un_mra->ma_dnattrs
+
+ /* and, or, not */
+ Filter *f_un_complex;
+ } f_un;
+
+#define f_result f_un.f_un_result
+#define f_and f_un.f_un_complex
+#define f_or f_un.f_un_complex
+#define f_not f_un.f_un_complex
+#define f_list f_un.f_un_complex
+
+ Filter *f_next;
+};
+
+/* compare routines can return undefined */
+#define SLAPD_COMPARE_UNDEFINED ((ber_int_t) -1)
+
+struct ValuesReturnFilter {
+ ber_tag_t vrf_choice;
+
+ union vrf_un_u {
+ /* precomputed result */
+ ber_int_t vrf_un_result;
+
+ /* DN */
+ char *vrf_un_dn;
+
+ /* present */
+ AttributeDescription *vrf_un_desc;
+
+ /* simple value assertion */
+ AttributeAssertion *vrf_un_ava;
+
+ /* substring assertion */
+ SubstringsAssertion *vrf_un_ssa;
+
+ /* matching rule assertion */
+ MatchingRuleAssertion *vrf_un_mra;
+
+#define vrf_result vrf_un.vrf_un_result
+#define vrf_dn vrf_un.vrf_un_dn
+#define vrf_desc vrf_un.vrf_un_desc
+#define vrf_ava vrf_un.vrf_un_ava
+#define vrf_av_desc vrf_un.vrf_un_ava->aa_desc
+#define vrf_av_value vrf_un.vrf_un_ava->aa_value
+#define vrf_ssa vrf_un.vrf_un_ssa
+#define vrf_sub vrf_un.vrf_un_ssa
+#define vrf_sub_desc vrf_un.vrf_un_ssa->sa_desc
+#define vrf_sub_initial vrf_un.vrf_un_ssa->sa_initial
+#define vrf_sub_any vrf_un.vrf_un_ssa->sa_any
+#define vrf_sub_final vrf_un.vrf_un_ssa->sa_final
+#define vrf_mra vrf_un.vrf_un_mra
+#define vrf_mr_rule vrf_un.vrf_un_mra->ma_rule
+#define vrf_mr_rule_text vrf_un.vrf_un_mra->ma_rule_text
+#define vrf_mr_desc vrf_un.vrf_un_mra->ma_desc
+#define vrf_mr_value vrf_un.vrf_un_mra->ma_value
+#define vrf_mr_dnattrs vrf_un.vrf_un_mra->ma_dnattrs
+
+
+ } vrf_un;
+
+ ValuesReturnFilter *vrf_next;
+};
+
+/*
+ * represents an attribute (description + values)
+ * desc, vals, nvals, numvals fields must align with Modification
+ */
+struct Attribute {
+ AttributeDescription *a_desc;
+ BerVarray a_vals; /* preserved values */
+ BerVarray a_nvals; /* normalized values */
+ unsigned a_numvals; /* number of vals */
+ unsigned a_flags;
+#define SLAP_ATTR_IXADD 0x1U
+#define SLAP_ATTR_IXDEL 0x2U
+#define SLAP_ATTR_DONT_FREE_DATA 0x4U
+#define SLAP_ATTR_DONT_FREE_VALS 0x8U
+#define SLAP_ATTR_SORTED_VALS 0x10U /* values are sorted */
+#define SLAP_ATTR_BIG_MULTI 0x20U /* for backends */
+
+/* These flags persist across an attr_dup() */
+#define SLAP_ATTR_PERSISTENT_FLAGS \
+ (SLAP_ATTR_SORTED_VALS|SLAP_ATTR_BIG_MULTI)
+
+ Attribute *a_next;
+#ifdef LDAP_COMP_MATCH
+ ComponentData *a_comp_data; /* component values */
+#endif
+};
+
+
+/*
+ * the id used in the indexes to refer to an entry
+ */
+typedef unsigned long ID;
+#define NOID ((ID)~0)
+
+typedef struct EntryHeader {
+ struct berval bv;
+ char *data;
+ int nattrs;
+ int nvals;
+} EntryHeader;
+
+/*
+ * represents an entry in core
+ */
+struct Entry {
+ /*
+ * The ID field should only be changed before entry is
+ * inserted into a cache. The ID value is backend
+ * specific.
+ */
+ ID e_id;
+
+ struct berval e_name; /* name (DN) of this entry */
+ struct berval e_nname; /* normalized name (DN) of this entry */
+
+ /* for migration purposes */
+#define e_dn e_name.bv_val
+#define e_ndn e_nname.bv_val
+
+ Attribute *e_attrs; /* list of attributes + values */
+
+ slap_mask_t e_ocflags;
+
+ struct berval e_bv; /* For entry_encode/entry_decode */
+
+ /* for use by the backend for any purpose */
+ void* e_private;
+};
+
+/*
+ * A list of LDAPMods
+ * desc, values, nvalues, numvals must align with Attribute
+ */
+struct Modification {
+ AttributeDescription *sm_desc;
+ BerVarray sm_values;
+ BerVarray sm_nvalues;
+ unsigned sm_numvals;
+ short sm_op;
+ short sm_flags;
+/* Set for internal mods, will bypass ACL checks. Only needed when
+ * running as non-root user, for user modifiable attributes.
+ */
+#define SLAP_MOD_INTERNAL 0x01
+#define SLAP_MOD_MANAGING 0x02
+ struct berval sm_type;
+};
+
+struct Modifications {
+ Modification sml_mod;
+#define sml_op sml_mod.sm_op
+#define sml_flags sml_mod.sm_flags
+#define sml_desc sml_mod.sm_desc
+#define sml_type sml_mod.sm_type
+#define sml_values sml_mod.sm_values
+#define sml_nvalues sml_mod.sm_nvalues
+#define sml_numvals sml_mod.sm_numvals
+ Modifications *sml_next;
+};
+
+/*
+ * represents an access control list
+ */
+typedef enum slap_access_t {
+ ACL_INVALID_ACCESS = -1,
+ ACL_NONE = 0,
+ ACL_DISCLOSE,
+ ACL_AUTH,
+ ACL_COMPARE,
+ ACL_SEARCH,
+ ACL_READ,
+ ACL_WRITE_,
+ ACL_MANAGE,
+
+ /* always leave at end of levels but not greater than ACL_LEVEL_MASK */
+ ACL_LAST,
+
+ /* ACL level mask and modifiers */
+ ACL_LEVEL_MASK = 0x000f,
+ ACL_QUALIFIER1 = 0x0100,
+ ACL_QUALIFIER2 = 0x0200,
+ ACL_QUALIFIER3 = 0x0400,
+ ACL_QUALIFIER4 = 0x0800,
+ ACL_QUALIFIER_MASK = 0x0f00,
+
+ /* write granularity */
+ ACL_WADD = ACL_WRITE_|ACL_QUALIFIER1,
+ ACL_WDEL = ACL_WRITE_|ACL_QUALIFIER2,
+
+ ACL_WRITE = ACL_WADD|ACL_WDEL
+} slap_access_t;
+
+typedef enum slap_control_e {
+ ACL_INVALID_CONTROL = 0,
+ ACL_STOP,
+ ACL_CONTINUE,
+ ACL_BREAK
+} slap_control_t;
+
+typedef enum slap_style_e {
+ ACL_STYLE_REGEX = 0,
+ ACL_STYLE_EXPAND,
+ ACL_STYLE_BASE,
+ ACL_STYLE_ONE,
+ ACL_STYLE_SUBTREE,
+ ACL_STYLE_CHILDREN,
+ ACL_STYLE_LEVEL,
+ ACL_STYLE_ATTROF,
+ ACL_STYLE_ANONYMOUS,
+ ACL_STYLE_USERS,
+ ACL_STYLE_SELF,
+ ACL_STYLE_IP,
+ ACL_STYLE_IPV6,
+ ACL_STYLE_PATH,
+
+ ACL_STYLE_NONE
+} slap_style_t;
+
+typedef struct AuthorizationInformation {
+ ber_tag_t sai_method; /* LDAP_AUTH_* from <ldap.h> */
+ struct berval sai_mech; /* SASL Mechanism */
+ struct berval sai_dn; /* DN for reporting purposes */
+ struct berval sai_ndn; /* Normalized DN */
+
+ /* Security Strength Factors */
+ slap_ssf_t sai_ssf; /* Overall SSF */
+ slap_ssf_t sai_transport_ssf; /* Transport SSF */
+ slap_ssf_t sai_tls_ssf; /* TLS SSF */
+ slap_ssf_t sai_sasl_ssf; /* SASL SSF */
+} AuthorizationInformation;
+
+typedef struct config_args_s ConfigArgs; /* slap-config.h */
+typedef struct config_reply_s ConfigReply; /* slap-config.h */
+
+#ifdef SLAP_DYNACL
+
+/*
+ * "dynamic" ACL infrastructure (for ACIs and more)
+ */
+typedef int (slap_dynacl_parse) LDAP_P(( ConfigArgs *ca,
+ const char *opts, slap_style_t, const char *, void **privp ));
+typedef int (slap_dynacl_unparse) LDAP_P(( void *priv, struct berval *bv ));
+typedef int (slap_dynacl_mask) LDAP_P((
+ void *priv,
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ int nmatch,
+ regmatch_t *matches,
+ slap_access_t *grant,
+ slap_access_t *deny ));
+typedef int (slap_dynacl_destroy) LDAP_P(( void *priv ));
+
+typedef struct slap_dynacl_t {
+ char *da_name;
+ slap_dynacl_parse *da_parse;
+ slap_dynacl_unparse *da_unparse;
+ slap_dynacl_mask *da_mask;
+ slap_dynacl_destroy *da_destroy;
+
+ void *da_private;
+ struct slap_dynacl_t *da_next;
+} slap_dynacl_t;
+#endif /* SLAP_DYNACL */
+
+/* the DN portion of the "by" part */
+typedef struct slap_dn_access {
+ /* DN pattern */
+ AuthorizationInformation a_dnauthz;
+#define a_pat a_dnauthz.sai_dn
+
+ slap_style_t a_style;
+ int a_level;
+ int a_self_level;
+ AttributeDescription *a_at;
+ int a_self;
+ int a_expand;
+} slap_dn_access;
+
+/* the "by" part */
+typedef struct Access {
+ slap_control_t a_type;
+
+/* strip qualifiers */
+#define ACL_LEVEL(p) ((p) & ACL_LEVEL_MASK)
+#define ACL_QUALIFIERS(p) ((p) & ~ACL_LEVEL_MASK)
+
+#define ACL_ACCESS2PRIV(access) ((0x01U << ACL_LEVEL((access))) | ACL_QUALIFIERS((access)))
+
+#define ACL_PRIV_NONE ACL_ACCESS2PRIV( ACL_NONE )
+#define ACL_PRIV_DISCLOSE ACL_ACCESS2PRIV( ACL_DISCLOSE )
+#define ACL_PRIV_AUTH ACL_ACCESS2PRIV( ACL_AUTH )
+#define ACL_PRIV_COMPARE ACL_ACCESS2PRIV( ACL_COMPARE )
+#define ACL_PRIV_SEARCH ACL_ACCESS2PRIV( ACL_SEARCH )
+#define ACL_PRIV_READ ACL_ACCESS2PRIV( ACL_READ )
+#define ACL_PRIV_WADD ACL_ACCESS2PRIV( ACL_WADD )
+#define ACL_PRIV_WDEL ACL_ACCESS2PRIV( ACL_WDEL )
+#define ACL_PRIV_WRITE ( ACL_PRIV_WADD | ACL_PRIV_WDEL )
+#define ACL_PRIV_MANAGE ACL_ACCESS2PRIV( ACL_MANAGE )
+
+/* NOTE: always use the highest level; current: 0x00ffUL */
+#define ACL_PRIV_MASK ((ACL_ACCESS2PRIV(ACL_LAST) - 1) | ACL_QUALIFIER_MASK)
+
+/* priv flags */
+#define ACL_PRIV_LEVEL 0x1000UL
+#define ACL_PRIV_ADDITIVE 0x2000UL
+#define ACL_PRIV_SUBSTRACTIVE 0x4000UL
+
+/* invalid privs */
+#define ACL_PRIV_INVALID 0x0UL
+
+#define ACL_PRIV_ISSET(m,p) (((m) & (p)) == (p))
+#define ACL_PRIV_ASSIGN(m,p) do { (m) = (p); } while(0)
+#define ACL_PRIV_SET(m,p) do { (m) |= (p); } while(0)
+#define ACL_PRIV_CLR(m,p) do { (m) &= ~(p); } while(0)
+
+#define ACL_INIT(m) ACL_PRIV_ASSIGN((m), ACL_PRIV_NONE)
+#define ACL_INVALIDATE(m) ACL_PRIV_ASSIGN((m), ACL_PRIV_INVALID)
+
+#define ACL_GRANT(m,a) ACL_PRIV_ISSET((m),ACL_ACCESS2PRIV(a))
+
+#define ACL_IS_INVALID(m) ((m) == ACL_PRIV_INVALID)
+
+#define ACL_IS_LEVEL(m) ACL_PRIV_ISSET((m),ACL_PRIV_LEVEL)
+#define ACL_IS_ADDITIVE(m) ACL_PRIV_ISSET((m),ACL_PRIV_ADDITIVE)
+#define ACL_IS_SUBTRACTIVE(m) ACL_PRIV_ISSET((m),ACL_PRIV_SUBSTRACTIVE)
+
+#define ACL_LVL_NONE (ACL_PRIV_NONE|ACL_PRIV_LEVEL)
+#define ACL_LVL_DISCLOSE (ACL_PRIV_DISCLOSE|ACL_LVL_NONE)
+#define ACL_LVL_AUTH (ACL_PRIV_AUTH|ACL_LVL_DISCLOSE)
+#define ACL_LVL_COMPARE (ACL_PRIV_COMPARE|ACL_LVL_AUTH)
+#define ACL_LVL_SEARCH (ACL_PRIV_SEARCH|ACL_LVL_COMPARE)
+#define ACL_LVL_READ (ACL_PRIV_READ|ACL_LVL_SEARCH)
+#define ACL_LVL_WADD (ACL_PRIV_WADD|ACL_LVL_READ)
+#define ACL_LVL_WDEL (ACL_PRIV_WDEL|ACL_LVL_READ)
+#define ACL_LVL_WRITE (ACL_PRIV_WRITE|ACL_LVL_READ)
+#define ACL_LVL_MANAGE (ACL_PRIV_MANAGE|ACL_LVL_WRITE)
+
+#define ACL_LVL(m,l) (((m)&ACL_PRIV_MASK) == ((l)&ACL_PRIV_MASK))
+#define ACL_LVL_IS_NONE(m) ACL_LVL((m),ACL_LVL_NONE)
+#define ACL_LVL_IS_DISCLOSE(m) ACL_LVL((m),ACL_LVL_DISCLOSE)
+#define ACL_LVL_IS_AUTH(m) ACL_LVL((m),ACL_LVL_AUTH)
+#define ACL_LVL_IS_COMPARE(m) ACL_LVL((m),ACL_LVL_COMPARE)
+#define ACL_LVL_IS_SEARCH(m) ACL_LVL((m),ACL_LVL_SEARCH)
+#define ACL_LVL_IS_READ(m) ACL_LVL((m),ACL_LVL_READ)
+#define ACL_LVL_IS_WADD(m) ACL_LVL((m),ACL_LVL_WADD)
+#define ACL_LVL_IS_WDEL(m) ACL_LVL((m),ACL_LVL_WDEL)
+#define ACL_LVL_IS_WRITE(m) ACL_LVL((m),ACL_LVL_WRITE)
+#define ACL_LVL_IS_MANAGE(m) ACL_LVL((m),ACL_LVL_MANAGE)
+
+#define ACL_LVL_ASSIGN_NONE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_NONE)
+#define ACL_LVL_ASSIGN_DISCLOSE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_DISCLOSE)
+#define ACL_LVL_ASSIGN_AUTH(m) ACL_PRIV_ASSIGN((m),ACL_LVL_AUTH)
+#define ACL_LVL_ASSIGN_COMPARE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_COMPARE)
+#define ACL_LVL_ASSIGN_SEARCH(m) ACL_PRIV_ASSIGN((m),ACL_LVL_SEARCH)
+#define ACL_LVL_ASSIGN_READ(m) ACL_PRIV_ASSIGN((m),ACL_LVL_READ)
+#define ACL_LVL_ASSIGN_WADD(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WADD)
+#define ACL_LVL_ASSIGN_WDEL(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WDEL)
+#define ACL_LVL_ASSIGN_WRITE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_WRITE)
+#define ACL_LVL_ASSIGN_MANAGE(m) ACL_PRIV_ASSIGN((m),ACL_LVL_MANAGE)
+
+ slap_mask_t a_access_mask;
+
+ /* DN pattern */
+ slap_dn_access a_dn;
+#define a_dn_pat a_dn.a_dnauthz.sai_dn
+#define a_dn_at a_dn.a_at
+#define a_dn_self a_dn.a_self
+
+ /* real DN pattern */
+ slap_dn_access a_realdn;
+#define a_realdn_pat a_realdn.a_dnauthz.sai_dn
+#define a_realdn_at a_realdn.a_at
+#define a_realdn_self a_realdn.a_self
+
+ /* used for ssf stuff
+ * NOTE: the ssf stuff in a_realdn is ignored */
+#define a_authz a_dn.a_dnauthz
+
+ /* connection related stuff */
+ slap_style_t a_peername_style;
+ struct berval a_peername_pat;
+#ifdef LDAP_PF_INET6
+ union {
+ struct in6_addr ax6;
+ unsigned long ax;
+ } ax_peername_addr,
+ ax_peername_mask;
+#define a_peername_addr6 ax_peername_addr.ax6
+#define a_peername_addr ax_peername_addr.ax
+#define a_peername_mask6 ax_peername_mask.ax6
+#define a_peername_mask ax_peername_mask.ax
+/* apparently, only s6_addr is portable;
+ * define a portable address mask comparison */
+#define slap_addr6_mask(val, msk, asr) ( \
+ (((val)->s6_addr[0] & (msk)->s6_addr[0]) == (asr)->s6_addr[0]) \
+ && (((val)->s6_addr[1] & (msk)->s6_addr[1]) == (asr)->s6_addr[1]) \
+ && (((val)->s6_addr[2] & (msk)->s6_addr[2]) == (asr)->s6_addr[2]) \
+ && (((val)->s6_addr[3] & (msk)->s6_addr[3]) == (asr)->s6_addr[3]) \
+ && (((val)->s6_addr[4] & (msk)->s6_addr[4]) == (asr)->s6_addr[4]) \
+ && (((val)->s6_addr[5] & (msk)->s6_addr[5]) == (asr)->s6_addr[5]) \
+ && (((val)->s6_addr[6] & (msk)->s6_addr[6]) == (asr)->s6_addr[6]) \
+ && (((val)->s6_addr[7] & (msk)->s6_addr[7]) == (asr)->s6_addr[7]) \
+ && (((val)->s6_addr[8] & (msk)->s6_addr[8]) == (asr)->s6_addr[8]) \
+ && (((val)->s6_addr[9] & (msk)->s6_addr[9]) == (asr)->s6_addr[9]) \
+ && (((val)->s6_addr[10] & (msk)->s6_addr[10]) == (asr)->s6_addr[10]) \
+ && (((val)->s6_addr[11] & (msk)->s6_addr[11]) == (asr)->s6_addr[11]) \
+ && (((val)->s6_addr[12] & (msk)->s6_addr[12]) == (asr)->s6_addr[12]) \
+ && (((val)->s6_addr[13] & (msk)->s6_addr[13]) == (asr)->s6_addr[13]) \
+ && (((val)->s6_addr[14] & (msk)->s6_addr[14]) == (asr)->s6_addr[14]) \
+ && (((val)->s6_addr[15] & (msk)->s6_addr[15]) == (asr)->s6_addr[15]) \
+ )
+#else /* ! LDAP_PF_INET6 */
+ unsigned long a_peername_addr,
+ a_peername_mask;
+#endif /* ! LDAP_PF_INET6 */
+ int a_peername_port;
+
+ slap_style_t a_sockname_style;
+ struct berval a_sockname_pat;
+
+ slap_style_t a_domain_style;
+ struct berval a_domain_pat;
+ int a_domain_expand;
+
+ slap_style_t a_sockurl_style;
+ struct berval a_sockurl_pat;
+ slap_style_t a_set_style;
+ struct berval a_set_pat;
+
+#ifdef SLAP_DYNACL
+ slap_dynacl_t *a_dynacl;
+#endif /* SLAP_DYNACL */
+
+ /* ACL Groups */
+ slap_style_t a_group_style;
+ struct berval a_group_pat;
+ ObjectClass *a_group_oc;
+ AttributeDescription *a_group_at;
+
+ struct Access *a_next;
+} Access;
+
+/* the "to" part */
+typedef struct AccessControl {
+ /* "to" part: the entries this acl applies to */
+ Filter *acl_filter;
+ slap_style_t acl_dn_style;
+ regex_t acl_dn_re;
+ struct berval acl_dn_pat;
+ AttributeName *acl_attrs;
+ MatchingRule *acl_attrval_mr;
+ slap_style_t acl_attrval_style;
+ regex_t acl_attrval_re;
+ struct berval acl_attrval;
+
+ /* "by" part: list of who has what access to the entries */
+ Access *acl_access;
+
+ struct AccessControl *acl_next;
+} AccessControl;
+
+typedef struct AccessControlState {
+ /* Access state */
+
+ /* The stored state is valid when requesting as_access access
+ * to the as_desc attributes. */
+ AttributeDescription *as_desc;
+ slap_access_t as_access;
+
+ /* Value dependent acl where processing can restart */
+ AccessControl *as_vd_acl;
+ int as_vd_acl_present;
+ int as_vd_acl_count;
+ slap_mask_t as_vd_mask;
+
+ /* The cached result after evaluating a value independent attr.
+ * Only valid when != -1 and as_vd_acl == NULL */
+ int as_result;
+
+ /* True if started to process frontend ACLs */
+ int as_fe_done;
+} AccessControlState;
+#define ACL_STATE_INIT { NULL, ACL_NONE, NULL, 0, 0, ACL_PRIV_NONE, -1, 0 }
+
+typedef struct AclRegexMatches {
+ int dn_count;
+ regmatch_t dn_data[MAXREMATCHES];
+ int val_count;
+ regmatch_t val_data[MAXREMATCHES];
+} AclRegexMatches;
+
+/*
+ * Backend-info
+ * represents a backend
+ */
+
+typedef LDAP_STAILQ_HEAD(BeI, BackendInfo) slap_bi_head;
+typedef LDAP_STAILQ_HEAD(BeDB, BackendDB) slap_be_head;
+
+LDAP_SLAPD_V (int) nBackendInfo;
+LDAP_SLAPD_V (int) nBackendDB;
+LDAP_SLAPD_V (slap_bi_head) backendInfo;
+LDAP_SLAPD_V (slap_be_head) backendDB;
+LDAP_SLAPD_V (BackendDB *) frontendDB;
+
+LDAP_SLAPD_V (int) slapMode;
+#define SLAP_UNDEFINED_MODE 0x0000
+#define SLAP_SERVER_MODE 0x0001
+#define SLAP_TOOL_MODE 0x0002
+#define SLAP_MODE 0x0003
+
+#define SLAP_TRUNCATE_MODE 0x0100
+#define SLAP_TOOL_READMAIN 0x0200
+#define SLAP_TOOL_READONLY 0x0400
+#define SLAP_TOOL_QUICK 0x0800
+#define SLAP_TOOL_NO_SCHEMA_CHECK 0x1000
+#define SLAP_TOOL_VALUE_CHECK 0x2000
+#define SLAP_TOOL_DRYRUN 0x4000
+
+#define SLAP_SERVER_RUNNING 0x8000
+
+#define SB_TLS_DEFAULT (-1)
+#define SB_TLS_OFF 0
+#define SB_TLS_ON 1
+#define SB_TLS_CRITICAL 2
+
+enum slaptool {
+ SLAPADD=1, /* LDIF -> database tool */
+ SLAPCAT, /* database -> LDIF tool */
+ SLAPDN, /* DN check w/ syntax tool */
+ SLAPINDEX, /* database index tool */
+ SLAPMODIFY, /* database modify tool */
+ SLAPPASSWD, /* password generation tool */
+ SLAPSCHEMA, /* schema checking tool */
+ SLAPTEST, /* slapd.conf test tool */
+ SLAPAUTH, /* test authz-regexp and authc/authz stuff */
+ SLAPACL, /* test acl */
+ SLAPLAST
+};
+
+LDAP_SLAPD_V(enum slaptool) slapTool;
+
+typedef struct slap_keepalive {
+ int sk_idle;
+ int sk_probes;
+ int sk_interval;
+} slap_keepalive;
+
+typedef struct slap_bindconf {
+ struct berval sb_uri;
+ int sb_version;
+ int sb_tls;
+ int sb_method;
+ int sb_timeout_api;
+ int sb_timeout_net;
+ struct berval sb_binddn;
+ struct berval sb_cred;
+ struct berval sb_saslmech;
+ char *sb_secprops;
+ struct berval sb_realm;
+ struct berval sb_authcId;
+ struct berval sb_authzId;
+ slap_keepalive sb_keepalive;
+ unsigned int sb_tcp_user_timeout;
+#ifdef HAVE_TLS
+ void *sb_tls_ctx;
+ char *sb_tls_cert;
+ char *sb_tls_key;
+ char *sb_tls_cacert;
+ char *sb_tls_cacertdir;
+ char *sb_tls_reqcert;
+ char *sb_tls_reqsan;
+ char *sb_tls_cipher_suite;
+ char *sb_tls_protocol_min;
+ char *sb_tls_ecname;
+#ifdef HAVE_OPENSSL
+ char *sb_tls_crlcheck;
+#endif
+ int sb_tls_int_reqcert;
+ int sb_tls_int_reqsan;
+ int sb_tls_do_init;
+#endif
+} slap_bindconf;
+
+typedef struct slap_verbmasks {
+ struct berval word;
+ const slap_mask_t mask;
+} slap_verbmasks;
+
+typedef struct slap_cf_aux_table {
+ struct berval key;
+ int off;
+ char type;
+ char quote;
+ void *aux;
+} slap_cf_aux_table;
+
+typedef int
+slap_cf_aux_table_parse_x LDAP_P((
+ struct berval *val,
+ void *bc,
+ slap_cf_aux_table *tab0,
+ const char *tabmsg,
+ int unparse ));
+
+#define SLAP_LIMIT_TIME 1
+#define SLAP_LIMIT_SIZE 2
+
+struct slap_limits_set {
+ /* time limits */
+ int lms_t_soft;
+ int lms_t_hard;
+
+ /* size limits */
+ int lms_s_soft;
+ int lms_s_hard;
+ int lms_s_unchecked;
+ int lms_s_pr;
+ int lms_s_pr_hide;
+ int lms_s_pr_total;
+};
+
+/* Note: this is different from LDAP_NO_LIMIT (0); slapd internal use only */
+#define SLAP_NO_LIMIT -1
+#define SLAP_MAX_LIMIT 2147483647
+
+struct slap_limits {
+ unsigned lm_flags; /* type of pattern */
+ /* Values must match lmpats[] in limits.c */
+#define SLAP_LIMITS_UNDEFINED 0x0000U
+#define SLAP_LIMITS_EXACT 0x0001U
+#define SLAP_LIMITS_BASE SLAP_LIMITS_EXACT
+#define SLAP_LIMITS_ONE 0x0002U
+#define SLAP_LIMITS_SUBTREE 0x0003U
+#define SLAP_LIMITS_CHILDREN 0x0004U
+#define SLAP_LIMITS_REGEX 0x0005U
+#define SLAP_LIMITS_ANONYMOUS 0x0006U
+#define SLAP_LIMITS_USERS 0x0007U
+#define SLAP_LIMITS_ANY 0x0008U
+#define SLAP_LIMITS_MASK 0x000FU
+
+#define SLAP_LIMITS_TYPE_SELF 0x0000U
+#define SLAP_LIMITS_TYPE_DN SLAP_LIMITS_TYPE_SELF
+#define SLAP_LIMITS_TYPE_GROUP 0x0010U
+#define SLAP_LIMITS_TYPE_THIS 0x0020U
+#define SLAP_LIMITS_TYPE_MASK 0x00F0U
+
+ regex_t lm_regex; /* regex data for REGEX */
+
+ /*
+ * normalized DN for EXACT, BASE, ONE, SUBTREE, CHILDREN;
+ * pattern for REGEX; NULL for ANONYMOUS, USERS
+ */
+ struct berval lm_pat;
+
+ /* if lm_flags & SLAP_LIMITS_TYPE_MASK == SLAP_LIMITS_GROUP,
+ * lm_group_oc is objectClass and lm_group_at is attributeType
+ * of member in oc for match; then lm_flags & SLAP_LIMITS_MASK
+ * can only be SLAP_LIMITS_EXACT */
+ ObjectClass *lm_group_oc;
+ AttributeDescription *lm_group_ad;
+
+ struct slap_limits_set lm_limits;
+};
+
+/* temporary aliases */
+typedef BackendDB Backend;
+#define nbackends nBackendDB
+#define backends backendDB
+
+/*
+ * syncinfo structure for syncrepl
+ */
+
+struct syncinfo_s;
+
+#define SLAP_SYNC_RID_MAX 999
+#define SLAP_SYNC_SID_MAX 4095 /* based on liblutil/csn.c field width */
+
+/* fake conn connid constructed as rid; real connids start
+ * at SLAPD_SYNC_CONN_OFFSET */
+#define SLAPD_SYNC_SYNCCONN_OFFSET (SLAP_SYNC_RID_MAX + 1)
+#define SLAPD_SYNC_IS_SYNCCONN(connid) ((connid) < SLAPD_SYNC_SYNCCONN_OFFSET)
+#define SLAPD_SYNC_RID2SYNCCONN(rid) (rid)
+
+#define SLAP_SYNCUUID_SET_SIZE 256
+
+struct sync_cookie {
+ BerVarray ctxcsn;
+ int *sids;
+ int numcsns;
+ int rid;
+ struct berval octet_str;
+ struct berval delcsn;
+ int sid;
+ LDAP_STAILQ_ENTRY(sync_cookie) sc_next;
+};
+
+LDAP_STAILQ_HEAD( slap_sync_cookie_s, sync_cookie );
+
+/* Defs for pending_csn_list */
+LDAP_TAILQ_HEAD( be_pclh, slap_csn_entry );
+
+typedef struct be_pcsn {
+ struct be_pclh be_pcsn_list;
+ ldap_pvt_thread_mutex_t be_pcsn_mutex;
+} be_pcsn;
+
+#ifndef SLAP_MAX_CIDS
+#define SLAP_MAX_CIDS 32 /* Maximum number of supported controls */
+#endif
+
+struct ConfigOCs; /* slap-config.h */
+
+struct BackendDB {
+ BackendInfo *bd_info; /* pointer to shared backend info */
+ BackendDB *bd_self; /* pointer to this struct */
+
+ /* fields in this structure (and routines acting on this structure)
+ should be renamed from be_ to bd_ */
+
+ /* BackendInfo accessors */
+#define be_config bd_info->bi_db_config
+#define be_type bd_info->bi_type
+
+#define be_bind bd_info->bi_op_bind
+#define be_unbind bd_info->bi_op_unbind
+#define be_add bd_info->bi_op_add
+#define be_compare bd_info->bi_op_compare
+#define be_delete bd_info->bi_op_delete
+#define be_modify bd_info->bi_op_modify
+#define be_modrdn bd_info->bi_op_modrdn
+#define be_search bd_info->bi_op_search
+#define be_abandon bd_info->bi_op_abandon
+
+#define be_extended bd_info->bi_extended
+#define be_cancel bd_info->bi_op_cancel
+
+#define be_chk_referrals bd_info->bi_chk_referrals
+#define be_chk_controls bd_info->bi_chk_controls
+#define be_fetch bd_info->bi_entry_get_rw
+#define be_release bd_info->bi_entry_release_rw
+#define be_group bd_info->bi_acl_group
+#define be_attribute bd_info->bi_acl_attribute
+#define be_operational bd_info->bi_operational
+
+/*
+ * define to honor hasSubordinates operational attribute in search filters
+ */
+#define be_has_subordinates bd_info->bi_has_subordinates
+
+#define be_connection_init bd_info->bi_connection_init
+#define be_connection_destroy bd_info->bi_connection_destroy
+
+#ifdef SLAPD_TOOLS
+#define be_entry_open bd_info->bi_tool_entry_open
+#define be_entry_close bd_info->bi_tool_entry_close
+#define be_entry_first bd_info->bi_tool_entry_first
+#define be_entry_first_x bd_info->bi_tool_entry_first_x
+#define be_entry_next bd_info->bi_tool_entry_next
+#define be_entry_reindex bd_info->bi_tool_entry_reindex
+#define be_entry_get bd_info->bi_tool_entry_get
+#define be_entry_put bd_info->bi_tool_entry_put
+#define be_sync bd_info->bi_tool_sync
+#define be_dn2id_get bd_info->bi_tool_dn2id_get
+#define be_entry_modify bd_info->bi_tool_entry_modify
+#define be_entry_delete bd_info->bi_tool_entry_delete
+#endif
+
+ /* supported controls */
+ /* note: set to 0 if the database does not support the control;
+ * be_ctrls[SLAP_MAX_CIDS] is set to 1 if initialized */
+ char be_ctrls[SLAP_MAX_CIDS + 1];
+
+/* Database flags */
+#define SLAP_DBFLAG_NOLASTMOD 0x0001U
+#define SLAP_DBFLAG_NO_SCHEMA_CHECK 0x0002U
+#define SLAP_DBFLAG_HIDDEN 0x0004U
+#define SLAP_DBFLAG_ONE_SUFFIX 0x0008U
+#define SLAP_DBFLAG_GLUE_INSTANCE 0x0010U /* a glue backend */
+#define SLAP_DBFLAG_GLUE_SUBORDINATE 0x0020U /* child of a glue hierarchy */
+#define SLAP_DBFLAG_GLUE_LINKED 0x0040U /* child is connected to parent */
+#define SLAP_DBFLAG_GLUE_ADVERTISE 0x0080U /* advertise in rootDSE */
+#define SLAP_DBFLAG_OVERLAY 0x0100U /* this db struct is an overlay */
+#define SLAP_DBFLAG_GLOBAL_OVERLAY 0x0200U /* this db struct is a global overlay */
+#define SLAP_DBFLAG_DYNAMIC 0x0400U /* this db allows dynamicObjects */
+#define SLAP_DBFLAG_MONITORING 0x0800U /* custom monitoring enabled */
+#define SLAP_DBFLAG_SHADOW 0x8000U /* a shadow */
+#define SLAP_DBFLAG_SINGLE_SHADOW 0x4000U /* a single-provider shadow */
+#define SLAP_DBFLAG_SYNC_SHADOW 0x1000U /* a sync shadow */
+#define SLAP_DBFLAG_SLURP_SHADOW 0x2000U /* a slurp shadow */
+#define SLAP_DBFLAG_SHADOW_MASK (SLAP_DBFLAG_SHADOW|SLAP_DBFLAG_SINGLE_SHADOW|SLAP_DBFLAG_SYNC_SHADOW|SLAP_DBFLAG_SLURP_SHADOW)
+#define SLAP_DBFLAG_CLEAN 0x10000U /* was cleanly shutdown */
+#define SLAP_DBFLAG_ACL_ADD 0x20000U /* check attr ACLs on adds */
+#define SLAP_DBFLAG_SYNC_SUBENTRY 0x40000U /* use subentry for context */
+#define SLAP_DBFLAG_MULTI_SHADOW 0x80000U /* uses multi-provider */
+#define SLAP_DBFLAG_DISABLED 0x100000U
+#define SLAP_DBFLAG_LASTBIND 0x200000U
+#define SLAP_DBFLAG_OPEN 0x400000U /* db is currently open */
+ slap_mask_t be_flags;
+#define SLAP_DBFLAGS(be) ((be)->be_flags)
+#define SLAP_NOLASTMOD(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_NOLASTMOD)
+#define SLAP_LASTMOD(be) (!SLAP_NOLASTMOD(be))
+#define SLAP_LASTBIND(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_LASTBIND)
+#define SLAP_DBHIDDEN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_HIDDEN)
+#define SLAP_DBDISABLED(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DISABLED)
+#define SLAP_DB_ONE_SUFFIX(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_ONE_SUFFIX)
+#define SLAP_ISOVERLAY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_OVERLAY)
+#define SLAP_ISGLOBALOVERLAY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLOBAL_OVERLAY)
+#define SLAP_DBMONITORING(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_MONITORING)
+#define SLAP_NO_SCHEMA_CHECK(be) \
+ (SLAP_DBFLAGS(be) & SLAP_DBFLAG_NO_SCHEMA_CHECK)
+#define SLAP_GLUE_INSTANCE(be) \
+ (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_INSTANCE)
+#define SLAP_GLUE_SUBORDINATE(be) \
+ (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_SUBORDINATE)
+#define SLAP_GLUE_LINKED(be) \
+ (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_LINKED)
+#define SLAP_GLUE_ADVERTISE(be) \
+ (SLAP_DBFLAGS(be) & SLAP_DBFLAG_GLUE_ADVERTISE)
+#define SLAP_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SHADOW)
+#define SLAP_SYNC_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SYNC_SHADOW)
+#define SLAP_SLURP_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SLURP_SHADOW)
+#define SLAP_SINGLE_SHADOW(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SINGLE_SHADOW)
+#define SLAP_MULTIPROVIDER(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_MULTI_SHADOW)
+#define SLAP_DBCLEAN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_CLEAN)
+#define SLAP_DBOPEN(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_OPEN)
+#define SLAP_DBACL_ADD(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_ACL_ADD)
+#define SLAP_SYNC_SUBENTRY(be) (SLAP_DBFLAGS(be) & SLAP_DBFLAG_SYNC_SUBENTRY)
+
+ slap_mask_t be_restrictops; /* restriction operations */
+#define SLAP_RESTRICT_OP_ADD 0x0001U
+#define SLAP_RESTRICT_OP_BIND 0x0002U
+#define SLAP_RESTRICT_OP_COMPARE 0x0004U
+#define SLAP_RESTRICT_OP_DELETE 0x0008U
+#define SLAP_RESTRICT_OP_EXTENDED 0x0010U
+#define SLAP_RESTRICT_OP_MODIFY 0x0020U
+#define SLAP_RESTRICT_OP_RENAME 0x0040U
+#define SLAP_RESTRICT_OP_SEARCH 0x0080U
+#define SLAP_RESTRICT_OP_MASK 0x00FFU
+
+#define SLAP_RESTRICT_READONLY 0x80000000U
+
+#define SLAP_RESTRICT_EXOP_START_TLS 0x0100U
+#define SLAP_RESTRICT_EXOP_MODIFY_PASSWD 0x0200U
+#define SLAP_RESTRICT_EXOP_WHOAMI 0x0400U
+#define SLAP_RESTRICT_EXOP_CANCEL 0x0800U
+#define SLAP_RESTRICT_EXOP_MASK 0xFF00U
+
+#define SLAP_RESTRICT_OP_READS \
+ ( SLAP_RESTRICT_OP_COMPARE \
+ | SLAP_RESTRICT_OP_SEARCH )
+#define SLAP_RESTRICT_OP_WRITES \
+ ( SLAP_RESTRICT_OP_ADD \
+ | SLAP_RESTRICT_OP_DELETE \
+ | SLAP_RESTRICT_OP_MODIFY \
+ | SLAP_RESTRICT_OP_RENAME )
+#define SLAP_RESTRICT_OP_ALL \
+ ( SLAP_RESTRICT_OP_READS \
+ | SLAP_RESTRICT_OP_WRITES \
+ | SLAP_RESTRICT_OP_BIND \
+ | SLAP_RESTRICT_OP_EXTENDED )
+
+#define SLAP_ALLOW_BIND_V2 0x0001U /* LDAPv2 bind */
+#define SLAP_ALLOW_BIND_ANON_CRED 0x0002U /* cred should be empty */
+#define SLAP_ALLOW_BIND_ANON_DN 0x0004U /* dn should be empty */
+
+#define SLAP_ALLOW_UPDATE_ANON 0x0008U /* allow anonymous updates */
+#define SLAP_ALLOW_PROXY_AUTHZ_ANON 0x0010U /* allow anonymous proxyAuthz */
+
+#define SLAP_DISALLOW_BIND_ANON 0x0001U /* no anonymous */
+#define SLAP_DISALLOW_BIND_SIMPLE 0x0002U /* simple authentication */
+
+#define SLAP_DISALLOW_TLS_2_ANON 0x0010U /* StartTLS -> Anonymous */
+#define SLAP_DISALLOW_TLS_AUTHC 0x0020U /* TLS while authenticated */
+
+#define SLAP_DISALLOW_PROXY_AUTHZ_N_CRIT 0x0100U
+#define SLAP_DISALLOW_DONTUSECOPY_N_CRIT 0x0200U
+
+#define SLAP_DISALLOW_AUX_WO_CR 0x4000U
+
+ slap_mask_t be_requires; /* pre-operation requirements */
+#define SLAP_REQUIRE_BIND 0x0001U /* bind before op */
+#define SLAP_REQUIRE_LDAP_V3 0x0002U /* LDAPv3 before op */
+#define SLAP_REQUIRE_AUTHC 0x0004U /* authentication before op */
+#define SLAP_REQUIRE_SASL 0x0008U /* SASL before op */
+#define SLAP_REQUIRE_STRONG 0x0010U /* strong authentication before op */
+
+ /* Required Security Strength Factor */
+ slap_ssf_set_t be_ssf_set;
+
+ BerVarray be_suffix; /* the DN suffixes of data in this backend */
+ BerVarray be_nsuffix; /* the normalized DN suffixes in this backend */
+ struct berval be_schemadn; /* per-backend subschema subentry DN */
+ struct berval be_schemandn; /* normalized subschema DN */
+ struct berval be_rootdn; /* the magic "root" name (DN) for this db */
+ struct berval be_rootndn; /* the magic "root" normalized name (DN) for this db */
+ struct berval be_rootpw; /* the magic "root" password for this db */
+ unsigned int be_max_deref_depth; /* limit for depth of an alias deref */
+#define be_sizelimit be_def_limit.lms_s_soft
+#define be_timelimit be_def_limit.lms_t_soft
+ struct slap_limits_set be_def_limit; /* default limits */
+ struct slap_limits **be_limits; /* regex-based size and time limits */
+ AccessControl *be_acl; /* access control list for this backend */
+ slap_access_t be_dfltaccess; /* access given if no acl matches */
+ AttributeName *be_extra_anlist; /* attributes that need to be added to search requests (ITS#6513) */
+
+ unsigned int be_lastbind_precision;
+
+ /* Consumer Information */
+ struct berval be_update_ndn; /* allowed to make changes (in replicas) */
+ BerVarray be_update_refs; /* where to refer modifying clients to */
+ be_pcsn be_pcsn_st; /* be_pending_csn_list now inside this */
+ be_pcsn *be_pcsn_p;
+ struct syncinfo_s *be_syncinfo; /* For syncrepl */
+
+ void *be_pb; /* Netscape plugin */
+ struct ConfigOCs *be_cf_ocs;
+
+ void *be_private; /* anything the backend database needs */
+ LDAP_STAILQ_ENTRY(BackendDB) be_next;
+};
+
+/* Backend function typedefs */
+typedef int (BI_bi_func) LDAP_P((BackendInfo *bi));
+typedef BI_bi_func BI_init;
+typedef BI_bi_func BI_open;
+typedef BI_bi_func BI_pause;
+typedef BI_bi_func BI_unpause;
+typedef BI_bi_func BI_close;
+typedef BI_bi_func BI_destroy;
+typedef int (BI_config) LDAP_P((BackendInfo *bi,
+ const char *fname, int lineno,
+ int argc, char **argv));
+
+typedef int (BI_db_func) LDAP_P((Backend *bd, ConfigReply *cr));
+typedef BI_db_func BI_db_init;
+typedef BI_db_func BI_db_open;
+typedef BI_db_func BI_db_close;
+typedef BI_db_func BI_db_destroy;
+typedef int (BI_db_config) LDAP_P((Backend *bd,
+ const char *fname, int lineno,
+ int argc, char **argv));
+
+typedef struct req_bind_s {
+ int rb_method;
+ struct berval rb_cred;
+ struct berval rb_edn;
+ slap_ssf_t rb_ssf;
+ struct berval rb_mech;
+} req_bind_s;
+
+typedef struct req_search_s {
+ int rs_scope;
+ int rs_deref;
+ int rs_slimit;
+ int rs_tlimit;
+ /* NULL means be_isroot evaluated to TRUE */
+ struct slap_limits_set *rs_limit;
+ int rs_attrsonly;
+ AttributeName *rs_attrs;
+ Filter *rs_filter;
+ struct berval rs_filterstr;
+} req_search_s;
+
+typedef struct req_compare_s {
+ AttributeAssertion *rs_ava;
+} req_compare_s;
+
+typedef struct req_modifications_s {
+ Modifications *rs_modlist;
+ char rs_no_opattrs; /* don't att modify operational attrs */
+} req_modifications_s;
+
+typedef struct req_modify_s {
+ req_modifications_s rs_mods; /* NOTE: must be first in req_modify_s & req_modrdn_s */
+ int rs_increment;
+} req_modify_s;
+
+typedef struct req_modrdn_s {
+ req_modifications_s rs_mods; /* NOTE: must be first in req_modify_s & req_modrdn_s */
+ int rs_deleteoldrdn;
+ struct berval rs_newrdn;
+ struct berval rs_nnewrdn;
+ struct berval *rs_newSup;
+ struct berval *rs_nnewSup;
+ struct berval rs_newDN;
+ struct berval rs_nnewDN;
+} req_modrdn_s;
+
+typedef struct req_add_s {
+ Modifications *rs_modlist;
+ Entry *rs_e;
+} req_add_s;
+
+typedef struct req_abandon_s {
+ ber_int_t rs_msgid;
+} req_abandon_s;
+
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_EXOP_HIDE 0x0000
+#else
+#define SLAP_EXOP_HIDE 0x8000
+#endif
+#define SLAP_EXOP_WRITES 0x0001 /* Exop does writes */
+
+typedef struct req_extended_s {
+ struct berval rs_reqoid;
+ int rs_flags;
+ struct berval *rs_reqdata;
+} req_extended_s;
+
+typedef struct req_pwdexop_s {
+ struct req_extended_s rs_extended;
+ struct berval rs_old;
+ struct berval rs_new;
+ Modifications *rs_mods;
+ Modifications **rs_modtail;
+} req_pwdexop_s;
+
+typedef enum slap_reply_e {
+ REP_RESULT,
+ REP_SASL,
+ REP_EXTENDED,
+ REP_SEARCH,
+ REP_SEARCHREF,
+ REP_INTERMEDIATE,
+ REP_GLUE_RESULT
+} slap_reply_t;
+
+typedef struct rep_sasl_s {
+ struct berval *r_sasldata;
+} rep_sasl_s;
+
+typedef struct rep_extended_s {
+ const char *r_rspoid;
+ struct berval *r_rspdata;
+} rep_extended_s;
+
+typedef struct rep_search_s {
+ Entry *r_entry;
+ slap_mask_t r_attr_flags;
+#define SLAP_ATTRS_UNDEFINED (0x00U)
+#define SLAP_OPATTRS_NO (0x01U)
+#define SLAP_OPATTRS_YES (0x02U)
+#define SLAP_USERATTRS_NO (0x10U)
+#define SLAP_USERATTRS_YES (0x20U)
+#define SLAP_OPATTRS_MASK(f) ((f) & (SLAP_OPATTRS_NO|SLAP_OPATTRS_YES))
+#define SLAP_OPATTRS(f) (((f) & SLAP_OPATTRS_YES) == SLAP_OPATTRS_YES)
+#define SLAP_USERATTRS_MASK(f) ((f) & (SLAP_USERATTRS_NO|SLAP_USERATTRS_YES))
+#define SLAP_USERATTRS(f) \
+ (((f) & SLAP_USERATTRS_YES) == SLAP_USERATTRS_YES)
+
+ Attribute *r_operational_attrs;
+ AttributeName *r_attrs;
+ int r_nentries;
+ BerVarray r_v2ref;
+} rep_search_s;
+
+struct SlapReply {
+ slap_reply_t sr_type;
+ ber_tag_t sr_tag;
+ ber_int_t sr_msgid;
+ ber_int_t sr_err;
+ const char *sr_matched;
+ const char *sr_text;
+ BerVarray sr_ref;
+ LDAPControl **sr_ctrls;
+ union sr_u {
+ rep_search_s sru_search;
+ rep_sasl_s sru_sasl;
+ rep_extended_s sru_extended;
+ } sr_un;
+ slap_mask_t sr_flags;
+#define REP_ENTRY_MODIFIABLE ((slap_mask_t) 0x0001U)
+#define REP_ENTRY_MUSTBEFREED ((slap_mask_t) 0x0002U)
+#define REP_ENTRY_MUSTRELEASE ((slap_mask_t) 0x0004U)
+#define REP_ENTRY_MASK (REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTFLUSH)
+#define REP_ENTRY_MUSTFLUSH (REP_ENTRY_MUSTBEFREED|REP_ENTRY_MUSTRELEASE)
+
+#define REP_MATCHED_MUSTBEFREED ((slap_mask_t) 0x0010U)
+#define REP_MATCHED_MASK (REP_MATCHED_MUSTBEFREED)
+
+#define REP_REF_MUSTBEFREED ((slap_mask_t) 0x0020U)
+#define REP_REF_MASK (REP_REF_MUSTBEFREED)
+
+#define REP_CTRLS_MUSTBEFREED ((slap_mask_t) 0x0040U)
+#define REP_CTRLS_MASK (REP_CTRLS_MUSTBEFREED)
+
+#define REP_NO_ENTRYDN ((slap_mask_t) 0x1000U)
+#define REP_NO_SUBSCHEMA ((slap_mask_t) 0x2000U)
+#define REP_NO_OPERATIONALS (REP_NO_ENTRYDN|REP_NO_SUBSCHEMA)
+};
+
+/* short hands for response members */
+#define sr_attrs sr_un.sru_search.r_attrs
+#define sr_entry sr_un.sru_search.r_entry
+#define sr_operational_attrs sr_un.sru_search.r_operational_attrs
+#define sr_attr_flags sr_un.sru_search.r_attr_flags
+#define sr_v2ref sr_un.sru_search.r_v2ref
+#define sr_nentries sr_un.sru_search.r_nentries
+#define sr_rspoid sr_un.sru_extended.r_rspoid
+#define sr_rspdata sr_un.sru_extended.r_rspdata
+#define sr_sasldata sr_un.sru_sasl.r_sasldata
+
+typedef int (BI_op_func) LDAP_P(( Operation *op, SlapReply *rs ));
+typedef BI_op_func BI_op_bind;
+typedef BI_op_func BI_op_unbind;
+typedef BI_op_func BI_op_search;
+typedef BI_op_func BI_op_compare;
+typedef BI_op_func BI_op_modify;
+typedef BI_op_func BI_op_modrdn;
+typedef BI_op_func BI_op_add;
+typedef BI_op_func BI_op_delete;
+typedef BI_op_func BI_op_abandon;
+typedef BI_op_func BI_op_extended;
+typedef BI_op_func BI_op_cancel;
+typedef BI_op_func BI_chk_referrals;
+typedef BI_op_func BI_chk_controls;
+typedef int (BI_entry_release_rw)
+ LDAP_P(( Operation *op, Entry *e, int rw ));
+typedef int (BI_entry_get_rw) LDAP_P(( Operation *op, struct berval *ndn,
+ ObjectClass *oc, AttributeDescription *at, int rw, Entry **e ));
+typedef int (BI_operational) LDAP_P(( Operation *op, SlapReply *rs ));
+typedef int (BI_has_subordinates) LDAP_P(( Operation *op,
+ Entry *e, int *hasSubs ));
+typedef int (BI_access_allowed) LDAP_P(( Operation *op, Entry *e,
+ AttributeDescription *desc, struct berval *val, slap_access_t access,
+ AccessControlState *state, slap_mask_t *maskp ));
+typedef int (BI_acl_group) LDAP_P(( Operation *op, Entry *target,
+ struct berval *gr_ndn, struct berval *op_ndn,
+ ObjectClass *group_oc, AttributeDescription *group_at ));
+typedef int (BI_acl_attribute) LDAP_P(( Operation *op, Entry *target,
+ struct berval *entry_ndn, AttributeDescription *entry_at,
+ BerVarray *vals, slap_access_t access ));
+struct OpExtra;
+typedef int (BI_op_txn) LDAP_P(( Operation *op, int txnop, struct OpExtra **ptr ));
+#define SLAP_TXN_BEGIN 1
+#define SLAP_TXN_COMMIT 2
+#define SLAP_TXN_ABORT 3
+
+typedef int (BI_conn_func) LDAP_P(( BackendDB *bd, Connection *c ));
+typedef BI_conn_func BI_connection_init;
+typedef BI_conn_func BI_connection_destroy;
+
+typedef int (BI_tool_entry_open) LDAP_P(( BackendDB *be, int mode ));
+typedef int (BI_tool_entry_close) LDAP_P(( BackendDB *be ));
+typedef ID (BI_tool_entry_first) LDAP_P(( BackendDB *be ));
+typedef ID (BI_tool_entry_first_x) LDAP_P(( BackendDB *be, struct berval *base, int scope, Filter *f ));
+typedef ID (BI_tool_entry_next) LDAP_P(( BackendDB *be ));
+typedef Entry* (BI_tool_entry_get) LDAP_P(( BackendDB *be, ID id ));
+typedef ID (BI_tool_entry_put) LDAP_P(( BackendDB *be, Entry *e,
+ struct berval *text ));
+typedef int (BI_tool_entry_reindex) LDAP_P(( BackendDB *be, ID id, AttributeDescription **adv ));
+typedef int (BI_tool_sync) LDAP_P(( BackendDB *be ));
+typedef ID (BI_tool_dn2id_get) LDAP_P(( BackendDB *be, struct berval *dn ));
+typedef ID (BI_tool_entry_modify) LDAP_P(( BackendDB *be, Entry *e,
+ struct berval *text ));
+typedef int (BI_tool_entry_delete) LDAP_P(( BackendDB *be, struct berval *ndn,
+ struct berval *text ));
+
+struct BackendInfo {
+ char *bi_type; /* type of backend */
+
+ /*
+ * per backend type routines:
+ * bi_init: called to allocate a backend_info structure,
+ * called once BEFORE configuration file is read.
+ * bi_init() initializes this structure hence is
+ * called directly from be_initialize()
+ * bi_config: called per 'backend' specific option
+ * all such options must before any 'database' options
+ * bi_config() is called only from read_config()
+ * bi_open: called to open each database, called
+ * once AFTER configuration file is read but
+ * BEFORE any bi_db_open() calls.
+ * bi_open() is called from backend_startup()
+ * bi_close: called to close each database, called
+ * once during shutdown after all bi_db_close calls.
+ * bi_close() is called from backend_shutdown()
+ * bi_destroy: called to destroy each database, called
+ * once during shutdown after all bi_db_destroy calls.
+ * bi_destroy() is called from backend_destroy()
+ */
+ BI_init *bi_init;
+ BI_config *bi_config;
+ BI_open *bi_open;
+ BI_pause *bi_pause;
+ BI_unpause *bi_unpause;
+ BI_close *bi_close;
+ BI_destroy *bi_destroy;
+
+ /*
+ * per database routines:
+ * bi_db_init: called to initialize each database,
+ * called upon reading 'database <type>'
+ * called only from backend_db_init()
+ * bi_db_config: called to configure each database,
+ * called per database to handle per database options
+ * called only from read_config()
+ * bi_db_open: called to open each database
+ * called once per database immediately AFTER bi_open()
+ * calls but before daemon startup.
+ * called only by backend_startup()
+ * bi_db_close: called to close each database
+ * called once per database during shutdown but BEFORE
+ * any bi_close call.
+ * called only by backend_shutdown()
+ * bi_db_destroy: called to destroy each database
+ * called once per database during shutdown AFTER all
+ * bi_close calls but before bi_destroy calls.
+ * called only by backend_destroy()
+ */
+ BI_db_init *bi_db_init;
+ BI_db_config *bi_db_config;
+ BI_db_open *bi_db_open;
+ BI_db_close *bi_db_close;
+ BI_db_destroy *bi_db_destroy;
+
+ /* LDAP Operations Handling Routines */
+ BI_op_bind *bi_op_bind;
+ BI_op_unbind *bi_op_unbind;
+ BI_op_search *bi_op_search;
+ BI_op_compare *bi_op_compare;
+ BI_op_modify *bi_op_modify;
+ BI_op_modrdn *bi_op_modrdn;
+ BI_op_add *bi_op_add;
+ BI_op_delete *bi_op_delete;
+ BI_op_abandon *bi_op_abandon;
+
+ /* Extended Operations Helper */
+ BI_op_extended *bi_extended;
+ BI_op_cancel *bi_op_cancel;
+
+ /* Auxiliary Functions */
+ BI_operational *bi_operational;
+ BI_chk_referrals *bi_chk_referrals;
+ BI_chk_controls *bi_chk_controls;
+ BI_op_txn *bi_op_txn;
+ BI_entry_get_rw *bi_entry_get_rw;
+ BI_entry_release_rw *bi_entry_release_rw;
+
+ BI_has_subordinates *bi_has_subordinates;
+ BI_access_allowed *bi_access_allowed;
+ BI_acl_group *bi_acl_group;
+ BI_acl_attribute *bi_acl_attribute;
+
+ BI_connection_init *bi_connection_init;
+ BI_connection_destroy *bi_connection_destroy;
+
+ /* hooks for slap tools */
+ BI_tool_entry_open *bi_tool_entry_open;
+ BI_tool_entry_close *bi_tool_entry_close;
+ BI_tool_entry_first *bi_tool_entry_first; /* deprecated */
+ BI_tool_entry_first_x *bi_tool_entry_first_x;
+ BI_tool_entry_next *bi_tool_entry_next;
+ BI_tool_entry_get *bi_tool_entry_get;
+ BI_tool_entry_put *bi_tool_entry_put;
+ BI_tool_entry_reindex *bi_tool_entry_reindex;
+ BI_tool_sync *bi_tool_sync;
+ BI_tool_dn2id_get *bi_tool_dn2id_get;
+ BI_tool_entry_modify *bi_tool_entry_modify;
+ BI_tool_entry_delete *bi_tool_entry_delete;
+
+#define SLAP_INDEX_ADD_OP 0x0001
+#define SLAP_INDEX_DELETE_OP 0x0002
+
+ slap_mask_t bi_flags; /* backend flags */
+#define SLAP_BFLAG_MONITOR 0x0001U /* a monitor backend */
+#define SLAP_BFLAG_CONFIG 0x0002U /* a config backend */
+#define SLAP_BFLAG_FRONTEND 0x0004U /* the frontendDB */
+#define SLAP_BFLAG_NOLASTMODCMD 0x0010U
+#define SLAP_BFLAG_INCREMENT 0x0100U
+#define SLAP_BFLAG_ALIASES 0x1000U
+#define SLAP_BFLAG_REFERRALS 0x2000U
+#define SLAP_BFLAG_SUBENTRIES 0x4000U
+#define SLAP_BFLAG_DYNAMIC 0x8000U
+#define SLAP_BFLAG_STANDALONE 0x10000U /* started up regardless of whether any databases use it */
+#define SLAP_BFLAG_TXNS 0x20000U /* supports LDAP transactions */
+
+/* overlay specific */
+#define SLAPO_BFLAG_SINGLE 0x01000000U
+#define SLAPO_BFLAG_DBONLY 0x02000000U
+#define SLAPO_BFLAG_GLOBONLY 0x04000000U
+#define SLAPO_BFLAG_DISABLED 0x08000000U
+#define SLAPO_BFLAG_MASK 0xFF000000U
+
+#define SLAP_BFLAGS(be) ((be)->bd_info->bi_flags)
+#define SLAP_MONITOR(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_MONITOR)
+#define SLAP_CONFIG(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_CONFIG)
+#define SLAP_FRONTEND(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_FRONTEND)
+#define SLAP_INCREMENT(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_INCREMENT)
+#define SLAP_ALIASES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_ALIASES)
+#define SLAP_REFERRALS(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_REFERRALS)
+#define SLAP_SUBENTRIES(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_SUBENTRIES)
+#define SLAP_DYNAMIC(be) ((SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC) || (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DYNAMIC))
+#define SLAP_NOLASTMODCMD(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_NOLASTMODCMD)
+#define SLAP_LASTMODCMD(be) (!SLAP_NOLASTMODCMD(be))
+#define SLAP_TXNS(be) (SLAP_BFLAGS(be) & SLAP_BFLAG_TXNS)
+
+/* overlay specific */
+#define SLAPO_SINGLE(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_SINGLE)
+#define SLAPO_DBONLY(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_DBONLY)
+#define SLAPO_GLOBONLY(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_GLOBONLY)
+#define SLAPO_DISABLED(be) (SLAP_BFLAGS(be) & SLAPO_BFLAG_DISABLED)
+
+ char **bi_controls; /* supported controls */
+ char bi_ctrls[SLAP_MAX_CIDS + 1];
+
+ unsigned int bi_nDB; /* number of databases of this type */
+ struct ConfigOCs *bi_cf_ocs;
+ char **bi_obsolete_names;
+ void *bi_extra; /* backend type-specific APIs */
+ void *bi_private; /* backend type-specific config data */
+ LDAP_STAILQ_ENTRY(BackendInfo) bi_next ;
+};
+
+#define c_authtype c_authz.sai_method
+#define c_authmech c_authz.sai_mech
+#define c_dn c_authz.sai_dn
+#define c_ndn c_authz.sai_ndn
+#define c_ssf c_authz.sai_ssf
+#define c_transport_ssf c_authz.sai_transport_ssf
+#define c_tls_ssf c_authz.sai_tls_ssf
+#define c_sasl_ssf c_authz.sai_sasl_ssf
+
+#define o_authtype o_authz.sai_method
+#define o_authmech o_authz.sai_mech
+#define o_dn o_authz.sai_dn
+#define o_ndn o_authz.sai_ndn
+#define o_ssf o_authz.sai_ssf
+#define o_transport_ssf o_authz.sai_transport_ssf
+#define o_tls_ssf o_authz.sai_tls_ssf
+#define o_sasl_ssf o_authz.sai_sasl_ssf
+
+typedef int (slap_response)( Operation *, SlapReply * );
+
+struct slap_callback;
+typedef void (slap_writewait)( Operation *, struct slap_callback * );
+
+typedef struct slap_callback {
+ struct slap_callback *sc_next;
+ slap_response *sc_response;
+ slap_response *sc_cleanup;
+ void *sc_private;
+ slap_writewait *sc_writewait;
+} slap_callback;
+
+struct slap_overinfo;
+
+typedef enum slap_operation_e {
+ op_bind = 0,
+ op_unbind,
+ op_search,
+ op_compare,
+ op_modify,
+ op_modrdn,
+ op_add,
+ op_delete,
+ op_abandon,
+ op_extended,
+ op_cancel,
+ op_aux_operational,
+ op_aux_chk_referrals,
+ op_aux_chk_controls,
+ op_txn,
+ op_last
+} slap_operation_t;
+
+typedef struct slap_overinst {
+ BackendInfo on_bi;
+ slap_response *on_response;
+ struct slap_overinfo *on_info;
+ struct slap_overinst *on_next;
+} slap_overinst;
+
+typedef struct slap_overinfo {
+ BackendInfo oi_bi;
+ BackendInfo *oi_orig;
+ BackendDB *oi_origdb;
+ struct slap_overinst *oi_list;
+} slap_overinfo;
+
+/* Should successive callbacks in a chain be processed? */
+#define SLAP_CB_BYPASS 0x08800
+#define SLAP_CB_CONTINUE 0x08000
+
+/*
+ * Paged Results state
+ */
+typedef unsigned long PagedResultsCookie;
+typedef struct PagedResultsState {
+ Backend *ps_be;
+ ber_int_t ps_size;
+ int ps_count;
+ PagedResultsCookie ps_cookie;
+ struct berval ps_cookieval;
+} PagedResultsState;
+
+struct slap_csn_entry {
+ Operation *ce_op;
+ struct berval ce_csn;
+ int ce_sid;
+#define SLAP_CSN_PENDING 1
+#define SLAP_CSN_COMMIT 2
+ long ce_state;
+ LDAP_TAILQ_ENTRY (slap_csn_entry) ce_csn_link;
+};
+
+/*
+ * Caches the result of a backend_group check for ACL evaluation
+ */
+typedef struct GroupAssertion {
+ struct GroupAssertion *ga_next;
+ Backend *ga_be;
+ ObjectClass *ga_oc;
+ AttributeDescription *ga_at;
+ int ga_res;
+ ber_len_t ga_len;
+ char ga_ndn[1];
+} GroupAssertion;
+
+struct slap_control_ids {
+ int sc_LDAPsync;
+ int sc_assert;
+ int sc_domainScope;
+ int sc_dontUseCopy;
+ int sc_manageDSAit;
+ int sc_modifyIncrement;
+ int sc_noOp;
+ int sc_pagedResults;
+ int sc_permissiveModify;
+ int sc_postRead;
+ int sc_preRead;
+ int sc_proxyAuthz;
+ int sc_relax;
+ int sc_searchOptions;
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+ int sc_sortedResults;
+#endif
+ int sc_subentries;
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ int sc_treeDelete;
+#endif
+ int sc_txnSpec;
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ int sc_sessionTracking;
+#endif
+ int sc_valuesReturnFilter;
+#ifdef SLAP_CONTROL_X_WHATFAILED
+ int sc_whatFailed;
+#endif
+#ifdef LDAP_CONTROL_X_LAZY_COMMIT
+ int sc_lazyCommit;
+#endif
+};
+
+/*
+ * Operation indices
+ */
+typedef enum {
+ SLAP_OP_BIND = 0,
+ SLAP_OP_UNBIND,
+ SLAP_OP_SEARCH,
+ SLAP_OP_COMPARE,
+ SLAP_OP_MODIFY,
+ SLAP_OP_MODRDN,
+ SLAP_OP_ADD,
+ SLAP_OP_DELETE,
+ SLAP_OP_ABANDON,
+ SLAP_OP_EXTENDED,
+ SLAP_OP_LAST
+} slap_op_t;
+
+typedef struct slap_counters_t {
+ struct slap_counters_t *sc_next;
+ ldap_pvt_thread_mutex_t sc_mutex;
+ ldap_pvt_mp_t sc_bytes;
+ ldap_pvt_mp_t sc_pdu;
+ ldap_pvt_mp_t sc_entries;
+ ldap_pvt_mp_t sc_refs;
+
+ ldap_pvt_mp_t sc_ops_completed;
+ ldap_pvt_mp_t sc_ops_initiated;
+ ldap_pvt_mp_t sc_ops_completed_[SLAP_OP_LAST];
+ ldap_pvt_mp_t sc_ops_initiated_[SLAP_OP_LAST];
+} slap_counters_t;
+
+/*
+ * represents an operation pending from an ldap client
+ */
+typedef struct Opheader {
+ unsigned long oh_opid; /* id of this operation */
+ unsigned long oh_connid; /* id of conn initiating this op */
+ Connection *oh_conn; /* connection spawning this op */
+
+ ber_int_t oh_msgid; /* msgid of the request */
+ ber_int_t oh_protocol; /* version of the LDAP protocol used by client */
+
+ ldap_pvt_thread_t oh_tid; /* thread handling this op */
+
+ void *oh_threadctx; /* thread pool thread context */
+ void *oh_tmpmemctx; /* slab malloc context */
+ BerMemoryFunctions *oh_tmpmfuncs;
+
+ slap_counters_t *oh_counters;
+
+ char oh_log_prefix[ /* sizeof("conn= op=") + 2*LDAP_PVT_INTTYPE_CHARS(unsigned long) */ SLAP_TEXT_BUFLEN ];
+
+#ifdef LDAP_SLAPI
+ void *oh_extensions; /* NS-SLAPI plugin */
+#endif
+} Opheader;
+
+typedef union OpRequest {
+ req_add_s oq_add;
+ req_bind_s oq_bind;
+ req_compare_s oq_compare;
+ req_modify_s oq_modify;
+ req_modrdn_s oq_modrdn;
+ req_search_s oq_search;
+ req_abandon_s oq_abandon;
+ req_abandon_s oq_cancel;
+ req_extended_s oq_extended;
+ req_pwdexop_s oq_pwdexop;
+} OpRequest;
+
+/* This is only a header. Actual users should define their own
+ * structs with the oe_next / oe_key fields at the top and
+ * whatever else they need following.
+ */
+typedef struct OpExtra {
+ LDAP_SLIST_ENTRY(OpExtra) oe_next;
+ void *oe_key;
+} OpExtra;
+
+typedef struct OpExtraDB {
+ OpExtra oe;
+ BackendDB *oe_db;
+} OpExtraDB;
+
+struct Operation {
+ Opheader *o_hdr;
+
+#define o_opid o_hdr->oh_opid
+#define o_connid o_hdr->oh_connid
+#define o_conn o_hdr->oh_conn
+#define o_msgid o_hdr->oh_msgid
+#define o_protocol o_hdr->oh_protocol
+#define o_tid o_hdr->oh_tid
+#define o_threadctx o_hdr->oh_threadctx
+#define o_tmpmemctx o_hdr->oh_tmpmemctx
+#define o_tmpmfuncs o_hdr->oh_tmpmfuncs
+#define o_counters o_hdr->oh_counters
+
+#define o_tmpalloc o_tmpmfuncs->bmf_malloc
+#define o_tmpcalloc o_tmpmfuncs->bmf_calloc
+#define o_tmprealloc o_tmpmfuncs->bmf_realloc
+#define o_tmpfree o_tmpmfuncs->bmf_free
+
+#define o_log_prefix o_hdr->oh_log_prefix
+
+ ber_tag_t o_tag; /* tag of the request */
+ time_t o_time; /* time op was initiated */
+ int o_tincr; /* counter for multiple ops with same o_time */
+ int o_tusec; /* microsecond timestamp */
+ struct timeval o_qtime; /* time spent in queues before execution */
+
+ BackendDB *o_bd; /* backend DB processing this op */
+ struct berval o_req_dn; /* DN of target of request */
+ struct berval o_req_ndn;
+
+ OpRequest o_request;
+
+/* short hands for union members */
+#define oq_add o_request.oq_add
+#define oq_bind o_request.oq_bind
+#define oq_compare o_request.oq_compare
+#define oq_modify o_request.oq_modify
+#define oq_modrdn o_request.oq_modrdn
+#define oq_search o_request.oq_search
+#define oq_abandon o_request.oq_abandon
+#define oq_cancel o_request.oq_cancel
+#define oq_extended o_request.oq_extended
+#define oq_pwdexop o_request.oq_pwdexop
+
+/* short hands for inner request members */
+#define orb_method oq_bind.rb_method
+#define orb_cred oq_bind.rb_cred
+#define orb_edn oq_bind.rb_edn
+#define orb_ssf oq_bind.rb_ssf
+#define orb_mech oq_bind.rb_mech
+
+#define ors_scope oq_search.rs_scope
+#define ors_deref oq_search.rs_deref
+#define ors_slimit oq_search.rs_slimit
+#define ors_tlimit oq_search.rs_tlimit
+#define ors_limit oq_search.rs_limit
+#define ors_attrsonly oq_search.rs_attrsonly
+#define ors_attrs oq_search.rs_attrs
+#define ors_filter oq_search.rs_filter
+#define ors_filterstr oq_search.rs_filterstr
+
+#define orr_modlist oq_modrdn.rs_mods.rs_modlist
+#define orr_no_opattrs oq_modrdn.rs_mods.rs_no_opattrs
+#define orr_deleteoldrdn oq_modrdn.rs_deleteoldrdn
+#define orr_newrdn oq_modrdn.rs_newrdn
+#define orr_nnewrdn oq_modrdn.rs_nnewrdn
+#define orr_newSup oq_modrdn.rs_newSup
+#define orr_nnewSup oq_modrdn.rs_nnewSup
+#define orr_newDN oq_modrdn.rs_newDN
+#define orr_nnewDN oq_modrdn.rs_nnewDN
+
+#define orc_ava oq_compare.rs_ava
+
+#define ora_e oq_add.rs_e
+#define ora_modlist oq_add.rs_modlist
+
+#define orn_msgid oq_abandon.rs_msgid
+
+#define orm_modlist oq_modify.rs_mods.rs_modlist
+#define orm_no_opattrs oq_modify.rs_mods.rs_no_opattrs
+#define orm_increment oq_modify.rs_increment
+
+#define ore_reqoid oq_extended.rs_reqoid
+#define ore_flags oq_extended.rs_flags
+#define ore_reqdata oq_extended.rs_reqdata
+ volatile sig_atomic_t o_abandon; /* abandon flag */
+ volatile sig_atomic_t o_cancel; /* cancel flag */
+#define SLAP_CANCEL_NONE 0x00
+#define SLAP_CANCEL_REQ 0x01
+#define SLAP_CANCEL_ACK 0x02
+#define SLAP_CANCEL_DONE 0x03
+
+ GroupAssertion *o_groups;
+ char o_do_not_cache; /* don't cache groups from this op */
+ char o_is_auth_check; /* authorization in progress */
+ char o_dont_replicate;
+ slap_access_t o_acl_priv;
+
+ char o_nocaching;
+ char o_delete_glue_parent;
+ char o_no_schema_check;
+#define get_no_schema_check(op) ((op)->o_no_schema_check)
+ char o_no_subordinate_glue;
+#define get_no_subordinate_glue(op) ((op)->o_no_subordinate_glue)
+
+#define SLAP_CONTROL_NONE 0
+#define SLAP_CONTROL_IGNORED 1
+#define SLAP_CONTROL_NONCRITICAL 2
+#define SLAP_CONTROL_CRITICAL 3
+#define SLAP_CONTROL_MASK 3
+
+/* spare bits for simple flags */
+#define SLAP_CONTROL_SHIFT 4 /* shift to reach data bits */
+#define SLAP_CONTROL_DATA0 0x10
+#define SLAP_CONTROL_DATA1 0x20
+#define SLAP_CONTROL_DATA2 0x40
+#define SLAP_CONTROL_DATA3 0x80
+
+#define _SCM(x) ((x) & SLAP_CONTROL_MASK)
+
+ char o_ctrlflag[SLAP_MAX_CIDS]; /* per-control flags */
+ void **o_controls; /* per-control state */
+
+#define o_dontUseCopy o_ctrlflag[slap_cids.sc_dontUseCopy]
+#define get_dontUseCopy(op) _SCM((op)->o_dontUseCopy)
+
+#define o_relax o_ctrlflag[slap_cids.sc_relax]
+#define get_relax(op) _SCM((op)->o_relax)
+
+#define o_managedsait o_ctrlflag[slap_cids.sc_manageDSAit]
+#define get_manageDSAit(op) _SCM((op)->o_managedsait)
+
+#define o_noop o_ctrlflag[slap_cids.sc_noOp]
+#define o_proxy_authz o_ctrlflag[slap_cids.sc_proxyAuthz]
+#define o_subentries o_ctrlflag[slap_cids.sc_subentries]
+
+#define get_subentries(op) _SCM((op)->o_subentries)
+#define o_subentries_visibility o_ctrlflag[slap_cids.sc_subentries]
+
+#define set_subentries_visibility(op) ((op)->o_subentries |= SLAP_CONTROL_DATA0)
+#define get_subentries_visibility(op) (((op)->o_subentries & SLAP_CONTROL_DATA0) != 0)
+
+#define o_assert o_ctrlflag[slap_cids.sc_assert]
+#define get_assert(op) ((int)(op)->o_assert)
+#define o_assertion o_controls[slap_cids.sc_assert]
+#define get_assertion(op) ((op)->o_assertion)
+
+#define o_valuesreturnfilter o_ctrlflag[slap_cids.sc_valuesReturnFilter]
+#define o_vrFilter o_controls[slap_cids.sc_valuesReturnFilter]
+
+#define o_permissive_modify o_ctrlflag[slap_cids.sc_permissiveModify]
+#define get_permissiveModify(op) ((int)(op)->o_permissive_modify)
+
+#define o_domain_scope o_ctrlflag[slap_cids.sc_domainScope]
+#define get_domainScope(op) ((int)(op)->o_domain_scope)
+
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+#define o_tree_delete o_ctrlflag[slap_cids.sc_treeDelete]
+#define get_treeDelete(op) ((int)(op)->o_tree_delete)
+#endif
+
+#define o_preread o_ctrlflag[slap_cids.sc_preRead]
+#define o_postread o_ctrlflag[slap_cids.sc_postRead]
+
+#define o_preread_attrs o_controls[slap_cids.sc_preRead]
+#define o_postread_attrs o_controls[slap_cids.sc_postRead]
+
+#define o_pagedresults o_ctrlflag[slap_cids.sc_pagedResults]
+#define o_pagedresults_state o_controls[slap_cids.sc_pagedResults]
+#define get_pagedresults(op) ((int)(op)->o_pagedresults)
+
+#ifdef SLAP_CONTROL_X_SORTEDRESULTS
+#define o_sortedresults o_ctrlflag[slap_cids.sc_sortedResults]
+#endif
+
+#define o_txnSpec o_ctrlflag[slap_cids.sc_txnSpec]
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define o_session_tracking o_ctrlflag[slap_cids.sc_sessionTracking]
+#define o_tracked_sessions o_controls[slap_cids.sc_sessionTracking]
+#define get_sessionTracking(op) ((int)(op)->o_session_tracking)
+#endif
+
+#ifdef SLAP_CONTROL_X_WHATFAILED
+#define o_whatFailed o_ctrlflag[slap_cids.sc_whatFailed]
+#define get_whatFailed(op) _SCM((op)->o_whatFailed)
+#endif
+
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+#define o_lazyCommit o_ctrlflag[slap_cids.sc_lazyCommit]
+#define get_lazyCommit(op) _SCM((op)->o_lazyCommit)
+#endif
+
+#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync]
+
+ AuthorizationInformation o_authz;
+
+ BerElement *o_ber; /* ber of the request */
+ BerElement *o_res_ber; /* ber of the CLDAP reply or readback control */
+ slap_callback *o_callback; /* callback pointers */
+ LDAPControl **o_ctrls; /* controls */
+ struct berval o_csn;
+
+ /* DEPRECATE o_private - use o_extra instead */
+ void *o_private; /* anything the backend needs */
+ LDAP_SLIST_HEAD(o_e, OpExtra) o_extra; /* anything the backend needs */
+
+ LDAP_STAILQ_ENTRY(Operation) o_next; /* next operation in list */
+};
+
+typedef struct OperationBuffer {
+ Operation ob_op;
+ Opheader ob_hdr;
+ void *ob_controls[SLAP_MAX_CIDS];
+} OperationBuffer;
+
+#define send_ldap_error( op, rs, err, text ) do { \
+ (rs)->sr_err = err; (rs)->sr_text = text; \
+ ((op)->o_conn->c_send_ldap_result)( op, rs ); \
+ } while (0)
+#define send_ldap_discon( op, rs, err, text ) do { \
+ (rs)->sr_err = err; (rs)->sr_text = text; \
+ send_ldap_disconnect( op, rs ); \
+ } while (0)
+
+typedef void (SEND_LDAP_RESULT)(
+ Operation *op, SlapReply *rs);
+typedef int (SEND_SEARCH_ENTRY)(
+ Operation *op, SlapReply *rs);
+typedef int (SEND_SEARCH_REFERENCE)(
+ Operation *op, SlapReply *rs);
+typedef void (SEND_LDAP_EXTENDED)(
+ Operation *op, SlapReply *rs);
+typedef void (SEND_LDAP_INTERMEDIATE)(
+ Operation *op, SlapReply *rs);
+
+#define send_ldap_result( op, rs ) \
+ ((op)->o_conn->c_send_ldap_result)( op, rs )
+#define send_search_entry( op, rs ) \
+ ((op)->o_conn->c_send_search_entry)( op, rs )
+#define send_search_reference( op, rs ) \
+ ((op)->o_conn->c_send_search_reference)( op, rs )
+#define send_ldap_extended( op, rs ) \
+ ((op)->o_conn->c_send_ldap_extended)( op, rs )
+#define send_ldap_intermediate( op, rs ) \
+ ((op)->o_conn->c_send_ldap_intermediate)( op, rs )
+
+typedef struct Listener Listener;
+
+/*
+ * represents a connection from an ldap client
+ */
+/* connection state (protected by c_mutex ) */
+enum sc_conn_state {
+ SLAP_C_INVALID = 0, /* MUST BE ZERO (0) */
+ SLAP_C_INACTIVE, /* zero threads */
+ SLAP_C_CLOSING, /* closing */
+ SLAP_C_ACTIVE, /* one or more threads */
+ SLAP_C_BINDING, /* binding */
+ SLAP_C_CLIENT /* outbound client conn */
+};
+struct Connection {
+ enum sc_conn_state c_conn_state; /* connection state */
+ int c_conn_idx; /* slot in connections array */
+ ber_socket_t c_sd;
+ const char *c_close_reason; /* why connection is closing */
+
+ ldap_pvt_thread_mutex_t c_mutex; /* protect the connection */
+ Sockbuf *c_sb; /* ber connection stuff */
+
+ /* only can be changed by connect_init */
+ time_t c_starttime; /* when the connection was opened */
+ time_t c_activitytime; /* when the connection was last used */
+ unsigned long c_connid; /* id of this connection for stats*/
+
+ struct berval c_peer_domain; /* DNS name of client */
+ struct berval c_peer_name; /* peer name (trans=addr:port) */
+ Listener *c_listener;
+#define c_listener_url c_listener->sl_url /* listener URL */
+#define c_sock_name c_listener->sl_name /* sock name (trans=addr:port) */
+
+ /* only can be changed by binding thread */
+ struct berval c_sasl_bind_mech; /* mech in progress */
+ struct berval c_sasl_dn; /* temporary storage */
+ struct berval c_sasl_authz_dn; /* SASL proxy authz */
+
+ /* authorization backend */
+ Backend *c_authz_backend;
+ void *c_authz_cookie;
+#define SLAP_IS_AUTHZ_BACKEND( op ) \
+ ( (op)->o_bd != NULL \
+ && (op)->o_bd->be_private != NULL \
+ && (op)->o_conn != NULL \
+ && (op)->o_conn->c_authz_backend != NULL \
+ && ( (op)->o_bd->be_private == (op)->o_conn->c_authz_backend->be_private \
+ || (op)->o_bd->be_private == (op)->o_conn->c_authz_cookie ) )
+
+ AuthorizationInformation c_authz;
+
+ ber_int_t c_protocol; /* version of the LDAP protocol used by client */
+
+ LDAP_STAILQ_HEAD(c_o, Operation) c_ops; /* list of operations being processed */
+ LDAP_STAILQ_HEAD(c_po, Operation) c_pending_ops; /* list of pending operations */
+
+ ldap_pvt_thread_mutex_t c_write1_mutex; /* only one pdu written at a time */
+ ldap_pvt_thread_cond_t c_write1_cv; /* only one pdu written at a time */
+
+ BerElement *c_currentber; /* ber we're attempting to read */
+ int c_writers; /* number of writers waiting */
+ char c_writing; /* someone is writing */
+
+ char c_sasl_bind_in_progress; /* multi-op bind in progress */
+ char c_writewaiter; /* true if blocked on write */
+
+
+#define CONN_IS_TLS 1
+#define CONN_IS_UDP 2
+#define CONN_IS_CLIENT 4
+#define CONN_IS_IPC 8
+
+#ifdef LDAP_CONNECTIONLESS
+ char c_is_udp; /* true if this is (C)LDAP over UDP */
+#endif
+#ifdef HAVE_TLS
+ char c_is_tls; /* true if this LDAP over raw TLS */
+ char c_needs_tls_accept; /* true if SSL_accept should be called */
+#endif
+ char c_sasl_layers; /* true if we need to install SASL i/o handlers */
+ char c_sasl_done; /* SASL completed once */
+ void *c_sasl_authctx; /* SASL authentication context */
+ void *c_sasl_sockctx; /* SASL security layer context */
+ void *c_sasl_extra; /* SASL session extra stuff */
+ void *c_sasl_cbind; /* SASL channel binding */
+ Operation *c_sasl_bindop; /* set to current op if it's a bind */
+
+#define CONN_TXN_INACTIVE 0
+#define CONN_TXN_SPECIFY 1
+#define CONN_TXN_SETTLE -1
+ int c_txn;
+
+ Backend *c_txn_backend;
+ LDAP_STAILQ_HEAD(c_to, Operation) c_txn_ops; /* list of operations in txn */
+
+ PagedResultsState c_pagedresults_state; /* paged result state */
+
+ long c_n_ops_received; /* num of ops received (next op_id) */
+ long c_n_ops_executing; /* num of ops currently executing */
+ long c_n_ops_pending; /* num of ops pending execution */
+ long c_n_ops_completed; /* num of ops completed */
+ long c_n_ops_async; /* mum of ops currently executing asynchronously */
+
+ long c_n_get; /* num of get calls */
+ long c_n_read; /* num of read calls */
+ long c_n_write; /* num of write calls */
+
+ void *c_extensions; /* Netscape plugin */
+
+ /*
+ * Client connection handling
+ */
+ ldap_pvt_thread_start_t *c_clientfunc;
+ void *c_clientarg;
+
+ /*
+ * These are the "callbacks" that are available for back-ends to
+ * supply data back to connected clients that are connected
+ * through the "front-end".
+ */
+ SEND_LDAP_RESULT *c_send_ldap_result;
+ SEND_SEARCH_ENTRY *c_send_search_entry;
+ SEND_SEARCH_REFERENCE *c_send_search_reference;
+ SEND_LDAP_EXTENDED *c_send_ldap_extended;
+ SEND_LDAP_INTERMEDIATE *c_send_ldap_intermediate;
+};
+
+#ifdef LDAP_DEBUG
+#ifdef LDAP_SYSLOG
+#ifdef LOG_LOCAL4
+#define SLAP_DEFAULT_SYSLOG_USER LOG_LOCAL4
+#endif /* LOG_LOCAL4 */
+#endif /* !LDAP_SYSLOG */
+#endif /* !LDAP_DEBUG */
+
+/*
+ * listener; need to access it from monitor backend
+ */
+struct Listener {
+ struct berval sl_url;
+ struct berval sl_name;
+ mode_t sl_perms;
+#ifdef HAVE_TLS
+ int sl_is_tls;
+#endif
+#ifdef LDAP_CONNECTIONLESS
+ int sl_is_udp; /* UDP listener is also data port */
+#endif
+ int sl_is_proxied;
+ int sl_mute; /* Listener is temporarily disabled due to emfile */
+ int sl_busy; /* Listener is busy (accept thread activated) */
+ ber_socket_t sl_sd;
+ Sockaddr sl_sa;
+#define sl_addr sl_sa.sa_in_addr
+#define LDAP_TCP_BUFFER
+#ifdef LDAP_TCP_BUFFER
+ int sl_tcp_rmem; /* custom TCP read buffer size */
+ int sl_tcp_wmem; /* custom TCP write buffer size */
+#endif
+};
+
+/*
+ * Better know these all around slapd
+ */
+#define SLAP_LDAPDN_PRETTY 0x1
+#define SLAP_LDAPDN_MAXLEN 8192
+
+/* number of response controls supported */
+#define SLAP_MAX_RESPONSE_CONTROLS 6
+
+#ifdef SLAP_SCHEMA_EXPOSE
+#define SLAP_CTRL_HIDE 0x00000000U
+#else
+#define SLAP_CTRL_HIDE 0x80000000U
+#endif
+
+#define SLAP_CTRL_REQUIRES_ROOT 0x40000000U /* for Relax */
+
+#define SLAP_CTRL_GLOBAL 0x00800000U
+#define SLAP_CTRL_GLOBAL_SEARCH 0x00010000U /* for NOOP */
+
+#define SLAP_CTRL_OPFLAGS 0x0000FFFFU
+#define SLAP_CTRL_ABANDON 0x00000001U
+#define SLAP_CTRL_ADD 0x00002002U
+#define SLAP_CTRL_BIND 0x00000004U
+#define SLAP_CTRL_COMPARE 0x00001008U
+#define SLAP_CTRL_DELETE 0x00002010U
+#define SLAP_CTRL_MODIFY 0x00002020U
+#define SLAP_CTRL_RENAME 0x00002040U
+#define SLAP_CTRL_SEARCH 0x00001080U
+#define SLAP_CTRL_UNBIND 0x00000100U
+
+#define SLAP_CTRL_INTROGATE (SLAP_CTRL_COMPARE|SLAP_CTRL_SEARCH)
+#define SLAP_CTRL_UPDATE \
+ (SLAP_CTRL_ADD|SLAP_CTRL_DELETE|SLAP_CTRL_MODIFY|SLAP_CTRL_RENAME)
+#define SLAP_CTRL_ACCESS (SLAP_CTRL_INTROGATE|SLAP_CTRL_UPDATE)
+
+typedef int (SLAP_CTRL_PARSE_FN) LDAP_P((
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl ));
+
+typedef int (*SLAP_ENTRY_INFO_FN) LDAP_P(( void *arg, Entry *e ));
+
+#define SLAP_SLAB_SIZE (1024*1024)
+#define SLAP_SLAB_STACK 1
+
+#define SLAP_ZONE_ALLOC 1
+#undef SLAP_ZONE_ALLOC
+
+#ifdef LDAP_COMP_MATCH
+/*
+ * Extensible Filter Definition
+ *
+ * MatchingRuleAssertion := SEQUENCE {
+ * matchingRule [1] MatchingRuleId OPTIONAL,
+ * type [2] AttributeDescription OPTIONAL,
+ * matchValue [3] AssertionValue,
+ * dnAttributes [4] BOOLEAN DEFAULT FALSE }
+ *
+ * Following ComponentFilter is contained in matchValue
+ *
+ * ComponentAssertion ::= SEQUENCE {
+ * component ComponentReference (SIZE(1..MAX)) OPTIONAL
+ * useDefaultValues BOOLEAN DEFAULT TRUE,
+ * rule MATCHING-RULE.&id,
+ * value MATCHING-RULE.&AssertionType }
+ *
+ * ComponentFilter ::= CHOICE {
+ * item [0] ComponentAssertion,
+ * and [1] SEQUENCE OF ComponentFilter,
+ * or [2] SEQUENCE OF ComponentFilter,
+ * not [3] ComponentFilter }
+ */
+
+#define LDAP_COMPREF_IDENTIFIER ((ber_tag_t) 0x80U)
+#define LDAP_COMPREF_FROM_BEGINNING ((ber_tag_t) 0x81U)
+#define LDAP_COMPREF_COUNT ((ber_tag_t) 0x82U)
+#define LDAP_COMPREF_FROM_END ((ber_tag_t) 0x83U)
+#define LDAP_COMPREF_CONTENT ((ber_tag_t) 0x84U)
+#define LDAP_COMPREF_SELECT ((ber_tag_t) 0x85U)
+#define LDAP_COMPREF_ALL ((ber_tag_t) 0x86U)
+#define LDAP_COMPREF_DEFINED ((ber_tag_t) 0x87U)
+#define LDAP_COMPREF_UNDEFINED ((ber_tag_t) 0x88U)
+
+#define LDAP_COMP_FILTER_AND ((ber_tag_t) 0xa0U)
+#define LDAP_COMP_FILTER_OR ((ber_tag_t) 0xa1U)
+#define LDAP_COMP_FILTER_NOT ((ber_tag_t) 0xa2U)
+#define LDAP_COMP_FILTER_ITEM ((ber_tag_t) 0xa3U)
+#define LDAP_COMP_FILTER_UNDEFINED ((ber_tag_t) 0xa4U)
+
+typedef struct ComponentId ComponentId;
+typedef struct ComponentReference ComponentReference;
+typedef struct ComponentAssertion ComponentAssertion;
+typedef struct ComponentAssertionValue ComponentAssertionValue;
+typedef struct ComponentSyntaxInfo ComponentSyntaxInfo;
+typedef struct ComponentDesc ComponentDesc;
+
+struct ComponentData {
+ void *cd_mem_op; /* nibble memory handler */
+ ComponentSyntaxInfo** cd_tree; /* component tree */
+};
+
+struct ComponentId {
+ int ci_type;
+ ComponentId *ci_next;
+
+ union comp_id_value{
+ BerValue ci_identifier;
+ ber_int_t ci_from_beginning;
+ ber_int_t ci_count;
+ ber_int_t ci_from_end;
+ ber_int_t ci_content;
+ BerValue ci_select_value;
+ char ci_all;
+ } ci_val;
+};
+
+struct ComponentReference {
+ ComponentId *cr_list;
+ ComponentId *cr_curr;
+ struct berval cr_string;
+ int cr_len;
+ /* Component Indexing */
+ int cr_asn_type_id;
+ slap_mask_t cr_indexmask;
+ AttributeDescription* cr_ad;
+ BerVarray cr_nvals;
+ ComponentReference* cr_next;
+};
+
+struct ComponentAssertion {
+ ComponentReference *ca_comp_ref;
+ ber_int_t ca_use_def;
+ MatchingRule *ca_ma_rule;
+ struct berval ca_ma_value;
+ ComponentData ca_comp_data; /* componentized assertion */
+ ComponentFilter *ca_cf;
+ MatchingRuleAssertion *ca_mra;
+};
+
+struct ComponentFilter {
+ ber_tag_t cf_choice;
+ union cf_un_u {
+ ber_int_t cf_un_result;
+ ComponentAssertion *cf_un_ca;
+ ComponentFilter *cf_un_complex;
+ } cf_un;
+
+#define cf_ca cf_un.cf_un_ca
+#define cf_result cf_un.cf_un_result
+#define cf_and cf_un.cf_un_complex
+#define cf_or cf_un.cf_un_complex
+#define cf_not cf_un.cf_un_complex
+#define cf_any cf_un.cf_un_complex
+
+ ComponentFilter *cf_next;
+};
+
+struct ComponentAssertionValue {
+ char* cav_buf;
+ char* cav_ptr;
+ char* cav_end;
+};
+
+typedef int encoder_func LDAP_P((
+ void* b,
+ void* comp));
+
+typedef int gser_decoder_func LDAP_P((
+ void* mem_op,
+ void* b,
+ ComponentSyntaxInfo** comp_syn_info,
+ int* len,
+ int mode));
+
+typedef int comp_free_func LDAP_P((
+ void* b));
+
+typedef int ber_decoder_func LDAP_P((
+ void* mem_op,
+ void* b,
+ int tag,
+ int elmtLen,
+ ComponentSyntaxInfo* comp_syn_info,
+ int* len,
+ int mode));
+
+typedef int ber_tag_decoder_func LDAP_P((
+ void* mem_op,
+ void* b,
+ ComponentSyntaxInfo* comp_syn_info,
+ int* len,
+ int mode));
+
+typedef void* extract_component_from_id_func LDAP_P((
+ void* mem_op,
+ ComponentReference* cr,
+ void* comp ));
+
+typedef void* convert_attr_to_comp_func LDAP_P ((
+ Attribute* a,
+ Syntax* syn,
+ struct berval* bv ));
+
+typedef void* alloc_nibble_func LDAP_P ((
+ int initial_size,
+ int increment_size ));
+
+typedef void free_nibble_func LDAP_P ((
+ void* nm ));
+
+typedef void convert_assert_to_comp_func LDAP_P ((
+ void *mem_op,
+ ComponentSyntaxInfo* csi_attr,
+ struct berval* bv,
+ ComponentSyntaxInfo** csi,
+ int* len,
+ int mode ));
+
+typedef int convert_asn_to_ldap_func LDAP_P ((
+ ComponentSyntaxInfo* csi,
+ struct berval *bv ));
+
+typedef void free_component_func LDAP_P ((
+ void* mem_op));
+
+typedef int test_component_func LDAP_P ((
+ void* attr_mem_op,
+ void* assert_mem_op,
+ ComponentSyntaxInfo* csi,
+ ComponentAssertion* ca));
+
+typedef void* test_membership_func LDAP_P ((
+ void* in ));
+
+typedef void* get_component_info_func LDAP_P ((
+ int in ));
+
+typedef int component_encoder_func LDAP_P ((
+ void* mem_op,
+ ComponentSyntaxInfo* csi,
+ struct berval* nvals ));
+
+typedef int allcomponent_matching_func LDAP_P((
+ char* oid,
+ ComponentSyntaxInfo* comp1,
+ ComponentSyntaxInfo* comp));
+
+struct ComponentDesc {
+ /* Don't change the order of following four fields */
+ int cd_tag;
+ AttributeType *cd_comp_type;
+ struct berval cd_ad_type; /* ad_type, ad_cname */
+ struct berval cd_ad_cname; /* ad_type, ad_cname */
+ unsigned cd_flags; /* ad_flags */
+ int cd_type;
+ int cd_type_id;
+ encoder_func *cd_ldap_encoder;
+ encoder_func *cd_gser_encoder;
+ encoder_func *cd_ber_encoder;
+ gser_decoder_func *cd_gser_decoder;
+ ber_decoder_func *cd_ber_decoder;
+ comp_free_func *cd_free;
+ extract_component_from_id_func* cd_extract_i;
+ allcomponent_matching_func *cd_all_match;
+};
+
+struct ComponentSyntaxInfo {
+ Syntax *csi_syntax;
+ ComponentDesc *csi_comp_desc;
+};
+
+#endif /* LDAP_COMP_MATCH */
+
+#ifdef SLAP_ZONE_ALLOC
+#define SLAP_ZONE_SIZE 0x80000 /* 512KB */
+#define SLAP_ZONE_SHIFT 19
+#define SLAP_ZONE_INITSIZE 0x800000 /* 8MB */
+#define SLAP_ZONE_MAXSIZE 0x80000000/* 2GB */
+#define SLAP_ZONE_DELTA 0x800000 /* 8MB */
+#define SLAP_ZONE_ZOBLOCK 256
+
+struct zone_object {
+ void *zo_ptr;
+ int zo_siz;
+ int zo_idx;
+ int zo_blockhead;
+ LDAP_LIST_ENTRY(zone_object) zo_link;
+};
+
+struct zone_latency_history {
+ double zlh_latency;
+ LDAP_STAILQ_ENTRY(zone_latency_history) zlh_next;
+};
+
+struct zone_heap {
+ int zh_fd;
+ int zh_zonesize;
+ int zh_zoneorder;
+ int zh_numzones;
+ int zh_maxzones;
+ int zh_deltazones;
+ void **zh_zones;
+ ldap_pvt_thread_rdwr_t *zh_znlock;
+ Avlnode *zh_zonetree;
+ unsigned char ***zh_maps;
+ int *zh_seqno;
+ LDAP_LIST_HEAD( zh_freelist, zone_object ) *zh_free;
+ LDAP_LIST_HEAD( zh_so, zone_object ) zh_zopool;
+ ldap_pvt_thread_mutex_t zh_mutex;
+ ldap_pvt_thread_rdwr_t zh_lock;
+ double zh_ema_latency;
+ unsigned long zh_ema_samples;
+ LDAP_STAILQ_HEAD( zh_latency_history, zone_latency_history )
+ zh_latency_history_queue;
+ int zh_latency_history_qlen;
+ int zh_latency_jump;
+ int zh_swapping;
+};
+#endif
+
+#define SLAP_BACKEND_INIT_MODULE(b) \
+ static BackendInfo bi; \
+ int \
+ init_module( int argc, char *argv[] ) \
+ { \
+ bi.bi_type = #b ; \
+ bi.bi_init = b ## _back_initialize; \
+ backend_add( &bi ); \
+ return 0; \
+ }
+
+typedef int (OV_init)(void);
+typedef struct slap_oinit_t {
+ const char *ov_type;
+ OV_init *ov_init;
+} OverlayInit;
+
+LDAP_END_DECL
+
+#include "proto-slap.h"
+
+#endif /* _SLAP_H_ */
diff --git a/servers/slapd/slapacl.c b/servers/slapd/slapacl.c
new file mode 100644
index 0000000..f5bdd09
--- /dev/null
+++ b/servers/slapd/slapacl.c
@@ -0,0 +1,411 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include <lber.h>
+#include <ldif.h>
+#include <lutil.h>
+
+#include "slapcommon.h"
+
+static int
+print_access(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ struct berval *nval )
+{
+ int rc;
+ slap_mask_t mask;
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+
+ rc = access_allowed_mask( op, e, desc, nval, ACL_AUTH, NULL, &mask );
+
+ fprintf( stderr, "%s%s%s: %s\n",
+ desc->ad_cname.bv_val,
+ ( val && !BER_BVISNULL( val ) ) ? "=" : "",
+ ( val && !BER_BVISNULL( val ) ) ?
+ ( desc == slap_schema.si_ad_userPassword ?
+ "****" : val->bv_val ) : "",
+ accessmask2str( mask, accessmaskbuf, 1 ) );
+
+ return rc;
+}
+
+int
+slapacl( int argc, char **argv )
+{
+ int rc = EXIT_SUCCESS;
+ const char *progname = "slapacl";
+ Connection conn = { 0 };
+ Listener listener;
+ OperationBuffer opbuf;
+ Operation *op = NULL;
+ Entry e = { 0 }, *ep = &e;
+ char *attr = NULL;
+ int doclose = 0;
+ BackendDB *bd;
+ void *thrctx;
+
+ slap_tool_init( progname, SLAPACL, argc, argv );
+
+ if ( !dryrun ) {
+ int i = 0;
+
+ LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) {
+ if ( bd != be && backend_startup( bd ) ) {
+ fprintf( stderr, "backend_startup(#%d%s%s) failed\n",
+ i,
+ bd->be_suffix ? ": " : "",
+ bd->be_suffix ? bd->be_suffix[0].bv_val : "" );
+ rc = 1;
+ goto destroy;
+ }
+
+ i++;
+ }
+ }
+
+ argv = &argv[ optind ];
+ argc -= optind;
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+ op->o_tmpmemctx = NULL;
+
+ conn.c_listener = &listener;
+ conn.c_listener_url = listener_url;
+ conn.c_peer_domain = peer_domain;
+ conn.c_peer_name = peer_name;
+ conn.c_sock_name = sock_name;
+ op->o_ssf = ssf;
+ op->o_transport_ssf = transport_ssf;
+ op->o_tls_ssf = tls_ssf;
+ op->o_sasl_ssf = sasl_ssf;
+
+ if ( !BER_BVISNULL( &authcID ) ) {
+ if ( !BER_BVISNULL( &authcDN ) ) {
+ fprintf( stderr, "both authcID=\"%s\" "
+ "and authcDN=\"%s\" provided\n",
+ authcID.bv_val, authcDN.bv_val );
+ rc = 1;
+ goto destroy;
+ }
+
+ rc = slap_sasl_getdn( &conn, op, &authcID, NULL,
+ &authcDN, SLAP_GETDN_AUTHCID );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "authcID: <%s> check failed %d (%s)\n",
+ authcID.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ goto destroy;
+ }
+
+ } else if ( !BER_BVISNULL( &authcDN ) ) {
+ struct berval ndn;
+
+ rc = dnNormalize( 0, NULL, NULL, &authcDN, &ndn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "autchDN=\"%s\" normalization failed %d (%s)\n",
+ authcDN.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ goto destroy;
+ }
+ ch_free( authcDN.bv_val );
+ authcDN = ndn;
+ }
+
+ if ( !BER_BVISNULL( &authzID ) ) {
+ if ( !BER_BVISNULL( &authzDN ) ) {
+ fprintf( stderr, "both authzID=\"%s\" "
+ "and authzDN=\"%s\" provided\n",
+ authzID.bv_val, authzDN.bv_val );
+ rc = 1;
+ goto destroy;
+ }
+
+ rc = slap_sasl_getdn( &conn, op, &authzID, NULL,
+ &authzDN, SLAP_GETDN_AUTHZID );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "authzID: <%s> check failed %d (%s)\n",
+ authzID.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ goto destroy;
+ }
+
+ } else if ( !BER_BVISNULL( &authzDN ) ) {
+ struct berval ndn;
+
+ rc = dnNormalize( 0, NULL, NULL, &authzDN, &ndn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "autchDN=\"%s\" normalization failed %d (%s)\n",
+ authzDN.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ goto destroy;
+ }
+ ch_free( authzDN.bv_val );
+ authzDN = ndn;
+ }
+
+
+ if ( !BER_BVISNULL( &authcDN ) ) {
+ fprintf( stderr, "authcDN: \"%s\"\n", authcDN.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &authzDN ) ) {
+ fprintf( stderr, "authzDN: \"%s\"\n", authzDN.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &authzDN ) ) {
+ op->o_dn = authzDN;
+ op->o_ndn = authzDN;
+
+ if ( !BER_BVISNULL( &authcDN ) ) {
+ op->o_conn->c_dn = authcDN;
+ op->o_conn->c_ndn = authcDN;
+
+ } else {
+ op->o_conn->c_dn = authzDN;
+ op->o_conn->c_ndn = authzDN;
+ }
+
+ } else if ( !BER_BVISNULL( &authcDN ) ) {
+ op->o_conn->c_dn = authcDN;
+ op->o_conn->c_ndn = authcDN;
+ op->o_dn = authcDN;
+ op->o_ndn = authcDN;
+ }
+
+ assert( !BER_BVISNULL( &baseDN ) );
+ rc = dnPrettyNormal( NULL, &baseDN, &e.e_name, &e.e_nname, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "base=\"%s\" normalization failed %d (%s)\n",
+ baseDN.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ goto destroy;
+ }
+
+ op->o_bd = be;
+ if ( op->o_bd == NULL ) {
+ /* NOTE: if no database could be found (e.g. because
+ * accessing the rootDSE or so), use the frontendDB
+ * rules; might need work */
+ op->o_bd = frontendDB;
+ }
+
+ if ( !dryrun ) {
+ ID id;
+
+ if ( be == NULL ) {
+ fprintf( stderr, "%s: no target database "
+ "has been found for baseDN=\"%s\"; "
+ "you may try with \"-u\" (dry run).\n",
+ baseDN.bv_val, progname );
+ rc = 1;
+ goto destroy;
+ }
+
+ if ( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !be->be_dn2id_get ||
+ !be->be_entry_get )
+ {
+ fprintf( stderr, "%s: target database "
+ "doesn't support necessary operations; "
+ "you may try with \"-u\" (dry run).\n",
+ progname );
+ rc = 1;
+ goto destroy;
+ }
+
+ if ( be->be_entry_open( be, 0 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ rc = 1;
+ goto destroy;
+ }
+
+ doclose = 1;
+
+ id = be->be_dn2id_get( be, &e.e_nname );
+ if ( id == NOID ) {
+ fprintf( stderr, "%s: unable to fetch ID of DN \"%s\"\n",
+ progname, e.e_nname.bv_val );
+ rc = 1;
+ goto destroy;
+ }
+ ep = be->be_entry_get( be, id );
+ if ( ep == NULL ) {
+ fprintf( stderr, "%s: unable to fetch entry \"%s\" (%lu)\n",
+ progname, e.e_nname.bv_val, id );
+ rc = 1;
+ goto destroy;
+
+ }
+
+ if ( argc == 0 ) {
+ Attribute *a;
+
+ (void)print_access( op, ep, slap_schema.si_ad_entry, NULL, NULL );
+ (void)print_access( op, ep, slap_schema.si_ad_children, NULL, NULL );
+
+ for ( a = ep->e_attrs; a; a = a->a_next ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ (void)print_access( op, ep, a->a_desc,
+ &a->a_vals[ i ],
+ &a->a_nvals[ i ] );
+ }
+ }
+ }
+ }
+
+ for ( ; argc--; argv++ ) {
+ slap_mask_t mask;
+ AttributeDescription *desc = NULL;
+ struct berval val = BER_BVNULL,
+ *valp = NULL;
+ const char *text;
+ char accessmaskbuf[ACCESSMASK_MAXLEN];
+ char *accessstr;
+ slap_access_t access = ACL_AUTH;
+
+ if ( attr == NULL ) {
+ attr = argv[ 0 ];
+ }
+
+ val.bv_val = strchr( attr, ':' );
+ if ( val.bv_val != NULL ) {
+ val.bv_val[0] = '\0';
+ val.bv_val++;
+ val.bv_len = strlen( val.bv_val );
+ valp = &val;
+ }
+
+ accessstr = strchr( attr, '/' );
+ if ( accessstr != NULL ) {
+ int invalid = 0;
+
+ accessstr[0] = '\0';
+ accessstr++;
+ access = str2access( accessstr );
+ switch ( access ) {
+ case ACL_INVALID_ACCESS:
+ fprintf( stderr, "unknown access \"%s\" for attribute \"%s\"\n",
+ accessstr, attr );
+ invalid = 1;
+ break;
+
+ case ACL_NONE:
+ fprintf( stderr, "\"none\" not allowed for attribute \"%s\"\n",
+ attr );
+ invalid = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ if ( invalid ) {
+ if ( continuemode ) {
+ continue;
+ }
+ break;
+ }
+ }
+
+ rc = slap_str2ad( attr, &desc, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "slap_str2ad(%s) failed %d (%s)\n",
+ attr, rc, ldap_err2string( rc ) );
+ if ( continuemode ) {
+ continue;
+ }
+ break;
+ }
+
+ rc = access_allowed_mask( op, ep, desc, valp, access,
+ NULL, &mask );
+
+ if ( accessstr ) {
+ fprintf( stderr, "%s access to %s%s%s: %s\n",
+ accessstr,
+ desc->ad_cname.bv_val,
+ val.bv_val ? "=" : "",
+ val.bv_val ? val.bv_val : "",
+ rc ? "ALLOWED" : "DENIED" );
+
+ } else {
+ fprintf( stderr, "%s%s%s: %s\n",
+ desc->ad_cname.bv_val,
+ val.bv_val ? "=" : "",
+ val.bv_val ? val.bv_val : "",
+ accessmask2str( mask, accessmaskbuf, 1 ) );
+ }
+ rc = 0;
+ attr = NULL;
+ }
+
+destroy:;
+ if ( !BER_BVISNULL( &e.e_name ) ) {
+ ber_memfree( e.e_name.bv_val );
+ }
+ if ( !BER_BVISNULL( &e.e_nname ) ) {
+ ber_memfree( e.e_nname.bv_val );
+ }
+ if ( !dryrun && be ) {
+ if ( ep && ep != &e ) {
+ be_entry_release_r( op, ep );
+ }
+ if ( doclose ) {
+ be->be_entry_close( be );
+ }
+
+ LDAP_STAILQ_FOREACH( bd, &backendDB, be_next ) {
+ if ( bd != be ) {
+ backend_shutdown( bd );
+ }
+ }
+ }
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
+
diff --git a/servers/slapd/slapadd.c b/servers/slapd/slapadd.c
new file mode 100644
index 0000000..056d80a
--- /dev/null
+++ b/servers/slapd/slapadd.c
@@ -0,0 +1,528 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Kurt Zeilenga for inclusion
+ * in OpenLDAP Software. Additional significant contributors include
+ * Jong Hyuk Choi
+ * Pierangelo Masarati
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include <lber.h>
+#include <ldif.h>
+#include <lutil.h>
+#include <lutil_meter.h>
+#include <sys/stat.h>
+
+#include "slapcommon.h"
+
+#ifdef _WIN32
+# ifdef __WIN64__
+# define ftello(fp) _ftelli64(fp)
+# else
+/* Ideally we would use _ftelli64 but that was only available
+ * starting in MSVCR80.DLL. The approach used here is inaccurate
+ * because returning the underlying file handle's file pointer
+ * doesn't take the stdio buffer offset into account. But, it
+ * works with all versions of MSVCRT.
+ */
+# define ftello(fp) _telli64(fileno(fp))
+# endif
+#endif
+
+extern int slap_DN_strict; /* dn.c */
+
+static char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+
+typedef struct Erec {
+ Entry *e;
+ unsigned long lineno;
+ unsigned long nextline;
+} Erec;
+
+typedef struct Trec {
+ Entry *e;
+ unsigned long lineno;
+ unsigned long nextline;
+ int rc;
+ int ready;
+} Trec;
+
+static Trec trec;
+static unsigned long sid = SLAP_SYNC_SID_MAX + 1;
+static int checkvals;
+static int enable_meter;
+static lutil_meter_t meter;
+static const char *progname = "slapadd";
+static OperationBuffer opbuf;
+static char *buf;
+static int lmax;
+
+static ldap_pvt_thread_mutex_t add_mutex;
+static ldap_pvt_thread_cond_t add_cond;
+static int add_stop;
+
+/* returns:
+ * 1: got a record
+ * 0: EOF
+ * -1: read failure
+ * -2: parse failure
+ */
+static int
+getrec0(Erec *erec)
+{
+ const char *text;
+ int ldifrc;
+ char textbuf[SLAP_TEXT_BUFLEN] = { '\0' };
+ size_t textlen = sizeof textbuf;
+ struct berval csn;
+ Operation *op = &opbuf.ob_op;
+ op->o_hdr = &opbuf.ob_hdr;
+
+again:
+ erec->lineno = erec->nextline+1;
+ /* nextline is the line number of the end of the current entry */
+ ldifrc = ldif_read_record( ldiffp, &erec->nextline, &buf, &lmax );
+ if (ldifrc < 1)
+ return ldifrc < 0 ? -1 : 0;
+ {
+ BackendDB *bd;
+ Entry *e;
+ int prev_DN_strict;
+
+ if ( erec->lineno < jumpline )
+ goto again;
+
+ if ( !dbnum ) {
+ prev_DN_strict = slap_DN_strict;
+ slap_DN_strict = 0;
+ }
+ e = str2entry2( buf, checkvals );
+ if ( !dbnum ) {
+ slap_DN_strict = prev_DN_strict;
+ }
+
+ if ( enable_meter )
+ lutil_meter_update( &meter,
+ ftello( ldiffp->fp ),
+ 0);
+
+ if( e == NULL ) {
+ fprintf( stderr, "%s: could not parse entry (line=%lu)\n",
+ progname, erec->lineno );
+ return -2;
+ }
+
+ /* make sure the DN is not empty */
+ if( BER_BVISEMPTY( &e->e_nname ) &&
+ !BER_BVISEMPTY( be->be_nsuffix ))
+ {
+ fprintf( stderr, "%s: line %lu: "
+ "cannot add entry with empty dn=\"%s\"",
+ progname, erec->lineno, e->e_dn );
+ bd = select_backend( &e->e_nname, nosubordinates );
+ if ( bd ) {
+ BackendDB *bdtmp;
+ int dbidx = 0;
+ LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) {
+ if ( bdtmp == bd ) break;
+ dbidx++;
+ }
+
+ assert( bdtmp != NULL );
+
+ fprintf( stderr, "; did you mean to use database #%d (%s)?",
+ dbidx,
+ bd->be_suffix[0].bv_val );
+
+ }
+ fprintf( stderr, "\n" );
+ entry_free( e );
+ return -2;
+ }
+
+ /* check backend */
+ bd = select_backend( &e->e_nname, nosubordinates );
+ if ( bd != be ) {
+ fprintf( stderr, "%s: line %lu: "
+ "database #%d (%s) not configured to hold \"%s\"",
+ progname, erec->lineno,
+ dbnum,
+ be->be_suffix[0].bv_val,
+ e->e_dn );
+ if ( bd ) {
+ BackendDB *bdtmp;
+ int dbidx = 0;
+ LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) {
+ if ( bdtmp == bd ) break;
+ dbidx++;
+ }
+
+ assert( bdtmp != NULL );
+
+ fprintf( stderr, "; did you mean to use database #%d (%s)?",
+ dbidx,
+ bd->be_suffix[0].bv_val );
+
+ } else {
+ fprintf( stderr, "; no database configured for that naming context" );
+ }
+ fprintf( stderr, "\n" );
+ entry_free( e );
+ return -2;
+ }
+
+ if ( slap_tool_entry_check( progname, op, e, erec->lineno, &text, textbuf, textlen ) !=
+ LDAP_SUCCESS ) {
+ entry_free( e );
+ return -2;
+ }
+
+ if ( SLAP_LASTMOD(be) ) {
+ time_t now = slap_get_time();
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+ struct berval vals[ 2 ];
+
+ struct berval name, timestamp;
+
+ struct berval nvals[ 2 ];
+ struct berval nname;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+
+ enum {
+ GOT_NONE = 0x0,
+ GOT_CSN = 0x1,
+ GOT_UUID = 0x2,
+ GOT_ALL = (GOT_CSN|GOT_UUID)
+ } got = GOT_ALL;
+
+ vals[1].bv_len = 0;
+ vals[1].bv_val = NULL;
+
+ nvals[1].bv_len = 0;
+ nvals[1].bv_val = NULL;
+
+ csn.bv_len = ldap_pvt_csnstr( csnbuf, sizeof( csnbuf ), csnsid, 0 );
+ csn.bv_val = csnbuf;
+
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = sizeof(timebuf);
+
+ slap_timestamp( &now, &timestamp );
+
+ if ( BER_BVISEMPTY( &be->be_rootndn ) ) {
+ BER_BVSTR( &name, SLAPD_ANONYMOUS );
+ nname = name;
+ } else {
+ name = be->be_rootdn;
+ nname = be->be_rootndn;
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_entryUUID )
+ == NULL )
+ {
+ got &= ~GOT_UUID;
+ vals[0].bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) );
+ vals[0].bv_val = uuidbuf;
+ attr_merge_normalize_one( e, slap_schema.si_ad_entryUUID, vals, NULL );
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_creatorsName )
+ == NULL )
+ {
+ vals[0] = name;
+ nvals[0] = nname;
+ attr_merge( e, slap_schema.si_ad_creatorsName, vals, nvals );
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_createTimestamp )
+ == NULL )
+ {
+ vals[0] = timestamp;
+ attr_merge( e, slap_schema.si_ad_createTimestamp, vals, NULL );
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_entryCSN )
+ == NULL )
+ {
+ got &= ~GOT_CSN;
+ vals[0] = csn;
+ attr_merge( e, slap_schema.si_ad_entryCSN, vals, NULL );
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_modifiersName )
+ == NULL )
+ {
+ vals[0] = name;
+ nvals[0] = nname;
+ attr_merge( e, slap_schema.si_ad_modifiersName, vals, nvals );
+ }
+
+ if( attr_find( e->e_attrs, slap_schema.si_ad_modifyTimestamp )
+ == NULL )
+ {
+ vals[0] = timestamp;
+ attr_merge( e, slap_schema.si_ad_modifyTimestamp, vals, NULL );
+ }
+
+ if ( SLAP_SINGLE_SHADOW(be) && got != GOT_ALL ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: warning, missing attrs %s%s%s from entry dn=\"%s\"\n",
+ progname,
+ (!(got & GOT_UUID) ? slap_schema.si_ad_entryUUID->ad_cname.bv_val : ""),
+ (!(got & GOT_CSN) ? "," : ""),
+ (!(got & GOT_CSN) ? slap_schema.si_ad_entryCSN->ad_cname.bv_val : ""),
+ e->e_name.bv_val );
+ }
+
+ sid = slap_tool_update_ctxcsn_check( progname, e );
+ }
+ erec->e = e;
+ }
+ return 1;
+}
+
+static void *
+getrec_thr(void *ctx)
+{
+ ldap_pvt_thread_mutex_lock( &add_mutex );
+ while (!add_stop) {
+ trec.rc = getrec0((Erec *)&trec);
+ trec.ready = 1;
+ while (trec.ready)
+ ldap_pvt_thread_cond_wait( &add_cond, &add_mutex );
+ /* eof or read failure */
+ if ( trec.rc == 0 || trec.rc == -1 )
+ break;
+ }
+ ldap_pvt_thread_mutex_unlock( &add_mutex );
+ return NULL;
+}
+
+static int ldif_threaded;
+
+static int
+getrec(Erec *erec)
+{
+ int rc;
+ if ( !ldif_threaded )
+ return getrec0(erec);
+
+ while (!trec.ready)
+ ldap_pvt_thread_yield();
+ erec->e = trec.e;
+ erec->lineno = trec.lineno;
+ erec->nextline = trec.nextline;
+ trec.ready = 0;
+ rc = trec.rc;
+ ldap_pvt_thread_mutex_lock( &add_mutex );
+ ldap_pvt_thread_mutex_unlock( &add_mutex );
+ ldap_pvt_thread_cond_signal( &add_cond );
+ return rc;
+}
+
+int
+slapadd( int argc, char **argv )
+{
+ char textbuf[SLAP_TEXT_BUFLEN] = { '\0' };
+ size_t textlen = sizeof textbuf;
+ Erec erec;
+ struct berval bvtext;
+ ldap_pvt_thread_t thr;
+ ID id;
+ Entry *prev = NULL;
+
+ int ldifrc;
+ int rc = EXIT_SUCCESS;
+
+ struct stat stat_buf;
+
+ /* default "000" */
+ csnsid = 0;
+
+ if ( isatty (2) ) enable_meter = 1;
+ slap_tool_init( progname, SLAPADD, argc, argv );
+
+ if( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !be->be_entry_put ||
+ (update_ctxcsn &&
+ (!be->be_dn2id_get ||
+ !be->be_entry_get ||
+ !be->be_entry_modify)) )
+ {
+ fprintf( stderr, "%s: database doesn't support necessary operations.\n",
+ progname );
+ if ( dryrun ) {
+ fprintf( stderr, "\t(dry) continuing...\n" );
+
+ } else {
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ checkvals = (slapMode & SLAP_TOOL_QUICK) ? 0 : 1;
+
+ /* do not check values in quick mode */
+ if ( slapMode & SLAP_TOOL_QUICK ) {
+ if ( slapMode & SLAP_TOOL_VALUE_CHECK ) {
+ fprintf( stderr, "%s: value-check incompatible with quick mode; disabled.\n", progname );
+ slapMode &= ~SLAP_TOOL_VALUE_CHECK;
+ }
+ }
+
+ /* enforce schema checking unless not disabled */
+ if ( (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) == 0) {
+ SLAP_DBFLAGS(be) &= ~(SLAP_DBFLAG_NO_SCHEMA_CHECK);
+ }
+
+ if( be->be_entry_open && be->be_entry_open( be, 1 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ (void)slap_tool_update_ctxcsn_init();
+
+ if ( enable_meter
+#ifdef LDAP_DEBUG
+ /* tools default to "none" */
+ && slap_debug == LDAP_DEBUG_NONE
+#endif
+ && !fstat ( fileno ( ldiffp->fp ), &stat_buf )
+ && S_ISREG(stat_buf.st_mode) ) {
+ enable_meter = !lutil_meter_open(
+ &meter,
+ &lutil_meter_text_display,
+ &lutil_meter_linear_estimator,
+ stat_buf.st_size);
+ } else {
+ enable_meter = 0;
+ }
+
+ if ( slap_tool_thread_max > 1 ) {
+ ldap_pvt_thread_mutex_init( &add_mutex );
+ ldap_pvt_thread_cond_init( &add_cond );
+ ldap_pvt_thread_create( &thr, 0, getrec_thr, NULL );
+ ldif_threaded = 1;
+ }
+
+ erec.nextline = 0;
+ erec.e = NULL;
+
+ for (;;) {
+ ldifrc = getrec( &erec );
+ if ( ldifrc < 1 ) {
+ if ( ldifrc == -2 && continuemode )
+ continue;
+ break;
+ }
+
+ if ( be->be_entry_put ) {
+ /*
+ * Initialize text buffer
+ */
+ bvtext.bv_len = textlen;
+ bvtext.bv_val = textbuf;
+ bvtext.bv_val[0] = '\0';
+
+ id = be->be_entry_put( be, erec.e, &bvtext );
+ if( id == NOID ) {
+ fprintf( stderr, "%s: could not add entry dn=\"%s\" "
+ "(line=%lu): %s\n", progname, erec.e->e_dn,
+ erec.lineno, bvtext.bv_val );
+ rc = EXIT_FAILURE;
+ if( continuemode ) {
+ if ( prev ) entry_free( prev );
+ prev = erec.e;
+ continue;
+ }
+ break;
+ }
+ if ( verbose )
+ fprintf( stderr, "added: \"%s\" (%08lx)\n",
+ erec.e->e_dn, (long) id );
+ } else {
+ if ( verbose )
+ fprintf( stderr, "added: \"%s\"\n",
+ erec.e->e_dn );
+ }
+
+ if ( prev ) entry_free( prev );
+ prev = erec.e;
+ }
+
+ if ( ldif_threaded ) {
+ ldap_pvt_thread_mutex_lock( &add_mutex );
+ add_stop = 1;
+ trec.ready = 0;
+ ldap_pvt_thread_cond_signal( &add_cond );
+ ldap_pvt_thread_mutex_unlock( &add_mutex );
+ ldap_pvt_thread_join( thr, NULL );
+ }
+ if ( erec.e ) entry_free( erec.e );
+
+ if ( ldifrc < 0 )
+ rc = EXIT_FAILURE;
+
+ bvtext.bv_len = textlen;
+ bvtext.bv_val = textbuf;
+ bvtext.bv_val[0] = '\0';
+
+ if ( enable_meter ) {
+ lutil_meter_update( &meter, ftello( ldiffp->fp ), 1);
+ lutil_meter_close( &meter );
+ }
+
+ if ( rc == EXIT_SUCCESS ) {
+ rc = slap_tool_update_ctxcsn( progname, sid, &bvtext );
+ }
+
+ ch_free( buf );
+
+ if ( be->be_entry_close ) {
+ if ( enable_meter ) {
+ fprintf( stderr, "Closing DB..." );
+ }
+ if( be->be_entry_close( be ) ) {
+ rc = EXIT_FAILURE;
+ }
+
+ if( be->be_sync ) {
+ be->be_sync( be );
+ }
+ if ( enable_meter ) {
+ fprintf( stderr, "\n" );
+ }
+ }
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
+
diff --git a/servers/slapd/slapauth.c b/servers/slapd/slapauth.c
new file mode 100644
index 0000000..19cdcaf
--- /dev/null
+++ b/servers/slapd/slapauth.c
@@ -0,0 +1,177 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include <lber.h>
+#include <ldif.h>
+#include <lutil.h>
+
+#include "slapcommon.h"
+
+static int
+do_check( Connection *c, Operation *op, struct berval *id )
+{
+ struct berval authcdn;
+ int rc;
+
+ rc = slap_sasl_getdn( c, op, id, realm, &authcdn, SLAP_GETDN_AUTHCID );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "ID: <%s> check failed %d (%s)\n",
+ id->bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+
+ } else {
+ if ( !BER_BVISNULL( &authzID ) ) {
+ rc = slap_sasl_authorized( op, &authcdn, &authzID );
+
+ fprintf( stderr,
+ "ID: <%s>\n"
+ "authcDN: <%s>\n"
+ "authzDN: <%s>\n"
+ "authorization %s\n",
+ id->bv_val,
+ authcdn.bv_val,
+ authzID.bv_val,
+ rc == LDAP_SUCCESS ? "OK" : "failed" );
+
+ } else {
+ fprintf( stderr, "ID: <%s> check succeeded\n"
+ "authcID: <%s>\n",
+ id->bv_val,
+ authcdn.bv_val );
+ op->o_tmpfree( authcdn.bv_val, op->o_tmpmemctx );
+ }
+ rc = 0;
+ }
+
+ return rc;
+}
+
+int
+slapauth( int argc, char **argv )
+{
+ int rc = EXIT_SUCCESS;
+ const char *progname = "slapauth";
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+
+ slap_tool_init( progname, SLAPAUTH, argc, argv );
+
+ argv = &argv[ optind ];
+ argc -= optind;
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ conn.c_sasl_bind_mech = mech;
+
+ if ( !BER_BVISNULL( &authzID ) ) {
+ struct berval authzdn;
+
+ rc = slap_sasl_getdn( &conn, op, &authzID, NULL, &authzdn,
+ SLAP_GETDN_AUTHZID );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "authzID: <%s> check failed %d (%s)\n",
+ authzID.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = 1;
+ BER_BVZERO( &authzID );
+ goto destroy;
+ }
+
+ authzID = authzdn;
+ }
+
+
+ if ( !BER_BVISNULL( &authcID ) ) {
+ if ( !BER_BVISNULL( &authzID ) || argc == 0 ) {
+ rc = do_check( &conn, op, &authcID );
+ goto destroy;
+ }
+
+ for ( ; argc--; argv++ ) {
+ struct berval authzdn;
+
+ ber_str2bv( argv[ 0 ], 0, 0, &authzID );
+
+ rc = slap_sasl_getdn( &conn, op, &authzID, NULL, &authzdn,
+ SLAP_GETDN_AUTHZID );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "authzID: <%s> check failed %d (%s)\n",
+ authzID.bv_val, rc,
+ ldap_err2string( rc ) );
+ rc = -1;
+ BER_BVZERO( &authzID );
+ if ( !continuemode ) {
+ goto destroy;
+ }
+ }
+
+ authzID = authzdn;
+
+ rc = do_check( &conn, op, &authcID );
+
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &authzID );
+
+ if ( rc && !continuemode ) {
+ goto destroy;
+ }
+ }
+
+ goto destroy;
+ }
+
+ for ( ; argc--; argv++ ) {
+ struct berval id;
+
+ ber_str2bv( argv[ 0 ], 0, 0, &id );
+
+ rc = do_check( &conn, op, &id );
+
+ if ( rc && !continuemode ) {
+ goto destroy;
+ }
+ }
+
+destroy:;
+ if ( !BER_BVISNULL( &authzID ) ) {
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ }
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
+
diff --git a/servers/slapd/slapcat.c b/servers/slapd/slapcat.c
new file mode 100644
index 0000000..6f580e7
--- /dev/null
+++ b/servers/slapd/slapcat.c
@@ -0,0 +1,175 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Kurt Zeilenga for inclusion
+ * in OpenLDAP Software. Additional significant contributors include
+ * Jong Hyuk Choi
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slapcommon.h"
+#include "ldif.h"
+
+static volatile sig_atomic_t gotsig;
+
+static RETSIGTYPE
+slapcat_sig( int sig )
+{
+ gotsig=1;
+}
+
+int
+slapcat( int argc, char **argv )
+{
+ ID id;
+ int rc = EXIT_SUCCESS;
+ Operation op = {0};
+ const char *progname = "slapcat";
+ int requestBSF;
+ int doBSF = 0;
+
+ slap_tool_init( progname, SLAPCAT, argc, argv );
+
+ requestBSF = ( sub_ndn.bv_len || filter );
+
+#ifdef SIGPIPE
+ (void) SIGNAL( SIGPIPE, slapcat_sig );
+#endif
+#ifdef SIGHUP
+ (void) SIGNAL( SIGHUP, slapcat_sig );
+#endif
+ (void) SIGNAL( SIGINT, slapcat_sig );
+ (void) SIGNAL( SIGTERM, slapcat_sig );
+
+ if( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !( be->be_entry_first_x || be->be_entry_first ) ||
+ !be->be_entry_next ||
+ !be->be_entry_get )
+ {
+ fprintf( stderr, "%s: database doesn't support necessary operations.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ if( be->be_entry_open( be, 0 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ op.o_bd = be;
+ if ( !requestBSF && be->be_entry_first ) {
+ id = be->be_entry_first( be );
+
+ } else {
+ if ( be->be_entry_first_x ) {
+ id = be->be_entry_first_x( be,
+ sub_ndn.bv_len ? &sub_ndn : NULL, scope, filter );
+
+ } else {
+ assert( be->be_entry_first != NULL );
+ doBSF = 1;
+ id = be->be_entry_first( be );
+ }
+ }
+
+ for ( ; id != NOID; id = be->be_entry_next( be ) )
+ {
+ char *data;
+ int len;
+ Entry* e;
+
+ if ( gotsig )
+ break;
+
+ e = be->be_entry_get( be, id );
+ if ( e == NULL ) {
+ printf("# no data for entry id=%08lx\n\n", (long) id );
+ rc = EXIT_FAILURE;
+ if ( continuemode == 0 ) {
+ break;
+
+ } else if ( continuemode == 1 ) {
+ continue;
+ }
+
+ /* this is a last resort: linearly scan all ids
+ * trying to recover as much as possible (ITS#6482) */
+ while ( ++id != NOID ) {
+ e = be->be_entry_get( be, id );
+ if ( e != NULL ) break;
+ printf("# no data for entry id=%08lx\n\n", (long) id );
+ }
+
+ if ( e == NULL ) break;
+ }
+
+ if ( doBSF ) {
+ if ( sub_ndn.bv_len && !dnIsSuffixScope( &e->e_nname, &sub_ndn, scope ) )
+ {
+ be_entry_release_r( &op, e );
+ continue;
+ }
+
+
+ if ( filter != NULL ) {
+ int rc = test_filter( NULL, e, filter );
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ be_entry_release_r( &op, e );
+ continue;
+ }
+ }
+ }
+
+ if ( verbose ) {
+ printf( "# id=%08lx\n", (long) id );
+ }
+
+ data = entry2str_wrap( e, &len, ldif_wrap );
+ be_entry_release_r( &op, e );
+
+ if ( data == NULL ) {
+ printf("# bad data for entry id=%08lx\n\n", (long) id );
+ rc = EXIT_FAILURE;
+ if( continuemode ) continue;
+ break;
+ }
+
+ if ( fputs( data, ldiffp->fp ) == EOF ||
+ fputs( "\n", ldiffp->fp ) == EOF ) {
+ fprintf(stderr, "%s: error writing output.\n",
+ progname);
+ rc = EXIT_FAILURE;
+ break;
+ }
+ }
+
+ be->be_entry_close( be );
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+ return rc;
+}
diff --git a/servers/slapd/slapcommon.c b/servers/slapd/slapcommon.c
new file mode 100644
index 0000000..374d08f
--- /dev/null
+++ b/servers/slapd/slapcommon.c
@@ -0,0 +1,1214 @@
+/* slapcommon.c - common routine for the slap tools */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Kurt Zeilenga for inclusion
+ * in OpenLDAP Software. Additional significant contributors include
+ * Jong Hyuk Choi
+ * Hallvard B. Furuseth
+ * Howard Chu
+ * Pierangelo Masarati
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include "slapcommon.h"
+#include "lutil.h"
+#include "ldif.h"
+
+tool_vars tool_globals;
+enum slaptool slapTool;
+
+#ifdef CSRIMALLOC
+static char *leakfilename;
+static FILE *leakfile;
+#endif
+
+static LDIFFP dummy;
+
+#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG)
+int start_syslog;
+#ifdef LOG_LOCAL4
+static int syslogUser = SLAP_DEFAULT_SYSLOG_USER;
+#endif /* LOG_LOCAL4 */
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+static void
+usage( int tool, const char *progname )
+{
+ char *options = NULL;
+ fprintf( stderr,
+ "usage: %s [-v] [-d debuglevel] [-f configfile] [-F configdir] [-o <name>[=<value>]]",
+ progname );
+
+ switch( tool ) {
+ case SLAPACL:
+ options = "\n\t[-U authcID | -D authcDN] [-X authzID | -o authzDN=<DN>]"
+ "\n\t-b DN [-u] [attr[/access][:value]] [...]\n";
+ break;
+
+ case SLAPADD:
+ options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]\n"
+ "\t[-l ldiffile] [-j linenumber] [-q] [-u] [-s] [-w]\n";
+ break;
+
+ case SLAPAUTH:
+ options = "\n\t[-U authcID] [-X authzID] [-R realm] [-M mech] ID [...]\n";
+ break;
+
+ case SLAPCAT:
+ options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]"
+ " [-l ldiffile] [-a filter] [-s subtree] [-H url]\n";
+ break;
+
+ case SLAPDN:
+ options = "\n\t[-N | -P] DN [...]\n";
+ break;
+
+ case SLAPINDEX:
+ options = " [-c]\n\t[-g] [-n databasenumber | -b suffix] [attr ...] [-q] [-t]\n";
+ break;
+
+ case SLAPMODIFY:
+ options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]\n"
+ "\t[-l ldiffile] [-j linenumber] [-q] [-u] [-s] [-w]\n";
+ break;
+
+ case SLAPTEST:
+ options = " [-n databasenumber] [-u] [-Q]\n";
+ break;
+
+ case SLAPSCHEMA:
+ options = " [-c]\n\t[-g] [-n databasenumber | -b suffix]"
+ " [-l errorfile] [-a filter] [-s subtree] [-H url]\n";
+ break;
+ }
+
+ if ( options != NULL ) {
+ fputs( options, stderr );
+ }
+ exit( EXIT_FAILURE );
+}
+
+static int
+parse_slapopt( int tool, int *mode )
+{
+ size_t len = 0;
+ char *p;
+
+ p = strchr( optarg, '=' );
+ if ( p != NULL ) {
+ len = p - optarg;
+ p++;
+ }
+
+ if ( strncasecmp( optarg, "sockurl", len ) == 0 ) {
+ if ( !BER_BVISNULL( &listener_url ) ) {
+ ber_memfree( listener_url.bv_val );
+ }
+ ber_str2bv( p, 0, 1, &listener_url );
+
+ } else if ( strncasecmp( optarg, "domain", len ) == 0 ) {
+ if ( !BER_BVISNULL( &peer_domain ) ) {
+ ber_memfree( peer_domain.bv_val );
+ }
+ ber_str2bv( p, 0, 1, &peer_domain );
+
+ } else if ( strncasecmp( optarg, "peername", len ) == 0 ) {
+ if ( !BER_BVISNULL( &peer_name ) ) {
+ ber_memfree( peer_name.bv_val );
+ }
+ ber_str2bv( p, 0, 1, &peer_name );
+
+ } else if ( strncasecmp( optarg, "sockname", len ) == 0 ) {
+ if ( !BER_BVISNULL( &sock_name ) ) {
+ ber_memfree( sock_name.bv_val );
+ }
+ ber_str2bv( p, 0, 1, &sock_name );
+
+ } else if ( strncasecmp( optarg, "ssf", len ) == 0 ) {
+ if ( lutil_atou( &ssf, p ) ) {
+ Debug( LDAP_DEBUG_ANY, "unable to parse ssf=\"%s\".\n", p );
+ return -1;
+ }
+
+ } else if ( strncasecmp( optarg, "transport_ssf", len ) == 0 ) {
+ if ( lutil_atou( &transport_ssf, p ) ) {
+ Debug( LDAP_DEBUG_ANY, "unable to parse transport_ssf=\"%s\".\n", p );
+ return -1;
+ }
+
+ } else if ( strncasecmp( optarg, "tls_ssf", len ) == 0 ) {
+ if ( lutil_atou( &tls_ssf, p ) ) {
+ Debug( LDAP_DEBUG_ANY, "unable to parse tls_ssf=\"%s\".\n", p );
+ return -1;
+ }
+
+ } else if ( strncasecmp( optarg, "sasl_ssf", len ) == 0 ) {
+ if ( lutil_atou( &sasl_ssf, p ) ) {
+ Debug( LDAP_DEBUG_ANY, "unable to parse sasl_ssf=\"%s\".\n", p );
+ return -1;
+ }
+
+ } else if ( strncasecmp( optarg, "authzDN", len ) == 0 ) {
+ ber_str2bv( p, 0, 1, &authzDN );
+
+#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG)
+ } else if ( strncasecmp( optarg, "syslog", len ) == 0 ) {
+ if ( slap_parse_debug_level( p, &ldap_syslog, 1 ) ) {
+ return -1;
+ }
+ start_syslog = 1;
+
+ } else if ( strncasecmp( optarg, "syslog-level", len ) == 0 ) {
+ if ( slap_parse_syslog_level( p, &ldap_syslog_level ) ) {
+ return -1;
+ }
+ start_syslog = 1;
+
+#ifdef LOG_LOCAL4
+ } else if ( strncasecmp( optarg, "syslog-user", len ) == 0 ) {
+ if ( slap_parse_syslog_user( p, &syslogUser ) ) {
+ return -1;
+ }
+ start_syslog = 1;
+#endif /* LOG_LOCAL4 */
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+ } else if ( strncasecmp( optarg, "schema-check", len ) == 0 ) {
+ switch ( tool ) {
+ case SLAPADD:
+ if ( strcasecmp( p, "yes" ) == 0 ) {
+ *mode &= ~SLAP_TOOL_NO_SCHEMA_CHECK;
+ } else if ( strcasecmp( p, "no" ) == 0 ) {
+ *mode |= SLAP_TOOL_NO_SCHEMA_CHECK;
+ } else {
+ Debug( LDAP_DEBUG_ANY, "unable to parse schema-check=\"%s\".\n", p );
+ return -1;
+ }
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "schema-check meaningless for tool.\n" );
+ break;
+ }
+
+ } else if ( strncasecmp( optarg, "value-check", len ) == 0 ) {
+ switch ( tool ) {
+ case SLAPADD:
+ if ( strcasecmp( p, "yes" ) == 0 ) {
+ *mode |= SLAP_TOOL_VALUE_CHECK;
+ } else if ( strcasecmp( p, "no" ) == 0 ) {
+ *mode &= ~SLAP_TOOL_VALUE_CHECK;
+ } else {
+ Debug( LDAP_DEBUG_ANY, "unable to parse value-check=\"%s\".\n", p );
+ return -1;
+ }
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "value-check meaningless for tool.\n" );
+ break;
+ }
+
+ } else if ( ( strncasecmp( optarg, "ldif_wrap", len ) == 0 ) ||
+ ( strncasecmp( optarg, "ldif-wrap", len ) == 0 ) ) {
+ switch ( tool ) {
+ case SLAPCAT:
+ if ( strcasecmp( p, "no" ) == 0 ) {
+ ldif_wrap = LDIF_LINE_WIDTH_MAX;
+
+ } else {
+ unsigned int u;
+ if ( lutil_atou( &u, p ) ) {
+ Debug( LDAP_DEBUG_ANY, "unable to parse ldif_wrap=\"%s\".\n", p );
+ return -1;
+ }
+ ldif_wrap = (ber_len_t)u;
+ }
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "ldif-wrap meaningless for tool.\n" );
+ break;
+ }
+
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * slap_tool_init - initialize slap utility, handle program options.
+ * arguments:
+ * name program name
+ * tool tool code
+ * argc, argv command line arguments
+ */
+
+static int need_shutdown;
+
+void
+slap_tool_init(
+ const char* progname,
+ int tool,
+ int argc, char **argv )
+{
+ char *options;
+ char *conffile = NULL;
+ char *confdir = NULL;
+ struct berval base = BER_BVNULL;
+ char *filterstr = NULL;
+ char *subtree = NULL;
+ char *ldiffile = NULL;
+ int rc, i;
+ int mode = SLAP_TOOL_MODE;
+ int truncatemode = 0;
+ int use_glue = 1;
+ int writer;
+
+#ifdef LDAP_DEBUG
+ /* tools default to "none", so that at least LDAP_DEBUG_ANY
+ * messages show up; use -d 0 to reset */
+ slap_debug = LDAP_DEBUG_NONE;
+ ldif_debug = slap_debug;
+#endif
+ ldap_syslog = 0;
+ /* make sure libldap gets init'd */
+ ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug );
+
+#ifdef CSRIMALLOC
+ leakfilename = malloc( strlen( progname ) + STRLENOF( ".leak" ) + 1 );
+ sprintf( leakfilename, "%s.leak", progname );
+ if( ( leakfile = fopen( leakfilename, "w" )) == NULL ) {
+ leakfile = stderr;
+ }
+ free( leakfilename );
+ leakfilename = NULL;
+#endif
+
+ ldif_wrap = LDIF_LINE_WIDTH;
+
+ scope = LDAP_SCOPE_DEFAULT;
+
+ switch( tool ) {
+ case SLAPADD:
+ options = "b:cd:f:F:gj:l:n:o:qsS:uvw";
+ break;
+
+ case SLAPCAT:
+ options = "a:b:cd:f:F:gH:l:n:o:s:v";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ case SLAPDN:
+ options = "d:f:F:No:Pv";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ case SLAPMODIFY:
+ options = "b:cd:f:F:gj:l:n:o:qsS:uvw";
+ break;
+
+ case SLAPSCHEMA:
+ options = "a:b:cd:f:F:gH:l:n:o:s:v";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ case SLAPTEST:
+ options = "d:f:F:n:o:Quv";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ case SLAPAUTH:
+ options = "d:f:F:M:o:R:U:vX:";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ case SLAPINDEX:
+ options = "b:cd:f:F:gn:o:qtv";
+ mode |= SLAP_TOOL_READMAIN;
+ break;
+
+ case SLAPACL:
+ options = "b:D:d:f:F:o:uU:vX:";
+ mode |= SLAP_TOOL_READMAIN | SLAP_TOOL_READONLY;
+ break;
+
+ default:
+ fprintf( stderr, "%s: unknown tool mode (%d)\n", progname, tool );
+ exit( EXIT_FAILURE );
+ }
+
+ dbnum = -1;
+ while ( (i = getopt( argc, argv, options )) != EOF ) {
+ switch ( i ) {
+ case 'a':
+ filterstr = optarg;
+ break;
+
+ case 'b':
+ ber_str2bv( optarg, 0, 1, &base );
+ break;
+
+ case 'c': /* enable continue mode */
+ continuemode++;
+ break;
+
+ case 'd': { /* turn on debugging */
+ int level = 0;
+
+ if ( slap_parse_debug_level( optarg, &level, 0 ) ) {
+ usage( tool, progname );
+ }
+#ifdef LDAP_DEBUG
+ if ( level == 0 ) {
+ /* allow to reset log level */
+ slap_debug = 0;
+
+ } else {
+ slap_debug |= level;
+ }
+#else
+ if ( level != 0 )
+ fputs( "must compile with LDAP_DEBUG for debugging\n",
+ stderr );
+#endif
+ } break;
+
+ case 'D':
+ ber_str2bv( optarg, 0, 1, &authcDN );
+ break;
+
+ case 'f': /* specify a conf file */
+ conffile = optarg;
+ break;
+
+ case 'F': /* specify a conf dir */
+ confdir = optarg;
+ break;
+
+ case 'g': /* disable subordinate glue */
+ use_glue = 0;
+ break;
+
+ case 'H': {
+ LDAPURLDesc *ludp;
+ int rc;
+
+ rc = ldap_url_parse_ext( optarg, &ludp,
+ LDAP_PVT_URL_PARSE_NOEMPTY_HOST | LDAP_PVT_URL_PARSE_NOEMPTY_DN );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ usage( tool, progname );
+ }
+
+ /* don't accept host, port, attrs, extensions */
+ if ( ldap_pvt_url_scheme2proto( ludp->lud_scheme ) != LDAP_PROTO_TCP ) {
+ usage( tool, progname );
+ }
+
+ if ( ludp->lud_host != NULL ) {
+ usage( tool, progname );
+ }
+
+ if ( ludp->lud_port != 0 ) {
+ usage( tool, progname );
+ }
+
+ if ( ludp->lud_attrs != NULL ) {
+ usage( tool, progname );
+ }
+
+ if ( ludp->lud_exts != NULL ) {
+ usage( tool, progname );
+ }
+
+ if ( ludp->lud_dn != NULL && ludp->lud_dn[0] != '\0' ) {
+ ch_free( subtree );
+ subtree = ludp->lud_dn;
+ ludp->lud_dn = NULL;
+ }
+
+ if ( ludp->lud_filter != NULL && ludp->lud_filter[0] != '\0' ) {
+ filterstr = ludp->lud_filter;
+ ludp->lud_filter = NULL;
+ }
+
+ scope = ludp->lud_scope;
+
+ ldap_free_urldesc( ludp );
+ } break;
+
+ case 'j': /* jump to linenumber */
+ if ( lutil_atoul( &jumpline, optarg ) ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case 'l': /* LDIF file */
+ ldiffile = optarg;
+ break;
+
+ case 'M':
+ ber_str2bv( optarg, 0, 0, &mech );
+ break;
+
+ case 'N':
+ if ( dn_mode && dn_mode != SLAP_TOOL_LDAPDN_NORMAL ) {
+ usage( tool, progname );
+ }
+ dn_mode = SLAP_TOOL_LDAPDN_NORMAL;
+ break;
+
+ case 'n': /* which config file db to index */
+ if ( lutil_atoi( &dbnum, optarg ) || dbnum < 0 ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case 'o':
+ if ( parse_slapopt( tool, &mode ) ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case 'P':
+ if ( dn_mode && dn_mode != SLAP_TOOL_LDAPDN_PRETTY ) {
+ usage( tool, progname );
+ }
+ dn_mode = SLAP_TOOL_LDAPDN_PRETTY;
+ break;
+
+ case 'Q':
+ quiet++;
+ slap_debug = 0;
+ break;
+
+ case 'q': /* turn on quick */
+ mode |= SLAP_TOOL_QUICK;
+ break;
+
+ case 'R':
+ realm = optarg;
+ break;
+
+ case 'S':
+ if ( lutil_atou( &csnsid, optarg )
+ || csnsid > SLAP_SYNC_SID_MAX )
+ {
+ usage( tool, progname );
+ }
+ break;
+
+ case 's':
+ switch ( tool ) {
+ case SLAPADD:
+ case SLAPMODIFY:
+ /* no schema check */
+ mode |= SLAP_TOOL_NO_SCHEMA_CHECK;
+ break;
+
+ case SLAPCAT:
+ case SLAPSCHEMA:
+ /* dump subtree */
+ ch_free( subtree );
+ subtree = ch_strdup( optarg );
+ break;
+ }
+ break;
+
+ case 't': /* turn on truncate */
+ truncatemode++;
+ mode |= SLAP_TRUNCATE_MODE;
+ break;
+
+ case 'U':
+ ber_str2bv( optarg, 0, 0, &authcID );
+ break;
+
+ case 'u': /* dry run */
+ dryrun++;
+ mode |= SLAP_TOOL_DRYRUN;
+ break;
+
+ case 'v': /* turn on verbose */
+ verbose++;
+ break;
+
+ case 'w': /* write context csn at the end */
+ update_ctxcsn++;
+ break;
+
+ case 'X':
+ ber_str2bv( optarg, 0, 0, &authzID );
+ break;
+
+ default:
+ usage( tool, progname );
+ break;
+ }
+ }
+ slap_debug_orig = slap_debug;
+
+#if defined(LDAP_SYSLOG) && defined(LDAP_DEBUG)
+ if ( start_syslog ) {
+ char *logName;
+#ifdef HAVE_EBCDIC
+ logName = ch_strdup( progname );
+ __atoe( logName );
+#else
+ logName = (char *)progname;
+#endif
+
+#ifdef LOG_LOCAL4
+ openlog( logName, OPENLOG_OPTIONS, syslogUser );
+#elif defined LOG_DEBUG
+ openlog( logName, OPENLOG_OPTIONS );
+#endif
+#ifdef HAVE_EBCDIC
+ free( logName );
+ logName = NULL;
+#endif
+ }
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+ switch ( tool ) {
+ case SLAPCAT:
+ case SLAPSCHEMA:
+ writer = 1;
+ break;
+
+ default:
+ writer = 0;
+ break;
+ }
+
+ switch ( tool ) {
+ case SLAPADD:
+ case SLAPCAT:
+ case SLAPMODIFY:
+ case SLAPSCHEMA:
+ if ( ( argc != optind ) || (dbnum >= 0 && base.bv_val != NULL ) ) {
+ usage( tool, progname );
+ }
+
+ break;
+
+ case SLAPINDEX:
+ if ( dbnum >= 0 && base.bv_val != NULL ) {
+ usage( tool, progname );
+ }
+
+ break;
+
+ case SLAPDN:
+ if ( argc == optind ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case SLAPAUTH:
+ if ( argc == optind && BER_BVISNULL( &authcID ) ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case SLAPTEST:
+ if ( argc != optind ) {
+ usage( tool, progname );
+ }
+ break;
+
+ case SLAPACL:
+ if ( !BER_BVISNULL( &authcDN ) && !BER_BVISNULL( &authcID ) ) {
+ usage( tool, progname );
+ }
+ if ( BER_BVISNULL( &base ) ) {
+ usage( tool, progname );
+ }
+ ber_dupbv( &baseDN, &base );
+ break;
+
+ default:
+ break;
+ }
+
+ if ( ldiffile == NULL ) {
+ dummy.fp = writer ? stdout : stdin;
+ ldiffp = &dummy;
+
+ } else if ((ldiffp = ldif_open( ldiffile, writer ? "w" : "r" ))
+ == NULL )
+ {
+ perror( ldiffile );
+ exit( EXIT_FAILURE );
+ }
+
+ /*
+ * initialize stuff and figure out which backend we're dealing with
+ */
+
+ slapTool = tool;
+ rc = slap_init( mode, progname );
+ if ( rc != 0 ) {
+ fprintf( stderr, "%s: slap_init failed!\n", progname );
+ exit( EXIT_FAILURE );
+ }
+
+ rc = read_config( conffile, confdir );
+
+ if ( rc != 0 ) {
+ fprintf( stderr, "%s: bad configuration %s!\n",
+ progname, confdir ? "directory" : "file" );
+ exit( EXIT_FAILURE );
+ }
+
+ rc = slap_parse_debug_unknowns();
+ if ( rc )
+ exit( EXIT_FAILURE );
+
+ at_oc_cache = 1;
+
+ switch ( tool ) {
+ case SLAPADD:
+ case SLAPCAT:
+ case SLAPINDEX:
+ case SLAPMODIFY:
+ case SLAPSCHEMA:
+ if ( !nbackends ) {
+ fprintf( stderr, "No databases found "
+ "in config file\n" );
+ exit( EXIT_FAILURE );
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if ( use_glue ) {
+ rc = glue_sub_attach( 0 );
+
+ if ( rc != 0 ) {
+ fprintf( stderr,
+ "%s: subordinate configuration error\n", progname );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ rc = slap_schema_check();
+
+ if ( rc != 0 ) {
+ fprintf( stderr, "%s: slap_schema_prep failed!\n", progname );
+ exit( EXIT_FAILURE );
+ }
+
+ switch ( tool ) {
+ case SLAPTEST:
+ if ( dbnum >= 0 )
+ goto get_db;
+ /* FALLTHRU */
+ case SLAPDN:
+ case SLAPAUTH:
+ be = NULL;
+ goto startup;
+
+ default:
+ break;
+ }
+
+ if( filterstr ) {
+ filter = str2filter( filterstr );
+
+ if( filter == NULL ) {
+ fprintf( stderr, "Invalid filter '%s'\n", filterstr );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ if( subtree ) {
+ struct berval val;
+ ber_str2bv( subtree, 0, 0, &val );
+ rc = dnNormalize( 0, NULL, NULL, &val, &sub_ndn, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "Invalid subtree DN '%s'\n", subtree );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( BER_BVISNULL( &base ) && dbnum == -1 ) {
+ base = val;
+ } else {
+ free( subtree );
+ subtree = NULL;
+ }
+ }
+
+ if( base.bv_val != NULL ) {
+ struct berval nbase;
+
+ rc = dnNormalize( 0, NULL, NULL, &base, &nbase, NULL );
+ if( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: slap_init invalid suffix (\"%s\")\n",
+ progname, base.bv_val );
+ exit( EXIT_FAILURE );
+ }
+
+ be = select_backend( &nbase, 0 );
+ ber_memfree( nbase.bv_val );
+ BER_BVZERO( &nbase );
+
+ if( be == NULL ) {
+ fprintf( stderr, "%s: slap_init no backend for \"%s\"\n",
+ progname, base.bv_val );
+ exit( EXIT_FAILURE );
+ }
+ switch ( tool ) {
+ case SLAPACL:
+ goto startup;
+
+ default:
+ break;
+ }
+
+ /* If the named base is a glue primary, operate on the
+ * entire context
+ */
+ if ( SLAP_GLUE_INSTANCE( be ) ) {
+ nosubordinates = 1;
+ }
+
+ ch_free( base.bv_val );
+ BER_BVZERO( &base );
+
+ } else if ( dbnum == -1 ) {
+ /* no suffix and no dbnum specified, just default to
+ * the first available database
+ */
+ if ( nbackends <= 0 ) {
+ fprintf( stderr, "No available databases\n" );
+ exit( EXIT_FAILURE );
+ }
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ dbnum++;
+
+ /* db #0 is cn=config, don't select it as a default */
+ if ( dbnum < 1 ) continue;
+
+ if ( SLAP_MONITOR(be))
+ continue;
+
+ /* If just doing the first by default and it is a
+ * glue subordinate, find the primary.
+ */
+ if ( SLAP_GLUE_SUBORDINATE(be) ) {
+ nosubordinates = 1;
+ continue;
+ }
+ break;
+ }
+
+ if ( !be ) {
+ fprintf( stderr, "Available database(s) "
+ "do not allow %s\n", progname );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( nosubordinates == 0 && dbnum > 1 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "The first database does not allow %s;"
+ " using the first available one (%d)\n",
+ progname, dbnum );
+ }
+
+ } else if ( dbnum >= nbackends ) {
+ fprintf( stderr,
+ "Database number selected via -n is out of range\n"
+ "Must be in the range 0 to %d"
+ " (the number of configured databases)\n",
+ nbackends - 1 );
+ exit( EXIT_FAILURE );
+
+ } else {
+get_db:
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ if ( dbnum == 0 ) break;
+ dbnum--;
+ }
+ }
+
+ if ( scope != LDAP_SCOPE_DEFAULT && BER_BVISNULL( &sub_ndn ) ) {
+ if ( be && be->be_nsuffix ) {
+ ber_dupbv( &sub_ndn, be->be_nsuffix );
+
+ } else {
+ fprintf( stderr,
+ "<scope> needs a DN or a valid database\n" );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+startup:;
+ if ( be ) {
+ BackendDB *bdtmp;
+
+ dbnum = 0;
+ LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) {
+ if ( bdtmp == be ) break;
+ dbnum++;
+ }
+ }
+
+#ifdef CSRIMALLOC
+ mal_leaktrace(1);
+#endif
+
+
+ /* slapdn doesn't specify a backend to startup */
+ if ( !dryrun && tool != SLAPDN ) {
+ need_shutdown = 1;
+
+ if ( slap_startup( be ) ) {
+ switch ( tool ) {
+ case SLAPTEST:
+ fprintf( stderr, "slap_startup failed "
+ "(test would succeed using "
+ "the -u switch)\n" );
+ break;
+
+ default:
+ fprintf( stderr, "slap_startup failed\n" );
+ break;
+ }
+
+ exit( EXIT_FAILURE );
+ }
+ }
+}
+
+int slap_tool_destroy( void )
+{
+ int rc = 0;
+ if ( !dryrun ) {
+ if ( need_shutdown ) {
+ if ( slap_shutdown( be ))
+ rc = EXIT_FAILURE;
+ }
+ if ( slap_destroy())
+ rc = EXIT_FAILURE;
+ }
+#ifdef SLAPD_MODULES
+ if ( slapMode == SLAP_SERVER_MODE ) {
+ /* always false. just pulls in necessary symbol references. */
+ lutil_uuidstr(NULL, 0);
+ }
+ module_kill();
+#endif
+ schema_destroy();
+#ifdef HAVE_TLS
+ ldap_pvt_tls_destroy();
+#endif
+ config_destroy();
+
+#ifdef CSRIMALLOC
+ mal_dumpleaktrace( leakfile );
+#endif
+
+ if ( !BER_BVISNULL( &authcDN ) ) {
+ ch_free( authcDN.bv_val );
+ BER_BVZERO( &authcDN );
+ }
+
+ if ( ldiffp && ldiffp != &dummy ) {
+ ldif_close( ldiffp );
+ }
+ return rc;
+}
+
+int
+slap_tool_update_ctxcsn(
+ const char *progname,
+ unsigned long sid,
+ struct berval *bvtext )
+{
+ struct berval ctxdn;
+ ID ctxcsn_id;
+ Entry *ctxcsn_e;
+ int rc = EXIT_SUCCESS;
+
+ if ( !(update_ctxcsn && !dryrun && sid != SLAP_SYNC_SID_MAX + 1) ) {
+ return rc;
+ }
+
+ if ( SLAP_SYNC_SUBENTRY( be )) {
+ build_new_dn( &ctxdn, &be->be_nsuffix[0],
+ (struct berval *)&slap_ldapsync_cn_bv, NULL );
+ } else {
+ ctxdn = be->be_nsuffix[0];
+ }
+ ctxcsn_id = be->be_dn2id_get( be, &ctxdn );
+ if ( ctxcsn_id == NOID ) {
+ if ( SLAP_SYNC_SUBENTRY( be )) {
+ ctxcsn_e = slap_create_context_csn_entry( be, NULL );
+ for ( sid = 0; sid <= SLAP_SYNC_SID_MAX; sid++ ) {
+ if ( maxcsn[ sid ].bv_len ) {
+ attr_merge_one( ctxcsn_e, slap_schema.si_ad_contextCSN,
+ &maxcsn[ sid ], NULL );
+ }
+ }
+ ctxcsn_id = be->be_entry_put( be, ctxcsn_e, bvtext );
+ if ( ctxcsn_id == NOID ) {
+ fprintf( stderr, "%s: couldn't create context entry\n", progname );
+ rc = EXIT_FAILURE;
+ }
+ entry_free( ctxcsn_e );
+ } else {
+ fprintf( stderr, "%s: context entry is missing\n", progname );
+ rc = EXIT_FAILURE;
+ }
+ } else {
+ ctxcsn_e = be->be_entry_get( be, ctxcsn_id );
+ if ( ctxcsn_e != NULL ) {
+ Operation op = { 0 };
+ Entry *e = entry_dup( ctxcsn_e );
+ Attribute *attr = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN );
+
+ int change;
+ op.o_bd = be;
+ be_entry_release_r( &op, ctxcsn_e );
+
+ if ( attr ) {
+ int i;
+
+ change = 0;
+
+ for ( i = 0; !BER_BVISNULL( &attr->a_nvals[ i ] ); i++ ) {
+ int rc_sid;
+ int match;
+ const char *text = NULL;
+
+ rc_sid = slap_parse_csn_sid( &attr->a_nvals[ i ] );
+ if ( rc_sid < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: unable to extract SID "
+ "from #%d contextCSN=%s\n",
+ progname, i,
+ attr->a_nvals[ i ].bv_val );
+ continue;
+ }
+
+ assert( rc_sid <= SLAP_SYNC_SID_MAX );
+
+ sid = (unsigned)rc_sid;
+
+ if ( maxcsn[ sid ].bv_len == 0 ) {
+ match = -1;
+
+ } else {
+ value_match( &match, slap_schema.si_ad_entryCSN,
+ slap_schema.si_ad_entryCSN->ad_type->sat_ordering,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &maxcsn[ sid ], &attr->a_nvals[i], &text );
+ }
+
+ if ( match > 0 ) {
+ change = 1;
+ } else {
+ AC_MEMCPY( maxcsn[ sid ].bv_val,
+ attr->a_nvals[ i ].bv_val,
+ attr->a_nvals[ i ].bv_len );
+ maxcsn[ sid ].bv_val[ attr->a_nvals[ i ].bv_len ] = '\0';
+ maxcsn[ sid ].bv_len = attr->a_nvals[ i ].bv_len;
+ }
+ }
+
+ if ( change ) {
+ if ( attr->a_nvals != attr->a_vals ) {
+ ber_bvarray_free( attr->a_nvals );
+ }
+ attr->a_nvals = NULL;
+ ber_bvarray_free( attr->a_vals );
+ attr->a_vals = NULL;
+ attr->a_numvals = 0;
+ }
+ } else {
+ change = 1;
+ }
+
+ if ( change ) {
+ for ( sid = 0; sid <= SLAP_SYNC_SID_MAX; sid++ ) {
+ if ( maxcsn[ sid ].bv_len ) {
+ attr_merge_one( e, slap_schema.si_ad_contextCSN,
+ &maxcsn[ sid], NULL );
+ }
+ }
+
+ ctxcsn_id = be->be_entry_modify( be, e, bvtext );
+ if( ctxcsn_id == NOID ) {
+ fprintf( stderr, "%s: could not modify ctxcsn (%s)\n",
+ progname, bvtext->bv_val ? bvtext->bv_val : "" );
+ rc = EXIT_FAILURE;
+ } else if ( verbose ) {
+ fprintf( stderr, "modified: \"%s\" (%08lx)\n",
+ e->e_dn, (long) ctxcsn_id );
+ }
+ }
+ entry_free( e );
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * return value:
+ * -1: update_ctxcsn == 0
+ * SLAP_SYNC_SID_MAX + 1: unable to extract SID
+ * 0 <= SLAP_SYNC_SID_MAX: the SID
+ */
+unsigned long
+slap_tool_update_ctxcsn_check(
+ const char *progname,
+ Entry *e )
+{
+ if ( update_ctxcsn ) {
+ unsigned long sid = SLAP_SYNC_SID_MAX + 1;
+ int rc_sid;
+ Attribute *attr;
+
+ attr = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN );
+ assert( attr != NULL );
+
+ rc_sid = slap_parse_csn_sid( &attr->a_nvals[ 0 ] );
+ if ( rc_sid < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: could not "
+ "extract SID from entryCSN=%s, entry dn=\"%s\"\n",
+ progname, attr->a_nvals[ 0 ].bv_val, e->e_name.bv_val );
+ return (unsigned long)(-1);
+
+ } else {
+ int match;
+ const char *text = NULL;
+
+ assert( rc_sid <= SLAP_SYNC_SID_MAX );
+
+ sid = (unsigned)rc_sid;
+ if ( maxcsn[ sid ].bv_len != 0 ) {
+ match = 0;
+ value_match( &match, slap_schema.si_ad_entryCSN,
+ slap_schema.si_ad_entryCSN->ad_type->sat_ordering,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &maxcsn[ sid ], &attr->a_nvals[0], &text );
+ } else {
+ match = -1;
+ }
+ if ( match < 0 ) {
+ strcpy( maxcsn[ sid ].bv_val, attr->a_nvals[0].bv_val );
+ maxcsn[ sid ].bv_len = attr->a_nvals[0].bv_len;
+ }
+ }
+ }
+
+ return (unsigned long)(-1);
+}
+
+int
+slap_tool_update_ctxcsn_init(void)
+{
+ if ( update_ctxcsn ) {
+ unsigned long sid;
+ maxcsn[ 0 ].bv_val = maxcsnbuf;
+ for ( sid = 1; sid <= SLAP_SYNC_SID_MAX; sid++ ) {
+ maxcsn[ sid ].bv_val = maxcsn[ sid - 1 ].bv_val + LDAP_PVT_CSNSTR_BUFSIZE;
+ maxcsn[ sid ].bv_len = 0;
+ }
+ }
+
+ return 0;
+}
+
+int
+slap_tool_entry_check(
+ const char *progname,
+ Operation *op,
+ Entry *e,
+ int lineno,
+ const char **text,
+ char *textbuf,
+ size_t textlen )
+{
+ /* NOTE: we may want to conditionally enable manage */
+ int manage = 0;
+
+ Attribute *oc = attr_find( e->e_attrs,
+ slap_schema.si_ad_objectClass );
+
+ if( oc == NULL ) {
+ fprintf( stderr, "%s: dn=\"%s\" (line=%d): %s\n",
+ progname, e->e_dn, lineno,
+ "no objectClass attribute");
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ /* check schema */
+ op->o_bd = be;
+
+ if ( (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) == 0) {
+ int rc = entry_schema_check( op, e, NULL, manage, 1, NULL,
+ text, textbuf, textlen );
+
+ if( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n",
+ progname, e->e_dn, lineno, rc, *text );
+ return rc;
+ }
+ textbuf[ 0 ] = '\0';
+ }
+
+ if ( (slapMode & SLAP_TOOL_VALUE_CHECK) != 0) {
+ Modifications *ml = NULL;
+
+ int rc = slap_entry2mods( e, &ml, text, textbuf, textlen );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n",
+ progname, e->e_dn, lineno, rc, *text );
+ return rc;
+ }
+ textbuf[ 0 ] = '\0';
+
+ rc = slap_mods_check( op, ml, text, textbuf, textlen, NULL );
+ slap_mods_free( ml, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: dn=\"%s\" (line=%d): (%d) %s\n",
+ progname, e->e_dn, lineno, rc, *text );
+ return rc;
+ }
+ textbuf[ 0 ] = '\0';
+ }
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/slapcommon.h b/servers/slapd/slapcommon.h
new file mode 100644
index 0000000..f0de178
--- /dev/null
+++ b/servers/slapd/slapcommon.h
@@ -0,0 +1,125 @@
+/* slapcommon.h - common definitions for the slap tools */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef SLAPCOMMON_H_
+#define SLAPCOMMON_H_ 1
+
+#define SLAPD_TOOLS 1
+#include "slap.h"
+
+typedef struct tool_vars {
+ Backend *tv_be;
+ int tv_dbnum;
+ int tv_verbose;
+ int tv_quiet;
+ int tv_update_ctxcsn;
+ int tv_continuemode;
+ int tv_nosubordinates;
+ int tv_dryrun;
+ int tv_scope;
+ unsigned long tv_jumpline;
+ struct berval tv_sub_ndn;
+ Filter *tv_filter;
+ struct LDIFFP *tv_ldiffp;
+ struct berval tv_baseDN;
+ struct berval tv_authcDN;
+ struct berval tv_authzDN;
+ struct berval tv_authcID;
+ struct berval tv_authzID;
+ struct berval tv_mech;
+ char *tv_realm;
+ struct berval tv_listener_url;
+ struct berval tv_peer_domain;
+ struct berval tv_peer_name;
+ struct berval tv_sock_name;
+ slap_ssf_t tv_ssf;
+ slap_ssf_t tv_transport_ssf;
+ slap_ssf_t tv_tls_ssf;
+ slap_ssf_t tv_sasl_ssf;
+ unsigned tv_dn_mode;
+ unsigned int tv_csnsid;
+ ber_len_t tv_ldif_wrap;
+ char tv_maxcsnbuf[ LDAP_PVT_CSNSTR_BUFSIZE * ( SLAP_SYNC_SID_MAX + 1 ) ];
+ struct berval tv_maxcsn[ SLAP_SYNC_SID_MAX + 1 ];
+} tool_vars;
+
+extern tool_vars tool_globals;
+
+#define be tool_globals.tv_be
+#define dbnum tool_globals.tv_dbnum
+#define verbose tool_globals.tv_verbose
+#define quiet tool_globals.tv_quiet
+#define jumpline tool_globals.tv_jumpline
+#define update_ctxcsn tool_globals.tv_update_ctxcsn
+#define continuemode tool_globals.tv_continuemode
+#define nosubordinates tool_globals.tv_nosubordinates
+#define dryrun tool_globals.tv_dryrun
+#define sub_ndn tool_globals.tv_sub_ndn
+#define scope tool_globals.tv_scope
+#define filter tool_globals.tv_filter
+#define ldiffp tool_globals.tv_ldiffp
+#define baseDN tool_globals.tv_baseDN
+#define authcDN tool_globals.tv_authcDN
+#define authzDN tool_globals.tv_authzDN
+#define authcID tool_globals.tv_authcID
+#define authzID tool_globals.tv_authzID
+#define mech tool_globals.tv_mech
+#define realm tool_globals.tv_realm
+#define listener_url tool_globals.tv_listener_url
+#define peer_domain tool_globals.tv_peer_domain
+#define peer_name tool_globals.tv_peer_name
+#define sock_name tool_globals.tv_sock_name
+#define ssf tool_globals.tv_ssf
+#define transport_ssf tool_globals.tv_transport_ssf
+#define tls_ssf tool_globals.tv_tls_ssf
+#define sasl_ssf tool_globals.tv_sasl_ssf
+#define dn_mode tool_globals.tv_dn_mode
+#define csnsid tool_globals.tv_csnsid
+#define ldif_wrap tool_globals.tv_ldif_wrap
+#define maxcsn tool_globals.tv_maxcsn
+#define maxcsnbuf tool_globals.tv_maxcsnbuf
+
+#define SLAP_TOOL_LDAPDN_PRETTY SLAP_LDAPDN_PRETTY
+#define SLAP_TOOL_LDAPDN_NORMAL (SLAP_LDAPDN_PRETTY << 1)
+
+void slap_tool_init LDAP_P((
+ const char* name,
+ int tool,
+ int argc, char **argv ));
+
+int slap_tool_destroy LDAP_P((void));
+
+int slap_tool_update_ctxcsn LDAP_P((
+ const char *progname,
+ unsigned long sid,
+ struct berval *bvtext ));
+
+unsigned long slap_tool_update_ctxcsn_check LDAP_P((
+ const char *progname,
+ Entry *e ));
+
+int slap_tool_update_ctxcsn_init LDAP_P((void));
+
+int slap_tool_entry_check LDAP_P((
+ const char *progname,
+ Operation *op,
+ Entry *e,
+ int lineno,
+ const char **text,
+ char *textbuf,
+ size_t textlen ));
+
+#endif /* SLAPCOMMON_H_ */
diff --git a/servers/slapd/slapd.conf b/servers/slapd/slapd.conf
new file mode 100644
index 0000000..2fcab71
--- /dev/null
+++ b/servers/slapd/slapd.conf
@@ -0,0 +1,79 @@
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include %SYSCONFDIR%/schema/core.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile %LOCALSTATEDIR%/run/slapd.pid
+argsfile %LOCALSTATEDIR%/run/slapd.args
+
+# Load dynamic backend modules:
+modulepath %MODULEDIR%
+moduleload back_mdb.la
+# moduleload back_ldap.la
+
+# Sample security restrictions
+# Require integrity protection (prevent hijacking)
+# Require 112-bit (3DES or better) encryption for updates
+# Require 63-bit encryption for simple bind
+# security ssf=1 update_ssf=112 simple_bind=64
+
+# Sample access control policy:
+# Root DSE: allow anyone to read it
+# Subschema (sub)entry DSE: allow anyone to read it
+# Other DSEs:
+# Allow self write access
+# Allow authenticated users read access
+# Allow anonymous users to authenticate
+# Directives needed to implement policy:
+# access to dn.base="" by * read
+# access to dn.base="cn=Subschema" by * read
+# access to *
+# by self write
+# by users read
+# by anonymous auth
+#
+# if no access controls are present, the default policy
+# allows anyone and everyone to read anything but restricts
+# updates to rootdn. (e.g., "access to * by * read")
+#
+# rootdn can always read and write EVERYTHING!
+
+#######################################################################
+# config database definitions
+#######################################################################
+database config
+# Uncomment the rootpw line to allow binding as the cn=config
+# rootdn so that temporary modifications to the configuration can be made
+# while slapd is running. They will not persist across a restart.
+# rootpw secret
+
+#######################################################################
+# MDB database definitions
+#######################################################################
+
+database mdb
+maxsize 1073741824
+suffix "dc=my-domain,dc=com"
+rootdn "cn=Manager,dc=my-domain,dc=com"
+# Cleartext passwords, especially for the rootdn, should
+# be avoid. See slappasswd(8) and slapd.conf(5) for details.
+# Use of strong authentication encouraged.
+rootpw secret
+# The database directory MUST exist prior to running slapd AND
+# should only be accessible by the slapd and slap tools.
+# Mode 700 recommended.
+directory %LOCALSTATEDIR%/openldap-data
+# Indices to maintain
+index objectClass eq
+
+#######################################################################
+# monitor database definitions
+#######################################################################
+database monitor
diff --git a/servers/slapd/slapd.ldif b/servers/slapd/slapd.ldif
new file mode 100644
index 0000000..99e53de
--- /dev/null
+++ b/servers/slapd/slapd.ldif
@@ -0,0 +1,99 @@
+#
+# See slapd-config(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+dn: cn=config
+objectClass: olcGlobal
+cn: config
+#
+#
+# Define global ACLs to disable default read access.
+#
+olcArgsFile: %LOCALSTATEDIR%/run/slapd.args
+olcPidFile: %LOCALSTATEDIR%/run/slapd.pid
+#
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#olcReferral: ldap://root.openldap.org
+#
+# Sample security restrictions
+# Require integrity protection (prevent hijacking)
+# Require 112-bit (3DES or better) encryption for updates
+# Require 64-bit encryption for simple bind
+#olcSecurity: ssf=1 update_ssf=112 simple_bind=64
+
+
+#
+# Load dynamic backend modules:
+#
+dn: cn=module,cn=config
+objectClass: olcModuleList
+cn: module
+olcModulepath: %MODULEDIR%
+olcModuleload: back_mdb.la
+#olcModuleload: back_ldap.la
+#olcModuleload: back_passwd.la
+
+dn: cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: schema
+
+include: file://%SYSCONFDIR%/schema/core.ldif
+
+# Frontend settings
+#
+dn: olcDatabase=frontend,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcFrontendConfig
+olcDatabase: frontend
+#
+# Sample global access control policy:
+# Root DSE: allow anyone to read it
+# Subschema (sub)entry DSE: allow anyone to read it
+# Other DSEs:
+# Allow self write access
+# Allow authenticated users read access
+# Allow anonymous users to authenticate
+#
+#olcAccess: to dn.base="" by * read
+#olcAccess: to dn.base="cn=Subschema" by * read
+#olcAccess: to *
+# by self write
+# by users read
+# by anonymous auth
+#
+# if no access controls are present, the default policy
+# allows anyone and everyone to read anything but restricts
+# updates to rootdn. (e.g., "access to * by * read")
+#
+# rootdn can always read and write EVERYTHING!
+#
+
+
+#######################################################################
+# LMDB database definitions
+#######################################################################
+#
+dn: olcDatabase=mdb,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcMdbConfig
+olcDatabase: mdb
+olcDbMaxSize: 1073741824
+olcSuffix: dc=my-domain,dc=com
+olcRootDN: cn=Manager,dc=my-domain,dc=com
+# Cleartext passwords, especially for the rootdn, should
+# be avoided. See slappasswd(8) and slapd-config(5) for details.
+# Use of strong authentication encouraged.
+olcRootPW: secret
+# The database directory MUST exist prior to running slapd AND
+# should only be accessible by the slapd and slap tools.
+# Mode 700 recommended.
+olcDbDirectory: %LOCALSTATEDIR%/openldap-data
+# Indices to maintain
+olcDbIndex: objectClass eq
+
+dn: olcDatabase=monitor,cn=config
+objectClass: olcDatabaseConfig
+olcDatabase: monitor
+olcRootDN: cn=config
+olcMonitoring: FALSE
diff --git a/servers/slapd/slapd.service b/servers/slapd/slapd.service
new file mode 100644
index 0000000..a0599b8
--- /dev/null
+++ b/servers/slapd/slapd.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=OpenLDAP Server Daemon
+After=syslog.target network-online.target
+Documentation=man:slapd
+Documentation=man:slapd-config
+Documentation=man:slapd-mdb
+
+[Service]
+Type=notify
+Environment="SLAPD_URLS=ldap:/// ldapi:///" "SLAPD_OPTIONS="
+EnvironmentFile=/etc/sysconfig/slapd
+ExecStart=%LIBEXECDIR%/slapd -d 0 -h ${SLAPD_URLS} $SLAPD_OPTIONS
+
+[Install]
+WantedBy=multi-user.target
diff --git a/servers/slapd/slapdn.c b/servers/slapd/slapdn.c
new file mode 100644
index 0000000..d38322e
--- /dev/null
+++ b/servers/slapd/slapdn.c
@@ -0,0 +1,107 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include <lber.h>
+#include <ldif.h>
+#include <lutil.h>
+
+#include "slapcommon.h"
+
+int
+slapdn( int argc, char **argv )
+{
+ int rc = 0;
+ const char *progname = "slapdn";
+
+ slap_tool_init( progname, SLAPDN, argc, argv );
+
+ argv = &argv[ optind ];
+ argc -= optind;
+
+ for ( ; argc--; argv++ ) {
+ struct berval dn,
+ pdn = BER_BVNULL,
+ ndn = BER_BVNULL;
+
+ ber_str2bv( argv[ 0 ], 0, 0, &dn );
+
+ switch ( dn_mode ) {
+ case SLAP_TOOL_LDAPDN_PRETTY:
+ rc = dnPretty( NULL, &dn, &pdn, NULL );
+ break;
+
+ case SLAP_TOOL_LDAPDN_NORMAL:
+ rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+ break;
+
+ default:
+ rc = dnPrettyNormal( NULL, &dn, &pdn, &ndn, NULL );
+ break;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "DN: <%s> check failed %d (%s)\n",
+ dn.bv_val, rc,
+ ldap_err2string( rc ) );
+ if ( !continuemode ) {
+ rc = -1;
+ break;
+ }
+
+ } else {
+ switch ( dn_mode ) {
+ case SLAP_TOOL_LDAPDN_PRETTY:
+ printf( "%s\n", pdn.bv_val );
+ break;
+
+ case SLAP_TOOL_LDAPDN_NORMAL:
+ printf( "%s\n", ndn.bv_val );
+ break;
+
+ default:
+ printf( "DN: <%s> check succeeded\n"
+ "normalized: <%s>\n"
+ "pretty: <%s>\n",
+ dn.bv_val,
+ ndn.bv_val, pdn.bv_val );
+ break;
+ }
+
+ ch_free( ndn.bv_val );
+ ch_free( pdn.bv_val );
+ }
+ }
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
diff --git a/servers/slapd/slapi/Makefile.in b/servers/slapd/slapi/Makefile.in
new file mode 100644
index 0000000..32ade0c
--- /dev/null
+++ b/servers/slapd/slapi/Makefile.in
@@ -0,0 +1,51 @@
+# Makefile.in for SLAPI
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## Portions Copyright IBM Corp. 1997,2002,2003
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+LIBRARY = libslapi.la
+
+#all-common: $(LIBRARY) $(PROGRAMS)
+# @touch plugin.c slapi_pblock.c slapi_utils.c slapi_ops.c slapi_ext.c
+
+LIB_DEFS = -DSLAPI_LIBRARY
+
+SRCS= plugin.c slapi_pblock.c slapi_utils.c printmsg.c slapi_ops.c slapi_dn.c slapi_ext.c slapi_overlay.c \
+ $(@PLAT@_SRCS)
+OBJS= plugin.lo slapi_pblock.lo slapi_utils.lo printmsg.lo slapi_ops.lo slapi_dn.lo slapi_ext.lo slapi_overlay.lo \
+ $(@PLAT@_SRCS)
+
+XSRCS= version.c
+
+LDAP_INCDIR= ../../../include -I.. -I.
+LDAP_LIBDIR= ../../../libraries
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+
+XLIBS = $(LIBRARY)
+XXLIBS =
+MOD_LIBS = $(MODULES_LIBS)
+NT_LINK_LIBS = $(AC_LIBS) -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+XINCPATH = -I$(srcdir)/.. -I$(srcdir)
+XDEFS = $(MODULES_CPPFLAGS)
+
+BUILD_MOD = @BUILD_SLAPI@
+
+install-local: FORCE
+ if test "$(BUILD_MOD)" = "yes"; then \
+ $(MKDIR) $(DESTDIR)$(libdir); \
+ $(LTINSTALL) $(INSTALLFLAGS) -m 644 $(LIBRARY) $(DESTDIR)$(libdir); \
+ fi
+
diff --git a/servers/slapd/slapi/TODO b/servers/slapd/slapi/TODO
new file mode 100644
index 0000000..8916488
--- /dev/null
+++ b/servers/slapd/slapi/TODO
@@ -0,0 +1,16 @@
+- de-IBM SLAPI
+- add a config statement, or redefine the dynamic backend one,
+ "modulepath", to set/modify the load path also for plugins
+ (both plugins and modules use ltdl, so "modulepath" suffices ...)
+- improve slapi logging (use some [v]s[n]printf function)
+- add a config statement to set the log file name, or better
+- use syslog where available?
+- add some plugin monitoring stuff in back-monitor (e.g. a subentry
+ for each plugin with data from struct Slapi_PluginDesc)
+- This is a very tough task: try to implement a sandbox to execute
+ plugins in, trap deadly signals and possibly disable unsafe plugins
+ without crashing slapd (fork from inside thread? trap signals
+ and longjump to next plugin execution? Brrr).
+
+---
+$OpenLDAP$
diff --git a/servers/slapd/slapi/plugin.c b/servers/slapd/slapi/plugin.c
new file mode 100644
index 0000000..ca5dbea
--- /dev/null
+++ b/servers/slapd/slapi/plugin.c
@@ -0,0 +1,832 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#include "portable.h"
+
+/*
+ * Note: if ltdl.h is not available, slapi should not be compiled
+ */
+
+#ifdef HAVE_LTDL_H
+#include "ldap_pvt_thread.h"
+#include "slap.h"
+#include "slap-config.h"
+#include "slapi.h"
+#include "lutil.h"
+
+#include <ltdl.h>
+
+static int slapi_int_load_plugin( Slapi_PBlock *, const char *, const char *, int,
+ SLAPI_FUNC *, lt_dlhandle *, ConfigArgs *c );
+
+/* pointer to link list of extended objects */
+static ExtendedOp *pGExtendedOps = NULL;
+
+/*********************************************************************
+ * Function Name: plugin_pblock_new
+ *
+ * Description: This routine creates a new Slapi_PBlock structure,
+ * loads in the plugin module and executes the init
+ * function provided by the module.
+ *
+ * Input: type - type of the plugin, such as SASL, database, etc.
+ * path - the loadpath to load the module in
+ * initfunc - name of the plugin function to execute first
+ * argc - number of arguments
+ * argv[] - an array of char pointers point to
+ * the arguments passed in via
+ * the configuration file.
+ *
+ * Output:
+ *
+ * Return Values: a pointer to a newly created Slapi_PBlock structure or
+ * NULL - function failed
+ *
+ * Messages: None
+ *********************************************************************/
+
+static Slapi_PBlock *
+plugin_pblock_new(
+ int type,
+ int argc,
+ ConfigArgs *c )
+{
+ Slapi_PBlock *pPlugin = NULL;
+ Slapi_PluginDesc *pPluginDesc = NULL;
+ lt_dlhandle hdLoadHandle;
+ int rc;
+ char **av2 = NULL, **ppPluginArgv;
+ char *path = c->argv[2];
+ char *initfunc = c->argv[3];
+
+ pPlugin = slapi_pblock_new();
+ if ( pPlugin == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type );
+ slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGC, (void *)&argc );
+
+ av2 = ldap_charray_dup( c->argv );
+ if ( av2 == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ if ( argc > 0 ) {
+ ppPluginArgv = &av2[4];
+ } else {
+ ppPluginArgv = NULL;
+ }
+
+ slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGV, (void *)ppPluginArgv );
+ slapi_pblock_set( pPlugin, SLAPI_X_CONFIG_ARGV, (void *)av2 );
+
+ rc = slapi_int_load_plugin( pPlugin, path, initfunc, 1, NULL, &hdLoadHandle, c );
+ if ( rc != 0 ) {
+ goto done;
+ }
+
+ if ( slapi_pblock_get( pPlugin, SLAPI_PLUGIN_DESCRIPTION, (void **)&pPluginDesc ) == 0 &&
+ pPluginDesc != NULL ) {
+ slapi_log_error(SLAPI_LOG_TRACE, "plugin_pblock_new",
+ "Registered plugin %s %s [%s] (%s)\n",
+ pPluginDesc->spd_id,
+ pPluginDesc->spd_version,
+ pPluginDesc->spd_vendor,
+ pPluginDesc->spd_description);
+ }
+
+done:
+ if ( rc != 0 && pPlugin != NULL ) {
+ slapi_pblock_destroy( pPlugin );
+ pPlugin = NULL;
+ if ( av2 != NULL ) {
+ ldap_charray_free( av2 );
+ }
+ }
+
+ return pPlugin;
+}
+
+/*********************************************************************
+ * Function Name: slapi_int_register_plugin
+ *
+ * Description: insert the slapi_pblock structure to a given position the end of the plugin
+ * list
+ *
+ * Input: a pointer to a plugin slapi_pblock structure to be added to
+ * the list
+ *
+ * Output: none
+ *
+ * Return Values: LDAP_SUCCESS - successfully inserted.
+ * LDAP_LOCAL_ERROR.
+ *
+ * Messages: None
+ *********************************************************************/
+int
+slapi_int_register_plugin_index(
+ Backend *be,
+ Slapi_PBlock *pPB,
+ int index )
+{
+ Slapi_PBlock *pTmpPB;
+ Slapi_PBlock *pSavePB;
+ int pos = 0, rc = LDAP_SUCCESS;
+
+ assert( be != NULL );
+
+ pTmpPB = SLAPI_BACKEND_PBLOCK( be );
+ if ( pTmpPB == NULL || index == 0 ) {
+ SLAPI_BACKEND_PBLOCK( be ) = pPB;
+ } else {
+ while ( pTmpPB != NULL && rc == LDAP_SUCCESS &&
+ ( index < 0 || pos++ < index ) ) {
+ pSavePB = pTmpPB;
+ rc = slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pTmpPB );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = slapi_pblock_set( pSavePB, SLAPI_IBM_PBLOCK, (void *)pPB );
+ }
+ }
+
+ if ( index >= 0 && rc == LDAP_SUCCESS ) {
+ rc = slapi_pblock_set( pPB, SLAPI_IBM_PBLOCK, (void *)pTmpPB );
+ }
+
+ return ( rc != LDAP_SUCCESS ) ? LDAP_OTHER : LDAP_SUCCESS;
+}
+
+int
+slapi_int_register_plugin(
+ Backend *be,
+ Slapi_PBlock *pPB )
+{
+ return slapi_int_register_plugin_index( be, pPB, -1 );
+}
+
+/*********************************************************************
+ * Function Name: slapi_int_get_plugins
+ *
+ * Description: get the desired type of function pointers defined
+ * in all the plugins
+ *
+ * Input: the type of the functions to get, such as pre-operation,etc.
+ *
+ * Output: none
+ *
+ * Return Values: this routine returns a pointer to an array of function
+ * pointers containing backend-specific plugin functions
+ * followed by global plugin functions
+ *
+ * Messages: None
+ *********************************************************************/
+int
+slapi_int_get_plugins(
+ Backend *be,
+ int functype,
+ SLAPI_FUNC **ppFuncPtrs )
+{
+
+ Slapi_PBlock *pCurrentPB;
+ SLAPI_FUNC FuncPtr;
+ SLAPI_FUNC *pTmpFuncPtr;
+ int numPB = 0;
+ int rc = LDAP_SUCCESS;
+
+ assert( ppFuncPtrs != NULL );
+ *ppFuncPtrs = NULL;
+
+ if ( be == NULL ) {
+ goto done;
+ }
+
+ pCurrentPB = SLAPI_BACKEND_PBLOCK( be );
+
+ while ( pCurrentPB != NULL && rc == LDAP_SUCCESS ) {
+ rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( FuncPtr != NULL ) {
+ numPB++;
+ }
+ rc = slapi_pblock_get( pCurrentPB,
+ SLAPI_IBM_PBLOCK, &pCurrentPB );
+ }
+ }
+
+ if ( numPB == 0 ) {
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ /*
+ * Now, build the function pointer array of backend-specific
+ * plugins followed by global plugins.
+ */
+ *ppFuncPtrs = pTmpFuncPtr =
+ (SLAPI_FUNC *)ch_malloc( ( numPB + 1 ) * sizeof(SLAPI_FUNC) );
+ if ( ppFuncPtrs == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ pCurrentPB = SLAPI_BACKEND_PBLOCK( be );
+
+ while ( pCurrentPB != NULL && rc == LDAP_SUCCESS ) {
+ rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( FuncPtr != NULL ) {
+ *pTmpFuncPtr = FuncPtr;
+ pTmpFuncPtr++;
+ }
+ rc = slapi_pblock_get( pCurrentPB,
+ SLAPI_IBM_PBLOCK, &pCurrentPB );
+ }
+ }
+
+ *pTmpFuncPtr = NULL;
+
+
+done:
+ if ( rc != LDAP_SUCCESS && *ppFuncPtrs != NULL ) {
+ ch_free( *ppFuncPtrs );
+ *ppFuncPtrs = NULL;
+ }
+
+ return rc;
+}
+
+/*********************************************************************
+ * Function Name: createExtendedOp
+ *
+ * Description: Creates an extended operation structure and
+ * initializes the fields
+ *
+ * Return value: A newly allocated structure or NULL
+ ********************************************************************/
+ExtendedOp *
+createExtendedOp()
+{
+ ExtendedOp *ret;
+
+ ret = (ExtendedOp *)slapi_ch_malloc(sizeof(ExtendedOp));
+ ret->ext_oid.bv_val = NULL;
+ ret->ext_oid.bv_len = 0;
+ ret->ext_func = NULL;
+ ret->ext_be = NULL;
+ ret->ext_next = NULL;
+
+ return ret;
+}
+
+
+/*********************************************************************
+ * Function Name: slapi_int_unregister_extop
+ *
+ * Description: This routine removes the ExtendedOp structures
+ * asscoiated with a particular extended operation
+ * plugin.
+ *
+ * Input: pBE - pointer to a backend structure
+ * opList - pointer to a linked list of extended
+ * operation structures
+ * pPB - pointer to a slapi parameter block
+ *
+ * Output:
+ *
+ * Return Value: none
+ *
+ * Messages: None
+ *********************************************************************/
+void
+slapi_int_unregister_extop(
+ Backend *pBE,
+ ExtendedOp **opList,
+ Slapi_PBlock *pPB )
+{
+ ExtendedOp *pTmpExtOp, *backExtOp;
+ char **pTmpOIDs;
+ int i;
+
+#if 0
+ assert( pBE != NULL); /* unused */
+#endif /* 0 */
+ assert( opList != NULL );
+ assert( pPB != NULL );
+
+ if ( *opList == NULL ) {
+ return;
+ }
+
+ slapi_pblock_get( pPB, SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs );
+ if ( pTmpOIDs == NULL ) {
+ return;
+ }
+
+ for ( i = 0; pTmpOIDs[i] != NULL; i++ ) {
+ backExtOp = NULL;
+ pTmpExtOp = *opList;
+ for ( ; pTmpExtOp != NULL; pTmpExtOp = pTmpExtOp->ext_next) {
+ int rc;
+ rc = strcasecmp( pTmpExtOp->ext_oid.bv_val,
+ pTmpOIDs[ i ] );
+ if ( rc == 0 ) {
+ if ( backExtOp == NULL ) {
+ *opList = pTmpExtOp->ext_next;
+ } else {
+ backExtOp->ext_next
+ = pTmpExtOp->ext_next;
+ }
+
+ ch_free( pTmpExtOp );
+ break;
+ }
+ backExtOp = pTmpExtOp;
+ }
+ }
+}
+
+
+/*********************************************************************
+ * Function Name: slapi_int_register_extop
+ *
+ * Description: This routine creates a new ExtendedOp structure, loads
+ * in the extended op module and put the extended op function address
+ * in the structure. The function will not be executed in
+ * this routine.
+ *
+ * Input: pBE - pointer to a backend structure
+ * opList - pointer to a linked list of extended
+ * operation structures
+ * pPB - pointer to a slapi parameter block
+ *
+ * Output:
+ *
+ * Return Value: an LDAP return code
+ *
+ * Messages: None
+ *********************************************************************/
+int
+slapi_int_register_extop(
+ Backend *pBE,
+ ExtendedOp **opList,
+ Slapi_PBlock *pPB )
+{
+ ExtendedOp *pTmpExtOp = NULL;
+ SLAPI_FUNC tmpFunc;
+ char **pTmpOIDs;
+ int rc = LDAP_OTHER;
+ int i;
+
+ if ( (*opList) == NULL ) {
+ *opList = createExtendedOp();
+ if ( (*opList) == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto error_return;
+ }
+ pTmpExtOp = *opList;
+
+ } else { /* Find the end of the list */
+ for ( pTmpExtOp = *opList; pTmpExtOp->ext_next != NULL;
+ pTmpExtOp = pTmpExtOp->ext_next )
+ ; /* EMPTY */
+ pTmpExtOp->ext_next = createExtendedOp();
+ if ( pTmpExtOp->ext_next == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto error_return;
+ }
+ pTmpExtOp = pTmpExtOp->ext_next;
+ }
+
+ rc = slapi_pblock_get( pPB,SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs );
+ if ( rc != 0 ) {
+ rc = LDAP_OTHER;
+ goto error_return;
+ }
+
+ rc = slapi_pblock_get(pPB,SLAPI_PLUGIN_EXT_OP_FN, &tmpFunc);
+ if ( rc != 0 ) {
+ rc = LDAP_OTHER;
+ goto error_return;
+ }
+
+ if ( (pTmpOIDs == NULL) || (tmpFunc == NULL) ) {
+ rc = LDAP_OTHER;
+ goto error_return;
+ }
+
+ for ( i = 0; pTmpOIDs[i] != NULL; i++ ) {
+ pTmpExtOp->ext_oid.bv_val = pTmpOIDs[i];
+ pTmpExtOp->ext_oid.bv_len = strlen( pTmpOIDs[i] );
+ pTmpExtOp->ext_func = tmpFunc;
+ pTmpExtOp->ext_be = pBE;
+ if ( pTmpOIDs[i + 1] != NULL ) {
+ pTmpExtOp->ext_next = createExtendedOp();
+ if ( pTmpExtOp->ext_next == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ break;
+ }
+ pTmpExtOp = pTmpExtOp->ext_next;
+ }
+ }
+
+error_return:
+ return rc;
+}
+
+/*********************************************************************
+ * Function Name: slapi_int_get_extop_plugin
+ *
+ * Description: This routine gets the function address for a given function
+ * name.
+ *
+ * Input:
+ * funcName - name of the extended op function, ie. an OID.
+ *
+ * Output: pFuncAddr - the function address of the requested function name.
+ *
+ * Return Values: a pointer to a newly created ExtendOp structure or
+ * NULL - function failed
+ *
+ * Messages: None
+ *********************************************************************/
+int
+slapi_int_get_extop_plugin(
+ struct berval *reqoid,
+ SLAPI_FUNC *pFuncAddr )
+{
+ ExtendedOp *pTmpExtOp;
+
+ assert( reqoid != NULL );
+ assert( pFuncAddr != NULL );
+
+ *pFuncAddr = NULL;
+
+ if ( pGExtendedOps == NULL ) {
+ return LDAP_OTHER;
+ }
+
+ pTmpExtOp = pGExtendedOps;
+ while ( pTmpExtOp != NULL ) {
+ int rc;
+
+ rc = strcasecmp( reqoid->bv_val, pTmpExtOp->ext_oid.bv_val );
+ if ( rc == 0 ) {
+ *pFuncAddr = pTmpExtOp->ext_func;
+ break;
+ }
+ pTmpExtOp = pTmpExtOp->ext_next;
+ }
+
+ return ( *pFuncAddr == NULL ? 1 : 0 );
+}
+
+/***************************************************************************
+ * This function is similar to slapi_int_get_extop_plugin above. except it returns one OID
+ * per call. It is called from root_dse_info (root_dse.c).
+ * The function is a modified version of get_supported_extop (file extended.c).
+ ***************************************************************************/
+struct berval *
+slapi_int_get_supported_extop( int index )
+{
+ ExtendedOp *ext;
+
+ for ( ext = pGExtendedOps ; ext != NULL && --index >= 0;
+ ext = ext->ext_next) {
+ ; /* empty */
+ }
+
+ if ( ext == NULL ) {
+ return NULL;
+ }
+
+ return &ext->ext_oid ;
+}
+
+/*********************************************************************
+ * Function Name: slapi_int_load_plugin
+ *
+ * Description: This routine loads the specified DLL, gets and executes the init function
+ * if requested.
+ *
+ * Input:
+ * pPlugin - a pointer to a Slapi_PBlock struct which will be passed to
+ * the DLL init function.
+ * path - path name of the DLL to be load.
+ * initfunc - either the DLL initialization function or an OID of the
+ * loaded extended operation.
+ * doInit - if it is TRUE, execute the init function, otherwise, save the
+ * function address but not execute it.
+ *
+ * Output: pInitFunc - the function address of the loaded function. This param
+ * should be not be null if doInit is FALSE.
+ * pLdHandle - handle returned by lt_dlopen()
+ *
+ * Return Values: LDAP_SUCCESS, LDAP_LOCAL_ERROR
+ *
+ * Messages: None
+ *********************************************************************/
+
+static int
+slapi_int_load_plugin(
+ Slapi_PBlock *pPlugin,
+ const char *path,
+ const char *initfunc,
+ int doInit,
+ SLAPI_FUNC *pInitFunc,
+ lt_dlhandle *pLdHandle,
+ ConfigArgs *c )
+{
+ int rc = LDAP_SUCCESS;
+ SLAPI_FUNC fpInitFunc = NULL;
+
+ assert( pLdHandle != NULL );
+
+ if ( lt_dlinit() ) {
+ return LDAP_LOCAL_ERROR;
+ }
+
+ /* load in the module */
+ *pLdHandle = lt_dlopen( path );
+ if ( *pLdHandle == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "failed to load plugin %s: %s",
+ path, lt_dlerror() );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return LDAP_LOCAL_ERROR;
+ }
+
+ fpInitFunc = (SLAPI_FUNC)lt_dlsym( *pLdHandle, initfunc );
+ if ( fpInitFunc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "failed to find symbol %s in plugin %s: %s",
+ initfunc, path, lt_dlerror() );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ lt_dlclose( *pLdHandle );
+ return LDAP_LOCAL_ERROR;
+ }
+
+ if ( doInit ) {
+ rc = ( *fpInitFunc )( pPlugin );
+ if ( rc != LDAP_SUCCESS ) {
+ lt_dlclose( *pLdHandle );
+ }
+
+ } else {
+ *pInitFunc = fpInitFunc;
+ }
+
+ return rc;
+}
+
+/*
+ * Special support for computed attribute plugins
+ */
+int
+slapi_int_call_plugins(
+ Backend *be,
+ int funcType,
+ Slapi_PBlock *pPB )
+{
+
+ int rc = 0;
+ SLAPI_FUNC *pGetPlugin = NULL, *tmpPlugin = NULL;
+
+ if ( pPB == NULL ) {
+ return 1;
+ }
+
+ rc = slapi_int_get_plugins( be, funcType, &tmpPlugin );
+ if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) {
+ /* Nothing to do, front-end should ignore. */
+ return rc;
+ }
+
+ for ( pGetPlugin = tmpPlugin ; *pGetPlugin != NULL; pGetPlugin++ ) {
+ rc = (*pGetPlugin)(pPB);
+
+ /*
+ * Only non-postoperation plugins abort processing on
+ * failure (confirmed with SLAPI specification).
+ */
+ if ( !SLAPI_PLUGIN_IS_POST_FN( funcType ) && rc != 0 ) {
+ /*
+ * Plugins generally return negative error codes
+ * to indicate failure, although in the case of
+ * bind plugins they may return SLAPI_BIND_xxx
+ */
+ break;
+ }
+ }
+
+ slapi_ch_free( (void **)&tmpPlugin );
+
+ return rc;
+}
+
+int
+slapi_int_read_config(
+ struct config_args_s *c )
+{
+ int iType = -1;
+ int numPluginArgc = 0;
+
+ if ( c->argc < 4 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing arguments "
+ "in \"plugin <plugin_type> <lib_path> "
+ "<init_function> [<arguments>]\" line" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ /* automatically instantiate overlay if necessary */
+ if ( !slapi_over_is_inst( c->be ) ) {
+ if ( slapi_over_config( c->be, &c->reply ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "%s: "
+ "Failed to instantiate SLAPI overlay: "
+ "err=%d msg=\"%s\"\n", c->log, c->reply.err, c->reply.msg );
+ return -1;
+ }
+ }
+
+ if ( strcasecmp( c->argv[1], "preoperation" ) == 0 ) {
+ iType = SLAPI_PLUGIN_PREOPERATION;
+ } else if ( strcasecmp( c->argv[1], "postoperation" ) == 0 ) {
+ iType = SLAPI_PLUGIN_POSTOPERATION;
+ } else if ( strcasecmp( c->argv[1], "extendedop" ) == 0 ) {
+ iType = SLAPI_PLUGIN_EXTENDEDOP;
+ } else if ( strcasecmp( c->argv[1], "object" ) == 0 ) {
+ iType = SLAPI_PLUGIN_OBJECT;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid plugin type \"%s\"", c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ numPluginArgc = c->argc - 4;
+
+ if ( iType == SLAPI_PLUGIN_PREOPERATION ||
+ iType == SLAPI_PLUGIN_EXTENDEDOP ||
+ iType == SLAPI_PLUGIN_POSTOPERATION ||
+ iType == SLAPI_PLUGIN_OBJECT ) {
+ int rc;
+ Slapi_PBlock *pPlugin;
+
+ pPlugin = plugin_pblock_new( iType, numPluginArgc, c->argv );
+ if (pPlugin == NULL) {
+ return 1;
+ }
+
+ if (iType == SLAPI_PLUGIN_EXTENDEDOP) {
+ rc = slapi_int_register_extop(c->be, &pGExtendedOps, pPlugin);
+ if ( rc != LDAP_SUCCESS ) {
+ slapi_pblock_destroy( pPlugin );
+ return 1;
+ }
+ }
+
+ rc = slapi_int_register_plugin_index( c->be, pPlugin, c->valx );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( iType == SLAPI_PLUGIN_EXTENDEDOP ) {
+ slapi_int_unregister_extop( c->be, &pGExtendedOps, pPlugin );
+ }
+ slapi_pblock_destroy( pPlugin );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int
+slapi_int_unregister_plugin(
+ Backend *be,
+ Slapi_PBlock *pPlugin,
+ Slapi_PBlock *pPrev
+)
+{
+ int type;
+
+ assert( pPlugin != NULL );
+
+ slapi_pblock_get( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type );
+ if ( type == SLAPI_PLUGIN_EXTENDEDOP ) {
+ slapi_int_unregister_extop( be, &pGExtendedOps, pPlugin );
+ }
+
+ if ( pPrev != NULL ) {
+ Slapi_PBlock *pNext = NULL;
+
+ slapi_pblock_get( pPlugin, SLAPI_IBM_PBLOCK, &pNext );
+ slapi_pblock_set( pPrev, SLAPI_IBM_PBLOCK, &pNext );
+ }
+ slapi_pblock_destroy( pPlugin );
+
+ return LDAP_SUCCESS;
+}
+
+int
+slapi_int_unregister_plugins(
+ Backend *be,
+ int index
+)
+{
+ Slapi_PBlock *pTmpPB = NULL;
+ Slapi_PBlock *pSavePB = NULL;
+ int rc = LDAP_SUCCESS;
+
+ pTmpPB = SLAPI_BACKEND_PBLOCK( be );
+ if ( pTmpPB == NULL ) {
+ return ( index < 0 ) ? LDAP_SUCCESS : LDAP_OTHER;
+ }
+
+ if ( index < 0 ) {
+ /* All plugins must go */
+ while ( pTmpPB != NULL && rc == LDAP_SUCCESS ) {
+ pSavePB = pTmpPB;
+ rc = slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pTmpPB );
+ if ( pSavePB != NULL ) {
+ slapi_int_unregister_plugin( be, pSavePB, NULL );
+ }
+ }
+ } else if ( index == 0 ) {
+ slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pSavePB );
+ SLAPI_BACKEND_PBLOCK( be ) = pSavePB;
+ slapi_int_unregister_plugin( be, pTmpPB, NULL );
+ } else {
+ int pos = -1;
+ while ( pTmpPB != NULL && rc == LDAP_SUCCESS && ++pos < index ) {
+ pSavePB = pTmpPB;
+ rc = slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pTmpPB );
+ }
+ if ( pos == index ) {
+ slapi_int_unregister_plugin( be, pTmpPB, pSavePB );
+ }
+ }
+ return rc;
+}
+
+void
+slapi_int_plugin_unparse(
+ Backend *be,
+ BerVarray *out
+)
+{
+ Slapi_PBlock *pp;
+ int i, j;
+ char **argv, ibuf[32], *ptr;
+ struct berval idx, bv;
+
+ *out = NULL;
+ idx.bv_val = ibuf;
+ i = 0;
+
+ for ( pp = SLAPI_BACKEND_PBLOCK( be );
+ pp != NULL;
+ slapi_pblock_get( pp, SLAPI_IBM_PBLOCK, &pp ) )
+ {
+ slapi_pblock_get( pp, SLAPI_X_CONFIG_ARGV, &argv );
+ if ( argv == NULL ) /* could be dynamic plugin */
+ continue;
+ idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), "{%d}", i );
+ if ( idx.bv_len >= sizeof( ibuf ) ) {
+ /* FIXME: just truncating by now */
+ idx.bv_len = sizeof( ibuf ) - 1;
+ }
+ bv.bv_len = idx.bv_len;
+ for (j=1; argv[j]; j++) {
+ bv.bv_len += strlen(argv[j]);
+ if ( j ) bv.bv_len++;
+ }
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( bv.bv_val, ibuf );
+ for (j=1; argv[j]; j++) {
+ if ( j ) *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, argv[j] );
+ }
+ ber_bvarray_add( out, &bv );
+ }
+}
+#endif /* HAVE_LTDL_H */
diff --git a/servers/slapd/slapi/printmsg.c b/servers/slapd/slapi/printmsg.c
new file mode 100644
index 0000000..5251846
--- /dev/null
+++ b/servers/slapd/slapi/printmsg.c
@@ -0,0 +1,120 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani.
+ */
+
+#include <portable.h>
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/stdarg.h>
+#include <ac/unistd.h>
+#include <fcntl.h>
+#include <ac/errno.h>
+
+#include <ldap.h>
+#include <ldap_config.h>
+#include <slap.h>
+#include <slapi.h>
+
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+#include <ldap_pvt_thread.h>
+
+/* Single threads access to routine */
+ldap_pvt_thread_mutex_t slapi_printmessage_mutex;
+char *slapi_log_file = NULL;
+int slapi_log_level = SLAPI_LOG_PLUGIN;
+
+int
+slapi_int_log_error(
+ int level,
+ char *subsystem,
+ char *fmt,
+ va_list arglist )
+{
+ int rc = 0;
+ FILE *fp = NULL;
+
+ char timeStr[100];
+ struct tm *ltm;
+ time_t currentTime;
+
+ assert( subsystem != NULL );
+ assert( fmt != NULL );
+
+ ldap_pvt_thread_mutex_lock( &slapi_printmessage_mutex ) ;
+
+ /* for now, we log all severities */
+ if ( level <= slapi_log_level ) {
+#ifdef _WIN32
+ intptr_t fhandle;
+#endif
+ fp = fopen( slapi_log_file, "a" );
+ if ( fp == NULL) {
+ rc = -1;
+ goto done;
+ }
+
+#ifdef _WIN32
+ fhandle = _get_osfhandle( fileno( fp ));
+#endif
+ /*
+ * FIXME: could block
+ */
+#ifdef _WIN32
+ while ( LockFile( fhandle, 0, 0, UINT_MAX, UINT_MAX ) == 0 ) {
+ /* DO NOTHING */ ;
+ }
+#else
+ while ( lockf( fileno( fp ), F_LOCK, 0 ) != 0 ) {
+ /* DO NOTHING */ ;
+ }
+#endif
+
+ time( &currentTime );
+ ltm = localtime( &currentTime );
+ strftime( timeStr, sizeof(timeStr), "%x %X", ltm );
+ fputs( timeStr, fp );
+
+ fprintf( fp, " %s: ", subsystem );
+ vfprintf( fp, fmt, arglist );
+ if ( fmt[ strlen( fmt ) - 1 ] != '\n' ) {
+ fputs( "\n", fp );
+ }
+ fflush( fp );
+
+#ifdef _WIN32
+ UnlockFile( fhandle, 0, 0, UINT_MAX, UINT_MAX );
+#else
+ lockf( fileno( fp ), F_ULOCK, 0 );
+#endif
+
+ fclose( fp );
+
+ } else {
+ rc = -1;
+ }
+
+done:
+ ldap_pvt_thread_mutex_unlock( &slapi_printmessage_mutex );
+
+ return rc;
+}
diff --git a/servers/slapd/slapi/proto-slapi.h b/servers/slapd/slapi/proto-slapi.h
new file mode 100644
index 0000000..e9eb76b
--- /dev/null
+++ b/servers/slapd/slapi/proto-slapi.h
@@ -0,0 +1,92 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#ifndef _PROTO_SLAPI_H
+#define _PROTO_SLAPI_H
+
+LDAP_BEGIN_DECL
+
+/* slapi_utils.c */
+LDAP_SLAPI_F (LDAPMod **) slapi_int_modifications2ldapmods LDAP_P(( Modifications * ));
+LDAP_SLAPI_F (Modifications *) slapi_int_ldapmods2modifications LDAP_P(( Operation *op, LDAPMod ** ));
+LDAP_SLAPI_F (int) slapi_int_count_controls LDAP_P(( LDAPControl **ctrls ));
+LDAP_SLAPI_F (char **) slapi_get_supported_extended_ops LDAP_P((void));
+LDAP_SLAPI_F (int) slapi_int_access_allowed LDAP_P((Operation *op, Entry *entry, AttributeDescription *desc, struct berval *val, slap_access_t access, AccessControlState *state ));
+
+/* slapi_ops.c */
+LDAP_SLAPI_F (int) slapi_int_response LDAP_P(( Slapi_Operation *op, SlapReply *rs ));
+LDAP_SLAPI_F (void) slapi_int_connection_init_pb LDAP_P(( Slapi_PBlock *pb, ber_tag_t OpType ));
+LDAP_SLAPI_F (void) slapi_int_connection_done_pb LDAP_P(( Slapi_PBlock *pb ));
+
+/* slapi_pblock.c */
+LDAP_SLAPI_F (int) slapi_pblock_delete_param LDAP_P(( Slapi_PBlock *p, int param ));
+LDAP_SLAPI_F (void) slapi_pblock_clear LDAP_P(( Slapi_PBlock *pb ));
+
+LDAP_SLAPI_F (int) slapi_int_pblock_get_first LDAP_P(( Backend *be, Slapi_PBlock **pb ));
+LDAP_SLAPI_F (int) slapi_int_pblock_get_next LDAP_P(( Slapi_PBlock **pb ));
+
+#define PBLOCK_ASSERT_CONN( _pb ) do { \
+ assert( (_pb) != NULL ); \
+ assert( (_pb)->pb_conn != NULL ); \
+ } while (0)
+
+#define PBLOCK_ASSERT_OP( _pb, _tag ) do { \
+ PBLOCK_ASSERT_CONN( _pb ); \
+ assert( (_pb)->pb_op != NULL ); \
+ assert( (_pb)->pb_rs != NULL ); \
+ if ( _tag != 0 ) \
+ assert( (_pb)->pb_op->o_tag == (_tag)); \
+ } while (0)
+
+#define PBLOCK_ASSERT_INTOP( _pb, _tag ) do { \
+ PBLOCK_ASSERT_OP( _pb, _tag ); \
+ assert( (_pb)->pb_intop ); \
+ assert( (_pb)->pb_op == (Operation *)pb->pb_conn->c_pending_ops.stqh_first ); \
+ } while (0)
+
+/* plugin.c */
+LDAP_SLAPI_F (int) slapi_int_register_plugin LDAP_P((Backend *be, Slapi_PBlock *pPB));
+LDAP_SLAPI_F (int) slapi_int_register_plugin_index LDAP_P((Backend *be, Slapi_PBlock *pPB, int index));
+LDAP_SLAPI_F (int) slapi_int_call_plugins LDAP_P((Backend *be, int funcType, Slapi_PBlock * pPB));
+LDAP_SLAPI_F (int) slapi_int_get_plugins LDAP_P((Backend *be, int functype, SLAPI_FUNC **ppFuncPtrs));
+LDAP_SLAPI_F (int) slapi_int_register_extop LDAP_P((Backend *pBE, ExtendedOp **opList, Slapi_PBlock *pPB));
+LDAP_SLAPI_F (int) slapi_int_get_extop_plugin LDAP_P((struct berval *reqoid, SLAPI_FUNC *pFuncAddr ));
+LDAP_SLAPI_F (struct berval *) slapi_int_get_supported_extop LDAP_P(( int ));
+LDAP_SLAPI_F (int) slapi_int_unregister_plugins LDAP_P((Backend *be, int index));
+LDAP_SLAPI_F (int) slapi_int_read_config LDAP_P(( struct config_args_s *c ));
+LDAP_SLAPI_F (void) slapi_int_plugin_unparse LDAP_P((Backend *be, BerVarray *out ));
+LDAP_SLAPI_F (int) slapi_int_initialize LDAP_P((void));
+
+/* slapi_ext.c */
+LDAP_SLAPI_F (int) slapi_int_init_object_extensions LDAP_P((void));
+LDAP_SLAPI_F (int) slapi_int_free_object_extensions LDAP_P((int objecttype, void *object));
+LDAP_SLAPI_F (int) slapi_int_create_object_extensions LDAP_P((int objecttype, void *object));
+LDAP_SLAPI_F (int) slapi_int_clear_object_extensions LDAP_P((int objecttype, void *object));
+
+/* slapi_overlay.c */
+LDAP_SLAPI_F (int) slapi_over_is_inst LDAP_P((BackendDB *));
+LDAP_SLAPI_F (int) slapi_over_config LDAP_P((BackendDB *, ConfigReply *));
+
+LDAP_END_DECL
+
+#endif /* _PROTO_SLAPI_H */
+
diff --git a/servers/slapd/slapi/slapi.h b/servers/slapd/slapi/slapi.h
new file mode 100644
index 0000000..14d6bc1
--- /dev/null
+++ b/servers/slapd/slapi/slapi.h
@@ -0,0 +1,204 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#ifdef LDAP_SLAPI /* SLAPI is OPTIONAL */
+
+#ifndef _SLAPI_H
+#define _SLAPI_H
+
+LDAP_BEGIN_DECL
+
+/*
+ * Quick 'n' dirty to make struct slapi_* in slapi-plugin.h opaque
+ */
+#define slapi_entry Entry
+#define slapi_attr Attribute
+#define slapi_value berval
+#define slapi_valueset berval *
+#define slapi_filter Filter
+
+LDAP_END_DECL
+
+#include <slapi-plugin.h>
+
+LDAP_BEGIN_DECL
+
+#define SLAPI_OVERLAY_NAME "slapi"
+
+#define SLAPI_OPERATION_PBLOCK(_op) ((_op)->o_callback->sc_private)
+#define SLAPI_BACKEND_PBLOCK(_be) ((_be)->be_pb)
+
+#define SLAPI_OPERATION_EXTENSIONS(_op) ((_op)->o_hdr->oh_extensions)
+#define SLAPI_CONNECTION_EXTENSIONS(_conn) ((_conn)->c_extensions)
+
+#define SLAPI_CONTROL_MANAGEDSAIT_OID LDAP_CONTROL_MANAGEDSAIT
+#define SLAPI_CONTROL_SORTEDSEARCH_OID LDAP_CONTROL_SORTREQUEST
+#define SLAPI_CONTROL_PAGED_RESULTS_OID LDAP_CONTROL_PAGEDRESULTS
+
+typedef int (*SLAPI_FUNC)( Slapi_PBlock *pb );
+
+typedef struct _slapi_control {
+ int s_ctrl_num;
+ char **s_ctrl_oids;
+ unsigned long *s_ctrl_ops;
+} Slapi_Control;
+
+typedef struct _ExtendedOp {
+ struct berval ext_oid;
+ SLAPI_FUNC ext_func;
+ Backend *ext_be;
+ struct _ExtendedOp *ext_next;
+} ExtendedOp;
+
+/* Computed attribute support */
+struct _computed_attr_context {
+ Slapi_PBlock *cac_pb;
+ Operation *cac_op;
+ void *cac_private;
+};
+
+/* for slapi_attr_type_cmp() */
+#define SLAPI_TYPE_CMP_EXACT 0
+#define SLAPI_TYPE_CMP_BASE 1
+#define SLAPI_TYPE_CMP_SUBTYPE 2
+
+typedef enum slapi_extension_e {
+ SLAPI_X_EXT_CONNECTION = 0,
+ SLAPI_X_EXT_OPERATION = 1,
+ SLAPI_X_EXT_MAX = 2
+} slapi_extension_t;
+
+struct slapi_dn {
+ unsigned char flag;
+ struct berval dn;
+ struct berval ndn;
+};
+
+struct slapi_rdn {
+ unsigned char flag;
+ struct berval bv;
+ LDAPRDN rdn;
+};
+
+/*
+ * Was: slapi_pblock.h
+ */
+
+#ifndef NO_PBLOCK_CLASS /* where's this test from? */
+
+typedef enum slapi_pblock_class_e {
+ PBLOCK_CLASS_INVALID = 0,
+ PBLOCK_CLASS_INTEGER,
+ PBLOCK_CLASS_LONG_INTEGER,
+ PBLOCK_CLASS_POINTER,
+ PBLOCK_CLASS_FUNCTION_POINTER
+} slapi_pblock_class_t;
+
+#define PBLOCK_SUCCESS (0)
+#define PBLOCK_ERROR (-1)
+#define PBLOCK_MAX_PARAMS 100
+
+union slapi_pblock_value {
+ int pv_integer;
+ long pv_long_integer;
+ void *pv_pointer;
+ int (*pv_function_pointer)();
+};
+
+struct slapi_pblock {
+ ldap_pvt_thread_mutex_t pb_mutex;
+ int pb_nParams;
+ int pb_params[PBLOCK_MAX_PARAMS];
+ union slapi_pblock_value pb_values[PBLOCK_MAX_PARAMS];
+ /* native types */
+ Connection *pb_conn;
+ Operation *pb_op;
+ SlapReply *pb_rs;
+ int pb_intop;
+ char pb_textbuf[ SLAP_TEXT_BUFLEN ];
+};
+
+#endif /* !NO_PBLOCK_CLASS */
+
+/*
+ * Was: plugin.h
+ */
+
+#define SLAPI_PLUGIN_IS_POST_FN(x) ((x) >= SLAPI_PLUGIN_POST_BIND_FN && (x) <= SLAPI_PLUGIN_BE_POST_DELETE_FN)
+
+#define SLAPI_IBM_PBLOCK -3
+
+#define SLAPI_ENTRY_PRE_OP 52
+#define SLAPI_ENTRY_POST_OP 53
+
+/* This is the spelling in the SunOne 5.2 docs */
+#define SLAPI_RES_CONTROLS SLAPI_RESCONTROLS
+
+#define SLAPI_ABANDON_MSGID 120
+
+#define SLAPI_OPERATION_PARAMETERS 138
+
+#define SLAPI_SEQ_TYPE 150
+#define SLAPI_SEQ_ATTRNAME 151
+#define SLAPI_SEQ_VAL 152
+
+#define SLAPI_MR_FILTER_ENTRY 170
+#define SLAPI_MR_FILTER_TYPE 171
+#define SLAPI_MR_FILTER_VALUE 172
+#define SLAPI_MR_FILTER_OID 173
+#define SLAPI_MR_FILTER_DNATTRS 174
+
+#define SLAPI_LDIF2DB_FILE 180
+#define SLAPI_LDIF2DB_REMOVEDUPVALS 185
+
+#define SLAPI_DB2LDIF_PRINTKEY 183
+
+#define SLAPI_CHANGENUMBER 197
+#define SLAPI_LOG_OPERATION 198
+
+#define SLAPI_DBSIZE 199
+
+#define SLAPI_PLUGIN_DB_TEST_FN 227
+#define SLAPI_PLUGIN_DB_NO_ACL 250
+
+/* OpenLDAP private parameters */
+#define SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN 1200
+#define SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN 1201
+
+#define SLAPI_X_CONFIG_ARGV 1400
+#define SLAPI_X_INTOP_FLAGS 1401
+#define SLAPI_X_INTOP_RESULT_CALLBACK 1402
+#define SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK 1403
+#define SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK 1404
+#define SLAPI_X_INTOP_CALLBACK_DATA 1405
+#define SLAPI_X_OLD_RESCONTROLS 1406
+
+LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_hn_mutex;
+LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_time_mutex;
+LDAP_SLAPI_V (ldap_pvt_thread_mutex_t) slapi_printmessage_mutex;
+LDAP_SLAPI_V (char *) slapi_log_file;
+LDAP_SLAPI_V (int) slapi_log_level;
+
+#include "proto-slapi.h"
+
+#endif /* _SLAPI_H */
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_dn.c b/servers/slapd/slapi/slapi_dn.c
new file mode 100644
index 0000000..32d04d2
--- /dev/null
+++ b/servers/slapd/slapi/slapi_dn.c
@@ -0,0 +1,669 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Luke Howard for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/stdarg.h>
+#include <ac/ctype.h>
+#include <ac/unistd.h>
+#include <ldap_pvt.h>
+
+#include <slap.h>
+#include <slapi.h>
+
+#ifdef LDAP_SLAPI
+#define FLAG_DN 0x1
+#define FLAG_NDN 0x2
+
+void slapi_sdn_init( Slapi_DN *sdn )
+{
+ sdn->flag = 0;
+ BER_BVZERO( &sdn->dn );
+ BER_BVZERO( &sdn->ndn );
+}
+
+Slapi_DN *slapi_sdn_new( void )
+{
+ Slapi_DN *sdn;
+
+ sdn = (Slapi_DN *)slapi_ch_malloc( sizeof(*sdn ));
+ slapi_sdn_init( sdn );
+
+ return sdn;
+}
+
+void slapi_sdn_done( Slapi_DN *sdn )
+{
+ if ( sdn == NULL )
+ return;
+
+ if ( sdn->flag & FLAG_DN ) {
+ slapi_ch_free_string( &sdn->dn.bv_val );
+ }
+ if ( sdn->flag & FLAG_NDN ) {
+ slapi_ch_free_string( &sdn->ndn.bv_val );
+ }
+
+ slapi_sdn_init( sdn );
+}
+
+void slapi_sdn_free( Slapi_DN **sdn )
+{
+ slapi_sdn_done( *sdn );
+ slapi_ch_free( (void **)sdn );
+}
+
+const char *slapi_sdn_get_dn( const Slapi_DN *sdn )
+{
+ if ( !BER_BVISNULL( &sdn->dn ) )
+ return sdn->dn.bv_val;
+ else
+ return sdn->ndn.bv_val;
+}
+
+const char *slapi_sdn_get_ndn( const Slapi_DN *sdn )
+{
+ if ( BER_BVISNULL( &sdn->ndn ) ) {
+ dnNormalize( 0, NULL, NULL,
+ (struct berval *)&sdn->dn, (struct berval *)&sdn->ndn, NULL );
+ ((Slapi_DN *)sdn)->flag |= FLAG_NDN;
+ }
+
+ return sdn->ndn.bv_val;
+}
+
+Slapi_DN *slapi_sdn_new_dn_byval( const char *dn )
+{
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new();
+ return slapi_sdn_set_dn_byval( sdn, dn );
+}
+
+Slapi_DN *slapi_sdn_new_ndn_byval( const char *ndn )
+{
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new();
+ return slapi_sdn_set_ndn_byval( sdn, ndn );
+}
+
+Slapi_DN *slapi_sdn_new_dn_byref( const char *dn )
+{
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new();
+ return slapi_sdn_set_dn_byref( sdn, dn );
+}
+
+Slapi_DN *slapi_sdn_new_ndn_byref( const char *ndn )
+{
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new();
+ return slapi_sdn_set_ndn_byref( sdn, ndn );
+}
+
+Slapi_DN *slapi_sdn_new_dn_passin( const char *dn )
+{
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new();
+ return slapi_sdn_set_dn_passin( sdn, dn );
+}
+
+Slapi_DN *slapi_sdn_set_dn_byval( Slapi_DN *sdn, const char *dn )
+{
+ if ( sdn == NULL ) {
+ return NULL;
+ }
+
+ slapi_sdn_done( sdn );
+ if ( dn != NULL ) {
+ sdn->dn.bv_val = slapi_ch_strdup( dn );
+ sdn->dn.bv_len = strlen( dn );
+ }
+ sdn->flag |= FLAG_DN;
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_dn_byref( Slapi_DN *sdn, const char *dn )
+{
+ if ( sdn == NULL )
+ return NULL;
+
+ slapi_sdn_done( sdn );
+ if ( dn != NULL ) {
+ sdn->dn.bv_val = (char *)dn;
+ sdn->dn.bv_len = strlen( dn );
+ }
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_dn_passin( Slapi_DN *sdn, const char *dn )
+{
+ if ( sdn == NULL )
+ return NULL;
+
+ slapi_sdn_set_dn_byref( sdn, dn );
+ sdn->flag |= FLAG_DN;
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_ndn_byval( Slapi_DN *sdn, const char *ndn )
+{
+ if ( sdn == NULL ) {
+ return NULL;
+ }
+
+ slapi_sdn_done( sdn );
+ if ( ndn != NULL ) {
+ sdn->ndn.bv_val = slapi_ch_strdup( ndn );
+ sdn->ndn.bv_len = strlen( ndn );
+ }
+ sdn->flag |= FLAG_NDN;
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_ndn_byref( Slapi_DN *sdn, const char *ndn )
+{
+ if ( sdn == NULL )
+ return NULL;
+
+ slapi_sdn_done( sdn );
+ if ( ndn != NULL ) {
+ sdn->ndn.bv_val = (char *)ndn;
+ sdn->ndn.bv_len = strlen( ndn );
+ }
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_ndn_passin( Slapi_DN *sdn, const char *ndn )
+{
+ if ( sdn == NULL )
+ return NULL;
+
+ slapi_sdn_set_ndn_byref( sdn, ndn );
+ sdn->flag |= FLAG_NDN;
+
+ return sdn;
+}
+
+void slapi_sdn_get_parent( const Slapi_DN *sdn, Slapi_DN *sdn_parent )
+{
+ struct berval parent_dn;
+
+ if ( !(sdn->flag & FLAG_DN) ) {
+ dnParent( (struct berval *)&sdn->ndn, &parent_dn );
+ slapi_sdn_set_ndn_byval( sdn_parent, parent_dn.bv_val );
+ } else {
+ dnParent( (struct berval *)&sdn->dn, &parent_dn );
+ slapi_sdn_set_dn_byval( sdn_parent, parent_dn.bv_val );
+ }
+}
+
+void slapi_sdn_get_backend_parent( const Slapi_DN *sdn,
+ Slapi_DN *sdn_parent,
+ const Slapi_Backend *backend )
+{
+ slapi_sdn_get_ndn( sdn );
+
+ if ( backend == NULL ||
+ be_issuffix( (Slapi_Backend *)backend, (struct berval *)&sdn->ndn ) == 0 ) {
+ slapi_sdn_get_parent( sdn, sdn_parent );
+ }
+
+}
+
+Slapi_DN * slapi_sdn_dup( const Slapi_DN *sdn )
+{
+ Slapi_DN *new_sdn;
+
+ new_sdn = slapi_sdn_new();
+ slapi_sdn_copy( sdn, new_sdn );
+
+ return new_sdn;
+}
+
+void slapi_sdn_copy( const Slapi_DN *from, Slapi_DN *to )
+{
+ slapi_sdn_set_dn_byval( to, from->dn.bv_val );
+}
+
+int slapi_sdn_compare( const Slapi_DN *sdn1, const Slapi_DN *sdn2 )
+{
+ int match = -1;
+
+ slapi_sdn_get_ndn( sdn1 );
+ slapi_sdn_get_ndn( sdn2 );
+
+ dnMatch( &match, 0, slap_schema.si_syn_distinguishedName, NULL,
+ (struct berval *)&sdn1->ndn, (void *)&sdn2->ndn );
+
+ return match;
+}
+
+int slapi_sdn_isempty( const Slapi_DN *sdn)
+{
+ return ( BER_BVISEMPTY( &sdn->dn ) && BER_BVISEMPTY( &sdn->ndn ) );
+}
+
+int slapi_sdn_issuffix( const Slapi_DN *sdn, const Slapi_DN *suffix_sdn )
+{
+ slapi_sdn_get_ndn( sdn );
+ slapi_sdn_get_ndn( suffix_sdn );
+
+ return dnIsSuffix( &sdn->ndn, &suffix_sdn->ndn );
+}
+
+int slapi_sdn_isparent( const Slapi_DN *parent, const Slapi_DN *child )
+{
+ Slapi_DN child_parent;
+
+ slapi_sdn_get_ndn( child );
+
+ slapi_sdn_init( &child_parent );
+ dnParent( (struct berval *)&child->ndn, &child_parent.ndn );
+
+ return ( slapi_sdn_compare( parent, &child_parent ) == 0 );
+}
+
+int slapi_sdn_isgrandparent( const Slapi_DN *parent, const Slapi_DN *child )
+{
+ Slapi_DN child_grandparent;
+
+ slapi_sdn_get_ndn( child );
+
+ slapi_sdn_init( &child_grandparent );
+ dnParent( (struct berval *)&child->ndn, &child_grandparent.ndn );
+ if ( child_grandparent.ndn.bv_len == 0 ) {
+ return 0;
+ }
+
+ dnParent( &child_grandparent.ndn, &child_grandparent.ndn );
+
+ return ( slapi_sdn_compare( parent, &child_grandparent ) == 0 );
+}
+
+int slapi_sdn_get_ndn_len( const Slapi_DN *sdn )
+{
+ slapi_sdn_get_ndn( sdn );
+
+ return sdn->ndn.bv_len;
+}
+
+int slapi_sdn_scope_test( const Slapi_DN *dn, const Slapi_DN *base, int scope )
+{
+ int rc;
+
+ switch ( scope ) {
+ case LDAP_SCOPE_BASE:
+ rc = ( slapi_sdn_compare( dn, base ) == 0 );
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ rc = slapi_sdn_isparent( base, dn );
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ rc = slapi_sdn_issuffix( dn, base );
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ return rc;
+}
+
+void slapi_rdn_init( Slapi_RDN *rdn )
+{
+ rdn->flag = 0;
+ BER_BVZERO( &rdn->bv );
+ rdn->rdn = NULL;
+}
+
+Slapi_RDN *slapi_rdn_new( void )
+{
+ Slapi_RDN *rdn;
+
+ rdn = (Slapi_RDN *)slapi_ch_malloc( sizeof(*rdn ));
+ slapi_rdn_init( rdn );
+
+ return rdn;
+}
+
+Slapi_RDN *slapi_rdn_new_dn( const char *dn )
+{
+ Slapi_RDN *rdn;
+
+ rdn = slapi_rdn_new();
+ slapi_rdn_init_dn( rdn, dn );
+ return rdn;
+}
+
+Slapi_RDN *slapi_rdn_new_sdn( const Slapi_DN *sdn )
+{
+ return slapi_rdn_new_dn( slapi_sdn_get_dn( sdn ) );
+}
+
+Slapi_RDN *slapi_rdn_new_rdn( const Slapi_RDN *fromrdn )
+{
+ return slapi_rdn_new_dn( fromrdn->bv.bv_val );
+}
+
+void slapi_rdn_init_dn( Slapi_RDN *rdn, const char *dn )
+{
+ slapi_rdn_init( rdn );
+ slapi_rdn_set_dn( rdn, dn );
+}
+
+void slapi_rdn_init_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn )
+{
+ slapi_rdn_init( rdn );
+ slapi_rdn_set_sdn( rdn, sdn );
+}
+
+void slapi_rdn_init_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn )
+{
+ slapi_rdn_init( rdn );
+ slapi_rdn_set_rdn( rdn, fromrdn );
+}
+
+void slapi_rdn_set_dn( Slapi_RDN *rdn, const char *dn )
+{
+ struct berval bv;
+
+ slapi_rdn_done( rdn );
+
+ BER_BVZERO( &bv );
+
+ if ( dn != NULL ) {
+ bv.bv_val = (char *)dn;
+ bv.bv_len = strlen( dn );
+ }
+
+ dnExtractRdn( &bv, &rdn->bv, NULL );
+ rdn->flag |= FLAG_DN;
+}
+
+void slapi_rdn_set_sdn( Slapi_RDN *rdn, const Slapi_DN *sdn )
+{
+ slapi_rdn_set_dn( rdn, slapi_sdn_get_dn( sdn ) );
+}
+
+void slapi_rdn_set_rdn( Slapi_RDN *rdn, const Slapi_RDN *fromrdn )
+{
+ slapi_rdn_set_dn( rdn, fromrdn->bv.bv_val );
+}
+
+void slapi_rdn_free( Slapi_RDN **rdn )
+{
+ slapi_rdn_done( *rdn );
+ slapi_ch_free( (void **)rdn );
+}
+
+void slapi_rdn_done( Slapi_RDN *rdn )
+{
+ if ( rdn->rdn != NULL ) {
+ ldap_rdnfree( rdn->rdn );
+ rdn->rdn = NULL;
+ }
+ slapi_ch_free_string( &rdn->bv.bv_val );
+ slapi_rdn_init( rdn );
+}
+
+const char *slapi_rdn_get_rdn( const Slapi_RDN *rdn )
+{
+ return rdn->bv.bv_val;
+}
+
+static int slapi_int_rdn_explode( Slapi_RDN *rdn )
+{
+ char *next;
+
+ if ( rdn->rdn != NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ return ldap_bv2rdn( &rdn->bv, &rdn->rdn, &next, LDAP_DN_FORMAT_LDAP );
+}
+
+static int slapi_int_rdn_implode( Slapi_RDN *rdn )
+{
+ struct berval bv;
+ int rc;
+
+ if ( rdn->rdn == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ rc = ldap_rdn2bv( rdn->rdn, &bv, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ slapi_ch_free_string( &rdn->bv.bv_val );
+ rdn->bv = bv;
+
+ return 0;
+}
+
+int slapi_rdn_get_num_components( Slapi_RDN *rdn )
+{
+ int i;
+
+ if ( slapi_int_rdn_explode( rdn ) != LDAP_SUCCESS )
+ return 0;
+
+ for ( i = 0; rdn->rdn[i] != NULL; i++ )
+ ;
+
+ return i;
+}
+
+int slapi_rdn_get_first( Slapi_RDN *rdn, char **type, char **value )
+{
+ return slapi_rdn_get_next( rdn, 0, type, value );
+}
+
+int slapi_rdn_get_next( Slapi_RDN *rdn, int index, char **type, char **value )
+{
+ slapi_int_rdn_explode( rdn );
+
+ if ( rdn->rdn == NULL || rdn->rdn[index] == NULL )
+ return -1;
+
+ *type = rdn->rdn[index]->la_attr.bv_val;
+ *value = rdn->rdn[index]->la_value.bv_val;
+
+ return index + 1;
+}
+
+int slapi_rdn_get_index( Slapi_RDN *rdn, const char *type, const char *value, size_t length )
+{
+ int i, match;
+ struct berval bv;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ slapi_int_rdn_explode( rdn );
+
+ if ( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ bv.bv_val = (char *)value;
+ bv.bv_len = length;
+
+ for ( i = 0; rdn->rdn[i] != NULL; i++ ) {
+ if ( !slapi_attr_types_equivalent( ad->ad_cname.bv_val, type ))
+ continue;
+
+ if ( value_match( &match, ad, ad->ad_type->sat_equality, 0,
+ &rdn->rdn[i]->la_value, (void *)&bv, &text ) != LDAP_SUCCESS )
+ match = -1;
+
+ if ( match == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+int slapi_rdn_get_index_attr( Slapi_RDN *rdn, const char *type, char **value )
+{
+ int i;
+
+ for ( i = 0; rdn->rdn[i] != NULL; i++ ) {
+ if ( slapi_attr_types_equivalent( rdn->rdn[i]->la_attr.bv_val, type ) ) {
+ *value = rdn->rdn[i]->la_value.bv_val;
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int slapi_rdn_contains( Slapi_RDN *rdn, const char *type, const char *value, size_t length )
+{
+ return ( slapi_rdn_get_index( rdn, type, value, length ) != -1 );
+}
+
+int slapi_rdn_contains_attr( Slapi_RDN *rdn, const char *type, char **value )
+{
+ return ( slapi_rdn_get_index_attr( rdn, type, value ) != -1 );
+}
+
+int slapi_rdn_compare( Slapi_RDN *rdn1, Slapi_RDN *rdn2 )
+{
+ struct berval nrdn1 = BER_BVNULL;
+ struct berval nrdn2 = BER_BVNULL;
+ int match;
+
+ rdnNormalize( 0, NULL, NULL, (struct berval *)&rdn1->bv, &nrdn1, NULL );
+ rdnNormalize( 0, NULL, NULL, (struct berval *)&rdn2->bv, &nrdn2, NULL );
+
+ if ( rdnMatch( &match, 0, NULL, NULL, &nrdn1, (void *)&nrdn2 ) != LDAP_SUCCESS) {
+ match = -1;
+ }
+
+ return match;
+}
+
+int slapi_rdn_isempty( const Slapi_RDN *rdn )
+{
+ return ( BER_BVISEMPTY( &rdn->bv ) );
+}
+
+int slapi_rdn_add( Slapi_RDN *rdn, const char *type, const char *value )
+{
+ char *s;
+ size_t len;
+
+ len = strlen(type) + 1 + strlen( value );
+ if ( !BER_BVISEMPTY( &rdn->bv ) ) {
+ len += 1 + rdn->bv.bv_len;
+ }
+
+ s = slapi_ch_malloc( len + 1 );
+
+ if ( BER_BVISEMPTY( &rdn->bv ) ) {
+ snprintf( s, len + 1, "%s=%s", type, value );
+ } else {
+ snprintf( s, len + 1, "%s=%s+%s", type, value, rdn->bv.bv_val );
+ }
+
+ slapi_rdn_done( rdn );
+
+ rdn->bv.bv_len = len;
+ rdn->bv.bv_val = s;
+
+ return 1;
+}
+
+int slapi_rdn_remove_index( Slapi_RDN *rdn, int atindex )
+{
+ int count, i;
+
+ count = slapi_rdn_get_num_components( rdn );
+
+ if ( atindex < 0 || atindex >= count )
+ return 0;
+
+ if ( rdn->rdn == NULL )
+ return 0;
+
+ slapi_ch_free_string( &rdn->rdn[atindex]->la_attr.bv_val );
+ slapi_ch_free_string( &rdn->rdn[atindex]->la_value.bv_val );
+
+ for ( i = atindex; i < count; i++ ) {
+ rdn->rdn[i] = rdn->rdn[i + 1];
+ }
+
+ if ( slapi_int_rdn_implode( rdn ) != LDAP_SUCCESS )
+ return 0;
+
+ return 1;
+}
+
+int slapi_rdn_remove( Slapi_RDN *rdn, const char *type, const char *value, size_t length )
+{
+ int index = slapi_rdn_get_index( rdn, type, value, length );
+
+ return slapi_rdn_remove_index( rdn, index );
+}
+
+int slapi_rdn_remove_attr( Slapi_RDN *rdn, const char *type )
+{
+ char *value;
+ int index = slapi_rdn_get_index_attr( rdn, type, &value );
+
+ return slapi_rdn_remove_index( rdn, index );
+}
+
+Slapi_DN *slapi_sdn_add_rdn( Slapi_DN *sdn, const Slapi_RDN *rdn )
+{
+ struct berval bv;
+
+ build_new_dn( &bv, &sdn->dn, (struct berval *)&rdn->bv, NULL );
+
+ slapi_sdn_done( sdn );
+ sdn->dn = bv;
+
+ return sdn;
+}
+
+Slapi_DN *slapi_sdn_set_parent( Slapi_DN *sdn, const Slapi_DN *parentdn )
+{
+ Slapi_RDN rdn;
+
+ slapi_rdn_init_sdn( &rdn, sdn );
+ slapi_sdn_set_dn_byref( sdn, slapi_sdn_get_dn( parentdn ) );
+ slapi_sdn_add_rdn( sdn, &rdn );
+ slapi_rdn_done( &rdn );
+
+ return sdn;
+}
+
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_ext.c b/servers/slapd/slapi/slapi_ext.c
new file mode 100644
index 0000000..81a50f1
--- /dev/null
+++ b/servers/slapd/slapi/slapi_ext.c
@@ -0,0 +1,349 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* (C) Copyright PADL Software Pty Ltd. 2003
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that this notice is preserved
+ * and that due credit is given to PADL Software Pty Ltd. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Luke Howard for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/stdarg.h>
+#include <ac/ctype.h>
+#include <ac/unistd.h>
+
+#ifdef LDAP_SLAPI
+
+#include <slap.h>
+#include <slapi.h>
+
+/*
+ * Object extensions
+ *
+ * We only support two types -- connection and operation extensions.
+ * Define more types in slapi.h
+ */
+
+/* global state */
+struct slapi_registered_extension_set {
+ ldap_pvt_thread_mutex_t mutex;
+ struct slapi_registered_extension {
+ int active;
+ int count;
+ slapi_extension_constructor_fnptr *constructors;
+ slapi_extension_destructor_fnptr *destructors;
+ } extensions[SLAPI_X_EXT_MAX];
+} registered_extensions;
+
+/* per-object state */
+struct slapi_extension_block {
+ void **extensions;
+};
+
+static int get_extension_block(int objecttype, void *object, struct slapi_extension_block **eblock, void **parent)
+{
+ switch ((slapi_extension_t) objecttype) {
+ case SLAPI_X_EXT_CONNECTION:
+ *eblock = ((Connection *)object)->c_extensions;
+ *parent = NULL;
+ break;
+ case SLAPI_X_EXT_OPERATION:
+ *eblock = ((Operation *)object)->o_hdr->oh_extensions;
+ *parent = ((Operation *)object)->o_conn;
+ break;
+ default:
+ return -1;
+ break;
+ }
+
+ if ( *eblock == NULL ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int map_extension_type(const char *objectname, slapi_extension_t *type)
+{
+ if ( strcasecmp( objectname, SLAPI_EXT_CONNECTION ) == 0 ) {
+ *type = SLAPI_X_EXT_CONNECTION;
+ } else if ( strcasecmp( objectname, SLAPI_EXT_OPERATION ) == 0 ) {
+ *type = SLAPI_X_EXT_OPERATION;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void new_extension(struct slapi_extension_block *eblock,
+ int objecttype, void *object, void *parent,
+ int extensionhandle )
+{
+ slapi_extension_constructor_fnptr constructor;
+
+ assert( objecttype < SLAPI_X_EXT_MAX );
+ assert( extensionhandle < registered_extensions.extensions[objecttype].count );
+
+ assert( registered_extensions.extensions[objecttype].constructors != NULL );
+ constructor = registered_extensions.extensions[objecttype].constructors[extensionhandle];
+
+ assert( eblock->extensions[extensionhandle] == NULL );
+
+ if ( constructor != NULL ) {
+ eblock->extensions[extensionhandle] = (*constructor)( object, parent );
+ } else {
+ eblock->extensions[extensionhandle] = NULL;
+ }
+}
+
+static void free_extension(struct slapi_extension_block *eblock, int objecttype, void *object, void *parent, int extensionhandle )
+{
+ slapi_extension_destructor_fnptr destructor;
+
+ assert( objecttype < SLAPI_X_EXT_MAX );
+ assert( extensionhandle < registered_extensions.extensions[objecttype].count );
+
+ if ( eblock->extensions[extensionhandle] != NULL ) {
+ assert( registered_extensions.extensions[objecttype].destructors != NULL );
+ destructor = registered_extensions.extensions[objecttype].destructors[extensionhandle];
+ if ( destructor != NULL ) {
+ (*destructor)( eblock->extensions[extensionhandle], object, parent );
+ }
+ eblock->extensions[extensionhandle] = NULL;
+ }
+}
+
+void *slapi_get_object_extension(int objecttype, void *object, int extensionhandle)
+{
+ struct slapi_extension_block *eblock;
+ void *parent;
+
+ if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) {
+ return NULL;
+ }
+
+ if ( extensionhandle < registered_extensions.extensions[objecttype].count ) {
+ return eblock->extensions[extensionhandle];
+ }
+
+ return NULL;
+}
+
+void slapi_set_object_extension(int objecttype, void *object, int extensionhandle, void *extension)
+{
+ struct slapi_extension_block *eblock;
+ void *parent;
+
+ if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) {
+ return;
+ }
+
+ if ( extensionhandle < registered_extensions.extensions[objecttype].count ) {
+ /* free the old one */
+ free_extension( eblock, objecttype, object, parent, extensionhandle );
+
+ /* constructed by caller */
+ eblock->extensions[extensionhandle] = extension;
+ }
+}
+
+int slapi_register_object_extension(
+ const char *pluginname,
+ const char *objectname,
+ slapi_extension_constructor_fnptr constructor,
+ slapi_extension_destructor_fnptr destructor,
+ int *objecttype,
+ int *extensionhandle)
+{
+ int rc;
+ slapi_extension_t type;
+ struct slapi_registered_extension *re;
+
+ ldap_pvt_thread_mutex_lock( &registered_extensions.mutex );
+
+ rc = map_extension_type( objectname, &type );
+ if ( rc != 0 ) {
+ ldap_pvt_thread_mutex_unlock( &registered_extensions.mutex );
+ return rc;
+ }
+
+ *objecttype = (int)type;
+
+ re = &registered_extensions.extensions[*objecttype];
+
+ *extensionhandle = re->count;
+
+ if ( re->active ) {
+ /* can't add new extensions after objects have been created */
+ ldap_pvt_thread_mutex_unlock( &registered_extensions.mutex );
+ return -1;
+ }
+
+ re->count++;
+
+ if ( re->constructors == NULL ) {
+ re->constructors = (slapi_extension_constructor_fnptr *)slapi_ch_calloc( re->count,
+ sizeof( slapi_extension_constructor_fnptr ) );
+ } else {
+ re->constructors = (slapi_extension_constructor_fnptr *)slapi_ch_realloc( (char *)re->constructors,
+ re->count * sizeof( slapi_extension_constructor_fnptr ) );
+ }
+ re->constructors[*extensionhandle] = constructor;
+
+ if ( re->destructors == NULL ) {
+ re->destructors = (slapi_extension_destructor_fnptr *)slapi_ch_calloc( re->count,
+ sizeof( slapi_extension_destructor_fnptr ) );
+ } else {
+ re->destructors = (slapi_extension_destructor_fnptr *)slapi_ch_realloc( (char *)re->destructors,
+ re->count * sizeof( slapi_extension_destructor_fnptr ) );
+ }
+ re->destructors[*extensionhandle] = destructor;
+
+ ldap_pvt_thread_mutex_unlock( &registered_extensions.mutex );
+
+ return 0;
+}
+
+int slapi_int_create_object_extensions(int objecttype, void *object)
+{
+ int i;
+ struct slapi_extension_block *eblock;
+ void **peblock;
+ void *parent;
+
+ switch ((slapi_extension_t) objecttype) {
+ case SLAPI_X_EXT_CONNECTION:
+ peblock = &(((Connection *)object)->c_extensions);
+ parent = NULL;
+ break;
+ case SLAPI_X_EXT_OPERATION:
+ peblock = &(((Operation *)object)->o_hdr->oh_extensions);
+ parent = ((Operation *)object)->o_conn;
+ break;
+ default:
+ return -1;
+ break;
+ }
+
+ *peblock = NULL;
+
+ ldap_pvt_thread_mutex_lock( &registered_extensions.mutex );
+ if ( registered_extensions.extensions[objecttype].active == 0 ) {
+ /*
+ * once we've created some extensions, no new extensions can
+ * be registered.
+ */
+ registered_extensions.extensions[objecttype].active = 1;
+ }
+ ldap_pvt_thread_mutex_unlock( &registered_extensions.mutex );
+
+ eblock = (struct slapi_extension_block *)slapi_ch_calloc( 1, sizeof(*eblock) );
+
+ if ( registered_extensions.extensions[objecttype].count ) {
+ eblock->extensions = (void **)slapi_ch_calloc( registered_extensions.extensions[objecttype].count, sizeof(void *) );
+ for ( i = 0; i < registered_extensions.extensions[objecttype].count; i++ ) {
+ new_extension( eblock, objecttype, object, parent, i );
+ }
+ } else {
+ eblock->extensions = NULL;
+ }
+
+ *peblock = eblock;
+
+ return 0;
+}
+
+int slapi_int_free_object_extensions(int objecttype, void *object)
+{
+ int i;
+ struct slapi_extension_block *eblock;
+ void **peblock;
+ void *parent;
+
+ switch ((slapi_extension_t) objecttype) {
+ case SLAPI_X_EXT_CONNECTION:
+ peblock = &(((Connection *)object)->c_extensions);
+ parent = NULL;
+ break;
+ case SLAPI_X_EXT_OPERATION:
+ peblock = &(((Operation *)object)->o_hdr->oh_extensions);
+ parent = ((Operation *)object)->o_conn;
+ break;
+ default:
+ return -1;
+ break;
+ }
+
+ eblock = (struct slapi_extension_block *)*peblock;
+
+ if ( eblock != NULL && eblock->extensions != NULL ) {
+ for ( i = registered_extensions.extensions[objecttype].count - 1; i >= 0; --i ) {
+ free_extension( eblock, objecttype, object, parent, i );
+ }
+
+ slapi_ch_free( (void **)&eblock->extensions );
+ }
+
+ slapi_ch_free( peblock );
+
+ return 0;
+}
+
+/* for reusable object types */
+int slapi_int_clear_object_extensions(int objecttype, void *object)
+{
+ int i;
+ struct slapi_extension_block *eblock;
+ void *parent;
+
+ if ( get_extension_block( objecttype, object, &eblock, &parent ) != 0 ) {
+ return -1;
+ }
+
+ if ( eblock->extensions == NULL ) {
+ /* no extensions */
+ return 0;
+ }
+
+ for ( i = registered_extensions.extensions[objecttype].count - 1; i >= 0; --i ) {
+ free_extension( eblock, objecttype, object, parent, i );
+ }
+
+ for ( i = 0; i < registered_extensions.extensions[objecttype].count; i++ ) {
+ new_extension( eblock, objecttype, object, parent, i );
+ }
+
+ return 0;
+}
+
+int slapi_int_init_object_extensions(void)
+{
+ memset( &registered_extensions, 0, sizeof( registered_extensions ) );
+
+ if ( ldap_pvt_thread_mutex_init( &registered_extensions.mutex ) != 0 ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_ops.c b/servers/slapd/slapi/slapi_ops.c
new file mode 100644
index 0000000..afec4bd
--- /dev/null
+++ b/servers/slapd/slapi/slapi_ops.c
@@ -0,0 +1,954 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/stdarg.h>
+#include <ac/ctype.h>
+#include <ac/unistd.h>
+
+#include <slap.h>
+#include <lber_pvt.h>
+#include <slapi.h>
+
+#ifdef LDAP_SLAPI
+
+static struct Listener slapi_listener = {
+ BER_BVC("slapi://"),
+ BER_BVC("slapi://")
+};
+
+static LDAPControl **
+slapi_int_dup_controls( LDAPControl **controls )
+{
+ LDAPControl **c;
+ size_t i;
+
+ if ( controls == NULL )
+ return NULL;
+
+ for ( i = 0; controls[i] != NULL; i++ )
+ ;
+
+ c = (LDAPControl **) slapi_ch_calloc( i + 1, sizeof(LDAPControl *) );
+
+ for ( i = 0; controls[i] != NULL; i++ ) {
+ c[i] = slapi_dup_control( controls[i] );
+ }
+
+ return c;
+}
+
+static int
+slapi_int_result(
+ Operation *op,
+ SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ plugin_result_callback prc = NULL;
+ void *callback_data = NULL;
+ LDAPControl **ctrls = NULL;
+
+ assert( pb != NULL );
+
+ slapi_pblock_get( pb, SLAPI_X_INTOP_RESULT_CALLBACK, (void **)&prc );
+ slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data );
+
+ /* we need to duplicate controls because they might go out of scope */
+ ctrls = slapi_int_dup_controls( rs->sr_ctrls );
+ slapi_pblock_set( pb, SLAPI_RESCONTROLS, ctrls );
+
+ if ( prc != NULL ) {
+ (*prc)( rs->sr_err, callback_data );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+slapi_int_search_entry(
+ Operation *op,
+ SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ plugin_search_entry_callback psec = NULL;
+ void *callback_data = NULL;
+ int rc = LDAP_SUCCESS;
+
+ assert( pb != NULL );
+
+ slapi_pblock_get( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK, (void **)&psec );
+ slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data );
+
+ if ( psec != NULL ) {
+ rc = (*psec)( rs->sr_entry, callback_data );
+ }
+
+ return rc;
+}
+
+static int
+slapi_int_search_reference(
+ Operation *op,
+ SlapReply *rs )
+{
+ int i, rc = LDAP_SUCCESS;
+ plugin_referral_entry_callback prec = NULL;
+ void *callback_data = NULL;
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+
+ assert( pb != NULL );
+
+ slapi_pblock_get( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK, (void **)&prec );
+ slapi_pblock_get( pb, SLAPI_X_INTOP_CALLBACK_DATA, &callback_data );
+
+ if ( prec != NULL ) {
+ for ( i = 0; rs->sr_ref[i].bv_val != NULL; i++ ) {
+ rc = (*prec)( rs->sr_ref[i].bv_val, callback_data );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ }
+ }
+
+ return rc;
+}
+
+int
+slapi_int_response( Slapi_Operation *op, SlapReply *rs )
+{
+ int rc;
+
+ switch ( rs->sr_type ) {
+ case REP_RESULT:
+ rc = slapi_int_result( op, rs );
+ break;
+ case REP_SEARCH:
+ rc = slapi_int_search_entry( op, rs );
+ break;
+ case REP_SEARCHREF:
+ rc = slapi_int_search_reference( op, rs );
+ break;
+ default:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ assert( rc != SLAP_CB_CONTINUE ); /* never try to send a wire response */
+
+ return rc;
+}
+
+static int
+slapi_int_get_ctrls( Slapi_PBlock *pb )
+{
+ LDAPControl **c;
+ int rc = LDAP_SUCCESS;
+
+ if ( pb->pb_op->o_ctrls != NULL ) {
+ for ( c = pb->pb_op->o_ctrls; *c != NULL; c++ ) {
+ rc = slap_parse_ctrl( pb->pb_op, pb->pb_rs, *c, &pb->pb_rs->sr_text );
+ if ( rc != LDAP_SUCCESS )
+ break;
+ }
+ }
+
+ return rc;
+}
+
+void
+slapi_int_connection_init_pb( Slapi_PBlock *pb, ber_tag_t tag )
+{
+ Connection *conn;
+ Operation *op;
+ ber_len_t max = sockbuf_max_incoming;
+
+ conn = (Connection *) slapi_ch_calloc( 1, sizeof(Connection) );
+
+ LDAP_STAILQ_INIT( &conn->c_pending_ops );
+
+ op = (Operation *) slapi_ch_calloc( 1, sizeof(OperationBuffer) );
+ op->o_hdr = &((OperationBuffer *) op)->ob_hdr;
+ op->o_controls = ((OperationBuffer *) op)->ob_controls;
+
+ op->o_callback = (slap_callback *) slapi_ch_calloc( 1, sizeof(slap_callback) );
+ op->o_callback->sc_response = slapi_int_response;
+ op->o_callback->sc_cleanup = NULL;
+ op->o_callback->sc_private = pb;
+ op->o_callback->sc_next = NULL;
+
+ conn->c_pending_ops.stqh_first = op;
+
+ /* connection object authorization information */
+ conn->c_authtype = LDAP_AUTH_NONE;
+ BER_BVZERO( &conn->c_authmech );
+ BER_BVZERO( &conn->c_dn );
+ BER_BVZERO( &conn->c_ndn );
+
+ conn->c_listener = &slapi_listener;
+ ber_dupbv( &conn->c_peer_domain, (struct berval *)&slap_unknown_bv );
+ ber_dupbv( &conn->c_peer_name, (struct berval *)&slap_unknown_bv );
+
+ LDAP_STAILQ_INIT( &conn->c_ops );
+
+ BER_BVZERO( &conn->c_sasl_bind_mech );
+ conn->c_sasl_authctx = NULL;
+ conn->c_sasl_sockctx = NULL;
+ conn->c_sasl_extra = NULL;
+
+ conn->c_sb = ber_sockbuf_alloc();
+
+ ber_sockbuf_ctrl( conn->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+
+ conn->c_currentber = NULL;
+
+ /* should check status of thread calls */
+ ldap_pvt_thread_mutex_init( &conn->c_mutex );
+ ldap_pvt_thread_mutex_init( &conn->c_write1_mutex );
+ ldap_pvt_thread_cond_init( &conn->c_write1_cv );
+
+ ldap_pvt_thread_mutex_lock( &conn->c_mutex );
+
+ conn->c_n_ops_received = 0;
+ conn->c_n_ops_executing = 0;
+ conn->c_n_ops_pending = 0;
+ conn->c_n_ops_completed = 0;
+ conn->c_n_ops_async = 0;
+
+ conn->c_n_get = 0;
+ conn->c_n_read = 0;
+ conn->c_n_write = 0;
+
+ conn->c_protocol = LDAP_VERSION3;
+
+ conn->c_activitytime = conn->c_starttime = slap_get_time();
+
+ /*
+ * A real connection ID is required, because syncrepl associates
+ * pending CSNs with unique ( connection, operation ) tuples.
+ * Setting a fake connection ID will cause slap_get_commit_csn()
+ * to return a stale value.
+ */
+ connection_assign_nextid( conn );
+
+ conn->c_conn_state = SLAP_C_ACTIVE;
+
+ conn->c_ssf = conn->c_transport_ssf = local_ssf;
+ conn->c_tls_ssf = 0;
+
+ backend_connection_init( conn );
+
+ conn->c_send_ldap_result = slap_send_ldap_result;
+ conn->c_send_search_entry = slap_send_search_entry;
+ conn->c_send_ldap_extended = slap_send_ldap_extended;
+ conn->c_send_search_reference = slap_send_search_reference;
+
+ /* operation object */
+ op->o_tag = tag;
+ op->o_protocol = LDAP_VERSION3;
+ BER_BVZERO( &op->o_authmech );
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+ op->o_threadctx = ldap_pvt_thread_pool_context();
+ op->o_tmpmemctx = NULL;
+ op->o_tmpmfuncs = &ch_mfuncs;
+ op->o_conn = conn;
+ op->o_connid = conn->c_connid;
+ op->o_bd = frontendDB;
+
+ /* extensions */
+ slapi_int_create_object_extensions( SLAPI_X_EXT_OPERATION, op );
+ slapi_int_create_object_extensions( SLAPI_X_EXT_CONNECTION, conn );
+
+ pb->pb_rs = (SlapReply *)slapi_ch_calloc( 1, sizeof(SlapReply) );
+ pb->pb_op = op;
+ pb->pb_conn = conn;
+ pb->pb_intop = 1;
+
+ ldap_pvt_thread_mutex_unlock( &conn->c_mutex );
+}
+
+static void
+slapi_int_set_operation_dn( Slapi_PBlock *pb )
+{
+ Backend *be;
+ Operation *op = pb->pb_op;
+
+ if ( BER_BVISNULL( &op->o_ndn ) ) {
+ /* set to root DN */
+ be = select_backend( &op->o_req_ndn, 1 );
+ if ( be != NULL ) {
+ ber_dupbv( &op->o_dn, &be->be_rootdn );
+ ber_dupbv( &op->o_ndn, &be->be_rootndn );
+ }
+ }
+}
+
+void
+slapi_int_connection_done_pb( Slapi_PBlock *pb )
+{
+ Connection *conn;
+ Operation *op;
+
+ PBLOCK_ASSERT_INTOP( pb, 0 );
+
+ conn = pb->pb_conn;
+ op = pb->pb_op;
+
+ /* free allocated DNs */
+ if ( !BER_BVISNULL( &op->o_dn ) )
+ op->o_tmpfree( op->o_dn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &op->o_ndn ) )
+ op->o_tmpfree( op->o_ndn.bv_val, op->o_tmpmemctx );
+
+ if ( !BER_BVISNULL( &op->o_req_dn ) )
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &op->o_req_ndn ) )
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_MODRDN:
+ if ( !BER_BVISNULL( &op->orr_newrdn ))
+ op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &op->orr_nnewrdn ))
+ op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx );
+ if ( op->orr_newSup != NULL ) {
+ assert( !BER_BVISNULL( op->orr_newSup ) );
+ op->o_tmpfree( op->orr_newSup->bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx );
+ }
+ if ( op->orr_nnewSup != NULL ) {
+ assert( !BER_BVISNULL( op->orr_nnewSup ) );
+ op->o_tmpfree( op->orr_nnewSup->bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &op->orr_newDN ))
+ op->o_tmpfree( op->orr_newDN.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &op->orr_nnewDN ))
+ op->o_tmpfree( op->orr_nnewDN.bv_val, op->o_tmpmemctx );
+ slap_mods_free( op->orr_modlist, 1 );
+ break;
+ case LDAP_REQ_ADD:
+ slap_mods_free( op->ora_modlist, 0 );
+ break;
+ case LDAP_REQ_MODIFY:
+ slap_mods_free( op->orm_modlist, 1 );
+ break;
+ case LDAP_REQ_SEARCH:
+ if ( op->ors_attrs != NULL ) {
+ op->o_tmpfree( op->ors_attrs, op->o_tmpmemctx );
+ op->ors_attrs = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ slapi_ch_free_string( &conn->c_authmech.bv_val );
+ slapi_ch_free_string( &conn->c_dn.bv_val );
+ slapi_ch_free_string( &conn->c_ndn.bv_val );
+ slapi_ch_free_string( &conn->c_peer_domain.bv_val );
+ slapi_ch_free_string( &conn->c_peer_name.bv_val );
+
+ if ( conn->c_sb != NULL ) {
+ ber_sockbuf_free( conn->c_sb );
+ }
+
+ slapi_int_free_object_extensions( SLAPI_X_EXT_OPERATION, op );
+ slapi_int_free_object_extensions( SLAPI_X_EXT_CONNECTION, conn );
+
+ slapi_ch_free( (void **)&pb->pb_op->o_callback );
+ slapi_ch_free( (void **)&pb->pb_op );
+ slapi_ch_free( (void **)&pb->pb_conn );
+ slapi_ch_free( (void **)&pb->pb_rs );
+}
+
+static int
+slapi_int_func_internal_pb( Slapi_PBlock *pb, slap_operation_t which )
+{
+ SlapReply *rs = pb->pb_rs;
+ int rc;
+
+ PBLOCK_ASSERT_INTOP( pb, 0 );
+
+ rc = slapi_int_get_ctrls( pb );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ return rc;
+ }
+
+ pb->pb_op->o_bd = frontendDB;
+ return (&frontendDB->be_bind)[which]( pb->pb_op, pb->pb_rs );
+}
+
+int
+slapi_delete_internal_pb( Slapi_PBlock *pb )
+{
+ if ( pb == NULL ) {
+ return -1;
+ }
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_DELETE );
+
+ slapi_int_func_internal_pb( pb, op_delete );
+
+ return 0;
+}
+
+int
+slapi_add_internal_pb( Slapi_PBlock *pb )
+{
+ SlapReply *rs;
+ Slapi_Entry *entry_orig = NULL;
+ OpExtraDB oex;
+ int rc;
+
+ if ( pb == NULL ) {
+ return -1;
+ }
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_ADD );
+
+ rs = pb->pb_rs;
+
+ entry_orig = pb->pb_op->ora_e;
+ pb->pb_op->ora_e = NULL;
+
+ /*
+ * The caller can specify a new entry, or a target DN and set
+ * of modifications, but not both.
+ */
+ if ( entry_orig != NULL ) {
+ if ( pb->pb_op->ora_modlist != NULL || !BER_BVISNULL( &pb->pb_op->o_req_ndn )) {
+ rs->sr_err = LDAP_PARAM_ERROR;
+ goto cleanup;
+ }
+
+ assert( BER_BVISNULL( &pb->pb_op->o_req_dn ) ); /* shouldn't get set */
+ ber_dupbv( &pb->pb_op->o_req_dn, &entry_orig->e_name );
+ ber_dupbv( &pb->pb_op->o_req_ndn, &entry_orig->e_nname );
+ } else if ( pb->pb_op->ora_modlist == NULL || BER_BVISNULL( &pb->pb_op->o_req_ndn )) {
+ rs->sr_err = LDAP_PARAM_ERROR;
+ goto cleanup;
+ }
+
+ pb->pb_op->ora_e = (Entry *)slapi_ch_calloc( 1, sizeof(Entry) );
+ ber_dupbv( &pb->pb_op->ora_e->e_name, &pb->pb_op->o_req_dn );
+ ber_dupbv( &pb->pb_op->ora_e->e_nname, &pb->pb_op->o_req_ndn );
+
+ if ( entry_orig != NULL ) {
+ assert( pb->pb_op->ora_modlist == NULL );
+
+ rs->sr_err = slap_entry2mods( entry_orig, &pb->pb_op->ora_modlist,
+ &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+ } else {
+ assert( pb->pb_op->ora_modlist != NULL );
+ }
+
+ rs->sr_err = slap_mods_check( pb->pb_op, pb->pb_op->ora_modlist, &rs->sr_text,
+ pb->pb_textbuf, sizeof( pb->pb_textbuf ), NULL );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ /* Duplicate the values, because we may call slapi_entry_free() */
+ rs->sr_err = slap_mods2entry( pb->pb_op->ora_modlist, &pb->pb_op->ora_e,
+ 1, 0, &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ oex.oe.oe_key = (void *)do_add;
+ oex.oe_db = NULL;
+ LDAP_SLIST_INSERT_HEAD(&pb->pb_op->o_extra, &oex.oe, oe_next);
+ rc = slapi_int_func_internal_pb( pb, op_add );
+ LDAP_SLIST_REMOVE(&pb->pb_op->o_extra, &oex.oe, OpExtra, oe_next);
+
+ if ( !rc ) {
+ if ( pb->pb_op->ora_e != NULL && oex.oe_db != NULL ) {
+ BackendDB *bd = pb->pb_op->o_bd;
+
+ pb->pb_op->o_bd = oex.oe_db;
+ be_entry_release_w( pb->pb_op, pb->pb_op->ora_e );
+ pb->pb_op->ora_e = NULL;
+ pb->pb_op->o_bd = bd;
+ }
+ }
+
+cleanup:
+
+ if ( pb->pb_op->ora_e != NULL ) {
+ slapi_entry_free( pb->pb_op->ora_e );
+ pb->pb_op->ora_e = NULL;
+ }
+ if ( entry_orig != NULL ) {
+ pb->pb_op->ora_e = entry_orig;
+ slap_mods_free( pb->pb_op->ora_modlist, 1 );
+ pb->pb_op->ora_modlist = NULL;
+ }
+
+ return 0;
+}
+
+int
+slapi_modrdn_internal_pb( Slapi_PBlock *pb )
+{
+ if ( pb == NULL ) {
+ return -1;
+ }
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_MODRDN );
+
+ if ( BER_BVISEMPTY( &pb->pb_op->o_req_ndn ) ) {
+ pb->pb_rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto cleanup;
+ }
+
+ slapi_int_func_internal_pb( pb, op_modrdn );
+
+cleanup:
+
+ return 0;
+}
+
+int
+slapi_modify_internal_pb( Slapi_PBlock *pb )
+{
+ SlapReply *rs;
+
+ if ( pb == NULL ) {
+ return -1;
+ }
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_MODIFY );
+
+ rs = pb->pb_rs;
+
+ if ( pb->pb_op->orm_modlist == NULL ) {
+ rs->sr_err = LDAP_PARAM_ERROR;
+ goto cleanup;
+ }
+
+ if ( BER_BVISEMPTY( &pb->pb_op->o_req_ndn ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto cleanup;
+ }
+
+ rs->sr_err = slap_mods_check( pb->pb_op, pb->pb_op->orm_modlist,
+ &rs->sr_text, pb->pb_textbuf, sizeof( pb->pb_textbuf ), NULL );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto cleanup;
+ }
+
+ slapi_int_func_internal_pb( pb, op_modify );
+
+cleanup:
+
+ return 0;
+}
+
+static int
+slapi_int_search_entry_callback( Slapi_Entry *entry, void *callback_data )
+{
+ int nentries = 0, i = 0;
+ Slapi_Entry **head = NULL, **tp;
+ Slapi_PBlock *pb = (Slapi_PBlock *)callback_data;
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_SEARCH );
+
+ entry = slapi_entry_dup( entry );
+ if ( entry == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ slapi_pblock_get( pb, SLAPI_NENTRIES, &nentries );
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &head );
+
+ i = nentries + 1;
+ if ( nentries == 0 ) {
+ tp = (Slapi_Entry **)slapi_ch_malloc( 2 * sizeof(Slapi_Entry *) );
+ if ( tp == NULL ) {
+ slapi_entry_free( entry );
+ return LDAP_NO_MEMORY;
+ }
+
+ tp[0] = entry;
+ } else {
+ tp = (Slapi_Entry **)slapi_ch_realloc( (char *)head,
+ sizeof(Slapi_Entry *) * ( i + 1 ) );
+ if ( tp == NULL ) {
+ slapi_entry_free( entry );
+ return LDAP_NO_MEMORY;
+ }
+ tp[i - 1] = entry;
+ }
+ tp[i] = NULL;
+
+ slapi_pblock_set( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, (void *)tp );
+ slapi_pblock_set( pb, SLAPI_NENTRIES, (void *)&i );
+
+ return LDAP_SUCCESS;
+}
+
+int
+slapi_search_internal_pb( Slapi_PBlock *pb )
+{
+ return slapi_search_internal_callback_pb( pb,
+ (void *)pb,
+ NULL,
+ slapi_int_search_entry_callback,
+ NULL );
+}
+
+int
+slapi_search_internal_callback_pb( Slapi_PBlock *pb,
+ void *callback_data,
+ plugin_result_callback prc,
+ plugin_search_entry_callback psec,
+ plugin_referral_entry_callback prec )
+{
+ int free_filter = 0;
+ SlapReply *rs;
+
+ if ( pb == NULL ) {
+ return -1;
+ }
+
+ PBLOCK_ASSERT_INTOP( pb, LDAP_REQ_SEARCH );
+
+ rs = pb->pb_rs;
+
+ /* search callback and arguments */
+ slapi_pblock_set( pb, SLAPI_X_INTOP_RESULT_CALLBACK, (void *)prc );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK, (void *)psec );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK, (void *)prec );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_CALLBACK_DATA, (void *)callback_data );
+
+ if ( BER_BVISEMPTY( &pb->pb_op->ors_filterstr )) {
+ rs->sr_err = LDAP_PARAM_ERROR;
+ goto cleanup;
+ }
+
+ if ( pb->pb_op->ors_filter == NULL ) {
+ pb->pb_op->ors_filter = slapi_str2filter( pb->pb_op->ors_filterstr.bv_val );
+ if ( pb->pb_op->ors_filter == NULL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto cleanup;
+ }
+
+ free_filter = 1;
+ }
+
+ slapi_int_func_internal_pb( pb, op_search );
+
+cleanup:
+ if ( free_filter ) {
+ slapi_filter_free( pb->pb_op->ors_filter, 1 );
+ pb->pb_op->ors_filter = NULL;
+ }
+
+ slapi_pblock_delete_param( pb, SLAPI_X_INTOP_RESULT_CALLBACK );
+ slapi_pblock_delete_param( pb, SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK );
+ slapi_pblock_delete_param( pb, SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK );
+ slapi_pblock_delete_param( pb, SLAPI_X_INTOP_CALLBACK_DATA );
+
+ return 0;
+}
+
+/* Wrappers for old API */
+
+void
+slapi_search_internal_set_pb( Slapi_PBlock *pb,
+ const char *base,
+ int scope,
+ const char *filter,
+ char **attrs,
+ int attrsonly,
+ LDAPControl **controls,
+ const char *uniqueid,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ int no_limit = SLAP_NO_LIMIT;
+ int deref = LDAP_DEREF_NEVER;
+
+ slapi_int_connection_init_pb( pb, LDAP_REQ_SEARCH );
+ slapi_pblock_set( pb, SLAPI_SEARCH_TARGET, (void *)base );
+ slapi_pblock_set( pb, SLAPI_SEARCH_SCOPE, (void *)&scope );
+ slapi_pblock_set( pb, SLAPI_SEARCH_FILTER, (void *)0 );
+ slapi_pblock_set( pb, SLAPI_SEARCH_STRFILTER, (void *)filter );
+ slapi_pblock_set( pb, SLAPI_SEARCH_ATTRS, (void *)attrs );
+ slapi_pblock_set( pb, SLAPI_SEARCH_ATTRSONLY, (void *)&attrsonly );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slapi_pblock_set( pb, SLAPI_SEARCH_DEREF, (void *)&deref );
+ slapi_pblock_set( pb, SLAPI_SEARCH_SIZELIMIT, (void *)&no_limit );
+ slapi_pblock_set( pb, SLAPI_SEARCH_TIMELIMIT, (void *)&no_limit );
+
+ slapi_int_set_operation_dn( pb );
+}
+
+Slapi_PBlock *
+slapi_search_internal(
+ char *ldn,
+ int scope,
+ char *filStr,
+ LDAPControl **controls,
+ char **attrs,
+ int attrsonly )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+
+ slapi_search_internal_set_pb( pb, ldn, scope, filStr,
+ attrs, attrsonly,
+ controls, NULL, NULL, 0 );
+
+ slapi_search_internal_pb( pb );
+
+ return pb;
+}
+
+void
+slapi_modify_internal_set_pb( Slapi_PBlock *pb,
+ const char *dn,
+ LDAPMod **mods,
+ LDAPControl **controls,
+ const char *uniqueid,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ slapi_int_connection_init_pb( pb, LDAP_REQ_MODIFY );
+ slapi_pblock_set( pb, SLAPI_MODIFY_TARGET, (void *)dn );
+ slapi_pblock_set( pb, SLAPI_MODIFY_MODS, (void *)mods );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slapi_int_set_operation_dn( pb );
+}
+
+/* Function : slapi_modify_internal
+ *
+ * Description: Plugin functions call this routine to modify an entry
+ * in the backend directly
+ * Return values : LDAP_SUCCESS
+ * LDAP_PARAM_ERROR
+ * LDAP_NO_MEMORY
+ * LDAP_OTHER
+ * LDAP_UNWILLING_TO_PERFORM
+*/
+Slapi_PBlock *
+slapi_modify_internal(
+ char *ldn,
+ LDAPMod **mods,
+ LDAPControl **controls,
+ int log_change )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+
+ slapi_modify_internal_set_pb( pb, ldn, mods, controls, NULL, NULL, 0 );
+ slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change );
+ slapi_modify_internal_pb( pb );
+
+ return pb;
+}
+
+int
+slapi_add_internal_set_pb( Slapi_PBlock *pb,
+ const char *dn,
+ LDAPMod **attrs,
+ LDAPControl **controls,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ slapi_int_connection_init_pb( pb, LDAP_REQ_ADD );
+ slapi_pblock_set( pb, SLAPI_ADD_TARGET, (void *)dn );
+ slapi_pblock_set( pb, SLAPI_MODIFY_MODS, (void *)attrs );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slapi_int_set_operation_dn( pb );
+
+ return 0;
+}
+
+Slapi_PBlock *
+slapi_add_internal(
+ char * dn,
+ LDAPMod **attrs,
+ LDAPControl **controls,
+ int log_change )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+
+ slapi_add_internal_set_pb( pb, dn, attrs, controls, NULL, 0);
+ slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change );
+ slapi_add_internal_pb( pb );
+
+ return pb;
+}
+
+void
+slapi_add_entry_internal_set_pb( Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ LDAPControl **controls,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ slapi_int_connection_init_pb( pb, LDAP_REQ_ADD );
+ slapi_pblock_set( pb, SLAPI_ADD_ENTRY, (void *)e );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slapi_int_set_operation_dn( pb );
+}
+
+Slapi_PBlock *
+slapi_add_entry_internal(
+ Slapi_Entry *e,
+ LDAPControl **controls,
+ int log_change )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+
+ slapi_add_entry_internal_set_pb( pb, e, controls, NULL, 0 );
+ slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change );
+ slapi_add_internal_pb( pb );
+
+ return pb;
+}
+
+void
+slapi_rename_internal_set_pb( Slapi_PBlock *pb,
+ const char *olddn,
+ const char *newrdn,
+ const char *newsuperior,
+ int deloldrdn,
+ LDAPControl **controls,
+ const char *uniqueid,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ slapi_int_connection_init_pb( pb, LDAP_REQ_MODRDN );
+ slapi_pblock_set( pb, SLAPI_MODRDN_TARGET, (void *)olddn );
+ slapi_pblock_set( pb, SLAPI_MODRDN_NEWRDN, (void *)newrdn );
+ slapi_pblock_set( pb, SLAPI_MODRDN_NEWSUPERIOR, (void *)newsuperior );
+ slapi_pblock_set( pb, SLAPI_MODRDN_DELOLDRDN, (void *)&deloldrdn );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slap_modrdn2mods( pb->pb_op, pb->pb_rs );
+ slapi_int_set_operation_dn( pb );
+}
+
+/* Function : slapi_modrdn_internal
+ *
+ * Description : Plugin functions call this routine to modify the rdn
+ * of an entry in the backend directly
+ * Return values : LDAP_SUCCESS
+ * LDAP_PARAM_ERROR
+ * LDAP_NO_MEMORY
+ * LDAP_OTHER
+ * LDAP_UNWILLING_TO_PERFORM
+ *
+ * NOTE: This function does not support the "newSuperior" option from LDAP V3.
+ */
+Slapi_PBlock *
+slapi_modrdn_internal(
+ char *olddn,
+ char *lnewrdn,
+ int deloldrdn,
+ LDAPControl **controls,
+ int log_change )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new ();
+
+ slapi_rename_internal_set_pb( pb, olddn, lnewrdn, NULL,
+ deloldrdn, controls, NULL, NULL, 0 );
+ slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change );
+ slapi_modrdn_internal_pb( pb );
+
+ return pb;
+}
+
+void
+slapi_delete_internal_set_pb( Slapi_PBlock *pb,
+ const char *dn,
+ LDAPControl **controls,
+ const char *uniqueid,
+ Slapi_ComponentId *plugin_identity,
+ int operation_flags )
+{
+ slapi_int_connection_init_pb( pb, LDAP_REQ_DELETE );
+ slapi_pblock_set( pb, SLAPI_TARGET_DN, (void *)dn );
+ slapi_pblock_set( pb, SLAPI_REQCONTROLS, (void *)controls );
+ slapi_pblock_set( pb, SLAPI_TARGET_UNIQUEID, (void *)uniqueid );
+ slapi_pblock_set( pb, SLAPI_PLUGIN_IDENTITY, (void *)plugin_identity );
+ slapi_pblock_set( pb, SLAPI_X_INTOP_FLAGS, (void *)&operation_flags );
+ slapi_int_set_operation_dn( pb );
+}
+
+/* Function : slapi_delete_internal
+ *
+ * Description : Plugin functions call this routine to delete an entry
+ * in the backend directly
+ * Return values : LDAP_SUCCESS
+ * LDAP_PARAM_ERROR
+ * LDAP_NO_MEMORY
+ * LDAP_OTHER
+ * LDAP_UNWILLING_TO_PERFORM
+*/
+Slapi_PBlock *
+slapi_delete_internal(
+ char *ldn,
+ LDAPControl **controls,
+ int log_change )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+
+ slapi_delete_internal_set_pb( pb, ldn, controls, NULL, NULL, 0 );
+ slapi_pblock_set( pb, SLAPI_LOG_OPERATION, (void *)&log_change );
+ slapi_delete_internal_pb( pb );
+
+ return pb;
+}
+
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_overlay.c b/servers/slapd/slapi/slapi_overlay.c
new file mode 100644
index 0000000..b01793b
--- /dev/null
+++ b/servers/slapd/slapi/slapi_overlay.c
@@ -0,0 +1,952 @@
+/* slapi_overlay.c - SLAPI overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2001-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Luke Howard for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slapi.h"
+#include "slap-config.h"
+
+#ifdef LDAP_SLAPI
+
+static slap_overinst slapi;
+static int slapi_over_initialized = 0;
+
+static int slapi_over_response( Operation *op, SlapReply *rs );
+static int slapi_over_cleanup( Operation *op, SlapReply *rs );
+
+static Slapi_PBlock *
+slapi_over_pblock_new( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb;
+
+ pb = slapi_pblock_new();
+ pb->pb_op = op;
+ pb->pb_conn = op->o_conn;
+ pb->pb_rs = rs;
+ pb->pb_intop = 0;
+
+ PBLOCK_ASSERT_OP( pb, op->o_tag );
+
+ return pb;
+}
+
+static int
+slapi_op_internal_p( Operation *op, SlapReply *rs, slap_callback *cb )
+{
+ int internal_op = 0;
+ Slapi_PBlock *pb = NULL;
+ slap_callback *pcb;
+
+ /*
+ * Abstraction violating check for SLAPI internal operations
+ * allows pblock to remain consistent when invoking internal
+ * op plugins
+ */
+ for ( pcb = op->o_callback; pcb != NULL; pcb = pcb->sc_next ) {
+ if ( pcb->sc_response == slapi_int_response ) {
+ pb = (Slapi_PBlock *)pcb->sc_private;
+ PBLOCK_ASSERT_INTOP( pb, 0 );
+ internal_op = 1;
+ break;
+ }
+ }
+
+ if ( cb != NULL ) {
+ if ( pb == NULL ) {
+ pb = slapi_over_pblock_new( op, rs );
+ }
+
+ cb->sc_response = slapi_over_response;
+ cb->sc_cleanup = slapi_over_cleanup;
+ cb->sc_private = pb;
+ cb->sc_writewait = 0;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+
+ return internal_op;
+}
+
+static int
+slapi_over_compute_output(
+ computed_attr_context *c,
+ Slapi_Attr *attribute,
+ Slapi_Entry *entry
+)
+{
+ Attribute **a;
+ AttributeDescription *desc;
+ SlapReply *rs;
+
+ if ( c == NULL || attribute == NULL || entry == NULL ) {
+ return 0;
+ }
+
+ rs = (SlapReply *)c->cac_private;
+
+ assert( rs->sr_entry == entry );
+
+ desc = attribute->a_desc;
+
+ if ( rs->sr_attrs == NULL ) {
+ /* All attrs request, skip operational attributes */
+ if ( is_at_operational( desc->ad_type ) ) {
+ return 0;
+ }
+ } else {
+ /* Specific attributes requested */
+ if ( is_at_operational( desc->ad_type ) ) {
+ if ( !SLAP_OPATTRS( rs->sr_attr_flags ) &&
+ !ad_inlist( desc, rs->sr_attrs ) ) {
+ return 0;
+ }
+ } else {
+ if ( !SLAP_USERATTRS( rs->sr_attr_flags ) &&
+ !ad_inlist( desc, rs->sr_attrs ) ) {
+ return 0;
+ }
+ }
+ }
+
+ /* XXX perhaps we should check for existing attributes and merge */
+ for ( a = &rs->sr_operational_attrs; *a != NULL; a = &(*a)->a_next )
+ ;
+
+ *a = slapi_attr_dup( attribute );
+
+ return 0;
+}
+
+static int
+slapi_over_aux_operational( Operation *op, SlapReply *rs )
+{
+ /* Support for computed attribute plugins */
+ computed_attr_context ctx;
+ AttributeName *anp;
+
+ if ( slapi_op_internal_p( op, rs, NULL ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ ctx.cac_pb = slapi_over_pblock_new( op, rs );
+ ctx.cac_op = op;
+ ctx.cac_private = rs;
+
+ if ( rs->sr_entry != NULL ) {
+ /*
+ * For each client requested attribute, call the plugins.
+ */
+ if ( rs->sr_attrs != NULL ) {
+ for ( anp = rs->sr_attrs; anp->an_name.bv_val != NULL; anp++ ) {
+ if ( compute_evaluator( &ctx, anp->an_name.bv_val,
+ rs->sr_entry, slapi_over_compute_output ) == 1 ) {
+ break;
+ }
+ }
+ } else {
+ /*
+ * Technically we shouldn't be returning operational attributes
+ * when the user requested only user attributes. We'll let the
+ * plugin decide whether to be naughty or not.
+ */
+ compute_evaluator( &ctx, "*", rs->sr_entry, slapi_over_compute_output );
+ }
+ }
+
+ slapi_pblock_destroy( ctx.cac_pb );
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * We need this function to call frontendDB (global) plugins before
+ * database plugins, if we are invoked by a slap_callback.
+ */
+static int
+slapi_over_call_plugins( Slapi_PBlock *pb, int type )
+{
+ int rc = 1; /* means no plugins called */
+ Operation *op;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ op = pb->pb_op;
+
+ if ( !be_match( op->o_bd, frontendDB ) ) {
+ rc = slapi_int_call_plugins( frontendDB, type, pb );
+ }
+ if ( rc >= 0 ) {
+ rc = slapi_int_call_plugins( op->o_bd, type, pb );
+ }
+
+ return rc;
+}
+
+static int
+slapi_over_search( Operation *op, SlapReply *rs, int type )
+{
+ int rc;
+ Slapi_PBlock *pb;
+
+ assert( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF );
+
+ /* create a new pblock to not trample on result controls */
+ pb = slapi_over_pblock_new( op, rs );
+
+ rc = slapi_over_call_plugins( pb, type );
+ if ( rc >= 0 ) /* 1 means no plugins called */
+ rc = SLAP_CB_CONTINUE;
+ else
+ rc = LDAP_SUCCESS; /* confusing: don't abort, but don't send */
+
+ slapi_pblock_destroy(pb);
+
+ return rc;
+}
+
+/*
+ * Call pre- and post-result plugins
+ */
+static int
+slapi_over_result( Operation *op, SlapReply *rs, int type )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+
+ assert( rs->sr_type == REP_RESULT || rs->sr_type == REP_SASL || rs->sr_type == REP_EXTENDED );
+
+ slapi_over_call_plugins( pb, type );
+
+ return SLAP_CB_CONTINUE;
+}
+
+
+static int
+slapi_op_bind_callback( Operation *op, SlapReply *rs, int prc )
+{
+ switch ( prc ) {
+ case SLAPI_BIND_SUCCESS:
+ /* Continue with backend processing */
+ break;
+ case SLAPI_BIND_FAIL:
+ /* Failure, frontend (that's us) sends result */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ break;
+ case SLAPI_BIND_ANONYMOUS: /* undocumented */
+ default: /* plugin sent result or no plugins called */
+ BER_BVZERO( &op->orb_edn );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ /*
+ * Plugin will have called slapi_pblock_set(LDAP_CONN_DN) which
+ * will have set conn->c_dn and conn->c_ndn
+ */
+ if ( BER_BVISNULL( &op->o_conn->c_ndn ) && prc == 1 ) {
+ /* No plugins were called; continue processing */
+ return LDAP_SUCCESS;
+ }
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ if ( !BER_BVISEMPTY( &op->o_conn->c_ndn ) ) {
+ ber_len_t max = sockbuf_max_incoming_auth;
+ ber_sockbuf_ctrl( op->o_conn->c_sb,
+ LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ /* log authorization identity */
+ Debug( LDAP_DEBUG_STATS,
+ "%s BIND dn=\"%s\" mech=%s (SLAPI) ssf=0\n",
+ op->o_log_prefix,
+ BER_BVISNULL( &op->o_conn->c_dn )
+ ? "<empty>" : op->o_conn->c_dn.bv_val,
+ BER_BVISNULL( &op->orb_mech )
+ ? "<empty>" : op->orb_mech.bv_val );
+
+ return -1;
+ }
+ break;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+slapi_op_search_callback( Operation *op, SlapReply *rs, int prc )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ Filter *f = op->ors_filter;
+
+ /* check preoperation result code */
+ if ( prc < 0 ) {
+ return rs->sr_err;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ if ( pb->pb_intop == 0 &&
+ slapi_int_call_plugins( op->o_bd, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, pb ) == 0 ) {
+ /*
+ * The plugin can set the SLAPI_SEARCH_FILTER.
+ * SLAPI_SEARCH_STRFILER is not normative.
+ */
+ if (f != op->ors_filter) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+struct slapi_op_info {
+ int soi_preop; /* preoperation plugin parameter */
+ int soi_postop; /* postoperation plugin parameter */
+ int soi_internal_preop; /* internal preoperation plugin parameter */
+ int soi_internal_postop; /* internal postoperation plugin parameter */
+ int (*soi_callback)(Operation *, SlapReply *, int); /* preoperation result handler */
+} slapi_op_dispatch_table[] = {
+ {
+ SLAPI_PLUGIN_PRE_BIND_FN,
+ SLAPI_PLUGIN_POST_BIND_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_BIND_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_BIND_FN,
+ slapi_op_bind_callback
+ },
+ {
+ SLAPI_PLUGIN_PRE_UNBIND_FN,
+ SLAPI_PLUGIN_POST_UNBIND_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_UNBIND_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_UNBIND_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_SEARCH_FN,
+ SLAPI_PLUGIN_POST_SEARCH_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_SEARCH_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_SEARCH_FN,
+ slapi_op_search_callback
+ },
+ {
+ SLAPI_PLUGIN_PRE_COMPARE_FN,
+ SLAPI_PLUGIN_POST_COMPARE_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_COMPARE_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_COMPARE_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_MODIFY_FN,
+ SLAPI_PLUGIN_POST_MODIFY_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_MODRDN_FN,
+ SLAPI_PLUGIN_POST_MODRDN_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_ADD_FN,
+ SLAPI_PLUGIN_POST_ADD_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_ADD_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_DELETE_FN,
+ SLAPI_PLUGIN_POST_DELETE_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN,
+ NULL
+ },
+ {
+ SLAPI_PLUGIN_PRE_ABANDON_FN,
+ SLAPI_PLUGIN_POST_ABANDON_FN,
+ SLAPI_PLUGIN_INTERNAL_PRE_ABANDON_FN,
+ SLAPI_PLUGIN_INTERNAL_POST_ABANDON_FN,
+ NULL
+ },
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ NULL
+ }
+};
+
+slap_operation_t
+slapi_tag2op( ber_tag_t tag )
+{
+ slap_operation_t op;
+
+ switch ( tag ) {
+ case LDAP_REQ_BIND:
+ op = op_bind;
+ break;
+ case LDAP_REQ_ADD:
+ op = op_add;
+ break;
+ case LDAP_REQ_DELETE:
+ op = op_delete;
+ break;
+ case LDAP_REQ_MODRDN:
+ op = op_modrdn;
+ break;
+ case LDAP_REQ_MODIFY:
+ op = op_modify;
+ break;
+ case LDAP_REQ_COMPARE:
+ op = op_compare;
+ break;
+ case LDAP_REQ_SEARCH:
+ op = op_search;
+ break;
+ case LDAP_REQ_UNBIND:
+ op = op_unbind;
+ break;
+ default:
+ op = op_last;
+ break;
+ }
+
+ return op;
+}
+
+/* Add SLAPI_RESCONTROLS to rs->sr_ctrls, with care, because
+ * rs->sr_ctrls could be allocated on the stack */
+static int
+slapi_over_merge_controls( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ LDAPControl **ctrls = NULL;
+ LDAPControl **slapi_ctrls = NULL;
+ size_t n_slapi_ctrls = 0;
+ size_t n_rs_ctrls = 0;
+ size_t i;
+
+ slapi_pblock_get( pb, SLAPI_RESCONTROLS, (void **)&slapi_ctrls );
+
+ n_slapi_ctrls = slapi_int_count_controls( slapi_ctrls );
+ n_rs_ctrls = slapi_int_count_controls( rs->sr_ctrls );
+
+ if ( n_slapi_ctrls == 0 )
+ return LDAP_SUCCESS; /* no SLAPI controls */
+
+ slapi_pblock_set( pb, SLAPI_X_OLD_RESCONTROLS, (void *)rs->sr_ctrls );
+
+ ctrls = (LDAPControl **) op->o_tmpalloc(
+ ( n_slapi_ctrls + n_rs_ctrls + 1 ) * sizeof(LDAPControl *),
+ op->o_tmpmemctx );
+
+ for ( i = 0; i < n_slapi_ctrls; i++ ) {
+ ctrls[i] = slapi_ctrls[i];
+ }
+ if ( rs->sr_ctrls != NULL ) {
+ for ( i = 0; i < n_rs_ctrls; i++ ) {
+ ctrls[n_slapi_ctrls + i] = rs->sr_ctrls[i];
+ }
+ }
+ ctrls[n_slapi_ctrls + n_rs_ctrls] = NULL;
+
+ rs->sr_ctrls = ctrls;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+slapi_over_unmerge_controls( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ LDAPControl **rs_ctrls = NULL;
+
+ slapi_pblock_get( pb, SLAPI_X_OLD_RESCONTROLS, (void **)&rs_ctrls );
+
+ if ( rs_ctrls == NULL || rs->sr_ctrls == rs_ctrls ) {
+ /* no copying done */
+ return LDAP_SUCCESS;
+ }
+
+ op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );
+ rs->sr_ctrls = rs_ctrls;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+slapi_over_response( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( pb->pb_intop == 0 ) {
+ switch ( rs->sr_type ) {
+ case REP_RESULT:
+ case REP_SASL:
+ case REP_EXTENDED:
+ rc = slapi_over_result( op, rs, SLAPI_PLUGIN_PRE_RESULT_FN );
+ break;
+ case REP_SEARCH:
+ rc = slapi_over_search( op, rs, SLAPI_PLUGIN_PRE_ENTRY_FN );
+ break;
+ case REP_SEARCHREF:
+ rc = slapi_over_search( op, rs, SLAPI_PLUGIN_PRE_REFERRAL_FN );
+ break;
+ default:
+ break;
+ }
+ }
+
+ slapi_over_merge_controls( op, rs );
+
+ return rc;
+}
+
+static int
+slapi_over_cleanup( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb = SLAPI_OPERATION_PBLOCK( op );
+ int rc = SLAP_CB_CONTINUE;
+
+ slapi_over_unmerge_controls( op, rs );
+
+ if ( pb->pb_intop == 0 ) {
+ switch ( rs->sr_type ) {
+ case REP_RESULT:
+ case REP_SASL:
+ case REP_EXTENDED:
+ rc = slapi_over_result( op, rs, SLAPI_PLUGIN_POST_RESULT_FN );
+ break;
+ case REP_SEARCH:
+ rc = slapi_over_search( op, rs, SLAPI_PLUGIN_POST_ENTRY_FN );
+ break;
+ case REP_SEARCHREF:
+ rc = slapi_over_search( op, rs, SLAPI_PLUGIN_POST_REFERRAL_FN );
+ break;
+ default:
+ break;
+ }
+ }
+
+ return rc;
+}
+
+static int
+slapi_op_func( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb;
+ slap_operation_t which;
+ struct slapi_op_info *opinfo;
+ int rc;
+ slap_overinfo *oi;
+ slap_overinst *on;
+ slap_callback cb;
+ int internal_op;
+ int preop_type, postop_type;
+ BackendDB *be;
+
+ if ( !slapi_plugins_used )
+ return SLAP_CB_CONTINUE;
+
+ /*
+ * Find the SLAPI operation information for this LDAP
+ * operation; this will contain the preop and postop
+ * plugin types, as well as optional callbacks for
+ * setting up the SLAPI environment.
+ */
+ which = slapi_tag2op( op->o_tag );
+ if ( which >= op_last ) {
+ /* invalid operation, but let someone else deal with it */
+ return SLAP_CB_CONTINUE;
+ }
+
+ opinfo = &slapi_op_dispatch_table[which];
+ if ( opinfo == NULL ) {
+ /* no SLAPI plugin types for this operation */
+ return SLAP_CB_CONTINUE;
+ }
+
+ internal_op = slapi_op_internal_p( op, rs, &cb );
+
+ if ( internal_op ) {
+ preop_type = opinfo->soi_internal_preop;
+ postop_type = opinfo->soi_internal_postop;
+ } else {
+ preop_type = opinfo->soi_preop;
+ postop_type = opinfo->soi_postop;
+ }
+
+ if ( preop_type == 0 ) {
+ /* no SLAPI plugin types for this operation */
+ pb = NULL;
+ rc = SLAP_CB_CONTINUE;
+ goto cleanup;
+ }
+
+ pb = SLAPI_OPERATION_PBLOCK( op );
+
+ /* cache backend so we call correct postop plugins */
+ be = pb->pb_op->o_bd;
+
+ rc = slapi_int_call_plugins( be, preop_type, pb );
+
+ /*
+ * soi_callback is responsible for examining the result code
+ * of the preoperation plugin and determining whether to
+ * abort. This is needed because of special SLAPI behaviour
+ e with bind preoperation plugins.
+ *
+ * The soi_callback function is also used to reset any values
+ * returned from the preoperation plugin before calling the
+ * backend (for the success case).
+ */
+ if ( opinfo->soi_callback == NULL ) {
+ /* default behaviour is preop plugin can abort operation */
+ if ( rc < 0 ) {
+ rc = rs->sr_err;
+ goto cleanup;
+ }
+ } else {
+ rc = (opinfo->soi_callback)( op, rs, rc );
+ if ( rc )
+ goto cleanup;
+ }
+
+ /*
+ * Call actual backend (or next overlay in stack). We need to
+ * do this rather than returning SLAP_CB_CONTINUE and calling
+ * postoperation plugins in a response handler to match the
+ * behaviour of SLAPI in OpenLDAP 2.2, where postoperation
+ * plugins are called after the backend has completely
+ * finished processing the operation.
+ */
+ on = (slap_overinst *)op->o_bd->bd_info;
+ oi = on->on_info;
+
+ rc = overlay_op_walk( op, rs, which, oi, on->on_next );
+
+ /*
+ * Call postoperation plugins
+ */
+ slapi_int_call_plugins( be, postop_type, pb );
+
+cleanup:
+ if ( !internal_op ) {
+ slapi_pblock_destroy(pb);
+ cb.sc_private = NULL;
+ }
+
+ op->o_callback = cb.sc_next;
+
+ return rc;
+}
+
+static int
+slapi_over_extended( Operation *op, SlapReply *rs )
+{
+ Slapi_PBlock *pb;
+ SLAPI_FUNC callback;
+ int rc;
+ int internal_op;
+ slap_callback cb;
+
+ slapi_int_get_extop_plugin( &op->ore_reqoid, &callback );
+ if ( callback == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ internal_op = slapi_op_internal_p( op, rs, &cb );
+ if ( internal_op ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ pb = SLAPI_OPERATION_PBLOCK( op );
+
+ rc = (*callback)( pb );
+ if ( rc == SLAPI_PLUGIN_EXTENDED_SENT_RESULT ) {
+ goto cleanup;
+ } else if ( rc == SLAPI_PLUGIN_EXTENDED_NOT_HANDLED ) {
+ rc = SLAP_CB_CONTINUE;
+ goto cleanup;
+ }
+
+ assert( rs->sr_rspoid != NULL );
+
+ send_ldap_extended( op, rs );
+
+#if 0
+ slapi_ch_free_string( (char **)&rs->sr_rspoid );
+#endif
+
+ if ( rs->sr_rspdata != NULL )
+ ber_bvfree( rs->sr_rspdata );
+
+ rc = rs->sr_err;
+
+cleanup:
+ slapi_pblock_destroy( pb );
+ op->o_callback = cb.sc_next;
+
+ return rc;
+}
+
+static int
+slapi_over_access_allowed(
+ Operation *op,
+ Entry *e,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state,
+ slap_mask_t *maskp )
+{
+ int rc;
+ Slapi_PBlock *pb;
+ slap_callback cb;
+ int internal_op;
+ SlapReply rs = { REP_RESULT };
+
+ internal_op = slapi_op_internal_p( op, &rs, &cb );
+
+ cb.sc_response = NULL;
+ cb.sc_cleanup = NULL;
+ cb.sc_writewait = NULL;
+
+ pb = SLAPI_OPERATION_PBLOCK( op );
+
+ rc = slapi_int_access_allowed( op, e, desc, val, access, state );
+ if ( rc ) {
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ if ( !internal_op ) {
+ slapi_pblock_destroy( pb );
+ }
+
+ op->o_callback = cb.sc_next;
+
+ return rc;
+}
+
+static int
+slapi_over_acl_group(
+ Operation *op,
+ Entry *target,
+ struct berval *gr_ndn,
+ struct berval *op_ndn,
+ ObjectClass *group_oc,
+ AttributeDescription *group_at )
+{
+ Slapi_Entry *e;
+ int rc;
+ Slapi_PBlock *pb;
+ BackendDB *be = op->o_bd;
+ GroupAssertion *g;
+ SlapReply rs = { REP_RESULT };
+
+ op->o_bd = select_backend( gr_ndn, 0 );
+
+ for ( g = op->o_groups; g; g = g->ga_next ) {
+ if ( g->ga_be != op->o_bd || g->ga_oc != group_oc ||
+ g->ga_at != group_at || g->ga_len != gr_ndn->bv_len )
+ {
+ continue;
+ }
+ if ( strcmp( g->ga_ndn, gr_ndn->bv_val ) == 0 ) {
+ break;
+ }
+ }
+ if ( g != NULL ) {
+ rc = g->ga_res;
+ goto done;
+ }
+
+ if ( target != NULL && dn_match( &target->e_nname, gr_ndn ) ) {
+ e = target;
+ rc = 0;
+ } else {
+ rc = be_entry_get_rw( op, gr_ndn, group_oc, group_at, 0, &e );
+ }
+ if ( e != NULL ) {
+ int internal_op;
+ slap_callback cb;
+
+ internal_op = slapi_op_internal_p( op, &rs, &cb );
+
+ cb.sc_response = NULL;
+ cb.sc_cleanup = NULL;
+ cb.sc_writewait = NULL;
+
+ pb = SLAPI_OPERATION_PBLOCK( op );
+
+ slapi_pblock_set( pb, SLAPI_X_GROUP_ENTRY, (void *)e );
+ slapi_pblock_set( pb, SLAPI_X_GROUP_OPERATION_DN, (void *)op_ndn->bv_val );
+ slapi_pblock_set( pb, SLAPI_X_GROUP_ATTRIBUTE, (void *)group_at->ad_cname.bv_val );
+ slapi_pblock_set( pb, SLAPI_X_GROUP_TARGET_ENTRY, (void *)target );
+
+ rc = slapi_over_call_plugins( pb, SLAPI_X_PLUGIN_PRE_GROUP_FN );
+ if ( rc >= 0 ) /* 1 means no plugins called */
+ rc = SLAP_CB_CONTINUE;
+ else
+ rc = pb->pb_rs->sr_err;
+
+ slapi_pblock_delete_param( pb, SLAPI_X_GROUP_ENTRY );
+ slapi_pblock_delete_param( pb, SLAPI_X_GROUP_OPERATION_DN );
+ slapi_pblock_delete_param( pb, SLAPI_X_GROUP_ATTRIBUTE );
+ slapi_pblock_delete_param( pb, SLAPI_X_GROUP_TARGET_ENTRY );
+
+ if ( !internal_op )
+ slapi_pblock_destroy( pb );
+
+ if ( e != target ) {
+ be_entry_release_r( op, e );
+ }
+
+ op->o_callback = cb.sc_next;
+ } else {
+ rc = LDAP_NO_SUCH_OBJECT; /* return SLAP_CB_CONTINUE for correctness? */
+ }
+
+ if ( op->o_tag != LDAP_REQ_BIND && !op->o_do_not_cache &&
+ rc != SLAP_CB_CONTINUE ) {
+ g = op->o_tmpalloc( sizeof( GroupAssertion ) + gr_ndn->bv_len,
+ op->o_tmpmemctx );
+ g->ga_be = op->o_bd;
+ g->ga_oc = group_oc;
+ g->ga_at = group_at;
+ g->ga_res = rc;
+ g->ga_len = gr_ndn->bv_len;
+ strcpy( g->ga_ndn, gr_ndn->bv_val );
+ g->ga_next = op->o_groups;
+ op->o_groups = g;
+ }
+ /*
+ * XXX don't call POST_GROUP_FN, I have no idea what the point of
+ * that plugin function was anyway
+ */
+done:
+ op->o_bd = be;
+ return rc;
+}
+
+static int
+slapi_over_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ Slapi_PBlock *pb;
+ int rc;
+
+ pb = slapi_pblock_new();
+
+ rc = slapi_int_call_plugins( be, SLAPI_PLUGIN_START_FN, pb );
+
+ slapi_pblock_destroy( pb );
+
+ return rc;
+}
+
+static int
+slapi_over_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ Slapi_PBlock *pb;
+ int rc;
+
+ pb = slapi_pblock_new();
+
+ rc = slapi_int_call_plugins( be, SLAPI_PLUGIN_CLOSE_FN, pb );
+
+ slapi_pblock_destroy( pb );
+
+ return rc;
+}
+
+static int
+slapi_over_init()
+{
+ memset( &slapi, 0, sizeof(slapi) );
+
+ slapi.on_bi.bi_type = SLAPI_OVERLAY_NAME;
+
+ slapi.on_bi.bi_op_bind = slapi_op_func;
+ slapi.on_bi.bi_op_unbind = slapi_op_func;
+ slapi.on_bi.bi_op_search = slapi_op_func;
+ slapi.on_bi.bi_op_compare = slapi_op_func;
+ slapi.on_bi.bi_op_modify = slapi_op_func;
+ slapi.on_bi.bi_op_modrdn = slapi_op_func;
+ slapi.on_bi.bi_op_add = slapi_op_func;
+ slapi.on_bi.bi_op_delete = slapi_op_func;
+ slapi.on_bi.bi_op_abandon = slapi_op_func;
+ slapi.on_bi.bi_op_cancel = slapi_op_func;
+
+ slapi.on_bi.bi_db_open = slapi_over_db_open;
+ slapi.on_bi.bi_db_close = slapi_over_db_close;
+
+ slapi.on_bi.bi_extended = slapi_over_extended;
+ slapi.on_bi.bi_access_allowed = slapi_over_access_allowed;
+ slapi.on_bi.bi_operational = slapi_over_aux_operational;
+ slapi.on_bi.bi_acl_group = slapi_over_acl_group;
+
+ return overlay_register( &slapi );
+}
+
+int slapi_over_is_inst( BackendDB *be )
+{
+ return overlay_is_inst( be, SLAPI_OVERLAY_NAME );
+}
+
+int slapi_over_config( BackendDB *be, ConfigReply *cr )
+{
+ if ( slapi_over_initialized == 0 ) {
+ int rc;
+
+ /* do global initialization */
+ ldap_pvt_thread_mutex_init( &slapi_hn_mutex );
+ ldap_pvt_thread_mutex_init( &slapi_time_mutex );
+ ldap_pvt_thread_mutex_init( &slapi_printmessage_mutex );
+
+ if ( slapi_log_file == NULL )
+ slapi_log_file = slapi_ch_strdup( LDAP_RUNDIR LDAP_DIRSEP "errors" );
+
+ rc = slapi_int_init_object_extensions();
+ if ( rc != 0 )
+ return rc;
+
+ rc = slapi_over_init();
+ if ( rc != 0 )
+ return rc;
+
+ slapi_over_initialized = 1;
+ }
+
+ return overlay_config( be, SLAPI_OVERLAY_NAME, -1, NULL, cr );
+}
+
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_pblock.c b/servers/slapd/slapi/slapi_pblock.c
new file mode 100644
index 0000000..2c0afda
--- /dev/null
+++ b/servers/slapd/slapi/slapi_pblock.c
@@ -0,0 +1,1447 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#include "portable.h"
+#include <slap.h>
+#include <slapi.h>
+
+#ifdef LDAP_SLAPI
+
+/* some parameters require a valid connection and operation */
+#define PBLOCK_LOCK_CONN( _pb ) do { \
+ ldap_pvt_thread_mutex_lock( &(_pb)->pb_conn->c_mutex ); \
+ } while (0)
+
+#define PBLOCK_UNLOCK_CONN( _pb ) do { \
+ ldap_pvt_thread_mutex_unlock( &(_pb)->pb_conn->c_mutex ); \
+ } while (0)
+
+/* some parameters are only settable for internal operations */
+#define PBLOCK_VALIDATE_IS_INTOP( _pb ) do { if ( (_pb)->pb_intop == 0 ) break; } while ( 0 )
+
+static slapi_pblock_class_t
+pblock_get_param_class( int param )
+{
+ switch ( param ) {
+ case SLAPI_PLUGIN_TYPE:
+ case SLAPI_PLUGIN_ARGC:
+ case SLAPI_PLUGIN_OPRETURN:
+ case SLAPI_PLUGIN_INTOP_RESULT:
+ case SLAPI_CONFIG_LINENO:
+ case SLAPI_CONFIG_ARGC:
+ case SLAPI_BIND_METHOD:
+ case SLAPI_MODRDN_DELOLDRDN:
+ case SLAPI_SEARCH_SCOPE:
+ case SLAPI_SEARCH_DEREF:
+ case SLAPI_SEARCH_SIZELIMIT:
+ case SLAPI_SEARCH_TIMELIMIT:
+ case SLAPI_SEARCH_ATTRSONLY:
+ case SLAPI_NENTRIES:
+ case SLAPI_CHANGENUMBER:
+ case SLAPI_DBSIZE:
+ case SLAPI_REQUESTOR_ISROOT:
+ case SLAPI_BE_READONLY:
+ case SLAPI_BE_LASTMOD:
+ case SLAPI_DB2LDIF_PRINTKEY:
+ case SLAPI_LDIF2DB_REMOVEDUPVALS:
+ case SLAPI_MANAGEDSAIT:
+ case SLAPI_X_RELAX:
+ case SLAPI_X_OPERATION_NO_SCHEMA_CHECK:
+ case SLAPI_IS_REPLICATED_OPERATION:
+ case SLAPI_X_CONN_IS_UDP:
+ case SLAPI_X_CONN_SSF:
+ case SLAPI_RESULT_CODE:
+ case SLAPI_LOG_OPERATION:
+ case SLAPI_IS_INTERNAL_OPERATION:
+ return PBLOCK_CLASS_INTEGER;
+ break;
+
+ case SLAPI_CONN_ID:
+ case SLAPI_OPERATION_ID:
+ case SLAPI_OPINITIATED_TIME:
+ case SLAPI_ABANDON_MSGID:
+ case SLAPI_X_OPERATION_DELETE_GLUE_PARENT:
+ case SLAPI_OPERATION_MSGID:
+ return PBLOCK_CLASS_LONG_INTEGER;
+ break;
+
+ case SLAPI_PLUGIN_DESTROY_FN:
+ case SLAPI_PLUGIN_DB_BIND_FN:
+ case SLAPI_PLUGIN_DB_UNBIND_FN:
+ case SLAPI_PLUGIN_DB_SEARCH_FN:
+ case SLAPI_PLUGIN_DB_COMPARE_FN:
+ case SLAPI_PLUGIN_DB_MODIFY_FN:
+ case SLAPI_PLUGIN_DB_MODRDN_FN:
+ case SLAPI_PLUGIN_DB_ADD_FN:
+ case SLAPI_PLUGIN_DB_DELETE_FN:
+ case SLAPI_PLUGIN_DB_ABANDON_FN:
+ case SLAPI_PLUGIN_DB_CONFIG_FN:
+ case SLAPI_PLUGIN_CLOSE_FN:
+ case SLAPI_PLUGIN_DB_FLUSH_FN:
+ case SLAPI_PLUGIN_START_FN:
+ case SLAPI_PLUGIN_DB_SEQ_FN:
+ case SLAPI_PLUGIN_DB_ENTRY_FN:
+ case SLAPI_PLUGIN_DB_REFERRAL_FN:
+ case SLAPI_PLUGIN_DB_RESULT_FN:
+ case SLAPI_PLUGIN_DB_LDIF2DB_FN:
+ case SLAPI_PLUGIN_DB_DB2LDIF_FN:
+ case SLAPI_PLUGIN_DB_BEGIN_FN:
+ case SLAPI_PLUGIN_DB_COMMIT_FN:
+ case SLAPI_PLUGIN_DB_ABORT_FN:
+ case SLAPI_PLUGIN_DB_ARCHIVE2DB_FN:
+ case SLAPI_PLUGIN_DB_DB2ARCHIVE_FN:
+ case SLAPI_PLUGIN_DB_NEXT_SEARCH_ENTRY_FN:
+ case SLAPI_PLUGIN_DB_FREE_RESULT_SET_FN:
+ case SLAPI_PLUGIN_DB_SIZE_FN:
+ case SLAPI_PLUGIN_DB_TEST_FN:
+ case SLAPI_PLUGIN_DB_NO_ACL:
+ case SLAPI_PLUGIN_EXT_OP_FN:
+ case SLAPI_PLUGIN_EXT_OP_OIDLIST:
+ case SLAPI_PLUGIN_PRE_BIND_FN:
+ case SLAPI_PLUGIN_PRE_UNBIND_FN:
+ case SLAPI_PLUGIN_PRE_SEARCH_FN:
+ case SLAPI_PLUGIN_PRE_COMPARE_FN:
+ case SLAPI_PLUGIN_PRE_MODIFY_FN:
+ case SLAPI_PLUGIN_PRE_MODRDN_FN:
+ case SLAPI_PLUGIN_PRE_ADD_FN:
+ case SLAPI_PLUGIN_PRE_DELETE_FN:
+ case SLAPI_PLUGIN_PRE_ABANDON_FN:
+ case SLAPI_PLUGIN_PRE_ENTRY_FN:
+ case SLAPI_PLUGIN_PRE_REFERRAL_FN:
+ case SLAPI_PLUGIN_PRE_RESULT_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_ADD_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_MODIFY_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_MODRDN_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_DELETE_FN:
+ case SLAPI_PLUGIN_BE_PRE_ADD_FN:
+ case SLAPI_PLUGIN_BE_PRE_MODIFY_FN:
+ case SLAPI_PLUGIN_BE_PRE_MODRDN_FN:
+ case SLAPI_PLUGIN_BE_PRE_DELETE_FN:
+ case SLAPI_PLUGIN_POST_BIND_FN:
+ case SLAPI_PLUGIN_POST_UNBIND_FN:
+ case SLAPI_PLUGIN_POST_SEARCH_FN:
+ case SLAPI_PLUGIN_POST_COMPARE_FN:
+ case SLAPI_PLUGIN_POST_MODIFY_FN:
+ case SLAPI_PLUGIN_POST_MODRDN_FN:
+ case SLAPI_PLUGIN_POST_ADD_FN:
+ case SLAPI_PLUGIN_POST_DELETE_FN:
+ case SLAPI_PLUGIN_POST_ABANDON_FN:
+ case SLAPI_PLUGIN_POST_ENTRY_FN:
+ case SLAPI_PLUGIN_POST_REFERRAL_FN:
+ case SLAPI_PLUGIN_POST_RESULT_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_ADD_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN:
+ case SLAPI_PLUGIN_BE_POST_ADD_FN:
+ case SLAPI_PLUGIN_BE_POST_MODIFY_FN:
+ case SLAPI_PLUGIN_BE_POST_MODRDN_FN:
+ case SLAPI_PLUGIN_BE_POST_DELETE_FN:
+ case SLAPI_PLUGIN_MR_FILTER_CREATE_FN:
+ case SLAPI_PLUGIN_MR_INDEXER_CREATE_FN:
+ case SLAPI_PLUGIN_MR_FILTER_MATCH_FN:
+ case SLAPI_PLUGIN_MR_FILTER_INDEX_FN:
+ case SLAPI_PLUGIN_MR_FILTER_RESET_FN:
+ case SLAPI_PLUGIN_MR_INDEX_FN:
+ case SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN:
+ case SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN:
+ case SLAPI_PLUGIN_ACL_ALLOW_ACCESS:
+ case SLAPI_X_PLUGIN_PRE_GROUP_FN:
+ case SLAPI_X_PLUGIN_POST_GROUP_FN:
+ case SLAPI_PLUGIN_AUDIT_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_BIND_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_UNBIND_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_SEARCH_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_COMPARE_FN:
+ case SLAPI_PLUGIN_INTERNAL_PRE_ABANDON_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_BIND_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_UNBIND_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_SEARCH_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_COMPARE_FN:
+ case SLAPI_PLUGIN_INTERNAL_POST_ABANDON_FN:
+ return PBLOCK_CLASS_FUNCTION_POINTER;
+ break;
+
+ case SLAPI_BACKEND:
+ case SLAPI_CONNECTION:
+ case SLAPI_OPERATION:
+ case SLAPI_OPERATION_PARAMETERS:
+ case SLAPI_OPERATION_TYPE:
+ case SLAPI_OPERATION_AUTHTYPE:
+ case SLAPI_BE_MONITORDN:
+ case SLAPI_BE_TYPE:
+ case SLAPI_REQUESTOR_DN:
+ case SLAPI_CONN_DN:
+ case SLAPI_CONN_CLIENTIP:
+ case SLAPI_CONN_SERVERIP:
+ case SLAPI_CONN_AUTHTYPE:
+ case SLAPI_CONN_AUTHMETHOD:
+ case SLAPI_CONN_CERT:
+ case SLAPI_X_CONN_CLIENTPATH:
+ case SLAPI_X_CONN_SERVERPATH:
+ case SLAPI_X_CONN_SASL_CONTEXT:
+ case SLAPI_X_CONFIG_ARGV:
+ case SLAPI_X_INTOP_FLAGS:
+ case SLAPI_X_INTOP_RESULT_CALLBACK:
+ case SLAPI_X_INTOP_SEARCH_ENTRY_CALLBACK:
+ case SLAPI_X_INTOP_REFERRAL_ENTRY_CALLBACK:
+ case SLAPI_X_INTOP_CALLBACK_DATA:
+ case SLAPI_PLUGIN_MR_OID:
+ case SLAPI_PLUGIN_MR_TYPE:
+ case SLAPI_PLUGIN_MR_VALUE:
+ case SLAPI_PLUGIN_MR_VALUES:
+ case SLAPI_PLUGIN_MR_KEYS:
+ case SLAPI_PLUGIN:
+ case SLAPI_PLUGIN_PRIVATE:
+ case SLAPI_PLUGIN_ARGV:
+ case SLAPI_PLUGIN_OBJECT:
+ case SLAPI_PLUGIN_DESCRIPTION:
+ case SLAPI_PLUGIN_IDENTITY:
+ case SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES:
+ case SLAPI_PLUGIN_INTOP_SEARCH_REFERRALS:
+ case SLAPI_PLUGIN_MR_FILTER_REUSABLE:
+ case SLAPI_PLUGIN_MR_QUERY_OPERATOR:
+ case SLAPI_PLUGIN_MR_USAGE:
+ case SLAPI_OP_LESS:
+ case SLAPI_OP_LESS_OR_EQUAL:
+ case SLAPI_PLUGIN_MR_USAGE_INDEX:
+ case SLAPI_PLUGIN_SYNTAX_FILTER_AVA:
+ case SLAPI_PLUGIN_SYNTAX_FILTER_SUB:
+ case SLAPI_PLUGIN_SYNTAX_VALUES2KEYS:
+ case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA:
+ case SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB:
+ case SLAPI_PLUGIN_SYNTAX_NAMES:
+ case SLAPI_PLUGIN_SYNTAX_OID:
+ case SLAPI_PLUGIN_SYNTAX_FLAGS:
+ case SLAPI_PLUGIN_SYNTAX_COMPARE:
+ case SLAPI_CONFIG_FILENAME:
+ case SLAPI_CONFIG_ARGV:
+ case SLAPI_TARGET_ADDRESS:
+ case SLAPI_TARGET_UNIQUEID:
+ case SLAPI_TARGET_DN:
+ case SLAPI_REQCONTROLS:
+ case SLAPI_ENTRY_PRE_OP:
+ case SLAPI_ENTRY_POST_OP:
+ case SLAPI_RESCONTROLS:
+ case SLAPI_X_OLD_RESCONTROLS:
+ case SLAPI_ADD_RESCONTROL:
+ case SLAPI_CONTROLS_ARG:
+ case SLAPI_ADD_ENTRY:
+ case SLAPI_ADD_EXISTING_DN_ENTRY:
+ case SLAPI_ADD_PARENT_ENTRY:
+ case SLAPI_ADD_PARENT_UNIQUEID:
+ case SLAPI_ADD_EXISTING_UNIQUEID_ENTRY:
+ case SLAPI_BIND_CREDENTIALS:
+ case SLAPI_BIND_SASLMECHANISM:
+ case SLAPI_BIND_RET_SASLCREDS:
+ case SLAPI_COMPARE_TYPE:
+ case SLAPI_COMPARE_VALUE:
+ case SLAPI_MODIFY_MODS:
+ case SLAPI_MODRDN_NEWRDN:
+ case SLAPI_MODRDN_NEWSUPERIOR:
+ case SLAPI_MODRDN_PARENT_ENTRY:
+ case SLAPI_MODRDN_NEWPARENT_ENTRY:
+ case SLAPI_MODRDN_TARGET_ENTRY:
+ case SLAPI_MODRDN_NEWSUPERIOR_ADDRESS:
+ case SLAPI_SEARCH_FILTER:
+ case SLAPI_SEARCH_STRFILTER:
+ case SLAPI_SEARCH_ATTRS:
+ case SLAPI_SEQ_TYPE:
+ case SLAPI_SEQ_ATTRNAME:
+ case SLAPI_SEQ_VAL:
+ case SLAPI_EXT_OP_REQ_OID:
+ case SLAPI_EXT_OP_REQ_VALUE:
+ case SLAPI_EXT_OP_RET_OID:
+ case SLAPI_EXT_OP_RET_VALUE:
+ case SLAPI_MR_FILTER_ENTRY:
+ case SLAPI_MR_FILTER_TYPE:
+ case SLAPI_MR_FILTER_VALUE:
+ case SLAPI_MR_FILTER_OID:
+ case SLAPI_MR_FILTER_DNATTRS:
+ case SLAPI_LDIF2DB_FILE:
+ case SLAPI_PARENT_TXN:
+ case SLAPI_TXN:
+ case SLAPI_SEARCH_RESULT_SET:
+ case SLAPI_SEARCH_RESULT_ENTRY:
+ case SLAPI_SEARCH_REFERRALS:
+ case SLAPI_RESULT_TEXT:
+ case SLAPI_RESULT_MATCHED:
+ case SLAPI_X_GROUP_ENTRY:
+ case SLAPI_X_GROUP_ATTRIBUTE:
+ case SLAPI_X_GROUP_OPERATION_DN:
+ case SLAPI_X_GROUP_TARGET_ENTRY:
+ case SLAPI_X_ADD_STRUCTURAL_CLASS:
+ case SLAPI_PLUGIN_AUDIT_DATA:
+ case SLAPI_IBM_PBLOCK:
+ case SLAPI_PLUGIN_VERSION:
+ return PBLOCK_CLASS_POINTER;
+ break;
+ default:
+ break;
+ }
+
+ return PBLOCK_CLASS_INVALID;
+}
+
+static void
+pblock_lock( Slapi_PBlock *pb )
+{
+ ldap_pvt_thread_mutex_lock( &pb->pb_mutex );
+}
+
+static void
+pblock_unlock( Slapi_PBlock *pb )
+{
+ ldap_pvt_thread_mutex_unlock( &pb->pb_mutex );
+}
+
+static int
+pblock_get_default( Slapi_PBlock *pb, int param, void **value )
+{
+ int i;
+ slapi_pblock_class_t pbClass;
+
+ pbClass = pblock_get_param_class( param );
+ if ( pbClass == PBLOCK_CLASS_INVALID ) {
+ return PBLOCK_ERROR;
+ }
+
+ switch ( pbClass ) {
+ case PBLOCK_CLASS_INTEGER:
+ *((int *)value) = 0;
+ break;
+ case PBLOCK_CLASS_LONG_INTEGER:
+ *((long *)value) = 0L;
+ break;
+ case PBLOCK_CLASS_POINTER:
+ case PBLOCK_CLASS_FUNCTION_POINTER:
+ *value = NULL;
+ break;
+ case PBLOCK_CLASS_INVALID:
+ return PBLOCK_ERROR;
+ }
+
+ for ( i = 0; i < pb->pb_nParams; i++ ) {
+ if ( pb->pb_params[i] == param ) {
+ switch ( pbClass ) {
+ case PBLOCK_CLASS_INTEGER:
+ *((int *)value) = pb->pb_values[i].pv_integer;
+ break;
+ case PBLOCK_CLASS_LONG_INTEGER:
+ *((long *)value) = pb->pb_values[i].pv_long_integer;
+ break;
+ case PBLOCK_CLASS_POINTER:
+ *value = pb->pb_values[i].pv_pointer;
+ break;
+ case PBLOCK_CLASS_FUNCTION_POINTER:
+ *value = pb->pb_values[i].pv_function_pointer;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ return PBLOCK_SUCCESS;
+}
+
+static char *
+pblock_get_authtype( AuthorizationInformation *authz, int is_tls )
+{
+ char *authType;
+
+ switch ( authz->sai_method ) {
+ case LDAP_AUTH_SASL:
+ authType = SLAPD_AUTH_SASL;
+ break;
+ case LDAP_AUTH_SIMPLE:
+ authType = SLAPD_AUTH_SIMPLE;
+ break;
+ case LDAP_AUTH_NONE:
+ authType = SLAPD_AUTH_NONE;
+ break;
+ default:
+ authType = NULL;
+ break;
+ }
+
+ if ( is_tls && authType == NULL ) {
+ authType = SLAPD_AUTH_SSL;
+ }
+
+ return authType;
+}
+
+static int
+pblock_set_default( Slapi_PBlock *pb, int param, void *value )
+{
+ slapi_pblock_class_t pbClass;
+ int i;
+
+ pbClass = pblock_get_param_class( param );
+ if ( pbClass == PBLOCK_CLASS_INVALID ) {
+ return PBLOCK_ERROR;
+ }
+
+ if ( pb->pb_nParams == PBLOCK_MAX_PARAMS ) {
+ return PBLOCK_ERROR;
+ }
+
+ for ( i = 0; i < pb->pb_nParams; i++ ) {
+ if ( pb->pb_params[i] == param )
+ break;
+ }
+ if ( i >= pb->pb_nParams ) {
+ pb->pb_params[i] = param;
+ pb->pb_nParams++;
+ }
+
+ switch ( pbClass ) {
+ case PBLOCK_CLASS_INTEGER:
+ pb->pb_values[i].pv_integer = (*((int *)value));
+ break;
+ case PBLOCK_CLASS_LONG_INTEGER:
+ pb->pb_values[i].pv_long_integer = (*((long *)value));
+ break;
+ case PBLOCK_CLASS_POINTER:
+ pb->pb_values[i].pv_pointer = value;
+ break;
+ case PBLOCK_CLASS_FUNCTION_POINTER:
+ pb->pb_values[i].pv_function_pointer = value;
+ break;
+ default:
+ break;
+ }
+
+ return PBLOCK_SUCCESS;
+}
+
+static int
+pblock_be_call( Slapi_PBlock *pb, int (*bep)(Operation *) )
+{
+ BackendDB *be_orig;
+ Operation *op;
+ int rc;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ op = pb->pb_op;
+
+ be_orig = op->o_bd;
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ rc = (*bep)( op );
+ op->o_bd = be_orig;
+
+ return rc;
+}
+
+static int
+pblock_get( Slapi_PBlock *pb, int param, void **value )
+{
+ int rc = PBLOCK_SUCCESS;
+
+ pblock_lock( pb );
+
+ switch ( param ) {
+ case SLAPI_OPERATION:
+ *value = pb->pb_op;
+ break;
+ case SLAPI_OPINITIATED_TIME:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((long *)value) = pb->pb_op->o_time;
+ break;
+ case SLAPI_OPERATION_ID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((long *)value) = pb->pb_op->o_opid;
+ break;
+ case SLAPI_OPERATION_TYPE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((ber_tag_t *)value) = pb->pb_op->o_tag;
+ break;
+ case SLAPI_OPERATION_MSGID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((long *)value) = pb->pb_op->o_msgid;
+ break;
+ case SLAPI_X_OPERATION_DELETE_GLUE_PARENT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = pb->pb_op->o_delete_glue_parent;
+ break;
+ case SLAPI_X_OPERATION_NO_SCHEMA_CHECK:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = get_no_schema_check( pb->pb_op );
+ break;
+ case SLAPI_X_ADD_STRUCTURAL_CLASS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) {
+ struct berval tmpval = BER_BVNULL;
+
+ rc = mods_structural_class( pb->pb_op->ora_modlist,
+ &tmpval, &pb->pb_rs->sr_text,
+ pb->pb_textbuf, sizeof( pb->pb_textbuf ),
+ pb->pb_op->o_tmpmemctx );
+ *((char **)value) = tmpval.bv_val;
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_X_OPERATION_NO_SUBORDINATE_GLUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = pb->pb_op->o_no_subordinate_glue;
+ break;
+ case SLAPI_REQCONTROLS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((LDAPControl ***)value) = pb->pb_op->o_ctrls;
+ break;
+ case SLAPI_REQUESTOR_DN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((char **)value) = pb->pb_op->o_dn.bv_val;
+ break;
+ case SLAPI_MANAGEDSAIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = get_manageDSAit( pb->pb_op );
+ break;
+ case SLAPI_X_RELAX:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = get_relax( pb->pb_op );
+ break;
+ case SLAPI_BACKEND:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((BackendDB **)value) = select_backend( &pb->pb_op->o_req_ndn, 0 );
+ break;
+ case SLAPI_BE_TYPE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_bd != NULL )
+ *((char **)value) = pb->pb_op->o_bd->bd_info->bi_type;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_CONNECTION:
+ *value = pb->pb_conn;
+ break;
+ case SLAPI_X_CONN_SSF:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((slap_ssf_t *)value) = pb->pb_conn->c_ssf;
+ break;
+ case SLAPI_X_CONN_SASL_CONTEXT:
+ PBLOCK_ASSERT_CONN( pb );
+ if ( pb->pb_conn->c_sasl_authctx != NULL )
+ *value = pb->pb_conn->c_sasl_authctx;
+ else
+ *value = pb->pb_conn->c_sasl_sockctx;
+ break;
+ case SLAPI_TARGET_DN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((char **)value) = pb->pb_op->o_req_dn.bv_val;
+ break;
+ case SLAPI_REQUESTOR_ISROOT:
+ *((int *)value) = pblock_be_call( pb, be_isroot );
+ break;
+ case SLAPI_IS_REPLICATED_OPERATION:
+ *((int *)value) = pblock_be_call( pb, be_slurp_update );
+ break;
+ case SLAPI_CONN_AUTHTYPE:
+ case SLAPI_CONN_AUTHMETHOD: /* XXX should return SASL mech */
+ PBLOCK_ASSERT_CONN( pb );
+ *((char **)value) = pblock_get_authtype( &pb->pb_conn->c_authz,
+#ifdef HAVE_TLS
+ pb->pb_conn->c_is_tls
+#else
+ 0
+#endif
+ );
+ break;
+ case SLAPI_IS_INTERNAL_OPERATION:
+ *((int *)value) = pb->pb_intop;
+ break;
+ case SLAPI_X_CONN_IS_UDP:
+ PBLOCK_ASSERT_CONN( pb );
+#ifdef LDAP_CONNECTIONLESS
+ *((int *)value) = pb->pb_conn->c_is_udp;
+#else
+ *((int *)value) = 0;
+#endif
+ break;
+ case SLAPI_CONN_ID:
+ PBLOCK_ASSERT_CONN( pb );
+ *((long *)value) = pb->pb_conn->c_connid;
+ break;
+ case SLAPI_CONN_DN:
+ PBLOCK_ASSERT_CONN( pb );
+#if 0
+ /* This would be necessary to keep plugin compat after the fix in ITS#4158 */
+ if ( pb->pb_op->o_tag == LDAP_REQ_BIND && pb->pb_rs->sr_err == LDAP_SUCCESS )
+ *((char **)value) = pb->pb_op->orb_edn.bv_val;
+ else
+#endif
+ *((char **)value) = pb->pb_conn->c_dn.bv_val;
+ break;
+ case SLAPI_CONN_CLIENTIP:
+ PBLOCK_ASSERT_CONN( pb );
+ if ( strncmp( pb->pb_conn->c_peer_name.bv_val, "IP=", 3 ) == 0 )
+ *((char **)value) = &pb->pb_conn->c_peer_name.bv_val[3];
+ else
+ *value = NULL;
+ break;
+ case SLAPI_X_CONN_CLIENTPATH:
+ PBLOCK_ASSERT_CONN( pb );
+ if ( strncmp( pb->pb_conn->c_peer_name.bv_val, "PATH=", 3 ) == 0 )
+ *((char **)value) = &pb->pb_conn->c_peer_name.bv_val[5];
+ else
+ *value = NULL;
+ break;
+ case SLAPI_CONN_SERVERIP:
+ PBLOCK_ASSERT_CONN( pb );
+ if ( strncmp( pb->pb_conn->c_sock_name.bv_val, "IP=", 3 ) == 0 )
+ *((char **)value) = &pb->pb_conn->c_sock_name.bv_val[3];
+ else
+ *value = NULL;
+ break;
+ case SLAPI_X_CONN_SERVERPATH:
+ PBLOCK_ASSERT_CONN( pb );
+ if ( strncmp( pb->pb_conn->c_sock_name.bv_val, "PATH=", 3 ) == 0 )
+ *((char **)value) = &pb->pb_conn->c_sock_name.bv_val[5];
+ else
+ *value = NULL;
+ break;
+ case SLAPI_RESULT_CODE:
+ case SLAPI_PLUGIN_INTOP_RESULT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((int *)value) = pb->pb_rs->sr_err;
+ break;
+ case SLAPI_RESULT_TEXT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((const char **)value) = pb->pb_rs->sr_text;
+ break;
+ case SLAPI_RESULT_MATCHED:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((const char **)value) = pb->pb_rs->sr_matched;
+ break;
+ case SLAPI_ADD_ENTRY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_ADD )
+ *((Slapi_Entry **)value) = pb->pb_op->ora_e;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_MODIFY_MODS: {
+ LDAPMod **mods = NULL;
+ Modifications *ml = NULL;
+
+ pblock_get_default( pb, param, (void **)&mods );
+ if ( mods == NULL && pb->pb_intop == 0 ) {
+ switch ( pb->pb_op->o_tag ) {
+ case LDAP_REQ_MODIFY:
+ ml = pb->pb_op->orm_modlist;
+ break;
+ case LDAP_REQ_MODRDN:
+ ml = pb->pb_op->orr_modlist;
+ break;
+ default:
+ rc = PBLOCK_ERROR;
+ break;
+ }
+ if ( rc != PBLOCK_ERROR ) {
+ mods = slapi_int_modifications2ldapmods( ml );
+ pblock_set_default( pb, param, (void *)mods );
+ }
+ }
+ *((LDAPMod ***)value) = mods;
+ break;
+ }
+ case SLAPI_MODRDN_NEWRDN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN )
+ *((char **)value) = pb->pb_op->orr_newrdn.bv_val;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_MODRDN_NEWSUPERIOR:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN && pb->pb_op->orr_newSup != NULL )
+ *((char **)value) = pb->pb_op->orr_newSup->bv_val;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_MODRDN_DELOLDRDN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN )
+ *((int *)value) = pb->pb_op->orr_deleteoldrdn;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_SCOPE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((int *)value) = pb->pb_op->ors_scope;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_DEREF:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((int *)value) = pb->pb_op->ors_deref;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_SIZELIMIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((int *)value) = pb->pb_op->ors_slimit;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_TIMELIMIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((int *)value) = pb->pb_op->ors_tlimit;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_FILTER:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((Slapi_Filter **)value) = pb->pb_op->ors_filter;
+ else
+ *((Slapi_Filter **)value) = NULL;
+ break;
+ case SLAPI_SEARCH_STRFILTER:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((char **)value) = pb->pb_op->ors_filterstr.bv_val;
+ else
+ *((char **)value) = NULL;
+ break;
+ case SLAPI_SEARCH_ATTRS: {
+ char **attrs = NULL;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag != LDAP_REQ_SEARCH ) {
+ rc = PBLOCK_ERROR;
+ break;
+ }
+ pblock_get_default( pb, param, (void **)&attrs );
+ if ( attrs == NULL && pb->pb_intop == 0 ) {
+ attrs = anlist2charray_x( pb->pb_op->ors_attrs, 0, pb->pb_op->o_tmpmemctx );
+ pblock_set_default( pb, param, (void *)attrs );
+ }
+ *((char ***)value) = attrs;
+ break;
+ }
+ case SLAPI_SEARCH_ATTRSONLY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ *((int *)value) = pb->pb_op->ors_attrsonly;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_SEARCH_RESULT_ENTRY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((Slapi_Entry **)value) = pb->pb_rs->sr_entry;
+ break;
+ case SLAPI_BIND_RET_SASLCREDS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((struct berval **)value) = pb->pb_rs->sr_sasldata;
+ break;
+ case SLAPI_EXT_OP_REQ_OID:
+ *((const char **)value) = pb->pb_op->ore_reqoid.bv_val;
+ break;
+ case SLAPI_EXT_OP_REQ_VALUE:
+ *((struct berval **)value) = pb->pb_op->ore_reqdata;
+ break;
+ case SLAPI_EXT_OP_RET_OID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((const char **)value) = pb->pb_rs->sr_rspoid;
+ break;
+ case SLAPI_EXT_OP_RET_VALUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ *((struct berval **)value) = pb->pb_rs->sr_rspdata;
+ break;
+ case SLAPI_BIND_METHOD:
+ if ( pb->pb_op->o_tag == LDAP_REQ_BIND )
+ *((int *)value) = pb->pb_op->orb_method;
+ else
+ *((int *)value) = 0;
+ break;
+ case SLAPI_BIND_CREDENTIALS:
+ if ( pb->pb_op->o_tag == LDAP_REQ_BIND )
+ *((struct berval **)value) = &pb->pb_op->orb_cred;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_COMPARE_TYPE:
+ if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE )
+ *((char **)value) = pb->pb_op->orc_ava->aa_desc->ad_cname.bv_val;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_COMPARE_VALUE:
+ if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE )
+ *((struct berval **)value) = &pb->pb_op->orc_ava->aa_value;
+ else
+ *value = NULL;
+ break;
+ case SLAPI_ABANDON_MSGID:
+ if ( pb->pb_op->o_tag == LDAP_REQ_ABANDON )
+ *((int *)value) = pb->pb_op->orn_msgid;
+ else
+ *((int *)value) = 0;
+ break;
+ default:
+ rc = pblock_get_default( pb, param, value );
+ break;
+ }
+
+ pblock_unlock( pb );
+
+ return rc;
+}
+
+static int
+pblock_add_control( Slapi_PBlock *pb, LDAPControl *control )
+{
+ LDAPControl **controls = NULL;
+ size_t i;
+
+ pblock_get_default( pb, SLAPI_RESCONTROLS, (void **)&controls );
+
+ if ( controls != NULL ) {
+ for ( i = 0; controls[i] != NULL; i++ )
+ ;
+ } else {
+ i = 0;
+ }
+
+ controls = (LDAPControl **)slapi_ch_realloc( (char *)controls,
+ ( i + 2 ) * sizeof(LDAPControl *));
+ controls[i++] = slapi_dup_control( control );
+ controls[i] = NULL;
+
+ return pblock_set_default( pb, SLAPI_RESCONTROLS, (void *)controls );
+}
+
+static int
+pblock_set_dn( void *value, struct berval *dn, struct berval *ndn, void *memctx )
+{
+ struct berval bv;
+
+ if ( !BER_BVISNULL( dn )) {
+ slap_sl_free( dn->bv_val, memctx );
+ BER_BVZERO( dn );
+ }
+ if ( !BER_BVISNULL( ndn )) {
+ slap_sl_free( ndn->bv_val, memctx );
+ BER_BVZERO( ndn );
+ }
+
+ bv.bv_val = (char *)value;
+ bv.bv_len = ( value != NULL ) ? strlen( bv.bv_val ) : 0;
+
+ return dnPrettyNormal( NULL, &bv, dn, ndn, memctx );
+}
+
+static int
+pblock_set( Slapi_PBlock *pb, int param, void *value )
+{
+ int rc = PBLOCK_SUCCESS;
+
+ pblock_lock( pb );
+
+ switch ( param ) {
+ case SLAPI_OPERATION:
+ pb->pb_op = (Operation *)value;
+ break;
+ case SLAPI_OPINITIATED_TIME:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_time = *((long *)value);
+ break;
+ case SLAPI_OPERATION_ID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_opid = *((long *)value);
+ break;
+ case SLAPI_OPERATION_TYPE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_tag = *((ber_tag_t *)value);
+ break;
+ case SLAPI_OPERATION_MSGID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_msgid = *((long *)value);
+ break;
+ case SLAPI_X_OPERATION_DELETE_GLUE_PARENT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_delete_glue_parent = *((int *)value);
+ break;
+ case SLAPI_X_OPERATION_NO_SCHEMA_CHECK:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_no_schema_check = *((int *)value);
+ break;
+ case SLAPI_X_OPERATION_NO_SUBORDINATE_GLUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_no_subordinate_glue = *((int *)value);
+ break;
+ case SLAPI_REQCONTROLS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_ctrls = (LDAPControl **)value;
+ break;
+ case SLAPI_RESCONTROLS: {
+ LDAPControl **ctrls = NULL;
+
+ pblock_get_default( pb, param, (void **)&ctrls );
+ if ( ctrls != NULL ) {
+ /* free old ones first */
+ ldap_controls_free( ctrls );
+ }
+ rc = pblock_set_default( pb, param, value );
+ break;
+ }
+ case SLAPI_ADD_RESCONTROL:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ rc = pblock_add_control( pb, (LDAPControl *)value );
+ break;
+ case SLAPI_REQUESTOR_DN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ rc = pblock_set_dn( value, &pb->pb_op->o_dn, &pb->pb_op->o_ndn, pb->pb_op->o_tmpmemctx );
+ break;
+ case SLAPI_MANAGEDSAIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_managedsait = *((int *)value);
+ break;
+ case SLAPI_X_RELAX:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_relax = *((int *)value);
+ break;
+ case SLAPI_BACKEND:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_op->o_bd = (BackendDB *)value;
+ break;
+ case SLAPI_CONNECTION:
+ pb->pb_conn = (Connection *)value;
+ break;
+ case SLAPI_X_CONN_SSF:
+ PBLOCK_ASSERT_CONN( pb );
+ PBLOCK_LOCK_CONN( pb );
+ pb->pb_conn->c_ssf = (slap_ssf_t)(long)value;
+ PBLOCK_UNLOCK_CONN( pb );
+ break;
+ case SLAPI_X_CONN_SASL_CONTEXT:
+ PBLOCK_ASSERT_CONN( pb );
+ PBLOCK_LOCK_CONN( pb );
+ pb->pb_conn->c_sasl_authctx = value;
+ PBLOCK_UNLOCK_CONN( pb );
+ break;
+ case SLAPI_TARGET_DN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ rc = pblock_set_dn( value, &pb->pb_op->o_req_dn, &pb->pb_op->o_req_ndn, pb->pb_op->o_tmpmemctx );
+ break;
+ case SLAPI_CONN_ID:
+ PBLOCK_ASSERT_CONN( pb );
+ PBLOCK_LOCK_CONN( pb );
+ pb->pb_conn->c_connid = *((long *)value);
+ PBLOCK_UNLOCK_CONN( pb );
+ break;
+ case SLAPI_CONN_DN:
+ PBLOCK_ASSERT_CONN( pb );
+ PBLOCK_LOCK_CONN( pb );
+ rc = pblock_set_dn( value, &pb->pb_conn->c_dn, &pb->pb_conn->c_ndn, NULL );
+ PBLOCK_UNLOCK_CONN( pb );
+ break;
+ case SLAPI_RESULT_CODE:
+ case SLAPI_PLUGIN_INTOP_RESULT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_rs->sr_err = *((int *)value);
+ break;
+ case SLAPI_RESULT_TEXT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ snprintf( pb->pb_textbuf, sizeof( pb->pb_textbuf ), "%s", (char *)value );
+ pb->pb_rs->sr_text = pb->pb_textbuf;
+ break;
+ case SLAPI_RESULT_MATCHED:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_rs->sr_matched = (char *)value; /* XXX should dup? */
+ break;
+ case SLAPI_ADD_ENTRY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_ADD )
+ pb->pb_op->ora_e = (Slapi_Entry *)value;
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_MODIFY_MODS: {
+ Modifications **mlp;
+ Modifications *newmods;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ rc = pblock_set_default( pb, param, value );
+ if ( rc != PBLOCK_SUCCESS ) {
+ break;
+ }
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODIFY ) {
+ mlp = &pb->pb_op->orm_modlist;
+ } else if ( pb->pb_op->o_tag == LDAP_REQ_ADD ) {
+ mlp = &pb->pb_op->ora_modlist;
+ } else if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) {
+ mlp = &pb->pb_op->orr_modlist;
+ } else {
+ break;
+ }
+
+ newmods = slapi_int_ldapmods2modifications( pb->pb_op, (LDAPMod **)value );
+ if ( newmods != NULL ) {
+ slap_mods_free( *mlp, 1 );
+ *mlp = newmods;
+ }
+ break;
+ }
+ case SLAPI_MODRDN_NEWRDN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) {
+ rc = pblock_set_dn( value, &pb->pb_op->orr_newrdn, &pb->pb_op->orr_nnewrdn, pb->pb_op->o_tmpmemctx );
+ if ( rc == LDAP_SUCCESS )
+ rc = rdn_validate( &pb->pb_op->orr_nnewrdn );
+ if ( rc == LDAP_SUCCESS ) {
+ struct berval pdn, pndn;
+ if ( pb->pb_op->orr_nnewSup ) {
+ pdn = *pb->pb_op->orr_newSup;
+ pndn = *pb->pb_op->orr_nnewSup;
+ } else {
+ dnParent( &pb->pb_op->o_req_dn, &pdn );
+ dnParent( &pb->pb_op->o_req_ndn, &pndn );
+ }
+ build_new_dn( &pb->pb_op->orr_newDN, &pdn, &pb->pb_op->orr_newrdn, pb->pb_op->o_tmpmemctx );
+ build_new_dn( &pb->pb_op->orr_nnewDN, &pndn, &pb->pb_op->orr_nnewrdn, pb->pb_op->o_tmpmemctx );
+ }
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_MODRDN_NEWSUPERIOR:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN ) {
+ if ( value == NULL ) {
+ struct berval pdn, pndn;
+ if ( pb->pb_op->orr_newSup != NULL ) {
+ pb->pb_op->o_tmpfree( pb->pb_op->orr_newSup, pb->pb_op->o_tmpmemctx );
+ BER_BVZERO( pb->pb_op->orr_newSup );
+ pb->pb_op->orr_newSup = NULL;
+ }
+ if ( pb->pb_op->orr_newSup != NULL ) {
+ pb->pb_op->o_tmpfree( pb->pb_op->orr_nnewSup, pb->pb_op->o_tmpmemctx );
+ BER_BVZERO( pb->pb_op->orr_nnewSup );
+ pb->pb_op->orr_nnewSup = NULL;
+ }
+ dnParent( &pb->pb_op->o_req_dn, &pdn );
+ build_new_dn( &pb->pb_op->orr_newDN, &pdn, &pb->pb_op->orr_newrdn, pb->pb_op->o_tmpmemctx );
+ dnParent( &pb->pb_op->o_req_ndn, &pndn );
+ build_new_dn( &pb->pb_op->orr_nnewDN, &pndn, &pb->pb_op->orr_nnewrdn, pb->pb_op->o_tmpmemctx );
+ } else {
+ if ( pb->pb_op->orr_newSup == NULL ) {
+ pb->pb_op->orr_newSup = (struct berval *)pb->pb_op->o_tmpalloc(
+ sizeof(struct berval), pb->pb_op->o_tmpmemctx );
+ BER_BVZERO( pb->pb_op->orr_newSup );
+ }
+ if ( pb->pb_op->orr_nnewSup == NULL ) {
+ pb->pb_op->orr_nnewSup = (struct berval *)pb->pb_op->o_tmpalloc(
+ sizeof(struct berval), pb->pb_op->o_tmpmemctx );
+ BER_BVZERO( pb->pb_op->orr_nnewSup );
+ }
+ rc = pblock_set_dn( value, pb->pb_op->orr_newSup, pb->pb_op->orr_nnewSup, pb->pb_op->o_tmpmemctx );
+ if ( rc == LDAP_SUCCESS ) {
+ build_new_dn( &pb->pb_op->orr_newDN, pb->pb_op->orr_newSup, &pb->pb_op->orr_newrdn, pb->pb_op->o_tmpmemctx );
+ build_new_dn( &pb->pb_op->orr_nnewDN, pb->pb_op->orr_nnewSup, &pb->pb_op->orr_nnewrdn, pb->pb_op->o_tmpmemctx );
+ }
+ }
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_MODRDN_DELOLDRDN:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+ if ( pb->pb_op->o_tag == LDAP_REQ_MODRDN )
+ pb->pb_op->orr_deleteoldrdn = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_SCOPE: {
+ int scope = *((int *)value);
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) {
+ switch ( *((int *)value) ) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ pb->pb_op->ors_scope = scope;
+ break;
+ default:
+ rc = PBLOCK_ERROR;
+ break;
+ }
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ }
+ case SLAPI_SEARCH_DEREF:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ pb->pb_op->ors_deref = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_SIZELIMIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ pb->pb_op->ors_slimit = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_TIMELIMIT:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ pb->pb_op->ors_tlimit = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_FILTER:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ pb->pb_op->ors_filter = (Slapi_Filter *)value;
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_STRFILTER:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH ) {
+ pb->pb_op->ors_filterstr.bv_val = (char *)value;
+ pb->pb_op->ors_filterstr.bv_len = strlen((char *)value);
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_SEARCH_ATTRS: {
+ AttributeName *an = NULL;
+ size_t i = 0, j = 0;
+ char **attrs = (char **)value;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag != LDAP_REQ_SEARCH ) {
+ rc = PBLOCK_ERROR;
+ break;
+ }
+ /* also set mapped attrs */
+ rc = pblock_set_default( pb, param, value );
+ if ( rc != PBLOCK_SUCCESS ) {
+ break;
+ }
+ if ( pb->pb_op->ors_attrs != NULL ) {
+ pb->pb_op->o_tmpfree( pb->pb_op->ors_attrs, pb->pb_op->o_tmpmemctx );
+ pb->pb_op->ors_attrs = NULL;
+ }
+ if ( attrs != NULL ) {
+ for ( i = 0; attrs[i] != NULL; i++ )
+ ;
+ }
+ if ( i ) {
+ an = (AttributeName *)pb->pb_op->o_tmpcalloc( i + 1,
+ sizeof(AttributeName), pb->pb_op->o_tmpmemctx );
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ an[j].an_desc = NULL;
+ an[j].an_oc = NULL;
+ an[j].an_flags = 0;
+ an[j].an_name.bv_val = attrs[i];
+ an[j].an_name.bv_len = strlen( attrs[i] );
+ if ( slap_bv2ad( &an[j].an_name, &an[j].an_desc, &pb->pb_rs->sr_text ) == LDAP_SUCCESS ) {
+ j++;
+ }
+ }
+ an[j].an_name.bv_val = NULL;
+ an[j].an_name.bv_len = 0;
+ }
+ pb->pb_op->ors_attrs = an;
+ break;
+ }
+ case SLAPI_SEARCH_ATTRSONLY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ pb->pb_op->ors_attrsonly = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_SEARCH_RESULT_ENTRY:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ rs_replace_entry( pb->pb_op, pb->pb_rs, NULL, (Slapi_Entry *)value );
+ /* TODO: Should REP_ENTRY_MODIFIABLE be set? */
+ pb->pb_rs->sr_flags |= REP_ENTRY_MUSTBEFREED;
+ break;
+ case SLAPI_BIND_RET_SASLCREDS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_rs->sr_sasldata = (struct berval *)value;
+ break;
+ case SLAPI_EXT_OP_REQ_OID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_EXTENDED ) {
+ pb->pb_op->ore_reqoid.bv_val = (char *)value;
+ pb->pb_op->ore_reqoid.bv_len = strlen((char *)value);
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_EXT_OP_REQ_VALUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_EXTENDED )
+ pb->pb_op->ore_reqdata = (struct berval *)value;
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_EXT_OP_RET_OID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_rs->sr_rspoid = (char *)value;
+ break;
+ case SLAPI_EXT_OP_RET_VALUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ pb->pb_rs->sr_rspdata = (struct berval *)value;
+ break;
+ case SLAPI_BIND_METHOD:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_BIND )
+ pb->pb_op->orb_method = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_BIND_CREDENTIALS:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_BIND )
+ pb->pb_op->orb_cred = *((struct berval *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_COMPARE_TYPE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE ) {
+ const char *text;
+
+ pb->pb_op->orc_ava->aa_desc = NULL;
+ rc = slap_str2ad( (char *)value, &pb->pb_op->orc_ava->aa_desc, &text );
+ } else {
+ rc = PBLOCK_ERROR;
+ }
+ break;
+ case SLAPI_COMPARE_VALUE:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_COMPARE )
+ pb->pb_op->orc_ava->aa_value = *((struct berval *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_ABANDON_MSGID:
+ PBLOCK_ASSERT_OP( pb, 0 );
+ PBLOCK_VALIDATE_IS_INTOP( pb );
+
+ if ( pb->pb_op->o_tag == LDAP_REQ_ABANDON)
+ pb->pb_op->orn_msgid = *((int *)value);
+ else
+ rc = PBLOCK_ERROR;
+ break;
+ case SLAPI_REQUESTOR_ISROOT:
+ case SLAPI_IS_REPLICATED_OPERATION:
+ case SLAPI_CONN_AUTHTYPE:
+ case SLAPI_CONN_AUTHMETHOD:
+ case SLAPI_IS_INTERNAL_OPERATION:
+ case SLAPI_X_CONN_IS_UDP:
+ case SLAPI_CONN_CLIENTIP:
+ case SLAPI_X_CONN_CLIENTPATH:
+ case SLAPI_CONN_SERVERIP:
+ case SLAPI_X_CONN_SERVERPATH:
+ case SLAPI_X_ADD_STRUCTURAL_CLASS:
+ /* These parameters cannot be set */
+ rc = PBLOCK_ERROR;
+ break;
+ default:
+ rc = pblock_set_default( pb, param, value );
+ break;
+ }
+
+ pblock_unlock( pb );
+
+ return rc;
+}
+
+static void
+pblock_clear( Slapi_PBlock *pb )
+{
+ pb->pb_nParams = 1;
+}
+
+static int
+pblock_delete_param( Slapi_PBlock *p, int param )
+{
+ int i;
+
+ pblock_lock(p);
+
+ for ( i = 0; i < p->pb_nParams; i++ ) {
+ if ( p->pb_params[i] == param ) {
+ break;
+ }
+ }
+
+ if (i >= p->pb_nParams ) {
+ pblock_unlock( p );
+ return PBLOCK_ERROR;
+ }
+
+ /* move last parameter to index of deleted parameter */
+ if ( p->pb_nParams > 1 ) {
+ p->pb_params[i] = p->pb_params[p->pb_nParams - 1];
+ p->pb_values[i] = p->pb_values[p->pb_nParams - 1];
+ }
+ p->pb_nParams--;
+
+ pblock_unlock( p );
+
+ return PBLOCK_SUCCESS;
+}
+
+Slapi_PBlock *
+slapi_pblock_new(void)
+{
+ Slapi_PBlock *pb;
+
+ pb = (Slapi_PBlock *) ch_calloc( 1, sizeof(Slapi_PBlock) );
+ if ( pb != NULL ) {
+ ldap_pvt_thread_mutex_init( &pb->pb_mutex );
+
+ pb->pb_params[0] = SLAPI_IBM_PBLOCK;
+ pb->pb_values[0].pv_pointer = NULL;
+ pb->pb_nParams = 1;
+ pb->pb_conn = NULL;
+ pb->pb_op = NULL;
+ pb->pb_rs = NULL;
+ pb->pb_intop = 0;
+ }
+ return pb;
+}
+
+static void
+pblock_destroy( Slapi_PBlock *pb )
+{
+ LDAPControl **controls = NULL;
+ LDAPMod **mods = NULL;
+ char **attrs = NULL;
+
+ assert( pb != NULL );
+
+ pblock_get_default( pb, SLAPI_RESCONTROLS, (void **)&controls );
+ if ( controls != NULL ) {
+ ldap_controls_free( controls );
+ }
+
+ if ( pb->pb_intop ) {
+ slapi_int_connection_done_pb( pb );
+ } else {
+ pblock_get_default( pb, SLAPI_MODIFY_MODS, (void **)&mods );
+ ldap_mods_free( mods, 1 );
+
+ pblock_get_default( pb, SLAPI_SEARCH_ATTRS, (void **)&attrs );
+ if ( attrs != NULL )
+ pb->pb_op->o_tmpfree( attrs, pb->pb_op->o_tmpmemctx );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &pb->pb_mutex );
+ slapi_ch_free( (void **)&pb );
+}
+
+void
+slapi_pblock_destroy( Slapi_PBlock *pb )
+{
+ if ( pb != NULL ) {
+ pblock_destroy( pb );
+ }
+}
+
+int
+slapi_pblock_get( Slapi_PBlock *pb, int arg, void *value )
+{
+ return pblock_get( pb, arg, (void **)value );
+}
+
+int
+slapi_pblock_set( Slapi_PBlock *pb, int arg, void *value )
+{
+ return pblock_set( pb, arg, value );
+}
+
+void
+slapi_pblock_clear( Slapi_PBlock *pb )
+{
+ pblock_clear( pb );
+}
+
+int
+slapi_pblock_delete_param( Slapi_PBlock *p, int param )
+{
+ return pblock_delete_param( p, param );
+}
+
+/*
+ * OpenLDAP extension
+ */
+int
+slapi_int_pblock_get_first( Backend *be, Slapi_PBlock **pb )
+{
+ assert( pb != NULL );
+ *pb = SLAPI_BACKEND_PBLOCK( be );
+ return (*pb == NULL ? LDAP_OTHER : LDAP_SUCCESS);
+}
+
+/*
+ * OpenLDAP extension
+ */
+int
+slapi_int_pblock_get_next( Slapi_PBlock **pb )
+{
+ assert( pb != NULL );
+ return slapi_pblock_get( *pb, SLAPI_IBM_PBLOCK, pb );
+}
+
+#endif /* LDAP_SLAPI */
diff --git a/servers/slapd/slapi/slapi_utils.c b/servers/slapd/slapi/slapi_utils.c
new file mode 100644
index 0000000..c459122
--- /dev/null
+++ b/servers/slapd/slapi/slapi_utils.c
@@ -0,0 +1,3479 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2002-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1997,2002-2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by IBM Corporation for use in
+ * IBM products and subsequently ported to OpenLDAP Software by
+ * Steve Omrani. Additional significant contributors include:
+ * Luke Howard
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <ac/stdarg.h>
+#include <ac/ctype.h>
+#include <ac/unistd.h>
+#include <lutil.h>
+
+#include <slap.h>
+#include <slapi.h>
+
+#ifdef _WIN32
+#include <winsock.h>
+#else
+#include <netdb.h>
+#endif
+
+#ifdef LDAP_SLAPI
+
+/*
+ * server start time (should we use a struct timeval also in slapd?
+ */
+static struct timeval base_time;
+ldap_pvt_thread_mutex_t slapi_hn_mutex;
+ldap_pvt_thread_mutex_t slapi_time_mutex;
+
+struct slapi_mutex {
+ ldap_pvt_thread_mutex_t mutex;
+};
+
+struct slapi_condvar {
+ ldap_pvt_thread_cond_t cond;
+ ldap_pvt_thread_mutex_t mutex;
+};
+
+static int checkBVString(const struct berval *bv)
+{
+ ber_len_t i;
+
+ for ( i = 0; i < bv->bv_len; i++ ) {
+ if ( bv->bv_val[i] == '\0' )
+ return 0;
+ }
+ if ( bv->bv_val[i] != '\0' )
+ return 0;
+
+ return 1;
+}
+
+/*
+ * This function converts an array of pointers to berval objects to
+ * an array of berval objects.
+ */
+
+int
+bvptr2obj(
+ struct berval **bvptr,
+ BerVarray *bvobj,
+ unsigned *num )
+{
+ int rc = LDAP_SUCCESS;
+ int i;
+ BerVarray tmpberval;
+
+ if ( bvptr == NULL || *bvptr == NULL ) {
+ return LDAP_OTHER;
+ }
+
+ for ( i = 0; bvptr != NULL && bvptr[i] != NULL; i++ ) {
+ ; /* EMPTY */
+ }
+ if ( num )
+ *num = i;
+
+ tmpberval = (BerVarray)slapi_ch_malloc( (i + 1)*sizeof(struct berval));
+ if ( tmpberval == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = 0; bvptr[i] != NULL; i++ ) {
+ tmpberval[i].bv_val = bvptr[i]->bv_val;
+ tmpberval[i].bv_len = bvptr[i]->bv_len;
+ }
+ tmpberval[i].bv_val = NULL;
+ tmpberval[i].bv_len = 0;
+
+ if ( rc == LDAP_SUCCESS ) {
+ *bvobj = tmpberval;
+ }
+
+ return rc;
+}
+
+Slapi_Entry *
+slapi_str2entry(
+ char *s,
+ int flags )
+{
+ return str2entry( s );
+}
+
+char *
+slapi_entry2str(
+ Slapi_Entry *e,
+ int *len )
+{
+ char *ret = NULL;
+ char *s;
+
+ ldap_pvt_thread_mutex_lock( &entry2str_mutex );
+ s = entry2str( e, len );
+ if ( s != NULL )
+ ret = slapi_ch_strdup( s );
+ ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
+
+ return ret;
+}
+
+char *
+slapi_entry_get_dn( Slapi_Entry *e )
+{
+ return e->e_name.bv_val;
+}
+
+int
+slapi_x_entry_get_id( Slapi_Entry *e )
+{
+ return e->e_id;
+}
+
+static int
+slapi_int_dn_pretty( struct berval *in, struct berval *out )
+{
+ Syntax *syntax = slap_schema.si_syn_distinguishedName;
+
+ assert( syntax != NULL );
+
+ return (syntax->ssyn_pretty)( syntax, in, out, NULL );
+}
+
+static int
+slapi_int_dn_normalize( struct berval *in, struct berval *out )
+{
+ MatchingRule *mr = slap_schema.si_mr_distinguishedNameMatch;
+ Syntax *syntax = slap_schema.si_syn_distinguishedName;
+
+ assert( mr != NULL );
+
+ return (mr->smr_normalize)( 0, syntax, mr, in, out, NULL );
+}
+
+void
+slapi_entry_set_dn(
+ Slapi_Entry *e,
+ char *ldn )
+{
+ struct berval dn = BER_BVNULL;
+
+ dn.bv_val = ldn;
+ dn.bv_len = strlen( ldn );
+
+ slapi_int_dn_pretty( &dn, &e->e_name );
+ slapi_int_dn_normalize( &dn, &e->e_nname );
+}
+
+Slapi_Entry *
+slapi_entry_dup( Slapi_Entry *e )
+{
+ return entry_dup( e );
+}
+
+int
+slapi_entry_attr_delete(
+ Slapi_Entry *e,
+ char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ if ( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) {
+ return 1; /* LDAP_NO_SUCH_ATTRIBUTE */
+ }
+
+ if ( attr_delete( &e->e_attrs, ad ) == LDAP_SUCCESS ) {
+ return 0; /* attribute is deleted */
+ } else {
+ return -1; /* something went wrong */
+ }
+}
+
+Slapi_Entry *
+slapi_entry_alloc( void )
+{
+ return (Slapi_Entry *)entry_alloc();
+}
+
+void
+slapi_entry_free( Slapi_Entry *e )
+{
+ if ( e != NULL )
+ entry_free( e );
+}
+
+int
+slapi_entry_attr_merge(
+ Slapi_Entry *e,
+ char *type,
+ struct berval **vals )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ BerVarray bv;
+ int rc;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ rc = bvptr2obj( vals, &bv, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ rc = attr_merge_normalize( e, ad, bv, NULL );
+ ch_free( bv );
+
+ return rc;
+}
+
+int
+slapi_entry_attr_find(
+ Slapi_Entry *e,
+ char *type,
+ Slapi_Attr **attr )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ *attr = attr_find( e->e_attrs, ad );
+ if ( *attr == NULL ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+char *
+slapi_entry_attr_get_charptr( const Slapi_Entry *e, const char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return NULL;
+ }
+
+ if ( attr->a_vals != NULL && attr->a_vals[0].bv_len != 0 ) {
+ const char *p;
+
+ p = slapi_value_get_string( &attr->a_vals[0] );
+ if ( p != NULL ) {
+ return slapi_ch_strdup( p );
+ }
+ }
+
+ return NULL;
+}
+
+int
+slapi_entry_attr_get_int( const Slapi_Entry *e, const char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return 0;
+ }
+
+ return slapi_value_get_int( attr->a_vals );
+}
+
+long
+slapi_entry_attr_get_long( const Slapi_Entry *e, const char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return 0;
+ }
+
+ return slapi_value_get_long( attr->a_vals );
+}
+
+unsigned int
+slapi_entry_attr_get_uint( const Slapi_Entry *e, const char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return 0;
+ }
+
+ return slapi_value_get_uint( attr->a_vals );
+}
+
+unsigned long
+slapi_entry_attr_get_ulong( const Slapi_Entry *e, const char *type )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return 0;
+ }
+
+ return slapi_value_get_ulong( attr->a_vals );
+}
+
+int
+slapi_entry_attr_hasvalue( Slapi_Entry *e, const char *type, const char *value )
+{
+ struct berval bv;
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ Attribute *attr;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr = attr_find( e->e_attrs, ad );
+ if ( attr == NULL ) {
+ return 0;
+ }
+
+ bv.bv_val = (char *)value;
+ bv.bv_len = strlen( value );
+
+ return ( slapi_attr_value_find( attr, &bv ) != -1 );
+}
+
+void
+slapi_entry_attr_set_charptr(Slapi_Entry* e, const char *type, const char *value)
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ struct berval bv;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return;
+ }
+
+ attr_delete ( &e->e_attrs, ad );
+ if ( value != NULL ) {
+ bv.bv_val = (char *)value;
+ bv.bv_len = strlen(value);
+ attr_merge_normalize_one( e, ad, &bv, NULL );
+ }
+}
+
+void
+slapi_entry_attr_set_int( Slapi_Entry* e, const char *type, int l)
+{
+ char buf[64];
+
+ snprintf( buf, sizeof( buf ), "%d", l );
+ slapi_entry_attr_set_charptr( e, type, buf );
+}
+
+void
+slapi_entry_attr_set_uint( Slapi_Entry* e, const char *type, unsigned int l)
+{
+ char buf[64];
+
+ snprintf( buf, sizeof( buf ), "%u", l );
+ slapi_entry_attr_set_charptr( e, type, buf );
+}
+
+void
+slapi_entry_attr_set_long(Slapi_Entry* e, const char *type, long l)
+{
+ char buf[64];
+
+ snprintf( buf, sizeof( buf ), "%ld", l );
+ slapi_entry_attr_set_charptr( e, type, buf );
+}
+
+void
+slapi_entry_attr_set_ulong(Slapi_Entry* e, const char *type, unsigned long l)
+{
+ char buf[64];
+
+ snprintf( buf, sizeof( buf ), "%lu", l );
+ slapi_entry_attr_set_charptr( e, type, buf );
+}
+
+int
+slapi_is_rootdse( const char *dn )
+{
+ return ( dn == NULL || dn[0] == '\0' );
+}
+
+int
+slapi_entry_has_children( const Slapi_Entry *e )
+{
+ Slapi_PBlock *pb;
+ Backend *be = select_backend( (struct berval *)&e->e_nname, 0 );
+ int rc, hasSubordinates = 0;
+
+ if ( be == NULL || be->be_has_subordinates == 0 ) {
+ return 0;
+ }
+
+ pb = slapi_pblock_new();
+ if ( pb == NULL ) {
+ return 0;
+ }
+ slapi_int_connection_init_pb( pb, LDAP_REQ_SEARCH );
+
+ rc = slapi_pblock_set( pb, SLAPI_TARGET_DN, slapi_entry_get_dn(
+ (Entry *) e ));
+ if ( rc == LDAP_SUCCESS ) {
+ pb->pb_op->o_bd = be;
+ rc = be->be_has_subordinates( pb->pb_op, (Entry *) e,
+ &hasSubordinates );
+ }
+
+ slapi_pblock_destroy( pb );
+
+ return ( rc == LDAP_SUCCESS && hasSubordinates == LDAP_COMPARE_TRUE );
+}
+
+/*
+ * Return approximate size of the entry rounded to the nearest
+ * 1K. Only the size of the attribute values are counted in the
+ * Sun implementation.
+ *
+ * http://docs.sun.com/source/816-6701-10/funcref.html#1017388
+ */
+size_t slapi_entry_size(Slapi_Entry *e)
+{
+ size_t size;
+ Attribute *a;
+ int i;
+
+ for ( size = 0, a = e->e_attrs; a != NULL; a = a->a_next ) {
+ for ( i = 0; a->a_vals[i].bv_val != NULL; i++ ) {
+ size += a->a_vals[i].bv_len + 1;
+ }
+ }
+
+ size += 1023;
+ size -= (size % 1024);
+
+ return size;
+}
+
+/*
+ * Add values to entry.
+ *
+ * Returns:
+ * LDAP_SUCCESS Values added to entry
+ * LDAP_TYPE_OR_VALUE_EXISTS One or more values exist in entry already
+ * LDAP_CONSTRAINT_VIOLATION Any other error (odd, but it's the spec)
+ */
+int
+slapi_entry_add_values( Slapi_Entry *e, const char *type, struct berval **vals )
+{
+ Modification mod;
+ const char *text;
+ int rc;
+ char textbuf[SLAP_TEXT_BUFLEN];
+
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_flags = 0;
+ mod.sm_desc = NULL;
+ mod.sm_type.bv_val = (char *)type;
+ mod.sm_type.bv_len = strlen( type );
+
+ rc = slap_str2ad( type, &mod.sm_desc, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( vals == NULL ) {
+ /* Apparently vals can be NULL
+ * FIXME: sm_values = NULL ? */
+ mod.sm_values = (BerVarray)ch_malloc( sizeof(struct berval) );
+ mod.sm_values->bv_val = NULL;
+ mod.sm_numvals = 0;
+
+ } else {
+ rc = bvptr2obj( vals, &mod.sm_values, &mod.sm_numvals );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+ mod.sm_nvalues = NULL;
+
+ rc = modify_add_values( e, &mod, 0, &text, textbuf, sizeof(textbuf) );
+
+ slapi_ch_free( (void **)&mod.sm_values );
+
+ return (rc == LDAP_SUCCESS) ? LDAP_SUCCESS : LDAP_CONSTRAINT_VIOLATION;
+}
+
+int
+slapi_entry_add_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals )
+{
+ return slapi_entry_add_values( e, type, vals );
+}
+
+int
+slapi_entry_add_valueset(Slapi_Entry *e, const char *type, Slapi_ValueSet *vs)
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ return attr_merge_normalize( e, ad, *vs, NULL );
+}
+
+int
+slapi_entry_delete_values( Slapi_Entry *e, const char *type, struct berval **vals )
+{
+ Modification mod;
+ const char *text;
+ int rc;
+ char textbuf[SLAP_TEXT_BUFLEN];
+
+ mod.sm_op = LDAP_MOD_DELETE;
+ mod.sm_flags = 0;
+ mod.sm_desc = NULL;
+ mod.sm_type.bv_val = (char *)type;
+ mod.sm_type.bv_len = strlen( type );
+
+ if ( vals == NULL ) {
+ /* If vals is NULL, this is a NOOP. */
+ return LDAP_SUCCESS;
+ }
+
+ rc = slap_str2ad( type, &mod.sm_desc, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( vals[0] == NULL ) {
+ /* SLAPI doco says LDApb_opERATIONS_ERROR but LDAP_OTHER is better */
+ return attr_delete( &e->e_attrs, mod.sm_desc ) ? LDAP_OTHER : LDAP_SUCCESS;
+ }
+
+ rc = bvptr2obj( vals, &mod.sm_values, &mod.sm_numvals );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ mod.sm_nvalues = NULL;
+
+ rc = modify_delete_values( e, &mod, 0, &text, textbuf, sizeof(textbuf) );
+
+ slapi_ch_free( (void **)&mod.sm_values );
+
+ return rc;
+}
+
+int
+slapi_entry_delete_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals )
+{
+ return slapi_entry_delete_values( e, type, vals );
+}
+
+int
+slapi_entry_merge_values_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals )
+{
+ return slapi_entry_attr_merge( e, (char *)type, vals );
+}
+
+int
+slapi_entry_add_value(Slapi_Entry *e, const char *type, const Slapi_Value *value)
+{
+ AttributeDescription *ad = NULL;
+ int rc;
+ const char *text;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ rc = attr_merge_normalize_one( e, ad, (Slapi_Value *)value, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+slapi_entry_add_string(Slapi_Entry *e, const char *type, const char *value)
+{
+ Slapi_Value val;
+
+ val.bv_val = (char *)value;
+ val.bv_len = strlen( value );
+
+ return slapi_entry_add_value( e, type, &val );
+}
+
+int
+slapi_entry_delete_string(Slapi_Entry *e, const char *type, const char *value)
+{
+ Slapi_Value *vals[2];
+ Slapi_Value val;
+
+ val.bv_val = (char *)value;
+ val.bv_len = strlen( value );
+ vals[0] = &val;
+ vals[1] = NULL;
+
+ return slapi_entry_delete_values_sv( e, type, vals );
+}
+
+int
+slapi_entry_attr_merge_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals )
+{
+ return slapi_entry_attr_merge( e, (char *)type, vals );
+}
+
+int
+slapi_entry_first_attr( const Slapi_Entry *e, Slapi_Attr **attr )
+{
+ if ( e == NULL ) {
+ return -1;
+ }
+
+ *attr = e->e_attrs;
+
+ return ( *attr != NULL ) ? 0 : -1;
+}
+
+int
+slapi_entry_next_attr( const Slapi_Entry *e, Slapi_Attr *prevattr, Slapi_Attr **attr )
+{
+ if ( e == NULL ) {
+ return -1;
+ }
+
+ if ( prevattr == NULL ) {
+ return -1;
+ }
+
+ *attr = prevattr->a_next;
+
+ return ( *attr != NULL ) ? 0 : -1;
+}
+
+int
+slapi_entry_attr_replace_sv( Slapi_Entry *e, const char *type, Slapi_Value **vals )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+ BerVarray bv;
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ attr_delete( &e->e_attrs, ad );
+
+ rc = bvptr2obj( vals, &bv, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ rc = attr_merge_normalize( e, ad, bv, NULL );
+ slapi_ch_free( (void **)&bv );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * FIXME -- The caller must free the allocated memory.
+ * In Netscape they do not have to.
+ */
+int
+slapi_attr_get_values(
+ Slapi_Attr *attr,
+ struct berval ***vals )
+{
+ int i, j;
+ struct berval **bv;
+
+ if ( attr == NULL ) {
+ return 1;
+ }
+
+ for ( i = 0; attr->a_vals[i].bv_val != NULL; i++ ) {
+ ; /* EMPTY */
+ }
+
+ bv = (struct berval **)ch_malloc( (i + 1) * sizeof(struct berval *) );
+ for ( j = 0; j < i; j++ ) {
+ bv[j] = ber_dupbv( NULL, &attr->a_vals[j] );
+ }
+ bv[j] = NULL;
+
+ *vals = (struct berval **)bv;
+
+ return 0;
+}
+
+char *
+slapi_dn_normalize( char *dn )
+{
+ struct berval bdn;
+ struct berval pdn;
+
+ assert( dn != NULL );
+
+ bdn.bv_val = dn;
+ bdn.bv_len = strlen( dn );
+
+ if ( slapi_int_dn_pretty( &bdn, &pdn ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ return pdn.bv_val;
+}
+
+char *
+slapi_dn_normalize_case( char *dn )
+{
+ struct berval bdn;
+ struct berval ndn;
+
+ assert( dn != NULL );
+
+ bdn.bv_val = dn;
+ bdn.bv_len = strlen( dn );
+
+ if ( slapi_int_dn_normalize( &bdn, &ndn ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ return ndn.bv_val;
+}
+
+int
+slapi_dn_issuffix(
+ char *dn,
+ char *suffix )
+{
+ struct berval bdn, ndn;
+ struct berval bsuffix, nsuffix;
+ int rc;
+
+ assert( dn != NULL );
+ assert( suffix != NULL );
+
+ bdn.bv_val = dn;
+ bdn.bv_len = strlen( dn );
+
+ bsuffix.bv_val = suffix;
+ bsuffix.bv_len = strlen( suffix );
+
+ if ( dnNormalize( 0, NULL, NULL, &bdn, &ndn, NULL ) != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ if ( dnNormalize( 0, NULL, NULL, &bsuffix, &nsuffix, NULL )
+ != LDAP_SUCCESS )
+ {
+ slapi_ch_free( (void **)&ndn.bv_val );
+ return 0;
+ }
+
+ rc = dnIsSuffix( &ndn, &nsuffix );
+
+ slapi_ch_free( (void **)&ndn.bv_val );
+ slapi_ch_free( (void **)&nsuffix.bv_val );
+
+ return rc;
+}
+
+int
+slapi_dn_isparent(
+ const char *parentdn,
+ const char *childdn )
+{
+ struct berval assertedParentDN, normalizedAssertedParentDN;
+ struct berval childDN, normalizedChildDN;
+ struct berval normalizedParentDN;
+ int match;
+
+ assert( parentdn != NULL );
+ assert( childdn != NULL );
+
+ assertedParentDN.bv_val = (char *)parentdn;
+ assertedParentDN.bv_len = strlen( parentdn );
+
+ if ( dnNormalize( 0, NULL, NULL, &assertedParentDN,
+ &normalizedAssertedParentDN, NULL ) != LDAP_SUCCESS )
+ {
+ return 0;
+ }
+
+ childDN.bv_val = (char *)childdn;
+ childDN.bv_len = strlen( childdn );
+
+ if ( dnNormalize( 0, NULL, NULL, &childDN,
+ &normalizedChildDN, NULL ) != LDAP_SUCCESS )
+ {
+ slapi_ch_free( (void **)&normalizedAssertedParentDN.bv_val );
+ return 0;
+ }
+
+ dnParent( &normalizedChildDN, &normalizedParentDN );
+
+ if ( dnMatch( &match, 0, slap_schema.si_syn_distinguishedName, NULL,
+ &normalizedParentDN, (void *)&normalizedAssertedParentDN ) != LDAP_SUCCESS )
+ {
+ match = -1;
+ }
+
+ slapi_ch_free( (void **)&normalizedAssertedParentDN.bv_val );
+ slapi_ch_free( (void **)&normalizedChildDN.bv_val );
+
+ return ( match == 0 );
+}
+
+/*
+ * Returns DN of the parent entry, or NULL if the DN is
+ * an empty string or NULL, or has no parent.
+ */
+char *
+slapi_dn_parent( const char *_dn )
+{
+ struct berval dn, prettyDN;
+ struct berval parentDN;
+ char *ret;
+
+ if ( _dn == NULL ) {
+ return NULL;
+ }
+
+ dn.bv_val = (char *)_dn;
+ dn.bv_len = strlen( _dn );
+
+ if ( dn.bv_len == 0 ) {
+ return NULL;
+ }
+
+ if ( dnPretty( NULL, &dn, &prettyDN, NULL ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ dnParent( &prettyDN, &parentDN ); /* in-place */
+
+ if ( parentDN.bv_len == 0 ) {
+ slapi_ch_free_string( &prettyDN.bv_val );
+ return NULL;
+ }
+
+ ret = slapi_ch_strdup( parentDN.bv_val );
+ slapi_ch_free_string( &prettyDN.bv_val );
+
+ return ret;
+}
+
+int slapi_dn_isbesuffix( Slapi_PBlock *pb, char *ldn )
+{
+ struct berval ndn;
+ Backend *be;
+
+ if ( slapi_is_rootdse( ldn ) ) {
+ return 0;
+ }
+
+ /* according to spec should already be normalized */
+ ndn.bv_len = strlen( ldn );
+ ndn.bv_val = ldn;
+
+ be = select_backend( &pb->pb_op->o_req_ndn, 0 );
+ if ( be == NULL ) {
+ return 0;
+ }
+
+ return be_issuffix( be, &ndn );
+}
+
+/*
+ * Returns DN of the parent entry; or NULL if the DN is
+ * an empty string, if the DN has no parent, or if the
+ * DN is the suffix of the backend database
+ */
+char *slapi_dn_beparent( Slapi_PBlock *pb, const char *ldn )
+{
+ Backend *be;
+ struct berval dn, prettyDN;
+ struct berval normalizedDN, parentDN;
+ char *parent = NULL;
+
+ if ( pb == NULL ) {
+ return NULL;
+ }
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+
+ if ( slapi_is_rootdse( ldn ) ) {
+ return NULL;
+ }
+
+ dn.bv_val = (char *)ldn;
+ dn.bv_len = strlen( ldn );
+
+ if ( dnPrettyNormal( NULL, &dn, &prettyDN, &normalizedDN, NULL ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ be = select_backend( &pb->pb_op->o_req_ndn, 0 );
+
+ if ( be == NULL || be_issuffix( be, &normalizedDN ) == 0 ) {
+ dnParent( &prettyDN, &parentDN );
+
+ if ( parentDN.bv_len != 0 )
+ parent = slapi_ch_strdup( parentDN.bv_val );
+ }
+
+ slapi_ch_free_string( &prettyDN.bv_val );
+ slapi_ch_free_string( &normalizedDN.bv_val );
+
+ return parent;
+}
+
+char *
+slapi_dn_ignore_case( char *dn )
+{
+ return slapi_dn_normalize_case( dn );
+}
+
+char *
+slapi_ch_malloc( unsigned long size )
+{
+ return ch_malloc( size );
+}
+
+void
+slapi_ch_free( void **ptr )
+{
+ if ( ptr == NULL || *ptr == NULL )
+ return;
+ ch_free( *ptr );
+ *ptr = NULL;
+}
+
+void
+slapi_ch_free_string( char **ptr )
+{
+ slapi_ch_free( (void **)ptr );
+}
+
+void
+slapi_ch_array_free( char **arrayp )
+{
+ char **p;
+
+ if ( arrayp != NULL ) {
+ for ( p = arrayp; *p != NULL; p++ ) {
+ slapi_ch_free( (void **)p );
+ }
+ slapi_ch_free( (void **)&arrayp );
+ }
+}
+
+struct berval *
+slapi_ch_bvdup(const struct berval *v)
+{
+ return ber_dupbv(NULL, (struct berval *)v);
+}
+
+struct berval **
+slapi_ch_bvecdup(const struct berval **v)
+{
+ int i;
+ struct berval **rv;
+
+ if ( v == NULL ) {
+ return NULL;
+ }
+
+ for ( i = 0; v[i] != NULL; i++ )
+ ;
+
+ rv = (struct berval **) slapi_ch_malloc( (i + 1) * sizeof(struct berval *) );
+
+ for ( i = 0; v[i] != NULL; i++ ) {
+ rv[i] = slapi_ch_bvdup( v[i] );
+ }
+ rv[i] = NULL;
+
+ return rv;
+}
+
+char *
+slapi_ch_calloc(
+ unsigned long nelem,
+ unsigned long size )
+{
+ return ch_calloc( nelem, size );
+}
+
+char *
+slapi_ch_realloc(
+ char *block,
+ unsigned long size )
+{
+ return ch_realloc( block, size );
+}
+
+char *
+slapi_ch_strdup( const char *s )
+{
+ return ch_strdup( s );
+}
+
+size_t
+slapi_ch_stlen( const char *s )
+{
+ return strlen( s );
+}
+
+int
+slapi_control_present(
+ LDAPControl **controls,
+ char *oid,
+ struct berval **val,
+ int *iscritical )
+{
+ int i;
+ int rc = 0;
+
+ if ( val ) {
+ *val = NULL;
+ }
+
+ if ( iscritical ) {
+ *iscritical = 0;
+ }
+
+ for ( i = 0; controls != NULL && controls[i] != NULL; i++ ) {
+ if ( strcmp( controls[i]->ldctl_oid, oid ) != 0 ) {
+ continue;
+ }
+
+ rc = 1;
+ if ( controls[i]->ldctl_value.bv_len != 0 ) {
+ if ( val ) {
+ *val = &controls[i]->ldctl_value;
+ }
+ }
+
+ if ( iscritical ) {
+ *iscritical = controls[i]->ldctl_iscritical;
+ }
+
+ break;
+ }
+
+ return rc;
+}
+
+static void
+slapControlMask2SlapiControlOp(slap_mask_t slap_mask,
+ unsigned long *slapi_mask)
+{
+ *slapi_mask = SLAPI_OPERATION_NONE;
+
+ if ( slap_mask & SLAP_CTRL_ABANDON )
+ *slapi_mask |= SLAPI_OPERATION_ABANDON;
+
+ if ( slap_mask & SLAP_CTRL_ADD )
+ *slapi_mask |= SLAPI_OPERATION_ADD;
+
+ if ( slap_mask & SLAP_CTRL_BIND )
+ *slapi_mask |= SLAPI_OPERATION_BIND;
+
+ if ( slap_mask & SLAP_CTRL_COMPARE )
+ *slapi_mask |= SLAPI_OPERATION_COMPARE;
+
+ if ( slap_mask & SLAP_CTRL_DELETE )
+ *slapi_mask |= SLAPI_OPERATION_DELETE;
+
+ if ( slap_mask & SLAP_CTRL_MODIFY )
+ *slapi_mask |= SLAPI_OPERATION_MODIFY;
+
+ if ( slap_mask & SLAP_CTRL_RENAME )
+ *slapi_mask |= SLAPI_OPERATION_MODDN;
+
+ if ( slap_mask & SLAP_CTRL_SEARCH )
+ *slapi_mask |= SLAPI_OPERATION_SEARCH;
+
+ if ( slap_mask & SLAP_CTRL_UNBIND )
+ *slapi_mask |= SLAPI_OPERATION_UNBIND;
+}
+
+static void
+slapiControlOp2SlapControlMask(unsigned long slapi_mask,
+ slap_mask_t *slap_mask)
+{
+ *slap_mask = 0;
+
+ if ( slapi_mask & SLAPI_OPERATION_BIND )
+ *slap_mask |= SLAP_CTRL_BIND;
+
+ if ( slapi_mask & SLAPI_OPERATION_UNBIND )
+ *slap_mask |= SLAP_CTRL_UNBIND;
+
+ if ( slapi_mask & SLAPI_OPERATION_SEARCH )
+ *slap_mask |= SLAP_CTRL_SEARCH;
+
+ if ( slapi_mask & SLAPI_OPERATION_MODIFY )
+ *slap_mask |= SLAP_CTRL_MODIFY;
+
+ if ( slapi_mask & SLAPI_OPERATION_ADD )
+ *slap_mask |= SLAP_CTRL_ADD;
+
+ if ( slapi_mask & SLAPI_OPERATION_DELETE )
+ *slap_mask |= SLAP_CTRL_DELETE;
+
+ if ( slapi_mask & SLAPI_OPERATION_MODDN )
+ *slap_mask |= SLAP_CTRL_RENAME;
+
+ if ( slapi_mask & SLAPI_OPERATION_COMPARE )
+ *slap_mask |= SLAP_CTRL_COMPARE;
+
+ if ( slapi_mask & SLAPI_OPERATION_ABANDON )
+ *slap_mask |= SLAP_CTRL_ABANDON;
+
+ *slap_mask |= SLAP_CTRL_GLOBAL;
+}
+
+static int
+slapi_int_parse_control(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ /* Plugins must deal with controls themselves. */
+
+ return LDAP_SUCCESS;
+}
+
+void
+slapi_register_supported_control(
+ char *controloid,
+ unsigned long controlops )
+{
+ slap_mask_t controlmask;
+
+ slapiControlOp2SlapControlMask( controlops, &controlmask );
+
+ register_supported_control( controloid, controlmask, NULL, slapi_int_parse_control, NULL );
+}
+
+int
+slapi_get_supported_controls(
+ char ***ctrloidsp,
+ unsigned long **ctrlopsp )
+{
+ int i, rc;
+
+ rc = get_supported_controls( ctrloidsp, (slap_mask_t **)ctrlopsp );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ for ( i = 0; (*ctrloidsp)[i] != NULL; i++ ) {
+ /* In place, naughty. */
+ slapControlMask2SlapiControlOp( (*ctrlopsp)[i], &((*ctrlopsp)[i]) );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+LDAPControl *
+slapi_dup_control( LDAPControl *ctrl )
+{
+ LDAPControl *ret;
+
+ ret = (LDAPControl *)slapi_ch_malloc( sizeof(*ret) );
+ ret->ldctl_oid = slapi_ch_strdup( ctrl->ldctl_oid );
+ ber_dupbv( &ret->ldctl_value, &ctrl->ldctl_value );
+ ret->ldctl_iscritical = ctrl->ldctl_iscritical;
+
+ return ret;
+}
+
+void
+slapi_register_supported_saslmechanism( char *mechanism )
+{
+ /* FIXME -- can not add saslmechanism to OpenLDAP dynamically */
+ slapi_log_error( SLAPI_LOG_FATAL, "slapi_register_supported_saslmechanism",
+ "OpenLDAP does not support dynamic registration of SASL mechanisms\n" );
+}
+
+char **
+slapi_get_supported_saslmechanisms( void )
+{
+ /* FIXME -- can not get the saslmechanism without a connection. */
+ slapi_log_error( SLAPI_LOG_FATAL, "slapi_get_supported_saslmechanisms",
+ "can not get the SASL mechanism list "
+ "without a connection\n" );
+ return NULL;
+}
+
+char **
+slapi_get_supported_extended_ops( void )
+{
+ int i, j, k;
+ char **ppExtOpOID = NULL;
+ int numExtOps = 0;
+
+ for ( i = 0; get_supported_extop( i ) != NULL; i++ ) {
+ ;
+ }
+
+ for ( j = 0; slapi_int_get_supported_extop( j ) != NULL; j++ ) {
+ ;
+ }
+
+ numExtOps = i + j;
+ if ( numExtOps == 0 ) {
+ return NULL;
+ }
+
+ ppExtOpOID = (char **)slapi_ch_malloc( (numExtOps + 1) * sizeof(char *) );
+ for ( k = 0; k < i; k++ ) {
+ struct berval *bv;
+
+ bv = get_supported_extop( k );
+ assert( bv != NULL );
+
+ ppExtOpOID[ k ] = bv->bv_val;
+ }
+
+ for ( ; k < j; k++ ) {
+ struct berval *bv;
+
+ bv = slapi_int_get_supported_extop( k );
+ assert( bv != NULL );
+
+ ppExtOpOID[ i + k ] = bv->bv_val;
+ }
+ ppExtOpOID[ i + k ] = NULL;
+
+ return ppExtOpOID;
+}
+
+void
+slapi_send_ldap_result(
+ Slapi_PBlock *pb,
+ int err,
+ char *matched,
+ char *text,
+ int nentries,
+ struct berval **urls )
+{
+ SlapReply *rs;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+
+ rs = pb->pb_rs;
+
+ rs->sr_err = err;
+ rs->sr_matched = matched;
+ rs->sr_text = text;
+ rs->sr_ref = NULL;
+
+ if ( err == LDAP_SASL_BIND_IN_PROGRESS ) {
+ send_ldap_sasl( pb->pb_op, rs );
+ } else if ( rs->sr_rspoid != NULL ) {
+ send_ldap_extended( pb->pb_op, rs );
+ } else {
+ if ( pb->pb_op->o_tag == LDAP_REQ_SEARCH )
+ rs->sr_nentries = nentries;
+ if ( urls != NULL )
+ bvptr2obj( urls, &rs->sr_ref, NULL );
+
+ send_ldap_result( pb->pb_op, rs );
+
+ if ( urls != NULL )
+ slapi_ch_free( (void **)&rs->sr_ref );
+ }
+}
+
+int
+slapi_send_ldap_search_entry(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ LDAPControl **ectrls,
+ char **attrs,
+ int attrsonly )
+{
+ SlapReply rs = { REP_SEARCH };
+ int i = 0, j = 0;
+ AttributeName *an = NULL;
+ const char *text;
+ int rc;
+
+ assert( pb->pb_op != NULL );
+
+ if ( attrs != NULL ) {
+ for ( i = 0; attrs[ i ] != NULL; i++ ) {
+ ; /* empty */
+ }
+ }
+
+ if ( i ) {
+ an = (AttributeName *) slapi_ch_calloc( i + 1, sizeof(AttributeName) );
+ for ( i = 0; attrs[i] != NULL; i++ ) {
+ an[j].an_name.bv_val = attrs[i];
+ an[j].an_name.bv_len = strlen( attrs[i] );
+ an[j].an_desc = NULL;
+ if ( slap_bv2ad( &an[j].an_name, &an[j].an_desc, &text ) == LDAP_SUCCESS) {
+ j++;
+ }
+ }
+ an[j].an_name.bv_len = 0;
+ an[j].an_name.bv_val = NULL;
+ }
+
+ rs.sr_err = LDAP_SUCCESS;
+ rs.sr_matched = NULL;
+ rs.sr_text = NULL;
+ rs.sr_ref = NULL;
+ rs.sr_ctrls = ectrls;
+ rs.sr_attrs = an;
+ rs.sr_operational_attrs = NULL;
+ rs.sr_entry = e;
+ rs.sr_v2ref = NULL;
+ rs.sr_flags = 0;
+
+ rc = send_search_entry( pb->pb_op, &rs );
+
+ slapi_ch_free( (void **)&an );
+
+ return rc;
+}
+
+int
+slapi_send_ldap_search_reference(
+ Slapi_PBlock *pb,
+ Slapi_Entry *e,
+ struct berval **references,
+ LDAPControl **ectrls,
+ struct berval **v2refs
+ )
+{
+ SlapReply rs = { REP_SEARCHREF };
+ int rc;
+
+ rs.sr_err = LDAP_SUCCESS;
+ rs.sr_matched = NULL;
+ rs.sr_text = NULL;
+
+ rc = bvptr2obj( references, &rs.sr_ref, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ rs.sr_ctrls = ectrls;
+ rs.sr_attrs = NULL;
+ rs.sr_operational_attrs = NULL;
+ rs.sr_entry = e;
+
+ if ( v2refs != NULL ) {
+ rc = bvptr2obj( v2refs, &rs.sr_v2ref, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ slapi_ch_free( (void **)&rs.sr_ref );
+ return rc;
+ }
+ } else {
+ rs.sr_v2ref = NULL;
+ }
+
+ rc = send_search_reference( pb->pb_op, &rs );
+
+ slapi_ch_free( (void **)&rs.sr_ref );
+ slapi_ch_free( (void **)&rs.sr_v2ref );
+
+ return rc;
+}
+
+Slapi_Filter *
+slapi_str2filter( char *str )
+{
+ return str2filter( str );
+}
+
+void
+slapi_filter_free(
+ Slapi_Filter *f,
+ int recurse )
+{
+ filter_free( f );
+}
+
+Slapi_Filter *
+slapi_filter_dup( Slapi_Filter *filter )
+{
+ return filter_dup( filter, NULL );
+}
+
+int
+slapi_filter_get_choice( Slapi_Filter *f )
+{
+ int rc;
+
+ if ( f != NULL ) {
+ rc = f->f_choice;
+ } else {
+ rc = 0;
+ }
+
+ return rc;
+}
+
+int
+slapi_filter_get_ava(
+ Slapi_Filter *f,
+ char **type,
+ struct berval **bval )
+{
+ int ftype;
+ int rc = LDAP_SUCCESS;
+
+ assert( type != NULL );
+ assert( bval != NULL );
+
+ *type = NULL;
+ *bval = NULL;
+
+ ftype = f->f_choice;
+ if ( ftype == LDAP_FILTER_EQUALITY
+ || ftype == LDAP_FILTER_GE
+ || ftype == LDAP_FILTER_LE
+ || ftype == LDAP_FILTER_APPROX ) {
+ /*
+ * According to the SLAPI Reference Manual these are
+ * not duplicated.
+ */
+ *type = f->f_un.f_un_ava->aa_desc->ad_cname.bv_val;
+ *bval = &f->f_un.f_un_ava->aa_value;
+ } else { /* filter type not supported */
+ rc = -1;
+ }
+
+ return rc;
+}
+
+Slapi_Filter *
+slapi_filter_list_first( Slapi_Filter *f )
+{
+ int ftype;
+
+ if ( f == NULL ) {
+ return NULL;
+ }
+
+ ftype = f->f_choice;
+ if ( ftype == LDAP_FILTER_AND
+ || ftype == LDAP_FILTER_OR
+ || ftype == LDAP_FILTER_NOT ) {
+ return (Slapi_Filter *)f->f_list;
+ } else {
+ return NULL;
+ }
+}
+
+Slapi_Filter *
+slapi_filter_list_next(
+ Slapi_Filter *f,
+ Slapi_Filter *fprev )
+{
+ int ftype;
+
+ if ( f == NULL ) {
+ return NULL;
+ }
+
+ ftype = f->f_choice;
+ if ( ftype == LDAP_FILTER_AND
+ || ftype == LDAP_FILTER_OR
+ || ftype == LDAP_FILTER_NOT )
+ {
+ return fprev->f_next;
+ }
+
+ return NULL;
+}
+
+int
+slapi_filter_get_attribute_type( Slapi_Filter *f, char **type )
+{
+ if ( f == NULL ) {
+ return -1;
+ }
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_APPROX:
+ *type = f->f_av_desc->ad_cname.bv_val;
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ *type = f->f_sub_desc->ad_cname.bv_val;
+ break;
+ case LDAP_FILTER_PRESENT:
+ *type = f->f_desc->ad_cname.bv_val;
+ break;
+ case LDAP_FILTER_EXT:
+ *type = f->f_mr_desc->ad_cname.bv_val;
+ break;
+ default:
+ /* Complex filters need not apply. */
+ *type = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+slapi_x_filter_set_attribute_type( Slapi_Filter *f, const char *type )
+{
+ AttributeDescription **adp, *ad = NULL;
+ const char *text;
+ int rc;
+
+ if ( f == NULL ) {
+ return -1;
+ }
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_APPROX:
+ adp = &f->f_av_desc;
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ adp = &f->f_sub_desc;
+ break;
+ case LDAP_FILTER_PRESENT:
+ adp = &f->f_desc;
+ break;
+ case LDAP_FILTER_EXT:
+ adp = &f->f_mr_desc;
+ break;
+ default:
+ /* Complex filters need not apply. */
+ return -1;
+ }
+
+ rc = slap_str2ad( type, &ad, &text );
+ if ( rc == LDAP_SUCCESS )
+ *adp = ad;
+
+ return ( rc == LDAP_SUCCESS ) ? 0 : -1;
+}
+
+int
+slapi_filter_get_subfilt( Slapi_Filter *f, char **type, char **initial,
+ char ***any, char **final )
+{
+ int i;
+
+ if ( f->f_choice != LDAP_FILTER_SUBSTRINGS ) {
+ return -1;
+ }
+
+ /*
+ * The caller shouldn't free but we can't return an
+ * array of char *s from an array of bervals without
+ * allocating memory, so we may as well be consistent.
+ * XXX
+ */
+ *type = f->f_sub_desc->ad_cname.bv_val;
+ *initial = f->f_sub_initial.bv_val ? slapi_ch_strdup(f->f_sub_initial.bv_val) : NULL;
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ )
+ ;
+ *any = (char **)slapi_ch_malloc( (i + 1) * sizeof(char *) );
+ for ( i = 0; f->f_sub_any[i].bv_val != NULL; i++ ) {
+ (*any)[i] = slapi_ch_strdup(f->f_sub_any[i].bv_val);
+ }
+ (*any)[i] = NULL;
+ } else {
+ *any = NULL;
+ }
+ *final = f->f_sub_final.bv_val ? slapi_ch_strdup(f->f_sub_final.bv_val) : NULL;
+
+ return 0;
+}
+
+Slapi_Filter *
+slapi_filter_join( int ftype, Slapi_Filter *f1, Slapi_Filter *f2 )
+{
+ Slapi_Filter *f = NULL;
+
+ if ( ftype == LDAP_FILTER_AND ||
+ ftype == LDAP_FILTER_OR ||
+ ftype == LDAP_FILTER_NOT )
+ {
+ f = (Slapi_Filter *)slapi_ch_malloc( sizeof(*f) );
+ f->f_choice = ftype;
+ f->f_list = f1;
+ f->f_list->f_next = f2;
+ f->f_next = NULL;
+ }
+
+ return f;
+}
+
+int
+slapi_x_filter_append( int ftype,
+ Slapi_Filter **pContainingFilter, /* NULL on first call */
+ Slapi_Filter **pNextFilter,
+ Slapi_Filter *filterToAppend )
+{
+ if ( ftype == LDAP_FILTER_AND ||
+ ftype == LDAP_FILTER_OR ||
+ ftype == LDAP_FILTER_NOT )
+ {
+ if ( *pContainingFilter == NULL ) {
+ *pContainingFilter = (Slapi_Filter *)slapi_ch_malloc( sizeof(Slapi_Filter) );
+ (*pContainingFilter)->f_choice = ftype;
+ (*pContainingFilter)->f_list = filterToAppend;
+ (*pContainingFilter)->f_next = NULL;
+ } else {
+ if ( (*pContainingFilter)->f_choice != ftype ) {
+ /* Sanity check */
+ return -1;
+ }
+ (*pNextFilter)->f_next = filterToAppend;
+ }
+ *pNextFilter = filterToAppend;
+
+ return 0;
+ }
+ return -1;
+}
+
+int
+slapi_filter_test( Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Filter *f,
+ int verify_access )
+{
+ Operation *op;
+ int rc;
+
+ if ( f == NULL ) {
+ /* spec says return zero if no filter. */
+ return 0;
+ }
+
+ if ( verify_access ) {
+ op = pb->pb_op;
+ if ( op == NULL )
+ return LDAP_PARAM_ERROR;
+ } else {
+ op = NULL;
+ }
+
+ /*
+ * According to acl.c it is safe to call test_filter() with
+ * NULL arguments...
+ */
+ rc = test_filter( op, e, f );
+ switch (rc) {
+ case LDAP_COMPARE_TRUE:
+ rc = 0;
+ break;
+ case LDAP_COMPARE_FALSE:
+ break;
+ case SLAPD_COMPARE_UNDEFINED:
+ rc = LDAP_OTHER;
+ break;
+ case LDAP_PROTOCOL_ERROR:
+ /* filter type unknown: spec says return -1 */
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+int
+slapi_filter_test_simple( Slapi_Entry *e, Slapi_Filter *f)
+{
+ return slapi_filter_test( NULL, e, f, 0 );
+}
+
+int
+slapi_filter_apply( Slapi_Filter *f, FILTER_APPLY_FN fn, void *arg, int *error_code )
+{
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_NOT:
+ case LDAP_FILTER_OR: {
+ int rc;
+
+ /*
+ * FIXME: altering f; should we use a temporary?
+ */
+ for ( f = f->f_list; f != NULL; f = f->f_next ) {
+ rc = slapi_filter_apply( f, fn, arg, error_code );
+ if ( rc != 0 ) {
+ return rc;
+ }
+ if ( *error_code == SLAPI_FILTER_SCAN_NOMORE ) {
+ break;
+ }
+ }
+ break;
+ }
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_PRESENT:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_EXT:
+ *error_code = fn( f, arg );
+ break;
+ default:
+ *error_code = SLAPI_FILTER_UNKNOWN_FILTER_TYPE;
+ }
+
+ if ( *error_code == SLAPI_FILTER_SCAN_NOMORE ||
+ *error_code == SLAPI_FILTER_SCAN_CONTINUE ) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int
+slapi_pw_find(
+ struct berval **vals,
+ struct berval *v )
+{
+ int i;
+
+ if( ( vals == NULL ) || ( v == NULL ) )
+ return 1;
+
+ for ( i = 0; vals[i] != NULL; i++ ) {
+ if ( !lutil_passwd( vals[i], v, NULL, NULL ) )
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Get connected client IP address.
+ *
+ * The user must free the returned client IP after its use.
+ * Compatible with IBM Tivoli call.
+ *
+ * Errors:
+ * * LDAP_PARAM_ERROR - If the pb parameter is null.
+ * * LDAP_OPERATIONS_ERROR - If the API encounters error processing the request.
+ * * LDAP_NO_MEMORY - Failed to allocate required memory.
+ */
+int
+slapi_get_client_ip(Slapi_PBlock *pb, char **clientIP)
+{
+ char *s = NULL;
+
+ if(pb == NULL || pb->pb_conn == NULL) return(LDAP_PARAM_ERROR);
+ if((s = (char *) slapi_ch_malloc(pb->pb_conn->c_peer_name.bv_len + 1)) == NULL) {
+ return(LDAP_NO_MEMORY);
+ }
+
+ memcpy(s, pb->pb_conn->c_peer_name.bv_val, pb->pb_conn->c_peer_name.bv_len);
+
+ s[pb->pb_conn->c_peer_name.bv_len] = 0;
+
+ *clientIP = s;
+
+ return(LDAP_SUCCESS);
+}
+
+/* Free previously allocated client IP address. */
+void
+slapi_free_client_ip(char **clientIP)
+{
+ slapi_ch_free((void **) clientIP);
+}
+
+#define MAX_HOSTNAME 512
+
+char *
+slapi_get_hostname( void )
+{
+ char *hn = NULL;
+ static int been_here = 0;
+ static char *static_hn = NULL;
+
+ ldap_pvt_thread_mutex_lock( &slapi_hn_mutex );
+ if ( !been_here ) {
+ static_hn = (char *)slapi_ch_malloc( MAX_HOSTNAME );
+ if ( static_hn == NULL) {
+ slapi_log_error( SLAPI_LOG_FATAL, "slapi_get_hostname",
+ "Cannot allocate memory for hostname\n" );
+ static_hn = NULL;
+ ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex );
+
+ return hn;
+
+ } else {
+ if ( gethostname( static_hn, MAX_HOSTNAME ) != 0 ) {
+ slapi_log_error( SLAPI_LOG_FATAL,
+ "SLAPI",
+ "can't get hostname\n" );
+ slapi_ch_free( (void **)&static_hn );
+ static_hn = NULL;
+ ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex );
+
+ return hn;
+
+ } else {
+ been_here = 1;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &slapi_hn_mutex );
+
+ hn = ch_strdup( static_hn );
+
+ return hn;
+}
+
+/*
+ * FIXME: this should go in an appropriate header ...
+ */
+extern int slapi_int_log_error( int level, char *subsystem, char *fmt, va_list arglist );
+
+int
+slapi_log_error(
+ int severity,
+ char *subsystem,
+ char *fmt,
+ ... )
+{
+ int rc = LDAP_SUCCESS;
+ va_list arglist;
+
+ va_start( arglist, fmt );
+ rc = slapi_int_log_error( severity, subsystem, fmt, arglist );
+ va_end( arglist );
+
+ return rc;
+}
+
+
+unsigned long
+slapi_timer_current_time( void )
+{
+ static int first_time = 1;
+#if !defined (_WIN32)
+ struct timeval now;
+ unsigned long ret;
+
+ ldap_pvt_thread_mutex_lock( &slapi_time_mutex );
+ if (first_time) {
+ first_time = 0;
+ gettimeofday( &base_time, NULL );
+ }
+ gettimeofday( &now, NULL );
+ ret = ( now.tv_sec - base_time.tv_sec ) * 1000000 +
+ (now.tv_usec - base_time.tv_usec);
+ ldap_pvt_thread_mutex_unlock( &slapi_time_mutex );
+
+ return ret;
+
+ /*
+ * Ain't it better?
+ return (slap_get_time() - starttime) * 1000000;
+ */
+#else /* _WIN32 */
+ LARGE_INTEGER now;
+ static LARGE_INTEGER base_time, performance_freq;
+ static int performance_counter_present;
+
+ if ( first_time ) {
+ first_time = 0;
+ performance_counter_present = QueryPerformanceCounter( &base_time );
+ QueryPerformanceFrequency( &performance_freq );
+ }
+
+ if ( !performance_counter_present )
+ return 0;
+
+ QueryPerformanceCounter( &now );
+ return (1000000*(now.QuadPart-base_time.QuadPart))/performance_freq.QuadPart;
+#endif /* _WIN32 */
+}
+
+/*
+ * FIXME ?
+ */
+unsigned long
+slapi_timer_get_time( char *label )
+{
+ unsigned long start = slapi_timer_current_time();
+ printf("%10ld %10d usec %s\n", start, 0, label);
+ return start;
+}
+
+/*
+ * FIXME ?
+ */
+void
+slapi_timer_elapsed_time(
+ char *label,
+ unsigned long start )
+{
+ unsigned long stop = slapi_timer_current_time();
+ printf ("%10ld %10ld usec %s\n", stop, stop - start, label);
+}
+
+void
+slapi_free_search_results_internal( Slapi_PBlock *pb )
+{
+ Slapi_Entry **entries;
+ int k = 0, nEnt = 0;
+
+ slapi_pblock_get( pb, SLAPI_NENTRIES, &nEnt );
+ slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries );
+ if ( nEnt == 0 || entries == NULL ) {
+ return;
+ }
+
+ for ( k = 0; k < nEnt; k++ ) {
+ slapi_entry_free( entries[k] );
+ entries[k] = NULL;
+ }
+
+ slapi_ch_free( (void **)&entries );
+}
+
+int slapi_is_connection_ssl( Slapi_PBlock *pb, int *isSSL )
+{
+ if ( pb == NULL )
+ return LDAP_PARAM_ERROR;
+
+ if ( pb->pb_conn == NULL )
+ return LDAP_PARAM_ERROR;
+
+#ifdef HAVE_TLS
+ *isSSL = pb->pb_conn->c_is_tls;
+#else
+ *isSSL = 0;
+#endif
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * DS 5.x compatibility API follow
+ */
+
+int slapi_attr_get_flags( const Slapi_Attr *attr, unsigned long *flags )
+{
+ AttributeType *at;
+
+ if ( attr == NULL )
+ return LDAP_PARAM_ERROR;
+
+ at = attr->a_desc->ad_type;
+
+ *flags = SLAPI_ATTR_FLAG_STD_ATTR;
+
+ if ( is_at_single_value( at ) )
+ *flags |= SLAPI_ATTR_FLAG_SINGLE;
+ if ( is_at_operational( at ) )
+ *flags |= SLAPI_ATTR_FLAG_OPATTR;
+ if ( is_at_obsolete( at ) )
+ *flags |= SLAPI_ATTR_FLAG_OBSOLETE;
+ if ( is_at_collective( at ) )
+ *flags |= SLAPI_ATTR_FLAG_COLLECTIVE;
+ if ( is_at_no_user_mod( at ) )
+ *flags |= SLAPI_ATTR_FLAG_NOUSERMOD;
+
+ return LDAP_SUCCESS;
+}
+
+int slapi_attr_flag_is_set( const Slapi_Attr *attr, unsigned long flag )
+{
+ unsigned long flags;
+
+ if ( slapi_attr_get_flags( attr, &flags ) != 0 )
+ return 0;
+ return (flags & flag) ? 1 : 0;
+}
+
+Slapi_Attr *slapi_attr_new( void )
+{
+ Attribute *ad;
+
+ ad = (Attribute *)slapi_ch_calloc( 1, sizeof(*ad) );
+
+ return ad;
+}
+
+Slapi_Attr *slapi_attr_init( Slapi_Attr *a, const char *type )
+{
+ const char *text;
+ AttributeDescription *ad = NULL;
+
+ if( slap_str2ad( type, &ad, &text ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ a->a_desc = ad;
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_next = NULL;
+ a->a_flags = 0;
+
+ return a;
+}
+
+void slapi_attr_free( Slapi_Attr **a )
+{
+ attr_free( *a );
+ *a = NULL;
+}
+
+Slapi_Attr *slapi_attr_dup( const Slapi_Attr *attr )
+{
+ return attr_dup( (Slapi_Attr *)attr );
+}
+
+int slapi_attr_add_value( Slapi_Attr *a, const Slapi_Value *v )
+{
+ struct berval nval;
+ struct berval *nvalp;
+ int rc;
+ AttributeDescription *desc = a->a_desc;
+
+ if ( desc->ad_type->sat_equality &&
+ desc->ad_type->sat_equality->smr_normalize ) {
+ rc = (*desc->ad_type->sat_equality->smr_normalize)(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ (Slapi_Value *)v, &nval, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ nvalp = &nval;
+ } else {
+ nvalp = NULL;
+ }
+
+ rc = attr_valadd( a, (Slapi_Value *)v, nvalp, 1 );
+
+ if ( nvalp != NULL ) {
+ slapi_ch_free_string( &nval.bv_val );
+ }
+
+ return rc;
+}
+
+int slapi_attr_type2plugin( const char *type, void **pi )
+{
+ *pi = NULL;
+
+ return LDAP_OTHER;
+}
+
+int slapi_attr_get_type( const Slapi_Attr *attr, char **type )
+{
+ if ( attr == NULL ) {
+ return LDAP_PARAM_ERROR;
+ }
+
+ *type = attr->a_desc->ad_cname.bv_val;
+
+ return LDAP_SUCCESS;
+}
+
+int slapi_attr_get_oid_copy( const Slapi_Attr *attr, char **oidp )
+{
+ if ( attr == NULL ) {
+ return LDAP_PARAM_ERROR;
+ }
+ *oidp = attr->a_desc->ad_type->sat_oid;
+
+ return LDAP_SUCCESS;
+}
+
+int slapi_attr_value_cmp( const Slapi_Attr *a, const struct berval *v1, const struct berval *v2 )
+{
+ MatchingRule *mr;
+ int ret;
+ int rc;
+ const char *text;
+
+ mr = a->a_desc->ad_type->sat_equality;
+ rc = value_match( &ret, a->a_desc, mr,
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ (struct berval *)v1, (void *)v2, &text );
+ if ( rc != LDAP_SUCCESS )
+ return -1;
+
+ return ( ret == 0 ) ? 0 : -1;
+}
+
+int slapi_attr_value_find( const Slapi_Attr *a, struct berval *v )
+{
+ int rc;
+
+ if ( a ->a_vals == NULL ) {
+ return -1;
+ }
+ rc = attr_valfind( (Attribute *)a, SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, v,
+ NULL, NULL );
+ return rc == 0 ? 0 : -1;
+}
+
+int slapi_attr_type_cmp( const char *t1, const char *t2, int opt )
+{
+ AttributeDescription *a1 = NULL;
+ AttributeDescription *a2 = NULL;
+ const char *text;
+ int ret;
+
+ if ( slap_str2ad( t1, &a1, &text ) != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ if ( slap_str2ad( t2, &a2, &text ) != LDAP_SUCCESS ) {
+ return 1;
+ }
+
+#define ad_base_cmp(l,r) (((l)->ad_type->sat_cname.bv_len < (r)->ad_type->sat_cname.bv_len) \
+ ? -1 : (((l)->ad_type->sat_cname.bv_len > (r)->ad_type->sat_cname.bv_len) \
+ ? 1 : strcasecmp((l)->ad_type->sat_cname.bv_val, (r)->ad_type->sat_cname.bv_val )))
+
+ switch ( opt ) {
+ case SLAPI_TYPE_CMP_EXACT:
+ ret = ad_cmp( a1, a2 );
+ break;
+ case SLAPI_TYPE_CMP_BASE:
+ ret = ad_base_cmp( a1, a2 );
+ break;
+ case SLAPI_TYPE_CMP_SUBTYPE:
+ ret = is_ad_subtype( a2, a2 );
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+int slapi_attr_types_equivalent( const char *t1, const char *t2 )
+{
+ return ( slapi_attr_type_cmp( t1, t2, SLAPI_TYPE_CMP_EXACT ) == 0 );
+}
+
+int slapi_attr_first_value( Slapi_Attr *a, Slapi_Value **v )
+{
+ return slapi_valueset_first_value( &a->a_vals, v );
+}
+
+int slapi_attr_next_value( Slapi_Attr *a, int hint, Slapi_Value **v )
+{
+ return slapi_valueset_next_value( &a->a_vals, hint, v );
+}
+
+int slapi_attr_get_numvalues( const Slapi_Attr *a, int *numValues )
+{
+ *numValues = slapi_valueset_count( &a->a_vals );
+
+ return 0;
+}
+
+int slapi_attr_get_valueset( const Slapi_Attr *a, Slapi_ValueSet **vs )
+{
+ *vs = &((Slapi_Attr *)a)->a_vals;
+
+ return 0;
+}
+
+int slapi_attr_get_bervals_copy( Slapi_Attr *a, struct berval ***vals )
+{
+ return slapi_attr_get_values( a, vals );
+}
+
+char *slapi_attr_syntax_normalize( const char *s )
+{
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ if ( slap_str2ad( s, &ad, &text ) != LDAP_SUCCESS ) {
+ return NULL;
+ }
+
+ return ad->ad_cname.bv_val;
+}
+
+Slapi_Value *slapi_value_new( void )
+{
+ struct berval *bv;
+
+ bv = (struct berval *)slapi_ch_malloc( sizeof(*bv) );
+
+ return bv;
+}
+
+Slapi_Value *slapi_value_new_berval(const struct berval *bval)
+{
+ return ber_dupbv( NULL, (struct berval *)bval );
+}
+
+Slapi_Value *slapi_value_new_value(const Slapi_Value *v)
+{
+ return slapi_value_new_berval( v );
+}
+
+Slapi_Value *slapi_value_new_string(const char *s)
+{
+ struct berval bv;
+
+ bv.bv_val = (char *)s;
+ bv.bv_len = strlen( s );
+
+ return slapi_value_new_berval( &bv );
+}
+
+Slapi_Value *slapi_value_init(Slapi_Value *val)
+{
+ val->bv_val = NULL;
+ val->bv_len = 0;
+
+ return val;
+}
+
+Slapi_Value *slapi_value_init_berval(Slapi_Value *v, struct berval *bval)
+{
+ return ber_dupbv( v, bval );
+}
+
+Slapi_Value *slapi_value_init_string(Slapi_Value *v, const char *s)
+{
+ v->bv_val = slapi_ch_strdup( s );
+ v->bv_len = strlen( s );
+
+ return v;
+}
+
+Slapi_Value *slapi_value_dup(const Slapi_Value *v)
+{
+ return slapi_value_new_value( v );
+}
+
+void slapi_value_free(Slapi_Value **value)
+{
+ if ( value == NULL ) {
+ return;
+ }
+
+ if ( (*value) != NULL ) {
+ slapi_ch_free( (void **)&(*value)->bv_val );
+ slapi_ch_free( (void **)value );
+ }
+}
+
+const struct berval *slapi_value_get_berval( const Slapi_Value *value )
+{
+ return value;
+}
+
+Slapi_Value *slapi_value_set_berval( Slapi_Value *value, const struct berval *bval )
+{
+ if ( value == NULL ) {
+ return NULL;
+ }
+ if ( value->bv_val != NULL ) {
+ slapi_ch_free( (void **)&value->bv_val );
+ }
+ slapi_value_init_berval( value, (struct berval *)bval );
+
+ return value;
+}
+
+Slapi_Value *slapi_value_set_value( Slapi_Value *value, const Slapi_Value *vfrom)
+{
+ if ( value == NULL ) {
+ return NULL;
+ }
+ return slapi_value_set_berval( value, vfrom );
+}
+
+Slapi_Value *slapi_value_set( Slapi_Value *value, void *val, unsigned long len)
+{
+ if ( value == NULL ) {
+ return NULL;
+ }
+ if ( value->bv_val != NULL ) {
+ slapi_ch_free( (void **)&value->bv_val );
+ }
+ value->bv_val = slapi_ch_malloc( len );
+ value->bv_len = len;
+ AC_MEMCPY( value->bv_val, val, len );
+
+ return value;
+}
+
+int slapi_value_set_string(Slapi_Value *value, const char *strVal)
+{
+ if ( value == NULL ) {
+ return -1;
+ }
+ slapi_value_set( value, (void *)strVal, strlen( strVal ) );
+ return 0;
+}
+
+int slapi_value_set_int(Slapi_Value *value, int intVal)
+{
+ char buf[64];
+
+ snprintf( buf, sizeof( buf ), "%d", intVal );
+
+ return slapi_value_set_string( value, buf );
+}
+
+const char *slapi_value_get_string(const Slapi_Value *value)
+{
+ if ( value == NULL ) return NULL;
+ if ( value->bv_val == NULL ) return NULL;
+ if ( !checkBVString( value ) ) return NULL;
+
+ return value->bv_val;
+}
+
+int slapi_value_get_int(const Slapi_Value *value)
+{
+ if ( value == NULL ) return 0;
+ if ( value->bv_val == NULL ) return 0;
+ if ( !checkBVString( value ) ) return 0;
+
+ return (int)strtol( value->bv_val, NULL, 10 );
+}
+
+unsigned int slapi_value_get_uint(const Slapi_Value *value)
+{
+ if ( value == NULL ) return 0;
+ if ( value->bv_val == NULL ) return 0;
+ if ( !checkBVString( value ) ) return 0;
+
+ return (unsigned int)strtoul( value->bv_val, NULL, 10 );
+}
+
+long slapi_value_get_long(const Slapi_Value *value)
+{
+ if ( value == NULL ) return 0;
+ if ( value->bv_val == NULL ) return 0;
+ if ( !checkBVString( value ) ) return 0;
+
+ return strtol( value->bv_val, NULL, 10 );
+}
+
+unsigned long slapi_value_get_ulong(const Slapi_Value *value)
+{
+ if ( value == NULL ) return 0;
+ if ( value->bv_val == NULL ) return 0;
+ if ( !checkBVString( value ) ) return 0;
+
+ return strtoul( value->bv_val, NULL, 10 );
+}
+
+size_t slapi_value_get_length(const Slapi_Value *value)
+{
+ if ( value == NULL )
+ return 0;
+
+ return (size_t) value->bv_len;
+}
+
+int slapi_value_compare(const Slapi_Attr *a, const Slapi_Value *v1, const Slapi_Value *v2)
+{
+ return slapi_attr_value_cmp( a, v1, v2 );
+}
+
+/* A ValueSet is a container for a BerVarray. */
+Slapi_ValueSet *slapi_valueset_new( void )
+{
+ Slapi_ValueSet *vs;
+
+ vs = (Slapi_ValueSet *)slapi_ch_malloc( sizeof( *vs ) );
+ *vs = NULL;
+
+ return vs;
+}
+
+void slapi_valueset_free(Slapi_ValueSet *vs)
+{
+ if ( vs != NULL ) {
+ BerVarray vp = *vs;
+
+ ber_bvarray_free( vp );
+ vp = NULL;
+
+ slapi_ch_free( (void **)&vp );
+ }
+}
+
+void slapi_valueset_init(Slapi_ValueSet *vs)
+{
+ if ( vs != NULL && *vs == NULL ) {
+ *vs = (Slapi_ValueSet)slapi_ch_calloc( 1, sizeof(struct berval) );
+ (*vs)->bv_val = NULL;
+ (*vs)->bv_len = 0;
+ }
+}
+
+void slapi_valueset_done(Slapi_ValueSet *vs)
+{
+ BerVarray vp;
+
+ if ( vs == NULL )
+ return;
+
+ for ( vp = *vs; vp->bv_val != NULL; vp++ ) {
+ vp->bv_len = 0;
+ slapi_ch_free( (void **)&vp->bv_val );
+ }
+ /* but don't free *vs or vs */
+}
+
+void slapi_valueset_add_value(Slapi_ValueSet *vs, const Slapi_Value *addval)
+{
+ struct berval bv;
+
+ ber_dupbv( &bv, (Slapi_Value *)addval );
+ ber_bvarray_add( vs, &bv );
+}
+
+int slapi_valueset_first_value( Slapi_ValueSet *vs, Slapi_Value **v )
+{
+ return slapi_valueset_next_value( vs, 0, v );
+}
+
+int slapi_valueset_next_value( Slapi_ValueSet *vs, int index, Slapi_Value **v)
+{
+ int i;
+ BerVarray vp;
+
+ if ( vs == NULL )
+ return -1;
+
+ vp = *vs;
+
+ for ( i = 0; vp[i].bv_val != NULL; i++ ) {
+ if ( i == index ) {
+ *v = &vp[i];
+ return index + 1;
+ }
+ }
+
+ return -1;
+}
+
+int slapi_valueset_count( const Slapi_ValueSet *vs )
+{
+ int i;
+ BerVarray vp;
+
+ if ( vs == NULL )
+ return 0;
+
+ vp = *vs;
+
+ if ( vp == NULL )
+ return 0;
+
+ for ( i = 0; vp[i].bv_val != NULL; i++ )
+ ;
+
+ return i;
+
+}
+
+void slapi_valueset_set_valueset(Slapi_ValueSet *vs1, const Slapi_ValueSet *vs2)
+{
+ BerVarray vp;
+
+ for ( vp = *vs2; vp->bv_val != NULL; vp++ ) {
+ slapi_valueset_add_value( vs1, vp );
+ }
+}
+
+int slapi_access_allowed( Slapi_PBlock *pb, Slapi_Entry *e, char *attr,
+ struct berval *val, int access )
+{
+ int rc;
+ slap_access_t slap_access;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ rc = slap_str2ad( attr, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ /*
+ * Whilst the SLAPI access types are arranged as a bitmask, the
+ * documentation indicates that they are to be used separately.
+ */
+ switch ( access & SLAPI_ACL_ALL ) {
+ case SLAPI_ACL_COMPARE:
+ slap_access = ACL_COMPARE;
+ break;
+ case SLAPI_ACL_SEARCH:
+ slap_access = ACL_SEARCH;
+ break;
+ case SLAPI_ACL_READ:
+ slap_access = ACL_READ;
+ break;
+ case SLAPI_ACL_WRITE:
+ slap_access = ACL_WRITE;
+ break;
+ case SLAPI_ACL_DELETE:
+ slap_access = ACL_WDEL;
+ break;
+ case SLAPI_ACL_ADD:
+ slap_access = ACL_WADD;
+ break;
+ case SLAPI_ACL_SELF: /* not documented */
+ case SLAPI_ACL_PROXY: /* not documented */
+ default:
+ return LDAP_INSUFFICIENT_ACCESS;
+ break;
+ }
+
+ assert( pb->pb_op != NULL );
+
+ if ( access_allowed( pb->pb_op, e, ad, val, slap_access, NULL ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ return LDAP_INSUFFICIENT_ACCESS;
+}
+
+int slapi_acl_check_mods(Slapi_PBlock *pb, Slapi_Entry *e, LDAPMod **mods, char **errbuf)
+{
+ int rc = LDAP_SUCCESS;
+ Modifications *ml;
+
+ if ( pb == NULL || pb->pb_op == NULL )
+ return LDAP_PARAM_ERROR;
+
+ ml = slapi_int_ldapmods2modifications( pb->pb_op, mods );
+ if ( ml == NULL ) {
+ return LDAP_OTHER;
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = acl_check_modlist( pb->pb_op, e, ml ) ? LDAP_SUCCESS : LDAP_INSUFFICIENT_ACCESS;
+ }
+
+ slap_mods_free( ml, 1 );
+
+ return rc;
+}
+
+/*
+ * Synthesise an LDAPMod array from a Modifications list to pass
+ * to SLAPI.
+ */
+LDAPMod **slapi_int_modifications2ldapmods( Modifications *modlist )
+{
+ Modifications *ml;
+ LDAPMod **mods, *modp;
+ int i, j;
+
+ for( i = 0, ml = modlist; ml != NULL; i++, ml = ml->sml_next )
+ ;
+
+ mods = (LDAPMod **)slapi_ch_malloc( (i + 1) * sizeof(LDAPMod *) );
+
+ for( i = 0, ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ mods[i] = (LDAPMod *)slapi_ch_malloc( sizeof(LDAPMod) );
+ modp = mods[i];
+ modp->mod_op = ml->sml_op | LDAP_MOD_BVALUES;
+ if ( BER_BVISNULL( &ml->sml_type ) ) {
+ /* may happen for internally generated mods */
+ assert( ml->sml_desc != NULL );
+ modp->mod_type = slapi_ch_strdup( ml->sml_desc->ad_cname.bv_val );
+ } else {
+ modp->mod_type = slapi_ch_strdup( ml->sml_type.bv_val );
+ }
+
+ if ( ml->sml_values != NULL ) {
+ for( j = 0; ml->sml_values[j].bv_val != NULL; j++ )
+ ;
+ modp->mod_bvalues = (struct berval **)slapi_ch_malloc( (j + 1) *
+ sizeof(struct berval *) );
+ for( j = 0; ml->sml_values[j].bv_val != NULL; j++ ) {
+ modp->mod_bvalues[j] = (struct berval *)slapi_ch_malloc(
+ sizeof(struct berval) );
+ ber_dupbv( modp->mod_bvalues[j], &ml->sml_values[j] );
+ }
+ modp->mod_bvalues[j] = NULL;
+ } else {
+ modp->mod_bvalues = NULL;
+ }
+ i++;
+ }
+
+ mods[i] = NULL;
+
+ return mods;
+}
+
+/*
+ * Convert a potentially modified array of LDAPMods back to a
+ * Modification list. Unfortunately the values need to be
+ * duplicated because slap_mods_check() will try to free them
+ * before prettying (and we can't easily get out of calling
+ * slap_mods_check() because we need normalized values).
+ */
+Modifications *slapi_int_ldapmods2modifications ( Operation *op, LDAPMod **mods )
+{
+ Modifications *modlist = NULL, **modtail;
+ LDAPMod **modp;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ const char *text;
+
+ if ( mods == NULL ) {
+ return NULL;
+ }
+
+ modtail = &modlist;
+
+ for ( modp = mods; *modp != NULL; modp++ ) {
+ Modifications *mod;
+ LDAPMod *lmod = *modp;
+ int i;
+ const char *text;
+ AttributeDescription *ad = NULL;
+
+ if ( slap_str2ad( lmod->mod_type, &ad, &text ) != LDAP_SUCCESS ) {
+ continue;
+ }
+
+ mod = (Modifications *) slapi_ch_malloc( sizeof(Modifications) );
+ mod->sml_op = lmod->mod_op & ~(LDAP_MOD_BVALUES);
+ mod->sml_flags = 0;
+ mod->sml_type = ad->ad_cname;
+ mod->sml_desc = ad;
+ mod->sml_next = NULL;
+
+ i = 0;
+ if ( lmod->mod_op & LDAP_MOD_BVALUES ) {
+ if ( lmod->mod_bvalues != NULL ) {
+ while ( lmod->mod_bvalues[i] != NULL )
+ i++;
+ }
+ } else {
+ if ( lmod->mod_values != NULL ) {
+ while ( lmod->mod_values[i] != NULL )
+ i++;
+ }
+ }
+ mod->sml_numvals = i;
+
+ if ( i == 0 ) {
+ mod->sml_values = NULL;
+ } else {
+ mod->sml_values = (BerVarray) slapi_ch_malloc( (i + 1) * sizeof(struct berval) );
+
+ /* NB: This implicitly trusts a plugin to return valid modifications. */
+ if ( lmod->mod_op & LDAP_MOD_BVALUES ) {
+ for ( i = 0; lmod->mod_bvalues[i] != NULL; i++ ) {
+ ber_dupbv( &mod->sml_values[i], lmod->mod_bvalues[i] );
+ }
+ } else {
+ for ( i = 0; lmod->mod_values[i] != NULL; i++ ) {
+ mod->sml_values[i].bv_val = slapi_ch_strdup( lmod->mod_values[i] );
+ mod->sml_values[i].bv_len = strlen( lmod->mod_values[i] );
+ }
+ }
+ mod->sml_values[i].bv_val = NULL;
+ mod->sml_values[i].bv_len = 0;
+ }
+ mod->sml_nvalues = NULL;
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( slap_mods_check( op, modlist, &text, textbuf, sizeof( textbuf ), NULL ) != LDAP_SUCCESS ) {
+ slap_mods_free( modlist, 1 );
+ modlist = NULL;
+ }
+
+ return modlist;
+}
+
+/*
+ * Sun ONE DS 5.x computed attribute support. Computed attributes
+ * allow for dynamically generated operational attributes, a very
+ * useful thing indeed.
+ */
+
+/*
+ * For some reason Sun don't use the normal plugin mechanism
+ * registration path to register an "evaluator" function (an
+ * "evaluator" is responsible for adding computed attributes;
+ * the nomenclature is somewhat confusing).
+ *
+ * As such slapi_compute_add_evaluator() registers the
+ * function directly.
+ */
+int slapi_compute_add_evaluator(slapi_compute_callback_t function)
+{
+ Slapi_PBlock *pPlugin = NULL;
+ int rc;
+ int type = SLAPI_PLUGIN_OBJECT;
+
+ pPlugin = slapi_pblock_new();
+ if ( pPlugin == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN, (void *)function );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = slapi_int_register_plugin( frontendDB, pPlugin );
+ if ( rc != 0 ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+done:
+ if ( rc != LDAP_SUCCESS ) {
+ if ( pPlugin != NULL ) {
+ slapi_pblock_destroy( pPlugin );
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * See notes above regarding slapi_compute_add_evaluator().
+ */
+int slapi_compute_add_search_rewriter(slapi_search_rewrite_callback_t function)
+{
+ Slapi_PBlock *pPlugin = NULL;
+ int rc;
+ int type = SLAPI_PLUGIN_OBJECT;
+
+ pPlugin = slapi_pblock_new();
+ if ( pPlugin == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = slapi_pblock_set( pPlugin, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, (void *)function );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = slapi_int_register_plugin( frontendDB, pPlugin );
+ if ( rc != 0 ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+done:
+ if ( rc != LDAP_SUCCESS ) {
+ if ( pPlugin != NULL ) {
+ slapi_pblock_destroy( pPlugin );
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Call compute evaluators
+ */
+int compute_evaluator(computed_attr_context *c, char *type, Slapi_Entry *e, slapi_compute_output_t outputfn)
+{
+ int rc = 0;
+ slapi_compute_callback_t *pGetPlugin, *tmpPlugin;
+
+ rc = slapi_int_get_plugins( frontendDB, SLAPI_PLUGIN_COMPUTE_EVALUATOR_FN, (SLAPI_FUNC **)&tmpPlugin );
+ if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) {
+ /* Nothing to do; front-end should ignore. */
+ return 0;
+ }
+
+ for ( pGetPlugin = tmpPlugin; *pGetPlugin != NULL; pGetPlugin++ ) {
+ /*
+ * -1: no attribute matched requested type
+ * 0: one attribute matched
+ * >0: error happened
+ */
+ rc = (*pGetPlugin)( c, type, e, outputfn );
+ if ( rc > 0 ) {
+ break;
+ }
+ }
+
+ slapi_ch_free( (void **)&tmpPlugin );
+
+ return rc;
+}
+
+int
+compute_rewrite_search_filter( Slapi_PBlock *pb )
+{
+ if ( pb == NULL || pb->pb_op == NULL )
+ return LDAP_PARAM_ERROR;
+
+ return slapi_int_call_plugins( pb->pb_op->o_bd, SLAPI_PLUGIN_COMPUTE_SEARCH_REWRITER_FN, pb );
+}
+
+/*
+ * New API to provide the plugin with access to the search
+ * pblock. Have informed Sun DS team.
+ */
+int
+slapi_x_compute_get_pblock(computed_attr_context *c, Slapi_PBlock **pb)
+{
+ if ( c == NULL )
+ return -1;
+
+ if ( c->cac_pb == NULL )
+ return -1;
+
+ *pb = c->cac_pb;
+
+ return 0;
+}
+
+Slapi_Mutex *slapi_new_mutex( void )
+{
+ Slapi_Mutex *m;
+
+ m = (Slapi_Mutex *)slapi_ch_malloc( sizeof(*m) );
+ if ( ldap_pvt_thread_mutex_init( &m->mutex ) != 0 ) {
+ slapi_ch_free( (void **)&m );
+ return NULL;
+ }
+
+ return m;
+}
+
+void slapi_destroy_mutex( Slapi_Mutex *mutex )
+{
+ if ( mutex != NULL ) {
+ ldap_pvt_thread_mutex_destroy( &mutex->mutex );
+ slapi_ch_free( (void **)&mutex);
+ }
+}
+
+void slapi_lock_mutex( Slapi_Mutex *mutex )
+{
+ ldap_pvt_thread_mutex_lock( &mutex->mutex );
+}
+
+int slapi_unlock_mutex( Slapi_Mutex *mutex )
+{
+ return ldap_pvt_thread_mutex_unlock( &mutex->mutex );
+}
+
+Slapi_CondVar *slapi_new_condvar( Slapi_Mutex *mutex )
+{
+ Slapi_CondVar *cv;
+
+ if ( mutex == NULL ) {
+ return NULL;
+ }
+
+ cv = (Slapi_CondVar *)slapi_ch_malloc( sizeof(*cv) );
+ if ( ldap_pvt_thread_cond_init( &cv->cond ) != 0 ) {
+ slapi_ch_free( (void **)&cv );
+ return NULL;
+ }
+
+ cv->mutex = mutex->mutex;
+
+ return cv;
+}
+
+void slapi_destroy_condvar( Slapi_CondVar *cvar )
+{
+ if ( cvar != NULL ) {
+ ldap_pvt_thread_cond_destroy( &cvar->cond );
+ slapi_ch_free( (void **)&cvar );
+ }
+}
+
+int slapi_wait_condvar( Slapi_CondVar *cvar, struct timeval *timeout )
+{
+ if ( cvar == NULL ) {
+ return -1;
+ }
+
+ return ldap_pvt_thread_cond_wait( &cvar->cond, &cvar->mutex );
+}
+
+int slapi_notify_condvar( Slapi_CondVar *cvar, int notify_all )
+{
+ if ( cvar == NULL ) {
+ return -1;
+ }
+
+ if ( notify_all ) {
+ return ldap_pvt_thread_cond_broadcast( &cvar->cond );
+ }
+
+ return ldap_pvt_thread_cond_signal( &cvar->cond );
+}
+
+int slapi_int_access_allowed( Operation *op,
+ Entry *entry,
+ AttributeDescription *desc,
+ struct berval *val,
+ slap_access_t access,
+ AccessControlState *state )
+{
+ int rc, slap_access = 0;
+ slapi_acl_callback_t *pGetPlugin, *tmpPlugin;
+ Slapi_PBlock *pb;
+
+ pb = SLAPI_OPERATION_PBLOCK( op );
+ if ( pb == NULL ) {
+ /* internal operation */
+ return 1;
+ }
+
+ switch ( access ) {
+ case ACL_COMPARE:
+ slap_access |= SLAPI_ACL_COMPARE;
+ break;
+ case ACL_SEARCH:
+ slap_access |= SLAPI_ACL_SEARCH;
+ break;
+ case ACL_READ:
+ slap_access |= SLAPI_ACL_READ;
+ break;
+ case ACL_WRITE:
+ slap_access |= SLAPI_ACL_WRITE;
+ break;
+ case ACL_WDEL:
+ slap_access |= SLAPI_ACL_DELETE;
+ break;
+ case ACL_WADD:
+ slap_access |= SLAPI_ACL_ADD;
+ break;
+ default:
+ break;
+ }
+
+ rc = slapi_int_get_plugins( frontendDB, SLAPI_PLUGIN_ACL_ALLOW_ACCESS, (SLAPI_FUNC **)&tmpPlugin );
+ if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) {
+ /* nothing to do; allowed access */
+ return 1;
+ }
+
+ rc = 1; /* default allow policy */
+
+ for ( pGetPlugin = tmpPlugin; *pGetPlugin != NULL; pGetPlugin++ ) {
+ /*
+ * 0 access denied
+ * 1 access granted
+ */
+ rc = (*pGetPlugin)( pb, entry, desc->ad_cname.bv_val,
+ val, slap_access, (void *)state );
+ if ( rc == 0 ) {
+ break;
+ }
+ }
+
+ slapi_ch_free( (void **)&tmpPlugin );
+
+ return rc;
+}
+
+/*
+ * There is no documentation for this.
+ */
+int slapi_rdn2typeval( char *rdn, char **type, struct berval *bv )
+{
+ LDAPRDN lrdn;
+ LDAPAVA *ava;
+ int rc;
+ char *p;
+
+ *type = NULL;
+
+ bv->bv_len = 0;
+ bv->bv_val = NULL;
+
+ rc = ldap_str2rdn( rdn, &lrdn, &p, LDAP_DN_FORMAT_LDAPV3 );
+ if ( rc != LDAP_SUCCESS ) {
+ return -1;
+ }
+
+ if ( lrdn[1] != NULL ) {
+ return -1; /* not single valued */
+ }
+
+ ava = lrdn[0];
+
+ *type = slapi_ch_strdup( ava->la_attr.bv_val );
+ ber_dupbv( bv, &ava->la_value );
+
+ ldap_rdnfree(lrdn);
+
+ return 0;
+}
+
+char *slapi_dn_plus_rdn( const char *dn, const char *rdn )
+{
+ struct berval new_dn, parent_dn, newrdn;
+
+ new_dn.bv_val = NULL;
+
+ parent_dn.bv_val = (char *)dn;
+ parent_dn.bv_len = strlen( dn );
+
+ newrdn.bv_val = (char *)rdn;
+ newrdn.bv_len = strlen( rdn );
+
+ build_new_dn( &new_dn, &parent_dn, &newrdn, NULL );
+
+ return new_dn.bv_val;
+}
+
+int slapi_entry_schema_check( Slapi_PBlock *pb, Slapi_Entry *e )
+{
+ Backend *be_orig;
+ const char *text;
+ char textbuf[SLAP_TEXT_BUFLEN] = { '\0' };
+ size_t textlen = sizeof textbuf;
+ int rc = LDAP_SUCCESS;
+
+ PBLOCK_ASSERT_OP( pb, 0 );
+
+ be_orig = pb->pb_op->o_bd;
+
+ pb->pb_op->o_bd = select_backend( &e->e_nname, 0 );
+ if ( pb->pb_op->o_bd != NULL ) {
+ rc = entry_schema_check( pb->pb_op, e, NULL, 0, 0, NULL,
+ &text, textbuf, textlen );
+ }
+ pb->pb_op->o_bd = be_orig;
+
+ return ( rc == LDAP_SUCCESS ) ? 0 : 1;
+}
+
+int slapi_entry_rdn_values_present( const Slapi_Entry *e )
+{
+ LDAPDN dn;
+ int rc;
+ int i = 0, match = 0;
+
+ rc = ldap_bv2dn( &((Entry *)e)->e_name, &dn, LDAP_DN_FORMAT_LDAPV3 );
+ if ( rc != LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ if ( dn[0] != NULL ) {
+ LDAPRDN rdn = dn[0];
+
+ for ( i = 0; rdn[i] != NULL; i++ ) {
+ LDAPAVA *ava = &rdn[0][i];
+ Slapi_Attr *a = NULL;
+
+ if ( slapi_entry_attr_find( (Slapi_Entry *)e, ava->la_attr.bv_val, &a ) == 0 &&
+ slapi_attr_value_find( a, &ava->la_value ) == 0 )
+ match++;
+ }
+ }
+
+ ldap_dnfree( dn );
+
+ return ( i == match );
+}
+
+int slapi_entry_add_rdn_values( Slapi_Entry *e )
+{
+ LDAPDN dn;
+ int i, rc;
+
+ rc = ldap_bv2dn( &e->e_name, &dn, LDAP_DN_FORMAT_LDAPV3 );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( dn[0] != NULL ) {
+ LDAPRDN rdn = dn[0];
+ struct berval *vals[2];
+
+ for ( i = 0; rdn[i] != NULL; i++ ) {
+ LDAPAVA *ava = &rdn[0][i];
+ Slapi_Attr *a = NULL;
+
+ if ( slapi_entry_attr_find( e, ava->la_attr.bv_val, &a ) == 0 &&
+ slapi_attr_value_find( a, &ava->la_value ) == 0 )
+ continue;
+
+ vals[0] = &ava->la_value;
+ vals[1] = NULL;
+
+ slapi_entry_attr_merge( e, ava->la_attr.bv_val, vals );
+ }
+ }
+
+ ldap_dnfree( dn );
+
+ return LDAP_SUCCESS;
+}
+
+const char *slapi_entry_get_uniqueid( const Slapi_Entry *e )
+{
+ Attribute *attr;
+
+ attr = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( attr == NULL ) {
+ return NULL;
+ }
+
+ if ( attr->a_vals != NULL && attr->a_vals[0].bv_len != 0 ) {
+ return slapi_value_get_string( &attr->a_vals[0] );
+ }
+
+ return NULL;
+}
+
+void slapi_entry_set_uniqueid( Slapi_Entry *e, char *uniqueid )
+{
+ struct berval bv;
+
+ attr_delete ( &e->e_attrs, slap_schema.si_ad_entryUUID );
+
+ bv.bv_val = uniqueid;
+ bv.bv_len = strlen( uniqueid );
+ attr_merge_normalize_one( e, slap_schema.si_ad_entryUUID, &bv, NULL );
+}
+
+LDAP *slapi_ldap_init( char *ldaphost, int ldapport, int secure, int shared )
+{
+ LDAP *ld;
+ char *url;
+ size_t size;
+ int rc;
+
+ size = sizeof("ldap:///");
+ if ( secure ) {
+ size++;
+ }
+ size += strlen( ldaphost );
+ if ( ldapport != 0 ) {
+ size += 32;
+ }
+
+ url = slapi_ch_malloc( size );
+
+ if ( ldapport != 0 ) {
+ rc = snprintf( url, size, "ldap%s://%s:%d/", ( secure ? "s" : "" ), ldaphost, ldapport );
+ } else {
+ rc = snprintf( url, size, "ldap%s://%s/", ( secure ? "s" : "" ), ldaphost );
+ }
+
+ if ( rc > 0 && (size_t) rc < size ) {
+ rc = ldap_initialize( &ld, url );
+ } else {
+ ld = NULL;
+ }
+
+ slapi_ch_free_string( &url );
+
+ return ( rc == LDAP_SUCCESS ) ? ld : NULL;
+}
+
+void slapi_ldap_unbind( LDAP *ld )
+{
+ ldap_unbind_ext_s( ld, NULL, NULL );
+}
+
+int slapi_x_backend_get_flags( const Slapi_Backend *be, unsigned long *flags )
+{
+ if ( be == NULL )
+ return LDAP_PARAM_ERROR;
+
+ *flags = SLAP_DBFLAGS(be);
+
+ return LDAP_SUCCESS;
+}
+
+int
+slapi_int_count_controls( LDAPControl **ctrls )
+{
+ size_t i;
+
+ if ( ctrls == NULL )
+ return 0;
+
+ for ( i = 0; ctrls[i] != NULL; i++ )
+ ;
+
+ return i;
+}
+
+int
+slapi_op_abandoned( Slapi_PBlock *pb )
+{
+ if ( pb->pb_op == NULL )
+ return 0;
+
+ return ( pb->pb_op->o_abandon );
+}
+
+char *
+slapi_op_type_to_string(unsigned long type)
+{
+ char *str;
+
+ switch (type) {
+ case SLAPI_OPERATION_BIND:
+ str = "bind";
+ break;
+ case SLAPI_OPERATION_UNBIND:
+ str = "unbind";
+ break;
+ case SLAPI_OPERATION_SEARCH:
+ str = "search";
+ break;
+ case SLAPI_OPERATION_MODIFY:
+ str = "modify";
+ break;
+ case SLAPI_OPERATION_ADD:
+ str = "add";
+ break;
+ case SLAPI_OPERATION_DELETE:
+ str = "delete";
+ break;
+ case SLAPI_OPERATION_MODDN:
+ str = "modrdn";
+ break;
+ case SLAPI_OPERATION_COMPARE:
+ str = "compare";
+ break;
+ case SLAPI_OPERATION_ABANDON:
+ str = "abandon";
+ break;
+ case SLAPI_OPERATION_EXTENDED:
+ str = "extended";
+ break;
+ default:
+ str = "unknown operation type";
+ break;
+ }
+ return str;
+}
+
+unsigned long
+slapi_op_get_type(Slapi_Operation * op)
+{
+ unsigned long type;
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND:
+ type = SLAPI_OPERATION_BIND;
+ break;
+ case LDAP_REQ_UNBIND:
+ type = SLAPI_OPERATION_UNBIND;
+ break;
+ case LDAP_REQ_SEARCH:
+ type = SLAPI_OPERATION_SEARCH;
+ break;
+ case LDAP_REQ_MODIFY:
+ type = SLAPI_OPERATION_MODIFY;
+ break;
+ case LDAP_REQ_ADD:
+ type = SLAPI_OPERATION_ADD;
+ break;
+ case LDAP_REQ_DELETE:
+ type = SLAPI_OPERATION_DELETE;
+ break;
+ case LDAP_REQ_MODRDN:
+ type = SLAPI_OPERATION_MODDN;
+ break;
+ case LDAP_REQ_COMPARE:
+ type = SLAPI_OPERATION_COMPARE;
+ break;
+ case LDAP_REQ_ABANDON:
+ type = SLAPI_OPERATION_ABANDON;
+ break;
+ case LDAP_REQ_EXTENDED:
+ type = SLAPI_OPERATION_EXTENDED;
+ break;
+ default:
+ type = SLAPI_OPERATION_NONE;
+ break;
+ }
+ return type;
+}
+
+void slapi_be_set_readonly( Slapi_Backend *be, int readonly )
+{
+ if ( be == NULL )
+ return;
+
+ if ( readonly )
+ be->be_restrictops |= SLAP_RESTRICT_OP_WRITES;
+ else
+ be->be_restrictops &= ~(SLAP_RESTRICT_OP_WRITES);
+}
+
+int slapi_be_get_readonly( Slapi_Backend *be )
+{
+ if ( be == NULL )
+ return 0;
+
+ return ( (be->be_restrictops & SLAP_RESTRICT_OP_WRITES) == SLAP_RESTRICT_OP_WRITES );
+}
+
+const char *slapi_x_be_get_updatedn( Slapi_Backend *be )
+{
+ if ( be == NULL )
+ return NULL;
+
+ return be->be_update_ndn.bv_val;
+}
+
+Slapi_Backend *slapi_be_select( const Slapi_DN *sdn )
+{
+ Slapi_Backend *be;
+
+ slapi_sdn_get_ndn( sdn );
+
+ be = select_backend( (struct berval *)&sdn->ndn, 0 );
+
+ return be;
+}
+
+#if 0
+void
+slapi_operation_set_flag(Slapi_Operation *op, unsigned long flag)
+{
+}
+
+void
+slapi_operation_clear_flag(Slapi_Operation *op, unsigned long flag)
+{
+}
+
+int
+slapi_operation_is_flag_set(Slapi_Operation *op, unsigned long flag)
+{
+}
+#endif
+
+#endif /* LDAP_SLAPI */
+
diff --git a/servers/slapd/slapindex.c b/servers/slapd/slapindex.c
new file mode 100644
index 0000000..8400e82
--- /dev/null
+++ b/servers/slapd/slapindex.c
@@ -0,0 +1,110 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Kurt Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+
+#include "slapcommon.h"
+
+int
+slapindex( int argc, char **argv )
+{
+ ID id;
+ int rc = EXIT_SUCCESS;
+ const char *progname = "slapindex";
+ AttributeDescription *ad, **adv = NULL;
+
+ slap_tool_init( progname, SLAPINDEX, argc, argv );
+
+ if( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !( be->be_entry_first || be->be_entry_first_x ) ||
+ !be->be_entry_next ||
+ !be->be_entry_reindex )
+ {
+ fprintf( stderr, "%s: database doesn't support necessary operations.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ argc -= optind;
+ if ( argc > 0 ) {
+ const char *text;
+ int i;
+
+ argv = &argv[optind];
+ adv = (AttributeDescription **)argv;
+
+ for (i = 0; i < argc; i++ ) {
+ ad = NULL;
+ rc = slap_str2ad( argv[i], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "slap_str2ad(%s) failed %d (%s)\n",
+ argv[i], rc, ldap_err2string( rc ));
+ exit( EXIT_FAILURE );
+ }
+ adv[i] = ad;
+ }
+ }
+
+ if( be->be_entry_open( be, 0 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( be->be_entry_first ) {
+ id = be->be_entry_first( be );
+
+ } else {
+ assert( be->be_entry_first_x != NULL );
+ id = be->be_entry_first_x( be, NULL, LDAP_SCOPE_DEFAULT, NULL );
+ }
+
+ for ( ; id != NOID; id = be->be_entry_next( be ) ) {
+ int rtn;
+
+ if( verbose ) {
+ printf("indexing id=%08lx\n", (long) id );
+ }
+
+ rtn = be->be_entry_reindex( be, id, adv );
+
+ if( rtn != LDAP_SUCCESS ) {
+ rc = EXIT_FAILURE;
+ if( continuemode ) continue;
+ break;
+ }
+ }
+
+ (void) be->be_entry_close( be );
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+ return( rc );
+}
diff --git a/servers/slapd/slapmodify.c b/servers/slapd/slapmodify.c
new file mode 100644
index 0000000..d59eba6
--- /dev/null
+++ b/servers/slapd/slapmodify.c
@@ -0,0 +1,650 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/string.h"
+#include "ac/socket.h"
+#include "ac/unistd.h"
+
+#include "lber.h"
+#include "ldif.h"
+#include "lutil.h"
+#include "lutil_meter.h"
+#include <sys/stat.h>
+
+#include "slapcommon.h"
+
+extern int slap_DN_strict; /* dn.c */
+
+static char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+
+int
+slapmodify( int argc, char **argv )
+{
+ char *buf = NULL;
+ const char *text;
+ char textbuf[SLAP_TEXT_BUFLEN] = { '\0' };
+ size_t textlen = sizeof textbuf;
+ const char *progname = "slapmodify";
+
+ struct berval csn;
+ unsigned long sid;
+ struct berval bvtext;
+ ID id;
+ OperationBuffer opbuf;
+ Operation *op;
+
+ int checkvals, ldifrc;
+ unsigned long lineno, nextline;
+ int lmax;
+ int rc = EXIT_SUCCESS;
+
+ int enable_meter = 0;
+ lutil_meter_t meter;
+ struct stat stat_buf;
+
+ /* default "000" */
+ csnsid = 0;
+
+ if ( isatty (2) ) enable_meter = 1;
+ slap_tool_init( progname, SLAPMODIFY, argc, argv );
+
+ memset( &opbuf, 0, sizeof(opbuf) );
+ op = &opbuf.ob_op;
+ op->o_hdr = &opbuf.ob_hdr;
+ op->o_bd = be;
+
+ if ( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !be->be_entry_put ||
+ !be->be_dn2id_get ||
+ !be->be_entry_get ||
+ !be->be_entry_modify )
+ {
+ fprintf( stderr, "%s: database doesn't support necessary operations.\n",
+ progname );
+ if ( dryrun ) {
+ fprintf( stderr, "\t(dry) continuing...\n" );
+
+ } else {
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ checkvals = (slapMode & SLAP_TOOL_QUICK) ? 0 : 1;
+
+ lmax = 0;
+ nextline = 0;
+
+ /* enforce schema checking unless not disabled and allow unknown
+ * attributes otherwise */
+ if ( (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) == 0) {
+ SLAP_DBFLAGS(be) &= ~(SLAP_DBFLAG_NO_SCHEMA_CHECK);
+ } else {
+ slap_DN_strict = 0;
+ }
+
+ if( !dryrun && be->be_entry_open( be, 1 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ (void)slap_tool_update_ctxcsn_init();
+
+ if ( enable_meter
+#ifdef LDAP_DEBUG
+ /* tools default to "none" */
+ && slap_debug == LDAP_DEBUG_NONE
+#endif
+ && !fstat ( fileno ( ldiffp->fp ), &stat_buf )
+ && S_ISREG(stat_buf.st_mode) ) {
+ enable_meter = !lutil_meter_open(
+ &meter,
+ &lutil_meter_text_display,
+ &lutil_meter_linear_estimator,
+ stat_buf.st_size);
+ } else {
+ enable_meter = 0;
+ }
+
+ /* nextline is the line number of the end of the current entry */
+ for( lineno=1; ( ldifrc = ldif_read_record( ldiffp, &nextline, &buf, &lmax )) > 0;
+ lineno=nextline+1 )
+ {
+ BackendDB *bd;
+ Entry *e_orig = NULL, *e = NULL;
+ struct berval rbuf;
+ LDIFRecord lr;
+ struct berval ndn = BER_BVNULL;
+ int n;
+ int is_oc = 0;
+ int local_rc;
+ int mod_err = 0;
+ char *request = "(unknown)";
+
+ ber_str2bv( buf, 0, 0, &rbuf );
+
+ if ( lineno < jumpline )
+ continue;
+
+ if ( enable_meter )
+ lutil_meter_update( &meter,
+ ftell( ldiffp->fp ),
+ 0);
+
+ /*
+ * Initialize text buffer
+ */
+ bvtext.bv_len = textlen;
+ bvtext.bv_val = textbuf;
+ bvtext.bv_val[0] = '\0';
+
+ local_rc = ldap_parse_ldif_record( &rbuf, lineno, &lr,
+ "slapmodify", LDIF_NO_CONTROLS );
+
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: could not parse entry (line=%lu)\n",
+ progname, lineno );
+ rc = EXIT_FAILURE;
+ if( continuemode ) continue;
+ break;
+ }
+
+ switch ( lr.lr_op ) {
+ case LDAP_REQ_ADD:
+ request = "add";
+ break;
+
+ case LDAP_REQ_MODIFY:
+ request = "modify";
+ break;
+
+ case LDAP_REQ_DELETE:
+ if ( be->be_entry_delete )
+ {
+ request = "delete";
+ break;
+ }
+ /* backend does not support delete, fallthru */
+
+ case LDAP_REQ_MODRDN:
+ fprintf( stderr, "%s: request 0x%lx not supported (line=%lu)\n",
+ progname, (unsigned long)lr.lr_op, lineno );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+
+ default:
+ /* record skipped e.g. version: or comment or something we don't handle yet */
+ goto cleanup;
+ }
+
+ local_rc = dnNormalize( 0, NULL, NULL, &lr.lr_dn, &ndn, NULL );
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: DN=\"%s\" normalization failed (line=%lu)\n",
+ progname, lr.lr_dn.bv_val, lineno );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* make sure the DN is not empty */
+ if( BER_BVISEMPTY( &ndn ) &&
+ !BER_BVISEMPTY( be->be_nsuffix ))
+ {
+ fprintf( stderr, "%s: line %lu: "
+ "%s entry with empty dn=\"\"",
+ progname, lineno, request );
+ bd = select_backend( &ndn, nosubordinates );
+ if ( bd ) {
+ BackendDB *bdtmp;
+ int dbidx = 0;
+ LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) {
+ if ( bdtmp == bd ) break;
+ dbidx++;
+ }
+
+ assert( bdtmp != NULL );
+
+ fprintf( stderr, "; did you mean to use database #%d (%s)?",
+ dbidx,
+ bd->be_suffix[0].bv_val );
+
+ }
+ fprintf( stderr, "\n" );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* check backend */
+ bd = select_backend( &ndn, nosubordinates );
+ if ( bd != be ) {
+ fprintf( stderr, "%s: line %lu: "
+ "database #%d (%s) not configured to hold \"%s\"",
+ progname, lineno,
+ dbnum,
+ be->be_suffix[0].bv_val,
+ lr.lr_dn.bv_val );
+ if ( bd ) {
+ BackendDB *bdtmp;
+ int dbidx = 0;
+ LDAP_STAILQ_FOREACH( bdtmp, &backendDB, be_next ) {
+ if ( bdtmp == bd ) break;
+ dbidx++;
+ }
+
+ assert( bdtmp != NULL );
+
+ fprintf( stderr, "; did you mean to use database #%d (%s)?",
+ dbidx,
+ bd->be_suffix[0].bv_val );
+
+ } else {
+ fprintf( stderr, "; no database configured for that naming context" );
+ }
+ fprintf( stderr, "\n" );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ /* get id and/or entry */
+ switch ( lr.lr_op ) {
+ case LDAP_REQ_ADD:
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &lr.lr_dn );
+ ber_dupbv( &e->e_nname, &ndn );
+ break;
+
+ //case LDAP_REQ_MODRDN:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODIFY:
+ id = be->be_dn2id_get( be, &ndn );
+ rc = (id == NOID);
+ if ( rc == LDAP_SUCCESS && lr.lr_op != LDAP_REQ_DELETE ) {
+ e_orig = be->be_entry_get( be, id );
+ if ( e_orig )
+ e = entry_dup( e_orig );
+ rc = (e == NULL);
+ }
+ break;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: no such entry \"%s\" in database (lineno=%lu)\n",
+ progname, ndn.bv_val, lineno );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ if ( lr.lrop_mods ) {
+ for ( n = 0; lr.lrop_mods && lr.lrop_mods[ n ] != NULL; n++ ) {
+ LDAPMod *mod = lr.lrop_mods[ n ];
+ Modification mods = { 0 };
+ unsigned i = 0;
+ int bin = (mod->mod_op & LDAP_MOD_BVALUES);
+ int pretty = 0;
+ int normalize = 0;
+
+ local_rc = slap_str2ad( mod->mod_type, &mods.sm_desc, &text );
+ /*
+ * Usually this would be a bad idea (way too dangerous, risks
+ * corrupting the DB), but ITS#7786 documents this as a last
+ * resort to fix cn=config and missing attributes are one of
+ * the possible issues we might encounter.
+ */
+ if ( local_rc == LDAP_UNDEFINED_TYPE &&
+ (slapMode & SLAP_TOOL_NO_SCHEMA_CHECK) ) {
+ local_rc = slap_str2undef_ad( mod->mod_type, &mods.sm_desc, &text, 0 );
+ }
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: slap_str2ad(\"%s\") failed for entry \"%s\" (%d: %s, lineno=%lu)\n",
+ progname, mod->mod_type, lr.lr_dn.bv_val, local_rc, text, lineno );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ mods.sm_type = mods.sm_desc->ad_cname;
+
+ if ( mods.sm_desc->ad_type->sat_syntax->ssyn_pretty ) {
+ pretty = 1;
+
+ } else {
+ assert( mods.sm_desc->ad_type->sat_syntax->ssyn_validate != NULL );
+ }
+
+ if ( mods.sm_desc->ad_type->sat_equality &&
+ mods.sm_desc->ad_type->sat_equality->smr_normalize )
+ {
+ normalize = 1;
+ }
+
+ if ( bin && mod->mod_bvalues ) {
+ for ( i = 0; mod->mod_bvalues[ i ] != NULL; i++ )
+ ;
+
+ } else if ( !bin && mod->mod_values ) {
+ for ( i = 0; mod->mod_values[ i ] != NULL; i++ )
+ ;
+ }
+
+ if ( i != 0 )
+ {
+ mods.sm_values = ch_calloc( sizeof( struct berval ), i + 1 );
+ if ( normalize ) {
+ mods.sm_nvalues = ch_calloc( sizeof( struct berval ), i + 1 );
+ } else {
+ mods.sm_nvalues = NULL;
+ }
+ }
+ mods.sm_numvals = i;
+
+ for ( i = 0; i < mods.sm_numvals; i++ ) {
+ struct berval bv;
+
+ if ( bin ) {
+ bv = *mod->mod_bvalues[ i ];
+ } else {
+ ber_str2bv( mod->mod_values[ i ], 0, 0, &bv );
+ }
+
+ if ( pretty ) {
+ local_rc = ordered_value_pretty( mods.sm_desc,
+ &bv, &mods.sm_values[i], NULL );
+
+ } else {
+ local_rc = ordered_value_validate( mods.sm_desc,
+ &bv, 0 );
+ }
+
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: DN=\"%s\": unable to %s attr=%s value #%d\n",
+ progname, e->e_dn, pretty ? "prettify" : "validate",
+ mods.sm_desc->ad_cname.bv_val, i );
+ /* handle error */
+ rc = EXIT_FAILURE;
+ ber_bvarray_free( mods.sm_values );
+ ber_bvarray_free( mods.sm_nvalues );
+ goto cleanup;
+ }
+
+ if ( !pretty ) {
+ ber_dupbv( &mods.sm_values[i], &bv );
+ }
+
+ if ( normalize ) {
+ local_rc = ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mods.sm_desc,
+ mods.sm_desc->ad_type->sat_equality,
+ &mods.sm_values[i], &mods.sm_nvalues[i],
+ NULL );
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: DN=\"%s\": unable to normalize attr=%s value #%d\n",
+ progname, e->e_dn, mods.sm_desc->ad_cname.bv_val, i );
+ /* handle error */
+ rc = EXIT_FAILURE;
+ ber_bvarray_free( mods.sm_values );
+ ber_bvarray_free( mods.sm_nvalues );
+ goto cleanup;
+ }
+ }
+ }
+
+ mods.sm_op = (mod->mod_op & ~LDAP_MOD_BVALUES);
+ mods.sm_flags = 0;
+
+ if ( mods.sm_desc == slap_schema.si_ad_objectClass ) {
+ is_oc = 1;
+ }
+
+ switch ( mods.sm_op ) {
+ case LDAP_MOD_ADD:
+ local_rc = modify_add_values( e, &mods,
+ 0, &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_DELETE:
+ local_rc = modify_delete_values( e, &mods,
+ 0, &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ local_rc = modify_replace_values( e, &mods,
+ 0, &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ local_rc = modify_increment_values( e, &mods,
+ 0, &text, textbuf, textlen );
+ break;
+ }
+
+ ber_bvarray_free( mods.sm_values );
+ ber_bvarray_free( mods.sm_nvalues );
+
+ if ( local_rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: DN=\"%s\": unable to modify attr=%s\n",
+ progname, e->e_dn, mods.sm_desc->ad_cname.bv_val );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ rc = slap_tool_entry_check( progname, op, e, lineno, &text, textbuf, textlen );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ if ( SLAP_LASTMOD(be) && e != NULL ) {
+ time_t now = slap_get_time();
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+ struct berval vals[ 2 ];
+
+ struct berval name, timestamp;
+
+ struct berval nvals[ 2 ];
+ struct berval nname;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+
+ Attribute *a;
+
+ vals[1].bv_len = 0;
+ vals[1].bv_val = NULL;
+
+ nvals[1].bv_len = 0;
+ nvals[1].bv_val = NULL;
+
+ csn.bv_len = ldap_pvt_csnstr( csnbuf, sizeof( csnbuf ), csnsid, 0 );
+ csn.bv_val = csnbuf;
+
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = sizeof(timebuf);
+
+ slap_timestamp( &now, &timestamp );
+
+ if ( BER_BVISEMPTY( &be->be_rootndn ) ) {
+ BER_BVSTR( &name, SLAPD_ANONYMOUS );
+ nname = name;
+ } else {
+ name = be->be_rootdn;
+ nname = be->be_rootndn;
+ }
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a != NULL ) {
+ if ( a->a_vals != a->a_nvals ) {
+ SLAP_FREE( a->a_nvals[0].bv_val );
+ SLAP_FREE( a->a_nvals );
+ }
+ SLAP_FREE( a->a_vals[0].bv_val );
+ SLAP_FREE( a->a_vals );
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ a->a_numvals = 0;
+ }
+ vals[0].bv_len = lutil_uuidstr( uuidbuf, sizeof( uuidbuf ) );
+ vals[0].bv_val = uuidbuf;
+ attr_merge_normalize_one( e, slap_schema.si_ad_entryUUID, vals, NULL );
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_creatorsName );
+ if ( a == NULL ) {
+ vals[0] = name;
+ nvals[0] = nname;
+ attr_merge( e, slap_schema.si_ad_creatorsName, vals, nvals );
+
+ } else {
+ ber_bvreplace( &a->a_vals[0], &name );
+ ber_bvreplace( &a->a_nvals[0], &nname );
+ }
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_createTimestamp );
+ if ( a == NULL ) {
+ vals[0] = timestamp;
+ attr_merge( e, slap_schema.si_ad_createTimestamp, vals, NULL );
+
+ } else {
+ ber_bvreplace( &a->a_vals[0], &timestamp );
+ }
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a == NULL ) {
+ vals[0] = csn;
+ attr_merge( e, slap_schema.si_ad_entryCSN, vals, NULL );
+
+ } else {
+ ber_bvreplace( &a->a_vals[0], &csn );
+ }
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_modifiersName );
+ if ( a == NULL ) {
+ vals[0] = name;
+ nvals[0] = nname;
+ attr_merge( e, slap_schema.si_ad_modifiersName, vals, nvals );
+
+ } else {
+ ber_bvreplace( &a->a_vals[0], &name );
+ ber_bvreplace( &a->a_nvals[0], &nname );
+ }
+
+ a = attr_find( e->e_attrs, slap_schema.si_ad_modifyTimestamp );
+ if ( a == NULL ) {
+ vals[0] = timestamp;
+ attr_merge( e, slap_schema.si_ad_modifyTimestamp, vals, NULL );
+
+ } else {
+ ber_bvreplace( &a->a_vals[0], &timestamp );
+ }
+ }
+
+ /* check schema, objectClass etc */
+
+ if ( !dryrun ) {
+ switch ( lr.lr_op ) {
+ case LDAP_REQ_ADD:
+ id = be->be_entry_put( be, e, &bvtext );
+ rc = (id == NOID);
+ break;
+
+ case LDAP_REQ_MODIFY:
+ id = be->be_entry_modify( be, e, &bvtext );
+ rc = (id == NOID);
+ break;
+
+ case LDAP_REQ_DELETE:
+ rc = be->be_entry_delete( be, &ndn, &bvtext );
+ break;
+
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ fprintf( stderr, "%s: could not %s entry dn=\"%s\" "
+ "(line=%lu): %s\n", progname, request, ndn.bv_val,
+ lineno, bvtext.bv_val );
+ rc = EXIT_FAILURE;
+ goto cleanup;
+ }
+
+ sid = slap_tool_update_ctxcsn_check( progname, e );
+
+ if ( verbose )
+ fprintf( stderr, "%s: \"%s\" (%08lx)\n",
+ request, ndn.bv_val, (long) id );
+ } else {
+ if ( verbose )
+ fprintf( stderr, "%s: \"%s\"\n",
+ request, ndn.bv_val );
+ }
+
+cleanup:;
+ ldap_ldif_record_done( &lr );
+ SLAP_FREE( ndn.bv_val );
+ if ( e ) entry_free( e );
+ if ( e_orig ) be_entry_release_w( op, e_orig );
+ if ( rc != LDAP_SUCCESS && !continuemode ) break;
+ }
+
+ if ( ldifrc < 0 )
+ rc = EXIT_FAILURE;
+
+ bvtext.bv_len = textlen;
+ bvtext.bv_val = textbuf;
+ bvtext.bv_val[0] = '\0';
+
+ if ( enable_meter ) {
+ lutil_meter_update( &meter, ftell( ldiffp->fp ), 1);
+ lutil_meter_close( &meter );
+ }
+
+ if ( rc == EXIT_SUCCESS ) {
+ rc = slap_tool_update_ctxcsn( progname, sid, &bvtext );
+ }
+
+ ch_free( buf );
+
+ if ( !dryrun ) {
+ if ( enable_meter ) {
+ fprintf( stderr, "Closing DB..." );
+ }
+ if( be->be_entry_close( be ) ) {
+ rc = EXIT_FAILURE;
+ }
+
+ if( be->be_sync ) {
+ be->be_sync( be );
+ }
+ if ( enable_meter ) {
+ fprintf( stderr, "\n" );
+ }
+ }
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
+
diff --git a/servers/slapd/slappasswd.c b/servers/slapd/slappasswd.c
new file mode 100644
index 0000000..e384364
--- /dev/null
+++ b/servers/slapd/slappasswd.c
@@ -0,0 +1,306 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Kurt Zeilenga for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <ldap.h>
+#include <lber_pvt.h>
+#include <lutil.h>
+#include <lutil_sha1.h>
+
+#include "ldap_defaults.h"
+
+#include "slap.h"
+#include "slap-config.h"
+#include "slapcommon.h"
+
+static char *modulepath = NULL;
+static char *moduleload = NULL;
+static int moduleargc = 0;
+static char **moduleargv = NULL;
+
+static void
+usage(const char *s)
+{
+ fprintf(stderr,
+ "Usage: %s [options]\n"
+ " -c format\tcrypt(3) salt format\n"
+ " -g\t\tgenerate random password\n"
+ " -h hash\tpassword scheme\n"
+ " -n\t\tomit trailing newline\n"
+ " -o <opt>[=val] specify an option with a(n optional) value\n"
+ " \tmodule-path=<pathspec>\n"
+ " \tmodule-load=<filename>\n"
+ " -s secret\tnew password\n"
+ " -u\t\tgenerate RFC2307 values (default)\n"
+ " -v\t\tincrease verbosity\n"
+ " -T file\tread file for new password\n"
+ , s );
+
+ exit( EXIT_FAILURE );
+}
+
+static int
+parse_slappasswdopt( void )
+{
+ size_t len = 0;
+ char *p;
+
+ p = strchr( optarg, '=' );
+ if ( p != NULL ) {
+ len = p - optarg;
+ p++;
+ }
+
+ if ( strncasecmp( optarg, "module-path", len ) == 0 ) {
+ modulepath = p;
+
+ } else if ( strncasecmp( optarg, "module-load", len ) == 0 ) {
+ ConfigArgs c = { .line = p };
+
+ if ( config_fp_parse_line( &c ) ) {
+ return -1;
+ }
+ moduleload = c.argv[0];
+
+ moduleargc = c.argc - 1;
+ if ( moduleargc ) {
+ moduleargv = c.argv+1;
+ }
+
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+slappasswd( int argc, char *argv[] )
+{
+ int rc = EXIT_SUCCESS;
+#ifdef LUTIL_SHA1_BYTES
+ char *default_scheme = "{SSHA}";
+#else
+ char *default_scheme = "{SMD5}";
+#endif
+ char *scheme = default_scheme;
+
+ char *newpw = NULL;
+ char *pwfile = NULL;
+ const char *text;
+ const char *progname = "slappasswd";
+
+ int i;
+ char *newline = "\n";
+ struct berval passwd = BER_BVNULL;
+ struct berval hash = BER_BVNULL;
+
+#ifdef LDAP_DEBUG
+ /* tools default to "none", so that at least LDAP_DEBUG_ANY
+ * messages show up; use -d 0 to reset */
+ slap_debug = LDAP_DEBUG_NONE;
+#endif
+ ldap_syslog = 0;
+
+ while( (i = getopt( argc, argv,
+ "c:d:gh:no:s:T:vu" )) != EOF )
+ {
+ switch (i) {
+ case 'c': /* crypt salt format */
+ scheme = "{CRYPT}";
+ lutil_salt_format( optarg );
+ break;
+
+ case 'g': /* new password (generate) */
+ if ( pwfile != NULL ) {
+ fprintf( stderr, "Option -g incompatible with -T\n" );
+ return EXIT_FAILURE;
+
+ } else if ( newpw != NULL ) {
+ fprintf( stderr, "New password already provided\n" );
+ return EXIT_FAILURE;
+
+ } else if ( lutil_passwd_generate( &passwd, 8 )) {
+ fprintf( stderr, "Password generation failed\n" );
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'h': /* scheme */
+ if ( scheme != default_scheme ) {
+ fprintf( stderr, "Scheme already provided\n" );
+ return EXIT_FAILURE;
+
+ } else {
+ scheme = optarg;
+ }
+ break;
+
+ case 'n':
+ newline = "";
+ break;
+
+ case 'o':
+ if ( parse_slappasswdopt() ) {
+ usage ( progname );
+ }
+ break;
+
+ case 's': /* new password (secret) */
+ if ( pwfile != NULL ) {
+ fprintf( stderr, "Option -s incompatible with -T\n" );
+ return EXIT_FAILURE;
+
+ } else if ( newpw != NULL ) {
+ fprintf( stderr, "New password already provided\n" );
+ return EXIT_FAILURE;
+
+ } else {
+ char* p;
+ newpw = ch_strdup( optarg );
+
+ for( p = optarg; *p != '\0'; p++ ) {
+ *p = '\0';
+ }
+ }
+ break;
+
+ case 'T': /* password file */
+ if ( pwfile != NULL ) {
+ fprintf( stderr, "Password file already provided\n" );
+ return EXIT_FAILURE;
+
+ } else if ( newpw != NULL ) {
+ fprintf( stderr, "Option -T incompatible with -s/-g\n" );
+ return EXIT_FAILURE;
+
+ }
+ pwfile = optarg;
+ break;
+
+ case 'u': /* RFC2307 userPassword */
+ break;
+
+ case 'v': /* verbose */
+ verbose++;
+ break;
+
+ default:
+ usage ( progname );
+ }
+ }
+ slapTool = SLAPPASSWD;
+
+ if( argc - optind != 0 ) {
+ usage( progname );
+ }
+
+#ifdef SLAPD_MODULES
+ if ( module_init() != 0 ) {
+ fprintf( stderr, "%s: module_init failed\n", progname );
+ return EXIT_FAILURE;
+ }
+
+ if ( modulepath && module_path(modulepath) ) {
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+
+ if ( moduleload && module_load(moduleload, moduleargc, moduleargv) ) {
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+#endif
+
+ if( pwfile != NULL ) {
+ if( lutil_get_filed_password( pwfile, &passwd )) {
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+ } else if ( BER_BVISEMPTY( &passwd )) {
+ if( newpw == NULL ) {
+ /* prompt for new password */
+ char *cknewpw;
+ newpw = getpassphrase("New password: ");
+ if ( newpw == NULL ) { /* Allow EOF to exit. */
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+ newpw = ch_strdup(newpw);
+ cknewpw = getpassphrase("Re-enter new password: ");
+ if( cknewpw == NULL || strcmp( newpw, cknewpw )) {
+ fprintf( stderr,
+ "Password values do not match\n" );
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+ }
+
+ passwd.bv_val = newpw;
+ passwd.bv_len = strlen(passwd.bv_val);
+ } else {
+ hash = passwd;
+ goto print_pw;
+ }
+
+ lutil_passwd_hash( &passwd, scheme, &hash, &text );
+ if ( BER_BVISNULL( &hash ) ) {
+ fprintf( stderr,
+ "Password generation failed for scheme %s: %s\n",
+ scheme, text ? text : "" );
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+
+ if( lutil_passwd( &hash, &passwd, NULL, &text ) ) {
+ fprintf( stderr, "Password verification failed. %s\n",
+ text ? text : "" );
+ rc = EXIT_FAILURE;
+ goto destroy;
+ }
+
+print_pw:;
+ printf( "%s%s" , hash.bv_val, newline );
+
+destroy:;
+#ifdef SLAPD_MODULES
+ module_kill();
+#endif
+ if ( !BER_BVISNULL( &hash ) ) {
+ ber_memfree( hash.bv_val );
+ }
+ if ( passwd.bv_val != hash.bv_val && !BER_BVISNULL( &passwd ) ) {
+ ber_memfree( passwd.bv_val );
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/slapschema.c b/servers/slapd/slapschema.c
new file mode 100644
index 0000000..f1d1d18
--- /dev/null
+++ b/servers/slapd/slapschema.c
@@ -0,0 +1,165 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1998-2003 Kurt D. Zeilenga.
+ * Portions Copyright 2003 IBM Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software. Code portions borrowed from slapcat.c;
+ * contributors are Kurt Zeilenga and Jong Hyuk Choi
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+#include "ac/ctype.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+
+#include "slapcommon.h"
+#include "ldif.h"
+
+static volatile sig_atomic_t gotsig;
+
+static RETSIGTYPE
+slapcat_sig( int sig )
+{
+ gotsig=1;
+}
+
+int
+slapschema( int argc, char **argv )
+{
+ ID id;
+ int rc = EXIT_SUCCESS;
+ const char *progname = "slapschema";
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op = NULL;
+ void *thrctx;
+ int requestBSF = 0;
+ int doBSF = 0;
+
+ slap_tool_init( progname, SLAPSCHEMA, argc, argv );
+
+ requestBSF = ( sub_ndn.bv_len || filter );
+
+#ifdef SIGPIPE
+ (void) SIGNAL( SIGPIPE, slapcat_sig );
+#endif
+#ifdef SIGHUP
+ (void) SIGNAL( SIGHUP, slapcat_sig );
+#endif
+ (void) SIGNAL( SIGINT, slapcat_sig );
+ (void) SIGNAL( SIGTERM, slapcat_sig );
+
+ if( !be->be_entry_open ||
+ !be->be_entry_close ||
+ !( be->be_entry_first || be->be_entry_first_x ) ||
+ !be->be_entry_next ||
+ !be->be_entry_get )
+ {
+ fprintf( stderr, "%s: database doesn't support necessary operations.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ if( be->be_entry_open( be, 0 ) != 0 ) {
+ fprintf( stderr, "%s: could not open database.\n",
+ progname );
+ exit( EXIT_FAILURE );
+ }
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+ op->o_tmpmemctx = NULL;
+ op->o_bd = be;
+
+
+ if ( !requestBSF && be->be_entry_first ) {
+ id = be->be_entry_first( be );
+
+ } else {
+ if ( be->be_entry_first_x ) {
+ id = be->be_entry_first_x( be,
+ sub_ndn.bv_len ? &sub_ndn : NULL, scope, filter );
+
+ } else {
+ assert( be->be_entry_first != NULL );
+ doBSF = 1;
+ id = be->be_entry_first( be );
+ }
+ }
+
+ for ( ; id != NOID; id = be->be_entry_next( be ) ) {
+ Entry* e;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+ const char *text = NULL;
+
+ if ( gotsig )
+ break;
+
+ e = be->be_entry_get( be, id );
+ if ( e == NULL ) {
+ printf("# no data for entry id=%08lx\n\n", (long) id );
+ rc = EXIT_FAILURE;
+ if( continuemode ) continue;
+ break;
+ }
+
+ if ( doBSF ) {
+ if ( sub_ndn.bv_len && !dnIsSuffixScope( &e->e_nname, &sub_ndn, scope ) )
+ {
+ be_entry_release_r( op, e );
+ continue;
+ }
+
+
+ if ( filter != NULL ) {
+ int rc = test_filter( NULL, e, filter );
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ be_entry_release_r( op, e );
+ continue;
+ }
+ }
+ }
+
+ if( verbose ) {
+ printf( "# id=%08lx\n", (long) id );
+ }
+
+ rc = entry_schema_check( op, e, NULL, 0, 0, NULL,
+ &text, textbuf, textlen );
+ if ( rc != LDAP_SUCCESS ) {
+ fprintf( ldiffp->fp, "# (%d) %s%s%s\n",
+ rc, ldap_err2string( rc ),
+ text ? ": " : "",
+ text ? text : "" );
+ fprintf( ldiffp->fp, "dn: %s\n\n", e->e_name.bv_val );
+ }
+
+ be_entry_release_r( op, e );
+ }
+
+ be->be_entry_close( be );
+
+ if ( slap_tool_destroy() )
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
diff --git a/servers/slapd/slaptest.c b/servers/slapd/slaptest.c
new file mode 100644
index 0000000..4992147
--- /dev/null
+++ b/servers/slapd/slaptest.c
@@ -0,0 +1,121 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ac/unistd.h>
+#include <ac/errno.h>
+
+#include <lber.h>
+#include <ldif.h>
+#include <lutil.h>
+
+#include "slapcommon.h"
+
+#ifndef S_IWRITE
+#define S_IWRITE S_IWUSR
+#endif
+
+static int
+test_file( const char *fname, const char *ftype )
+{
+ struct stat st;
+ char ebuf[128];
+ int save_errno;
+
+ switch ( stat( fname, &st ) ) {
+ case 0:
+ if ( !( st.st_mode & S_IWRITE ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s file "
+ "\"%s\" exists, but user does not have access\n",
+ ftype, fname );
+ return -1;
+ }
+ break;
+
+ case -1:
+ default:
+ save_errno = errno;
+ if ( save_errno == ENOENT ) {
+ FILE *fp = fopen( fname, "w" );
+
+ if ( fp == NULL ) {
+ save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open file "
+ "\"%s\": %d (%s)\n",
+ fname,
+ save_errno, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ return -1;
+ }
+ fclose( fp );
+ unlink( fname );
+ break;
+ }
+
+ Debug( LDAP_DEBUG_ANY, "unable to stat file "
+ "\"%s\": %d (%s)\n",
+ slapd_pid_file,
+ save_errno, AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+slaptest( int argc, char **argv )
+{
+ int rc = EXIT_SUCCESS;
+ const char *progname = "slaptest";
+
+ slap_tool_init( progname, SLAPTEST, argc, argv );
+
+ if ( slapd_pid_file != NULL ) {
+ if ( test_file( slapd_pid_file, "pid" ) ) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ if ( slapd_args_file != NULL ) {
+ if ( test_file( slapd_args_file, "args" ) ) {
+ return EXIT_FAILURE;
+ }
+ }
+
+ if ( !quiet ) {
+ fprintf( stderr, "config file testing succeeded\n");
+ }
+
+ if ( slap_tool_destroy())
+ rc = EXIT_FAILURE;
+
+ return rc;
+}
diff --git a/servers/slapd/starttls.c b/servers/slapd/starttls.c
new file mode 100644
index 0000000..6a3c90b
--- /dev/null
+++ b/servers/slapd/starttls.c
@@ -0,0 +1,112 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "lber_pvt.h"
+
+const struct berval slap_EXOP_START_TLS = BER_BVC(LDAP_EXOP_START_TLS);
+
+#ifdef HAVE_TLS
+int
+starttls_extop ( Operation *op, SlapReply *rs )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_STATS, "%s STARTTLS\n",
+ op->o_log_prefix );
+
+ if ( op->ore_reqdata != NULL ) {
+ /* no request data should be provided */
+ rs->sr_text = "no request data expected";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* acquire connection lock */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ /* can't start TLS if it is already started */
+ if (op->o_conn->c_is_tls != 0) {
+ rs->sr_text = "TLS already started";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /* can't start TLS if there are other op's around */
+ if (( !LDAP_STAILQ_EMPTY(&op->o_conn->c_ops) &&
+ (LDAP_STAILQ_FIRST(&op->o_conn->c_ops) != op ||
+ LDAP_STAILQ_NEXT(op, o_next) != NULL)) ||
+ ( !LDAP_STAILQ_EMPTY(&op->o_conn->c_pending_ops) ))
+ {
+ rs->sr_text = "cannot start TLS when operations are outstanding";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if ( !( global_disallows & SLAP_DISALLOW_TLS_2_ANON ) &&
+ ( op->o_conn->c_dn.bv_len != 0 ) )
+ {
+ Debug( LDAP_DEBUG_STATS,
+ "%s AUTHZ anonymous mech=starttls ssf=0\n",
+ op->o_log_prefix );
+
+ /* force to anonymous */
+ connection2anonymous( op->o_conn );
+ }
+
+ if ( ( global_disallows & SLAP_DISALLOW_TLS_AUTHC ) &&
+ ( op->o_conn->c_dn.bv_len != 0 ) )
+ {
+ rs->sr_text = "cannot start TLS after authentication";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /* fail if TLS could not be initialized */
+ if ( slap_tls_ctx == NULL ) {
+ if (default_referral != NULL) {
+ /* caller will put the referral in the result */
+ rc = LDAP_REFERRAL;
+ goto done;
+ }
+
+ rs->sr_text = "Could not initialize TLS";
+ rc = LDAP_UNAVAILABLE;
+ goto done;
+ }
+
+ op->o_conn->c_is_tls = 1;
+ op->o_conn->c_needs_tls_accept = 1;
+
+ rc = LDAP_SUCCESS;
+
+done:
+ /* give up connection lock */
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ /* FIXME: RACE CONDITION! we give up lock before sending result
+ * Should be resolved by reworking connection state, not
+ * by moving send here (so as to ensure proper TLS sequencing)
+ */
+
+ return rc;
+}
+
+#endif /* HAVE_TLS */
diff --git a/servers/slapd/str2filter.c b/servers/slapd/str2filter.c
new file mode 100644
index 0000000..8642ec3
--- /dev/null
+++ b/servers/slapd/str2filter.c
@@ -0,0 +1,84 @@
+/* str2filter.c - parse an RFC 4515 string filter */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+
+Filter *
+str2filter_x( Operation *op, const char *str )
+{
+ int rc;
+ Filter *f = NULL;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ const char *text = NULL;
+
+ Debug( LDAP_DEBUG_FILTER, "str2filter \"%s\"\n", str );
+
+ if ( str == NULL || *str == '\0' ) {
+ return NULL;
+ }
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ if ( op->o_tmpmemctx ) {
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+ }
+
+ rc = ldap_pvt_put_filter( ber, str );
+ if( rc < 0 ) {
+ goto done;
+ }
+
+ ber_reset( ber, 1 );
+
+ rc = get_filter( op, ber, &f, &text );
+
+done:
+ ber_free_buf( ber );
+
+ return f;
+}
+
+Filter *
+str2filter( const char *str )
+{
+ Operation op = {0};
+ Opheader ohdr = {0};
+
+ op.o_hdr = &ohdr;
+ op.o_tmpmemctx = NULL;
+ op.o_tmpmfuncs = &ch_mfuncs;
+
+ return str2filter_x( &op, str );
+}
diff --git a/servers/slapd/syncrepl.c b/servers/slapd/syncrepl.c
new file mode 100644
index 0000000..80f73a2
--- /dev/null
+++ b/servers/slapd/syncrepl.c
@@ -0,0 +1,7664 @@
+/* syncrepl.c -- Replication Engine which uses the LDAP Sync protocol */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2003 by IBM Corporation.
+ * Portions Copyright 2003-2008 by Howard Chu, Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "lutil_ldap.h"
+
+#include "slap-config.h"
+
+#include "ldap_rq.h"
+
+#include "rewrite.h"
+
+#include "back-monitor/back-monitor.h"
+
+#define SUFFIXM_CTX "<suffix massage>"
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+#define MSAD_DIRSYNC 0x04
+#define MSAD_DIRSYNC_MODIFY 0x10
+
+static AttributeDescription *sy_ad_objectGUID;
+static AttributeDescription *sy_ad_instanceType;
+static AttributeDescription *sy_ad_isDeleted;
+static AttributeDescription *sy_ad_whenCreated;
+static AttributeDescription *sy_ad_dirSyncCookie;
+
+static struct berval msad_addval = BER_BVC("range=1-1");
+static struct berval msad_delval = BER_BVC("range=0-0");
+#endif
+
+static AttributeDescription *sy_ad_nsUniqueId;
+static AttributeDescription *sy_ad_dseeLastChange;
+
+#define DSEE_SYNC_ADD 0x20
+
+#define UUIDLEN 16
+
+struct syncinfo_s;
+
+struct nonpresent_entry {
+ struct berval *npe_name;
+ struct berval *npe_nname;
+ LDAP_LIST_ENTRY(nonpresent_entry) npe_link;
+};
+
+typedef struct cookie_vals {
+ struct berval *cv_vals;
+ int *cv_sids;
+ int cv_num;
+} cookie_vals;
+
+typedef struct cookie_state {
+ ldap_pvt_thread_mutex_t cs_mutex;
+ ldap_pvt_thread_cond_t cs_cond;
+ struct berval *cs_vals;
+ int *cs_sids;
+ int cs_num;
+ int cs_age;
+ int cs_ref;
+ int cs_updating;
+
+ /* pending changes, not yet committed */
+ ldap_pvt_thread_mutex_t cs_pmutex;
+ struct berval *cs_pvals;
+ int *cs_psids;
+ int cs_pnum;
+
+ /* serialize multi-consumer refreshes */
+ ldap_pvt_thread_mutex_t cs_refresh_mutex;
+ struct syncinfo_s *cs_refreshing;
+} cookie_state;
+
+#define SYNC_TIMEOUT 0
+#define SYNC_SHUTDOWN -100
+#define SYNC_ERROR -101
+#define SYNC_REPOLL -102
+#define SYNC_PAUSED -103
+#define SYNC_BUSY -104
+
+#define SYNCDATA_DEFAULT 0 /* entries are plain LDAP entries */
+#define SYNCDATA_ACCESSLOG 1 /* entries are accesslog format */
+#define SYNCDATA_CHANGELOG 2 /* entries are changelog format */
+
+#define SYNCLOG_LOGGING 0 /* doing a log-based update */
+#define SYNCLOG_FALLBACK 1 /* doing a full refresh */
+
+#define RETRYNUM_FOREVER (-1) /* retry forever */
+#define RETRYNUM_TAIL (-2) /* end of retrynum array */
+#define RETRYNUM_VALID(n) ((n) >= RETRYNUM_FOREVER) /* valid retrynum */
+#define RETRYNUM_FINITE(n) ((n) > RETRYNUM_FOREVER) /* not forever */
+
+typedef struct syncinfo_s {
+ struct syncinfo_s *si_next;
+ BackendDB *si_be;
+ BackendDB *si_wbe;
+ struct re_s *si_re;
+ int si_rid;
+ char si_ridtxt[ STRLENOF("rid=999") + 1 ];
+ slap_bindconf si_bindconf;
+ struct berval si_base;
+ struct berval si_logbase;
+ struct berval si_filterstr;
+ struct berval si_logfilterstr;
+ Filter *si_filter;
+ Filter *si_logfilter;
+ struct berval si_contextdn;
+ int si_scope;
+ int si_attrsonly;
+ char *si_anfile;
+ AttributeName *si_anlist;
+ AttributeName *si_exanlist;
+ char **si_attrs;
+ char **si_exattrs;
+ int si_allattrs;
+ int si_allopattrs;
+ int si_schemachecking;
+ int si_type; /* the active type */
+ int si_ctype; /* the configured type */
+ time_t si_interval;
+ time_t *si_retryinterval;
+ int *si_retrynum_init;
+ int *si_retrynum;
+ struct sync_cookie si_syncCookie;
+ cookie_state *si_cookieState;
+ int si_cookieAge;
+ int si_manageDSAit;
+ int si_slimit;
+ int si_tlimit;
+ int si_refreshDelete;
+ int si_refreshPresent;
+ int si_refreshDone;
+ int si_paused;
+ int si_syncdata;
+ int si_logstate;
+ int si_lazyCommit;
+ int si_got;
+ int si_strict_refresh; /* stop listening during fallback refresh */
+ int si_too_old;
+ int si_is_configdb;
+ ber_int_t si_msgid;
+ Avlnode *si_presentlist;
+ LDAP *si_ld;
+ Connection *si_conn;
+ LDAP_LIST_HEAD(np, nonpresent_entry) si_nonpresentlist;
+ struct rewrite_info *si_rewrite;
+ struct berval si_suffixm;
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ struct berval si_dirSyncCookie;
+#endif
+ unsigned long si_prevchange;
+ unsigned long si_lastchange;
+
+ /* monitor info */
+ int si_monitorInited;
+ time_t si_lastconnect;
+ time_t si_lastcontact;
+ struct berval si_connaddr;
+ struct berval si_lastCookieRcvd;
+ struct berval si_lastCookieSent;
+ struct berval si_monitor_ndn;
+ char si_connaddrbuf[LDAP_IPADDRLEN];
+
+ ldap_pvt_thread_mutex_t si_monitor_mutex;
+ ldap_pvt_thread_mutex_t si_mutex;
+} syncinfo_t;
+
+static int syncuuid_cmp( const void *, const void * );
+static int presentlist_insert( syncinfo_t* si, struct berval *syncUUID );
+static void presentlist_delete( Avlnode **av, struct berval *syncUUID );
+static char *presentlist_find( Avlnode *av, struct berval *syncUUID );
+static int presentlist_free( Avlnode *av );
+static void syncrepl_del_nonpresent( Operation *, syncinfo_t *, BerVarray, struct sync_cookie *, int );
+static int syncrepl_message_to_op(
+ syncinfo_t *, Operation *, LDAPMessage *, int );
+static int syncrepl_message_to_entry(
+ syncinfo_t *, Operation *, LDAPMessage *,
+ Modifications **, Entry **, int, struct berval* );
+static int syncrepl_entry(
+ syncinfo_t *, Operation*, Entry*,
+ Modifications**,int, struct berval*,
+ struct berval *cookieCSN );
+static int syncrepl_updateCookie(
+ syncinfo_t *, Operation *,
+ struct sync_cookie *, int save );
+static struct berval * slap_uuidstr_from_normalized(
+ struct berval *, struct berval *, void * );
+static int syncrepl_add_glue_ancestors(
+ Operation* op, Entry *e );
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+static int syncrepl_dirsync_message(
+ syncinfo_t *, Operation *, LDAPMessage *,
+ Modifications **, Entry **, int *, struct berval* );
+static int syncrepl_dirsync_cookie(
+ syncinfo_t *, Operation *, LDAPControl ** );
+#endif
+
+static int syncrepl_dsee_update( syncinfo_t *si, Operation *op ) ;
+
+/* delta-mpr overlay handler */
+static int syncrepl_op_modify( Operation *op, SlapReply *rs );
+
+/* callback functions */
+static int dn_callback( Operation *, SlapReply * );
+static int nonpresent_callback( Operation *, SlapReply * );
+
+static AttributeDescription *sync_descs[4];
+
+static AttributeDescription *dsee_descs[7];
+
+/* delta-mpr */
+static AttributeDescription *ad_reqMod, *ad_reqDN;
+
+typedef struct logschema {
+ struct berval ls_dn;
+ struct berval ls_req;
+ struct berval ls_mod;
+ struct berval ls_newRdn;
+ struct berval ls_delRdn;
+ struct berval ls_newSup;
+ struct berval ls_controls;
+ struct berval ls_uuid;
+ struct berval ls_changenum;
+} logschema;
+
+static logschema changelog_sc = {
+ BER_BVC("targetDN"),
+ BER_BVC("changeType"),
+ BER_BVC("changes"),
+ BER_BVC("newRDN"),
+ BER_BVC("deleteOldRDN"),
+ BER_BVC("newSuperior"),
+ BER_BVNULL,
+ BER_BVC("targetUniqueId"),
+ BER_BVC("changeNumber")
+};
+
+static logschema accesslog_sc = {
+ BER_BVC("reqDN"),
+ BER_BVC("reqType"),
+ BER_BVC("reqMod"),
+ BER_BVC("reqNewRDN"),
+ BER_BVC("reqDeleteOldRDN"),
+ BER_BVC("reqNewSuperior"),
+ BER_BVC("reqControls")
+};
+
+static const char *
+syncrepl_state2str( int state )
+{
+ switch ( state ) {
+ case LDAP_SYNC_PRESENT:
+ return "PRESENT";
+
+ case LDAP_SYNC_ADD:
+ return "ADD";
+
+ case LDAP_SYNC_MODIFY:
+ return "MODIFY";
+
+ case LDAP_SYNC_DELETE:
+ return "DELETE";
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ case MSAD_DIRSYNC_MODIFY:
+ return "DIRSYNC_MOD";
+#endif
+ case DSEE_SYNC_ADD:
+ return "DSEE_ADD";
+ }
+
+ return "UNKNOWN";
+}
+
+static slap_overinst syncrepl_ov;
+
+static void
+init_syncrepl(syncinfo_t *si)
+{
+ int i, j, k, l, n;
+ char **attrs, **exattrs;
+
+ if ( !syncrepl_ov.on_bi.bi_type ) {
+ syncrepl_ov.on_bi.bi_type = "syncrepl";
+ syncrepl_ov.on_bi.bi_op_modify = syncrepl_op_modify;
+ overlay_register( &syncrepl_ov );
+ }
+
+ /* delta-MPR needs the overlay, nothing else does.
+ * This must happen before accesslog overlay is configured.
+ */
+ if ( si->si_syncdata &&
+ !overlay_is_inst( si->si_be, syncrepl_ov.on_bi.bi_type )) {
+ overlay_config( si->si_be, syncrepl_ov.on_bi.bi_type, -1, NULL, NULL );
+ if ( !ad_reqMod ) {
+ const char *text;
+ logschema *ls = &accesslog_sc;
+
+ slap_bv2ad( &ls->ls_mod, &ad_reqMod, &text );
+ slap_bv2ad( &ls->ls_dn, &ad_reqDN, &text );
+ }
+ }
+
+ if ( !sync_descs[0] ) {
+ sync_descs[0] = slap_schema.si_ad_objectClass;
+ sync_descs[1] = slap_schema.si_ad_structuralObjectClass;
+ sync_descs[2] = slap_schema.si_ad_entryCSN;
+ sync_descs[3] = NULL;
+ }
+
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ /* DSEE doesn't support allopattrs */
+ si->si_allopattrs = 0;
+ if ( !dsee_descs[0] ) {
+ dsee_descs[0] = slap_schema.si_ad_objectClass;
+ dsee_descs[1] = slap_schema.si_ad_creatorsName;
+ dsee_descs[2] = slap_schema.si_ad_createTimestamp;
+ dsee_descs[3] = slap_schema.si_ad_modifiersName;
+ dsee_descs[4] = slap_schema.si_ad_modifyTimestamp;
+ dsee_descs[5] = sy_ad_nsUniqueId;
+ dsee_descs[6] = NULL;
+ }
+ }
+
+ if ( si->si_allattrs && si->si_allopattrs )
+ attrs = NULL;
+ else
+ attrs = anlist2attrs( si->si_anlist );
+
+ if ( attrs ) {
+ if ( si->si_allattrs ) {
+ i = 0;
+ while ( attrs[i] ) {
+ if ( !is_at_operational( at_find( attrs[i] ) ) ) {
+ for ( j = i; attrs[j] != NULL; j++ ) {
+ if ( j == i )
+ ch_free( attrs[i] );
+ attrs[j] = attrs[j+1];
+ }
+ } else {
+ i++;
+ }
+ }
+ attrs = ( char ** ) ch_realloc( attrs, (i + 2)*sizeof( char * ) );
+ attrs[i] = ch_strdup("*");
+ attrs[i + 1] = NULL;
+
+ } else if ( si->si_allopattrs ) {
+ i = 0;
+ while ( attrs[i] ) {
+ if ( is_at_operational( at_find( attrs[i] ) ) ) {
+ for ( j = i; attrs[j] != NULL; j++ ) {
+ if ( j == i )
+ ch_free( attrs[i] );
+ attrs[j] = attrs[j+1];
+ }
+ } else {
+ i++;
+ }
+ }
+ attrs = ( char ** ) ch_realloc( attrs, (i + 2)*sizeof( char * ) );
+ attrs[i] = ch_strdup("+");
+ attrs[i + 1] = NULL;
+ }
+
+ for ( i = 0; sync_descs[i] != NULL; i++ ) {
+ j = 0;
+ while ( attrs[j] ) {
+ if ( !strcmp( attrs[j], sync_descs[i]->ad_cname.bv_val ) ) {
+ for ( k = j; attrs[k] != NULL; k++ ) {
+ if ( k == j )
+ ch_free( attrs[k] );
+ attrs[k] = attrs[k+1];
+ }
+ } else {
+ j++;
+ }
+ }
+ }
+
+ for ( n = 0; attrs[ n ] != NULL; n++ ) /* empty */;
+
+ if ( si->si_allopattrs ) {
+ attrs = ( char ** ) ch_realloc( attrs, (n + 2)*sizeof( char * ) );
+ } else {
+ attrs = ( char ** ) ch_realloc( attrs, (n + 4)*sizeof( char * ) );
+ }
+
+ /* Add Attributes */
+ if ( si->si_allopattrs ) {
+ attrs[n++] = ch_strdup( sync_descs[0]->ad_cname.bv_val );
+ } else {
+ if ( si->si_syncdata != SYNCDATA_CHANGELOG ) {
+ for ( i = 0; sync_descs[ i ] != NULL; i++ ) {
+ attrs[ n++ ] = ch_strdup ( sync_descs[i]->ad_cname.bv_val );
+ }
+ }
+ }
+ attrs[ n ] = NULL;
+
+ } else {
+
+ i = 0;
+ if ( si->si_allattrs == si->si_allopattrs ) {
+ attrs = (char**) ch_malloc( 3 * sizeof(char*) );
+ attrs[i++] = ch_strdup( "*" );
+ attrs[i++] = ch_strdup( "+" );
+ si->si_allattrs = si->si_allopattrs = 1;
+ } else if ( si->si_allattrs && !si->si_allopattrs ) {
+ for ( n = 0; sync_descs[ n ] != NULL; n++ ) ;
+ attrs = (char**) ch_malloc( (n+1)* sizeof(char*) );
+ attrs[i++] = ch_strdup( "*" );
+ for ( j = 1; sync_descs[ j ] != NULL; j++ ) {
+ attrs[i++] = ch_strdup ( sync_descs[j]->ad_cname.bv_val );
+ }
+ } else if ( !si->si_allattrs && si->si_allopattrs ) {
+ attrs = (char**) ch_malloc( 3 * sizeof(char*) );
+ attrs[i++] = ch_strdup( "+" );
+ attrs[i++] = ch_strdup( sync_descs[0]->ad_cname.bv_val );
+ }
+ attrs[i] = NULL;
+ }
+
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ for ( n = 0; attrs[ n ] != NULL; n++ ) /* empty */;
+ attrs = ( char ** ) ch_realloc( attrs, (n + 6)*sizeof( char * ) );
+ for ( i = 0; dsee_descs[ i ] != NULL; i++ ) {
+ attrs[ n++ ] = ch_strdup ( dsee_descs[i]->ad_cname.bv_val );
+ }
+ attrs[n] = NULL;
+ }
+
+ si->si_attrs = attrs;
+
+ exattrs = anlist2attrs( si->si_exanlist );
+
+ if ( exattrs ) {
+ for ( n = 0; exattrs[n] != NULL; n++ ) ;
+
+ for ( i = 0; sync_descs[i] != NULL; i++ ) {
+ j = 0;
+ while ( exattrs[j] != NULL ) {
+ if ( !strcmp( exattrs[j], sync_descs[i]->ad_cname.bv_val ) ) {
+ ch_free( exattrs[j] );
+ for ( k = j; exattrs[k] != NULL; k++ ) {
+ exattrs[k] = exattrs[k+1];
+ }
+ } else {
+ j++;
+ }
+ }
+ }
+
+ for ( i = 0; exattrs[i] != NULL; i++ ) {
+ for ( j = 0; si->si_anlist[j].an_name.bv_val; j++ ) {
+ ObjectClass *oc;
+ if ( ( oc = si->si_anlist[j].an_oc ) ) {
+ k = 0;
+ while ( oc->soc_required[k] ) {
+ if ( !strcmp( exattrs[i],
+ oc->soc_required[k]->sat_cname.bv_val ) ) {
+ ch_free( exattrs[i] );
+ for ( l = i; exattrs[l]; l++ ) {
+ exattrs[l] = exattrs[l+1];
+ }
+ } else {
+ k++;
+ }
+ }
+ }
+ }
+ }
+
+ for ( i = 0; exattrs[i] != NULL; i++ ) ;
+
+ if ( i != n )
+ exattrs = (char **) ch_realloc( exattrs, (i + 1)*sizeof(char *) );
+ }
+
+ si->si_exattrs = exattrs;
+}
+
+static int
+start_refresh(syncinfo_t *si)
+{
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_refresh_mutex );
+ if ( si->si_cookieState->cs_refreshing ) {
+ struct re_s* rtask = si->si_re;
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ si->si_paused = 1;
+ Debug( LDAP_DEBUG_SYNC, "start_refresh: %s "
+ "a refresh on %s in progress, pausing\n",
+ si->si_ridtxt, si->si_cookieState->cs_refreshing->si_ridtxt );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_refresh_mutex );
+ return SYNC_BUSY;
+ }
+ si->si_cookieState->cs_refreshing = si;
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_refresh_mutex );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+refresh_finished(syncinfo_t *si)
+{
+ syncinfo_t *sie;
+ int removed = 0;
+
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_refresh_mutex );
+ if ( si->si_cookieState->cs_refreshing == si ) {
+ si->si_cookieState->cs_refreshing = NULL;
+ removed = 1;
+ }
+
+ if ( removed ) {
+ for ( sie = si->si_be->be_syncinfo; sie; sie = sie->si_next ) {
+ if ( sie->si_paused ) {
+ struct re_s* rtask = sie->si_re;
+
+ Debug( LDAP_DEBUG_SYNC, "refresh_finished: %s "
+ "rescheduling refresh on %s\n",
+ si->si_ridtxt, sie->si_ridtxt );
+ sie->si_paused = 0;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ rtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ rtask->interval.tv_sec = si->si_interval;
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ break;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_refresh_mutex );
+ return removed;
+}
+
+static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
+
+static int
+ldap_sync_search(
+ syncinfo_t *si,
+ void *ctx )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPControl c[3], *ctrls[4];
+ int rc;
+ int rhint;
+ char *base;
+ char **attrs, *lattrs[9];
+ char *filter;
+ int attrsonly;
+ int scope;
+ char filterbuf[sizeof("(changeNumber>=18446744073709551615)")];
+
+ /* setup LDAP SYNC control */
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &ctx );
+
+ si->si_msgid = 0;
+
+ /* If we're using a log but we have no state, then fallback to
+ * normal mode for a full refresh.
+ */
+ if ( si->si_syncdata ) {
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ LDAPMessage *res, *msg;
+ unsigned long first = 0, last = 0;
+ int gotfirst = 0, gotlast = 0;
+
+ if ( (rc = start_refresh( si )) ) {
+ return rc;
+ }
+
+ /* See if we're new enough for the remote server */
+ lattrs[0] = "firstchangenumber";
+ lattrs[1] = "lastchangenumber";
+ lattrs[2] = NULL;
+ rc = ldap_search_ext_s( si->si_ld, "", LDAP_SCOPE_BASE, generic_filterstr.bv_val, lattrs, 0,
+ NULL, NULL, NULL, si->si_slimit, &res );
+ if ( rc ) {
+ ldap_msgfree( res );
+ return rc;
+ }
+ msg = ldap_first_message( si->si_ld, res );
+ if ( msg && ldap_msgtype( msg ) == LDAP_RES_SEARCH_ENTRY ) {
+ BerElement *ber = NULL;
+ struct berval bv, *bvals, **bvp = &bvals;;
+ rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bv );
+ for ( rc = ldap_get_attribute_ber( si->si_ld, msg, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( si->si_ld, msg, ber, &bv, bvp ) ) {
+ if ( bv.bv_val == NULL )
+ break;
+ if ( !strcasecmp( bv.bv_val, "firstchangenumber" )) {
+ first = strtoul( bvals[0].bv_val, NULL, 0 );
+ gotfirst = 1;
+ } else if ( !strcasecmp( bv.bv_val, "lastchangenumber" )) {
+ last = strtoul( bvals[0].bv_val, NULL, 0 );
+ gotlast = 1;
+ }
+ }
+ }
+ ldap_msgfree( res );
+ if ( gotfirst && gotlast ) {
+ if ( si->si_lastchange < first || (!si->si_lastchange && !si->si_refreshDone ))
+ si->si_logstate = SYNCLOG_FALLBACK;
+ /* if we're in logging mode, it will update si_lastchange itself */
+ if ( si->si_logstate == SYNCLOG_FALLBACK )
+ si->si_lastchange = last;
+ } else {
+ /* should be an error; changelog plugin not enabled on provider */
+ si->si_logstate = SYNCLOG_FALLBACK;
+ }
+ } else
+ if ( si->si_logstate == SYNCLOG_LOGGING && !si->si_syncCookie.numcsns &&
+ !si->si_refreshDone ) {
+ si->si_logstate = SYNCLOG_FALLBACK;
+ }
+ }
+
+ /* Use the log parameters if we're in log mode */
+ if ( si->si_syncdata && si->si_logstate == SYNCLOG_LOGGING ) {
+ logschema *ls;
+ if ( si->si_syncdata == SYNCDATA_ACCESSLOG )
+ ls = &accesslog_sc;
+ else
+ ls = &changelog_sc;
+ lattrs[0] = ls->ls_dn.bv_val;
+ lattrs[1] = ls->ls_req.bv_val;
+ lattrs[2] = ls->ls_mod.bv_val;
+ lattrs[3] = ls->ls_newRdn.bv_val;
+ lattrs[4] = ls->ls_delRdn.bv_val;
+ lattrs[5] = ls->ls_newSup.bv_val;
+ if ( si->si_syncdata == SYNCDATA_ACCESSLOG ) {
+ lattrs[6] = ls->ls_controls.bv_val;
+ lattrs[7] = slap_schema.si_ad_entryCSN->ad_cname.bv_val;
+ lattrs[8] = NULL;
+ filter = si->si_logfilterstr.bv_val;
+ scope = LDAP_SCOPE_SUBTREE;
+ } else {
+ lattrs[6] = ls->ls_uuid.bv_val;
+ lattrs[7] = ls->ls_changenum.bv_val;
+ lattrs[8] = NULL;
+ sprintf( filterbuf, "(changeNumber>=%lu)", si->si_lastchange+1 );
+ filter = filterbuf;
+ scope = LDAP_SCOPE_ONELEVEL;
+ }
+
+ rhint = 0;
+ base = si->si_logbase.bv_val;
+ attrs = lattrs;
+ attrsonly = 0;
+ } else {
+ if ( (rc = start_refresh( si )) ) {
+ return rc;
+ }
+
+ rhint = 1;
+ base = si->si_base.bv_val;
+ filter = si->si_filterstr.bv_val;
+ attrs = si->si_attrs;
+ attrsonly = si->si_attrsonly;
+ scope = si->si_scope;
+ }
+ if ( si->si_syncdata && si->si_logstate == SYNCLOG_FALLBACK ) {
+ si->si_type = LDAP_SYNC_REFRESH_ONLY;
+ } else {
+ si->si_type = si->si_ctype;
+ }
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( si->si_ctype == MSAD_DIRSYNC ) {
+ ber_printf( ber, "{iiO}", LDAP_CONTROL_X_DIRSYNC_INCREMENTAL_VALUES, 0, &si->si_dirSyncCookie );
+
+ if ( (rc = ber_flatten2( ber, &c[0].ldctl_value, 0 ) ) == -1 ) {
+ ber_free_buf( ber );
+ return rc;
+ }
+ c[0].ldctl_oid = LDAP_CONTROL_X_DIRSYNC;
+ c[0].ldctl_iscritical = 1;
+ ctrls[0] = &c[0];
+
+ if ( !BER_BVISEMPTY( &si->si_dirSyncCookie )) {
+ c[1].ldctl_oid = LDAP_CONTROL_X_SHOW_DELETED;
+ BER_BVZERO( &c[1].ldctl_value );
+ c[1].ldctl_iscritical = 1;
+ ctrls[1] = &c[1];
+ ctrls[2] = NULL;
+ } else {
+ ctrls[1] = NULL;
+ }
+ } else
+#endif
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ if ( si->si_logstate == SYNCLOG_LOGGING && si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ) {
+ c[0].ldctl_oid = LDAP_CONTROL_PERSIST_REQUEST;
+ c[0].ldctl_iscritical = 0;
+ rc = ldap_create_persistentsearch_control_value( si->si_ld, LDAP_CONTROL_PERSIST_ENTRY_CHANGE_ADD,
+ 0, 1, &c[0].ldctl_value );
+ ctrls[0] = &c[0];
+ ctrls[1] = NULL;
+ } else {
+ ctrls[0] = NULL;
+ }
+ } else
+ {
+ if ( !BER_BVISNULL( &si->si_syncCookie.octet_str ) )
+ {
+ ber_printf( ber, "{eOb}",
+ abs(si->si_type), &si->si_syncCookie.octet_str, rhint );
+ } else {
+ ber_printf( ber, "{eb}",
+ abs(si->si_type), rhint );
+ }
+
+ if ( (rc = ber_flatten2( ber, &c[0].ldctl_value, 0 ) ) == -1 ) {
+ ber_free_buf( ber );
+ return rc;
+ }
+
+ c[0].ldctl_oid = LDAP_CONTROL_SYNC;
+ c[0].ldctl_iscritical = si->si_type < 0;
+ ctrls[0] = &c[0];
+
+ c[1].ldctl_oid = LDAP_CONTROL_MANAGEDSAIT;
+ BER_BVZERO( &c[1].ldctl_value );
+ c[1].ldctl_iscritical = 1;
+ ctrls[1] = &c[1];
+
+ if ( !BER_BVISNULL( &si->si_bindconf.sb_authzId ) ) {
+ c[2].ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ c[2].ldctl_value = si->si_bindconf.sb_authzId;
+ c[2].ldctl_iscritical = 1;
+ ctrls[2] = &c[2];
+ ctrls[3] = NULL;
+ } else {
+ ctrls[2] = NULL;
+ }
+ }
+
+ si->si_refreshDone = 0;
+ si->si_refreshPresent = 0;
+ si->si_refreshDelete = 0;
+
+ rc = ldap_search_ext( si->si_ld, base, scope, filter, attrs, attrsonly,
+ ctrls, NULL, NULL, si->si_slimit, &si->si_msgid );
+ ber_free_buf( ber );
+ return rc;
+}
+
+/* #define DEBUG_MERGE_STATE 1 */
+
+static int
+merge_state( syncinfo_t *si, struct sync_cookie *sc1, struct sync_cookie *sc2 )
+{
+ int i, j, k, changed = 0;
+ int ei, ej;
+ int *newsids;
+ struct berval *newcsns;
+
+ ei = sc1->numcsns;
+ ej = sc2->numcsns;
+#ifdef DEBUG_MERGE_STATE
+ for ( i=0; i<ei; i++ ) {
+ fprintf(stderr, "merge_state: %s si_syncCookie [%d] %d %s\n",
+ si->si_ridtxt, i, sc1->sids[i], sc1->ctxcsn[i].bv_val );
+ }
+ for ( i=0; i<ej; i++ ) {
+ fprintf(stderr, "merge_state: %s si_cookieState [%d] %d %s\n",
+ si->si_ridtxt, i, sc2->sids[i], sc2->ctxcsn[i].bv_val );
+ }
+#endif
+ /* see if they cover the same SIDs */
+ if ( ei == ej ) {
+ for ( i = 0; i < ei; i++ ) {
+ if ( sc1->sids[i] != sc2->sids[i] ) {
+ changed = 1;
+ break;
+ }
+ }
+ /* SIDs are the same, take fast path */
+ if ( !changed ) {
+ for ( i = 0; i < ei; i++ ) {
+ if ( ber_bvcmp( &sc1->ctxcsn[i], &sc2->ctxcsn[i] ) < 0 ) {
+ ber_bvreplace( &sc1->ctxcsn[i], &sc2->ctxcsn[i] );
+ changed = 1;
+ }
+ }
+ return changed;
+ }
+ changed = 0;
+ }
+
+ i = ei + ej;
+ newsids = ch_malloc( sizeof(int) * i );
+ newcsns = ch_malloc( sizeof(struct berval) * ( i + 1 ));
+
+ for ( i=0, j=0, k=0; i < ei || j < ej ; ) {
+ if ( i < ei && sc1->sids[i] == -1 ) {
+ i++;
+ continue;
+ }
+ if ( j >= ej || (i < ei && sc1->sids[i] < sc2->sids[j] )) {
+ newsids[k] = sc1->sids[i];
+ ber_dupbv( &newcsns[k], &sc1->ctxcsn[i] );
+ i++; k++;
+ continue;
+ }
+ if ( i < ei && sc1->sids[i] == sc2->sids[j] ) {
+ newsids[k] = sc1->sids[i];
+ if ( ber_bvcmp( &sc1->ctxcsn[i], &sc2->ctxcsn[j] ) < 0 ) {
+ changed = 1;
+ ber_dupbv( &newcsns[k], &sc2->ctxcsn[j] );
+ } else {
+ ber_dupbv( &newcsns[k], &sc1->ctxcsn[i] );
+ }
+ i++; j++; k++;
+ continue;
+ }
+ if ( j < ej ) {
+ if ( sc2->sids[j] == -1 ) {
+ j++;
+ continue;
+ }
+ newsids[k] = sc2->sids[j];
+ ber_dupbv( &newcsns[k], &sc2->ctxcsn[j] );
+ changed = 1;
+ j++; k++;
+ }
+ }
+
+ ber_bvarray_free( sc1->ctxcsn );
+ ch_free( sc1->sids );
+ sc1->numcsns = k;
+ sc1->sids = ch_realloc( newsids, sizeof(int) * k );
+ sc1->ctxcsn = ch_realloc( newcsns, sizeof(struct berval) * (k+1) );
+ BER_BVZERO( &sc1->ctxcsn[k] );
+#ifdef DEBUG_MERGE_STATE
+ for ( i=0; i<sc1->numcsns; i++ ) {
+ fprintf(stderr, "merge_state: %s si_syncCookie2 [%d] %d %s\n",
+ si->si_ridtxt, i, sc1->sids[i], sc1->ctxcsn[i].bv_val );
+ }
+#endif
+
+ return changed;
+}
+
+#ifdef DEBUG_MERGE_STATE
+static void
+merge_test( syncinfo_t *si ) {
+ struct sync_cookie sc1, sc2;
+ int ret;
+
+ sc1.numcsns = 4;
+ sc1.sids = malloc( sizeof( int ) * sc1.numcsns );
+ sc1.ctxcsn = malloc( sizeof( struct berval ) * ( sc1.numcsns + 1 ));
+ sc1.sids[0] = 1;
+ sc1.sids[1] = 3;
+ sc1.sids[2] = 4;
+ sc1.sids[3] = 5;
+ { struct berval bv = BER_BVC("20200101000000.100000Z#sc1#001#000000"); /* unique */
+ ber_dupbv( &sc1.ctxcsn[0], &bv ); }
+ { struct berval bv = BER_BVC("20200101000000.100000Z#sc1#003#000000"); /* lower */
+ ber_dupbv( &sc1.ctxcsn[1], &bv ); }
+ { struct berval bv = BER_BVC("20201231000000.100000Z#sc1#004#000000"); /* higher */
+ ber_dupbv( &sc1.ctxcsn[2], &bv ); }
+ { struct berval bv = BER_BVC("20200228000000.100000Z#sc1#005#000000"); /* unique */
+ ber_dupbv( &sc1.ctxcsn[3], &bv ); }
+ BER_BVZERO( &sc1.ctxcsn[sc1.numcsns] );
+
+ sc2.numcsns = 4;
+ sc2.sids = malloc( sizeof( int ) * sc2.numcsns );
+ sc2.ctxcsn = malloc( sizeof( struct berval ) * ( sc2.numcsns + 1 ));
+ sc2.sids[0] = 2;
+ sc2.sids[1] = 3;
+ sc2.sids[2] = 4;
+ sc2.sids[3] = 6;
+ { struct berval bv = BER_BVC("20200101000000.100000Z#sc2#002#000000"); /* unique */
+ ber_dupbv( &sc2.ctxcsn[0], &bv ); }
+ { struct berval bv = BER_BVC("20200331000000.100000Z#sc2#003#000000"); /* higher */
+ ber_dupbv( &sc2.ctxcsn[1], &bv ); }
+ { struct berval bv = BER_BVC("20200501000000.100000Z#sc2#004#000000"); /* lower */
+ ber_dupbv( &sc2.ctxcsn[2], &bv ); }
+ { struct berval bv = BER_BVC("20200628000000.100000Z#sc2#006#000000"); /* unique */
+ ber_dupbv( &sc2.ctxcsn[3], &bv ); }
+ BER_BVZERO( &sc2.ctxcsn[sc2.numcsns] );
+
+ ret = merge_state( si, &sc1, &sc2 );
+}
+#endif
+
+static int
+check_syncprov(
+ Operation *op,
+ syncinfo_t *si )
+{
+ AttributeName at[2];
+ Attribute a = {0};
+ Entry e = {0};
+ SlapReply rs = {REP_SEARCH};
+ int i, j, changed = 0;
+
+ /* Look for contextCSN from syncprov overlay. If
+ * there's no overlay, this will be a no-op. That means
+ * this is a pure consumer, so local changes will not be
+ * allowed, and all changes will already be reflected in
+ * the cookieState.
+ */
+ a.a_desc = slap_schema.si_ad_contextCSN;
+ e.e_attrs = &a;
+ e.e_name = si->si_contextdn;
+ e.e_nname = si->si_contextdn;
+ at[0].an_name = a.a_desc->ad_cname;
+ at[0].an_desc = a.a_desc;
+ BER_BVZERO( &at[1].an_name );
+ rs.sr_entry = &e;
+ rs.sr_flags = REP_ENTRY_MODIFIABLE;
+ rs.sr_attrs = at;
+ op->o_req_dn = e.e_name;
+ op->o_req_ndn = e.e_nname;
+
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ i = backend_operational( op, &rs );
+ if ( i == LDAP_SUCCESS && a.a_nvals ) {
+ int num = a.a_numvals;
+ /* check for differences */
+ if ( num != si->si_cookieState->cs_num ) {
+ changed = 1;
+ } else {
+ for ( i=0; i<num; i++ ) {
+ if ( ber_bvcmp( &a.a_nvals[i],
+ &si->si_cookieState->cs_vals[i] )) {
+ changed = 1;
+ break;
+ }
+ }
+ }
+ if ( changed ) {
+ ber_bvarray_free( si->si_cookieState->cs_vals );
+ ch_free( si->si_cookieState->cs_sids );
+ si->si_cookieState->cs_num = num;
+ si->si_cookieState->cs_vals = a.a_nvals;
+ si->si_cookieState->cs_sids = slap_parse_csn_sids( a.a_nvals,
+ num, NULL );
+ si->si_cookieState->cs_age++;
+ } else {
+ ber_bvarray_free( a.a_nvals );
+ }
+ ber_bvarray_free( a.a_vals );
+ }
+ /* See if the cookieState has changed due to anything outside
+ * this particular consumer. That includes other consumers in
+ * the same context, or local changes detected above.
+ */
+ if ( si->si_cookieState->cs_num > 0 && si->si_cookieAge !=
+ si->si_cookieState->cs_age ) {
+ if ( !si->si_syncCookie.numcsns ) {
+ ber_bvarray_free( si->si_syncCookie.ctxcsn );
+ ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn,
+ si->si_cookieState->cs_vals, NULL );
+ changed = 1;
+ } else {
+ changed = merge_state( si, &si->si_syncCookie,
+ (struct sync_cookie *)&si->si_cookieState->cs_vals );
+ }
+ }
+ if ( changed ) {
+ si->si_cookieAge = si->si_cookieState->cs_age;
+ ch_free( si->si_syncCookie.octet_str.bv_val );
+ slap_compose_sync_cookie( NULL, &si->si_syncCookie.octet_str,
+ si->si_syncCookie.ctxcsn, si->si_syncCookie.rid,
+ si->si_syncCookie.sid, NULL );
+ ch_free( si->si_syncCookie.sids );
+ slap_reparse_sync_cookie( &si->si_syncCookie, op->o_tmpmemctx );
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ return changed;
+}
+
+static int
+do_syncrep1(
+ Operation *op,
+ syncinfo_t *si )
+{
+ int rc;
+ int cmdline_cookie_found = 0;
+
+ struct sync_cookie *sc = NULL;
+#ifdef HAVE_TLS
+ void *ssl;
+#endif
+
+ si->si_lastconnect = slap_get_time();
+ rc = slap_client_connect( &si->si_ld, &si->si_bindconf );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+ op->o_protocol = LDAP_VERSION3;
+
+ /* Set SSF to strongest of TLS, SASL SSFs */
+ op->o_sasl_ssf = 0;
+ op->o_tls_ssf = 0;
+ op->o_transport_ssf = 0;
+#ifdef HAVE_TLS
+ if ( ldap_get_option( si->si_ld, LDAP_OPT_X_TLS_SSL_CTX, &ssl )
+ == LDAP_SUCCESS && ssl != NULL )
+ {
+ op->o_tls_ssf = ldap_pvt_tls_get_strength( ssl );
+ }
+#endif /* HAVE_TLS */
+ {
+ ber_len_t ssf; /* ITS#5403, 3864 LDAP_OPT_X_SASL_SSF probably ought
+ to use sasl_ssf_t but currently uses ber_len_t */
+ if ( ldap_get_option( si->si_ld, LDAP_OPT_X_SASL_SSF, &ssf )
+ == LDAP_SUCCESS )
+ op->o_sasl_ssf = ssf;
+ }
+ op->o_ssf = ( op->o_sasl_ssf > op->o_tls_ssf )
+ ? op->o_sasl_ssf : op->o_tls_ssf;
+
+ ldap_set_option( si->si_ld, LDAP_OPT_TIMELIMIT, &si->si_tlimit );
+
+ rc = LDAP_DEREF_NEVER; /* actually could allow DEREF_FINDING */
+ ldap_set_option( si->si_ld, LDAP_OPT_DEREF, &rc );
+
+ ldap_set_option( si->si_ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF );
+
+ si->si_syncCookie.rid = si->si_rid;
+
+ /* whenever there are multiple data sources possible, advertise sid */
+ si->si_syncCookie.sid = ( SLAP_MULTIPROVIDER( si->si_be ) || si->si_be != si->si_wbe ) ?
+ slap_serverID : -1;
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( si->si_ctype == MSAD_DIRSYNC ) {
+ if ( BER_BVISEMPTY( &si->si_dirSyncCookie )) {
+ BerVarray cookies = NULL;
+ void *ctx = op->o_tmpmemctx;
+
+ op->o_req_ndn = si->si_contextdn;
+ op->o_req_dn = op->o_req_ndn;
+
+ /* try to read stored cookie */
+ op->o_tmpmemctx = NULL;
+ backend_attribute( op, NULL, &op->o_req_ndn,
+ sy_ad_dirSyncCookie, &cookies, ACL_READ );
+ op->o_tmpmemctx = ctx;
+ if ( cookies )
+ si->si_dirSyncCookie = cookies[0];
+ }
+ } else
+#endif
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ if ( !si->si_lastchange ) {
+ BerVarray vals = NULL;
+
+ op->o_req_ndn = si->si_contextdn;
+ op->o_req_dn = op->o_req_ndn;
+ /* try to read last change number */
+ backend_attribute( op, NULL, &op->o_req_ndn,
+ sy_ad_dseeLastChange, &vals, ACL_READ );
+ if ( vals ) {
+ si->si_lastchange = strtoul( vals[0].bv_val, NULL, 0 );
+ si->si_prevchange = si->si_lastchange;
+ }
+ }
+ } else
+ {
+
+ /* We've just started up, or the remote server hasn't sent us
+ * any meaningful state.
+ */
+ if ( !si->si_syncCookie.ctxcsn ) {
+ int i;
+
+ LDAP_STAILQ_FOREACH( sc, &slap_sync_cookie, sc_next ) {
+ if ( si->si_rid == sc->rid ) {
+ cmdline_cookie_found = 1;
+ break;
+ }
+ }
+
+ if ( cmdline_cookie_found ) {
+ /* cookie is supplied in the command line */
+
+ LDAP_STAILQ_REMOVE( &slap_sync_cookie, sc, sync_cookie, sc_next );
+
+ slap_sync_cookie_free( &si->si_syncCookie, 0 );
+ si->si_syncCookie.octet_str = sc->octet_str;
+ ch_free( sc );
+ /* ctxcsn wasn't parsed yet, do it now */
+ slap_parse_sync_cookie( &si->si_syncCookie, NULL );
+ } else {
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ if ( !si->si_cookieState->cs_num ) {
+ /* get contextCSN shadow replica from database */
+ BerVarray csn = NULL;
+ void *ctx = op->o_tmpmemctx;
+
+ op->o_req_ndn = si->si_contextdn;
+ op->o_req_dn = op->o_req_ndn;
+
+ /* try to read stored contextCSN */
+ op->o_tmpmemctx = NULL;
+ backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_contextCSN, &csn, ACL_READ );
+ op->o_tmpmemctx = ctx;
+ if ( csn ) {
+ si->si_cookieState->cs_vals = csn;
+ for (i=0; !BER_BVISNULL( &csn[i] ); i++);
+ si->si_cookieState->cs_num = i;
+ si->si_cookieState->cs_sids = slap_parse_csn_sids( csn, i, NULL );
+ slap_sort_csn_sids( csn, si->si_cookieState->cs_sids, i, NULL );
+ }
+ }
+ if ( si->si_cookieState->cs_num ) {
+ ber_bvarray_free( si->si_syncCookie.ctxcsn );
+ if ( ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn,
+ si->si_cookieState->cs_vals, NULL )) {
+ rc = LDAP_NO_MEMORY;
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ goto done;
+ }
+ si->si_syncCookie.numcsns = si->si_cookieState->cs_num;
+ si->si_syncCookie.sids = ch_malloc( si->si_cookieState->cs_num *
+ sizeof(int) );
+ for ( i=0; i<si->si_syncCookie.numcsns; i++ )
+ si->si_syncCookie.sids[i] = si->si_cookieState->cs_sids[i];
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ }
+ }
+
+ if ( !cmdline_cookie_found ) {
+ /* ITS#6367: recreate the cookie so it has our SID, not our peer's */
+ ch_free( si->si_syncCookie.octet_str.bv_val );
+ BER_BVZERO( &si->si_syncCookie.octet_str );
+ /* Look for contextCSN from syncprov overlay. */
+ check_syncprov( op, si );
+ if ( BER_BVISNULL( &si->si_syncCookie.octet_str ))
+ slap_compose_sync_cookie( NULL, &si->si_syncCookie.octet_str,
+ si->si_syncCookie.ctxcsn, si->si_syncCookie.rid,
+ si->si_syncCookie.sid, NULL );
+ }
+ }
+
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep1: %s starting refresh (sending cookie=%s)\n",
+ si->si_ridtxt, si->si_syncCookie.octet_str.bv_val ?
+ si->si_syncCookie.octet_str.bv_val : "" );
+
+ if ( si->si_syncCookie.octet_str.bv_val ) {
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieSent, &si->si_syncCookie.octet_str );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+
+ rc = ldap_sync_search( si, op->o_tmpmemctx );
+
+ if ( rc == SYNC_BUSY ) {
+ return rc;
+ } else if ( rc != LDAP_SUCCESS ) {
+ refresh_finished( si );
+ Debug( LDAP_DEBUG_ANY, "do_syncrep1: %s "
+ "ldap_search_ext: %s (%d)\n",
+ si->si_ridtxt, ldap_err2string( rc ), rc );
+ }
+
+done:
+ if ( rc ) {
+ if ( si->si_ld ) {
+ ldap_unbind_ext( si->si_ld, NULL, NULL );
+ si->si_ld = NULL;
+ }
+ }
+
+ return rc;
+}
+
+static int
+compare_csns( struct sync_cookie *sc1, struct sync_cookie *sc2, int *which )
+{
+ int i, j, match = 0;
+ const char *text;
+
+ *which = 0;
+
+ if ( sc1->numcsns < sc2->numcsns ) {
+ for ( i=0; i < sc1->numcsns && sc1->sids[i] == sc2->sids[i] ; i++ )
+ /* Find the first one that's missing */;
+ *which = i;
+ return -1;
+ }
+
+ for (j=0; j<sc2->numcsns; j++) {
+ for (i=0; i<sc1->numcsns; i++) {
+ if ( sc1->sids[i] != sc2->sids[j] )
+ continue;
+ value_match( &match, slap_schema.si_ad_entryCSN,
+ slap_schema.si_ad_entryCSN->ad_type->sat_ordering,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &sc1->ctxcsn[i], &sc2->ctxcsn[j], &text );
+ if ( match < 0 ) {
+ *which = j;
+ return match;
+ }
+ break;
+ }
+ if ( i == sc1->numcsns ) {
+ /* sc2 has a sid sc1 lacks */
+ *which = j;
+ return -1;
+ }
+ }
+ return match;
+}
+
+#define CV_CSN_OK 0
+#define CV_CSN_OLD 1
+#define CV_SID_NEW 2
+
+static int
+check_csn_age(
+ syncinfo_t *si,
+ struct berval *dn,
+ struct berval *csn,
+ int sid,
+ cookie_vals *cv,
+ int *slot )
+{
+ int i, rc = CV_SID_NEW;
+
+ for ( i =0; i<cv->cv_num; i++ ) {
+#ifdef CHATTY_SYNCLOG
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN for sid %d: %s\n",
+ si->si_ridtxt, i, cv->cv_vals[i].bv_val );
+#endif
+ /* new SID */
+ if ( sid < cv->cv_sids[i] )
+ break;
+ if ( cv->cv_sids[i] == sid ) {
+ if ( ber_bvcmp( csn, &cv->cv_vals[i] ) <= 0 ) {
+ dn->bv_val[dn->bv_len] = '\0';
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN too old, ignoring %s (%s)\n",
+ si->si_ridtxt, csn->bv_val, dn->bv_val );
+ return CV_CSN_OLD;
+ }
+ rc = CV_CSN_OK;
+ break;
+ }
+ }
+ if ( slot )
+ *slot = i;
+ return rc;
+}
+
+static int
+get_pmutex(
+ syncinfo_t *si
+)
+{
+ if ( !si->si_is_configdb ) {
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_pmutex );
+ } else {
+ /* avoid deadlock when replicating cn=config */
+ while ( ldap_pvt_thread_mutex_trylock( &si->si_cookieState->cs_pmutex )) {
+ if ( slapd_shutdown )
+ return SYNC_SHUTDOWN;
+ if ( !ldap_pvt_thread_pool_pausewait( &connection_pool ))
+ ldap_pvt_thread_yield();
+ }
+ }
+ if ( si->si_ctype < 0 ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex );
+ return SYNC_SHUTDOWN;
+ }
+
+ return 0;
+}
+
+static int
+do_syncrep2(
+ Operation *op,
+ syncinfo_t *si )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ LDAPMessage *msg = NULL;
+
+ struct sync_cookie syncCookie = { NULL };
+ struct sync_cookie syncCookie_req = { NULL };
+
+ int rc,
+ err = LDAP_SUCCESS;
+
+ Modifications *modlist = NULL;
+
+ int m;
+
+ struct timeval tout = { 0, 0 };
+
+ int refreshDeletes = 0;
+ int refreshing = !si->si_refreshDone &&
+ !( si->si_syncdata && si->si_logstate == SYNCLOG_LOGGING );
+ char empty[6] = "empty";
+
+ if ( slapd_shutdown ) {
+ rc = SYNC_SHUTDOWN;
+ goto done;
+ }
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, "=>do_syncrep2 %s\n", si->si_ridtxt );
+
+ slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie );
+
+ if ( abs(si->si_type) == LDAP_SYNC_REFRESH_AND_PERSIST && si->si_refreshDone ) {
+ tout.tv_sec = 0;
+ } else {
+ /* Give some time for refresh response to arrive */
+ tout.tv_sec = si->si_bindconf.sb_timeout_api;
+ }
+
+ while ( ( rc = ldap_result( si->si_ld, si->si_msgid, LDAP_MSG_ONE,
+ &tout, &msg ) ) > 0 )
+ {
+ int match, punlock, syncstate;
+ struct berval *retdata, syncUUID[2], cookie = BER_BVNULL;
+ char *retoid;
+ LDAPControl **rctrls = NULL, *rctrlp = NULL;
+ BerVarray syncUUIDs;
+ ber_len_t len;
+ ber_tag_t si_tag;
+ Entry *entry;
+ struct berval bdn;
+
+ if ( slapd_shutdown ) {
+ rc = SYNC_SHUTDOWN;
+ goto done;
+ }
+ si->si_lastcontact = slap_get_time();
+ switch( ldap_msgtype( msg ) ) {
+ case LDAP_RES_SEARCH_ENTRY:
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( si->si_ctype == MSAD_DIRSYNC ) {
+ BER_BVZERO( &syncUUID[0] );
+ rc = syncrepl_dirsync_message( si, op, msg, &modlist, &entry, &syncstate, syncUUID );
+ if ( rc == 0 )
+ rc = syncrepl_entry( si, op, entry, &modlist, syncstate, syncUUID, NULL );
+ op->o_tmpfree( syncUUID[0].bv_val, op->o_tmpmemctx );
+ if ( modlist )
+ slap_mods_free( modlist, 1);
+ if ( rc )
+ goto done;
+ break;
+ }
+#endif
+ punlock = -1;
+ ldap_get_entry_controls( si->si_ld, msg, &rctrls );
+ ldap_get_dn_ber( si->si_ld, msg, NULL, &bdn );
+ if (!bdn.bv_len) {
+ bdn.bv_val = empty;
+ bdn.bv_len = sizeof(empty)-1;
+ }
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ if ( si->si_logstate == SYNCLOG_LOGGING ) {
+ rc = syncrepl_message_to_op( si, op, msg, 1 );
+ if ( rc )
+ goto logerr;
+ if ( si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST && rctrls ) {
+ LDAPControl **next = NULL;
+ /* The notification control is only sent during persist phase */
+ rctrlp = ldap_control_find( LDAP_CONTROL_PERSIST_ENTRY_CHANGE_NOTICE, rctrls, &next );
+ if ( rctrlp ) {
+ if ( si->si_refreshDone )
+ syncrepl_dsee_update( si, op );
+ }
+ }
+
+ } else {
+ syncstate = DSEE_SYNC_ADD;
+ rc = syncrepl_message_to_entry( si, op, msg,
+ &modlist, &entry, syncstate, syncUUID );
+ if ( rc == 0 )
+ rc = syncrepl_entry( si, op, entry, &modlist, syncstate, syncUUID, NULL );
+ op->o_tmpfree( syncUUID[0].bv_val, op->o_tmpmemctx );
+ if ( modlist )
+ slap_mods_free( modlist, 1);
+ }
+ if ( rc )
+ goto done;
+ break;
+ }
+ /* we can't work without the control */
+ if ( rctrls ) {
+ LDAPControl **next = NULL;
+ /* NOTE: make sure we use the right one;
+ * a better approach would be to run thru
+ * the whole list and take care of all */
+ /* NOTE: since we issue the search request,
+ * we should know what controls to expect,
+ * and there should be none apart from the
+ * sync-related control */
+ rctrlp = ldap_control_find( LDAP_CONTROL_SYNC_STATE, rctrls, &next );
+ if ( next && ldap_control_find( LDAP_CONTROL_SYNC_STATE, next, NULL ) )
+ {
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "got search entry with multiple "
+ "Sync State control (%s)\n", si->si_ridtxt, bdn.bv_val );
+ ldap_controls_free( rctrls );
+ rc = -1;
+ goto done;
+ }
+ }
+ if ( rctrlp == NULL ) {
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "got search entry without "
+ "Sync State control (%s)\n", si->si_ridtxt, bdn.bv_val );
+ rc = -1;
+ goto done;
+ }
+ ber_init2( ber, &rctrlp->ldctl_value, LBER_USE_DER );
+ if ( ber_scanf( ber, "{em" /*"}"*/, &syncstate, &syncUUID[0] )
+ == LBER_ERROR ) {
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s malformed message (%s)\n",
+ si->si_ridtxt, bdn.bv_val );
+ ldap_controls_free( rctrls );
+ rc = -1;
+ goto done;
+ }
+ /* FIXME: what if syncUUID is NULL or empty?
+ * (happens with back-sql...) */
+ if ( syncUUID[0].bv_len != UUIDLEN ) {
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "got empty or invalid syncUUID with LDAP_SYNC_%s (%s)\n",
+ si->si_ridtxt,
+ syncrepl_state2str( syncstate ), bdn.bv_val );
+ ldap_controls_free( rctrls );
+ rc = -1;
+ goto done;
+ }
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
+ if ( ber_scanf( ber, /*"{"*/ "m}", &cookie ) != LBER_ERROR ) {
+
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n",
+ si->si_ridtxt,
+ BER_BVISNULL( &cookie ) ? "" : cookie.bv_val );
+
+ if ( !BER_BVISNULL( &cookie ) ) {
+ ch_free( syncCookie.octet_str.bv_val );
+ ber_dupbv( &syncCookie.octet_str, &cookie );
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieRcvd, &cookie );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+ if ( !BER_BVISNULL( &syncCookie.octet_str ) )
+ {
+ slap_parse_sync_cookie( &syncCookie, NULL );
+ if ( syncCookie.ctxcsn ) {
+ int i, slot, sid = slap_parse_csn_sid( syncCookie.ctxcsn );
+ check_syncprov( op, si );
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ i = check_csn_age( si, &bdn, syncCookie.ctxcsn, sid, (cookie_vals *)&si->si_cookieState->cs_vals, NULL );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ if ( i == CV_CSN_OLD ) {
+ si->si_too_old = 1;
+ ldap_controls_free( rctrls );
+ rc = 0;
+ goto done;
+ }
+ si->si_too_old = 0;
+
+ /* check pending CSNs too */
+ if (( rc = get_pmutex( si )))
+ goto done;
+
+ i = check_csn_age( si, &bdn, syncCookie.ctxcsn, sid, (cookie_vals *)&si->si_cookieState->cs_pvals, &slot );
+ if ( i == CV_CSN_OK ) {
+ ber_bvreplace( &si->si_cookieState->cs_pvals[slot],
+ syncCookie.ctxcsn );
+ } else if ( i == CV_CSN_OLD ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex );
+ ldap_controls_free( rctrls );
+ rc = 0;
+ goto done;
+ } else {
+ /* new SID, add it */
+ slap_insert_csn_sids(
+ (struct sync_cookie *)&si->si_cookieState->cs_pvals,
+ slot, sid, syncCookie.ctxcsn );
+ }
+ assert( punlock < 0 );
+ punlock = slot;
+ } else if (si->si_too_old) {
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s CSN too old, ignoring (%s)\n",
+ si->si_ridtxt, bdn.bv_val );
+ ldap_controls_free( rctrls );
+ rc = 0;
+ goto done;
+ }
+ op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie;
+ }
+ }
+ }
+ rc = 0;
+ if ( si->si_syncdata && si->si_logstate == SYNCLOG_LOGGING ) {
+ modlist = NULL;
+ if ( ( rc = syncrepl_message_to_op( si, op, msg, punlock < 0 ) ) == LDAP_SUCCESS &&
+ syncCookie.ctxcsn )
+ {
+ rc = syncrepl_updateCookie( si, op, &syncCookie, 0 );
+ } else
+logerr:
+ switch ( rc ) {
+ case LDAP_ALREADY_EXISTS:
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ rc = LDAP_SYNC_REFRESH_REQUIRED;
+ si->si_logstate = SYNCLOG_FALLBACK;
+ ldap_abandon_ext( si->si_ld, si->si_msgid, NULL, NULL );
+ bdn.bv_val[bdn.bv_len] = '\0';
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s delta-sync lost sync on (%s), switching to REFRESH\n",
+ si->si_ridtxt, bdn.bv_val );
+ if (si->si_strict_refresh) {
+ slap_suspend_listeners();
+ connections_drop();
+ }
+ break;
+ default:
+ break;
+ }
+ } else if ( ( rc = syncrepl_message_to_entry( si, op, msg,
+ &modlist, &entry, syncstate, syncUUID ) ) == LDAP_SUCCESS )
+ {
+ if ( punlock < 0 ) {
+ if (( rc = get_pmutex( si )))
+ goto done;
+ }
+ if ( ( rc = syncrepl_entry( si, op, entry, &modlist,
+ syncstate, syncUUID, syncCookie.ctxcsn ) ) == LDAP_SUCCESS &&
+ syncCookie.ctxcsn )
+ {
+ rc = syncrepl_updateCookie( si, op, &syncCookie, 0 );
+ }
+ if ( punlock < 0 )
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex );
+ }
+ if ( punlock >= 0 ) {
+ /* on failure, revert pending CSN */
+ if ( rc != LDAP_SUCCESS ) {
+ int i;
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ for ( i = 0; i<si->si_cookieState->cs_num; i++ ) {
+ if ( si->si_cookieState->cs_sids[i] == si->si_cookieState->cs_psids[punlock] ) {
+ ber_bvreplace( &si->si_cookieState->cs_pvals[punlock],
+ &si->si_cookieState->cs_vals[i] );
+ break;
+ }
+ }
+ if ( i == si->si_cookieState->cs_num )
+ si->si_cookieState->cs_pvals[punlock].bv_val[0] = '\0';
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex );
+ }
+ ldap_controls_free( rctrls );
+ if ( modlist ) {
+ slap_mods_free( modlist, 1 );
+ }
+ if ( rc )
+ goto done;
+ break;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrep2: %s reference received error\n",
+ si->si_ridtxt );
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ Debug( LDAP_DEBUG_SYNC,
+ "do_syncrep2: %s LDAP_RES_SEARCH_RESULT\n",
+ si->si_ridtxt );
+ err = LDAP_OTHER; /* FIXME check parse result properly */
+ ldap_parse_result( si->si_ld, msg, &err, NULL, NULL, NULL,
+ &rctrls, 0 );
+#ifdef LDAP_X_SYNC_REFRESH_REQUIRED
+ if ( err == LDAP_X_SYNC_REFRESH_REQUIRED ) {
+ /* map old result code to registered code */
+ err = LDAP_SYNC_REFRESH_REQUIRED;
+ }
+#endif
+ if ( err == LDAP_SYNC_REFRESH_REQUIRED ) {
+ if ( si->si_logstate == SYNCLOG_LOGGING ) {
+ si->si_logstate = SYNCLOG_FALLBACK;
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s delta-sync lost sync, switching to REFRESH\n",
+ si->si_ridtxt );
+ if (si->si_strict_refresh) {
+ slap_suspend_listeners();
+ connections_drop();
+ }
+ }
+ rc = err;
+ goto done;
+ }
+ if ( err ) {
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrep2: %s LDAP_RES_SEARCH_RESULT (%d) %s\n",
+ si->si_ridtxt, err, ldap_err2string( err ) );
+ }
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG && err == LDAP_SUCCESS ) {
+ rc = syncrepl_dsee_update( si, op );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( si->si_logstate == SYNCLOG_FALLBACK ) {
+ si->si_logstate = SYNCLOG_LOGGING;
+ si->si_refreshDone = 1;
+ rc = LDAP_SYNC_REFRESH_REQUIRED;
+ } else {
+ rc = SYNC_REPOLL;
+ }
+ }
+ goto done;
+ }
+ if ( rctrls ) {
+ LDAPControl **next = NULL;
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( si->si_ctype == MSAD_DIRSYNC ) {
+ rc = syncrepl_dirsync_cookie( si, op, rctrls );
+ if ( rc == LDAP_SUCCESS )
+ rc = SYNC_REPOLL; /* schedule a re-poll */
+ goto done;
+ }
+#endif
+ /* NOTE: make sure we use the right one;
+ * a better approach would be to run thru
+ * the whole list and take care of all */
+ /* NOTE: since we issue the search request,
+ * we should know what controls to expect,
+ * and there should be none apart from the
+ * sync-related control */
+ rctrlp = ldap_control_find( LDAP_CONTROL_SYNC_DONE, rctrls, &next );
+ if ( next && ldap_control_find( LDAP_CONTROL_SYNC_DONE, next, NULL ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "got search result with multiple "
+ "Sync State control\n", si->si_ridtxt );
+ ldap_controls_free( rctrls );
+ rc = SYNC_ERROR;
+ goto done;
+ }
+ }
+ if ( rctrlp ) {
+ ber_init2( ber, &rctrlp->ldctl_value, LBER_USE_DER );
+
+ ber_scanf( ber, "{" /*"}"*/);
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE ) {
+ ber_scanf( ber, "m", &cookie );
+
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n",
+ si->si_ridtxt,
+ BER_BVISNULL( &cookie ) ? "" : cookie.bv_val );
+
+ if ( !BER_BVISNULL( &cookie ) ) {
+ ch_free( syncCookie.octet_str.bv_val );
+ ber_dupbv( &syncCookie.octet_str, &cookie);
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieRcvd, &cookie );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+ if ( !BER_BVISNULL( &syncCookie.octet_str ) )
+ {
+ slap_parse_sync_cookie( &syncCookie, NULL );
+ op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie;
+ }
+ }
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_REFRESHDELETES )
+ {
+ ber_scanf( ber, "b", &refreshDeletes );
+ }
+ ber_scanf( ber, /*"{"*/ "}" );
+ }
+ if ( SLAP_MULTIPROVIDER( op->o_bd ) && check_syncprov( op, si )) {
+ slap_sync_cookie_free( &syncCookie_req, 0 );
+ slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie );
+ }
+ if ( !syncCookie.ctxcsn ) {
+ match = 1;
+ } else if ( !syncCookie_req.ctxcsn ) {
+ match = -1;
+ m = 0;
+ } else {
+ match = compare_csns( &syncCookie_req, &syncCookie, &m );
+ }
+ if ( rctrls ) {
+ ldap_controls_free( rctrls );
+ }
+ if (si->si_type != LDAP_SYNC_REFRESH_AND_PERSIST) {
+ /* FIXME : different error behaviors according to
+ * 1) err code : LDAP_BUSY ...
+ * 2) on err policy : stop service, stop sync, retry
+ */
+ if ( refreshDeletes == 0 && match < 0 && err == LDAP_SUCCESS )
+ {
+ syncrepl_del_nonpresent( op, si, NULL,
+ &syncCookie, m );
+ } else if ( si->si_presentlist ) {
+ presentlist_free( si->si_presentlist );
+ si->si_presentlist = NULL;
+ }
+ }
+ if ( syncCookie.ctxcsn && match < 0 && err == LDAP_SUCCESS )
+ {
+ rc = syncrepl_updateCookie( si, op, &syncCookie, 1 );
+ }
+ if ( err == LDAP_SUCCESS
+ && si->si_logstate == SYNCLOG_FALLBACK ) {
+ si->si_logstate = SYNCLOG_LOGGING;
+ si->si_refreshDone = 1;
+ rc = LDAP_SYNC_REFRESH_REQUIRED;
+ slap_resume_listeners();
+ } else {
+ /* for persist, we shouldn't get a SearchResult so this is an error */
+ if ( si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST )
+ rc = SYNC_ERROR;
+ else
+ rc = SYNC_REPOLL;
+ }
+ goto done;
+
+ case LDAP_RES_INTERMEDIATE:
+ retoid = NULL;
+ retdata = NULL;
+ rc = ldap_parse_intermediate( si->si_ld, msg,
+ &retoid, &retdata, NULL, 0 );
+ if ( !rc && !strcmp( retoid, LDAP_SYNC_INFO ) ) {
+ ber_init2( ber, retdata, LBER_USE_DER );
+
+ switch ( si_tag = ber_peek_tag( ber, &len ) ) {
+ ber_tag_t tag;
+ case LDAP_TAG_SYNC_NEW_COOKIE:
+ Debug( LDAP_DEBUG_SYNC,
+ "do_syncrep2: %s %s - %s\n",
+ si->si_ridtxt,
+ "LDAP_RES_INTERMEDIATE",
+ "NEW_COOKIE" );
+ ber_scanf( ber, "tm", &tag, &cookie );
+ Debug( LDAP_DEBUG_SYNC,
+ "do_syncrep2: %s NEW_COOKIE: %s\n",
+ si->si_ridtxt,
+ cookie.bv_val );
+ if ( !BER_BVISNULL( &cookie ) ) {
+ ch_free( syncCookie.octet_str.bv_val );
+ ber_dupbv( &syncCookie.octet_str, &cookie );
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieRcvd, &cookie );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+ if (!BER_BVISNULL( &syncCookie.octet_str ) ) {
+ slap_parse_sync_cookie( &syncCookie, NULL );
+ op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie;
+ }
+ break;
+ case LDAP_TAG_SYNC_REFRESH_DELETE:
+ case LDAP_TAG_SYNC_REFRESH_PRESENT:
+ Debug( LDAP_DEBUG_SYNC,
+ "do_syncrep2: %s %s - %s\n",
+ si->si_ridtxt,
+ "LDAP_RES_INTERMEDIATE",
+ si_tag == LDAP_TAG_SYNC_REFRESH_PRESENT ?
+ "REFRESH_PRESENT" : "REFRESH_DELETE" );
+ if ( si->si_refreshDone ) {
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "server sent multiple refreshDone "
+ "messages? Ending session\n",
+ si->si_ridtxt );
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ if ( si_tag == LDAP_TAG_SYNC_REFRESH_DELETE ) {
+ si->si_refreshDelete = 1;
+ } else {
+ si->si_refreshPresent = 1;
+ }
+ ber_scanf( ber, "t{" /*"}"*/, &tag );
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SYNC_COOKIE )
+ {
+ ber_scanf( ber, "m", &cookie );
+
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n",
+ si->si_ridtxt,
+ BER_BVISNULL( &cookie ) ? "" : cookie.bv_val );
+
+ if ( !BER_BVISNULL( &cookie ) ) {
+ ch_free( syncCookie.octet_str.bv_val );
+ ber_dupbv( &syncCookie.octet_str, &cookie );
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieRcvd, &cookie );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+ if ( !BER_BVISNULL( &syncCookie.octet_str ) )
+ {
+ slap_parse_sync_cookie( &syncCookie, NULL );
+ op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie;
+ }
+ }
+ /* Defaults to TRUE */
+ if ( ber_peek_tag( ber, &len ) ==
+ LDAP_TAG_REFRESHDONE )
+ {
+ ber_scanf( ber, "b", &si->si_refreshDone );
+ } else
+ {
+ si->si_refreshDone = 1;
+ }
+ ber_scanf( ber, /*"{"*/ "}" );
+ if ( refreshing && si->si_refreshDone ) {
+ refresh_finished( si );
+ refreshing = 0;
+ }
+ break;
+ case LDAP_TAG_SYNC_ID_SET:
+ Debug( LDAP_DEBUG_SYNC,
+ "do_syncrep2: %s %s - %s\n",
+ si->si_ridtxt,
+ "LDAP_RES_INTERMEDIATE",
+ "SYNC_ID_SET" );
+ ber_scanf( ber, "t{" /*"}"*/, &tag );
+ if ( ber_peek_tag( ber, &len ) ==
+ LDAP_TAG_SYNC_COOKIE )
+ {
+ ber_scanf( ber, "m", &cookie );
+
+ Debug( LDAP_DEBUG_SYNC, "do_syncrep2: %s cookie=%s\n",
+ si->si_ridtxt,
+ BER_BVISNULL( &cookie ) ? "" : cookie.bv_val );
+
+ if ( !BER_BVISNULL( &cookie ) ) {
+ ch_free( syncCookie.octet_str.bv_val );
+ ber_dupbv( &syncCookie.octet_str, &cookie );
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ ber_bvreplace( &si->si_lastCookieRcvd, &cookie );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ }
+ if ( !BER_BVISNULL( &syncCookie.octet_str ) )
+ {
+ slap_parse_sync_cookie( &syncCookie, NULL );
+ op->o_controls[slap_cids.sc_LDAPsync] = &syncCookie;
+ compare_csns( &syncCookie_req, &syncCookie, &m );
+ }
+ }
+ if ( ber_peek_tag( ber, &len ) ==
+ LDAP_TAG_REFRESHDELETES )
+ {
+ ber_scanf( ber, "b", &refreshDeletes );
+ }
+ syncUUIDs = NULL;
+ rc = ber_scanf( ber, "[W]", &syncUUIDs );
+ ber_scanf( ber, /*"{"*/ "}" );
+ if ( rc != LBER_ERROR ) {
+ if ( refreshDeletes ) {
+ syncrepl_del_nonpresent( op, si, syncUUIDs,
+ &syncCookie, m );
+ ber_bvarray_free_x( syncUUIDs, op->o_tmpmemctx );
+ } else {
+ int i;
+ for ( i = 0; !BER_BVISNULL( &syncUUIDs[i] ); i++ ) {
+ (void)presentlist_insert( si, &syncUUIDs[i] );
+ slap_sl_free( syncUUIDs[i].bv_val, op->o_tmpmemctx );
+ }
+ slap_sl_free( syncUUIDs, op->o_tmpmemctx );
+ }
+ }
+ rc = 0;
+ slap_sync_cookie_free( &syncCookie, 0 );
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrep2: %s unknown syncinfo tag (%ld)\n",
+ si->si_ridtxt, (long) si_tag );
+ ldap_memfree( retoid );
+ ber_bvfree( retdata );
+ continue;
+ }
+
+ if ( SLAP_MULTIPROVIDER( op->o_bd ) && check_syncprov( op, si )) {
+ slap_sync_cookie_free( &syncCookie_req, 0 );
+ slap_dup_sync_cookie( &syncCookie_req, &si->si_syncCookie );
+ }
+ if ( !syncCookie.ctxcsn ) {
+ match = 1;
+ } else if ( !syncCookie_req.ctxcsn ) {
+ match = -1;
+ m = 0;
+ } else {
+ match = compare_csns( &syncCookie_req, &syncCookie, &m );
+ }
+
+ if ( match < 0 ) {
+ if ( si->si_refreshPresent == 1 &&
+ si_tag != LDAP_TAG_SYNC_NEW_COOKIE ) {
+ syncrepl_del_nonpresent( op, si, NULL,
+ &syncCookie, m );
+ }
+
+ if ( syncCookie.ctxcsn )
+ {
+ rc = syncrepl_updateCookie( si, op, &syncCookie, 1 );
+ }
+ if ( si->si_presentlist ) {
+ presentlist_free( si->si_presentlist );
+ si->si_presentlist = NULL;
+ }
+ }
+
+ ldap_memfree( retoid );
+ ber_bvfree( retdata );
+
+ if ( rc )
+ goto done;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "unknown intermediate response (%d)\n",
+ si->si_ridtxt, rc );
+ ldap_memfree( retoid );
+ ber_bvfree( retdata );
+ }
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_ANY, "do_syncrep2: %s "
+ "unknown message (0x%02lx)\n",
+ si->si_ridtxt,
+ (unsigned long)ldap_msgtype( msg ) );
+ break;
+
+ }
+ if ( !BER_BVISNULL( &syncCookie.octet_str ) ) {
+ slap_sync_cookie_free( &syncCookie_req, 0 );
+ syncCookie_req = syncCookie;
+ memset( &syncCookie, 0, sizeof( syncCookie ));
+ }
+ ldap_msgfree( msg );
+ msg = NULL;
+ if ( ldap_pvt_thread_pool_pausing( &connection_pool )) {
+ slap_sync_cookie_free( &syncCookie, 0 );
+ slap_sync_cookie_free( &syncCookie_req, 0 );
+ return SYNC_PAUSED;
+ }
+ }
+
+ if ( rc == SYNC_ERROR ) {
+ rc = LDAP_OTHER;
+ ldap_get_option( si->si_ld, LDAP_OPT_ERROR_NUMBER, &rc );
+ err = rc;
+ }
+
+done:
+ if ( err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrep2: %s (%d) %s\n",
+ si->si_ridtxt, err, ldap_err2string( err ) );
+ }
+ if ( refreshing && ( rc || si->si_refreshDone ) ) {
+ refresh_finished( si );
+ }
+
+ slap_sync_cookie_free( &syncCookie, 0 );
+ slap_sync_cookie_free( &syncCookie_req, 0 );
+
+ if ( msg ) ldap_msgfree( msg );
+
+ if ( rc ) {
+ if ( rc == LDAP_SYNC_REFRESH_REQUIRED && si->si_logstate == SYNCLOG_LOGGING && si->si_ld )
+ return rc;
+ /* never reuse existing connection */
+ if ( si->si_conn ) {
+ connection_client_stop( si->si_conn );
+ si->si_conn = NULL;
+ }
+ ldap_unbind_ext( si->si_ld, NULL, NULL );
+ si->si_ld = NULL;
+ }
+
+ return rc;
+}
+
+static int
+syncrepl_monitor_add( syncinfo_t *si );
+
+static int
+syncrepl_monitor_del( syncinfo_t *si );
+
+static void *
+do_syncrepl(
+ void *ctx,
+ void *arg )
+{
+ struct re_s* rtask = arg;
+ syncinfo_t *si = ( syncinfo_t * ) rtask->arg;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ int rc = LDAP_SUCCESS;
+ int dostop = 0;
+ ber_socket_t s;
+ int i, fail = 0, freeinfo = 0;
+ Backend *be;
+
+ if ( si == NULL )
+ return NULL;
+ if ( slapd_shutdown )
+ return NULL;
+
+ if ( !si->si_monitorInited ) {
+ syncrepl_monitor_add( si );
+ si->si_monitorInited = 1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "=>do_syncrepl %s\n", si->si_ridtxt );
+
+ ldap_pvt_thread_mutex_lock( &si->si_mutex );
+
+ si->si_too_old = 0;
+
+ if ( si->si_ctype < 1 ) {
+ goto deleted;
+ }
+
+ switch( abs( si->si_type ) ) {
+ case LDAP_SYNC_REFRESH_ONLY:
+ case LDAP_SYNC_REFRESH_AND_PERSIST:
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ case MSAD_DIRSYNC:
+#endif
+ break;
+ default:
+ ldap_pvt_thread_mutex_unlock( &si->si_mutex );
+ return NULL;
+ }
+
+ if ( slapd_shutdown ) {
+ if ( si->si_ld ) {
+ if ( si->si_conn ) {
+ connection_client_stop( si->si_conn );
+ si->si_conn = NULL;
+ }
+ ldap_unbind_ext( si->si_ld, NULL, NULL );
+ si->si_ld = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_mutex );
+ return NULL;
+ }
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+ /* o_connids must be unique for slap_graduate_commit_csn */
+ op->o_connid = SLAPD_SYNC_RID2SYNCCONN(si->si_rid);
+
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+ be = si->si_be;
+
+ /* Coordinate contextCSN updates with any syncprov overlays
+ * in use. This may be complicated by the use of the glue
+ * overlay.
+ *
+ * Typically there is a single syncprov controlling the entire
+ * glued tree. In that case, our contextCSN updates should
+ * go to the primary DB. But if there is no syncprov on the
+ * primary DB, then nothing special is needed here.
+ *
+ * Alternatively, there may be individual syncprov overlays
+ * on each glued branch. In that case, each syncprov only
+ * knows about changes within its own branch. And so our
+ * contextCSN updates should only go to the local DB.
+ */
+ if ( !si->si_wbe ) {
+ if ( SLAP_GLUE_SUBORDINATE( be )) {
+ BackendDB *b0 = be;
+ struct berval ndn = be->be_nsuffix[0];
+ while ( !overlay_is_inst( be, "syncprov" )) {
+ /* If we got all the way to the primary without any
+ * syncprov, just use original backend */
+ if ( SLAP_GLUE_INSTANCE( be )) {
+ be = b0;
+ break;
+ }
+ dnParent( &ndn, &ndn );
+ be = select_backend( &ndn, 0 );
+ }
+ }
+ si->si_wbe = be;
+ if ( SLAP_SYNC_SUBENTRY( si->si_wbe )) {
+ build_new_dn( &si->si_contextdn, &si->si_wbe->be_nsuffix[0],
+ (struct berval *)&slap_ldapsync_cn_bv, NULL );
+ } else {
+ si->si_contextdn = si->si_wbe->be_nsuffix[0];
+ }
+ }
+ if ( !si->si_schemachecking )
+ op->o_no_schema_check = 1;
+
+ /* Establish session, do search */
+ if ( !si->si_ld ) {
+ if ( si->si_presentlist ) {
+ presentlist_free( si->si_presentlist );
+ si->si_presentlist = NULL;
+ }
+
+ /* use main DB when retrieving contextCSN */
+ op->o_bd = si->si_wbe;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ rc = do_syncrep1( op, si );
+ } else if ( !si->si_msgid ) {
+ /* We got a SYNC_BUSY, now told to resume */
+ rc = ldap_sync_search( si, op->o_tmpmemctx );
+ }
+ if ( rc == SYNC_BUSY ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_mutex );
+ return NULL;
+ }
+
+reload:
+ /* Process results */
+ if ( rc == LDAP_SUCCESS ) {
+ ldap_get_option( si->si_ld, LDAP_OPT_DESC, &s );
+
+ if ( !BER_BVISEMPTY( &si->si_monitor_ndn ))
+ {
+ Sockaddr addr;
+ socklen_t len = sizeof( addr );
+ if ( !getsockname( s, &addr.sa_addr, &len )) {
+ si->si_connaddr.bv_val = si->si_connaddrbuf;
+ si->si_connaddr.bv_len = sizeof( si->si_connaddrbuf );
+ ldap_pvt_sockaddrstr( &addr, &si->si_connaddr );
+ }
+ }
+
+ /* use current DB */
+ op->o_bd = be;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ rc = do_syncrep2( op, si );
+ if ( rc == LDAP_SYNC_REFRESH_REQUIRED ) {
+ if ( si->si_logstate == SYNCLOG_LOGGING ) {
+ if ( BER_BVISNULL( &si->si_syncCookie.octet_str ))
+ slap_compose_sync_cookie( NULL, &si->si_syncCookie.octet_str,
+ si->si_syncCookie.ctxcsn, si->si_syncCookie.rid,
+ si->si_syncCookie.sid, NULL );
+ rc = ldap_sync_search( si, op->o_tmpmemctx );
+ goto reload;
+ }
+ /* give up but schedule an immedite retry */
+ rc = SYNC_PAUSED;
+ }
+
+deleted:
+ /* We got deleted while running on cn=config */
+ if ( si->si_ctype < 1 ) {
+ if ( si->si_ctype == -1 ) {
+ si->si_ctype = 0;
+ freeinfo = 1;
+ }
+ if ( si->si_conn )
+ dostop = 1;
+ rc = SYNC_SHUTDOWN;
+ }
+
+ if ( rc != SYNC_PAUSED ) {
+ if ( rc == SYNC_TIMEOUT ) {
+ /* there was nothing to read, try to listen for more */
+ if ( si->si_conn ) {
+ connection_client_enable( si->si_conn );
+ } else {
+ si->si_conn = connection_client_setup( s, do_syncrepl, arg );
+ }
+ } else if ( si->si_conn ) {
+ dostop = 1;
+ }
+ }
+ }
+
+ /* At this point, we have 5 cases:
+ * 1) for any hard failure, give up and remove this task
+ * 2) for ServerDown, reschedule this task to run later
+ * 3) for threadpool pause, reschedule to run immediately
+ * 4) for SYNC_REPOLL, reschedule to run later
+ * 5) for SYNC_TIMEOUT, reschedule to defer
+ */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+
+ if ( dostop ) {
+ connection_client_stop( si->si_conn );
+ si->si_conn = NULL;
+ }
+
+ if ( rc == SYNC_PAUSED ) {
+ rtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ rtask->interval.tv_sec = si->si_interval;
+ rc = 0;
+ } else if ( rc == SYNC_TIMEOUT ) {
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 1 );
+ } else if ( rc == SYNC_REPOLL ) {
+ rtask->interval.tv_sec = si->si_interval;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ if ( si->si_retrynum ) {
+ for ( i = 0; si->si_retrynum_init[i] != RETRYNUM_TAIL; i++ ) {
+ si->si_retrynum[i] = si->si_retrynum_init[i];
+ }
+ si->si_retrynum[i] = RETRYNUM_TAIL;
+ }
+ slap_wake_listener();
+ rc = 0;
+ } else {
+ for ( i = 0; si->si_retrynum && si->si_retrynum[i] <= 0; i++ ) {
+ if ( si->si_retrynum[i] == RETRYNUM_FOREVER || si->si_retrynum[i] == RETRYNUM_TAIL )
+ break;
+ }
+
+ if ( si->si_ctype < 1 || rc == SYNC_SHUTDOWN
+ || !si->si_retrynum || si->si_retrynum[i] == RETRYNUM_TAIL ) {
+ if ( si->si_re ) {
+ ldap_pvt_runqueue_remove( &slapd_rq, rtask );
+ si->si_re = NULL;
+ }
+ fail = RETRYNUM_TAIL;
+ } else if ( RETRYNUM_VALID( si->si_retrynum[i] ) ) {
+ if ( si->si_retrynum[i] > 0 )
+ si->si_retrynum[i]--;
+ fail = si->si_retrynum[i];
+ rtask->interval.tv_sec = si->si_retryinterval[i];
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ slap_wake_listener();
+ }
+ }
+
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ ldap_pvt_thread_mutex_unlock( &si->si_mutex );
+
+ if ( rc ) {
+ if ( fail == RETRYNUM_TAIL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrepl: %s rc %d quitting\n",
+ si->si_ridtxt, rc );
+ } else if ( fail > 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrepl: %s rc %d retrying (%d retries left)\n",
+ si->si_ridtxt, rc, fail );
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "do_syncrepl: %s rc %d retrying\n",
+ si->si_ridtxt, rc );
+ }
+ }
+
+ /* Do final delete cleanup */
+ if ( freeinfo ) {
+ syncinfo_free( si, 0 );
+ }
+ return NULL;
+}
+
+static int
+syncrepl_rewrite_dn(
+ syncinfo_t *si,
+ struct berval *dn,
+ struct berval *sdn )
+{
+ char nul;
+ int rc;
+
+ nul = dn->bv_val[dn->bv_len];
+ dn->bv_val[dn->bv_len] = 0;
+ rc = rewrite( si->si_rewrite, SUFFIXM_CTX, dn->bv_val, &sdn->bv_val );
+ dn->bv_val[dn->bv_len] = nul;
+
+ if ( sdn->bv_val == dn->bv_val )
+ sdn->bv_val = NULL;
+ else if ( rc == REWRITE_REGEXEC_OK && sdn->bv_val )
+ sdn->bv_len = strlen( sdn->bv_val );
+ return rc;
+}
+#define REWRITE_VAL(si, ad, bv, bv2) \
+ BER_BVZERO( &bv2 ); \
+ if ( si->si_rewrite && ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName) \
+ syncrepl_rewrite_dn( si, &bv, &bv2); \
+ if ( BER_BVISNULL( &bv2 )) \
+ ber_dupbv( &bv2, &bv )
+#define REWRITE_DN(si, bv, bv2, dn, ndn) \
+ BER_BVZERO( &bv2 ); \
+ if (si->si_rewrite) \
+ syncrepl_rewrite_dn(si, &bv, &bv2); \
+ rc = dnPrettyNormal( NULL, bv2.bv_val ? &bv2 : &bv, &dn, &ndn, op->o_tmpmemctx ); \
+ ch_free(bv2.bv_val)
+
+static slap_verbmasks modops[] = {
+ { BER_BVC("add"), LDAP_REQ_ADD },
+ { BER_BVC("delete"), LDAP_REQ_DELETE },
+ { BER_BVC("modify"), LDAP_REQ_MODIFY },
+ { BER_BVC("modrdn"), LDAP_REQ_MODRDN},
+ { BER_BVNULL, 0 }
+};
+
+static int
+syncrepl_accesslog_mods(
+ syncinfo_t *si,
+ struct berval *vals,
+ struct Modifications **modres
+)
+{
+ char *colon;
+ const char *text;
+ AttributeDescription *ad;
+ struct berval bv, bv2;
+ short op;
+ Modifications *mod = NULL, *modlist = NULL, **modtail;
+ int i, rc = 0;
+
+ modtail = &modlist;
+
+ for (i=0; !BER_BVISNULL( &vals[i] ); i++) {
+ ad = NULL;
+ bv = vals[i];
+
+ colon = ber_bvchr( &bv, ':' );
+ if ( !colon ) {
+ /* Invalid */
+ continue;
+ } else if ( colon == bv.bv_val ) {
+ /* ITS#6545: An empty attribute signals that a new mod
+ * is about to start */
+ mod = NULL;
+ continue;
+ }
+
+ bv.bv_len = colon - bv.bv_val;
+ if ( slap_bv2ad( &bv, &ad, &text ) ) {
+ /* Invalid */
+ Debug( LDAP_DEBUG_ANY, "syncrepl_accesslog_mods: %s "
+ "Invalid attribute %s, %s\n",
+ si->si_ridtxt, bv.bv_val, text );
+ slap_mods_free( modlist, 1 );
+ modlist = NULL;
+ rc = -1;
+ break;
+ }
+
+ /* Ignore dynamically generated attrs */
+ if ( ad->ad_type->sat_flags & SLAP_AT_DYNAMIC ) {
+ continue;
+ }
+
+ /* Ignore excluded attrs */
+ if ( ldap_charray_inlist( si->si_exattrs,
+ ad->ad_type->sat_cname.bv_val ) )
+ {
+ continue;
+ }
+
+ switch(colon[1]) {
+ case '+': op = LDAP_MOD_ADD; break;
+ case '-': op = LDAP_MOD_DELETE; break;
+ case '=': op = LDAP_MOD_REPLACE; break;
+ case '#': op = LDAP_MOD_INCREMENT; break;
+ default: continue;
+ }
+
+ if ( !mod || ad != mod->sml_desc || op != mod->sml_op ) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_flags = 0;
+ mod->sml_op = op;
+ mod->sml_next = NULL;
+ mod->sml_desc = ad;
+ mod->sml_type = ad->ad_cname;
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+ mod->sml_numvals = 0;
+
+ if ( is_at_single_value( ad->ad_type ) ) {
+ if ( op == LDAP_MOD_ADD ) {
+ /* ITS#9295 an ADD might conflict with an existing value */
+ mod->sml_op = LDAP_MOD_REPLACE;
+ } else if ( op == LDAP_MOD_DELETE ) {
+ /* ITS#9295 the above REPLACE could invalidate subsequent
+ * DELETEs */
+ mod->sml_op = SLAP_MOD_SOFTDEL;
+ }
+ }
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ if ( colon[2] == ' ' ) {
+ bv.bv_val = colon + 3;
+ bv.bv_len = vals[i].bv_len - ( bv.bv_val - vals[i].bv_val );
+ REWRITE_VAL( si, ad, bv, bv2 );
+ ber_bvarray_add( &mod->sml_values, &bv2 );
+ mod->sml_numvals++;
+ }
+ }
+ *modres = modlist;
+ return rc;
+}
+
+static int
+syncrepl_dsee_uuid(
+ struct berval *dseestr,
+ struct berval *syncUUID,
+ void *ctx
+)
+{
+ slap_mr_normalize_func *normf;
+ /* DSEE UUID is of form 12345678-12345678-12345678-12345678 */
+ if ( dseestr->bv_len != 35 )
+ return -1;
+ dseestr->bv_len++;
+ dseestr->bv_val[35] = '-';
+ normf = slap_schema.si_ad_entryUUID->ad_type->sat_equality->smr_normalize;
+ if ( normf( SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, NULL, NULL,
+ dseestr, &syncUUID[0], ctx ))
+ return -1;
+ (void)slap_uuidstr_from_normalized( &syncUUID[1], &syncUUID[0], ctx );
+ return LDAP_SUCCESS;
+}
+
+static int
+syncrepl_changelog_mods(
+ syncinfo_t *si,
+ ber_tag_t req,
+ struct berval *vals,
+ struct Modifications **modres,
+ struct berval *uuid,
+ void *ctx
+)
+{
+ LDIFRecord lr;
+ struct berval rbuf = vals[0];
+ int i, rc;
+ int lrflags = LDIF_NO_DN;
+ Modifications *mod = NULL, *modlist = NULL, **modtail = &modlist;
+
+ if ( req == LDAP_REQ_ADD )
+ lrflags |= LDIF_ENTRIES_ONLY|LDIF_DEFAULT_ADD;
+ else
+ lrflags |= LDIF_MODS_ONLY;
+
+ rc = ldap_parse_ldif_record_x( &rbuf, 0, &lr, "syncrepl", lrflags, ctx );
+ for (i = 0; lr.lrop_mods[i] != NULL; i++) {
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int j;
+ if ( slap_str2ad( lr.lrop_mods[i]->mod_type, &ad, &text ) ) {
+ /* Invalid */
+ Debug( LDAP_DEBUG_ANY, "syncrepl_changelog_mods: %s "
+ "Invalid attribute %s, %s\n",
+ si->si_ridtxt, lr.lrop_mods[i]->mod_type, text );
+ slap_mods_free( modlist, 1 );
+ modlist = NULL;
+ rc = -1;
+ break;
+ }
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_flags = 0;
+ mod->sml_op = lr.lrop_mods[i]->mod_op ^ LDAP_MOD_BVALUES;
+ mod->sml_next = NULL;
+ mod->sml_desc = ad;
+ mod->sml_type = ad->ad_cname;
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+ j = 0;
+ if ( lr.lrop_mods[i]->mod_bvalues != NULL ) {
+ for (; lr.lrop_mods[i]->mod_bvalues[j] != NULL; j++ ) {
+ struct berval bv, bv2;
+ bv = *(lr.lrop_mods[i]->mod_bvalues[j]);
+ REWRITE_VAL( si, ad, bv, bv2 );
+ ber_bvarray_add( &mod->sml_values, &bv2 );
+ }
+ }
+ mod->sml_numvals = j;
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ ldap_ldif_record_done( &lr );
+
+ if ( req == LDAP_REQ_ADD && !BER_BVISNULL( uuid )) {
+ struct berval uuids[2];
+ if ( !syncrepl_dsee_uuid( uuid, uuids, ctx )) {
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mod->sml_flags = 0;
+ mod->sml_op = LDAP_MOD_ADD;
+ mod->sml_next = NULL;
+ mod->sml_desc = slap_schema.si_ad_entryUUID;
+ mod->sml_type = slap_schema.si_ad_entryUUID->ad_cname;
+ mod->sml_values = ch_malloc( 2 * sizeof(struct berval));
+ mod->sml_nvalues = NULL;
+ ber_dupbv( &mod->sml_values[0], &uuids[1] );
+ BER_BVZERO( &mod->sml_values[1] );
+ slap_sl_free( uuids[0].bv_val, ctx );
+ slap_sl_free( uuids[1].bv_val, ctx );
+ mod->sml_numvals = 1;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ }
+
+ *modres = modlist;
+ return rc;
+}
+
+typedef struct OpExtraSync {
+ OpExtra oe;
+ syncinfo_t *oe_si;
+} OpExtraSync;
+
+/* Copy the original modlist, split Replace ops into Delete/Add,
+ * and drop mod opattrs since this modification is in the past.
+ */
+static Modifications *mods_dup( Operation *op, Modifications *modlist, int match )
+{
+ Modifications *mod, *modnew = NULL, *modtail = NULL;
+ int size;
+ for ( ; modlist; modlist = modlist->sml_next ) {
+ /* older ops */
+ if ( match < 0 ) {
+ if ( modlist->sml_desc == slap_schema.si_ad_modifiersName ||
+ modlist->sml_desc == slap_schema.si_ad_modifyTimestamp ||
+ modlist->sml_desc == slap_schema.si_ad_entryCSN )
+ continue;
+ if ( modlist->sml_values == NULL && modlist->sml_op == LDAP_MOD_REPLACE ) {
+ /* ITS#9359 This adds no values, just change to a delete op */
+ modlist->sml_op = LDAP_MOD_DELETE;
+ } else if ( modlist->sml_op == LDAP_MOD_REPLACE ) {
+ mod = op->o_tmpalloc( sizeof(Modifications), op->o_tmpmemctx );
+ mod->sml_desc = modlist->sml_desc;
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+ mod->sml_op = LDAP_MOD_DELETE;
+ mod->sml_numvals = 0;
+ mod->sml_flags = 0;
+ if ( !modnew )
+ modnew = mod;
+ if ( modtail )
+ modtail->sml_next = mod;
+ modtail = mod;
+ }
+ }
+ if ( modlist->sml_numvals ) {
+ size = (modlist->sml_numvals+1) * sizeof(struct berval);
+ if ( modlist->sml_nvalues ) size *= 2;
+ } else {
+ size = 0;
+ }
+ size += sizeof(Modifications);
+ mod = op->o_tmpalloc( size, op->o_tmpmemctx );
+ if ( !modnew )
+ modnew = mod;
+ if ( modtail )
+ modtail->sml_next = mod;
+ modtail = mod;
+ mod->sml_desc = modlist->sml_desc;
+ mod->sml_numvals = modlist->sml_numvals;
+ mod->sml_flags = 0;
+ if ( modlist->sml_numvals ) {
+ int i;
+ mod->sml_values = (BerVarray)(mod+1);
+ for (i=0; i<mod->sml_numvals; i++)
+ mod->sml_values[i] = modlist->sml_values[i];
+ BER_BVZERO(&mod->sml_values[i]);
+ if ( modlist->sml_nvalues ) {
+ mod->sml_nvalues = mod->sml_values + mod->sml_numvals + 1;
+ for (i=0; i<mod->sml_numvals; i++)
+ mod->sml_nvalues[i] = modlist->sml_nvalues[i];
+ BER_BVZERO(&mod->sml_nvalues[i]);
+ } else {
+ mod->sml_nvalues = NULL;
+ }
+ } else {
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+ }
+ if ( match < 0 && modlist->sml_op == LDAP_MOD_REPLACE )
+ mod->sml_op = LDAP_MOD_ADD;
+ else
+ mod->sml_op = modlist->sml_op;
+ mod->sml_next = NULL;
+ }
+ return modnew;
+}
+
+typedef struct resolve_ctxt {
+ syncinfo_t *rx_si;
+ Entry *rx_entry;
+ Modifications *rx_mods;
+} resolve_ctxt;
+
+static void
+compare_vals( Modifications *m1, Modifications *m2 )
+{
+ int i, j;
+ struct berval *bv1, *bv2;
+
+ if ( m2->sml_nvalues ) {
+ bv2 = m2->sml_nvalues;
+ bv1 = m1->sml_nvalues;
+ } else {
+ bv2 = m2->sml_values;
+ bv1 = m1->sml_values;
+ }
+ for ( j=0; j<m2->sml_numvals; j++ ) {
+ for ( i=0; i<m1->sml_numvals; i++ ) {
+ if ( !ber_bvcmp( &bv1[i], &bv2[j] )) {
+ int k;
+ for ( k=i; k<m1->sml_numvals-1; k++ ) {
+ m1->sml_values[k] = m1->sml_values[k+1];
+ if ( m1->sml_nvalues )
+ m1->sml_nvalues[k] = m1->sml_nvalues[k+1];
+ }
+ BER_BVZERO(&m1->sml_values[k]);
+ if ( m1->sml_nvalues ) {
+ BER_BVZERO(&m1->sml_nvalues[k]);
+ }
+ m1->sml_numvals--;
+ i--;
+ }
+ }
+ }
+}
+
+static int
+syncrepl_resolve_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ resolve_ctxt *rx = op->o_callback->sc_private;
+ Attribute *a = attr_find( rs->sr_entry->e_attrs, ad_reqMod );
+ if ( a ) {
+ Modifications *oldmods, *newmods, *m1, *m2, **prev;
+ Entry *e = rx->rx_entry;
+ oldmods = rx->rx_mods;
+ syncrepl_accesslog_mods( rx->rx_si, a->a_vals, &newmods );
+ for ( m2 = newmods; m2; m2=m2->sml_next ) {
+ for ( prev = &oldmods, m1 = *prev; m1; m1 = *prev ) {
+ if ( m1->sml_desc != m2->sml_desc ) {
+ prev = &m1->sml_next;
+ continue;
+ }
+ if ( m2->sml_op == LDAP_MOD_DELETE ||
+ m2->sml_op == SLAP_MOD_SOFTDEL ||
+ m2->sml_op == LDAP_MOD_REPLACE ) {
+ int numvals = m2->sml_numvals;
+ if ( m2->sml_op == LDAP_MOD_REPLACE )
+ numvals = 0;
+ /* New delete All cancels everything */
+ if ( numvals == 0 ) {
+drop:
+ *prev = m1->sml_next;
+ op->o_tmpfree( m1, op->o_tmpmemctx );
+ continue;
+ }
+ if ( m1->sml_op == LDAP_MOD_DELETE ||
+ m1->sml_op == SLAP_MOD_SOFTDEL ) {
+ if ( m1->sml_numvals == 0 ) {
+ /* turn this to SOFTDEL later */
+ m1->sml_flags = SLAP_MOD_INTERNAL;
+ } else {
+ compare_vals( m1, m2 );
+ if ( !m1->sml_numvals )
+ goto drop;
+ }
+ } else if ( m1->sml_op == LDAP_MOD_ADD ) {
+ compare_vals( m1, m2 );
+ if ( !m1->sml_numvals )
+ goto drop;
+ }
+ }
+
+ if ( m2->sml_op == LDAP_MOD_ADD ||
+ m2->sml_op == LDAP_MOD_REPLACE ) {
+ if ( m2->sml_desc->ad_type->sat_atype.at_single_value )
+ goto drop;
+ if ( m1->sml_op == LDAP_MOD_DELETE ) {
+ if ( m2->sml_op == LDAP_MOD_REPLACE ) {
+ goto drop;
+ }
+ if ( !m1->sml_numvals ) {
+ Modifications *m;
+ unsigned int size, i;
+ /*
+ * ITS#9751 An ADD might supersede parts of
+ * this delete, but we still need to honour the
+ * rest. Keep resolving as if it was deleting
+ * specific values
+ */
+ a = attr_find( e->e_attrs, m1->sml_desc );
+ if ( !a ) {
+ goto drop;
+ }
+
+ size = (a->a_numvals+1) * sizeof(struct berval);
+ if ( a->a_nvals ) size *= 2;
+ size += sizeof(Modifications);
+ m = op->o_tmpalloc( size, op->o_tmpmemctx );
+ *m = *m1;
+
+ m->sml_numvals = a->a_numvals;
+ m->sml_values = (BerVarray)(m+1);
+
+ for ( i=0; i < a->a_numvals; i++ )
+ m->sml_values[i] = a->a_vals[i];
+ BER_BVZERO( &m->sml_values[i] );
+
+ if ( a->a_nvals ) {
+ m->sml_nvalues = m->sml_values + m->sml_numvals + 1;
+ for ( i=0; i < a->a_numvals; i++ )
+ m->sml_nvalues[i] = a->a_nvals[i];
+ BER_BVZERO( &m->sml_nvalues[i] );
+ } else {
+ m->sml_nvalues = NULL;
+ }
+ op->o_tmpfree( m1, op->o_tmpmemctx );
+ *prev = m1 = m;
+ }
+ }
+ compare_vals( m1, m2 );
+ if ( !m1->sml_numvals )
+ goto drop;
+ }
+ prev = &m1->sml_next;
+ }
+ }
+ slap_mods_free( newmods, 1 );
+ rx->rx_mods = oldmods;
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+typedef struct modify_ctxt {
+ Modifications *mx_orig;
+ Modifications *mx_free;
+ Entry *mx_entry;
+} modify_ctxt;
+
+static int
+syncrepl_modify_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ modify_ctxt *mx = sc->sc_private;
+ Modifications *ml;
+
+ op->orm_no_opattrs = 0;
+ op->orm_modlist = mx->mx_orig;
+ for ( ml = mx->mx_free; ml; ml = mx->mx_free ) {
+ mx->mx_free = ml->sml_next;
+ op->o_tmpfree( ml, op->o_tmpmemctx );
+ }
+ if ( mx->mx_entry ) {
+ entry_free( mx->mx_entry );
+ }
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncrepl_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ OpExtra *oex;
+ syncinfo_t *si;
+ Entry *e, *e_dup;
+ int rc, match = 0;
+ Modifications *mod, *newlist;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)syncrepl_message_to_op )
+ break;
+ }
+ if ( !oex )
+ return SLAP_CB_CONTINUE;
+
+ si = ((OpExtraSync *)oex)->oe_si;
+
+ /* Check if entryCSN in modlist is newer than entryCSN in entry.
+ * We do it here because the op has been serialized by accesslog
+ * by the time we get here. If the CSN is new enough, just do the
+ * mod. If not, we need to resolve conflicts.
+ */
+
+ for ( mod = op->orm_modlist; mod; mod=mod->sml_next ) {
+ if ( mod->sml_desc == slap_schema.si_ad_entryCSN ) break;
+ }
+ /* FIXME: what should we do if entryCSN is missing from the mod? */
+ if ( !mod )
+ return SLAP_CB_CONTINUE;
+
+ {
+ int sid = slap_parse_csn_sid( &mod->sml_nvalues[0] );
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ rc = check_csn_age( si, &op->o_req_dn, &mod->sml_nvalues[0],
+ sid, (cookie_vals *)&si->si_cookieState->cs_vals, NULL );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ if ( rc == CV_CSN_OLD ) {
+ slap_graduate_commit_csn( op );
+ /* tell accesslog this was a failure */
+ rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS;
+ return LDAP_SUCCESS;
+ }
+ }
+
+ rc = overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on );
+ if ( rc == 0 ) {
+ Attribute *a;
+ const char *text;
+ a = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ value_match( &match, slap_schema.si_ad_entryCSN,
+ slap_schema.si_ad_entryCSN->ad_type->sat_ordering,
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ &mod->sml_nvalues[0], &a->a_nvals[0], &text );
+ } else {
+ /* no entryCSN? shouldn't happen. assume mod is newer. */
+ match = 1;
+ }
+ e_dup = entry_dup( e );
+ overlay_entry_release_ov( op, e, 0, on );
+ } else {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* equal? Should never happen */
+ if ( match == 0 ) {
+ slap_graduate_commit_csn( op );
+ /* tell accesslog this was a failure */
+ rs->sr_err = LDAP_TYPE_OR_VALUE_EXISTS;
+ entry_free( e_dup );
+ return LDAP_SUCCESS;
+ }
+
+ /* mod is older: resolve conflicts...
+ * 1. Save/copy original modlist. Split Replace to Del/Add.
+ * 2. Find all mods to this reqDN newer than the mod stamp.
+ * 3. Resolve any mods in this request that affect attributes
+ * touched by newer mods.
+ * old new
+ * delete all delete all drop
+ * delete all delete X SOFTDEL
+ * delete X delete all drop
+ * delete X delete X drop
+ * delete X delete Y OK
+ * delete all add X convert to delete current values,
+ * drop delete X from it
+ * delete X add X drop
+ * delete X add Y OK
+ * add X delete all drop
+ * add X delete X drop
+ * add X add X drop
+ * add X add Y if SV, drop else OK
+ *
+ * 4. Swap original modlist back in response callback so
+ * that accesslog logs the original mod.
+ *
+ * Even if the mod is newer, other out-of-order changes may
+ * have been committed, forcing us to tweak the modlist:
+ * 1. Save/copy original modlist.
+ * 2. Change deletes to soft deletes.
+ * 3. Change Adds of single-valued attrs to Replace.
+ */
+
+ newlist = mods_dup( op, op->orm_modlist, match );
+
+ /* mod is older */
+ if ( match < 0 ) {
+ Operation op2 = *op;
+ AttributeName an[2];
+ struct berval bv;
+ int size;
+ SlapReply rs1 = {0};
+ resolve_ctxt rx;
+ slap_callback cb = { NULL, syncrepl_resolve_cb, NULL, NULL };
+ Filter lf[3] = {0};
+ AttributeAssertion aa[2] = {0};
+
+ rx.rx_si = si;
+ rx.rx_entry = e_dup;
+ rx.rx_mods = newlist;
+ cb.sc_private = &rx;
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.ors_scope = LDAP_SCOPE_SUBTREE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.o_req_dn = si->si_logbase;
+ op2.o_req_ndn = si->si_logbase;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_limit = NULL;
+ memset( an, 0, sizeof(an));
+ an[0].an_desc = ad_reqMod;
+ an[0].an_name = ad_reqMod->ad_cname;
+ op2.ors_attrs = an;
+ op2.ors_attrsonly = 0;
+
+ bv = mod->sml_nvalues[0];
+
+ size = sizeof("(&(entryCSN>=)(reqDN=))");
+ size += bv.bv_len + op->o_req_ndn.bv_len + si->si_logfilterstr.bv_len;
+ op2.ors_filterstr.bv_val = op->o_tmpalloc( size, op->o_tmpmemctx );
+ op2.ors_filterstr.bv_len = sprintf(op2.ors_filterstr.bv_val,
+ "(&(entryCSN>=%s)(reqDN=%s)%s)",
+ bv.bv_val, op->o_req_ndn.bv_val, si->si_logfilterstr.bv_val );
+
+ lf[0].f_choice = LDAP_FILTER_AND;
+ lf[0].f_and = lf+1;
+ lf[1].f_choice = LDAP_FILTER_GE;
+ lf[1].f_ava = aa;
+ lf[1].f_av_desc = slap_schema.si_ad_entryCSN;
+ lf[1].f_av_value = bv;
+ lf[1].f_next = lf+2;
+ lf[2].f_choice = LDAP_FILTER_EQUALITY;
+ lf[2].f_ava = aa+1;
+ lf[2].f_av_desc = ad_reqDN;
+ lf[2].f_av_value = op->o_req_ndn;
+ lf[2].f_next = si->si_logfilter;
+
+ op2.ors_filter = lf;
+
+ op2.o_callback = &cb;
+ op2.o_bd = select_backend( &op2.o_req_ndn, 1 );
+ op2.o_bd->be_search( &op2, &rs1 );
+ newlist = rx.rx_mods;
+ }
+
+ {
+ slap_callback *sc = op->o_tmpalloc( sizeof(slap_callback) +
+ sizeof(modify_ctxt), op->o_tmpmemctx );
+ modify_ctxt *mx = (modify_ctxt *)(sc+1);
+ Modifications *ml;
+
+ sc->sc_response = syncrepl_modify_cb;
+ sc->sc_private = mx;
+ sc->sc_next = op->o_callback;
+ sc->sc_cleanup = NULL;
+ sc->sc_writewait = NULL;
+ op->o_callback = sc;
+ op->orm_no_opattrs = 1;
+ mx->mx_orig = op->orm_modlist;
+ mx->mx_free = newlist;
+ mx->mx_entry = e_dup;
+ for ( ml = newlist; ml; ml=ml->sml_next ) {
+ if ( ml->sml_flags == SLAP_MOD_INTERNAL ) {
+ ml->sml_flags = 0;
+ ml->sml_op = SLAP_MOD_SOFTDEL;
+ }
+ else if ( ml->sml_op == LDAP_MOD_DELETE )
+ ml->sml_op = SLAP_MOD_SOFTDEL;
+ else if ( ml->sml_op == LDAP_MOD_ADD &&
+ ml->sml_desc->ad_type->sat_atype.at_single_value )
+ ml->sml_op = LDAP_MOD_REPLACE;
+ }
+ op->orm_modlist = newlist;
+ op->o_csn = mod->sml_nvalues[0];
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncrepl_null_callback(
+ Operation *op,
+ SlapReply *rs )
+{
+ /* If we're not the last callback in the chain, move to the end */
+ if ( op->o_callback->sc_next ) {
+ slap_callback **sc, *s1;
+ s1 = op->o_callback;
+ op->o_callback = op->o_callback->sc_next;
+ for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) ;
+ *sc = s1;
+ s1->sc_next = NULL;
+ return SLAP_CB_CONTINUE;
+ }
+ if ( rs->sr_err != LDAP_SUCCESS &&
+ rs->sr_err != LDAP_REFERRAL &&
+ rs->sr_err != LDAP_ALREADY_EXISTS &&
+ rs->sr_err != LDAP_NO_SUCH_OBJECT &&
+ rs->sr_err != LDAP_NOT_ALLOWED_ON_NONLEAF )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_null_callback : error code 0x%x\n",
+ rs->sr_err );
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+syncrepl_message_to_op(
+ syncinfo_t *si,
+ Operation *op,
+ LDAPMessage *msg,
+ int do_lock
+)
+{
+ BerElement *ber = NULL;
+ Modifications *modlist = NULL;
+ logschema *ls;
+ SlapReply rs = { REP_RESULT };
+ slap_callback cb = { NULL, syncrepl_null_callback, NULL, NULL };
+
+ const char *text;
+ char txtbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof txtbuf;
+
+ struct berval bdn, dn = BER_BVNULL, ndn;
+ struct berval bv, bv2, *bvals = NULL;
+ struct berval rdn = BER_BVNULL, sup = BER_BVNULL,
+ prdn = BER_BVNULL, nrdn = BER_BVNULL,
+ psup = BER_BVNULL, nsup = BER_BVNULL;
+ struct berval dsee_uuid = BER_BVNULL, dsee_mods = BER_BVNULL;
+ int rc, deleteOldRdn = 0, freeReqDn = 0;
+ int do_graduate = 0, do_unlock = 0;
+ unsigned long changenum = 0;
+
+ if ( ldap_msgtype( msg ) != LDAP_RES_SEARCH_ENTRY ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s "
+ "Message type should be entry (%d)",
+ si->si_ridtxt, ldap_msgtype( msg ) );
+ return -1;
+ }
+
+ if ( si->si_syncdata == SYNCDATA_ACCESSLOG )
+ ls = &accesslog_sc;
+ else
+ ls = &changelog_sc;
+
+ rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bdn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_op: %s dn get failed (%d)",
+ si->si_ridtxt, rc );
+ return rc;
+ }
+
+ op->o_tag = LBER_DEFAULT;
+ op->o_bd = si->si_wbe;
+
+ if ( BER_BVISEMPTY( &bdn )) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_op: %s got empty dn",
+ si->si_ridtxt );
+ return LDAP_OTHER;
+ }
+
+ while (( rc = ldap_get_attribute_ber( si->si_ld, msg, ber, &bv, &bvals ) )
+ == LDAP_SUCCESS ) {
+ if ( bv.bv_val == NULL )
+ break;
+
+ if ( !ber_bvstrcasecmp( &bv, &ls->ls_dn ) ) {
+ bdn = bvals[0];
+ REWRITE_DN( si, bdn, bv2, dn, ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_op: %s "
+ "dn \"%s\" normalization failed (%d)",
+ si->si_ridtxt, bdn.bv_val, rc );
+ rc = -1;
+ ch_free( bvals );
+ goto done;
+ }
+ op->o_req_dn = dn;
+ op->o_req_ndn = ndn;
+ freeReqDn = 1;
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_req ) ) {
+ int i = verb_to_mask( bvals[0].bv_val, modops );
+ if ( i < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_op: %s unknown op %s",
+ si->si_ridtxt, bvals[0].bv_val );
+ ch_free( bvals );
+ rc = -1;
+ goto done;
+ }
+ op->o_tag = modops[i].mask;
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_mod ) ) {
+ /* Parse attribute into modlist */
+ if ( si->si_syncdata == SYNCDATA_ACCESSLOG ) {
+ rc = syncrepl_accesslog_mods( si, bvals, &modlist );
+ } else {
+ dsee_mods = bvals[0];
+ }
+ if ( rc ) goto done;
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_newRdn ) ) {
+ rdn = bvals[0];
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_delRdn ) ) {
+ if ( !ber_bvstrcasecmp( &slap_true_bv, bvals ) ) {
+ deleteOldRdn = 1;
+ }
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_newSup ) ) {
+ sup = bvals[0];
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_controls ) ) {
+ int i;
+ struct berval rel_ctrl_bv;
+
+ (void)ber_str2bv( "{" LDAP_CONTROL_RELAX, 0, 0, &rel_ctrl_bv );
+ for ( i = 0; bvals[i].bv_val; i++ ) {
+ struct berval cbv, tmp;
+
+ ber_bvchr_post( &cbv, &bvals[i], '}' );
+ ber_bvchr_post( &tmp, &cbv, '{' );
+ ber_bvchr_pre( &cbv, &tmp, ' ' );
+ if ( cbv.bv_len == tmp.bv_len ) /* control w/o value */
+ ber_bvchr_pre( &cbv, &tmp, '}' );
+ if ( !ber_bvcmp( &cbv, &rel_ctrl_bv ) )
+ op->o_relax = SLAP_CONTROL_CRITICAL;
+ }
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_uuid ) ) {
+ dsee_uuid = bvals[0];
+ } else if ( !ber_bvstrcasecmp( &bv, &ls->ls_changenum ) ) {
+ changenum = strtoul( bvals->bv_val, NULL, 0 );
+ } else if ( !ber_bvstrcasecmp( &bv,
+ &slap_schema.si_ad_entryCSN->ad_cname ) )
+ {
+ int i, sid = slap_parse_csn_sid( bvals );
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ i = check_csn_age( si, &bdn, bvals, sid,
+ (cookie_vals *)&si->si_cookieState->cs_vals, NULL );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ if ( i == CV_CSN_OLD ) {
+ goto done;
+ }
+ slap_queue_csn( op, bvals );
+ do_graduate = 1;
+ }
+ ch_free( bvals );
+ }
+
+ /* don't parse mods until we've gotten the uuid */
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG && !BER_BVISNULL( &dsee_mods )) {
+ rc = syncrepl_changelog_mods( si, op->o_tag,
+ &dsee_mods, &modlist, &dsee_uuid, op->o_tmpmemctx );
+ if ( rc )
+ goto done;
+ }
+
+ /* If we didn't get a mod type or a target DN, bail out */
+ if ( op->o_tag == LBER_DEFAULT || BER_BVISNULL( &dn ) ) {
+ rc = -1;
+ goto done;
+ }
+
+ if ( do_lock ) {
+ if (( rc = get_pmutex( si )))
+ goto done;
+ do_unlock = 1;
+ }
+
+ op->o_callback = &cb;
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ Debug( LDAP_DEBUG_SYNC, "syncrepl_message_to_op: %s tid %p\n",
+ si->si_ridtxt, (void *)op->o_tid );
+
+ switch( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ /* If we didn't get required data, bail */
+ if ( !modlist ) goto done;
+
+ rc = slap_mods_check( op, modlist, &text, txtbuf, textlen, NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s "
+ "mods check (%s)\n",
+ si->si_ridtxt, text );
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_ADD ) {
+ Entry *e = entry_alloc();
+ op->ora_e = e;
+ ber_dupbv( &op->ora_e->e_name, &op->o_req_dn );
+ ber_dupbv( &op->ora_e->e_nname, &op->o_req_ndn );
+ rc = slap_mods2entry( modlist, &op->ora_e, 1, 0, &text, txtbuf, textlen);
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_op: %s "
+ "mods2entry (%s)\n",
+ si->si_ridtxt, text );
+ } else {
+ rc = op->o_bd->be_add( op, &rs );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_message_to_op: %s be_add %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ do_graduate = 0;
+ if ( rc == LDAP_ALREADY_EXISTS ) {
+ Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryCSN );
+ struct berval *vals;
+ if ( a && backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entryCSN, &vals, ACL_READ ) == LDAP_SUCCESS ) {
+ if ( ber_bvcmp( &vals[0], &a->a_vals[0] ) >= 0 )
+ rc = LDAP_SUCCESS;
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+ }
+ if ( e == op->ora_e )
+ be_entry_release_w( op, op->ora_e );
+ } else {
+ OpExtraSync oes;
+ op->orm_modlist = modlist;
+ op->o_bd = si->si_wbe;
+ /* delta-mpr needs additional checks in syncrepl_op_modify */
+ if ( SLAP_MULTIPROVIDER( op->o_bd )) {
+ oes.oe.oe_key = (void *)syncrepl_message_to_op;
+ oes.oe_si = si;
+ LDAP_SLIST_INSERT_HEAD( &op->o_extra, &oes.oe, oe_next );
+ }
+ rc = op->o_bd->be_modify( op, &rs );
+ if ( SLAP_MULTIPROVIDER( op->o_bd )) {
+ LDAP_SLIST_REMOVE( &op->o_extra, &oes.oe, OpExtra, oe_next );
+ BER_BVZERO( &op->o_csn );
+ }
+ modlist = op->orm_modlist;
+ Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC,
+ "syncrepl_message_to_op: %s be_modify %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ op->o_bd = si->si_be;
+ do_graduate = 0;
+ }
+ break;
+ case LDAP_REQ_MODRDN:
+ if ( BER_BVISNULL( &rdn ) ) goto done;
+
+ if ( rdnPretty( NULL, &rdn, &prdn, NULL ) ) {
+ goto done;
+ }
+ if ( rdnNormalize( 0, NULL, NULL, &rdn, &nrdn, NULL ) ) {
+ goto done;
+ }
+ if ( !BER_BVISNULL( &sup ) ) {
+ REWRITE_DN( si, sup, bv2, psup, nsup );
+ if ( rc )
+ goto done;
+ op->orr_newSup = &psup;
+ op->orr_nnewSup = &nsup;
+ } else {
+ op->orr_newSup = NULL;
+ op->orr_nnewSup = NULL;
+ dnParent( &op->o_req_dn, &psup );
+ dnParent( &op->o_req_ndn, &nsup );
+ }
+ op->orr_newrdn = prdn;
+ op->orr_nnewrdn = nrdn;
+ build_new_dn( &op->orr_newDN, &psup, &op->orr_newrdn, op->o_tmpmemctx );
+ build_new_dn( &op->orr_nnewDN, &nsup, &op->orr_nnewrdn, op->o_tmpmemctx );
+ if ( BER_BVISNULL( &sup ) ) {
+ BER_BVZERO( &psup );
+ BER_BVZERO( &nsup );
+ }
+
+ op->orr_deleteoldrdn = deleteOldRdn;
+ op->orr_modlist = NULL;
+ if ( slap_modrdn2mods( op, &rs ) ) {
+ goto done;
+ }
+
+ /* Append modlist for operational attrs */
+ {
+ Modifications *m;
+
+ for ( m = op->orr_modlist; m->sml_next; m = m->sml_next )
+ ;
+ m->sml_next = modlist;
+ modlist = NULL;
+ }
+ rc = op->o_bd->be_modrdn( op, &rs );
+ slap_mods_free( op->orr_modlist, 1 );
+ Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC,
+ "syncrepl_message_to_op: %s be_modrdn %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ do_graduate = 0;
+ break;
+ case LDAP_REQ_DELETE:
+ rc = op->o_bd->be_delete( op, &rs );
+ Debug( rc ? LDAP_DEBUG_ANY : LDAP_DEBUG_SYNC,
+ "syncrepl_message_to_op: %s be_delete %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ /* silently ignore this */
+ if ( rc == LDAP_NO_SUCH_OBJECT )
+ rc = LDAP_SUCCESS;
+ do_graduate = 0;
+ break;
+ }
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG && !rc )
+ si->si_lastchange = changenum;
+
+done:
+ if ( do_graduate )
+ slap_graduate_commit_csn( op );
+ if ( do_unlock )
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_pmutex );
+ op->o_bd = si->si_be;
+ op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_csn );
+ if ( modlist ) {
+ slap_mods_free( modlist, op->o_tag != LDAP_REQ_ADD );
+ }
+ if ( !BER_BVISNULL( &rdn ) ) {
+ if ( !BER_BVISNULL( &nsup ) ) {
+ ch_free( nsup.bv_val );
+ }
+ if ( !BER_BVISNULL( &psup ) ) {
+ ch_free( psup.bv_val );
+ }
+ if ( !BER_BVISNULL( &nrdn ) ) {
+ ch_free( nrdn.bv_val );
+ }
+ if ( !BER_BVISNULL( &prdn ) ) {
+ ch_free( prdn.bv_val );
+ }
+ }
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ op->o_tmpfree( op->orr_newDN.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewDN.bv_val, op->o_tmpmemctx );
+ }
+ if ( freeReqDn ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ ber_free( ber, 0 );
+ return rc;
+}
+
+static int
+syncrepl_message_to_entry(
+ syncinfo_t *si,
+ Operation *op,
+ LDAPMessage *msg,
+ Modifications **modlist,
+ Entry **entry,
+ int syncstate,
+ struct berval *syncUUID
+)
+{
+ Entry *e = NULL;
+ BerElement *ber = NULL;
+ Modifications tmp;
+ Modifications *mod;
+ Modifications **modtail = modlist;
+
+ const char *text;
+ char txtbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof txtbuf;
+
+ struct berval bdn = BER_BVNULL, dn, ndn, bv2;
+ int rc, is_ctx;
+
+ *modlist = NULL;
+
+ if ( ldap_msgtype( msg ) != LDAP_RES_SEARCH_ENTRY ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s "
+ "Message type should be entry (%d)",
+ si->si_ridtxt, ldap_msgtype( msg ) );
+ return -1;
+ }
+
+ op->o_tag = LDAP_REQ_ADD;
+
+ rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bdn );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_entry: %s dn get failed (%d)",
+ si->si_ridtxt, rc );
+ return rc;
+ }
+
+ if ( BER_BVISEMPTY( &bdn ) && !BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_entry: %s got empty dn",
+ si->si_ridtxt );
+ return LDAP_OTHER;
+ }
+
+ if ( si->si_syncdata != SYNCDATA_CHANGELOG ) {
+ /* syncUUID[0] is normalized UUID received over the wire
+ * syncUUID[1] is denormalized UUID, generated here
+ */
+ (void)slap_uuidstr_from_normalized( &syncUUID[1], &syncUUID[0], op->o_tmpmemctx );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_message_to_entry: %s DN: %s, UUID: %s\n",
+ si->si_ridtxt, bdn.bv_val, syncUUID[1].bv_val );
+ }
+
+ if ( syncstate == LDAP_SYNC_PRESENT || syncstate == LDAP_SYNC_DELETE ) {
+ /* NOTE: this could be done even before decoding the DN,
+ * although encoding errors wouldn't be detected */
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ if ( entry == NULL ) {
+ return -1;
+ }
+
+ REWRITE_DN( si, bdn, bv2, dn, ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ /* One of the things that could happen is that the schema
+ * is not lined-up; this could result in unknown attributes.
+ * A value non conformant to the syntax should be unlikely,
+ * except when replicating between different versions
+ * of the software, or when syntax validation bugs are fixed
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_message_to_entry: "
+ "%s dn \"%s\" normalization failed (%d)",
+ si->si_ridtxt, bdn.bv_val, rc );
+ return rc;
+ }
+
+ ber_dupbv( &op->o_req_dn, &dn );
+ ber_dupbv( &op->o_req_ndn, &ndn );
+ slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
+ slap_sl_free( dn.bv_val, op->o_tmpmemctx );
+
+ is_ctx = dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[0] );
+
+ e = entry_alloc();
+ e->e_name = op->o_req_dn;
+ e->e_nname = op->o_req_ndn;
+
+ while ( ber_remaining( ber ) ) {
+ if ( (ber_scanf( ber, "{mW}", &tmp.sml_type, &tmp.sml_values ) ==
+ LBER_ERROR ) || BER_BVISNULL( &tmp.sml_type ) )
+ {
+ break;
+ }
+
+ /* Drop all updates to the contextCSN of the context entry
+ * (ITS#4622, etc.)
+ */
+ if ( is_ctx && !strcasecmp( tmp.sml_type.bv_val,
+ slap_schema.si_ad_contextCSN->ad_cname.bv_val )) {
+ ber_bvarray_free( tmp.sml_values );
+ continue;
+ }
+
+ /* map nsUniqueId to entryUUID, drop nsUniqueId */
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG &&
+ !strcasecmp( tmp.sml_type.bv_val, sy_ad_nsUniqueId->ad_cname.bv_val )) {
+ rc = syncrepl_dsee_uuid( &tmp.sml_values[0], syncUUID, op->o_tmpmemctx );
+ ber_bvarray_free( tmp.sml_values );
+ if ( rc )
+ goto done;
+ continue;
+ }
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = 0;
+ mod->sml_next = NULL;
+ mod->sml_desc = NULL;
+ mod->sml_type = tmp.sml_type;
+ mod->sml_values = tmp.sml_values;
+ mod->sml_nvalues = NULL;
+ mod->sml_numvals = 0; /* slap_mods_check will set this */
+
+ if (si->si_rewrite) {
+ AttributeDescription *ad = NULL;
+ slap_bv2ad( &tmp.sml_type, &ad, &text );
+ if ( ad ) {
+ mod->sml_desc = ad;
+ mod->sml_type = ad->ad_cname;
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ int i;
+ for ( i = 0; tmp.sml_values[i].bv_val; i++ ) {
+ syncrepl_rewrite_dn( si, &tmp.sml_values[i], &bv2);
+ if ( !BER_BVISNULL( &bv2 )) {
+ ber_memfree( tmp.sml_values[i].bv_val );
+ tmp.sml_values[i] = bv2;
+ }
+ }
+ }
+ }
+ }
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( *modlist == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s no attributes\n",
+ si->si_ridtxt );
+ rc = -1;
+ goto done;
+ }
+
+ rc = slap_mods_check( op, *modlist, &text, txtbuf, textlen, NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s mods check (%s)\n",
+ si->si_ridtxt, text );
+ goto done;
+ }
+
+ /* Strip out dynamically generated attrs */
+ for ( modtail = modlist; *modtail ; ) {
+ mod = *modtail;
+ if ( mod->sml_desc->ad_type->sat_flags & SLAP_AT_DYNAMIC ) {
+ *modtail = mod->sml_next;
+ slap_mod_free( &mod->sml_mod, 0 );
+ ch_free( mod );
+ } else {
+ modtail = &mod->sml_next;
+ }
+ }
+
+ /* Strip out attrs in exattrs list */
+ for ( modtail = modlist; *modtail ; ) {
+ mod = *modtail;
+ if ( ldap_charray_inlist( si->si_exattrs,
+ mod->sml_desc->ad_type->sat_cname.bv_val ) )
+ {
+ *modtail = mod->sml_next;
+ slap_mod_free( &mod->sml_mod, 0 );
+ ch_free( mod );
+ } else {
+ modtail = &mod->sml_next;
+ }
+ }
+
+ rc = slap_mods2entry( *modlist, &e, 1, 1, &text, txtbuf, textlen);
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_message_to_entry: %s mods2entry (%s)\n",
+ si->si_ridtxt, text );
+ }
+
+done:
+ ber_free( ber, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( e ) {
+ entry_free( e );
+ e = NULL;
+ }
+ }
+ if ( entry )
+ *entry = e;
+
+ return rc;
+}
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+static int
+syncrepl_dirsync_message(
+ syncinfo_t *si,
+ Operation *op,
+ LDAPMessage *msg,
+ Modifications **modlist,
+ Entry **entry,
+ int *syncstate,
+ struct berval *syncUUID
+)
+{
+ Entry *e = NULL;
+ BerElement *ber = NULL;
+ Modifications tmp;
+ Modifications *mod, *rangeMod = NULL;
+ Modifications **modtail = modlist;
+
+ const char *text;
+ char txtbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof txtbuf;
+
+ struct berval bdn = BER_BVNULL, dn, ndn, bv2;
+ int rc;
+
+ *modlist = NULL;
+ *syncstate = MSAD_DIRSYNC_MODIFY;
+
+ if ( ldap_msgtype( msg ) != LDAP_RES_SEARCH_ENTRY ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_dirsync_message: %s "
+ "Message type should be entry (%d)\n",
+ si->si_ridtxt, ldap_msgtype( msg ) );
+ return -1;
+ }
+
+ rc = ldap_get_dn_ber( si->si_ld, msg, &ber, &bdn );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_dirsync_message: %s dn get failed (%d)\n",
+ si->si_ridtxt, rc );
+ return rc;
+ }
+
+ if ( BER_BVISEMPTY( &bdn ) && !BER_BVISEMPTY( &op->o_bd->be_nsuffix[0] ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_dirsync_message: %s got empty dn\n",
+ si->si_ridtxt );
+ return LDAP_OTHER;
+ }
+
+ while ( ber_remaining( ber ) ) {
+ AttributeDescription *ad = NULL;
+
+ if ( (ber_scanf( ber, "{mW}", &tmp.sml_type, &tmp.sml_values ) ==
+ LBER_ERROR ) || BER_BVISNULL( &tmp.sml_type ) )
+ {
+ break;
+ }
+ if ( tmp.sml_values == NULL )
+ continue;
+
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = 0;
+ mod->sml_next = NULL;
+ mod->sml_desc = NULL;
+ mod->sml_type = tmp.sml_type;
+ mod->sml_values = tmp.sml_values;
+ mod->sml_nvalues = NULL;
+ mod->sml_numvals = 0; /* slap_mods_check will set this */
+
+ rc = slap_bv2ad( &tmp.sml_type, &ad, &text );
+ if ( !ad ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_dirsync_message: %s unknown attributeType %s\n",
+ si->si_ridtxt, tmp.sml_type.bv_val );
+ return rc;
+ }
+ mod->sml_desc = ad;
+ mod->sml_type = ad->ad_cname;
+ if (( ad->ad_flags & SLAP_DESC_TAG_RANGE ) && rangeMod == NULL)
+ rangeMod = mod;
+ if (si->si_rewrite) {
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ int i;
+ for ( i = 0; tmp.sml_values[i].bv_val; i++ ) {
+ syncrepl_rewrite_dn( si, &tmp.sml_values[i], &bv2);
+ if ( !BER_BVISNULL( &bv2 )) {
+ ber_memfree( tmp.sml_values[i].bv_val );
+ tmp.sml_values[i] = bv2;
+ }
+ }
+ }
+ }
+ if ( mod->sml_desc == sy_ad_objectGUID ) {
+ ber_dupbv_x( &syncUUID[0], &tmp.sml_values[0], op->o_tmpmemctx );
+ /* syncUUID[0] is normalized UUID received over the wire
+ * syncUUID[1] is denormalized UUID, generated here
+ */
+ (void)slap_uuidstr_from_normalized( &syncUUID[1], &syncUUID[0], op->o_tmpmemctx );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_dirsync_message: %s DN: %s, UUID: %s\n",
+ si->si_ridtxt, bdn.bv_val, syncUUID[1].bv_val );
+ } else if ( mod->sml_desc == sy_ad_isDeleted ) {
+ *syncstate = LDAP_SYNC_DELETE;
+ } else if ( mod->sml_desc == sy_ad_whenCreated ) {
+ *syncstate = LDAP_SYNC_ADD;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ mod = (Modifications *) ch_malloc( sizeof( Modifications ) );
+
+ mod->sml_op = LDAP_MOD_REPLACE;
+ mod->sml_flags = 0;
+ mod->sml_next = NULL;
+ mod->sml_desc = slap_schema.si_ad_createTimestamp;
+ mod->sml_type = mod->sml_desc->ad_cname;
+ ber_bvarray_dup_x( &mod->sml_values, tmp.sml_values, NULL );
+ mod->sml_nvalues = NULL;
+ mod->sml_numvals = 0; /* slap_mods_check will set this */
+ } /* else is a modify or modrdn */
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+
+ if ( *modlist == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_dirsync_message: %s no attributes\n",
+ si->si_ridtxt );
+ rc = -1;
+ goto done;
+ }
+
+ if ( *syncstate == LDAP_SYNC_DELETE ) {
+ e = NULL;
+ slap_mods_free( *modlist, 1 );
+ *modlist = NULL;
+ } else {
+ /* check for incremental multival mods */
+ if ( *syncstate == MSAD_DIRSYNC_MODIFY && rangeMod != NULL ) {
+ for (; rangeMod; rangeMod = rangeMod->sml_next) {
+ if ( rangeMod->sml_desc->ad_flags & SLAP_DESC_TAG_RANGE ) {
+ if ( bvmatch( &rangeMod->sml_desc->ad_tags, &msad_addval ))
+ rangeMod->sml_op = SLAP_MOD_SOFTADD;
+ else if ( bvmatch( &rangeMod->sml_desc->ad_tags, &msad_delval ))
+ rangeMod->sml_op = SLAP_MOD_SOFTDEL;
+ /* turn the tagged attr into a normal one */
+ if ( rangeMod->sml_op != LDAP_MOD_REPLACE ) {
+ AttributeDescription *ad = NULL;
+ slap_bv2ad( &rangeMod->sml_desc->ad_type->sat_cname, &ad, &text );
+ rangeMod->sml_desc = ad;
+ }
+ }
+ }
+ }
+ rc = slap_mods_check( op, *modlist, &text, txtbuf, textlen, NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_dirsync_message: %s mods check (%s)\n",
+ si->si_ridtxt, text );
+ goto done;
+ }
+
+ REWRITE_DN( si, bdn, bv2, dn, ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ /* One of the things that could happen is that the schema
+ * is not lined-up; this could result in unknown attributes.
+ * A value non conformant to the syntax should be unlikely,
+ * except when replicating between different versions
+ * of the software, or when syntax validation bugs are fixed
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_dirsync_message: "
+ "%s dn \"%s\" normalization failed (%d)",
+ si->si_ridtxt, bdn.bv_val, rc );
+ return rc;
+ }
+
+ ber_dupbv( &op->o_req_dn, &dn );
+ ber_dupbv( &op->o_req_ndn, &ndn );
+ slap_sl_free( ndn.bv_val, op->o_tmpmemctx );
+ slap_sl_free( dn.bv_val, op->o_tmpmemctx );
+
+ e = entry_alloc();
+ e->e_name = op->o_req_dn;
+ e->e_nname = op->o_req_ndn;
+
+ /* Strip out redundant attrs */
+ if ( *syncstate == MSAD_DIRSYNC_MODIFY ) {
+ for ( modtail = modlist; *modtail ; ) {
+ mod = *modtail;
+ if ( mod->sml_desc == sy_ad_objectGUID ||
+ mod->sml_desc == sy_ad_instanceType ) {
+ *modtail = mod->sml_next;
+ slap_mod_free( &mod->sml_mod, 0 );
+ ch_free( mod );
+ } else {
+ modtail = &mod->sml_next;
+ }
+ }
+ }
+
+ /* Strip out dynamically generated attrs */
+ for ( modtail = modlist; *modtail ; ) {
+ mod = *modtail;
+ if ( mod->sml_desc->ad_type->sat_flags & SLAP_AT_DYNAMIC ) {
+ *modtail = mod->sml_next;
+ slap_mod_free( &mod->sml_mod, 0 );
+ ch_free( mod );
+ } else {
+ modtail = &mod->sml_next;
+ }
+ }
+
+ /* Strip out attrs in exattrs list */
+ for ( modtail = modlist; *modtail ; ) {
+ mod = *modtail;
+ if ( ldap_charray_inlist( si->si_exattrs,
+ mod->sml_desc->ad_type->sat_cname.bv_val ) )
+ {
+ *modtail = mod->sml_next;
+ slap_mod_free( &mod->sml_mod, 0 );
+ ch_free( mod );
+ } else {
+ modtail = &mod->sml_next;
+ }
+ }
+
+ rc = slap_mods2entry( *modlist, &e, 1, 1, &text, txtbuf, textlen);
+ if( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_dirsync_message: %s mods2entry (%s)\n",
+ si->si_ridtxt, text );
+ }
+ }
+
+done:
+ ber_free( ber, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( e ) {
+ entry_free( e );
+ e = NULL;
+ }
+ }
+ if ( entry )
+ *entry = e;
+
+ return rc;
+}
+
+static int
+syncrepl_dirsync_cookie(
+ syncinfo_t *si,
+ Operation *op,
+ LDAPControl **ctrls
+)
+{
+ LDAPControl *ctrl, **next;
+ Backend *be = op->o_bd;
+ Modifications mod;
+ struct berval vals[2];
+
+ int rc, continueFlag;
+
+ slap_callback cb = { NULL };
+ SlapReply rs_modify = {REP_RESULT};
+
+ ctrl = ldap_control_find( LDAP_CONTROL_X_DIRSYNC, ctrls, &next );
+ if ( ctrl == NULL ) {
+ ldap_controls_free( ctrls );
+ return -1;
+ }
+ rc = ldap_parse_dirsync_control( si->si_ld, ctrl, &continueFlag, &vals[0] );
+ if ( !bvmatch( &vals[0], &si->si_dirSyncCookie )) {
+
+ BER_BVZERO( &vals[1] );
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_desc = sy_ad_dirSyncCookie;
+ mod.sml_type = mod.sml_desc->ad_cname;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_nvalues = NULL;
+ mod.sml_next = NULL;
+
+ op->o_bd = si->si_wbe;
+ op->o_tag = LDAP_REQ_MODIFY;
+
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = si;
+
+ op->o_callback = &cb;
+ op->o_req_dn = si->si_contextdn;
+ op->o_req_ndn = si->si_contextdn;
+
+ op->o_dont_replicate = 0;
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ mod.sml_numvals = 1;
+ mod.sml_values = vals;
+
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ rc = op->o_bd->be_modify( op, &rs_modify );
+ op->orm_no_opattrs = 0;
+
+ op->o_bd = be;
+ if ( mod.sml_next ) slap_mods_free( mod.sml_next, 1 );
+
+ if ( rc == LDAP_SUCCESS ) {
+ ber_bvreplace( &si->si_dirSyncCookie, &vals[0] );
+ /* there are more changes still remaining */
+ if ( continueFlag )
+ rc = LDAP_SYNC_REFRESH_REQUIRED;
+ }
+ }
+
+ ch_free( vals[0].bv_val );
+ ldap_controls_free( ctrls );
+ return rc;
+}
+
+static int syncrepl_dirsync_schema()
+{
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( "objectGUID", &sy_ad_objectGUID, &text );
+ if ( rc )
+ return rc;
+ rc = slap_str2ad( "instanceType", &sy_ad_instanceType, &text );
+ if ( rc )
+ return rc;
+ rc = slap_str2ad( "isDeleted", &sy_ad_isDeleted, &text );
+ if ( rc )
+ return rc;
+ rc = slap_str2ad( "whenCreated", &sy_ad_whenCreated, &text );
+ if ( rc )
+ return rc;
+ return register_at( "( 1.3.6.1.4.1.4203.666.1.27 " /* OpenLDAP-specific */
+ "NAME 'dirSyncCookie' "
+ "DESC 'DirSync Cookie for shadow copy' "
+ "EQUALITY octetStringMatch "
+ "ORDERING octetStringOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", &sy_ad_dirSyncCookie, 0);
+}
+#endif /* LDAP_CONTROL_X_DIRSYNC */
+
+static int syncrepl_dsee_schema()
+{
+ const char *text;
+ int rc;
+
+ rc = slap_str2ad( "nsUniqueId", &sy_ad_nsUniqueId, &text );
+ if ( rc )
+ return rc;
+ return register_at( "( 1.3.6.1.4.1.4203.666.1.28 " /* OpenLDAP-specific */
+ "NAME 'lastChangeNumber' "
+ "DESC 'RetroChangelog latest change record' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", &sy_ad_dseeLastChange, 0);
+}
+
+/* During a refresh, we may get an LDAP_SYNC_ADD for an already existing
+ * entry if a previous refresh was interrupted before sending us a new
+ * context state. We try to compare the new entry to the existing entry
+ * and ignore the new entry if they are the same.
+ *
+ * Also, we may get an update where the entryDN has changed, due to
+ * a ModDn on the provider. We detect this as well, so we can issue
+ * the corresponding operation locally.
+ *
+ * In the case of a modify, we get a list of all the attributes
+ * in the original entry. Rather than deleting the entry and re-adding it,
+ * we issue a Modify request that deletes all the attributes and adds all
+ * the new ones. This avoids the issue of trying to delete/add a non-leaf
+ * entry.
+ *
+ * We otherwise distinguish ModDN from Modify; in the case of
+ * a ModDN we just use the CSN, modifyTimestamp and modifiersName
+ * operational attributes from the entry, and do a regular ModDN.
+ */
+typedef struct dninfo {
+ syncinfo_t *si;
+ Entry *new_entry;
+ struct berval dn;
+ struct berval ndn;
+ struct berval nnewSup;
+ int syncstate;
+ int renamed; /* Was an existing entry renamed? */
+ int delOldRDN; /* Was old RDN deleted? */
+ Modifications **modlist; /* the modlist we received */
+ Modifications *mods; /* the modlist we compared */
+ int oldNcount; /* #values of old naming attr */
+ AttributeDescription *oldDesc; /* for renames */
+ AttributeDescription *newDesc; /* for renames */
+} dninfo;
+
+#define HASHUUID 1
+
+/* return 1 if inserted, 0 otherwise */
+static int
+presentlist_insert(
+ syncinfo_t* si,
+ struct berval *syncUUID )
+{
+ char *val;
+
+#ifdef HASHUUID
+ Avlnode **av;
+ unsigned short s;
+
+ if ( !si->si_presentlist )
+ si->si_presentlist = ch_calloc(65536, sizeof( Avlnode * ));
+
+ av = (Avlnode **)si->si_presentlist;
+
+ val = ch_malloc(UUIDLEN-2);
+ memcpy(&s, syncUUID->bv_val, 2);
+ memcpy(val, syncUUID->bv_val+2, UUIDLEN-2);
+
+ if ( ldap_avl_insert( &av[s], val,
+ syncuuid_cmp, ldap_avl_dup_error ) )
+ {
+ ch_free( val );
+ return 0;
+ }
+#else
+ val = ch_malloc(UUIDLEN);
+
+ AC_MEMCPY( val, syncUUID->bv_val, UUIDLEN );
+
+ if ( ldap_avl_insert( &si->si_presentlist, val,
+ syncuuid_cmp, ldap_avl_dup_error ) )
+ {
+ ch_free( val );
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+static char *
+presentlist_find(
+ Avlnode *av,
+ struct berval *val )
+{
+#ifdef HASHUUID
+ Avlnode **a2 = (Avlnode **)av;
+ unsigned short s;
+
+ if (!av)
+ return NULL;
+
+ memcpy(&s, val->bv_val, 2);
+ return ldap_avl_find( a2[s], val->bv_val+2, syncuuid_cmp );
+#else
+ return ldap_avl_find( av, val->bv_val, syncuuid_cmp );
+#endif
+}
+
+static int
+presentlist_free( Avlnode *av )
+{
+#ifdef HASHUUID
+ Avlnode **a2 = (Avlnode **)av;
+ int i, count = 0;
+
+ if ( av ) {
+ for (i=0; i<65536; i++) {
+ if (a2[i])
+ count += ldap_avl_free( a2[i], ch_free );
+ }
+ ch_free( av );
+ }
+ return count;
+#else
+ return ldap_avl_free( av, ch_free );
+#endif
+}
+
+static void
+presentlist_delete(
+ Avlnode **av,
+ struct berval *val )
+{
+#ifdef HASHUUID
+ Avlnode **a2 = *(Avlnode ***)av;
+ unsigned short s;
+
+ memcpy(&s, val->bv_val, 2);
+ ldap_avl_delete( &a2[s], val->bv_val+2, syncuuid_cmp );
+#else
+ ldap_avl_delete( av, val->bv_val, syncuuid_cmp );
+#endif
+}
+
+static int
+syncrepl_entry(
+ syncinfo_t* si,
+ Operation *op,
+ Entry* entry,
+ Modifications** modlist,
+ int syncstate,
+ struct berval* syncUUID,
+ struct berval* syncCSN )
+{
+ Backend *be = op->o_bd;
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ int syncuuid_inserted = 0;
+
+ SlapReply rs_search = {REP_RESULT};
+ Filter f = {0};
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ int rc = LDAP_SUCCESS;
+
+ struct berval pdn = BER_BVNULL;
+ dninfo dni = {0};
+ int retry = 1;
+ int freecsn = 1;
+
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s LDAP_RES_SEARCH_ENTRY(LDAP_SYNC_%s) csn=%s tid %p\n",
+ si->si_ridtxt, syncrepl_state2str( syncstate ), syncCSN ? syncCSN->bv_val : "(none)", (void *)op->o_tid );
+
+ if (( syncstate == LDAP_SYNC_PRESENT || syncstate == LDAP_SYNC_ADD ) ) {
+ if ( !si->si_refreshPresent && !si->si_refreshDone ) {
+ syncuuid_inserted = presentlist_insert( si, syncUUID );
+ }
+ }
+
+ if ( syncstate == LDAP_SYNC_PRESENT ) {
+ return 0;
+ } else if ( syncstate != LDAP_SYNC_DELETE ) {
+ if ( entry == NULL ) {
+ return 0;
+ }
+ }
+
+ if ( syncstate != LDAP_SYNC_DELETE ) {
+ Attribute *a = attr_find( entry->e_attrs, slap_schema.si_ad_entryUUID );
+
+ if ( a == NULL ) {
+ /* add if missing */
+ attr_merge_one( entry, slap_schema.si_ad_entryUUID,
+ &syncUUID[1], syncUUID );
+
+ } else if ( !bvmatch( &a->a_nvals[0], syncUUID ) ) {
+ /* replace only if necessary */
+ if ( a->a_nvals != a->a_vals ) {
+ ber_memfree( a->a_nvals[0].bv_val );
+ ber_dupbv( &a->a_nvals[0], syncUUID );
+ }
+ ber_memfree( a->a_vals[0].bv_val );
+ ber_dupbv( &a->a_vals[0], &syncUUID[1] );
+ }
+ }
+
+ f.f_choice = LDAP_FILTER_EQUALITY;
+ f.f_ava = &ava;
+ ava.aa_desc = slap_schema.si_ad_entryUUID;
+ ava.aa_value = *syncUUID;
+
+ if ( syncuuid_inserted ) {
+ Debug( LDAP_DEBUG_SYNC, "syncrepl_entry: %s inserted UUID %s\n",
+ si->si_ridtxt, syncUUID[1].bv_val );
+ }
+ op->ors_filter = &f;
+
+ op->ors_filterstr.bv_len = STRLENOF( "(entryUUID=)" ) + syncUUID[1].bv_len;
+ op->ors_filterstr.bv_val = (char *) slap_sl_malloc(
+ op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+ AC_MEMCPY( op->ors_filterstr.bv_val, "(entryUUID=", STRLENOF( "(entryUUID=" ) );
+ AC_MEMCPY( &op->ors_filterstr.bv_val[STRLENOF( "(entryUUID=" )],
+ syncUUID[1].bv_val, syncUUID[1].bv_len );
+ op->ors_filterstr.bv_val[op->ors_filterstr.bv_len - 1] = ')';
+ op->ors_filterstr.bv_val[op->ors_filterstr.bv_len] = '\0';
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+
+ /* get the entry for this UUID */
+ if ( si->si_rewrite ) {
+ op->o_req_dn = si->si_suffixm;
+ op->o_req_ndn = si->si_suffixm;
+ } else
+ {
+ op->o_req_dn = si->si_base;
+ op->o_req_ndn = si->si_base;
+ }
+
+ op->o_time = slap_get_time();
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = 1;
+ op->ors_limit = NULL;
+
+ op->ors_attrs = slap_anlist_all_attributes;
+ op->ors_attrsonly = 0;
+
+ /* set callback function */
+ op->o_callback = &cb;
+ cb.sc_response = dn_callback;
+ cb.sc_private = &dni;
+ dni.si = si;
+ dni.new_entry = entry;
+ dni.modlist = modlist;
+ dni.syncstate = syncstate;
+
+ rc = be->be_search( op, &rs_search );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s be_search (%d)\n",
+ si->si_ridtxt, rc );
+
+ if ( !BER_BVISNULL( &op->ors_filterstr ) ) {
+ slap_sl_free( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = si;
+
+ if ( entry && !BER_BVISNULL( &entry->e_name ) ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s %s\n",
+ si->si_ridtxt, entry->e_name.bv_val );
+ } else {
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s %s\n",
+ si->si_ridtxt, dni.dn.bv_val ? dni.dn.bv_val : "(null)" );
+ }
+
+ assert( BER_BVISNULL( &op->o_csn ) );
+ if ( syncCSN ) {
+ slap_queue_csn( op, syncCSN );
+ }
+
+#ifdef SLAP_CONTROL_X_LAZY_COMMIT
+ if ( !si->si_refreshDone && si->si_lazyCommit )
+ op->o_lazyCommit = SLAP_CONTROL_NONCRITICAL;
+#endif
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+ switch ( syncstate ) {
+ case LDAP_SYNC_ADD:
+ case LDAP_SYNC_MODIFY:
+ case DSEE_SYNC_ADD:
+ if ( BER_BVISNULL( &op->o_csn ))
+ {
+
+ Attribute *a = attr_find( entry->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ /* FIXME: op->o_csn is assumed to be
+ * on the thread's slab; this needs
+ * to be cleared ASAP.
+ */
+ op->o_csn = a->a_vals[0];
+ freecsn = 0;
+ }
+ }
+retry_add:;
+ if ( !BER_BVISNULL( &op->o_csn ) ) {
+ /* Check we're not covered by current contextCSN */
+ int i, sid = slap_parse_csn_sid( &op->o_csn );
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ for ( i=0;
+ i < si->si_cookieState->cs_num &&
+ sid <= si->si_cookieState->cs_sids[i];
+ i++ ) {
+ if ( si->si_cookieState->cs_sids[i] == sid &&
+ ber_bvcmp( &op->o_csn, &si->si_cookieState->cs_vals[i] ) <= 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "syncrepl_entry: %s "
+ "entry '%s' csn=%s not new enough, ignored\n",
+ si->si_ridtxt, entry->e_name.bv_val, op->o_csn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ goto done;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ }
+ if ( BER_BVISNULL( &dni.dn ) ) {
+ SlapReply rs_add = {REP_RESULT};
+
+ op->o_req_dn = entry->e_name;
+ op->o_req_ndn = entry->e_nname;
+ op->o_tag = LDAP_REQ_ADD;
+ op->ora_e = entry;
+ op->o_bd = si->si_wbe;
+
+ rc = op->o_bd->be_add( op, &rs_add );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s be_add %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ switch ( rs_add.sr_err ) {
+ case LDAP_SUCCESS:
+ if ( op->ora_e == entry ) {
+ be_entry_release_w( op, entry );
+ }
+ entry = NULL;
+ break;
+
+ case LDAP_REFERRAL:
+ /* we assume that LDAP_NO_SUCH_OBJECT is returned
+ * only if the suffix entry is not present.
+ * This should not happen during Persist phase.
+ */
+ case LDAP_NO_SUCH_OBJECT:
+ if ( abs(si->si_type) == LDAP_SYNC_REFRESH_AND_PERSIST &&
+ si->si_refreshDone ) {
+ /* Something's wrong, start over */
+ ber_bvarray_free( si->si_syncCookie.ctxcsn );
+ si->si_syncCookie.ctxcsn = NULL;
+ entry_free( entry );
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ ber_bvarray_free( si->si_cookieState->cs_vals );
+ ch_free( si->si_cookieState->cs_sids );
+ si->si_cookieState->cs_vals = NULL;
+ si->si_cookieState->cs_sids = 0;
+ si->si_cookieState->cs_num = 0;
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ return LDAP_NO_SUCH_OBJECT;
+ }
+ rc = syncrepl_add_glue( op, entry );
+ entry = NULL;
+ break;
+
+ /* if an entry was added via syncrepl_add_glue(),
+ * it likely has no entryUUID, so the previous
+ * be_search() doesn't find it. In this case,
+ * give syncrepl a chance to modify it. Also
+ * allow for entries that were recreated with the
+ * same DN but a different entryUUID.
+ */
+ case LDAP_ALREADY_EXISTS:
+ if ( retry ) {
+ Operation op2 = *op;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb2 = { 0 };
+
+ op2.o_bd = be;
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_req_dn = entry->e_name;
+ op2.o_req_ndn = entry->e_nname;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_attrs = slap_anlist_all_attributes;
+ op2.ors_attrsonly = 0;
+ op2.ors_limit = NULL;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ BER_BVZERO( &op2.o_csn );
+
+ f.f_choice = LDAP_FILTER_PRESENT;
+ f.f_desc = slap_schema.si_ad_objectClass;
+ op2.ors_filter = &f;
+ op2.ors_filterstr = generic_filterstr;
+
+ op2.o_callback = &cb2;
+ cb2.sc_response = dn_callback;
+ cb2.sc_private = &dni;
+
+ rc = be->be_search( &op2, &rs2 );
+ if ( rc ) goto done;
+
+ retry = 0;
+ slap_op_time( &op->o_time, &op->o_tincr );
+ goto retry_add;
+ }
+ /* FALLTHRU */
+
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_entry: %s be_add %s failed (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rs_add.sr_err );
+ break;
+ }
+ syncCSN = NULL;
+ op->o_bd = be;
+ goto done;
+ }
+ /* FALLTHRU */
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ case MSAD_DIRSYNC_MODIFY:
+#endif
+ op->o_req_dn = dni.dn;
+ op->o_req_ndn = dni.ndn;
+ if ( dni.renamed ) {
+ struct berval noldp, newp;
+ Modifications *mod, **modtail, **ml, *m2 = NULL;
+ int i, got_replace = 0, just_rename = 0;
+ SlapReply rs_modify = {REP_RESULT};
+
+ op->o_tag = LDAP_REQ_MODRDN;
+ dnRdn( &entry->e_name, &op->orr_newrdn );
+ dnRdn( &entry->e_nname, &op->orr_nnewrdn );
+
+ if ( !BER_BVISNULL( &dni.nnewSup )) {
+ dnParent( &entry->e_name, &newp );
+ op->orr_newSup = &newp;
+ op->orr_nnewSup = &dni.nnewSup;
+ } else {
+ op->orr_newSup = NULL;
+ op->orr_nnewSup = NULL;
+ }
+ op->orr_newDN = entry->e_name;
+ op->orr_nnewDN = entry->e_nname;
+ op->orr_deleteoldrdn = dni.delOldRDN;
+ op->orr_modlist = NULL;
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( syncstate != MSAD_DIRSYNC_MODIFY )
+#endif
+ {
+ if ( ( rc = slap_modrdn2mods( op, &rs_modify ) ) ) {
+ goto done;
+ }
+ }
+
+ /* Drop the RDN-related mods from this op, because their
+ * equivalents were just setup by slap_modrdn2mods.
+ *
+ * If delOldRDN is TRUE then we should see a delete modop
+ * for oldDesc. We might see a replace instead.
+ * delete with no values: therefore newDesc != oldDesc.
+ * if oldNcount == 1, then Drop this op.
+ * delete with 1 value: can only be the oldRDN value. Drop op.
+ * delete with N values: Drop oldRDN value, keep remainder.
+ * replace with 1 value: if oldNcount == 1 and
+ * newDesc == oldDesc, Drop this op.
+ * Any other cases must be left intact.
+ *
+ * We should also see an add modop for newDesc. (But not if
+ * we got a replace modop due to delOldRDN.) If it has
+ * multiple values, we'll have to drop the new RDN value.
+ */
+ modtail = &op->orr_modlist;
+ if ( dni.delOldRDN ) {
+ for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next ) {
+ if ( (*ml)->sml_desc == dni.oldDesc ) {
+ mod = *ml;
+ if ( mod->sml_op == LDAP_MOD_REPLACE &&
+ dni.oldDesc != dni.newDesc ) {
+ /* This Replace is due to other Mods.
+ * Just let it ride.
+ */
+ continue;
+ }
+ if ( mod->sml_numvals <= 1 &&
+ dni.oldNcount == 1 &&
+ ( mod->sml_op == LDAP_MOD_DELETE ||
+ mod->sml_op == LDAP_MOD_REPLACE )) {
+ if ( mod->sml_op == LDAP_MOD_REPLACE )
+ got_replace = 1;
+ /* Drop this op */
+ *ml = mod->sml_next;
+ mod->sml_next = NULL;
+ slap_mods_free( mod, 1 );
+ break;
+ }
+ if ( mod->sml_op != LDAP_MOD_DELETE || mod->sml_numvals == 0 )
+ continue;
+ for ( m2 = op->orr_modlist; m2; m2=m2->sml_next ) {
+ if ( m2->sml_desc == dni.oldDesc &&
+ m2->sml_op == LDAP_MOD_DELETE ) break;
+ }
+ for ( i=0; i<mod->sml_numvals; i++ ) {
+ if ( bvmatch( &mod->sml_values[i], &m2->sml_values[0] )) {
+ mod->sml_numvals--;
+ ch_free( mod->sml_values[i].bv_val );
+ mod->sml_values[i] = mod->sml_values[mod->sml_numvals];
+ BER_BVZERO( &mod->sml_values[mod->sml_numvals] );
+ if ( mod->sml_nvalues ) {
+ ch_free( mod->sml_nvalues[i].bv_val );
+ mod->sml_nvalues[i] = mod->sml_nvalues[mod->sml_numvals];
+ BER_BVZERO( &mod->sml_nvalues[mod->sml_numvals] );
+ }
+ break;
+ }
+ }
+ if ( !mod->sml_numvals ) {
+ /* Drop this op */
+ *ml = mod->sml_next;
+ mod->sml_next = NULL;
+ slap_mods_free( mod, 1 );
+ }
+ break;
+ }
+ }
+ }
+ if ( !got_replace ) {
+ for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next ) {
+ if ( (*ml)->sml_desc == dni.newDesc ) {
+ mod = *ml;
+ if ( mod->sml_op != LDAP_MOD_ADD )
+ continue;
+ if ( mod->sml_numvals == 1 ) {
+ /* Drop this op */
+ *ml = mod->sml_next;
+ mod->sml_next = NULL;
+ slap_mods_free( mod, 1 );
+ break;
+ }
+ for ( m2 = op->orr_modlist; m2; m2=m2->sml_next ) {
+ if ( m2->sml_desc == dni.oldDesc &&
+ m2->sml_op == SLAP_MOD_SOFTADD ) break;
+ }
+ for ( i=0; i<mod->sml_numvals; i++ ) {
+ if ( bvmatch( &mod->sml_values[i], &m2->sml_values[0] )) {
+ mod->sml_numvals--;
+ ch_free( mod->sml_values[i].bv_val );
+ mod->sml_values[i] = mod->sml_values[mod->sml_numvals];
+ BER_BVZERO( &mod->sml_values[mod->sml_numvals] );
+ if ( mod->sml_nvalues ) {
+ ch_free( mod->sml_nvalues[i].bv_val );
+ mod->sml_nvalues[i] = mod->sml_nvalues[mod->sml_numvals];
+ BER_BVZERO( &mod->sml_nvalues[mod->sml_numvals] );
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /* RDNs must be NUL-terminated for back-ldap */
+ noldp = op->orr_newrdn;
+ ber_dupbv_x( &op->orr_newrdn, &noldp, op->o_tmpmemctx );
+ noldp = op->orr_nnewrdn;
+ ber_dupbv_x( &op->orr_nnewrdn, &noldp, op->o_tmpmemctx );
+
+ /* Setup opattrs too */
+ {
+ static AttributeDescription *nullattr = NULL;
+ static AttributeDescription **const opattrs[] = {
+ &slap_schema.si_ad_entryCSN,
+ &slap_schema.si_ad_modifiersName,
+ &slap_schema.si_ad_modifyTimestamp,
+ &nullattr
+ };
+ AttributeDescription *opattr;
+ int i;
+
+ modtail = &m2;
+ /* pull mod off incoming modlist */
+ for ( i = 0; (opattr = *opattrs[i]) != NULL; i++ ) {
+ for ( ml = &dni.mods; *ml; ml = &(*ml)->sml_next )
+ {
+ if ( (*ml)->sml_desc == opattr ) {
+ mod = *ml;
+ *ml = mod->sml_next;
+ mod->sml_next = NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ break;
+ }
+ }
+ }
+ /* If there are still Modifications left, put the opattrs
+ * back, and let be_modify run. Otherwise, append the opattrs
+ * to the orr_modlist.
+ */
+ if ( dni.mods ) {
+ mod = dni.mods;
+ /* don't set a CSN for the rename op */
+ if ( syncCSN )
+ slap_graduate_commit_csn( op );
+ } else {
+ mod = op->orr_modlist;
+ just_rename = 1;
+ }
+ for ( ; mod->sml_next; mod=mod->sml_next );
+ mod->sml_next = m2;
+ }
+ op->o_bd = si->si_wbe;
+retry_modrdn:;
+ rs_reinit( &rs_modify, REP_RESULT );
+ rc = op->o_bd->be_modrdn( op, &rs_modify );
+
+ /* NOTE: noSuchObject should result because the new superior
+ * has not been added yet (ITS#6472) */
+ if ( rc == LDAP_NO_SUCH_OBJECT && op->orr_nnewSup != NULL ) {
+ Operation op2 = *op;
+ rc = syncrepl_add_glue_ancestors( &op2, entry );
+ if ( rc == LDAP_SUCCESS ) {
+ goto retry_modrdn;
+ }
+ }
+
+ op->o_tmpfree( op->orr_nnewrdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_newrdn.bv_val, op->o_tmpmemctx );
+
+ slap_mods_free( op->orr_modlist, 1 );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s be_modrdn %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ op->o_bd = be;
+ /* Renamed entries may still have other mods so just fallthru */
+ op->o_req_dn = entry->e_name;
+ op->o_req_ndn = entry->e_nname;
+ /* Use CSN on the modify */
+ if ( just_rename )
+ syncCSN = NULL;
+ else if ( syncCSN )
+ slap_queue_csn( op, syncCSN );
+ }
+ if ( dni.mods ) {
+ SlapReply rs_modify = {REP_RESULT};
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = dni.mods;
+ op->orm_no_opattrs = 1;
+ op->o_bd = si->si_wbe;
+
+ rc = op->o_bd->be_modify( op, &rs_modify );
+ slap_mods_free( op->orm_modlist, 1 );
+ op->orm_no_opattrs = 0;
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s be_modify %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ if ( rs_modify.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_entry: %s be_modify failed (%d)\n",
+ si->si_ridtxt, rs_modify.sr_err );
+ }
+ syncCSN = NULL;
+ op->o_bd = be;
+ } else if ( !dni.renamed ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s entry unchanged, ignored (%s)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val );
+ if ( syncCSN ) {
+ slap_graduate_commit_csn( op );
+ syncCSN = NULL;
+ }
+ }
+ goto done;
+ case LDAP_SYNC_DELETE :
+ if ( !BER_BVISNULL( &dni.dn ) ) {
+ SlapReply rs_delete = {REP_RESULT};
+ op->o_req_dn = dni.dn;
+ op->o_req_ndn = dni.ndn;
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_bd = si->si_wbe;
+ if ( !syncCSN && si->si_syncCookie.ctxcsn ) {
+ slap_queue_csn( op, si->si_syncCookie.ctxcsn );
+ }
+ rc = op->o_bd->be_delete( op, &rs_delete );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_entry: %s be_delete %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+ if ( rc == LDAP_NO_SUCH_OBJECT )
+ rc = LDAP_SUCCESS;
+
+ while ( rs_delete.sr_err == LDAP_SUCCESS
+ && op->o_delete_glue_parent ) {
+ op->o_delete_glue_parent = 0;
+ if ( !be_issuffix( be, &op->o_req_ndn ) ) {
+ slap_callback cb = { NULL };
+ cb.sc_response = syncrepl_null_callback;
+ dnParent( &op->o_req_ndn, &pdn );
+ op->o_req_dn = pdn;
+ op->o_req_ndn = pdn;
+ op->o_callback = &cb;
+ rs_reinit( &rs_delete, REP_RESULT );
+ op->o_bd->be_delete( op, &rs_delete );
+ } else {
+ break;
+ }
+ }
+ syncCSN = NULL;
+ op->o_bd = be;
+ }
+ goto done;
+
+ default :
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_entry: %s unknown syncstate\n", si->si_ridtxt );
+ goto done;
+ }
+
+done:
+ slap_sl_free( syncUUID[1].bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &syncUUID[1] );
+ if ( !BER_BVISNULL( &dni.ndn ) ) {
+ op->o_tmpfree( dni.ndn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &dni.dn ) ) {
+ op->o_tmpfree( dni.dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( entry ) {
+ entry_free( entry );
+ }
+ if ( syncCSN ) {
+ slap_graduate_commit_csn( op );
+ }
+ if ( !BER_BVISNULL( &op->o_csn ) && freecsn ) {
+ op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
+ }
+ BER_BVZERO( &op->o_csn );
+ return rc;
+}
+
+static struct berval gcbva[] = {
+ BER_BVC("top"),
+ BER_BVC("glue"),
+ BER_BVNULL
+};
+
+#define NP_DELETE_ONE 2
+
+static void
+syncrepl_del_nonpresent(
+ Operation *op,
+ syncinfo_t *si,
+ BerVarray uuids,
+ struct sync_cookie *sc,
+ int m )
+{
+ Backend* be = op->o_bd;
+ slap_callback cb = { NULL };
+ struct nonpresent_entry *np_list, *np_prev;
+ int rc;
+ AttributeName an[3]; /* entryUUID, entryCSN, NULL */
+
+ struct berval pdn = BER_BVNULL;
+ struct berval csn;
+
+ if ( si->si_rewrite ) {
+ op->o_req_dn = si->si_suffixm;
+ op->o_req_ndn = si->si_suffixm;
+ } else
+ {
+ op->o_req_dn = si->si_base;
+ op->o_req_ndn = si->si_base;
+ }
+
+ cb.sc_response = nonpresent_callback;
+ cb.sc_private = si;
+
+ op->o_callback = &cb;
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->ors_scope = si->si_scope;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->o_time = slap_get_time();
+ op->ors_tlimit = SLAP_NO_LIMIT;
+
+
+ if ( uuids ) {
+ Filter uf;
+ AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
+ int i;
+
+ op->ors_attrsonly = 1;
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->ors_limit = NULL;
+ op->ors_filter = &uf;
+
+ uf.f_ava = &eq;
+ uf.f_av_desc = slap_schema.si_ad_entryUUID;
+ uf.f_next = NULL;
+ uf.f_choice = LDAP_FILTER_EQUALITY;
+ si->si_refreshDelete |= NP_DELETE_ONE;
+
+ for (i=0; uuids[i].bv_val; i++) {
+ SlapReply rs_search = {REP_RESULT};
+
+ op->ors_slimit = 1;
+ uf.f_av_value = uuids[i];
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ Debug( LDAP_DEBUG_SYNC, "syncrepl_del_nonpresent: %s "
+ "checking non-present filter=%s\n",
+ si->si_ridtxt, op->ors_filterstr.bv_val );
+ rc = be->be_search( op, &rs_search );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ si->si_refreshDelete ^= NP_DELETE_ONE;
+ } else {
+ Filter *cf, *of;
+ Filter mmf[2];
+ AttributeAssertion mmaa;
+ SlapReply rs_search = {REP_RESULT};
+
+ memset( &an[0], 0, 3 * sizeof( AttributeName ) );
+ an[0].an_name = slap_schema.si_ad_entryUUID->ad_cname;
+ an[0].an_desc = slap_schema.si_ad_entryUUID;
+ an[1].an_name = slap_schema.si_ad_entryCSN->ad_cname;
+ an[1].an_desc = slap_schema.si_ad_entryCSN;
+ op->ors_attrs = an;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ op->ors_attrsonly = 0;
+ op->ors_filter = filter_dup( si->si_filter, op->o_tmpmemctx );
+ /* In multi-provider, updates can continue to arrive while
+ * we're searching. Limit the search result to entries
+ * older than our newest cookie CSN.
+ */
+ if ( SLAP_MULTIPROVIDER( op->o_bd )) {
+ Filter *f;
+ int i;
+
+ f = mmf;
+ f->f_choice = LDAP_FILTER_AND;
+ f->f_next = op->ors_filter;
+ f->f_and = f+1;
+ of = f->f_and;
+ f = of;
+ f->f_choice = LDAP_FILTER_LE;
+ f->f_ava = &mmaa;
+ f->f_av_desc = slap_schema.si_ad_entryCSN;
+ f->f_next = NULL;
+ BER_BVZERO( &f->f_av_value );
+ for ( i=0; i<sc->numcsns; i++ ) {
+ if ( ber_bvcmp( &sc->ctxcsn[i], &f->f_av_value ) > 0 )
+ f->f_av_value = sc->ctxcsn[i];
+ }
+ of = op->ors_filter;
+ op->ors_filter = mmf;
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ } else {
+ cf = NULL;
+ op->ors_filterstr = si->si_filterstr;
+ }
+ op->o_nocaching = 1;
+
+
+ rc = be->be_search( op, &rs_search );
+ if ( SLAP_MULTIPROVIDER( op->o_bd )) {
+ op->ors_filter = of;
+ }
+ if ( op->ors_filter ) filter_free_x( op, op->ors_filter, 1 );
+ if ( op->ors_filterstr.bv_val != si->si_filterstr.bv_val ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+
+ }
+
+ op->o_nocaching = 0;
+
+ if ( !LDAP_LIST_EMPTY( &si->si_nonpresentlist ) ) {
+
+ if ( !BER_BVISNULL( &sc->delcsn ) ) {
+ Debug( LDAP_DEBUG_SYNC, "syncrepl_del_nonpresent: %s "
+ "using delcsn=%s\n",
+ si->si_ridtxt, sc->delcsn.bv_val );
+ csn = sc->delcsn;
+ } else if ( sc->ctxcsn && !BER_BVISNULL( &sc->ctxcsn[m] ) ) {
+ csn = sc->ctxcsn[m];
+ } else {
+ csn = si->si_syncCookie.ctxcsn[0];
+ }
+
+ op->o_bd = si->si_wbe;
+ slap_queue_csn( op, &csn );
+
+ np_list = LDAP_LIST_FIRST( &si->si_nonpresentlist );
+ while ( np_list != NULL ) {
+ SlapReply rs_delete = {REP_RESULT};
+
+ LDAP_LIST_REMOVE( np_list, npe_link );
+ np_prev = np_list;
+ np_list = LDAP_LIST_NEXT( np_list, npe_link );
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_callback = &cb;
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = si;
+ op->o_req_dn = *np_prev->npe_name;
+ op->o_req_ndn = *np_prev->npe_nname;
+ rc = op->o_bd->be_delete( op, &rs_delete );
+ Debug( LDAP_DEBUG_SYNC,
+ "syncrepl_del_nonpresent: %s be_delete %s (%d)\n",
+ si->si_ridtxt, op->o_req_dn.bv_val, rc );
+
+ if ( rs_delete.sr_err == LDAP_NOT_ALLOWED_ON_NONLEAF ) {
+ SlapReply rs_modify = {REP_RESULT};
+ Modifications mod1, mod2, mod3;
+ struct berval vals[2] = { csn, BER_BVNULL };
+
+ mod1.sml_op = LDAP_MOD_REPLACE;
+ mod1.sml_flags = 0;
+ mod1.sml_desc = slap_schema.si_ad_objectClass;
+ mod1.sml_type = mod1.sml_desc->ad_cname;
+ mod1.sml_numvals = 2;
+ mod1.sml_values = &gcbva[0];
+ mod1.sml_nvalues = NULL;
+ mod1.sml_next = &mod2;
+
+ mod2.sml_op = LDAP_MOD_REPLACE;
+ mod2.sml_flags = 0;
+ mod2.sml_desc = slap_schema.si_ad_structuralObjectClass;
+ mod2.sml_type = mod2.sml_desc->ad_cname;
+ mod2.sml_numvals = 1;
+ mod2.sml_values = &gcbva[1];
+ mod2.sml_nvalues = NULL;
+ mod2.sml_next = &mod3;
+
+ mod3.sml_op = LDAP_MOD_REPLACE;
+ mod3.sml_flags = 0;
+ mod3.sml_desc = slap_schema.si_ad_entryCSN;
+ mod3.sml_type = mod3.sml_desc->ad_cname;
+ mod3.sml_numvals = 1;
+ mod3.sml_values = vals;
+ mod3.sml_nvalues = NULL;
+ mod3.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod1;
+
+ rc = op->o_bd->be_modify( op, &rs_modify );
+ if ( mod3.sml_next ) slap_mods_free( mod3.sml_next, 1 );
+ }
+
+ while ( rs_delete.sr_err == LDAP_SUCCESS &&
+ op->o_delete_glue_parent ) {
+ op->o_delete_glue_parent = 0;
+ if ( !be_issuffix( be, &op->o_req_ndn ) ) {
+ slap_callback cb = { NULL };
+ cb.sc_response = syncrepl_null_callback;
+ dnParent( &op->o_req_ndn, &pdn );
+ op->o_req_dn = pdn;
+ op->o_req_ndn = pdn;
+ op->o_callback = &cb;
+ rs_reinit( &rs_delete, REP_RESULT );
+ /* give it a root privil ? */
+ op->o_bd->be_delete( op, &rs_delete );
+ } else {
+ break;
+ }
+ }
+
+ op->o_delete_glue_parent = 0;
+
+ ber_bvfree( np_prev->npe_name );
+ ber_bvfree( np_prev->npe_nname );
+ ch_free( np_prev );
+
+ if ( slapd_shutdown ) {
+ break;
+ }
+ }
+
+ slap_graduate_commit_csn( op );
+ op->o_bd = be;
+
+ op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_csn );
+ }
+
+ return;
+}
+
+static int
+syncrepl_add_glue_ancestors(
+ Operation* op,
+ Entry *e )
+{
+ Backend *be = op->o_bd;
+ slap_callback cb = { NULL };
+ Attribute *a;
+ int rc = LDAP_SUCCESS;
+ int suffrdns;
+ int i;
+ struct berval dn = BER_BVNULL;
+ struct berval ndn = BER_BVNULL;
+ Entry *glue;
+ struct berval ptr, nptr;
+ char *comma;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_callback = &cb;
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = NULL;
+
+ dn = e->e_name;
+ ndn = e->e_nname;
+
+ /* count RDNs in suffix */
+ if ( !BER_BVISEMPTY( &be->be_nsuffix[0] ) ) {
+ for ( i = 0, ptr = be->be_nsuffix[0], comma = ptr.bv_val; comma != NULL; comma = ber_bvchr( &ptr, ',' ) ) {
+ comma++;
+ ptr.bv_len -= comma - ptr.bv_val;
+ ptr.bv_val = comma;
+ i++;
+ }
+ suffrdns = i;
+ } else {
+ /* suffix is "" */
+ suffrdns = 0;
+ }
+
+ /* Start with BE suffix */
+ ptr = dn;
+ for ( i = 0; i < suffrdns; i++ ) {
+ comma = ber_bvrchr( &ptr, ',' );
+ if ( comma != NULL ) {
+ ptr.bv_len = comma - ptr.bv_val;
+ } else {
+ ptr.bv_len = 0;
+ break;
+ }
+ }
+
+ if ( !BER_BVISEMPTY( &ptr ) ) {
+ dn.bv_len -= ptr.bv_len + ( suffrdns != 0 );
+ dn.bv_val += ptr.bv_len + ( suffrdns != 0 );
+ }
+
+ /* the normalizedDNs are always the same length, no counting
+ * required.
+ */
+ nptr = ndn;
+ if ( ndn.bv_len > be->be_nsuffix[0].bv_len ) {
+ ndn.bv_val += ndn.bv_len - be->be_nsuffix[0].bv_len;
+ ndn.bv_len = be->be_nsuffix[0].bv_len;
+
+ nptr.bv_len = ndn.bv_val - nptr.bv_val - 1;
+
+ } else {
+ nptr.bv_len = 0;
+ }
+
+ while ( ndn.bv_val > e->e_nname.bv_val ) {
+ SlapReply rs_add = {REP_RESULT};
+
+ glue = entry_alloc();
+ ber_dupbv( &glue->e_name, &dn );
+ ber_dupbv( &glue->e_nname, &ndn );
+
+ a = attr_alloc( slap_schema.si_ad_objectClass );
+
+ a->a_numvals = 2;
+ a->a_vals = ch_calloc( 3, sizeof( struct berval ) );
+ ber_dupbv( &a->a_vals[0], &gcbva[0] );
+ ber_dupbv( &a->a_vals[1], &gcbva[1] );
+ ber_dupbv( &a->a_vals[2], &gcbva[2] );
+
+ a->a_nvals = a->a_vals;
+
+ a->a_next = glue->e_attrs;
+ glue->e_attrs = a;
+
+ a = attr_alloc( slap_schema.si_ad_structuralObjectClass );
+
+ a->a_numvals = 1;
+ a->a_vals = ch_calloc( 2, sizeof( struct berval ) );
+ ber_dupbv( &a->a_vals[0], &gcbva[1] );
+ ber_dupbv( &a->a_vals[1], &gcbva[2] );
+
+ a->a_nvals = a->a_vals;
+
+ a->a_next = glue->e_attrs;
+ glue->e_attrs = a;
+
+ op->o_req_dn = glue->e_name;
+ op->o_req_ndn = glue->e_nname;
+ op->ora_e = glue;
+ rc = be->be_add ( op, &rs_add );
+ if ( rs_add.sr_err == LDAP_SUCCESS ) {
+ if ( op->ora_e == glue )
+ be_entry_release_w( op, glue );
+ } else {
+ /* incl. ALREADY EXIST */
+ entry_free( glue );
+ if ( rs_add.sr_err != LDAP_ALREADY_EXISTS ) {
+ entry_free( e );
+ return rc;
+ }
+ }
+
+ /* Move to next child */
+ comma = ber_bvrchr( &ptr, ',' );
+ if ( comma == NULL ) {
+ break;
+ }
+ ptr.bv_len = comma - ptr.bv_val;
+
+ dn.bv_val = ++comma;
+ dn.bv_len = e->e_name.bv_len - (dn.bv_val - e->e_name.bv_val);
+
+ comma = ber_bvrchr( &nptr, ',' );
+ assert( comma != NULL );
+ nptr.bv_len = comma - nptr.bv_val;
+
+ ndn.bv_val = ++comma;
+ ndn.bv_len = e->e_nname.bv_len - (ndn.bv_val - e->e_nname.bv_val);
+ }
+
+ return rc;
+}
+
+int
+syncrepl_add_glue(
+ Operation* op,
+ Entry *e )
+{
+ slap_callback cb = { NULL };
+ int rc;
+ Backend *be = op->o_bd;
+ SlapReply rs_add = {REP_RESULT};
+
+ rc = syncrepl_add_glue_ancestors( op, e );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ case LDAP_ALREADY_EXISTS:
+ break;
+
+ default:
+ return rc;
+ }
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_callback = &cb;
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = NULL;
+
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ op->ora_e = e;
+ rc = be->be_add ( op, &rs_add );
+ if ( rs_add.sr_err == LDAP_SUCCESS ) {
+ if ( op->ora_e == e )
+ be_entry_release_w( op, e );
+ } else {
+ entry_free( e );
+ }
+
+ return rc;
+}
+
+static int
+syncrepl_dsee_update(
+ syncinfo_t *si,
+ Operation *op
+)
+{
+ Backend *be = op->o_bd;
+ Modifications mod;
+ struct berval first = BER_BVNULL;
+ slap_callback cb = { NULL };
+ SlapReply rs_modify = {REP_RESULT};
+ char valbuf[sizeof("18446744073709551615")];
+ struct berval bvals[2];
+ int rc;
+
+ if ( si->si_lastchange == si->si_prevchange )
+ return 0;
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_desc = sy_ad_dseeLastChange;
+ mod.sml_type = mod.sml_desc->ad_cname;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_nvalues = NULL;
+ mod.sml_values = bvals;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+ bvals[0].bv_val = valbuf;
+ bvals[0].bv_len = sprintf( valbuf, "%lu", si->si_lastchange );
+ BER_BVZERO( &bvals[1] );
+
+ op->o_bd = si->si_wbe;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = si;
+
+ op->o_callback = &cb;
+ op->o_req_dn = si->si_contextdn;
+ op->o_req_ndn = si->si_contextdn;
+
+ /* update contextCSN */
+ op->o_dont_replicate = 1;
+
+ /* avoid timestamp collisions */
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ rc = op->o_bd->be_modify( op, &rs_modify );
+
+ op->o_bd = be;
+ si->si_prevchange = si->si_lastchange;
+
+ return rc;
+}
+
+static int
+syncrepl_updateCookie(
+ syncinfo_t *si,
+ Operation *op,
+ struct sync_cookie *syncCookie,
+ int save )
+{
+ Backend *be = op->o_bd;
+ Modifications mod;
+ struct berval first = BER_BVNULL;
+ struct sync_cookie sc;
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+#endif
+
+ int rc, i, j, changed = 0;
+ ber_len_t len;
+
+ slap_callback cb = { NULL };
+ SlapReply rs_modify = {REP_RESULT};
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_desc = slap_schema.si_ad_contextCSN;
+ mod.sml_type = mod.sml_desc->ad_cname;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_nvalues = NULL;
+ mod.sml_next = NULL;
+
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+ while ( si->si_cookieState->cs_updating )
+ ldap_pvt_thread_cond_wait( &si->si_cookieState->cs_cond, &si->si_cookieState->cs_mutex );
+
+#ifdef CHECK_CSN
+ for ( i=0; i<syncCookie->numcsns; i++ ) {
+ assert( !syn->ssyn_validate( syn, syncCookie->ctxcsn+i ));
+ }
+ for ( i=0; i<si->si_cookieState->cs_num; i++ ) {
+ assert( !syn->ssyn_validate( syn, si->si_cookieState->cs_vals+i ));
+ }
+#endif
+
+ /* clone the cookieState CSNs so we can Replace the whole thing */
+ sc.numcsns = si->si_cookieState->cs_num;
+ if ( sc.numcsns ) {
+ ber_bvarray_dup_x( &sc.ctxcsn, si->si_cookieState->cs_vals, NULL );
+ sc.sids = ch_malloc( sc.numcsns * sizeof(int));
+ for ( i=0; i<sc.numcsns; i++ )
+ sc.sids[i] = si->si_cookieState->cs_sids[i];
+ } else {
+ sc.ctxcsn = NULL;
+ sc.sids = NULL;
+ }
+
+ /* find any CSNs in the syncCookie that are newer than the cookieState */
+ for ( i=0; i<syncCookie->numcsns; i++ ) {
+ for ( j=0; j<sc.numcsns; j++ ) {
+ if ( syncCookie->sids[i] < sc.sids[j] )
+ break;
+ if ( syncCookie->sids[i] != sc.sids[j] )
+ continue;
+ len = syncCookie->ctxcsn[i].bv_len;
+ if ( len > sc.ctxcsn[j].bv_len )
+ len = sc.ctxcsn[j].bv_len;
+ if ( memcmp( syncCookie->ctxcsn[i].bv_val,
+ sc.ctxcsn[j].bv_val, len ) > 0 ) {
+ ber_bvreplace( &sc.ctxcsn[j], &syncCookie->ctxcsn[i] );
+ changed = 1;
+ if ( BER_BVISNULL( &first ) ||
+ memcmp( syncCookie->ctxcsn[i].bv_val, first.bv_val, first.bv_len ) > 0 ) {
+ first = syncCookie->ctxcsn[i];
+ }
+ }
+ break;
+ }
+ /* there was no match for this SID, it's a new CSN */
+ if ( j == sc.numcsns ||
+ syncCookie->sids[i] != sc.sids[j] ) {
+ slap_insert_csn_sids( &sc, j, syncCookie->sids[i],
+ &syncCookie->ctxcsn[i] );
+ if ( BER_BVISNULL( &first ) ||
+ memcmp( syncCookie->ctxcsn[i].bv_val, first.bv_val, first.bv_len ) > 0 ) {
+ first = syncCookie->ctxcsn[i];
+ }
+ changed = 1;
+ }
+ }
+ /* Should never happen, ITS#5065 */
+ if ( BER_BVISNULL( &first ) || !changed ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+ ber_bvarray_free( sc.ctxcsn );
+ ch_free( sc.sids );
+ return 0;
+ }
+
+ si->si_cookieState->cs_updating = 1;
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+
+ op->o_bd = si->si_wbe;
+ slap_queue_csn( op, &first );
+
+ op->o_tag = LDAP_REQ_MODIFY;
+
+ cb.sc_response = syncrepl_null_callback;
+ cb.sc_private = si;
+
+ op->o_callback = &cb;
+ op->o_req_dn = si->si_contextdn;
+ op->o_req_ndn = si->si_contextdn;
+
+ /* update contextCSN */
+ op->o_dont_replicate = !save;
+
+ /* avoid timestamp collisions */
+ if ( save )
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ mod.sml_numvals = sc.numcsns;
+ mod.sml_values = sc.ctxcsn;
+
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ rc = op->o_bd->be_modify( op, &rs_modify );
+
+ if ( rs_modify.sr_err == LDAP_NO_SUCH_OBJECT &&
+ SLAP_SYNC_SUBENTRY( op->o_bd )) {
+ const char *text;
+ char txtbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof txtbuf;
+ Entry *e = slap_create_context_csn_entry( op->o_bd, NULL );
+ rs_reinit( &rs_modify, REP_RESULT );
+ rc = slap_mods2entry( &mod, &e, 0, 1, &text, txtbuf, textlen);
+ slap_queue_csn( op, &first );
+ op->o_tag = LDAP_REQ_ADD;
+ op->ora_e = e;
+ rc = op->o_bd->be_add( op, &rs_modify );
+ if ( e == op->ora_e )
+ be_entry_release_w( op, op->ora_e );
+ }
+
+ op->orm_no_opattrs = 0;
+ op->o_dont_replicate = 0;
+ ldap_pvt_thread_mutex_lock( &si->si_cookieState->cs_mutex );
+
+ if ( rs_modify.sr_err == LDAP_SUCCESS ) {
+ slap_sync_cookie_free( &si->si_syncCookie, 0 );
+ ber_bvarray_free( si->si_cookieState->cs_vals );
+ ch_free( si->si_cookieState->cs_sids );
+ si->si_cookieState->cs_vals = sc.ctxcsn;
+ si->si_cookieState->cs_sids = sc.sids;
+ si->si_cookieState->cs_num = sc.numcsns;
+
+ /* Don't just dup the provider's cookie, recreate it */
+ si->si_syncCookie.numcsns = si->si_cookieState->cs_num;
+ ber_bvarray_dup_x( &si->si_syncCookie.ctxcsn, si->si_cookieState->cs_vals, NULL );
+ si->si_syncCookie.sids = ch_malloc( si->si_cookieState->cs_num * sizeof(int) );
+ for ( i=0; i<si->si_cookieState->cs_num; i++ )
+ si->si_syncCookie.sids[i] = si->si_cookieState->cs_sids[i];
+
+ si->si_cookieState->cs_age++;
+ si->si_cookieAge = si->si_cookieState->cs_age;
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_updateCookie: %s be_modify failed (%d)\n",
+ si->si_ridtxt, rs_modify.sr_err );
+ ch_free( sc.sids );
+ ber_bvarray_free( sc.ctxcsn );
+ }
+
+#ifdef CHECK_CSN
+ for ( i=0; i<si->si_cookieState->cs_num; i++ ) {
+ assert( !syn->ssyn_validate( syn, si->si_cookieState->cs_vals+i ));
+ }
+#endif
+
+ si->si_cookieState->cs_updating = 0;
+ ldap_pvt_thread_cond_broadcast( &si->si_cookieState->cs_cond );
+ ldap_pvt_thread_mutex_unlock( &si->si_cookieState->cs_mutex );
+
+ op->o_bd = be;
+ op->o_tmpfree( op->o_csn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_csn );
+ if ( mod.sml_next ) slap_mods_free( mod.sml_next, 1 );
+
+ return rc;
+}
+
+/* Compare the attribute from the old entry to the one in the new
+ * entry. The Modifications from the new entry will either be left
+ * in place, or changed to an Add or Delete as needed.
+ */
+static void
+attr_cmp( Operation *op, Attribute *old, Attribute *new,
+ Modifications ***mret, Modifications ***mcur )
+{
+ int i, j;
+ Modifications *mod, **modtail;
+
+ modtail = *mret;
+
+ if ( old ) {
+ int n, o, nn, no;
+ struct berval **adds, **dels;
+ /* count old and new */
+ for ( o=0; old->a_vals[o].bv_val; o++ ) ;
+ for ( n=0; new->a_vals[n].bv_val; n++ ) ;
+
+ /* there MUST be both old and new values */
+ assert( o != 0 );
+ assert( n != 0 );
+ j = 0;
+
+ adds = op->o_tmpalloc( sizeof(struct berval *) * n, op->o_tmpmemctx );
+ dels = op->o_tmpalloc( sizeof(struct berval *) * o, op->o_tmpmemctx );
+
+ for ( i=0; i<o; i++ ) dels[i] = &old->a_vals[i];
+ for ( i=0; i<n; i++ ) adds[i] = &new->a_vals[i];
+
+ nn = n; no = o;
+
+ for ( i=0; i<o; i++ ) {
+ for ( j=0; j<n; j++ ) {
+ if ( !adds[j] )
+ continue;
+ if ( bvmatch( dels[i], adds[j] ) ) {
+ no--;
+ nn--;
+ adds[j] = NULL;
+ dels[i] = NULL;
+ break;
+ }
+ }
+ }
+
+ /* Don't delete/add an objectClass, always use the replace op.
+ * Modify would fail if provider has replaced entry with a new,
+ * and the new explicitly includes a superior of a class that was
+ * only included implicitly in the old entry. Ref ITS#5517.
+ *
+ * Also use replace op if attr has no equality matching rule.
+ * (ITS#5781)
+ */
+ if ( ( nn || ( no > 0 && no < o ) ) &&
+ ( old->a_desc == slap_schema.si_ad_objectClass ||
+ !old->a_desc->ad_type->sat_equality ) )
+ {
+ no = o;
+ }
+
+ i = j;
+ /* all old values were deleted, just use the replace op */
+ if ( no == o ) {
+ i = j-1;
+ } else if ( no ) {
+ /* delete some values */
+ mod = ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = LDAP_MOD_DELETE;
+ mod->sml_flags = 0;
+ mod->sml_desc = old->a_desc;
+ mod->sml_type = mod->sml_desc->ad_cname;
+ mod->sml_numvals = no;
+ mod->sml_values = ch_malloc( ( no + 1 ) * sizeof(struct berval) );
+ if ( old->a_vals != old->a_nvals ) {
+ mod->sml_nvalues = ch_malloc( ( no + 1 ) * sizeof(struct berval) );
+ } else {
+ mod->sml_nvalues = NULL;
+ }
+ j = 0;
+ for ( i = 0; i < o; i++ ) {
+ if ( !dels[i] ) continue;
+ ber_dupbv( &mod->sml_values[j], &old->a_vals[i] );
+ if ( mod->sml_nvalues ) {
+ ber_dupbv( &mod->sml_nvalues[j], &old->a_nvals[i] );
+ }
+ j++;
+ }
+ BER_BVZERO( &mod->sml_values[j] );
+ if ( mod->sml_nvalues ) {
+ BER_BVZERO( &mod->sml_nvalues[j] );
+ }
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ i = j;
+ }
+ op->o_tmpfree( dels, op->o_tmpmemctx );
+ /* some values were added */
+ if ( nn && no < o ) {
+ mod = ch_malloc( sizeof( Modifications ) );
+ if ( is_at_single_value( old->a_desc->ad_type ))
+ mod->sml_op = LDAP_MOD_REPLACE;
+ else
+ mod->sml_op = LDAP_MOD_ADD;
+ mod->sml_flags = 0;
+ mod->sml_desc = old->a_desc;
+ mod->sml_type = mod->sml_desc->ad_cname;
+ mod->sml_numvals = nn;
+ mod->sml_values = ch_malloc( ( nn + 1 ) * sizeof(struct berval) );
+ if ( old->a_vals != old->a_nvals ) {
+ mod->sml_nvalues = ch_malloc( ( nn + 1 ) * sizeof(struct berval) );
+ } else {
+ mod->sml_nvalues = NULL;
+ }
+ j = 0;
+ for ( i = 0; i < n; i++ ) {
+ if ( !adds[i] ) continue;
+ ber_dupbv( &mod->sml_values[j], &new->a_vals[i] );
+ if ( mod->sml_nvalues ) {
+ ber_dupbv( &mod->sml_nvalues[j], &new->a_nvals[i] );
+ }
+ j++;
+ }
+ BER_BVZERO( &mod->sml_values[j] );
+ if ( mod->sml_nvalues ) {
+ BER_BVZERO( &mod->sml_nvalues[j] );
+ }
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ i = j;
+ }
+ op->o_tmpfree( adds, op->o_tmpmemctx );
+ } else {
+ /* new attr, just use the new mod */
+ i = 0;
+ j = 1;
+ }
+ /* advance to next element */
+ mod = **mcur;
+ if ( mod ) {
+ if ( i != j ) {
+ **mcur = mod->sml_next;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ } else {
+ *mcur = &mod->sml_next;
+ }
+ }
+ *mret = modtail;
+}
+
+/* Generate a set of modifications to change the old entry into the
+ * new one. On input ml is a list of modifications equivalent to
+ * the new entry. It will be massaged and the result will be stored
+ * in mods.
+ */
+void syncrepl_diff_entry( Operation *op, Attribute *old, Attribute *new,
+ Modifications **mods, Modifications **ml, int is_ctx)
+{
+ Modifications **modtail = mods;
+
+ /* We assume that attributes are saved in the same order
+ * in the remote and local databases. So if we walk through
+ * the attributeDescriptions one by one they should match in
+ * lock step. If not, look for an add or delete.
+ */
+ while ( old && new )
+ {
+ /* If we've seen this before, use its mod now */
+ if ( new->a_flags & SLAP_ATTR_IXADD ) {
+ attr_cmp( op, NULL, new, &modtail, &ml );
+ new = new->a_next;
+ continue;
+ }
+ /* Skip contextCSN */
+ if ( is_ctx && old->a_desc ==
+ slap_schema.si_ad_contextCSN ) {
+ old = old->a_next;
+ continue;
+ }
+
+ if ( old->a_desc != new->a_desc ) {
+ Modifications *mod;
+ Attribute *tmp;
+
+ /* If it's just been re-added later,
+ * remember that we've seen it.
+ */
+ tmp = attr_find( new, old->a_desc );
+ if ( tmp ) {
+ tmp->a_flags |= SLAP_ATTR_IXADD;
+ } else {
+ /* If it's a new attribute, pull it in.
+ */
+ tmp = attr_find( old, new->a_desc );
+ if ( !tmp ) {
+ attr_cmp( op, NULL, new, &modtail, &ml );
+ new = new->a_next;
+ continue;
+ }
+ /* Delete old attr */
+ mod = ch_malloc( sizeof( Modifications ) );
+ mod->sml_op = LDAP_MOD_DELETE;
+ mod->sml_flags = 0;
+ mod->sml_desc = old->a_desc;
+ mod->sml_type = mod->sml_desc->ad_cname;
+ mod->sml_numvals = 0;
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ old = old->a_next;
+ continue;
+ }
+ /* kludge - always update modifiersName so that it
+ * stays co-located with the other mod opattrs. But only
+ * if we know there are other valid mods.
+ */
+ if ( *mods && ( old->a_desc == slap_schema.si_ad_modifiersName ||
+ old->a_desc == slap_schema.si_ad_modifyTimestamp ))
+ attr_cmp( op, NULL, new, &modtail, &ml );
+ else
+ attr_cmp( op, old, new, &modtail, &ml );
+ new = new->a_next;
+ old = old->a_next;
+ }
+
+ /* These are all missing from provider */
+ while ( old ) {
+ Modifications *mod = ch_malloc( sizeof( Modifications ) );
+
+ mod->sml_op = LDAP_MOD_DELETE;
+ mod->sml_flags = 0;
+ mod->sml_desc = old->a_desc;
+ mod->sml_type = mod->sml_desc->ad_cname;
+ mod->sml_numvals = 0;
+ mod->sml_values = NULL;
+ mod->sml_nvalues = NULL;
+
+ *modtail = mod;
+ modtail = &mod->sml_next;
+
+ old = old->a_next;
+ }
+
+ /* Newly added attributes */
+ while ( new ) {
+ attr_cmp( op, NULL, new, &modtail, &ml );
+
+ new = new->a_next;
+ }
+
+ *modtail = *ml;
+ *ml = NULL;
+}
+
+/* shallow copy attrs, excluding non-replicated attrs */
+static Attribute *
+attrs_exdup( Operation *op, dninfo *dni, Attribute *attrs )
+{
+ int i;
+ Attribute *tmp, *anew;
+
+ if ( attrs == NULL ) return NULL;
+
+ /* count attrs */
+ for ( tmp = attrs,i=0; tmp; tmp=tmp->a_next ) i++;
+
+ anew = op->o_tmpalloc( i * sizeof(Attribute), op->o_tmpmemctx );
+ for ( tmp = anew; attrs; attrs=attrs->a_next ) {
+ int flag = is_at_operational( attrs->a_desc->ad_type ) ? dni->si->si_allopattrs :
+ dni->si->si_allattrs;
+ if ( !flag && !ad_inlist( attrs->a_desc, dni->si->si_anlist ))
+ continue;
+ if ( dni->si->si_exattrs && ad_inlist( attrs->a_desc, dni->si->si_exanlist ))
+ continue;
+ *tmp = *attrs;
+ tmp->a_next = tmp+1;
+ tmp++;
+ }
+ if ( tmp == anew ) {
+ /* excluded everything */
+ op->o_tmpfree( anew, op->o_tmpmemctx );
+ return NULL;
+ }
+ tmp[-1].a_next = NULL;
+ return anew;
+}
+
+static int
+dn_callback(
+ Operation* op,
+ SlapReply* rs )
+{
+ dninfo *dni = op->o_callback->sc_private;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ if ( !BER_BVISNULL( &dni->dn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dn_callback : consistency error - "
+ "entryUUID is not unique\n" );
+ } else {
+ ber_dupbv_x( &dni->dn, &rs->sr_entry->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &dni->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ /* If there is a new entry, see if it differs from the old.
+ * We compare the non-normalized values so that cosmetic changes
+ * in the provider are always propagated.
+ */
+ if ( dni->new_entry ) {
+ Attribute *old, *new;
+ struct berval old_rdn, new_rdn;
+ struct berval old_p, new_p;
+ int is_ctx, new_sup = 0;
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( dni->syncstate != MSAD_DIRSYNC_MODIFY )
+#endif
+ {
+ /* If old entry is not a glue entry, make sure new entry
+ * is actually newer than old entry
+ */
+ if ( !is_entry_glue( rs->sr_entry )) {
+ old = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryCSN );
+ new = attr_find( dni->new_entry->e_attrs,
+ slap_schema.si_ad_entryCSN );
+ if ( new && old ) {
+ int rc;
+ ber_len_t len = old->a_vals[0].bv_len;
+ if ( len > new->a_vals[0].bv_len )
+ len = new->a_vals[0].bv_len;
+ rc = memcmp( old->a_vals[0].bv_val,
+ new->a_vals[0].bv_val, len );
+ if ( rc > 0 ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "dn_callback : new entry is older than ours "
+ "%s ours %s, new %s\n",
+ rs->sr_entry->e_name.bv_val,
+ old->a_vals[0].bv_val,
+ new->a_vals[0].bv_val );
+ return LDAP_SUCCESS;
+ } else if ( rc == 0 ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "dn_callback : entries have identical CSN "
+ "%s %s\n",
+ rs->sr_entry->e_name.bv_val,
+ old->a_vals[0].bv_val );
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ is_ctx = dn_match( &rs->sr_entry->e_nname,
+ &op->o_bd->be_nsuffix[0] );
+ }
+
+ /* Did the DN change?
+ * case changes in the parent are ignored,
+ * we only want to know if the RDN was
+ * actually changed.
+ */
+ dnRdn( &rs->sr_entry->e_name, &old_rdn );
+ dnRdn( &dni->new_entry->e_name, &new_rdn );
+ dnParent( &rs->sr_entry->e_nname, &old_p );
+ dnParent( &dni->new_entry->e_nname, &new_p );
+
+ new_sup = !dn_match( &old_p, &new_p );
+ if ( !dn_match( &old_rdn, &new_rdn ) || new_sup )
+ {
+ struct berval oldRDN, oldVal;
+ AttributeDescription *ad = NULL;
+ int oldpos, newpos;
+ Attribute *a;
+
+ dni->renamed = 1;
+ if ( new_sup )
+ dni->nnewSup = new_p;
+
+ /* See if the oldRDN was deleted */
+ dnRdn( &rs->sr_entry->e_nname, &oldRDN );
+ oldVal.bv_val = strchr(oldRDN.bv_val, '=') + 1;
+ oldVal.bv_len = oldRDN.bv_len - ( oldVal.bv_val -
+ oldRDN.bv_val );
+ oldRDN.bv_len -= oldVal.bv_len + 1;
+ slap_bv2ad( &oldRDN, &ad, &rs->sr_text );
+ dni->oldDesc = ad;
+ for ( oldpos=0, a=rs->sr_entry->e_attrs;
+ a && a->a_desc != ad; oldpos++, a=a->a_next );
+ /* a should not be NULL but apparently it happens.
+ * ITS#7144
+ */
+ if ( a ) {
+ dni->oldNcount = a->a_numvals;
+ for ( newpos=0, a=dni->new_entry->e_attrs;
+ a && a->a_desc != ad; newpos++, a=a->a_next );
+ if ( !a || oldpos != newpos || attr_valfind( a,
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_VALUE_OF_SYNTAX,
+ &oldVal, NULL, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ {
+ dni->delOldRDN = 1;
+ }
+ }
+ /* Get the newRDN's desc */
+ dnRdn( &dni->new_entry->e_nname, &oldRDN );
+ oldVal.bv_val = strchr(oldRDN.bv_val, '=');
+ oldRDN.bv_len = oldVal.bv_val - oldRDN.bv_val;
+ ad = NULL;
+ slap_bv2ad( &oldRDN, &ad, &rs->sr_text );
+ dni->newDesc = ad;
+
+ /* A ModDN has happened, but in Refresh mode other
+ * changes may have occurred before we picked it up.
+ * So fallthru to regular Modify processing.
+ */
+ }
+
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( dni->syncstate == MSAD_DIRSYNC_MODIFY ) {
+ /* DirSync actually sends a diff already, mostly.
+ * It has no way to indicate deletion of single-valued attrs.
+ * FIXME: should do an auxiliary search to get the true
+ * entry contents.
+ */
+ dni->mods = *dni->modlist;
+ *dni->modlist = NULL;
+ } else
+#endif
+ {
+ Attribute *old = attrs_exdup( op, dni, rs->sr_entry->e_attrs );
+ syncrepl_diff_entry( op, old,
+ dni->new_entry->e_attrs, &dni->mods, dni->modlist,
+ is_ctx );
+ op->o_tmpfree( old, op->o_tmpmemctx );
+ }
+ }
+ }
+ } else if ( rs->sr_type == REP_RESULT ) {
+ if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dn_callback : consistency error - "
+ "entryUUID is not unique\n" );
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+nonpresent_callback(
+ Operation* op,
+ SlapReply* rs )
+{
+ syncinfo_t *si = op->o_callback->sc_private;
+ Attribute *a;
+ int count = 0;
+ char *present_uuid = NULL;
+ struct nonpresent_entry *np_entry;
+ struct sync_cookie *syncCookie = op->o_controls[slap_cids.sc_LDAPsync];
+
+ if ( rs->sr_type == REP_RESULT ) {
+ count = presentlist_free( si->si_presentlist );
+ si->si_presentlist = NULL;
+ Debug( LDAP_DEBUG_SYNC, "nonpresent_callback: %s "
+ "had %d items left in the list\n", si->si_ridtxt, count );
+
+ } else if ( rs->sr_type == REP_SEARCH ) {
+ if ( !( si->si_refreshDelete & NP_DELETE_ONE ) ) {
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID );
+
+ if ( a ) {
+ present_uuid = presentlist_find( si->si_presentlist, &a->a_nvals[0] );
+ }
+
+ Debug(LDAP_DEBUG_SYNC, "nonpresent_callback: "
+ "%s %spresent UUID %s, dn %s\n",
+ si->si_ridtxt,
+ present_uuid ? "" : "non",
+ a ? a->a_vals[0].bv_val : "<missing>",
+ rs->sr_entry->e_name.bv_val );
+
+ if ( a == NULL ) return 0;
+ }
+
+ if ( is_entry_glue( rs->sr_entry ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( present_uuid == NULL ) {
+ int covered = 1; /* covered by our new contextCSN? */
+
+ if ( !syncCookie )
+ syncCookie = &si->si_syncCookie;
+
+ /* TODO: This can go once we can build a filter that takes care of
+ * the check for us */
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ int i, sid = slap_parse_csn_sid( &a->a_nvals[0] );
+ if ( sid != -1 ) {
+ covered = 0;
+ for ( i=0; i < syncCookie->numcsns && syncCookie->sids[i] <= sid; i++ ) {
+ if ( syncCookie->sids[i] == sid &&
+ ber_bvcmp( &a->a_nvals[0], &syncCookie->ctxcsn[i] ) <= 0 ) {
+ covered = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( covered ) {
+ np_entry = (struct nonpresent_entry *)
+ ch_calloc( 1, sizeof( struct nonpresent_entry ) );
+ np_entry->npe_name = ber_dupbv( NULL, &rs->sr_entry->e_name );
+ np_entry->npe_nname = ber_dupbv( NULL, &rs->sr_entry->e_nname );
+ LDAP_LIST_INSERT_HEAD( &si->si_nonpresentlist, np_entry, npe_link );
+ Debug( LDAP_DEBUG_SYNC, "nonpresent_callback: %s "
+ "adding entry %s to non-present list\n",
+ si->si_ridtxt, np_entry->npe_name->bv_val );
+ }
+
+ } else {
+ presentlist_delete( &si->si_presentlist, &a->a_nvals[0] );
+ ch_free( present_uuid );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+static struct berval *
+slap_uuidstr_from_normalized(
+ struct berval* uuidstr,
+ struct berval* normalized,
+ void *ctx )
+{
+#if 0
+ struct berval *new;
+ unsigned char nibble;
+ int i, d = 0;
+
+ if ( normalized == NULL ) return NULL;
+ if ( normalized->bv_len != 16 ) return NULL;
+
+ if ( uuidstr ) {
+ new = uuidstr;
+ } else {
+ new = (struct berval *)slap_sl_malloc( sizeof(struct berval), ctx );
+ if ( new == NULL ) {
+ return NULL;
+ }
+ }
+
+ new->bv_len = 36;
+
+ if ( ( new->bv_val = slap_sl_malloc( new->bv_len + 1, ctx ) ) == NULL ) {
+ if ( new != uuidstr ) {
+ slap_sl_free( new, ctx );
+ }
+ return NULL;
+ }
+
+ for ( i = 0; i < 16; i++ ) {
+ if ( i == 4 || i == 6 || i == 8 || i == 10 ) {
+ new->bv_val[(i<<1)+d] = '-';
+ d += 1;
+ }
+
+ nibble = (normalized->bv_val[i] >> 4) & 0xF;
+ if ( nibble < 10 ) {
+ new->bv_val[(i<<1)+d] = nibble + '0';
+ } else {
+ new->bv_val[(i<<1)+d] = nibble - 10 + 'a';
+ }
+
+ nibble = (normalized->bv_val[i]) & 0xF;
+ if ( nibble < 10 ) {
+ new->bv_val[(i<<1)+d+1] = nibble + '0';
+ } else {
+ new->bv_val[(i<<1)+d+1] = nibble - 10 + 'a';
+ }
+ }
+
+ new->bv_val[new->bv_len] = '\0';
+ return new;
+#endif
+
+ struct berval *new;
+ int rc = 0;
+
+ if ( normalized == NULL ) return NULL;
+ if ( normalized->bv_len != 16 ) return NULL;
+
+ if ( uuidstr ) {
+ new = uuidstr;
+
+ } else {
+ new = (struct berval *)slap_sl_malloc( sizeof(struct berval), ctx );
+ if ( new == NULL ) {
+ return NULL;
+ }
+ }
+
+ new->bv_len = 36;
+
+ if ( ( new->bv_val = slap_sl_malloc( new->bv_len + 1, ctx ) ) == NULL ) {
+ rc = 1;
+ goto done;
+ }
+
+ rc = lutil_uuidstr_from_normalized( normalized->bv_val,
+ normalized->bv_len, new->bv_val, new->bv_len + 1 );
+
+done:;
+ if ( rc == -1 ) {
+ if ( new != NULL ) {
+ if ( new->bv_val != NULL ) {
+ slap_sl_free( new->bv_val, ctx );
+ }
+
+ if ( new != uuidstr ) {
+ slap_sl_free( new, ctx );
+ }
+ }
+ new = NULL;
+
+ } else {
+ new->bv_len = rc;
+ }
+
+ return new;
+}
+
+static int
+syncuuid_cmp( const void* v_uuid1, const void* v_uuid2 )
+{
+#ifdef HASHUUID
+ return ( memcmp( v_uuid1, v_uuid2, UUIDLEN-2 ));
+#else
+ return ( memcmp( v_uuid1, v_uuid2, UUIDLEN ));
+#endif
+}
+
+void
+syncinfo_free( syncinfo_t *sie, int free_all )
+{
+ syncinfo_t *si_next;
+
+ Debug( LDAP_DEBUG_TRACE, "syncinfo_free: %s\n",
+ sie->si_ridtxt );
+
+ do {
+ si_next = sie->si_next;
+
+ if ( !BER_BVISEMPTY( &sie->si_monitor_ndn )) {
+ syncrepl_monitor_del( sie );
+ }
+ ch_free( sie->si_lastCookieSent.bv_val );
+ ch_free( sie->si_lastCookieRcvd.bv_val );
+
+ if ( sie->si_ld ) {
+ if ( sie->si_conn ) {
+ connection_client_stop( sie->si_conn );
+ sie->si_conn = NULL;
+ }
+ ldap_unbind_ext( sie->si_ld, NULL, NULL );
+ }
+
+ if ( sie->si_re ) {
+ struct re_s *re = sie->si_re;
+ sie->si_re = NULL;
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) )
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &sie->si_mutex );
+ ldap_pvt_thread_mutex_destroy( &sie->si_monitor_mutex );
+
+ bindconf_free( &sie->si_bindconf );
+
+ if ( sie->si_filterstr.bv_val ) {
+ ch_free( sie->si_filterstr.bv_val );
+ }
+ if ( sie->si_filter ) {
+ filter_free( sie->si_filter );
+ }
+ if ( sie->si_logfilterstr.bv_val ) {
+ ch_free( sie->si_logfilterstr.bv_val );
+ }
+ if ( sie->si_logfilter ) {
+ filter_free( sie->si_logfilter );
+ }
+ if ( sie->si_base.bv_val ) {
+ ch_free( sie->si_base.bv_val );
+ }
+ if ( sie->si_logbase.bv_val ) {
+ ch_free( sie->si_logbase.bv_val );
+ }
+ if ( sie->si_be && SLAP_SYNC_SUBENTRY( sie->si_be )) {
+ ch_free( sie->si_contextdn.bv_val );
+ }
+ if ( sie->si_attrs ) {
+ int i = 0;
+ while ( sie->si_attrs[i] != NULL ) {
+ ch_free( sie->si_attrs[i] );
+ i++;
+ }
+ ch_free( sie->si_attrs );
+ }
+ if ( sie->si_exattrs ) {
+ int i = 0;
+ while ( sie->si_exattrs[i] != NULL ) {
+ ch_free( sie->si_exattrs[i] );
+ i++;
+ }
+ ch_free( sie->si_exattrs );
+ }
+ if ( sie->si_anlist ) {
+ int i = 0;
+ while ( sie->si_anlist[i].an_name.bv_val != NULL ) {
+ ch_free( sie->si_anlist[i].an_name.bv_val );
+ i++;
+ }
+ ch_free( sie->si_anlist );
+ }
+ if ( sie->si_exanlist ) {
+ int i = 0;
+ while ( sie->si_exanlist[i].an_name.bv_val != NULL ) {
+ ch_free( sie->si_exanlist[i].an_name.bv_val );
+ i++;
+ }
+ ch_free( sie->si_exanlist );
+ }
+ if ( sie->si_retryinterval ) {
+ ch_free( sie->si_retryinterval );
+ }
+ if ( sie->si_retrynum ) {
+ ch_free( sie->si_retrynum );
+ }
+ if ( sie->si_retrynum_init ) {
+ ch_free( sie->si_retrynum_init );
+ }
+ slap_sync_cookie_free( &sie->si_syncCookie, 0 );
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( sie->si_dirSyncCookie.bv_val ) {
+ ch_free( sie->si_dirSyncCookie.bv_val );
+ }
+#endif
+ if ( sie->si_presentlist ) {
+ presentlist_free( sie->si_presentlist );
+ }
+ while ( !LDAP_LIST_EMPTY( &sie->si_nonpresentlist ) ) {
+ struct nonpresent_entry* npe;
+ npe = LDAP_LIST_FIRST( &sie->si_nonpresentlist );
+ LDAP_LIST_REMOVE( npe, npe_link );
+ if ( npe->npe_name ) {
+ if ( npe->npe_name->bv_val ) {
+ ch_free( npe->npe_name->bv_val );
+ }
+ ch_free( npe->npe_name );
+ }
+ if ( npe->npe_nname ) {
+ if ( npe->npe_nname->bv_val ) {
+ ch_free( npe->npe_nname->bv_val );
+ }
+ ch_free( npe->npe_nname );
+ }
+ ch_free( npe );
+ }
+ if ( sie->si_cookieState ) {
+ /* Could be called from do_syncrepl (server unpaused) */
+ if ( !free_all ) refresh_finished( sie );
+
+ sie->si_cookieState->cs_ref--;
+ if ( !sie->si_cookieState->cs_ref ) {
+ ch_free( sie->si_cookieState->cs_sids );
+ ber_bvarray_free( sie->si_cookieState->cs_vals );
+ ldap_pvt_thread_cond_destroy( &sie->si_cookieState->cs_cond );
+ ldap_pvt_thread_mutex_destroy( &sie->si_cookieState->cs_mutex );
+ ch_free( sie->si_cookieState->cs_psids );
+ ber_bvarray_free( sie->si_cookieState->cs_pvals );
+ ldap_pvt_thread_mutex_destroy( &sie->si_cookieState->cs_pmutex );
+ ldap_pvt_thread_mutex_destroy( &sie->si_cookieState->cs_refresh_mutex );
+ assert( sie->si_cookieState->cs_refreshing == NULL );
+ ch_free( sie->si_cookieState );
+ }
+ }
+ if ( sie->si_rewrite )
+ rewrite_info_delete( &sie->si_rewrite );
+ if ( sie->si_suffixm.bv_val )
+ ch_free( sie->si_suffixm.bv_val );
+ ch_free( sie );
+ sie = si_next;
+ } while ( free_all && si_next );
+}
+
+static int
+config_suffixm( ConfigArgs *c, syncinfo_t *si )
+{
+ char *argvEngine[] = { "rewriteEngine", "on", NULL };
+ char *argvContext[] = { "rewriteContext", SUFFIXM_CTX, NULL };
+ char *argvRule[] = { "rewriteRule", NULL, NULL, ":", NULL };
+ char *vnc, *rnc;
+ int rc;
+
+ if ( si->si_rewrite )
+ rewrite_info_delete( &si->si_rewrite );
+ si->si_rewrite = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+
+ rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 2, argvEngine );
+ if ( rc != LDAP_SUCCESS )
+ return rc;
+
+ rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 2, argvContext );
+ if ( rc != LDAP_SUCCESS )
+ return rc;
+
+ vnc = ch_malloc( si->si_base.bv_len + 6 );
+ strcpy( vnc, "(.*)" );
+ lutil_strcopy( lutil_strcopy( vnc+4, si->si_base.bv_val ), "$" );
+ argvRule[1] = vnc;
+
+ rnc = ch_malloc( si->si_suffixm.bv_len + 3 );
+ strcpy( rnc, "%1" );
+ strcpy( rnc+2, si->si_suffixm.bv_val );
+ argvRule[2] = rnc;
+
+ rc = rewrite_parse( si->si_rewrite, c->fname, c->lineno, 4, argvRule );
+ ch_free( vnc );
+ ch_free( rnc );
+ return rc;
+}
+
+/* NOTE: used & documented in slapd.conf(5) */
+#define IDSTR "rid"
+#define PROVIDERSTR "provider"
+#define SCHEMASTR "schemachecking"
+#define FILTERSTR "filter"
+#define SEARCHBASESTR "searchbase"
+#define SCOPESTR "scope"
+#define ATTRSONLYSTR "attrsonly"
+#define ATTRSSTR "attrs"
+#define TYPESTR "type"
+#define INTERVALSTR "interval"
+#define RETRYSTR "retry"
+#define SLIMITSTR "sizelimit"
+#define TLIMITSTR "timelimit"
+#define SYNCDATASTR "syncdata"
+#define LOGBASESTR "logbase"
+#define LOGFILTERSTR "logfilter"
+#define SUFFIXMSTR "suffixmassage"
+#define STRICT_REFRESH "strictrefresh"
+#define LAZY_COMMIT "lazycommit"
+
+/* FIXME: undocumented */
+#define EXATTRSSTR "exattrs"
+#define MANAGEDSAITSTR "manageDSAit"
+
+/* mandatory */
+enum {
+ GOT_RID = 0x00000001U,
+ GOT_PROVIDER = 0x00000002U,
+ GOT_SCHEMACHECKING = 0x00000004U,
+ GOT_FILTER = 0x00000008U,
+ GOT_SEARCHBASE = 0x00000010U,
+ GOT_SCOPE = 0x00000020U,
+ GOT_ATTRSONLY = 0x00000040U,
+ GOT_ATTRS = 0x00000080U,
+ GOT_TYPE = 0x00000100U,
+ GOT_INTERVAL = 0x00000200U,
+ GOT_RETRY = 0x00000400U,
+ GOT_SLIMIT = 0x00000800U,
+ GOT_TLIMIT = 0x00001000U,
+ GOT_SYNCDATA = 0x00002000U,
+ GOT_LOGBASE = 0x00004000U,
+ GOT_LOGFILTER = 0x00008000U,
+ GOT_EXATTRS = 0x00010000U,
+ GOT_MANAGEDSAIT = 0x00020000U,
+ GOT_BINDCONF = 0x00040000U,
+ GOT_SUFFIXM = 0x00080000U,
+
+/* check */
+ GOT_REQUIRED = (GOT_RID|GOT_PROVIDER|GOT_SEARCHBASE)
+};
+
+static slap_verbmasks datamodes[] = {
+ { BER_BVC("default"), SYNCDATA_DEFAULT },
+ { BER_BVC("accesslog"), SYNCDATA_ACCESSLOG },
+ { BER_BVC("changelog"), SYNCDATA_CHANGELOG },
+ { BER_BVNULL, 0 }
+};
+
+static int
+parse_syncrepl_retry(
+ ConfigArgs *c,
+ char *arg,
+ syncinfo_t *si )
+{
+ char **retry_list;
+ int j, k, n;
+ int use_default = 0;
+
+ char *val = arg + STRLENOF( RETRYSTR "=" );
+ if ( strcasecmp( val, "undefined" ) == 0 ) {
+ val = "3600 +";
+ use_default = 1;
+ }
+
+ retry_list = (char **) ch_calloc( 1, sizeof( char * ) );
+ retry_list[0] = NULL;
+
+ slap_str2clist( &retry_list, val, " ,\t" );
+
+ for ( k = 0; retry_list && retry_list[k]; k++ ) ;
+ n = k / 2;
+ if ( k % 2 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: incomplete syncrepl retry list" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ for ( k = 0; retry_list && retry_list[k]; k++ ) {
+ ch_free( retry_list[k] );
+ }
+ ch_free( retry_list );
+ return 1;
+ }
+ si->si_retryinterval = (time_t *) ch_calloc( n + 1, sizeof( time_t ) );
+ si->si_retrynum = (int *) ch_calloc( n + 1, sizeof( int ) );
+ si->si_retrynum_init = (int *) ch_calloc( n + 1, sizeof( int ) );
+ for ( j = 0; j < n; j++ ) {
+ unsigned long t;
+ if ( lutil_atoul( &t, retry_list[j*2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: invalid retry interval \"%s\" (#%d)",
+ retry_list[j*2], j );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ /* do some cleanup */
+ return 1;
+ }
+ si->si_retryinterval[j] = (time_t)t;
+ if ( *retry_list[j*2+1] == '+' ) {
+ si->si_retrynum_init[j] = RETRYNUM_FOREVER;
+ si->si_retrynum[j] = RETRYNUM_FOREVER;
+ j++;
+ break;
+ } else {
+ if ( lutil_atoi( &si->si_retrynum_init[j], retry_list[j*2+1] ) != 0
+ || si->si_retrynum_init[j] <= 0 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: invalid initial retry number \"%s\" (#%d)",
+ retry_list[j*2+1], j );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ /* do some cleanup */
+ return 1;
+ }
+ if ( lutil_atoi( &si->si_retrynum[j], retry_list[j*2+1] ) != 0
+ || si->si_retrynum[j] <= 0 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: invalid retry number \"%s\" (#%d)",
+ retry_list[j*2+1], j );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ /* do some cleanup */
+ return 1;
+ }
+ }
+ }
+ if ( j < 1 || si->si_retrynum_init[j-1] != RETRYNUM_FOREVER ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: syncrepl will eventually stop retrying; the \"retry\" parameter should end with a '+'.\n",
+ c->log );
+ }
+
+ si->si_retrynum_init[j] = RETRYNUM_TAIL;
+ si->si_retrynum[j] = RETRYNUM_TAIL;
+ si->si_retryinterval[j] = 0;
+
+ for ( k = 0; retry_list && retry_list[k]; k++ ) {
+ ch_free( retry_list[k] );
+ }
+ ch_free( retry_list );
+ if ( !use_default ) {
+ si->si_got |= GOT_RETRY;
+ }
+
+ return 0;
+}
+
+static int
+parse_syncrepl_line(
+ ConfigArgs *c,
+ syncinfo_t *si )
+{
+ int i;
+ char *val;
+
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( !strncasecmp( c->argv[ i ], IDSTR "=",
+ STRLENOF( IDSTR "=" ) ) )
+ {
+ int tmp;
+ /* '\0' string terminator accounts for '=' */
+ val = c->argv[ i ] + STRLENOF( IDSTR "=" );
+ if ( lutil_atoi( &tmp, val ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "unable to parse syncrepl id \"%s\"", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ if ( tmp > SLAP_SYNC_RID_MAX || tmp < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "syncrepl id %d is out of range [0..%d]", tmp, SLAP_SYNC_RID_MAX );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_rid = tmp;
+ sprintf( si->si_ridtxt, IDSTR "=%03d", si->si_rid );
+ si->si_got |= GOT_RID;
+ } else if ( !strncasecmp( c->argv[ i ], PROVIDERSTR "=",
+ STRLENOF( PROVIDERSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( PROVIDERSTR "=" );
+ ber_str2bv( val, 0, 1, &si->si_bindconf.sb_uri );
+#ifdef HAVE_TLS
+ if ( ldap_is_ldaps_url( val ))
+ si->si_bindconf.sb_tls_do_init = 1;
+#endif
+ si->si_got |= GOT_PROVIDER;
+ } else if ( !strncasecmp( c->argv[ i ], SCHEMASTR "=",
+ STRLENOF( SCHEMASTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( SCHEMASTR "=" );
+ if ( !strncasecmp( val, "on", STRLENOF( "on" ) ) ) {
+ si->si_schemachecking = 1;
+ } else if ( !strncasecmp( val, "off", STRLENOF( "off" ) ) ) {
+ si->si_schemachecking = 0;
+ } else {
+ si->si_schemachecking = 1;
+ }
+ si->si_got |= GOT_SCHEMACHECKING;
+ } else if ( !strncasecmp( c->argv[ i ], FILTERSTR "=",
+ STRLENOF( FILTERSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( FILTERSTR "=" );
+ if ( si->si_filterstr.bv_val )
+ ch_free( si->si_filterstr.bv_val );
+ ber_str2bv( val, 0, 1, &si->si_filterstr );
+ si->si_got |= GOT_FILTER;
+ } else if ( !strncasecmp( c->argv[ i ], LOGFILTERSTR "=",
+ STRLENOF( LOGFILTERSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( LOGFILTERSTR "=" );
+ if ( si->si_logfilterstr.bv_val )
+ ch_free( si->si_logfilterstr.bv_val );
+ ber_str2bv( val, 0, 1, &si->si_logfilterstr );
+ si->si_got |= GOT_LOGFILTER;
+ } else if ( !strncasecmp( c->argv[ i ], SEARCHBASESTR "=",
+ STRLENOF( SEARCHBASESTR "=" ) ) )
+ {
+ struct berval bv;
+ int rc;
+
+ val = c->argv[ i ] + STRLENOF( SEARCHBASESTR "=" );
+ if ( si->si_base.bv_val ) {
+ ch_free( si->si_base.bv_val );
+ }
+ ber_str2bv( val, 0, 0, &bv );
+ rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_base, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Invalid base DN \"%s\": %d (%s)",
+ val, rc, ldap_err2string( rc ) );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_got |= GOT_SEARCHBASE;
+ } else if ( !strncasecmp( c->argv[ i ], SUFFIXMSTR "=",
+ STRLENOF( SUFFIXMSTR "=" ) ) )
+ {
+ struct berval bv;
+ int rc;
+
+ val = c->argv[ i ] + STRLENOF( SUFFIXMSTR "=" );
+ if ( si->si_suffixm.bv_val ) {
+ ch_free( si->si_suffixm.bv_val );
+ }
+ ber_str2bv( val, 0, 0, &bv );
+ rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_suffixm, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Invalid massage DN \"%s\": %d (%s)",
+ val, rc, ldap_err2string( rc ) );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ if ( !be_issubordinate( c->be, &si->si_suffixm )) {
+ ch_free( si->si_suffixm.bv_val );
+ BER_BVZERO( &si->si_suffixm );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Massage DN \"%s\" is not within the database naming context",
+ val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_got |= GOT_SUFFIXM;
+ } else if ( !strncasecmp( c->argv[ i ], LOGBASESTR "=",
+ STRLENOF( LOGBASESTR "=" ) ) )
+ {
+ struct berval bv;
+ int rc;
+
+ val = c->argv[ i ] + STRLENOF( LOGBASESTR "=" );
+ if ( si->si_logbase.bv_val ) {
+ ch_free( si->si_logbase.bv_val );
+ }
+ ber_str2bv( val, 0, 0, &bv );
+ rc = dnNormalize( 0, NULL, NULL, &bv, &si->si_logbase, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Invalid logbase DN \"%s\": %d (%s)",
+ val, rc, ldap_err2string( rc ) );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_got |= GOT_LOGBASE;
+ } else if ( !strncasecmp( c->argv[ i ], SCOPESTR "=",
+ STRLENOF( SCOPESTR "=" ) ) )
+ {
+ int j;
+ val = c->argv[ i ] + STRLENOF( SCOPESTR "=" );
+ j = ldap_pvt_str2scope( val );
+ if ( j < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "unknown scope \"%s\"", val);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_scope = j;
+ si->si_got |= GOT_SCOPE;
+ } else if ( !strncasecmp( c->argv[ i ], ATTRSONLYSTR,
+ STRLENOF( ATTRSONLYSTR ) ) )
+ {
+ si->si_attrsonly = 1;
+ si->si_got |= GOT_ATTRSONLY;
+ } else if ( !strncasecmp( c->argv[ i ], ATTRSSTR "=",
+ STRLENOF( ATTRSSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( ATTRSSTR "=" );
+ if ( !strncasecmp( val, ":include:", STRLENOF(":include:") ) ) {
+ char *attr_fname;
+ attr_fname = ch_strdup( val + STRLENOF(":include:") );
+ si->si_anlist = file2anlist( si->si_anlist, attr_fname, " ,\t" );
+ if ( si->si_anlist == NULL ) {
+ ch_free( attr_fname );
+ return -1;
+ }
+ si->si_anfile = attr_fname;
+ } else {
+ char *str, *s, *next;
+ const char *delimstr = " ,\t";
+ str = ch_strdup( val );
+ for ( s = ldap_pvt_strtok( str, delimstr, &next );
+ s != NULL;
+ s = ldap_pvt_strtok( NULL, delimstr, &next ) )
+ {
+ if ( strlen(s) == 1 && *s == '*' ) {
+ si->si_allattrs = 1;
+ val[ s - str ] = delimstr[0];
+ }
+ if ( strlen(s) == 1 && *s == '+' ) {
+ si->si_allopattrs = 1;
+ val [ s - str ] = delimstr[0];
+ }
+ }
+ ch_free( str );
+ si->si_anlist = str2anlist( si->si_anlist, val, " ,\t" );
+ if ( si->si_anlist == NULL ) {
+ return -1;
+ }
+ }
+ si->si_got |= GOT_ATTRS;
+ } else if ( !strncasecmp( c->argv[ i ], EXATTRSSTR "=",
+ STRLENOF( EXATTRSSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( EXATTRSSTR "=" );
+ if ( !strncasecmp( val, ":include:", STRLENOF(":include:") ) ) {
+ char *attr_fname;
+ attr_fname = ch_strdup( val + STRLENOF(":include:") );
+ si->si_exanlist = file2anlist(
+ si->si_exanlist, attr_fname, " ,\t" );
+ if ( si->si_exanlist == NULL ) {
+ ch_free( attr_fname );
+ return -1;
+ }
+ ch_free( attr_fname );
+ } else {
+ si->si_exanlist = str2anlist( si->si_exanlist, val, " ,\t" );
+ if ( si->si_exanlist == NULL ) {
+ return -1;
+ }
+ }
+ si->si_got |= GOT_EXATTRS;
+ } else if ( !strncasecmp( c->argv[ i ], TYPESTR "=",
+ STRLENOF( TYPESTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( TYPESTR "=" );
+ if ( !strncasecmp( val, "refreshOnly",
+ STRLENOF("refreshOnly") ) )
+ {
+ si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_ONLY;
+ } else if ( !strncasecmp( val, "refreshAndPersist",
+ STRLENOF("refreshAndPersist") ) )
+ {
+ si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_AND_PERSIST;
+ si->si_interval = 60;
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ } else if ( !strncasecmp( val, "dirSync",
+ STRLENOF("dirSync") ) )
+ {
+ if ( sy_ad_objectGUID == NULL && syncrepl_dirsync_schema()) {
+ sprintf( c->cr_msg, "Error: dirSync schema is missing" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ /* MS DirSync is refreshOnly, no persist */
+ si->si_type = si->si_ctype = MSAD_DIRSYNC;
+#endif
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "unknown sync type \"%s\"", val);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_got |= GOT_TYPE;
+ } else if ( !strncasecmp( c->argv[ i ], INTERVALSTR "=",
+ STRLENOF( INTERVALSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( INTERVALSTR "=" );
+ if ( si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ) {
+ si->si_interval = 0;
+ } else if ( strchr( val, ':' ) != NULL ) {
+ char *next, *ptr = val;
+ int dd, hh, mm, ss;
+
+ dd = strtol( ptr, &next, 10 );
+ if ( next == ptr || next[0] != ':' || dd < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%s\", unable to parse days", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ ptr = next + 1;
+ hh = strtol( ptr, &next, 10 );
+ if ( next == ptr || next[0] != ':' || hh < 0 || hh > 24 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%s\", unable to parse hours", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ ptr = next + 1;
+ mm = strtol( ptr, &next, 10 );
+ if ( next == ptr || next[0] != ':' || mm < 0 || mm > 60 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%s\", unable to parse minutes", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ ptr = next + 1;
+ ss = strtol( ptr, &next, 10 );
+ if ( next == ptr || next[0] != '\0' || ss < 0 || ss > 60 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%s\", unable to parse seconds", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_interval = (( dd * 24 + hh ) * 60 + mm ) * 60 + ss;
+ } else {
+ unsigned long t;
+
+ if ( lutil_parse_time( val, &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%s\"", val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_interval = (time_t)t;
+ }
+ if ( si->si_interval < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "invalid interval \"%ld\"",
+ (long) si->si_interval);
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ si->si_got |= GOT_INTERVAL;
+ } else if ( !strncasecmp( c->argv[ i ], RETRYSTR "=",
+ STRLENOF( RETRYSTR "=" ) ) )
+ {
+ if ( parse_syncrepl_retry( c, c->argv[ i ], si ) ) {
+ return 1;
+ }
+ } else if ( !strncasecmp( c->argv[ i ], MANAGEDSAITSTR "=",
+ STRLENOF( MANAGEDSAITSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( MANAGEDSAITSTR "=" );
+ if ( lutil_atoi( &si->si_manageDSAit, val ) != 0
+ || si->si_manageDSAit < 0 || si->si_manageDSAit > 1 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid manageDSAit value \"%s\".\n",
+ val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ si->si_got |= GOT_MANAGEDSAIT;
+ } else if ( !strncasecmp( c->argv[ i ], SLIMITSTR "=",
+ STRLENOF( SLIMITSTR "=") ) )
+ {
+ val = c->argv[ i ] + STRLENOF( SLIMITSTR "=" );
+ if ( strcasecmp( val, "unlimited" ) == 0 ) {
+ si->si_slimit = 0;
+
+ } else if ( lutil_atoi( &si->si_slimit, val ) != 0 || si->si_slimit < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid size limit value \"%s\".\n",
+ val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ si->si_got |= GOT_SLIMIT;
+ } else if ( !strncasecmp( c->argv[ i ], TLIMITSTR "=",
+ STRLENOF( TLIMITSTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( TLIMITSTR "=" );
+ if ( strcasecmp( val, "unlimited" ) == 0 ) {
+ si->si_tlimit = 0;
+
+ } else if ( lutil_atoi( &si->si_tlimit, val ) != 0 || si->si_tlimit < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid time limit value \"%s\".\n",
+ val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ si->si_got |= GOT_TLIMIT;
+ } else if ( !strncasecmp( c->argv[ i ], SYNCDATASTR "=",
+ STRLENOF( SYNCDATASTR "=" ) ) )
+ {
+ val = c->argv[ i ] + STRLENOF( SYNCDATASTR "=" );
+ si->si_syncdata = verb_to_mask( val, datamodes );
+ si->si_got |= GOT_SYNCDATA;
+ if ( si->si_syncdata == SYNCDATA_CHANGELOG ) {
+ if ( sy_ad_nsUniqueId == NULL ) {
+ int rc = syncrepl_dsee_schema();
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "changelog schema problem (%d)\n", rc );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+ } else if ( !strncasecmp( c->argv[ i ], STRICT_REFRESH,
+ STRLENOF( STRICT_REFRESH ) ) )
+ {
+ si->si_strict_refresh = 1;
+ } else if ( !strncasecmp( c->argv[ i ], LAZY_COMMIT,
+ STRLENOF( LAZY_COMMIT ) ) )
+ {
+ si->si_lazyCommit = 1;
+ } else if ( !bindconf_parse( c->argv[i], &si->si_bindconf ) ) {
+ si->si_got |= GOT_BINDCONF;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: parse_syncrepl_line: "
+ "unable to parse \"%s\"\n", c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ }
+
+ if ( ( si->si_got & GOT_REQUIRED ) != GOT_REQUIRED ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error: Malformed \"syncrepl\" line in slapd config file, missing%s%s%s",
+ si->si_got & GOT_RID ? "" : " "IDSTR,
+ si->si_got & GOT_PROVIDER ? "" : " "PROVIDERSTR,
+ si->si_got & GOT_SEARCHBASE ? "" : " "SEARCHBASESTR );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+
+ if ( !be_issubordinate( c->be, &si->si_base ) && !( si->si_got & GOT_SUFFIXM )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Base DN \"%s\" is not within the database naming context",
+ si->si_base.bv_val );
+ ch_free( si->si_base.bv_val );
+ BER_BVZERO( &si->si_base );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+
+ if ( si->si_got & GOT_SUFFIXM ) {
+ if (config_suffixm( c, si )) {
+ ch_free( si->si_suffixm.bv_val );
+ BER_BVZERO( &si->si_suffixm );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "Error configuring rewrite engine" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return -1;
+ }
+ }
+
+ if ( !( si->si_got & GOT_RETRY ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": no retry defined, using default\n",
+ si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)" );
+ if ( si->si_retryinterval == NULL ) {
+ if ( parse_syncrepl_retry( c, "retry=undefined", si ) ) {
+ return 1;
+ }
+ }
+ }
+
+ si->si_filter = str2filter( si->si_filterstr.bv_val );
+ if ( si->si_filter == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": unable to parse filter=\"%s\"\n",
+ si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)", si->si_filterstr.bv_val );
+ return 1;
+ }
+
+ if ( si->si_got & GOT_LOGFILTER ) {
+ si->si_logfilter = str2filter( si->si_logfilterstr.bv_val );
+ if ( si->si_logfilter == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl %s " SEARCHBASESTR "=\"%s\": unable to parse logfilter=\"%s\"\n",
+ si->si_ridtxt, c->be->be_suffix ? c->be->be_suffix[ 0 ].bv_val : "(null)", si->si_logfilterstr.bv_val );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* monitor entry contains:
+ provider URLs
+ timestamp of last contact
+ cookievals
+ */
+
+static ObjectClass *oc_olmSyncRepl;
+static AttributeDescription *ad_olmProviderURIList,
+ *ad_olmConnection, *ad_olmSyncPhase,
+ *ad_olmNextConnect, *ad_olmLastConnect, *ad_olmLastContact,
+ *ad_olmLastCookieRcvd, *ad_olmLastCookieSent;
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "olmSyncReplAttributes", "olmOverlayAttributes:1" },
+ { "olmSyncReplObjectClasses", "olmOverlayObjectClasses:1" },
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **ad;
+} s_at[] = {
+ { "( olmSyncReplAttributes:1 "
+ "NAME ( 'olmSRProviderURIList' ) "
+ "DESC 'List of provider URIs for this consumer instance' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmProviderURIList },
+ { "( olmSyncReplAttributes:2 "
+ "NAME ( 'olmSRConnection' ) "
+ "DESC 'Local address:port of connection to provider' "
+ "SUP monitoredInfo "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmConnection },
+ { "( olmSyncReplAttributes:3 "
+ "NAME ( 'olmSRSyncPhase' ) "
+ "DESC 'Current syncrepl mode' "
+ "SUP monitoredInfo "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmSyncPhase },
+ { "( olmSyncReplAttributes:4 "
+ "NAME ( 'olmSRNextConnect' ) "
+ "DESC 'Scheduled time of next connection attempt' "
+ "SUP monitorTimestamp "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmNextConnect },
+ { "( olmSyncReplAttributes:5 "
+ "NAME ( 'olmSRLastConnect' ) "
+ "DESC 'Time last connected to provider' "
+ "SUP monitorTimestamp "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmLastConnect },
+ { "( olmSyncReplAttributes:6 "
+ "NAME ( 'olmSRLastContact' ) "
+ "DESC 'Time last message received from provider' "
+ "SUP monitorTimestamp "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmLastContact },
+ { "( olmSyncReplAttributes:7 "
+ "NAME ( 'olmSRLastCookieRcvd' ) "
+ "DESC 'Last sync cookie received from provider' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmLastCookieRcvd },
+ { "( olmSyncReplAttributes:8 "
+ "NAME ( 'olmSRLastCookieSent' ) "
+ "DESC 'Last sync cookie sent to provider' "
+ "SUP monitoredInfo "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmLastCookieSent },
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **oc;
+} s_oc[] = {
+ { "( olmSyncReplObjectClasses:1 "
+ "NAME ( 'olmSyncReplInstance' ) "
+ "SUP monitoredObject STRUCTURAL "
+ "MAY ( "
+ "olmSRProviderURIList "
+ "$ olmSRConnection "
+ "$ olmSRSyncPhase "
+ "$ olmSRNextConnect "
+ "$ olmSRLastConnect "
+ "$ olmSRLastContact "
+ "$ olmSRLastCookieRcvd "
+ "$ olmSRLastCookieSent "
+ ") )",
+ &oc_olmSyncRepl },
+ { NULL }
+};
+
+static int
+syncrepl_monitor_initialized;
+
+int
+syncrepl_monitor_init( void )
+{
+ int i, code;
+
+ if ( syncrepl_monitor_initialized )
+ return 0;
+
+ if ( backend_info( "monitor" ) == NULL )
+ return -1;
+
+ {
+ ConfigArgs c;
+ char *argv[3];
+
+ argv[ 0 ] = "syncrepl monitor";
+ c.argv = argv;
+ c.argc = 2;
+ c.fname = argv[0];
+ for ( i=0; s_oid[i].name; i++ ) {
+ argv[1] = s_oid[i].name;
+ argv[2] = s_oid[i].oid;
+ if ( parse_oidm( &c, 0, NULL )) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_monitor_init: unable to add "
+ "objectIdentifier \"%s=%s\"\n",
+ s_oid[i].name, s_oid[i].oid );
+ return 2;
+ }
+ }
+ }
+
+ for ( i=0; s_at[i].desc != NULL; i++ ) {
+ code = register_at( s_at[i].desc, s_at[i].ad, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_monitor_init: register_at failed for attributeType (%s)\n",
+ s_at[i].desc );
+ return 3;
+ } else {
+ (*s_at[i].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ for ( i=0; s_oc[i].desc != NULL; i++ ) {
+ code = register_oc( s_oc[i].desc, s_oc[i].oc, 1 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_monitor_init: register_oc failed for objectClass (%s)\n",
+ s_oc[i].desc );
+ return 4;
+ } else {
+ (*s_oc[i].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+ }
+ syncrepl_monitor_initialized = 1;
+
+ return 0;
+}
+
+static const struct berval zerotime = BER_BVC("00000101000000Z");
+
+static int
+syncrepl_monitor_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ syncinfo_t *si = (syncinfo_t *)priv;
+ Attribute *a;
+ int isConnected = 0;
+
+ a = attr_find( e->e_attrs, ad_olmConnection );
+ if ( !a )
+ return SLAP_CB_CONTINUE;
+ if ( si->si_ld ) {
+ if (!bvmatch( &a->a_vals[0], &si->si_connaddr )) {
+ AC_MEMCPY( a->a_vals[0].bv_val, si->si_connaddr.bv_val, si->si_connaddr.bv_len );
+ a->a_vals[0].bv_len = si->si_connaddr.bv_len;
+ }
+ isConnected = 1;
+ } else {
+ a->a_vals[0].bv_val[0] = '\0';
+ a->a_vals[0].bv_len = 0;
+ }
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmSyncPhase )
+ return SLAP_CB_CONTINUE;
+
+ if ( si->si_refreshDone ) {
+ struct berval bv = BER_BVC("Persist");
+ ber_bvreplace( &a->a_vals[0], &bv );
+ } else {
+ if ( si->si_syncdata && si->si_logstate == SYNCLOG_FALLBACK ) {
+ struct berval bv = BER_BVC("Fallback Refresh");
+ ber_bvreplace( &a->a_vals[0], &bv );
+ } else {
+ struct berval bv = BER_BVC("Refresh");
+ ber_bvreplace( &a->a_vals[0], &bv );
+ }
+ }
+
+ {
+ struct tm tm;
+ char tmbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ ber_len_t len;
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmNextConnect )
+ return SLAP_CB_CONTINUE;
+
+ if ( !isConnected && si->si_re && si->si_re->next_sched.tv_sec ) {
+ time_t next_sched = si->si_re->next_sched.tv_sec;
+ ldap_pvt_gmtime( &next_sched, &tm );
+ lutil_gentime( tmbuf, sizeof( tmbuf ), &tm );
+ len = strlen( tmbuf );
+ assert( len == a->a_vals[0].bv_len );
+ AC_MEMCPY( a->a_vals[0].bv_val, tmbuf, len );
+ } else {
+ AC_MEMCPY( a->a_vals[0].bv_val, zerotime.bv_val, zerotime.bv_len );
+ }
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmLastConnect )
+ return SLAP_CB_CONTINUE;
+
+ if ( si->si_lastconnect ) {
+ ldap_pvt_gmtime( &si->si_lastconnect, &tm );
+ lutil_gentime( tmbuf, sizeof( tmbuf ), &tm );
+ len = strlen( tmbuf );
+ assert( len == a->a_vals[0].bv_len );
+ AC_MEMCPY( a->a_vals[0].bv_val, tmbuf, len );
+ }
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmLastContact )
+ return SLAP_CB_CONTINUE;
+
+ if ( si->si_lastcontact ) {
+ ldap_pvt_gmtime( &si->si_lastcontact, &tm );
+ lutil_gentime( tmbuf, sizeof( tmbuf ), &tm );
+ len = strlen( tmbuf );
+ assert( len == a->a_vals[0].bv_len );
+ AC_MEMCPY( a->a_vals[0].bv_val, tmbuf, len );
+ }
+ }
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmLastCookieRcvd )
+ return SLAP_CB_CONTINUE;
+
+ ldap_pvt_thread_mutex_lock( &si->si_monitor_mutex );
+ if ( !BER_BVISEMPTY( &si->si_lastCookieRcvd ) &&
+ !bvmatch( &a->a_vals[0], &si->si_lastCookieRcvd ))
+ ber_bvreplace( &a->a_vals[0], &si->si_lastCookieRcvd );
+
+ a = a->a_next;
+ if ( a->a_desc != ad_olmLastCookieSent ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( !BER_BVISEMPTY( &si->si_lastCookieSent ) &&
+ !bvmatch( &a->a_vals[0], &si->si_lastCookieSent ))
+ ber_bvreplace( &a->a_vals[0], &si->si_lastCookieSent );
+ ldap_pvt_thread_mutex_unlock( &si->si_monitor_mutex );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncrepl_monitor_add(
+ syncinfo_t *si
+)
+{
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+ struct berval pndn, pdn, rdn, bv;
+ char rdnbuf[sizeof("cn=Consumer 999")];
+ Entry *e, *p;
+ int rc;
+
+ if ( !syncrepl_monitor_initialized )
+ return -1;
+
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ SLAP_DBFLAGS( si->si_be ) ^= SLAP_DBFLAG_MONITORING;
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ if ( !mbe->is_configured() ) {
+ return 0;
+ }
+
+ rc = mbe->register_database( si->si_be, &pndn );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "syncrepl_monitor_add: "
+ "failed to register the database with back-monitor\n" );
+ return rc;
+ }
+ rdn.bv_len = sprintf(rdnbuf, "cn=Consumer %03d", si->si_rid );
+ rdn.bv_val = rdnbuf;
+ p = mbe->entry_get_unlocked( &pndn );
+ if ( p ) {
+ pdn = p->e_name;
+ } else {
+ pdn = pndn;
+ }
+
+ e = mbe->entry_stub( &pdn, &pndn, &rdn,
+ oc_olmSyncRepl, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncrepl_monitor_add: "
+ "unable to create entry \"%s,%s\"\n",
+ rdn.bv_val, pndn.bv_val );
+ return -1;
+ }
+
+ attr_merge_normalize_one( e, ad_olmProviderURIList,
+ &si->si_bindconf.sb_uri, NULL );
+
+ {
+ si->si_connaddr.bv_val = si->si_connaddrbuf;
+ si->si_connaddr.bv_len = sizeof( si->si_connaddrbuf );
+ si->si_connaddrbuf[0] = '\0';
+ attr_merge_normalize_one( e, ad_olmConnection, &si->si_connaddr, NULL );
+ }
+ {
+ struct berval bv = BER_BVC("Refresh");
+ attr_merge_normalize_one( e, ad_olmSyncPhase, &bv, NULL );
+ }
+ {
+ attr_merge_normalize_one( e, ad_olmNextConnect, (struct berval *)&zerotime, NULL );
+ attr_merge_normalize_one( e, ad_olmLastConnect, (struct berval *)&zerotime, NULL );
+ attr_merge_normalize_one( e, ad_olmLastContact, (struct berval *)&zerotime, NULL );
+ }
+ {
+ struct berval bv = BER_BVC("");
+ attr_merge_normalize_one( e, ad_olmLastCookieRcvd, &bv, NULL );
+ attr_merge_normalize_one( e, ad_olmLastCookieSent, &bv, NULL );
+ }
+ {
+ monitor_callback_t *cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = syncrepl_monitor_update;
+ cb->mc_private = si;
+ rc = mbe->register_entry( e, cb, NULL, 0 );
+ }
+
+ si->si_monitor_ndn = e->e_nname;
+ BER_BVZERO( &e->e_nname );
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+syncrepl_monitor_del(
+ syncinfo_t *si
+)
+{
+ BackendInfo *mi;
+
+ mi = backend_info( "monitor" );
+ if ( mi && mi->bi_extra ) {
+ monitor_extra_t *mbe = mi->bi_extra;
+ mbe->unregister_entry( &si->si_monitor_ndn );
+ }
+ ch_free( si->si_monitor_ndn.bv_val );
+ return 0;
+}
+
+static int
+add_syncrepl(
+ ConfigArgs *c )
+{
+ syncinfo_t *si;
+ int i, rc = 0;
+
+ if ( !( c->be->be_search && c->be->be_add && c->be->be_modify && c->be->be_delete ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "database %s does not support "
+ "operations required for syncrepl", c->be->be_type );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+ if ( BER_BVISEMPTY( &c->be->be_rootdn ) ) {
+ strcpy( c->cr_msg, "rootDN must be defined before syncrepl may be used" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ return 1;
+ }
+ si = (syncinfo_t *) ch_calloc( 1, sizeof( syncinfo_t ) );
+
+ if ( si == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "out of memory in add_syncrepl\n" );
+ return 1;
+ }
+
+ si->si_bindconf.sb_tls = SB_TLS_OFF;
+ si->si_bindconf.sb_method = LDAP_AUTH_SIMPLE;
+ si->si_schemachecking = 0;
+ ber_str2bv( "(objectclass=*)", STRLENOF("(objectclass=*)"), 1,
+ &si->si_filterstr );
+ si->si_base.bv_val = NULL;
+ si->si_scope = LDAP_SCOPE_SUBTREE;
+ si->si_attrsonly = 0;
+ si->si_anlist = (AttributeName *) ch_calloc( 1, sizeof( AttributeName ) );
+ si->si_exanlist = (AttributeName *) ch_calloc( 1, sizeof( AttributeName ) );
+ si->si_attrs = NULL;
+ si->si_allattrs = 0;
+ si->si_allopattrs = 0;
+ si->si_exattrs = NULL;
+ si->si_type = si->si_ctype = LDAP_SYNC_REFRESH_ONLY;
+ si->si_interval = 86400;
+ si->si_retryinterval = NULL;
+ si->si_retrynum_init = NULL;
+ si->si_retrynum = NULL;
+ si->si_manageDSAit = 0;
+ si->si_tlimit = 0;
+ si->si_slimit = 0;
+
+ si->si_presentlist = NULL;
+ LDAP_LIST_INIT( &si->si_nonpresentlist );
+ ldap_pvt_thread_mutex_init( &si->si_monitor_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_mutex );
+
+ si->si_is_configdb = strcmp( c->be->be_suffix[0].bv_val, "cn=config" ) == 0;
+
+ rc = parse_syncrepl_line( c, si );
+
+ if ( rc == 0 ) {
+ LDAPURLDesc *lud;
+
+ /* Must be LDAPv3 because we need controls */
+ switch ( si->si_bindconf.sb_version ) {
+ case 0:
+ /* not explicitly set */
+ si->si_bindconf.sb_version = LDAP_VERSION3;
+ break;
+ case 3:
+ /* explicitly set */
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY,
+ "version %d incompatible with syncrepl\n",
+ si->si_bindconf.sb_version );
+ syncinfo_free( si, 0 );
+ return 1;
+ }
+
+ if ( ldap_url_parse( si->si_bindconf.sb_uri.bv_val, &lud )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid URL", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s %s\n",
+ c->log, c->cr_msg, si->si_bindconf.sb_uri.bv_val );
+ return 1;
+ }
+
+ si->si_be = c->be;
+ if ( slapMode & SLAP_SERVER_MODE ) {
+ int isMe = 0;
+ /* check if consumer points to current server and database.
+ * If so, ignore this configuration.
+ */
+ if ( !SLAP_DBHIDDEN( c->be ) ) {
+ int i;
+ /* if searchbase doesn't match current DB suffix,
+ * assume it's different
+ */
+ for ( i=0; !BER_BVISNULL( &c->be->be_nsuffix[i] ); i++ ) {
+ if ( bvmatch( &si->si_base, &c->be->be_nsuffix[i] )) {
+ isMe = 1;
+ break;
+ }
+ }
+ /* if searchbase matches, see if URLs match */
+ if ( isMe && config_check_my_url( si->si_bindconf.sb_uri.bv_val,
+ lud ) == NULL )
+ isMe = 0;
+ }
+
+ if ( !isMe ) {
+ init_syncrepl( si );
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ si->si_re = ldap_pvt_runqueue_insert( &slapd_rq,
+ si->si_interval, do_syncrepl, si, "do_syncrepl",
+ si->si_ridtxt );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ if ( si->si_re )
+ rc = config_sync_shadow( c ) ? -1 : 0;
+ else
+ rc = -1;
+ }
+ } else {
+ /* multiprovider still needs to see this flag in tool mode */
+ rc = config_sync_shadow( c ) ? -1 : 0;
+ }
+ ldap_free_urldesc( lud );
+ }
+
+#ifdef HAVE_TLS
+ /* Use main slapd defaults */
+ bindconf_tls_defaults( &si->si_bindconf );
+#endif
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "failed to add syncinfo\n" );
+ syncinfo_free( si, 0 );
+ return 1;
+ } else {
+ Debug( LDAP_DEBUG_CONFIG,
+ "Config: ** successfully added syncrepl %s \"%s\"\n",
+ si->si_ridtxt,
+ BER_BVISNULL( &si->si_bindconf.sb_uri ) ?
+ "(null)" : si->si_bindconf.sb_uri.bv_val );
+ if ( c->be->be_syncinfo ) {
+ syncinfo_t **sip;
+
+ si->si_cookieState = c->be->be_syncinfo->si_cookieState;
+
+ for ( i = 0, sip = &c->be->be_syncinfo;
+ (*sip)->si_next && ( c->valx < 0 || i < c->valx );
+ sip = &(*sip)->si_next, i++ )
+ /* advance to the desired position */ ;
+ si->si_next = *sip;
+ *sip = si;
+
+ } else {
+ si->si_cookieState = ch_calloc( 1, sizeof( cookie_state ));
+ ldap_pvt_thread_mutex_init( &si->si_cookieState->cs_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_cookieState->cs_pmutex );
+ ldap_pvt_thread_mutex_init( &si->si_cookieState->cs_refresh_mutex );
+ ldap_pvt_thread_cond_init( &si->si_cookieState->cs_cond );
+
+ c->be->be_syncinfo = si;
+ si->si_next = NULL;
+ }
+ si->si_cookieState->cs_ref++;
+
+ syncrepl_monitor_init();
+
+ return 0;
+ }
+}
+
+static void
+syncrepl_unparse( syncinfo_t *si, struct berval *bv )
+{
+ struct berval bc, uri, bs;
+ char buf[BUFSIZ*2], *ptr;
+ ber_len_t len;
+ int i;
+# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr))
+
+ BER_BVZERO( bv );
+
+ /* temporarily inhibit bindconf from printing URI */
+ uri = si->si_bindconf.sb_uri;
+ BER_BVZERO( &si->si_bindconf.sb_uri );
+ si->si_bindconf.sb_version = 0;
+ bindconf_unparse( &si->si_bindconf, &bc );
+ si->si_bindconf.sb_uri = uri;
+ si->si_bindconf.sb_version = LDAP_VERSION3;
+
+ ptr = buf;
+ assert( si->si_rid >= 0 && si->si_rid <= SLAP_SYNC_RID_MAX );
+ len = snprintf( ptr, WHATSLEFT, IDSTR "=%03d " PROVIDERSTR "=%s",
+ si->si_rid, si->si_bindconf.sb_uri.bv_val );
+ if ( len >= sizeof( buf ) ) return;
+ ptr += len;
+ if ( !BER_BVISNULL( &bc ) ) {
+ if ( WHATSLEFT <= bc.bv_len ) {
+ free( bc.bv_val );
+ return;
+ }
+ ptr = lutil_strcopy( ptr, bc.bv_val );
+ free( bc.bv_val );
+ }
+ if ( !BER_BVISEMPTY( &si->si_filterstr ) ) {
+ if ( WHATSLEFT <= STRLENOF( " " FILTERSTR "=\"" "\"" ) + si->si_filterstr.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " FILTERSTR "=\"" );
+ ptr = lutil_strcopy( ptr, si->si_filterstr.bv_val );
+ *ptr++ = '"';
+ }
+ if ( !BER_BVISNULL( &si->si_base ) ) {
+ if ( WHATSLEFT <= STRLENOF( " " SEARCHBASESTR "=\"" "\"" ) + si->si_base.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " SEARCHBASESTR "=\"" );
+ ptr = lutil_strcopy( ptr, si->si_base.bv_val );
+ *ptr++ = '"';
+ }
+ if ( !BER_BVISNULL( &si->si_suffixm ) ) {
+ if ( WHATSLEFT <= STRLENOF( " " SUFFIXMSTR "=\"" "\"" ) + si->si_suffixm.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " SUFFIXMSTR "=\"" );
+ ptr = lutil_strcopy( ptr, si->si_suffixm.bv_val );
+ *ptr++ = '"';
+ }
+ if ( !BER_BVISEMPTY( &si->si_logfilterstr ) ) {
+ if ( WHATSLEFT <= STRLENOF( " " LOGFILTERSTR "=\"" "\"" ) + si->si_logfilterstr.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " LOGFILTERSTR "=\"" );
+ ptr = lutil_strcopy( ptr, si->si_logfilterstr.bv_val );
+ *ptr++ = '"';
+ }
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ if ( WHATSLEFT <= STRLENOF( " " LOGBASESTR "=\"" "\"" ) + si->si_logbase.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " LOGBASESTR "=\"" );
+ ptr = lutil_strcopy( ptr, si->si_logbase.bv_val );
+ *ptr++ = '"';
+ }
+ if ( ldap_pvt_scope2bv( si->si_scope, &bs ) == LDAP_SUCCESS ) {
+ if ( WHATSLEFT <= STRLENOF( " " SCOPESTR "=" ) + bs.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " SCOPESTR "=" );
+ ptr = lutil_strcopy( ptr, bs.bv_val );
+ }
+ if ( si->si_attrsonly ) {
+ if ( WHATSLEFT <= STRLENOF( " " ATTRSONLYSTR "=\"" "\"" ) ) return;
+ ptr = lutil_strcopy( ptr, " " ATTRSONLYSTR );
+ }
+ if ( si->si_anfile ) {
+ if ( WHATSLEFT <= STRLENOF( " " ATTRSSTR "=\":include:" "\"" ) + strlen( si->si_anfile ) ) return;
+ ptr = lutil_strcopy( ptr, " " ATTRSSTR "=:include:\"" );
+ ptr = lutil_strcopy( ptr, si->si_anfile );
+ *ptr++ = '"';
+ } else if ( si->si_allattrs || si->si_allopattrs ||
+ ( si->si_anlist && !BER_BVISNULL(&si->si_anlist[0].an_name) ) )
+ {
+ char *old;
+
+ if ( WHATSLEFT <= STRLENOF( " " ATTRSONLYSTR "=\"" "\"" ) ) return;
+ ptr = lutil_strcopy( ptr, " " ATTRSSTR "=\"" );
+ old = ptr;
+ ptr = anlist_unparse( si->si_anlist, ptr, WHATSLEFT );
+ if ( ptr == NULL ) return;
+ if ( si->si_allattrs ) {
+ if ( WHATSLEFT <= STRLENOF( ",*\"" ) ) return;
+ if ( old != ptr ) *ptr++ = ',';
+ *ptr++ = '*';
+ }
+ if ( si->si_allopattrs ) {
+ if ( WHATSLEFT <= STRLENOF( ",+\"" ) ) return;
+ if ( old != ptr ) *ptr++ = ',';
+ *ptr++ = '+';
+ }
+ *ptr++ = '"';
+ }
+ if ( si->si_exanlist && !BER_BVISNULL(&si->si_exanlist[0].an_name) ) {
+ if ( WHATSLEFT <= STRLENOF( " " EXATTRSSTR "=" ) ) return;
+ ptr = lutil_strcopy( ptr, " " EXATTRSSTR "=" );
+ ptr = anlist_unparse( si->si_exanlist, ptr, WHATSLEFT );
+ if ( ptr == NULL ) return;
+ }
+ if ( WHATSLEFT <= STRLENOF( " " SCHEMASTR "=" ) + STRLENOF( "off" ) ) return;
+ ptr = lutil_strcopy( ptr, " " SCHEMASTR "=" );
+ ptr = lutil_strcopy( ptr, si->si_schemachecking ? "on" : "off" );
+
+ if ( WHATSLEFT <= STRLENOF( " " TYPESTR "=" ) + STRLENOF( "refreshAndPersist" ) ) return;
+ ptr = lutil_strcopy( ptr, " " TYPESTR "=" );
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ if ( si->si_type == MSAD_DIRSYNC )
+ ptr = lutil_strcopy( ptr, "dirSync" );
+ else
+#endif
+ ptr = lutil_strcopy( ptr, si->si_type == LDAP_SYNC_REFRESH_AND_PERSIST ?
+ "refreshAndPersist" : "refreshOnly" );
+
+ if ( si->si_type == LDAP_SYNC_REFRESH_ONLY
+#ifdef LDAP_CONTROL_X_DIRSYNC
+ || si->si_type == MSAD_DIRSYNC
+#endif
+ ) {
+ int dd, hh, mm, ss;
+
+ dd = si->si_interval;
+ ss = dd % 60;
+ dd /= 60;
+ mm = dd % 60;
+ dd /= 60;
+ hh = dd % 24;
+ dd /= 24;
+ len = snprintf( ptr, WHATSLEFT, " %s=%02d:%02d:%02d:%02d",
+ INTERVALSTR, dd, hh, mm, ss );
+ if ( len >= WHATSLEFT ) return;
+ ptr += len;
+ }
+
+ if ( si->si_got & GOT_RETRY ) {
+ const char *space = "";
+ if ( WHATSLEFT <= STRLENOF( " " RETRYSTR "=\"" "\"" ) ) return;
+ ptr = lutil_strcopy( ptr, " " RETRYSTR "=\"" );
+ for (i=0; si->si_retryinterval[i]; i++) {
+ len = snprintf( ptr, WHATSLEFT, "%s%ld ", space,
+ (long) si->si_retryinterval[i] );
+ space = " ";
+ if ( WHATSLEFT - 1 <= len ) return;
+ ptr += len;
+ if ( si->si_retrynum_init[i] == RETRYNUM_FOREVER )
+ *ptr++ = '+';
+ else {
+ len = snprintf( ptr, WHATSLEFT, "%d", si->si_retrynum_init[i] );
+ if ( WHATSLEFT <= len ) return;
+ ptr += len;
+ }
+ }
+ if ( WHATSLEFT <= STRLENOF( "\"" ) ) return;
+ *ptr++ = '"';
+ } else {
+ ptr = lutil_strcopy( ptr, " " RETRYSTR "=undefined" );
+ }
+
+ if ( si->si_slimit ) {
+ len = snprintf( ptr, WHATSLEFT, " " SLIMITSTR "=%d", si->si_slimit );
+ if ( WHATSLEFT <= len ) return;
+ ptr += len;
+ }
+
+ if ( si->si_tlimit ) {
+ len = snprintf( ptr, WHATSLEFT, " " TLIMITSTR "=%d", si->si_tlimit );
+ if ( WHATSLEFT <= len ) return;
+ ptr += len;
+ }
+
+ if ( si->si_syncdata ) {
+ if ( enum_to_verb( datamodes, si->si_syncdata, &bc ) >= 0 ) {
+ if ( WHATSLEFT <= STRLENOF( " " SYNCDATASTR "=" ) + bc.bv_len ) return;
+ ptr = lutil_strcopy( ptr, " " SYNCDATASTR "=" );
+ ptr = lutil_strcopy( ptr, bc.bv_val );
+ }
+ }
+
+ if ( si->si_lazyCommit ) {
+ ptr = lutil_strcopy( ptr, " " LAZY_COMMIT );
+ }
+
+ bc.bv_len = ptr - buf;
+ bc.bv_val = buf;
+ ber_dupbv( bv, &bc );
+}
+
+int
+syncrepl_config( ConfigArgs *c )
+{
+ if (c->op == SLAP_CONFIG_EMIT) {
+ if ( c->be->be_syncinfo ) {
+ struct berval bv;
+ syncinfo_t *si;
+
+ for ( si = c->be->be_syncinfo; si; si=si->si_next ) {
+ syncrepl_unparse( si, &bv );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ return 0;
+ }
+ return 1;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ int isrunning = 0;
+ if ( c->be->be_syncinfo ) {
+ syncinfo_t *si, **sip;
+ int i;
+
+ for ( sip = &c->be->be_syncinfo, i=0; *sip; i++ ) {
+ si = *sip;
+ if ( c->valx == -1 || i == c->valx ) {
+ *sip = si->si_next;
+ si->si_ctype = -1;
+ si->si_next = NULL;
+ /* If the task is currently active, we have to leave
+ * it running. It will exit on its own. This will only
+ * happen when running on the cn=config DB.
+ */
+ if ( si->si_re ) {
+ if ( si->si_be == c->ca_op->o_bd ||
+ ldap_pvt_thread_mutex_trylock( &si->si_mutex )) {
+ isrunning = 1;
+ } else {
+ /* There is no active thread, but we must still
+ * ensure that no thread is (or will be) queued
+ * while we removes the task.
+ */
+ struct re_s *re = si->si_re;
+ si->si_re = NULL;
+
+ if ( si->si_conn ) {
+ connection_client_stop( si->si_conn );
+ si->si_conn = NULL;
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ isrunning = 1;
+ }
+ if ( !re->pool_cookie || ldap_pvt_thread_pool_retract( re->pool_cookie ) > 0 )
+ isrunning = 0;
+
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ ldap_pvt_thread_mutex_unlock( &si->si_mutex );
+ }
+ }
+ if ( !isrunning ) {
+ syncinfo_free( si, 0 );
+ }
+ if ( i == c->valx )
+ break;
+ } else {
+ sip = &si->si_next;
+ }
+ }
+ }
+ if ( !c->be->be_syncinfo ) {
+ SLAP_DBFLAGS( c->be ) &= ~SLAP_DBFLAG_SHADOW_MASK;
+ }
+ return 0;
+ }
+ if ( SLAP_SLURP_SHADOW( c->be ) ) {
+ Debug(LDAP_DEBUG_ANY, "%s: "
+ "syncrepl: database already shadowed.\n",
+ c->log );
+ return(1);
+ } else {
+ return add_syncrepl( c );
+ }
+}
diff --git a/servers/slapd/syntax.c b/servers/slapd/syntax.c
new file mode 100644
index 0000000..0400011
--- /dev/null
+++ b/servers/slapd/syntax.c
@@ -0,0 +1,457 @@
+/* syntax.c - routines to manage syntax definitions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+
+struct sindexrec {
+ char *sir_name;
+ Syntax *sir_syn;
+};
+
+static Avlnode *syn_index = NULL;
+static LDAP_STAILQ_HEAD(SyntaxList, Syntax) syn_list
+ = LDAP_STAILQ_HEAD_INITIALIZER(syn_list);
+
+/* Last hardcoded attribute registered */
+Syntax *syn_sys_tail;
+
+static int
+syn_index_cmp(
+ const void *v_sir1,
+ const void *v_sir2
+)
+{
+ const struct sindexrec *sir1 = v_sir1, *sir2 = v_sir2;
+ return (strcmp( sir1->sir_name, sir2->sir_name ));
+}
+
+static int
+syn_index_name_cmp(
+ const void *name,
+ const void *sir
+)
+{
+ return (strcmp( name, ((const struct sindexrec *)sir)->sir_name ));
+}
+
+Syntax *
+syn_find( const char *synname )
+{
+ struct sindexrec *sir = NULL;
+
+ if ( (sir = ldap_avl_find( syn_index, synname, syn_index_name_cmp )) != NULL ) {
+ return( sir->sir_syn );
+ }
+ return( NULL );
+}
+
+Syntax *
+syn_find_desc( const char *syndesc, int *len )
+{
+ Syntax *synp;
+
+ LDAP_STAILQ_FOREACH(synp, &syn_list, ssyn_next) {
+ if ((*len = dscompare( synp->ssyn_syn.syn_desc, syndesc, '{' /*'}'*/ ))) {
+ return synp;
+ }
+ }
+ return( NULL );
+}
+
+int
+syn_is_sup( Syntax *syn, Syntax *sup )
+{
+ int i;
+
+ assert( syn != NULL );
+ assert( sup != NULL );
+
+ if ( syn == sup ) {
+ return 1;
+ }
+
+ if ( syn->ssyn_sups == NULL ) {
+ return 0;
+ }
+
+ for ( i = 0; syn->ssyn_sups[i]; i++ ) {
+ if ( syn->ssyn_sups[i] == sup ) {
+ return 1;
+ }
+
+ if ( syn_is_sup( syn->ssyn_sups[i], sup ) ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+syn_destroy( void )
+{
+ Syntax *s;
+
+ ldap_avl_free( syn_index, ldap_memfree );
+ while( !LDAP_STAILQ_EMPTY( &syn_list ) ) {
+ s = LDAP_STAILQ_FIRST( &syn_list );
+ LDAP_STAILQ_REMOVE_HEAD( &syn_list, ssyn_next );
+ if ( s->ssyn_sups ) {
+ SLAP_FREE( s->ssyn_sups );
+ }
+ ldap_syntax_free( (LDAPSyntax *)s );
+ }
+}
+
+static int
+syn_insert(
+ Syntax *ssyn,
+ Syntax *prev,
+ const char **err )
+{
+ struct sindexrec *sir;
+
+ LDAP_STAILQ_NEXT( ssyn, ssyn_next ) = NULL;
+
+ if ( ssyn->ssyn_oid ) {
+ sir = (struct sindexrec *)
+ SLAP_CALLOC( 1, sizeof(struct sindexrec) );
+ if( sir == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n" );
+ return LDAP_OTHER;
+ }
+ sir->sir_name = ssyn->ssyn_oid;
+ sir->sir_syn = ssyn;
+ if ( ldap_avl_insert( &syn_index, (caddr_t) sir,
+ syn_index_cmp, ldap_avl_dup_error ) ) {
+ *err = ssyn->ssyn_oid;
+ ldap_memfree(sir);
+ return SLAP_SCHERR_SYN_DUP;
+ }
+ /* FIX: temporal consistency check */
+ syn_find(sir->sir_name);
+ }
+
+ if ( ssyn->ssyn_flags & SLAP_AT_HARDCODE ) {
+ prev = syn_sys_tail;
+ syn_sys_tail = ssyn;
+ }
+
+ if ( prev ) {
+ LDAP_STAILQ_INSERT_AFTER( &syn_list, prev, ssyn, ssyn_next );
+ } else {
+ LDAP_STAILQ_INSERT_TAIL( &syn_list, ssyn, ssyn_next );
+ }
+ return 0;
+}
+
+int
+syn_add(
+ LDAPSyntax *syn,
+ int user,
+ slap_syntax_defs_rec *def,
+ Syntax **ssynp,
+ Syntax *prev,
+ const char **err )
+{
+ Syntax *ssyn;
+ int code = 0;
+
+ if ( ssynp != NULL ) {
+ *ssynp = NULL;
+ }
+
+ ssyn = (Syntax *) SLAP_CALLOC( 1, sizeof(Syntax) );
+ if ( ssyn == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n" );
+ return SLAP_SCHERR_OUTOFMEM;
+ }
+
+ AC_MEMCPY( &ssyn->ssyn_syn, syn, sizeof(LDAPSyntax) );
+
+ LDAP_STAILQ_NEXT(ssyn,ssyn_next) = NULL;
+
+ /*
+ * note: ssyn_bvoid uses the same memory of ssyn_syn.syn_oid;
+ * ssyn_oidlen is #defined as ssyn_bvoid.bv_len
+ */
+ ssyn->ssyn_bvoid.bv_val = ssyn->ssyn_syn.syn_oid;
+ ssyn->ssyn_oidlen = strlen(syn->syn_oid);
+ ssyn->ssyn_flags = def->sd_flags;
+ ssyn->ssyn_validate = def->sd_validate;
+ ssyn->ssyn_pretty = def->sd_pretty;
+
+ ssyn->ssyn_sups = NULL;
+
+#ifdef SLAPD_BINARY_CONVERSION
+ ssyn->ssyn_ber2str = def->sd_ber2str;
+ ssyn->ssyn_str2ber = def->sd_str2ber;
+#endif
+
+ if ( def->sd_validate == NULL && def->sd_pretty == NULL && syn->syn_extensions != NULL ) {
+ LDAPSchemaExtensionItem **lsei;
+ Syntax *subst = NULL;
+
+ for ( lsei = syn->syn_extensions; *lsei != NULL; lsei++) {
+ if ( strcmp( (*lsei)->lsei_name, "X-SUBST" ) != 0 ) {
+ continue;
+ }
+
+ assert( (*lsei)->lsei_values != NULL );
+ if ( (*lsei)->lsei_values[0] == NULL
+ || (*lsei)->lsei_values[1] != NULL )
+ {
+ Debug( LDAP_DEBUG_ANY, "syn_add(%s): exactly one substitute syntax must be present\n",
+ ssyn->ssyn_syn.syn_oid );
+ SLAP_FREE( ssyn );
+ return SLAP_SCHERR_SYN_SUBST_NOT_SPECIFIED;
+ }
+
+ subst = syn_find( (*lsei)->lsei_values[0] );
+ if ( subst == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "syn_add(%s): substitute syntax %s not found\n",
+ ssyn->ssyn_syn.syn_oid, (*lsei)->lsei_values[0] );
+ SLAP_FREE( ssyn );
+ return SLAP_SCHERR_SYN_SUBST_NOT_FOUND;
+ }
+ break;
+ }
+
+ if ( subst != NULL ) {
+ ssyn->ssyn_flags = subst->ssyn_flags;
+ ssyn->ssyn_validate = subst->ssyn_validate;
+ ssyn->ssyn_pretty = subst->ssyn_pretty;
+
+ ssyn->ssyn_sups = NULL;
+
+#ifdef SLAPD_BINARY_CONVERSION
+ ssyn->ssyn_ber2str = subst->ssyn_ber2str;
+ ssyn->ssyn_str2ber = subst->ssyn_str2ber;
+#endif
+ }
+ }
+
+ if ( def->sd_sups != NULL ) {
+ int cnt;
+
+ for ( cnt = 0; def->sd_sups[cnt] != NULL; cnt++ )
+ ;
+
+ ssyn->ssyn_sups = (Syntax **)SLAP_CALLOC( cnt + 1,
+ sizeof( Syntax * ) );
+ if ( ssyn->ssyn_sups == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "SLAP_CALLOC Error\n" );
+ code = SLAP_SCHERR_OUTOFMEM;
+
+ } else {
+ for ( cnt = 0; def->sd_sups[cnt] != NULL; cnt++ ) {
+ ssyn->ssyn_sups[cnt] = syn_find( def->sd_sups[cnt] );
+ if ( ssyn->ssyn_sups[cnt] == NULL ) {
+ *err = def->sd_sups[cnt];
+ code = SLAP_SCHERR_SYN_SUP_NOT_FOUND;
+ }
+ }
+ }
+ }
+
+ if ( !user )
+ ssyn->ssyn_flags |= SLAP_SYNTAX_HARDCODE;
+
+ if ( code == 0 ) {
+ code = syn_insert( ssyn, prev, err );
+ }
+
+ if ( code != 0 && ssyn != NULL ) {
+ if ( ssyn->ssyn_sups != NULL ) {
+ SLAP_FREE( ssyn->ssyn_sups );
+ }
+ SLAP_FREE( ssyn );
+ ssyn = NULL;
+ }
+
+ if (ssynp ) {
+ *ssynp = ssyn;
+ }
+
+ return code;
+}
+
+int
+register_syntax(
+ slap_syntax_defs_rec *def )
+{
+ LDAPSyntax *syn;
+ int code;
+ const char *err;
+
+ syn = ldap_str2syntax( def->sd_desc, &code, &err, LDAP_SCHEMA_ALLOW_ALL);
+ if ( !syn ) {
+ Debug( LDAP_DEBUG_ANY, "Error in register_syntax: %s before %s in %s\n",
+ ldap_scherr2str(code), err, def->sd_desc );
+
+ return( -1 );
+ }
+
+ code = syn_add( syn, 0, def, NULL, NULL, &err );
+
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY, "Error in register_syntax: %s %s in %s\n",
+ scherr2str(code), err, def->sd_desc );
+ ldap_syntax_free( syn );
+
+ return( -1 );
+ }
+
+ ldap_memfree( syn );
+
+ return( 0 );
+}
+
+int
+syn_schema_info( Entry *e )
+{
+ AttributeDescription *ad_ldapSyntaxes = slap_schema.si_ad_ldapSyntaxes;
+ Syntax *syn;
+ struct berval val;
+ struct berval nval;
+
+ LDAP_STAILQ_FOREACH(syn, &syn_list, ssyn_next ) {
+ if ( ! syn->ssyn_validate ) {
+ /* skip syntaxes without validators */
+ continue;
+ }
+ if ( syn->ssyn_flags & SLAP_SYNTAX_HIDE ) {
+ /* hide syntaxes */
+ continue;
+ }
+
+ if ( ldap_syntax2bv( &syn->ssyn_syn, &val ) == NULL ) {
+ return -1;
+ }
+#if 0
+ Debug( LDAP_DEBUG_TRACE, "Merging syn [%ld] %s\n",
+ (long) val.bv_len, val.bv_val );
+#endif
+
+ nval.bv_val = syn->ssyn_oid;
+ nval.bv_len = strlen(syn->ssyn_oid);
+
+ if( attr_merge_one( e, ad_ldapSyntaxes, &val, &nval ) )
+ {
+ return -1;
+ }
+ ldap_memfree( val.bv_val );
+ }
+ return 0;
+}
+
+void
+syn_delete( Syntax *syn )
+{
+ LDAP_STAILQ_REMOVE(&syn_list, syn, Syntax, ssyn_next);
+}
+
+int
+syn_start( Syntax **syn )
+{
+ assert( syn != NULL );
+
+ *syn = LDAP_STAILQ_FIRST(&syn_list);
+
+ return (*syn != NULL);
+}
+
+int
+syn_next( Syntax **syn )
+{
+ assert( syn != NULL );
+
+#if 0 /* pedantic check: don't use this */
+ {
+ Syntax *tmp = NULL;
+
+ LDAP_STAILQ_FOREACH(tmp,&syn_list,ssyn_next) {
+ if ( tmp == *syn ) {
+ break;
+ }
+ }
+
+ assert( tmp != NULL );
+ }
+#endif
+
+ *syn = LDAP_STAILQ_NEXT(*syn,ssyn_next);
+
+ return (*syn != NULL);
+}
+
+void
+syn_unparse( BerVarray *res, Syntax *start, Syntax *end, int sys )
+{
+ Syntax *syn;
+ int i, num;
+ struct berval bv, *bva = NULL, idx;
+ char ibuf[32];
+
+ if ( !start )
+ start = LDAP_STAILQ_FIRST( &syn_list );
+
+ /* count the result size */
+ i = 0;
+ for ( syn = start; syn; syn = LDAP_STAILQ_NEXT( syn, ssyn_next ) ) {
+ if ( sys && !( syn->ssyn_flags & SLAP_SYNTAX_HARDCODE ) ) break;
+ i++;
+ if ( syn == end ) break;
+ }
+ if ( !i ) return;
+
+ num = i;
+ bva = ch_malloc( (num+1) * sizeof(struct berval) );
+ BER_BVZERO( bva );
+ idx.bv_val = ibuf;
+ if ( sys ) {
+ idx.bv_len = 0;
+ ibuf[0] = '\0';
+ }
+ i = 0;
+ for ( syn = start; syn; syn = LDAP_STAILQ_NEXT( syn, ssyn_next ) ) {
+ if ( sys && !( syn->ssyn_flags & SLAP_SYNTAX_HARDCODE ) ) break;
+ if ( ldap_syntax2bv( &syn->ssyn_syn, &bv ) == NULL ) {
+ ber_bvarray_free( bva );
+ }
+ if ( !sys ) {
+ idx.bv_len = sprintf(idx.bv_val, "{%d}", i);
+ }
+ bva[i].bv_len = idx.bv_len + bv.bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ strcpy( bva[i].bv_val, ibuf );
+ strcpy( bva[i].bv_val + idx.bv_len, bv.bv_val );
+ i++;
+ bva[i].bv_val = NULL;
+ ldap_memfree( bv.bv_val );
+ if ( syn == end ) break;
+ }
+ *res = bva;
+}
+
diff --git a/servers/slapd/syslog.c b/servers/slapd/syslog.c
new file mode 100644
index 0000000..50f3053
--- /dev/null
+++ b/servers/slapd/syslog.c
@@ -0,0 +1,289 @@
+/* $OpenBSD: syslog.c,v 1.29 2007/11/09 18:40:19 millert Exp $ */
+/*
+ * Copyright (c) 1983, 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "portable.h"
+
+#include <sys/types.h>
+#include <ac/socket.h>
+#include <ac/syslog.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+#include <ac/errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/stdarg.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+static int LogType = SOCK_DGRAM; /* type of socket connection */
+static int LogFile = -1; /* fd for log */
+static int connected; /* have done connect */
+static int LogStat; /* status bits, set by openlog() */
+static const char *LogTag; /* string to tag the entry with */
+static int LogFacility = LOG_USER; /* default facility code */
+
+static void disconnectlog(void);
+static void connectlog(void);
+
+static void my_localtime(const time_t *t, struct tm *tm);
+
+/*
+ * syslog
+ * print message on log file; output is intended for syslogd(8).
+ */
+void
+syslog(int pri, const char *fmt, ...)
+{
+ va_list ap;
+ char *p, *pend;
+#define TBUF_LEN 2048
+#define FMT_LEN 1024
+ char tbuf[TBUF_LEN];
+ int cnt;
+ int error;
+ int tbuf_left, prlen;
+
+ va_start(ap, fmt);
+
+ /* Check for invalid bits. */
+ if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
+ if (LogTest(LOG_ERR))
+ lutil_debug(slap_debug, LOG_ERR,
+ "syslog: unknown facility/priority: %x", pri);
+ pri &= LOG_PRIMASK|LOG_FACMASK;
+ }
+
+ /* Set default facility if none specified. */
+ if ((pri & LOG_FACMASK) == 0)
+ pri |= LogFacility;
+
+ p = tbuf;
+ pend = p + TBUF_LEN;
+
+ *p++ = '<';
+ p += sprintf(p, "%d", pri);
+ *p++ = '>';
+
+#if 0
+ (void)time(&now);
+ my_localtime(&now, &tm);
+ p += strftime(p, tbuf_left, "%h %e %T ", &tm);
+#endif
+
+ if (LogTag != NULL) {
+ p = lutil_strcopy(p, LogTag);
+ }
+ if (LogStat & LOG_PID) {
+ *p++ = '[';
+ p += sprintf(p, "%ld", (long)getpid());
+ *p++ = ']';
+ }
+ if (LogTag != NULL) {
+ *p++ = ':';
+ *p++ = ' ';
+ }
+
+ tbuf_left = pend - p;
+ prlen = vsnprintf(p, tbuf_left, fmt, ap);
+ va_end(ap);
+ if (prlen < 0)
+ prlen = 0;
+ else if (prlen >= tbuf_left)
+ prlen = tbuf_left - 1;
+ p += prlen;
+ cnt = p - tbuf;
+
+ /* Get connected, output the message to the local logger. */
+ if (LogFile == -1)
+ openlog(LogTag, LogStat, 0);
+ connectlog();
+
+ /*
+ * If the send() failed, there are two likely scenarios:
+ * 1) syslogd was restarted
+ * 2) /dev/log is out of socket buffer space
+ * We attempt to reconnect to /dev/log to take care of
+ * case #1 and keep send()ing data to cover case #2
+ * to give syslogd a chance to empty its socket buffer.
+ */
+ if ((error = send(LogFile, tbuf, cnt, 0)) < 0) {
+ if (errno != ENOBUFS) {
+ disconnectlog();
+ connectlog();
+ }
+ do {
+ usleep(1);
+ if ((error = send(LogFile, tbuf, cnt, 0)) >= 0)
+ break;
+ } while (errno == ENOBUFS);
+ }
+}
+
+static void
+disconnectlog(void)
+{
+ /*
+ * If the user closed the FD and opened another in the same slot,
+ * that's their problem. They should close it before calling on
+ * system services.
+ */
+ if (LogFile != -1) {
+ close(LogFile);
+ LogFile = -1;
+ }
+ connected = 0; /* retry connect */
+}
+
+static void
+connectlog(void)
+{
+ struct sockaddr_un SyslogAddr; /* AF_UNIX address of local logger */
+
+ if (LogFile == -1) {
+ if ((LogFile = socket(AF_UNIX, LogType, 0)) == -1)
+ return;
+ (void)fcntl(LogFile, F_SETFD, FD_CLOEXEC);
+ }
+ if (LogFile != -1 && !connected) {
+ memset(&SyslogAddr, '\0', sizeof(SyslogAddr));
+#ifdef _BSD
+ SyslogAddr.sun_len = sizeof(SyslogAddr);
+#endif
+ SyslogAddr.sun_family = AF_UNIX;
+ strncpy(SyslogAddr.sun_path, _PATH_LOG,
+ sizeof(SyslogAddr.sun_path));
+ if (connect(LogFile, (struct sockaddr *)&SyslogAddr,
+ sizeof(SyslogAddr)) == -1) {
+ (void)close(LogFile);
+ LogFile = -1;
+ } else
+ connected = 1;
+ }
+}
+
+void
+openlog(const char *ident, int logstat, int logfac)
+{
+ if (ident != NULL)
+ LogTag = ident;
+ LogStat = logstat;
+ if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
+ LogFacility = logfac;
+
+ if (LogStat & LOG_NDELAY) /* open immediately */
+ connectlog();
+}
+
+void
+closelog(void)
+{
+ (void)close(LogFile);
+ LogFile = -1;
+ connected = 0;
+ LogTag = NULL;
+}
+
+#if 0
+#define SECS_PER_HOUR (60 * 60)
+#define SECS_PER_DAY (SECS_PER_HOUR * 24)
+
+/* How many days come before each month (0-12). */
+static const unsigned short int __mon_yday[2][13] =
+ {
+ /* Normal years. */
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+ /* Leap years. */
+ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+ };
+
+/* Compute the `struct tm' representation of *T,
+ and store year, yday, mon, mday, wday, hour, min, sec into *TP */
+static void my_localtime(const time_t *t, struct tm *tm)
+{
+ time_t days, rem, y;
+ const unsigned short int *ip;
+ int leap;
+
+ days = *t / SECS_PER_DAY;
+ rem = *t % SECS_PER_DAY;
+ rem -= timezone;
+ while (rem < 0)
+ {
+ rem += SECS_PER_DAY;
+ --days;
+ }
+ while (rem >= SECS_PER_DAY)
+ {
+ rem -= SECS_PER_DAY;
+ ++days;
+ }
+ tm->tm_hour = rem / SECS_PER_HOUR;
+ rem %= SECS_PER_HOUR;
+ tm->tm_min = rem / 60;
+ tm->tm_sec = rem % 60;
+ /* January 1, 1970 was a Thursday. */
+ tm->tm_wday = (4 + days) % 7;
+ if (tm->tm_wday < 0)
+ tm->tm_wday += 7;
+ y = 1970;
+
+#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
+#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
+#define ISLEAP(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
+
+ leap = ISLEAP(y);
+ while (days < 0 || days >= (leap ? 366 : 365))
+ {
+ /* Guess a corrected year, assuming 365 days per year. */
+ time_t yg = y + days / 365 - (days % 365 < 0);
+
+ /* Adjust DAYS and Y to match the guessed year. */
+ days -= ((yg - y) * 365
+ + LEAPS_THRU_END_OF (yg - 1)
+ - LEAPS_THRU_END_OF (y - 1));
+ y = yg;
+ }
+ tm->tm_year = y - 1900;
+ tm->tm_yday = days;
+ ip = __mon_yday[leap];
+ for (y = 11; days < (long int) ip[y]; --y)
+ continue;
+ days -= ip[y];
+ tm->tm_mon = y;
+ tm->tm_mday = days + 1;
+}
+#endif
diff --git a/servers/slapd/txn.c b/servers/slapd/txn.c
new file mode 100644
index 0000000..785a2d5
--- /dev/null
+++ b/servers/slapd/txn.c
@@ -0,0 +1,409 @@
+/* txn.c - LDAP Transactions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+
+#include <lber_pvt.h>
+#include <lutil.h>
+
+const struct berval slap_EXOP_TXN_START = BER_BVC(LDAP_EXOP_TXN_START);
+const struct berval slap_EXOP_TXN_END = BER_BVC(LDAP_EXOP_TXN_END);
+
+int txn_start_extop(
+ Operation *op, SlapReply *rs )
+{
+ int rc;
+ struct berval *bv;
+
+ Debug( LDAP_DEBUG_STATS, "%s TXN START\n",
+ op->o_log_prefix );
+
+ if( op->ore_reqdata != NULL ) {
+ rs->sr_text = "no request data expected";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_bd = op->o_conn->c_authz_backend;
+ if( backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_TXN_START ) != LDAP_SUCCESS )
+ {
+ return rs->sr_err;
+ }
+
+ /* acquire connection lock */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ if( op->o_conn->c_txn != CONN_TXN_INACTIVE ) {
+ rs->sr_text = "Too many transactions";
+ rc = LDAP_BUSY;
+ goto done;
+ }
+
+ assert( op->o_conn->c_txn_backend == NULL );
+ op->o_conn->c_txn = CONN_TXN_SPECIFY;
+
+ bv = (struct berval *) ch_malloc( sizeof (struct berval) );
+ bv->bv_len = 0;
+ bv->bv_val = NULL;
+
+ rs->sr_rspdata = bv;
+ rc = LDAP_SUCCESS;
+
+done:
+ /* release connection lock */
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ return rc;
+}
+
+int txn_spec_ctrl(
+ Operation *op, SlapReply *rs, LDAPControl *ctrl )
+{
+ if ( !ctrl->ldctl_iscritical ) {
+ rs->sr_text = "txnSpec control must be marked critical";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ if( op->o_txnSpec ) {
+ rs->sr_text = "txnSpec control provided multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( ctrl->ldctl_value.bv_val == NULL ) {
+ rs->sr_text = "no transaction identifier provided";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ if ( ctrl->ldctl_value.bv_len != 0 ) {
+ rs->sr_text = "invalid transaction identifier";
+ return LDAP_TXN_ID_INVALID;
+ }
+
+ if ( op->o_preread ) { /* temporary limitation */
+ rs->sr_text = "cannot perform pre-read in transaction";
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+ if ( op->o_postread ) { /* temporary limitation */
+ rs->sr_text = "cannot perform post-read in transaction";
+ return LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ op->o_txnSpec = SLAP_CONTROL_CRITICAL;
+ return LDAP_SUCCESS;
+}
+
+typedef struct txn_rctrls {
+ struct txn_rctrls *tr_next;
+ ber_int_t tr_msgid;
+ LDAPControl ** tr_ctrls;
+} txn_rctrls;
+
+static int txn_result( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_ctrls ) {
+ txn_rctrls **t0, *tr;
+ for ( t0 = (txn_rctrls **) &op->o_callback->sc_private; *t0;
+ t0 = &(*t0)->tr_next )
+ ;
+ tr = op->o_tmpalloc( sizeof( txn_rctrls ), op->o_tmpmemctx );
+ tr->tr_next = NULL;
+ *t0 = tr;
+ tr->tr_msgid = op->o_msgid;
+ tr->tr_ctrls = ldap_controls_dup( rs->sr_ctrls );
+ }
+ return rs->sr_err;
+}
+
+static int txn_put_ctrls( Operation *op, BerElement *ber, txn_rctrls *tr )
+{
+ txn_rctrls *next;
+ int i;
+ ber_printf( ber, "{" );
+ for ( ; tr; tr = next ) {
+ next = tr->tr_next;
+ ber_printf( ber, "{it{", tr->tr_msgid, LDAP_TAG_CONTROLS );
+ for ( i = 0; tr->tr_ctrls[i]; i++ )
+ ldap_pvt_put_control( tr->tr_ctrls[i], ber );
+ ber_printf( ber, "}}" );
+ ldap_controls_free( tr->tr_ctrls );
+ op->o_tmpfree( tr, op->o_tmpmemctx );
+ }
+ ber_printf( ber, "}" );
+ return 0;
+}
+
+int txn_end_extop(
+ Operation *op, SlapReply *rs )
+{
+ int rc;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_len_t len;
+ ber_int_t commit=1;
+ struct berval txnid;
+ Operation *o, *p;
+ Connection *c = op->o_conn;
+
+ Debug( LDAP_DEBUG_STATS, "%s TXN END\n",
+ op->o_log_prefix );
+
+ if( op->ore_reqdata == NULL ) {
+ rs->sr_text = "request data expected";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ if( op->ore_reqdata->bv_len == 0 ) {
+ rs->sr_text = "empty request data";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_bd = c->c_authz_backend;
+ if( backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_TXN_END ) != LDAP_SUCCESS )
+ {
+ return rs->sr_err;
+ }
+
+ ber_init2( ber, op->ore_reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+ if( tag == LBER_ERROR ) {
+ rs->sr_text = "request data decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if( tag == LBER_BOOLEAN ) {
+ tag = ber_scanf( ber, "b", &commit );
+ if( tag == LBER_ERROR ) {
+ rs->sr_text = "request data decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+
+ tag = ber_scanf( ber, /*{*/ "m}", &txnid );
+ if( tag == LBER_ERROR ) {
+ rs->sr_text = "request data decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if( txnid.bv_len ) {
+ rs->sr_text = "invalid transaction identifier";
+ return LDAP_TXN_ID_INVALID;
+ }
+
+ /* acquire connection lock */
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+
+ if( c->c_txn != CONN_TXN_SPECIFY ) {
+ rs->sr_text = "invalid transaction identifier";
+ rc = LDAP_TXN_ID_INVALID;
+ goto done;
+ }
+ c->c_txn = CONN_TXN_SETTLE;
+
+ if( commit ) {
+ slap_callback cb = {0};
+ OpExtra *txn = NULL;
+ if ( op->o_abandon ) {
+ goto drain;
+ }
+
+ if( LDAP_STAILQ_EMPTY(&c->c_txn_ops) ) {
+ /* no updates to commit */
+ rs->sr_text = "no updates to commit";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto settled;
+ }
+
+ cb.sc_response = txn_result;
+ LDAP_STAILQ_FOREACH( o, &c->c_txn_ops, o_next ) {
+ o->o_bd = c->c_txn_backend;
+ p = o;
+ if ( !txn ) {
+ rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_BEGIN, &txn );
+ if ( rc ) {
+ rs->sr_text = "couldn't start DB transaction";
+ rc = LDAP_OTHER;
+ goto drain;
+ }
+ } else {
+ LDAP_SLIST_INSERT_HEAD( &o->o_extra, txn, oe_next );
+ }
+ cb.sc_next = o->o_callback;
+ o->o_callback = &cb;
+ {
+ SlapReply rs = {REP_RESULT};
+ int opidx = slap_req2op( o->o_tag );
+ assert( opidx != SLAP_OP_LAST );
+ o->o_threadctx = op->o_threadctx;
+ o->o_tid = op->o_tid;
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+ rc = (&o->o_bd->bd_info->bi_op_bind)[opidx]( o, &rs );
+ ldap_pvt_thread_mutex_lock( &c->c_mutex );
+ }
+ LDAP_SLIST_REMOVE( &o->o_extra, txn, OpExtra, oe_next );
+ if ( rc ) {
+ struct berval *bv = NULL;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ ber_init_w_nullc( ber, LBER_USE_DER );
+ ber_printf( ber, "{i", o->o_msgid );
+ if ( cb.sc_private )
+ txn_put_ctrls( op, ber, cb.sc_private );
+ ber_printf( ber, "}" );
+ ber_flatten( ber, &bv );
+ ber_free_buf( ber );
+ rs->sr_rspdata = bv;
+ o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_ABORT, &txn );
+ goto drain;
+ }
+ }
+ if ( cb.sc_private ) {
+ struct berval *bv = NULL;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+
+ ber_init_w_nullc( ber, LBER_USE_DER );
+ ber_printf( ber, "{" );
+ txn_put_ctrls( op, ber, cb.sc_private );
+ ber_printf( ber, "}" );
+ ber_flatten( ber, &bv );
+ ber_free_buf( ber );
+ rs->sr_rspdata = bv;
+ }
+ o = p;
+ rc = o->o_bd->bd_info->bi_op_txn(o, SLAP_TXN_COMMIT, &txn );
+ if ( rc ) {
+ rs->sr_text = "transaction commit failed";
+ rc = LDAP_OTHER;
+ }
+ } else {
+ rs->sr_text = "transaction aborted";
+ rc = LDAP_SUCCESS;
+ }
+
+drain:
+ /* drain txn ops list */
+ while (( o = LDAP_STAILQ_FIRST( &c->c_txn_ops )) != NULL ) {
+ int freevals = 1;
+
+ LDAP_STAILQ_REMOVE_HEAD( &c->c_txn_ops, o_next );
+ LDAP_STAILQ_NEXT( o, o_next ) = NULL;
+
+ switch ( o->o_tag ) {
+ case LDAP_REQ_ADD: {
+ if ( o->ora_e != NULL ) {
+ OpExtra *oex;
+ OpExtraDB *oexdb = NULL;
+ LDAP_SLIST_FOREACH(oex, &o->o_extra, oe_next) {
+ if ( oex->oe_key == (void *)do_add ) {
+ oexdb = (OpExtraDB *)oex;
+ break;
+ }
+ }
+ if ( oexdb && oexdb->oe_db ) {
+ BackendDB *bd = o->o_bd;
+ o->o_bd = oexdb->oe_db;
+
+ be_entry_release_w( o, o->ora_e );
+
+ o->ora_e = NULL;
+ o->o_bd = bd;
+ } else {
+ entry_free( o->ora_e );
+ }
+ if ( oexdb ) {
+ o->o_tmpfree( oexdb, o->o_tmpmemctx );
+ }
+ }
+ freevals = 0;
+ } /* fallthru */
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ if ( o->orr_modlist != NULL ) {
+ slap_mods_free( o->orr_modlist, freevals );
+ }
+ break;
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_EXTENDED:
+ break;
+ default:
+ assert( 0 );
+ }
+ o->o_tmpfree( o->o_req_dn.bv_val, o->o_tmpmemctx );
+ o->o_tmpfree( o->o_req_ndn.bv_val, o->o_tmpmemctx );
+ slap_op_free( o, NULL );
+ }
+
+settled:
+ assert( LDAP_STAILQ_EMPTY(&c->c_txn_ops) );
+ assert( c->c_txn == CONN_TXN_SETTLE );
+ c->c_txn = CONN_TXN_INACTIVE;
+ c->c_txn_backend = NULL;
+
+done:
+ /* release connection lock */
+ ldap_pvt_thread_mutex_unlock( &c->c_mutex );
+
+ return rc;
+}
+
+int txn_preop( Operation *op, SlapReply *rs )
+{
+ /* acquire connection lock */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ if( op->o_conn->c_txn == CONN_TXN_INACTIVE ) {
+ rs->sr_text = "invalid transaction identifier";
+ rs->sr_err = LDAP_TXN_ID_INVALID;
+ goto txnReturn;
+ }
+
+ if( op->o_conn->c_txn_backend == NULL ) {
+ op->o_conn->c_txn_backend = op->o_bd;
+
+ } else if( op->o_conn->c_txn_backend != op->o_bd ) {
+ rs->sr_text = "transaction cannot span multiple database contexts";
+ rs->sr_err = LDAP_AFFECTS_MULTIPLE_DSAS;
+ goto txnReturn;
+ }
+
+ if ( !SLAP_TXNS( op->o_bd )) {
+ rs->sr_text = "backend doesn't support transactions";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto txnReturn;
+ }
+
+ /* insert operation into transaction */
+ LDAP_STAILQ_REMOVE( &op->o_conn->c_ops, op, Operation, o_next );
+ LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_txn_ops, op, o_next );
+
+txnReturn:
+ /* release connection lock */
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ if ( op->o_tag != LDAP_REQ_EXTENDED )
+ send_ldap_result( op, rs );
+ if ( !rs->sr_err )
+ rs->sr_err = LDAP_TXN_SPECIFY_OKAY;
+ return rs->sr_err;
+}
diff --git a/servers/slapd/unbind.c b/servers/slapd/unbind.c
new file mode 100644
index 0000000..7995ac2
--- /dev/null
+++ b/servers/slapd/unbind.c
@@ -0,0 +1,61 @@
+/* unbind.c - decode an ldap unbind operation and pass it to a backend db */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ *
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+
+#include "slap.h"
+
+int
+do_unbind( Operation *op, SlapReply *rs )
+{
+ Debug( LDAP_DEBUG_TRACE, "%s do_unbind\n",
+ op->o_log_prefix );
+
+ /*
+ * Parse the unbind request. It looks like this:
+ *
+ * UnBindRequest ::= NULL
+ */
+
+ Debug( LDAP_DEBUG_STATS, "%s UNBIND\n", op->o_log_prefix );
+
+ if ( frontendDB->be_unbind ) {
+ op->o_bd = frontendDB;
+ (void)frontendDB->be_unbind( op, rs );
+ op->o_bd = NULL;
+ }
+
+ /* pass the unbind to all backends */
+ (void)backend_unbind( op, rs );
+
+ return 0;
+}
+
diff --git a/servers/slapd/user.c b/servers/slapd/user.c
new file mode 100644
index 0000000..8adee4f
--- /dev/null
+++ b/servers/slapd/user.c
@@ -0,0 +1,171 @@
+/* user.c - set user id, group id and group access list */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 PM Lashley.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+#include <ac/ctype.h>
+#include <ac/unistd.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+/*
+ * Set real and effective user id and group id, and group access list
+ */
+
+void
+slap_init_user( char *user, char *group )
+{
+ uid_t uid = 0;
+ gid_t gid = 0;
+ int got_uid = 0, got_gid = 0;
+
+ if ( user ) {
+ struct passwd *pwd;
+ if ( isdigit( (unsigned char) *user ) ) {
+ unsigned u;
+
+ got_uid = 1;
+ if ( lutil_atou( &u, user ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Unble to parse user %s\n",
+ user );
+
+ exit( EXIT_FAILURE );
+ }
+ uid = (uid_t)u;
+#ifdef HAVE_GETPWUID
+ pwd = getpwuid( uid );
+ goto did_getpw;
+#else
+ user = NULL;
+#endif
+ } else {
+ pwd = getpwnam( user );
+ did_getpw:
+ if ( pwd == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "No passwd entry for user %s\n",
+ user );
+
+ exit( EXIT_FAILURE );
+ }
+ if ( got_uid ) {
+ user = (pwd != NULL ? pwd->pw_name : NULL);
+ } else {
+ got_uid = 1;
+ uid = pwd->pw_uid;
+ }
+ got_gid = 1;
+ gid = pwd->pw_gid;
+#ifdef HAVE_ENDPWENT
+ endpwent();
+#endif
+ }
+ }
+
+ if ( group ) {
+ struct group *grp;
+ if ( isdigit( (unsigned char) *group )) {
+ unsigned g;
+
+ if ( lutil_atou( &g, group ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Unble to parse group %s\n",
+ group );
+
+ exit( EXIT_FAILURE );
+ }
+ gid = (uid_t)g;
+#ifdef HAVE_GETGRGID
+ grp = getgrgid( gid );
+ goto did_group;
+#endif
+ } else {
+ grp = getgrnam( group );
+ if ( grp != NULL )
+ gid = grp->gr_gid;
+ did_group:
+ if ( grp == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "No group entry for group %s\n",
+ group );
+
+ exit( EXIT_FAILURE );
+ }
+ }
+ got_gid = 1;
+ }
+
+ if ( user ) {
+ if ( getuid() == 0 && initgroups( user, gid ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "Could not set the group access (gid) list\n" );
+
+ exit( EXIT_FAILURE );
+ }
+ }
+
+#ifdef HAVE_ENDGRENT
+ endgrent();
+#endif
+
+ if ( got_gid ) {
+ if ( setgid( gid ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Could not set real group id to %d\n",
+ (int) gid );
+
+ exit( EXIT_FAILURE );
+ }
+#ifdef HAVE_SETEGID
+ if ( setegid( gid ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Could not set effective group id to %d\n",
+ (int) gid );
+
+ exit( EXIT_FAILURE );
+ }
+#endif
+ }
+
+ if ( got_uid ) {
+ if ( setuid( uid ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Could not set real user id to %d\n",
+ (int) uid );
+
+ exit( EXIT_FAILURE );
+ }
+#ifdef HAVE_SETEUID
+ if ( seteuid( uid ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "Could not set effective user id to %d\n",
+ (int) uid );
+
+ exit( EXIT_FAILURE );
+ }
+#endif
+ }
+}
+
+#endif /* HAVE_PWD_H && HAVE_GRP_H */
diff --git a/servers/slapd/value.c b/servers/slapd/value.c
new file mode 100644
index 0000000..0f0cffc
--- /dev/null
+++ b/servers/slapd/value.c
@@ -0,0 +1,798 @@
+/* value.c - routines for dealing with values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/*
+ * Copyright (c) 1995 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include <sys/stat.h>
+
+#include "slap.h"
+
+int
+value_add(
+ BerVarray *vals,
+ BerVarray addvals )
+{
+ int n, nn = 0;
+ BerVarray v2;
+
+ if ( addvals != NULL ) {
+ for ( ; !BER_BVISNULL( &addvals[nn] ); nn++ )
+ ; /* NULL */
+ }
+
+ if ( *vals == NULL ) {
+ *vals = (BerVarray) SLAP_MALLOC( (nn + 1)
+ * sizeof(struct berval) );
+ if( *vals == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "value_add: SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ n = 0;
+
+ } else {
+ for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) {
+ ; /* Empty */
+ }
+ *vals = (BerVarray) SLAP_REALLOC( (char *) *vals,
+ (n + nn + 1) * sizeof(struct berval) );
+ if( *vals == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "value_add: SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ }
+
+ v2 = &(*vals)[n];
+ for ( n = 0 ; n < nn; v2++, addvals++ ) {
+ ber_dupbv( v2, addvals );
+ if ( BER_BVISNULL( v2 ) ) break;
+ }
+ BER_BVZERO( v2 );
+
+ return LDAP_SUCCESS;
+}
+
+int
+value_add_one(
+ BerVarray *vals,
+ struct berval *addval )
+{
+ int n;
+ BerVarray v2;
+
+ if ( *vals == NULL ) {
+ *vals = (BerVarray) SLAP_MALLOC( 2 * sizeof(struct berval) );
+ if( *vals == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "value_add_one: SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ n = 0;
+
+ } else {
+ for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) {
+ ; /* Empty */
+ }
+ *vals = (BerVarray) SLAP_REALLOC( (char *) *vals,
+ (n + 2) * sizeof(struct berval) );
+ if( *vals == NULL ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "value_add_one: SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ }
+
+ v2 = &(*vals)[n];
+ ber_dupbv(v2, addval);
+
+ v2++;
+ BER_BVZERO( v2 );
+
+ return LDAP_SUCCESS;
+}
+
+int asserted_value_validate_normalize(
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned usage,
+ struct berval *in,
+ struct berval *out,
+ const char ** text,
+ void *ctx )
+{
+ int rc;
+ struct berval pval;
+ pval.bv_val = NULL;
+
+ /* we expect the value to be in the assertion syntax */
+ assert( !SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX(usage) );
+
+ if( mr == NULL ) {
+ *text = "inappropriate matching request";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if( !mr->smr_match ) {
+ *text = "requested matching rule not supported";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if( mr->smr_syntax->ssyn_pretty ) {
+ rc = (mr->smr_syntax->ssyn_pretty)( mr->smr_syntax, in, &pval, ctx );
+ in = &pval;
+
+ } else if ( mr->smr_syntax->ssyn_validate ) {
+ rc = (mr->smr_syntax->ssyn_validate)( mr->smr_syntax, in );
+
+ } else {
+ *text = "inappropriate matching request";
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ if( rc != LDAP_SUCCESS ) {
+ *text = "value does not conform to assertion syntax";
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if( mr->smr_normalize ) {
+ rc = (mr->smr_normalize)(
+ usage|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ ad ? ad->ad_type->sat_syntax : NULL,
+ mr, in, out, ctx );
+
+ if( pval.bv_val ) ber_memfree_x( pval.bv_val, ctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ *text = "unable to normalize value for matching";
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ } else if ( pval.bv_val != NULL ) {
+ *out = pval;
+
+ } else {
+ ber_dupbv_x( out, in, ctx );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+value_match(
+ int *match,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned flags,
+ struct berval *v1, /* stored value */
+ void *v2, /* assertion */
+ const char ** text )
+{
+ int rc;
+
+ assert( mr != NULL );
+
+ if( !mr->smr_match ) {
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ rc = (mr->smr_match)( match, flags,
+ ad->ad_type->sat_syntax, mr, v1, v2 );
+
+ return rc;
+}
+
+int value_find_ex(
+ AttributeDescription *ad,
+ unsigned flags,
+ BerVarray vals,
+ struct berval *val,
+ void *ctx )
+{
+ int i;
+ int rc;
+ struct berval nval = BER_BVNULL;
+ MatchingRule *mr = ad->ad_type->sat_equality;
+
+ if( mr == NULL || !mr->smr_match ) {
+ return LDAP_INAPPROPRIATE_MATCHING;
+ }
+
+ assert( SLAP_IS_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH( flags ) != 0 );
+
+ if( !SLAP_IS_MR_ASSERTED_VALUE_NORMALIZED_MATCH( flags ) &&
+ mr->smr_normalize )
+ {
+ rc = (mr->smr_normalize)(
+ flags & (SLAP_MR_TYPE_MASK|SLAP_MR_SUBTYPE_MASK|SLAP_MR_VALUE_OF_SYNTAX),
+ ad->ad_type->sat_syntax,
+ mr, val, &nval, ctx );
+
+ if( rc != LDAP_SUCCESS ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ for ( i = 0; vals[i].bv_val != NULL; i++ ) {
+ int match;
+ const char *text;
+
+ rc = value_match( &match, ad, mr, flags,
+ &vals[i], nval.bv_val == NULL ? val : &nval, &text );
+
+ if( rc == LDAP_SUCCESS && match == 0 ) {
+ slap_sl_free( nval.bv_val, ctx );
+ return rc;
+ }
+ }
+
+ slap_sl_free( nval.bv_val, ctx );
+ return LDAP_NO_SUCH_ATTRIBUTE;
+}
+
+/* assign new indexes to an attribute's ordered values */
+void
+ordered_value_renumber( Attribute *a )
+{
+ char *ptr, ibuf[64]; /* many digits */
+ struct berval ibv, tmp, vtmp;
+ unsigned i;
+
+ ibv.bv_val = ibuf;
+
+ for (i=0; i<a->a_numvals; i++) {
+ ibv.bv_len = sprintf(ibv.bv_val, "{%u}", i);
+ vtmp = a->a_vals[i];
+ if ( vtmp.bv_val[0] == '{' ) {
+ ptr = ber_bvchr(&vtmp, '}');
+ assert( ptr != NULL );
+ ++ptr;
+ vtmp.bv_len -= ptr - vtmp.bv_val;
+ vtmp.bv_val = ptr;
+ }
+ tmp.bv_len = ibv.bv_len + vtmp.bv_len;
+ tmp.bv_val = ch_malloc( tmp.bv_len + 1 );
+ strcpy( tmp.bv_val, ibv.bv_val );
+ AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len );
+ tmp.bv_val[tmp.bv_len] = '\0';
+ ch_free( a->a_vals[i].bv_val );
+ a->a_vals[i] = tmp;
+
+ if ( a->a_nvals && a->a_nvals != a->a_vals ) {
+ vtmp = a->a_nvals[i];
+ if ( vtmp.bv_val[0] == '{' ) {
+ ptr = ber_bvchr(&vtmp, '}');
+ assert( ptr != NULL );
+ ++ptr;
+ vtmp.bv_len -= ptr - vtmp.bv_val;
+ vtmp.bv_val = ptr;
+ }
+ tmp.bv_len = ibv.bv_len + vtmp.bv_len;
+ tmp.bv_val = ch_malloc( tmp.bv_len + 1 );
+ strcpy( tmp.bv_val, ibv.bv_val );
+ AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len );
+ tmp.bv_val[tmp.bv_len] = '\0';
+ ch_free( a->a_nvals[i].bv_val );
+ a->a_nvals[i] = tmp;
+ }
+ }
+}
+
+/* Sort the values in an X-ORDERED VALUES attribute.
+ * If the values have no index, index them in their given order.
+ * If the values have indexes, sort them.
+ * If some are indexed and some are not, return Error.
+ */
+int
+ordered_value_sort( Attribute *a, int do_renumber )
+{
+ int i, vals;
+ int index = 0, noindex = 0, renumber = 0, gotnvals = 0;
+ struct berval tmp;
+
+ if ( a->a_nvals && a->a_nvals != a->a_vals )
+ gotnvals = 1;
+
+ /* count attrs, look for index */
+ for (i=0; a->a_vals[i].bv_val; i++) {
+ if ( a->a_vals[i].bv_val[0] == '{' ) {
+ char *ptr;
+ index = 1;
+ ptr = ber_bvchr( &a->a_vals[i], '}' );
+ if ( !ptr )
+ return LDAP_INVALID_SYNTAX;
+ if ( noindex )
+ return LDAP_INVALID_SYNTAX;
+ } else {
+ noindex = 1;
+ if ( index )
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+ vals = i;
+
+ /* If values have indexes, sort the values */
+ if ( index ) {
+ int *indexes, j, idx;
+ struct berval ntmp;
+
+#if 0
+ /* Strip index from normalized values */
+ if ( !a->a_nvals || a->a_vals == a->a_nvals ) {
+ a->a_nvals = ch_malloc( (vals+1)*sizeof(struct berval));
+ BER_BVZERO(a->a_nvals+vals);
+ for ( i=0; i<vals; i++ ) {
+ char *ptr = ber_bvchr(&a->a_vals[i], '}') + 1;
+ a->a_nvals[i].bv_len = a->a_vals[i].bv_len -
+ (ptr - a->a_vals[i].bv_val);
+ a->a_nvals[i].bv_val = ch_malloc( a->a_nvals[i].bv_len + 1);
+ strcpy(a->a_nvals[i].bv_val, ptr );
+ }
+ } else {
+ for ( i=0; i<vals; i++ ) {
+ char *ptr = ber_bvchr(&a->a_nvals[i], '}') + 1;
+ a->a_nvals[i].bv_len -= ptr - a->a_nvals[i].bv_val;
+ strcpy(a->a_nvals[i].bv_val, ptr);
+ }
+ }
+#endif
+
+ indexes = ch_malloc( vals * sizeof(int) );
+ for ( i=0; i<vals; i++) {
+ char *ptr;
+ indexes[i] = strtol(a->a_vals[i].bv_val+1, &ptr, 0);
+ if ( *ptr != '}' ) {
+ ch_free( indexes );
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ /* Insertion sort */
+ for ( i=1; i<vals; i++ ) {
+ idx = indexes[i];
+ tmp = a->a_vals[i];
+ if ( gotnvals ) ntmp = a->a_nvals[i];
+ j = i;
+ while ((j > 0) && (indexes[j-1] > idx)) {
+ indexes[j] = indexes[j-1];
+ a->a_vals[j] = a->a_vals[j-1];
+ if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
+ j--;
+ }
+ indexes[j] = idx;
+ a->a_vals[j] = tmp;
+ if ( gotnvals ) a->a_nvals[j] = ntmp;
+ }
+
+ /* If range is not contiguous, must renumber */
+ if ( indexes[0] != 0 || indexes[vals-1] != vals-1 ) {
+ renumber = 1;
+ }
+ ch_free( indexes );
+ } else {
+ renumber = 1;
+ }
+
+ if ( do_renumber && renumber )
+ ordered_value_renumber( a );
+
+ return 0;
+}
+
+/*
+ * wrapper for validate function
+ * uses the validate function of the syntax after removing
+ * the index, if allowed and present
+ */
+int
+ordered_value_validate(
+ AttributeDescription *ad,
+ struct berval *in,
+ int mop )
+{
+ struct berval bv = *in;
+
+ assert( ad->ad_type->sat_syntax != NULL );
+ assert( ad->ad_type->sat_syntax->ssyn_validate != NULL );
+
+ if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+
+ /* Skip past the assertion index */
+ if ( bv.bv_val[0] == '{' ) {
+ char *ptr;
+
+ ptr = ber_bvchr( &bv, '}' );
+ if ( ptr != NULL ) {
+ struct berval ns;
+
+ ns.bv_val = bv.bv_val + 1;
+ ns.bv_len = ptr - ns.bv_val;
+
+ if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) {
+ ptr++;
+ bv.bv_len -= ptr - bv.bv_val;
+ bv.bv_val = ptr;
+ in = &bv;
+ /* If deleting by index, just succeed */
+ if ( mop == LDAP_MOD_DELETE && BER_BVISEMPTY( &bv ) ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ return ad->ad_type->sat_syntax->ssyn_validate( ad->ad_type->sat_syntax, in );
+}
+
+/*
+ * wrapper for pretty function
+ * uses the pretty function of the syntax after removing
+ * the index, if allowed and present; in case, it's prepended
+ * to the pretty value
+ */
+int
+ordered_value_pretty(
+ AttributeDescription *ad,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ struct berval bv,
+ idx = BER_BVNULL;
+ int rc;
+
+ assert( ad->ad_type->sat_syntax != NULL );
+ assert( ad->ad_type->sat_syntax->ssyn_pretty != NULL );
+ assert( val != NULL );
+ assert( out != NULL );
+
+ bv = *val;
+
+ if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+
+ /* Skip past the assertion index */
+ if ( bv.bv_val[0] == '{' ) {
+ char *ptr;
+
+ ptr = ber_bvchr( &bv, '}' );
+ if ( ptr != NULL ) {
+ struct berval ns;
+
+ ns.bv_val = bv.bv_val + 1;
+ ns.bv_len = ptr - ns.bv_val;
+
+ if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) {
+ ptr++;
+
+ idx = bv;
+ idx.bv_len = ptr - bv.bv_val;
+
+ bv.bv_len -= idx.bv_len;
+ bv.bv_val = ptr;
+
+ val = &bv;
+ }
+ }
+ }
+ }
+
+ rc = ad->ad_type->sat_syntax->ssyn_pretty( ad->ad_type->sat_syntax, val, out, ctx );
+
+ if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &idx ) ) {
+ bv = *out;
+
+ out->bv_len = idx.bv_len + bv.bv_len;
+ out->bv_val = slap_sl_malloc( out->bv_len + 1, ctx );
+
+ AC_MEMCPY( out->bv_val, idx.bv_val, idx.bv_len );
+ AC_MEMCPY( &out->bv_val[ idx.bv_len ], bv.bv_val, bv.bv_len + 1 );
+
+ ber_memfree_x( bv.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+/*
+ * wrapper for normalize function
+ * uses the normalize function of the attribute description equality rule
+ * after removing the index, if allowed and present; in case, it's
+ * prepended to the value
+ */
+int
+ordered_value_normalize(
+ slap_mask_t usage,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *normalized,
+ void *ctx )
+{
+ struct berval bv,
+ idx = BER_BVNULL;
+ int rc;
+
+ assert( ad->ad_type->sat_equality != NULL );
+ assert( ad->ad_type->sat_equality->smr_normalize != NULL );
+ assert( val != NULL );
+ assert( normalized != NULL );
+
+ bv = *val;
+
+ if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+
+ /* Skip past the assertion index */
+ if ( bv.bv_val[ 0 ] == '{' ) {
+ char *ptr;
+
+ ptr = ber_bvchr( &bv, '}' );
+ if ( ptr != NULL ) {
+ struct berval ns;
+
+ ns.bv_val = bv.bv_val + 1;
+ ns.bv_len = ptr - ns.bv_val;
+
+ if ( numericStringValidate( NULL, &ns ) == LDAP_SUCCESS ) {
+ ptr++;
+
+ idx = bv;
+ idx.bv_len = ptr - bv.bv_val;
+
+ bv.bv_len -= idx.bv_len;
+ bv.bv_val = ptr;
+
+ /* validator will already prevent this for Adds */
+ if ( BER_BVISEMPTY( &bv )) {
+ ber_dupbv_x( normalized, &idx, ctx );
+ return LDAP_SUCCESS;
+ }
+ val = &bv;
+ }
+ }
+ }
+ }
+
+ rc = ad->ad_type->sat_equality->smr_normalize( usage,
+ ad->ad_type->sat_syntax, mr, val, normalized, ctx );
+
+ if ( rc == LDAP_SUCCESS && !BER_BVISNULL( &idx ) ) {
+ bv = *normalized;
+
+ normalized->bv_len = idx.bv_len + bv.bv_len;
+ normalized->bv_val = slap_sl_malloc( normalized->bv_len + 1, ctx );
+
+ AC_MEMCPY( normalized->bv_val, idx.bv_val, idx.bv_len );
+ AC_MEMCPY( &normalized->bv_val[ idx.bv_len ], bv.bv_val, bv.bv_len + 1 );
+
+ ber_memfree_x( bv.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+/* A wrapper for value match, handles Equality matches for attributes
+ * with ordered values.
+ */
+int
+ordered_value_match(
+ int *match,
+ AttributeDescription *ad,
+ MatchingRule *mr,
+ unsigned flags,
+ struct berval *v1, /* stored value */
+ struct berval *v2, /* assertion */
+ const char ** text )
+{
+ struct berval bv1, bv2;
+
+ /* X-ORDERED VALUES equality matching:
+ * If (SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX) that means we are
+ * comparing two attribute values. In this case, we want to ignore
+ * the ordering index of both values, we just want to know if their
+ * main values are equal.
+ *
+ * If (SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX) then we are comparing
+ * an assertion against an attribute value.
+ * If the assertion has no index, the index of the value is ignored.
+ * If the assertion has only an index, the remainder of the value is
+ * ignored.
+ * If the assertion has index and value, both are compared.
+ */
+ if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
+ char *ptr;
+ struct berval ns1 = BER_BVNULL, ns2 = BER_BVNULL;
+
+ bv1 = *v1;
+ bv2 = *v2;
+
+ /* Skip past the assertion index */
+ if ( bv2.bv_val[0] == '{' ) {
+ ptr = ber_bvchr( &bv2, '}' );
+ if ( ptr != NULL ) {
+ ns2.bv_val = bv2.bv_val + 1;
+ ns2.bv_len = ptr - ns2.bv_val;
+
+ if ( numericStringValidate( NULL, &ns2 ) == LDAP_SUCCESS ) {
+ ptr++;
+ bv2.bv_len -= ptr - bv2.bv_val;
+ bv2.bv_val = ptr;
+ v2 = &bv2;
+ }
+ }
+ }
+
+ /* Skip past the attribute index */
+ if ( bv1.bv_val[0] == '{' ) {
+ ptr = ber_bvchr( &bv1, '}' );
+ if ( ptr != NULL ) {
+ ns1.bv_val = bv1.bv_val + 1;
+ ns1.bv_len = ptr - ns1.bv_val;
+
+ if ( numericStringValidate( NULL, &ns1 ) == LDAP_SUCCESS ) {
+ ptr++;
+ bv1.bv_len -= ptr - bv1.bv_val;
+ bv1.bv_val = ptr;
+ v1 = &bv1;
+ }
+ }
+ }
+
+ if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( flags )) {
+ if ( !BER_BVISNULL( &ns2 ) && !BER_BVISNULL( &ns1 ) ) {
+ /* compare index values first */
+ (void)octetStringOrderingMatch( match, 0, NULL, NULL, &ns1, &ns2 );
+
+ /* If not equal, or we're only comparing the index,
+ * return result now.
+ */
+ if ( *match != 0 || BER_BVISEMPTY( &bv2 ) ) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ }
+
+ if ( !mr || !mr->smr_match ) {
+ *match = ber_bvcmp( v1, v2 );
+ return LDAP_SUCCESS;
+ }
+
+ return value_match( match, ad, mr, flags, v1, v2, text );
+}
+
+int
+ordered_value_add(
+ Entry *e,
+ AttributeDescription *ad,
+ Attribute *a,
+ BerVarray vals,
+ BerVarray nvals
+)
+{
+ int i, j, k, anum, vnum;
+ BerVarray new, nnew = NULL;
+
+ /* count new vals */
+ for (i=0; !BER_BVISNULL( vals+i ); i++) ;
+ vnum = i;
+
+ if ( a ) {
+ ordered_value_sort( a, 0 );
+ } else {
+ Attribute **ap;
+ for ( ap=&e->e_attrs; *ap; ap = &(*ap)->a_next ) ;
+ a = attr_alloc( ad );
+ *ap = a;
+ }
+ anum = a->a_numvals;
+
+ new = ch_malloc( (anum+vnum+1) * sizeof(struct berval));
+
+ /* sanity check: if normalized modifications come in, either
+ * no values are present or normalized existing values differ
+ * from non-normalized; if no normalized modifications come in,
+ * either no values are present or normalized existing values
+ * don't differ from non-normalized */
+ if ( nvals != NULL ) {
+ assert( nvals != vals );
+ assert( a->a_nvals == NULL || a->a_nvals != a->a_vals );
+
+ } else {
+ assert( a->a_nvals == NULL || a->a_nvals == a->a_vals );
+ }
+
+ if ( ( a->a_nvals && a->a_nvals != a->a_vals ) || nvals != NULL ) {
+ nnew = ch_malloc( (anum+vnum+1) * sizeof(struct berval));
+ /* Shouldn't happen... */
+ if ( !nvals ) nvals = vals;
+ }
+ if ( anum ) {
+ AC_MEMCPY( new, a->a_vals, anum * sizeof(struct berval));
+ if ( nnew && a->a_nvals )
+ AC_MEMCPY( nnew, a->a_nvals, anum * sizeof(struct berval));
+ }
+
+ for (i=0; i<vnum; i++) {
+ char *next;
+
+ k = -1;
+ if ( vals[i].bv_val[0] == '{' ) {
+ /* FIXME: strtol() could go past end... */
+ k = strtol( vals[i].bv_val + 1, &next, 0 );
+ if ( next == vals[i].bv_val + 1 ||
+ next[ 0 ] != '}' ||
+ (ber_len_t) (next - vals[i].bv_val) > vals[i].bv_len )
+ {
+ ch_free( nnew );
+ ch_free( new );
+ return -1;
+ }
+ if ( k > anum ) k = -1;
+ }
+ /* No index, or index is greater than current number of
+ * values, just tack onto the end
+ */
+ if ( k < 0 ) {
+ ber_dupbv( new+anum, vals+i );
+ if ( nnew ) ber_dupbv( nnew+anum, nvals+i );
+
+ /* Indexed, push everything else down one and insert */
+ } else {
+ for (j=anum; j>k; j--) {
+ new[j] = new[j-1];
+ if ( nnew ) nnew[j] = nnew[j-1];
+ }
+ ber_dupbv( new+k, vals+i );
+ if ( nnew ) ber_dupbv( nnew+k, nvals+i );
+ }
+ anum++;
+ }
+ BER_BVZERO( new+anum );
+ ch_free( a->a_vals );
+ a->a_vals = new;
+ if ( nnew ) {
+ BER_BVZERO( nnew+anum );
+ ch_free( a->a_nvals );
+ a->a_nvals = nnew;
+ } else {
+ a->a_nvals = a->a_vals;
+ }
+
+ a->a_numvals = anum;
+ ordered_value_renumber( a );
+
+ return 0;
+}
diff --git a/servers/slapd/verbs.c b/servers/slapd/verbs.c
new file mode 100644
index 0000000..36c53e3
--- /dev/null
+++ b/servers/slapd/verbs.c
@@ -0,0 +1,276 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "slap.h"
+#include "slap-config.h"
+
+int
+bverb_to_mask( struct berval *bword, slap_verbmasks *v ) {
+ int i;
+ for ( i = 0; !BER_BVISNULL(&v[i].word); i++ ) {
+ if ( !ber_bvstrcasecmp( bword, &v[i].word) ) break;
+ }
+ return i;
+}
+
+int
+verb_to_mask( const char *word, slap_verbmasks *v ) {
+ struct berval bword;
+ ber_str2bv( word, 0, 0, &bword );
+ return bverb_to_mask( &bword, v );
+}
+
+int
+verbs_to_mask( int argc, char *argv[], slap_verbmasks *v, slap_mask_t *m ) {
+ int i, j;
+ for (i = 1; i < argc; i++ ) {
+ j = verb_to_mask( argv[i], v );
+ if ( BER_BVISNULL(&v[j].word) ) return i;
+ while ( !v[j].mask ) j--;
+ *m |= v[j].mask;
+ }
+ return 0;
+}
+
+/*
+ * Mask keywords that represent multiple bits should occur before single
+ * bit keywords in the verbmasks array.
+ */
+int
+mask_to_verbs( slap_verbmasks *v, slap_mask_t m, BerVarray *bva ) {
+ int i, rc = 1;
+
+ if ( m ) {
+ for ( i=0; !BER_BVISNULL(&v[i].word); i++ ) {
+ if (!v[i].mask) continue;
+ if ( ( m & v[i].mask ) == v[i].mask ) {
+ value_add_one( bva, &v[i].word );
+ rc = 0;
+ m ^= v[i].mask;
+ if ( !m ) break;
+ }
+ }
+ }
+ return rc;
+}
+
+/* Return the verbs as a single string, separated by delim */
+int
+mask_to_verbstring( slap_verbmasks *v, slap_mask_t m0, char delim, struct berval *bv )
+{
+ int i, rc = 1;
+
+ BER_BVZERO( bv );
+ if ( m0 ) {
+ slap_mask_t m = m0;
+ char *ptr;
+ for ( i=0; !BER_BVISNULL(&v[i].word); i++ ) {
+ if ( !v[i].mask ) continue;
+ if ( ( m & v[i].mask ) == v[i].mask ) {
+ bv->bv_len += v[i].word.bv_len + 1;
+ rc = 0;
+ m ^= v[i].mask;
+ if ( !m ) break;
+ }
+ }
+ bv->bv_val = ch_malloc(bv->bv_len);
+ bv->bv_len--;
+ ptr = bv->bv_val;
+ m = m0;
+ for ( i=0; !BER_BVISNULL(&v[i].word); i++ ) {
+ if ( !v[i].mask ) continue;
+ if ( ( m & v[i].mask ) == v[i].mask ) {
+ ptr = lutil_strcopy( ptr, v[i].word.bv_val );
+ *ptr++ = delim;
+ m ^= v[i].mask;
+ if ( !m ) break;
+ }
+ }
+ ptr[-1] = '\0';
+ }
+ return rc;
+}
+
+/* Parse a verbstring */
+int
+verbstring_to_mask( slap_verbmasks *v, char *str, char delim, slap_mask_t *m ) {
+ int j;
+ char *d;
+ struct berval bv;
+
+ do {
+ bv.bv_val = str;
+ d = strchr( str, delim );
+ if ( d )
+ bv.bv_len = d - str;
+ else
+ bv.bv_len = strlen( str );
+ j = bverb_to_mask( &bv, v );
+ if ( BER_BVISNULL(&v[j].word) ) return 1;
+ while ( !v[j].mask ) j--;
+ *m |= v[j].mask;
+ str += bv.bv_len + 1;
+ } while ( d );
+ return 0;
+}
+
+int
+slap_verbmasks_init( slap_verbmasks **vp, slap_verbmasks *v )
+{
+ int i;
+
+ assert( *vp == NULL );
+
+ for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) /* EMPTY */;
+
+ *vp = ch_calloc( i + 1, sizeof( slap_verbmasks ) );
+
+ for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) {
+ ber_dupbv( &(*vp)[ i ].word, &v[ i ].word );
+ *((slap_mask_t *)&(*vp)[ i ].mask) = v[ i ].mask;
+ }
+
+ BER_BVZERO( &(*vp)[ i ].word );
+
+ return 0;
+}
+
+int
+slap_verbmasks_destroy( slap_verbmasks *v )
+{
+ int i;
+
+ assert( v != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &v[ i ].word ); i++ ) {
+ ch_free( v[ i ].word.bv_val );
+ }
+
+ ch_free( v );
+
+ return 0;
+}
+
+int
+slap_verbmasks_append(
+ slap_verbmasks **vp,
+ slap_mask_t m,
+ struct berval *v,
+ slap_mask_t *ignore )
+{
+ int i;
+
+ if ( !m ) {
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &(*vp)[ i ].word ); i++ ) {
+ if ( !(*vp)[ i ].mask ) continue;
+
+ if ( ignore != NULL ) {
+ int j;
+
+ for ( j = 0; ignore[ j ] != 0; j++ ) {
+ if ( (*vp)[ i ].mask == ignore[ j ] ) {
+ goto check_next;
+ }
+ }
+ }
+
+ if ( ( m & (*vp)[ i ].mask ) == (*vp)[ i ].mask ) {
+ if ( ber_bvstrcasecmp( v, &(*vp)[ i ].word ) == 0 ) {
+ /* already set; ignore */
+ return LDAP_SUCCESS;
+ }
+ /* conflicts */
+ return LDAP_TYPE_OR_VALUE_EXISTS;
+ }
+
+ if ( m & (*vp)[ i ].mask ) {
+ /* conflicts */
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+check_next:;
+ }
+
+ *vp = ch_realloc( *vp, sizeof( slap_verbmasks ) * ( i + 2 ) );
+ ber_dupbv( &(*vp)[ i ].word, v );
+ *((slap_mask_t *)&(*vp)[ i ].mask) = m;
+ BER_BVZERO( &(*vp)[ i + 1 ].word );
+
+ return LDAP_SUCCESS;
+}
+
+int
+enum_to_verb(slap_verbmasks *v, slap_mask_t m, struct berval *bv) {
+ int i;
+
+ for (i=0; !BER_BVISNULL(&v[i].word); i++) {
+ if ( m == v[i].mask ) {
+ if ( bv != NULL ) {
+ *bv = v[i].word;
+ }
+ return i;
+ }
+ }
+ return -1;
+}
+
+/* register a new verbmask */
+int
+slap_verbmask_register(
+ slap_verbmasks *vm_,
+ slap_verbmasks **vmp,
+ struct berval *bv,
+ int mask )
+{
+ slap_verbmasks *vm = *vmp;
+ int i;
+
+ /* check for duplicate word */
+ /* NOTE: we accept duplicate codes; the first occurrence will be used
+ * when mapping from mask to verb */
+ i = verb_to_mask( bv->bv_val, vm );
+ if ( !BER_BVISNULL( &vm[ i ].word ) ) {
+ return -1;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &vm[ i ].word ); i++ )
+ ;
+
+ if ( vm == vm_ ) {
+ /* first time: duplicate array */
+ vm = ch_calloc( i + 2, sizeof( slap_verbmasks ) );
+ for ( i = 0; !BER_BVISNULL( &vm_[ i ].word ); i++ )
+ {
+ ber_dupbv( &vm[ i ].word, &vm_[ i ].word );
+ *((slap_mask_t*)&vm[ i ].mask) = vm_[ i ].mask;
+ }
+
+ } else {
+ vm = ch_realloc( vm, (i + 2) * sizeof( slap_verbmasks ) );
+ }
+
+ ber_dupbv( &vm[ i ].word, bv );
+ *((slap_mask_t*)&vm[ i ].mask) = mask;
+
+ BER_BVZERO( &vm[ i+1 ].word );
+
+ *vmp = vm;
+
+ return i;
+}
diff --git a/servers/slapd/zn_malloc.c b/servers/slapd/zn_malloc.c
new file mode 100644
index 0000000..e2cdc21
--- /dev/null
+++ b/servers/slapd/zn_malloc.c
@@ -0,0 +1,970 @@
+/* zn_malloc.c - zone-based malloc routines */
+/* $OpenLDAP$*/
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* Portions Copyright 2004 IBM Corporation
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ */
+/* ACKNOWLEDGEMENTS
+ * This work originally developed by Jong-Hyuk Choi for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "slap.h"
+
+#ifdef SLAP_ZONE_ALLOC
+
+#include <sys/mman.h>
+
+static int slap_zone_cmp(const void *v1, const void *v2);
+void * slap_replenish_zopool(void *ctx);
+
+static void
+slap_zo_release(void *data)
+{
+ struct zone_object *zo = (struct zone_object *)data;
+ ch_free( zo );
+}
+
+void
+slap_zn_mem_destroy(
+ void *ctx
+)
+{
+ struct zone_heap *zh = ctx;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ int order_start = -1, i, j;
+ struct zone_object *zo;
+
+ pad_shift = pad - 1;
+ do {
+ order_start++;
+ } while (pad_shift >>= 1);
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ for (i = 0; i < zh->zh_zoneorder - order_start + 1; i++) {
+ zo = LDAP_LIST_FIRST(&zh->zh_free[i]);
+ while (zo) {
+ struct zone_object *zo_tmp = zo;
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ LDAP_LIST_REMOVE(zo_tmp, zo_link);
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_tmp, zo_link);
+ }
+ }
+ ch_free(zh->zh_free);
+
+ for (i = 0; i < zh->zh_numzones; i++) {
+ for (j = 0; j < zh->zh_zoneorder - order_start + 1; j++) {
+ ch_free(zh->zh_maps[i][j]);
+ }
+ ch_free(zh->zh_maps[i]);
+ munmap(zh->zh_zones[i], zh->zh_zonesize);
+ ldap_pvt_thread_rdwr_destroy(&zh->zh_znlock[i]);
+ }
+ ch_free(zh->zh_maps);
+ ch_free(zh->zh_zones);
+ ch_free(zh->zh_seqno);
+ ch_free(zh->zh_znlock);
+
+ ldap_avl_free(zh->zh_zonetree, slap_zo_release);
+
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ while (zo) {
+ struct zone_object *zo_tmp = zo;
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ if (!zo_tmp->zo_blockhead) {
+ LDAP_LIST_REMOVE(zo_tmp, zo_link);
+ }
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ while (zo) {
+ struct zone_object *zo_tmp = zo;
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ ch_free(zo_tmp);
+ }
+ ldap_pvt_thread_mutex_unlock(&zh->zh_mutex);
+ ldap_pvt_thread_rdwr_destroy(&zh->zh_lock);
+ ldap_pvt_thread_mutex_destroy(&zh->zh_mutex);
+ ch_free(zh);
+}
+
+void *
+slap_zn_mem_create(
+ ber_len_t initsize,
+ ber_len_t maxsize,
+ ber_len_t deltasize,
+ ber_len_t zonesize
+)
+{
+ struct zone_heap *zh = NULL;
+ ber_len_t zpad;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ int size_shift;
+ int order = -1, order_start = -1, order_end = -1;
+ int i, j;
+ struct zone_object *zo;
+
+ Debug(LDAP_DEBUG_NONE,
+ "--> slap_zn_mem_create: initsize=%d, maxsize=%d\n",
+ initsize, maxsize );
+ Debug(LDAP_DEBUG_NONE,
+ "++> slap_zn_mem_create: deltasize=%d, zonesize=%d\n",
+ deltasize, zonesize );
+
+ zh = (struct zone_heap *)ch_calloc(1, sizeof(struct zone_heap));
+
+ zh->zh_fd = open("/dev/zero", O_RDWR);
+
+ if ( zonesize ) {
+ zh->zh_zonesize = zonesize;
+ } else {
+ zh->zh_zonesize = SLAP_ZONE_SIZE;
+ }
+
+ zpad = zh->zh_zonesize - 1;
+ zh->zh_numzones = ((initsize + zpad) & ~zpad) / zh->zh_zonesize;
+
+ if ( maxsize && maxsize >= initsize ) {
+ zh->zh_maxzones = ((maxsize + zpad) & ~zpad) / zh->zh_zonesize;
+ } else {
+ zh->zh_maxzones = ((initsize + zpad) & ~zpad) / zh->zh_zonesize;
+ }
+
+ if ( deltasize ) {
+ zh->zh_deltazones = ((deltasize + zpad) & ~zpad) / zh->zh_zonesize;
+ } else {
+ zh->zh_deltazones = ((SLAP_ZONE_DELTA+zpad) & ~zpad) / zh->zh_zonesize;
+ }
+
+ size_shift = zh->zh_zonesize - 1;
+ do {
+ order_end++;
+ } while (size_shift >>= 1);
+
+ pad_shift = pad - 1;
+ do {
+ order_start++;
+ } while (pad_shift >>= 1);
+
+ order = order_end - order_start + 1;
+
+ zh->zh_zones = (void **)ch_malloc(zh->zh_maxzones * sizeof(void*));
+ zh->zh_znlock = (ldap_pvt_thread_rdwr_t *)ch_malloc(
+ zh->zh_maxzones * sizeof(ldap_pvt_thread_rdwr_t *));
+ zh->zh_maps = (unsigned char ***)ch_malloc(
+ zh->zh_maxzones * sizeof(unsigned char**));
+
+ zh->zh_zoneorder = order_end;
+ zh->zh_free = (struct zh_freelist *)
+ ch_malloc(order * sizeof(struct zh_freelist));
+ zh->zh_seqno = (unsigned long *)ch_calloc(zh->zh_maxzones,
+ sizeof(unsigned long));
+ for (i = 0; i < order; i++) {
+ LDAP_LIST_INIT(&zh->zh_free[i]);
+ }
+ LDAP_LIST_INIT(&zh->zh_zopool);
+
+ for (i = 0; i < zh->zh_numzones; i++) {
+ zh->zh_zones[i] = mmap(0, zh->zh_zonesize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, zh->zh_fd, 0);
+ zh->zh_maps[i] = (unsigned char **)
+ ch_malloc(order * sizeof(unsigned char *));
+ for (j = 0; j < order; j++) {
+ int shiftamt = order_start + 1 + j;
+ int nummaps = zh->zh_zonesize >> shiftamt;
+ assert(nummaps);
+ nummaps >>= 3;
+ if (!nummaps) nummaps = 1;
+ zh->zh_maps[i][j] = (unsigned char *)ch_malloc(nummaps);
+ memset(zh->zh_maps[i][j], 0, nummaps);
+ }
+
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = zh->zh_zones[i];
+ zo->zo_idx = i;
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[order-1], zo, zo_link);
+
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = zh->zh_zones[i];
+ zo->zo_siz = zh->zh_zonesize;
+ zo->zo_idx = i;
+ ldap_avl_insert(&zh->zh_zonetree, zo, slap_zone_cmp, ldap_avl_dup_error);
+ ldap_pvt_thread_rdwr_init(&zh->zh_znlock[i]);
+ }
+
+ LDAP_STAILQ_INIT(&zh->zh_latency_history_queue);
+ ldap_pvt_thread_mutex_init(&zh->zh_mutex);
+ ldap_pvt_thread_rdwr_init(&zh->zh_lock);
+
+ return zh;
+}
+
+void *
+slap_zn_malloc(
+ ber_len_t size,
+ void *ctx
+)
+{
+ struct zone_heap *zh = ctx;
+ ber_len_t size_shift;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ int order = -1, order_start = -1;
+ struct zone_object *zo, *zo_new, *zo_left, *zo_right;
+ ber_len_t *ptr, *new;
+ int idx;
+ unsigned long diff;
+ int i, j, k;
+
+ Debug(LDAP_DEBUG_NONE,
+ "--> slap_zn_malloc: size=%d\n", size );
+
+ if (!zh) return ber_memalloc_x(size, NULL);
+
+ /* round up to doubleword boundary */
+ size += 2*sizeof(ber_len_t) + pad;
+ size &= ~pad;
+
+ size_shift = size - 1;
+ do {
+ order++;
+ } while (size_shift >>= 1);
+
+ pad_shift = pad - 1;
+ do {
+ order_start++;
+ } while (pad_shift >>= 1);
+
+retry:
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ for (i = order; i <= zh->zh_zoneorder &&
+ LDAP_LIST_EMPTY(&zh->zh_free[i-order_start]); i++);
+
+ if (i == order) {
+ zo_new = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]);
+ LDAP_LIST_REMOVE(zo_new, zo_link);
+ ptr = zo_new->zo_ptr;
+ idx = zo_new->zo_idx;
+ diff = (unsigned long)((char*)ptr -
+ (char*)zh->zh_zones[idx]) >> (order + 1);
+ zh->zh_maps[idx][order-order_start][diff>>3] |= (1 << (diff & 0x7));
+ *ptr++ = zh->zh_seqno[idx];
+ *ptr++ = size - 2*sizeof(ber_len_t);
+ zo_new->zo_ptr = NULL;
+ zo_new->zo_idx = -1;
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_new, zo_link);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ Debug(LDAP_DEBUG_NONE, "slap_zn_malloc: returning 0x%x, 0x%x\n",
+ ptr, (int)ptr>>(zh->zh_zoneorder+1) );
+ return((void*)ptr);
+ } else if (i <= zh->zh_zoneorder) {
+ for (j = i; j > order; j--) {
+ zo_left = LDAP_LIST_FIRST(&zh->zh_free[j-order_start]);
+ LDAP_LIST_REMOVE(zo_left, zo_link);
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo_right = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo_right, zo_link);
+ zo_right->zo_ptr = zo_left->zo_ptr + (1 << j);
+ zo_right->zo_idx = zo_left->zo_idx;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_malloc: split (left=0x%x, right=0x%x)\n",
+ zo_left->zo_ptr, zo_right->zo_ptr );
+ if (j == order + 1) {
+ ptr = zo_left->zo_ptr;
+ diff = (unsigned long)((char*)ptr -
+ (char*)zh->zh_zones[zo_left->zo_idx]) >> (order+1);
+ zh->zh_maps[zo_left->zo_idx][order-order_start][diff>>3] |=
+ (1 << (diff & 0x7));
+ *ptr++ = zh->zh_seqno[zo_left->zo_idx];
+ *ptr++ = size - 2*sizeof(ber_len_t);
+ LDAP_LIST_INSERT_HEAD(
+ &zh->zh_free[j-1-order_start], zo_right, zo_link);
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_left, zo_link);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_malloc: returning 0x%x, 0x%x\n",
+ ptr, (int)ptr>>(zh->zh_zoneorder+1) );
+ return((void*)ptr);
+ } else {
+ LDAP_LIST_INSERT_HEAD(
+ &zh->zh_free[j-1-order_start], zo_right, zo_link);
+ LDAP_LIST_INSERT_HEAD(
+ &zh->zh_free[j-1-order_start], zo_left, zo_link);
+ }
+ }
+ assert(0);
+ } else {
+
+ if ( zh->zh_maxzones < zh->zh_numzones + zh->zh_deltazones ) {
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ Debug( LDAP_DEBUG_TRACE,
+ "zn_malloc %lu: ch_malloc\n",
+ (long)size );
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_malloc: returning 0x%x, 0x%x\n",
+ ptr, (int)ptr>>(zh->zh_zoneorder+1) );
+ return (void*)ch_malloc(size);
+ }
+
+ for (i = zh->zh_numzones; i < zh->zh_numzones+zh->zh_deltazones; i++) {
+ zh->zh_zones[i] = mmap(0, zh->zh_zonesize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, zh->zh_fd, 0);
+ zh->zh_maps[i] = (unsigned char **)
+ ch_malloc((zh->zh_zoneorder - order_start + 1) *
+ sizeof(unsigned char *));
+ for (j = 0; j < zh->zh_zoneorder-order_start+1; j++) {
+ int shiftamt = order_start + 1 + j;
+ int nummaps = zh->zh_zonesize >> shiftamt;
+ assert(nummaps);
+ nummaps >>= 3;
+ if (!nummaps) nummaps = 1;
+ zh->zh_maps[i][j] = (unsigned char *)ch_malloc(nummaps);
+ memset(zh->zh_maps[i][j], 0, nummaps);
+ }
+
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = zh->zh_zones[i];
+ zo->zo_idx = i;
+ LDAP_LIST_INSERT_HEAD(&zh->
+ zh_free[zh->zh_zoneorder-order_start],zo,zo_link);
+
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = zh->zh_zones[i];
+ zo->zo_siz = zh->zh_zonesize;
+ zo->zo_idx = i;
+ ldap_avl_insert(&zh->zh_zonetree, zo, slap_zone_cmp, ldap_avl_dup_error);
+ ldap_pvt_thread_rdwr_init(&zh->zh_znlock[i]);
+ }
+ zh->zh_numzones += zh->zh_deltazones;
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ goto retry;
+ }
+}
+
+void *
+slap_zn_calloc( ber_len_t n, ber_len_t size, void *ctx )
+{
+ void *new;
+
+ new = slap_zn_malloc( n*size, ctx );
+ if ( new ) {
+ memset( new, 0, n*size );
+ }
+ return new;
+}
+
+void *
+slap_zn_realloc(void *ptr, ber_len_t size, void *ctx)
+{
+ struct zone_heap *zh = ctx;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ int order_start = -1, order = -1;
+ struct zone_object zoi, *zoo;
+ ber_len_t *p = (ber_len_t *)ptr, *new;
+ unsigned long diff;
+ int i;
+ void *newptr = NULL;
+ struct zone_heap *zone = NULL;
+
+ Debug(LDAP_DEBUG_NONE,
+ "--> slap_zn_realloc: ptr=0x%x, size=%d\n", ptr, size );
+
+ if (ptr == NULL)
+ return slap_zn_malloc(size, zh);
+
+ zoi.zo_ptr = p;
+ zoi.zo_idx = -1;
+
+ if (zh) {
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ }
+
+ /* Not our memory? */
+ if (!zoo) {
+ /* duplicate of realloc behavior, oh well */
+ new = ber_memrealloc_x(ptr, size, NULL);
+ if (new) {
+ return new;
+ }
+ Debug(LDAP_DEBUG_ANY, "ch_realloc of %lu bytes failed\n",
+ (long) size );
+ assert(0);
+ exit( EXIT_FAILURE );
+ }
+
+ assert(zoo->zo_idx != -1);
+
+ zone = zh->zh_zones[zoo->zo_idx];
+
+ if (size == 0) {
+ slap_zn_free(ptr, zh);
+ return NULL;
+ }
+
+ newptr = slap_zn_malloc(size, zh);
+ if (size < p[-1]) {
+ AC_MEMCPY(newptr, ptr, size);
+ } else {
+ AC_MEMCPY(newptr, ptr, p[-1]);
+ }
+ slap_zn_free(ptr, zh);
+ return newptr;
+}
+
+void
+slap_zn_free(void *ptr, void *ctx)
+{
+ struct zone_heap *zh = ctx;
+ int size, size_shift, order_size;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ ber_len_t *p = (ber_len_t *)ptr, *tmpp;
+ int order_start = -1, order = -1;
+ struct zone_object zoi, *zoo, *zo;
+ unsigned long diff;
+ int i, k, inserted = 0, idx;
+ struct zone_heap *zone = NULL;
+
+ zoi.zo_ptr = p;
+ zoi.zo_idx = -1;
+
+ Debug(LDAP_DEBUG_NONE, "--> slap_zn_free: ptr=0x%x\n", ptr );
+
+ if (zh) {
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ }
+
+ if (!zoo) {
+ ber_memfree_x(ptr, NULL);
+ } else {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ zone = zh->zh_zones[idx];
+
+ size = *(--p);
+ size_shift = size + 2*sizeof(ber_len_t) - 1;
+ do {
+ order++;
+ } while (size_shift >>= 1);
+
+ pad_shift = pad - 1;
+ do {
+ order_start++;
+ } while (pad_shift >>= 1);
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ for (i = order, tmpp = p; i <= zh->zh_zoneorder; i++) {
+ order_size = 1 << (i+1);
+ diff = (unsigned long)((char*)tmpp - (char*)zone) >> (i+1);
+ zh->zh_maps[idx][i-order_start][diff>>3] &= (~(1 << (diff & 0x7)));
+ if (diff == ((diff>>1)<<1)) {
+ if (!(zh->zh_maps[idx][i-order_start][(diff+1)>>3] &
+ (1<<((diff+1)&0x7)))) {
+ zo = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]);
+ while (zo) {
+ if ((char*)zo->zo_ptr == (char*)tmpp) {
+ LDAP_LIST_REMOVE( zo, zo_link );
+ } else if ((char*)zo->zo_ptr ==
+ (char*)tmpp + order_size) {
+ LDAP_LIST_REMOVE(zo, zo_link);
+ break;
+ }
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ }
+ if (zo) {
+ if (i < zh->zh_zoneorder) {
+ inserted = 1;
+ zo->zo_ptr = tmpp;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start+1],
+ zo, zo_link);
+ }
+ continue;
+ } else {
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = tmpp;
+ zo->zo_idx = idx;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start],
+ zo, zo_link);
+ break;
+
+ Debug(LDAP_DEBUG_ANY, "slap_zn_free: "
+ "free object not found while bit is clear.\n" );
+ assert(zo != NULL);
+
+ }
+ } else {
+ if (!inserted) {
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = tmpp;
+ zo->zo_idx = idx;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start],
+ zo, zo_link);
+ }
+ break;
+ }
+ } else {
+ if (!(zh->zh_maps[idx][i-order_start][(diff-1)>>3] &
+ (1<<((diff-1)&0x7)))) {
+ zo = LDAP_LIST_FIRST(&zh->zh_free[i-order_start]);
+ while (zo) {
+ if ((char*)zo->zo_ptr == (char*)tmpp) {
+ LDAP_LIST_REMOVE(zo, zo_link);
+ } else if ((char*)tmpp == zo->zo_ptr + order_size) {
+ LDAP_LIST_REMOVE(zo, zo_link);
+ tmpp = zo->zo_ptr;
+ break;
+ }
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ }
+ if (zo) {
+ if (i < zh->zh_zoneorder) {
+ inserted = 1;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start+1],
+ zo, zo_link);
+ continue;
+ }
+ } else {
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = tmpp;
+ zo->zo_idx = idx;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start],
+ zo, zo_link);
+ break;
+
+ Debug(LDAP_DEBUG_ANY, "slap_zn_free: "
+ "free object not found while bit is clear.\n" );
+ assert(zo != NULL);
+
+ }
+ } else {
+ if ( !inserted ) {
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = tmpp;
+ zo->zo_idx = idx;
+ Debug(LDAP_DEBUG_NONE,
+ "slap_zn_free: merging 0x%x\n",
+ zo->zo_ptr );
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[i-order_start],
+ zo, zo_link);
+ }
+ break;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ }
+}
+
+static int
+slap_zone_cmp(const void *v1, const void *v2)
+{
+ const struct zone_object *zo1 = v1;
+ const struct zone_object *zo2 = v2;
+ char *ptr1;
+ char *ptr2;
+ ber_len_t zpad;
+
+ zpad = zo2->zo_siz - 1;
+ ptr1 = (char*)(((unsigned long)zo1->zo_ptr + zpad) & ~zpad);
+ ptr2 = (char*)zo2->zo_ptr + ((char*)ptr1 - (char*)zo1->zo_ptr);
+ ptr2 = (char*)(((unsigned long)ptr2 + zpad) & ~zpad);
+ return (int)((char*)ptr1 - (char*)ptr2);
+}
+
+void *
+slap_replenish_zopool(
+ void *ctx
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object *zo_block;
+ int i;
+
+ zo_block = (struct zone_object *)ch_malloc(
+ SLAP_ZONE_ZOBLOCK * sizeof(struct zone_object));
+
+ if ( zo_block == NULL ) {
+ return NULL;
+ }
+
+ zo_block[0].zo_blockhead = 1;
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, &zo_block[0], zo_link);
+ for (i = 1; i < SLAP_ZONE_ZOBLOCK; i++) {
+ zo_block[i].zo_blockhead = 0;
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, &zo_block[i], zo_link );
+ }
+
+ return zo_block;
+}
+
+int
+slap_zn_invalidate(
+ void *ctx,
+ void *ptr
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int seqno = *((ber_len_t*)ptr - 2);
+ int idx = -1, rc = 0;
+ int pad = 2*sizeof(int)-1, pad_shift;
+ int order_start = -1, i;
+ struct zone_object *zo;
+
+ pad_shift = pad - 1;
+ do {
+ order_start++;
+ } while (pad_shift >>= 1);
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ madvise(zh->zh_zones[idx], zh->zh_zonesize, MADV_DONTNEED);
+ for (i = 0; i < zh->zh_zoneorder - order_start + 1; i++) {
+ int shiftamt = order_start + 1 + i;
+ int nummaps = zh->zh_zonesize >> shiftamt;
+ assert(nummaps);
+ nummaps >>= 3;
+ if (!nummaps) nummaps = 1;
+ memset(zh->zh_maps[idx][i], 0, nummaps);
+ zo = LDAP_LIST_FIRST(&zh->zh_free[i]);
+ while (zo) {
+ struct zone_object *zo_tmp = zo;
+ zo = LDAP_LIST_NEXT(zo, zo_link);
+ if (zo_tmp && zo_tmp->zo_idx == idx) {
+ LDAP_LIST_REMOVE(zo_tmp, zo_link);
+ LDAP_LIST_INSERT_HEAD(&zh->zh_zopool, zo_tmp, zo_link);
+ }
+ }
+ }
+ if (LDAP_LIST_EMPTY(&zh->zh_zopool)) {
+ slap_replenish_zopool(zh);
+ }
+ zo = LDAP_LIST_FIRST(&zh->zh_zopool);
+ LDAP_LIST_REMOVE(zo, zo_link);
+ zo->zo_ptr = zh->zh_zones[idx];
+ zo->zo_idx = idx;
+ LDAP_LIST_INSERT_HEAD(&zh->zh_free[zh->zh_zoneorder-order_start],
+ zo, zo_link);
+ zh->zh_seqno[idx]++;
+ } else {
+ Debug(LDAP_DEBUG_NONE, "zone not found for (ctx=0x%x, ptr=0x%x) !\n",
+ ctx, ptr );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+ Debug(LDAP_DEBUG_NONE, "zone %d invalidate\n", idx );
+ return rc;
+}
+
+int
+slap_zn_validate(
+ void *ctx,
+ void *ptr,
+ int seqno
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int idx, rc = 0;
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ assert(seqno <= zh->zh_seqno[idx]);
+ rc = (seqno == zh->zh_seqno[idx]);
+ }
+
+ return rc;
+}
+
+int slap_zh_rlock(
+ void *ctx
+)
+{
+ struct zone_heap* zh = ctx;
+ ldap_pvt_thread_rdwr_rlock(&zh->zh_lock);
+}
+
+int slap_zh_runlock(
+ void *ctx
+)
+{
+ struct zone_heap* zh = ctx;
+ ldap_pvt_thread_rdwr_runlock(&zh->zh_lock);
+}
+
+int slap_zh_wlock(
+ void *ctx
+)
+{
+ struct zone_heap* zh = ctx;
+ ldap_pvt_thread_rdwr_wlock(&zh->zh_lock);
+}
+
+int slap_zh_wunlock(
+ void *ctx
+)
+{
+ struct zone_heap* zh = ctx;
+ ldap_pvt_thread_rdwr_wunlock(&zh->zh_lock);
+}
+
+int slap_zn_rlock(
+ void *ctx,
+ void *ptr
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int idx;
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ ldap_pvt_thread_rdwr_rlock(&zh->zh_znlock[idx]);
+ }
+}
+
+int slap_zn_runlock(
+ void *ctx,
+ void *ptr
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int idx;
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ ldap_pvt_thread_rdwr_runlock(&zh->zh_znlock[idx]);
+ }
+}
+
+int slap_zn_wlock(
+ void *ctx,
+ void *ptr
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int idx;
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ ldap_pvt_thread_rdwr_wlock(&zh->zh_znlock[idx]);
+ }
+}
+
+int slap_zn_wunlock(
+ void *ctx,
+ void *ptr
+)
+{
+ struct zone_heap* zh = ctx;
+ struct zone_object zoi, *zoo;
+ struct zone_heap *zone = NULL;
+ int idx;
+
+ zoi.zo_ptr = ptr;
+ zoi.zo_idx = -1;
+
+ ldap_pvt_thread_mutex_lock( &zh->zh_mutex );
+ zoo = ldap_avl_find(zh->zh_zonetree, &zoi, slap_zone_cmp);
+ ldap_pvt_thread_mutex_unlock( &zh->zh_mutex );
+
+ if (zoo) {
+ idx = zoo->zo_idx;
+ assert(idx != -1);
+ ldap_pvt_thread_rdwr_wunlock(&zh->zh_znlock[idx]);
+ }
+}
+
+#define T_SEC_IN_USEC 1000000
+
+static int
+slap_timediff(struct timeval *tv_begin, struct timeval *tv_end)
+{
+ uint64_t t_begin, t_end, t_diff;
+
+ t_begin = T_SEC_IN_USEC * tv_begin->tv_sec + tv_begin->tv_usec;
+ t_end = T_SEC_IN_USEC * tv_end->tv_sec + tv_end->tv_usec;
+ t_diff = t_end - t_begin;
+
+ if ( t_diff < 0 )
+ t_diff = 0;
+
+ return (int)t_diff;
+}
+
+void
+slap_set_timing(struct timeval *tv_set)
+{
+ gettimeofday(tv_set, (struct timezone *)NULL);
+}
+
+int
+slap_measure_timing(struct timeval *tv_set, struct timeval *tv_measure)
+{
+ gettimeofday(tv_measure, (struct timezone *)NULL);
+ return(slap_timediff(tv_set, tv_measure));
+}
+
+#define EMA_WEIGHT 0.999000
+#define SLAP_ZN_LATENCY_HISTORY_QLEN 500
+int
+slap_zn_latency_history(void* ctx, int ea_latency)
+{
+/* TODO: monitor /proc/stat (swap) as well */
+ struct zone_heap* zh = ctx;
+ double t_diff = 0.0;
+
+ zh->zh_ema_latency = (double)ea_latency * (1.0 - EMA_WEIGHT)
+ + zh->zh_ema_latency * EMA_WEIGHT;
+ if (!zh->zh_swapping && zh->zh_ema_samples++ % 100 == 99) {
+ struct zone_latency_history *zlh_entry;
+ zlh_entry = ch_calloc(1, sizeof(struct zone_latency_history));
+ zlh_entry->zlh_latency = zh->zh_ema_latency;
+ LDAP_STAILQ_INSERT_TAIL(
+ &zh->zh_latency_history_queue, zlh_entry, zlh_next);
+ zh->zh_latency_history_qlen++;
+ while (zh->zh_latency_history_qlen > SLAP_ZN_LATENCY_HISTORY_QLEN) {
+ struct zone_latency_history *zlh;
+ zlh = LDAP_STAILQ_FIRST(&zh->zh_latency_history_queue);
+ LDAP_STAILQ_REMOVE_HEAD(
+ &zh->zh_latency_history_queue, zlh_next);
+ zh->zh_latency_history_qlen--;
+ ch_free(zlh);
+ }
+ if (zh->zh_latency_history_qlen == SLAP_ZN_LATENCY_HISTORY_QLEN) {
+ struct zone_latency_history *zlh_first, *zlh_last;
+ zlh_first = LDAP_STAILQ_FIRST(&zh->zh_latency_history_queue);
+ zlh_last = LDAP_STAILQ_LAST(&zh->zh_latency_history_queue,
+ zone_latency_history, zlh_next);
+ t_diff = zlh_last->zlh_latency - zlh_first->zlh_latency;
+ }
+ if (t_diff >= 2000) {
+ zh->zh_latency_jump++;
+ } else {
+ zh->zh_latency_jump = 0;
+ }
+ if (zh->zh_latency_jump > 3) {
+ zh->zh_latency_jump = 0;
+ zh->zh_swapping = 1;
+ }
+ }
+ return zh->zh_swapping;
+}
+#endif /* SLAP_ZONE_ALLOC */