1/* 2 Unix SMB/CIFS implementation. 3 4 rootDSE ldb module 5 6 Copyright (C) Andrew Tridgell 2005 7 Copyright (C) Simo Sorce 2005-2008 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 3 of the License, or 12 (at your option) any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program. If not, see <http://www.gnu.org/licenses/>. 21*/ 22 23#include "includes.h" 24#include "lib/ldb/include/ldb.h" 25#include "lib/ldb/include/ldb_module.h" 26#include "system/time.h" 27#include "dsdb/samdb/samdb.h" 28#include "version.h" 29 30struct private_data { 31 int num_controls; 32 char **controls; 33 int num_partitions; 34 struct ldb_dn **partitions; 35}; 36 37/* 38 return 1 if a specific attribute has been requested 39*/ 40static int do_attribute(const char * const *attrs, const char *name) 41{ 42 return attrs == NULL || 43 ldb_attr_in_list(attrs, name) || 44 ldb_attr_in_list(attrs, "*"); 45} 46 47static int do_attribute_explicit(const char * const *attrs, const char *name) 48{ 49 return attrs != NULL && ldb_attr_in_list(attrs, name); 50} 51 52 53/* 54 expand a DN attribute to include extended DN information if requested 55 */ 56static int expand_dn_in_message(struct ldb_module *module, struct ldb_message *msg, 57 const char *attrname, struct ldb_control *edn_control, 58 struct ldb_request *req) 59{ 60 struct ldb_dn *dn, *dn2; 61 struct ldb_val *v; 62 int ret; 63 struct ldb_request *req2; 64 char *dn_string; 65 const char *no_attrs[] = { NULL }; 66 struct ldb_result *res; 67 struct ldb_extended_dn_control *edn; 68 TALLOC_CTX *tmp_ctx = talloc_new(req); 69 struct ldb_context *ldb; 70 int edn_type = 0; 71 72 ldb = ldb_module_get_ctx(module); 73 74 edn = talloc_get_type(edn_control->data, struct ldb_extended_dn_control); 75 if (edn) { 76 edn_type = edn->type; 77 } 78 79 v = discard_const_p(struct ldb_val, ldb_msg_find_ldb_val(msg, attrname)); 80 if (v == NULL) { 81 talloc_free(tmp_ctx); 82 return 0; 83 } 84 85 dn_string = talloc_strndup(tmp_ctx, (const char *)v->data, v->length); 86 if (dn_string == NULL) { 87 talloc_free(tmp_ctx); 88 return LDB_ERR_OPERATIONS_ERROR; 89 } 90 91 res = talloc_zero(tmp_ctx, struct ldb_result); 92 if (res == NULL) { 93 talloc_free(tmp_ctx); 94 return LDB_ERR_OPERATIONS_ERROR; 95 } 96 97 dn = ldb_dn_new(tmp_ctx, ldb, dn_string); 98 if (!ldb_dn_validate(dn)) { 99 talloc_free(tmp_ctx); 100 return LDB_ERR_OPERATIONS_ERROR; 101 } 102 103 ret = ldb_build_search_req(&req2, ldb, tmp_ctx, 104 dn, 105 LDB_SCOPE_BASE, 106 NULL, 107 no_attrs, 108 NULL, 109 res, ldb_search_default_callback, 110 req); 111 if (ret != LDB_SUCCESS) { 112 talloc_free(tmp_ctx); 113 return ret; 114 } 115 116 117 ret = ldb_request_add_control(req2, 118 LDB_CONTROL_EXTENDED_DN_OID, 119 edn_control->critical, edn); 120 if (ret != LDB_SUCCESS) { 121 talloc_free(tmp_ctx); 122 return ret; 123 } 124 125 ret = ldb_next_request(module, req2); 126 if (ret == LDB_SUCCESS) { 127 ret = ldb_wait(req2->handle, LDB_WAIT_ALL); 128 } 129 if (ret != LDB_SUCCESS) { 130 talloc_free(tmp_ctx); 131 return ret; 132 } 133 134 if (!res || res->count != 1) { 135 talloc_free(tmp_ctx); 136 return LDB_ERR_OPERATIONS_ERROR; 137 } 138 139 dn2 = res->msgs[0]->dn; 140 141 v->data = (uint8_t *)ldb_dn_get_extended_linearized(msg->elements, dn2, edn_type); 142 v->length = strlen((char *)v->data); 143 144 if (v->data == NULL) { 145 talloc_free(tmp_ctx); 146 return LDB_ERR_OPERATIONS_ERROR; 147 } 148 149 talloc_free(tmp_ctx); 150 151 return 0; 152} 153 154 155/* 156 add dynamically generated attributes to rootDSE result 157*/ 158static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_message *msg, 159 const char * const *attrs, struct ldb_request *req) 160{ 161 struct ldb_context *ldb; 162 struct private_data *priv = talloc_get_type(ldb_module_get_private(module), struct private_data); 163 char **server_sasl; 164 const struct dsdb_schema *schema; 165 int *val; 166 struct ldb_control *edn_control; 167 const char *dn_attrs[] = { 168 "configurationNamingContext", 169 "defaultNamingContext", 170 "dsServiceName", 171 "rootDomainNamingContext", 172 "schemaNamingContext", 173 "serverName", 174 NULL 175 }; 176 177 ldb = ldb_module_get_ctx(module); 178 schema = dsdb_get_schema(ldb); 179 180 msg->dn = ldb_dn_new(msg, ldb, NULL); 181 182 /* don't return the distinduishedName, cn and name attributes */ 183 ldb_msg_remove_attr(msg, "distinguishedName"); 184 ldb_msg_remove_attr(msg, "cn"); 185 ldb_msg_remove_attr(msg, "name"); 186 187 if (do_attribute(attrs, "currentTime")) { 188 if (ldb_msg_add_steal_string(msg, "currentTime", 189 ldb_timestring(msg, time(NULL))) != 0) { 190 goto failed; 191 } 192 } 193 194 if (priv && do_attribute(attrs, "supportedControl")) { 195 int i; 196 for (i = 0; i < priv->num_controls; i++) { 197 char *control = talloc_strdup(msg, priv->controls[i]); 198 if (!control) { 199 goto failed; 200 } 201 if (ldb_msg_add_steal_string(msg, "supportedControl", 202 control) != 0) { 203 goto failed; 204 } 205 } 206 } 207 208 if (priv && do_attribute(attrs, "namingContexts")) { 209 int i; 210 for (i = 0; i < priv->num_partitions; i++) { 211 struct ldb_dn *dn = priv->partitions[i]; 212 if (ldb_msg_add_steal_string(msg, "namingContexts", 213 ldb_dn_alloc_linearized(msg, dn)) != 0) { 214 goto failed; 215 } 216 } 217 } 218 219 server_sasl = talloc_get_type(ldb_get_opaque(ldb, "supportedSASLMechanims"), 220 char *); 221 if (server_sasl && do_attribute(attrs, "supportedSASLMechanisms")) { 222 int i; 223 for (i = 0; server_sasl && server_sasl[i]; i++) { 224 char *sasl_name = talloc_strdup(msg, server_sasl[i]); 225 if (!sasl_name) { 226 goto failed; 227 } 228 if (ldb_msg_add_steal_string(msg, "supportedSASLMechanisms", 229 sasl_name) != 0) { 230 goto failed; 231 } 232 } 233 } 234 235 if (do_attribute(attrs, "highestCommittedUSN")) { 236 uint64_t seq_num; 237 int ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num); 238 if (ret == LDB_SUCCESS) { 239 if (ldb_msg_add_fmt(msg, "highestCommittedUSN", 240 "%llu", (unsigned long long)seq_num) != 0) { 241 goto failed; 242 } 243 } 244 } 245 246 if (schema && do_attribute_explicit(attrs, "dsSchemaAttrCount")) { 247 struct dsdb_attribute *cur; 248 uint32_t n = 0; 249 250 for (cur = schema->attributes; cur; cur = cur->next) { 251 n++; 252 } 253 254 if (ldb_msg_add_fmt(msg, "dsSchemaAttrCount", 255 "%u", n) != 0) { 256 goto failed; 257 } 258 } 259 260 if (schema && do_attribute_explicit(attrs, "dsSchemaClassCount")) { 261 struct dsdb_class *cur; 262 uint32_t n = 0; 263 264 for (cur = schema->classes; cur; cur = cur->next) { 265 n++; 266 } 267 268 if (ldb_msg_add_fmt(msg, "dsSchemaClassCount", 269 "%u", n) != 0) { 270 goto failed; 271 } 272 } 273 274 if (schema && do_attribute_explicit(attrs, "dsSchemaPrefixCount")) { 275 if (ldb_msg_add_fmt(msg, "dsSchemaPrefixCount", 276 "%u", schema->num_prefixes) != 0) { 277 goto failed; 278 } 279 } 280 281 if (do_attribute_explicit(attrs, "validFSMOs")) { 282 const struct dsdb_naming_fsmo *naming_fsmo; 283 const struct dsdb_pdc_fsmo *pdc_fsmo; 284 const char *dn_str; 285 286 if (schema && schema->fsmo.we_are_master) { 287 dn_str = ldb_dn_get_linearized(samdb_schema_dn(ldb)); 288 if (dn_str && dn_str[0]) { 289 if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) { 290 goto failed; 291 } 292 } 293 } 294 295 naming_fsmo = talloc_get_type(ldb_get_opaque(ldb, "dsdb_naming_fsmo"), 296 struct dsdb_naming_fsmo); 297 if (naming_fsmo && naming_fsmo->we_are_master) { 298 dn_str = ldb_dn_get_linearized(samdb_partitions_dn(ldb, msg)); 299 if (dn_str && dn_str[0]) { 300 if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) { 301 goto failed; 302 } 303 } 304 } 305 306 pdc_fsmo = talloc_get_type(ldb_get_opaque(ldb, "dsdb_pdc_fsmo"), 307 struct dsdb_pdc_fsmo); 308 if (pdc_fsmo && pdc_fsmo->we_are_master) { 309 dn_str = ldb_dn_get_linearized(samdb_base_dn(ldb)); 310 if (dn_str && dn_str[0]) { 311 if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) { 312 goto failed; 313 } 314 } 315 } 316 } 317 318 if (do_attribute_explicit(attrs, "vendorVersion")) { 319 if (ldb_msg_add_fmt(msg, "vendorVersion", 320 "%s", SAMBA_VERSION_STRING) != 0) { 321 goto failed; 322 } 323 } 324 325 if (priv && do_attribute(attrs, "domainFunctionality") 326 && (val = talloc_get_type(ldb_get_opaque(ldb, "domainFunctionality"), int))) { 327 if (ldb_msg_add_fmt(msg, "domainFunctionality", 328 "%d", *val) != 0) { 329 goto failed; 330 } 331 } 332 333 if (priv && do_attribute(attrs, "forestFunctionality") 334 && (val = talloc_get_type(ldb_get_opaque(ldb, "forestFunctionality"), int))) { 335 if (ldb_msg_add_fmt(msg, "forestFunctionality", 336 "%d", *val) != 0) { 337 goto failed; 338 } 339 } 340 341 if (priv && do_attribute(attrs, "domainControllerFunctionality") 342 && (val = talloc_get_type(ldb_get_opaque(ldb, "domainControllerFunctionality"), int))) { 343 if (ldb_msg_add_fmt(msg, "domainControllerFunctionality", 344 "%d", *val) != 0) { 345 goto failed; 346 } 347 } 348 349 edn_control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID); 350 351 /* if the client sent us the EXTENDED_DN control then we need 352 to expand the DNs to have GUID and SID. W2K8 join relies on 353 this */ 354 if (edn_control) { 355 int i, ret; 356 for (i=0; dn_attrs[i]; i++) { 357 if (!do_attribute(attrs, dn_attrs[i])) continue; 358 ret = expand_dn_in_message(module, msg, dn_attrs[i], 359 edn_control, req); 360 if (ret != LDB_SUCCESS) { 361 DEBUG(0,(__location__ ": Failed to expand DN in rootDSE for %s\n", 362 dn_attrs[i])); 363 goto failed; 364 } 365 } 366 } 367 368 369 /* TODO: lots more dynamic attributes should be added here */ 370 371 return LDB_SUCCESS; 372 373failed: 374 return LDB_ERR_OPERATIONS_ERROR; 375} 376 377/* 378 handle search requests 379*/ 380 381struct rootdse_context { 382 struct ldb_module *module; 383 struct ldb_request *req; 384}; 385 386static struct rootdse_context *rootdse_init_context(struct ldb_module *module, 387 struct ldb_request *req) 388{ 389 struct ldb_context *ldb; 390 struct rootdse_context *ac; 391 392 ldb = ldb_module_get_ctx(module); 393 394 ac = talloc_zero(req, struct rootdse_context); 395 if (ac == NULL) { 396 ldb_set_errstring(ldb, "Out of Memory"); 397 return NULL; 398 } 399 400 ac->module = module; 401 ac->req = req; 402 403 return ac; 404} 405 406static int rootdse_callback(struct ldb_request *req, struct ldb_reply *ares) 407{ 408 struct rootdse_context *ac; 409 int ret; 410 411 ac = talloc_get_type(req->context, struct rootdse_context); 412 413 if (!ares) { 414 return ldb_module_done(ac->req, NULL, NULL, 415 LDB_ERR_OPERATIONS_ERROR); 416 } 417 if (ares->error != LDB_SUCCESS) { 418 return ldb_module_done(ac->req, ares->controls, 419 ares->response, ares->error); 420 } 421 422 switch (ares->type) { 423 case LDB_REPLY_ENTRY: 424 /* 425 * if the client explicit asks for the 'netlogon' attribute 426 * the reply_entry needs to be skipped 427 */ 428 if (ac->req->op.search.attrs && 429 ldb_attr_in_list(ac->req->op.search.attrs, "netlogon")) { 430 talloc_free(ares); 431 return LDB_SUCCESS; 432 } 433 434 /* for each record returned post-process to add any dynamic 435 attributes that have been asked for */ 436 ret = rootdse_add_dynamic(ac->module, ares->message, 437 ac->req->op.search.attrs, ac->req); 438 if (ret != LDB_SUCCESS) { 439 talloc_free(ares); 440 return ldb_module_done(ac->req, NULL, NULL, ret); 441 } 442 443 return ldb_module_send_entry(ac->req, ares->message, ares->controls); 444 445 case LDB_REPLY_REFERRAL: 446 /* should we allow the backend to return referrals in this case 447 * ?? */ 448 break; 449 450 case LDB_REPLY_DONE: 451 return ldb_module_done(ac->req, ares->controls, 452 ares->response, ares->error); 453 } 454 455 talloc_free(ares); 456 return LDB_SUCCESS; 457} 458 459static int rootdse_search(struct ldb_module *module, struct ldb_request *req) 460{ 461 struct ldb_context *ldb; 462 struct rootdse_context *ac; 463 struct ldb_request *down_req; 464 int ret; 465 466 ldb = ldb_module_get_ctx(module); 467 468 /* see if its for the rootDSE - only a base search on the "" DN qualifies */ 469 if (!(req->op.search.scope == LDB_SCOPE_BASE && ldb_dn_is_null(req->op.search.base))) { 470 /* Otherwise, pass down to the rest of the stack */ 471 return ldb_next_request(module, req); 472 } 473 474 ac = rootdse_init_context(module, req); 475 if (ac == NULL) { 476 return LDB_ERR_OPERATIONS_ERROR; 477 } 478 479 /* in our db we store the rootDSE with a DN of @ROOTDSE */ 480 ret = ldb_build_search_req(&down_req, ldb, ac, 481 ldb_dn_new(ac, ldb, "@ROOTDSE"), 482 LDB_SCOPE_BASE, 483 NULL, 484 req->op.search.attrs, 485 NULL,/* for now skip the controls from the client */ 486 ac, rootdse_callback, 487 req); 488 if (ret != LDB_SUCCESS) { 489 return ret; 490 } 491 492 return ldb_next_request(module, down_req); 493} 494 495static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req) 496{ 497 struct private_data *priv = talloc_get_type(ldb_module_get_private(module), struct private_data); 498 char **list; 499 500 list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1); 501 if (!list) { 502 return LDB_ERR_OPERATIONS_ERROR; 503 } 504 505 list[priv->num_controls] = talloc_strdup(list, req->op.reg_control.oid); 506 if (!list[priv->num_controls]) { 507 return LDB_ERR_OPERATIONS_ERROR; 508 } 509 510 priv->num_controls += 1; 511 priv->controls = list; 512 513 return ldb_module_done(req, NULL, NULL, LDB_SUCCESS); 514} 515 516static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req) 517{ 518 struct private_data *priv = talloc_get_type(ldb_module_get_private(module), struct private_data); 519 struct ldb_dn **list; 520 521 list = talloc_realloc(priv, priv->partitions, struct ldb_dn *, priv->num_partitions + 1); 522 if (!list) { 523 return LDB_ERR_OPERATIONS_ERROR; 524 } 525 526 list[priv->num_partitions] = ldb_dn_copy(list, req->op.reg_partition.dn); 527 if (!list[priv->num_partitions]) { 528 return LDB_ERR_OPERATIONS_ERROR; 529 } 530 531 priv->num_partitions += 1; 532 priv->partitions = list; 533 534 return ldb_module_done(req, NULL, NULL, LDB_SUCCESS); 535} 536 537 538static int rootdse_request(struct ldb_module *module, struct ldb_request *req) 539{ 540 switch (req->operation) { 541 542 case LDB_REQ_REGISTER_CONTROL: 543 return rootdse_register_control(module, req); 544 case LDB_REQ_REGISTER_PARTITION: 545 return rootdse_register_partition(module, req); 546 547 default: 548 break; 549 } 550 return ldb_next_request(module, req); 551} 552 553static int rootdse_init(struct ldb_module *module) 554{ 555 int ret; 556 struct ldb_context *ldb; 557 struct ldb_result *res; 558 struct private_data *data; 559 const char *attrs[] = { "msDS-Behavior-Version", NULL }; 560 const char *ds_attrs[] = { "dsServiceName", NULL }; 561 TALLOC_CTX *mem_ctx; 562 563 ldb = ldb_module_get_ctx(module); 564 565 data = talloc_zero(module, struct private_data); 566 if (data == NULL) { 567 return -1; 568 } 569 570 data->num_controls = 0; 571 data->controls = NULL; 572 data->num_partitions = 0; 573 data->partitions = NULL; 574 ldb_module_set_private(module, data); 575 576 ldb_set_default_dns(ldb); 577 578 ret = ldb_next_init(module); 579 580 if (ret) { 581 return ret; 582 } 583 584 mem_ctx = talloc_new(data); 585 if (!mem_ctx) { 586 ldb_oom(ldb); 587 return LDB_ERR_OPERATIONS_ERROR; 588 } 589 590 /* Now that the partitions are set up, do a search for: 591 - domainControllerFunctionality 592 - domainFunctionality 593 - forestFunctionality 594 595 Then stuff these values into an opaque 596 */ 597 ret = ldb_search(ldb, mem_ctx, &res, 598 ldb_get_default_basedn(ldb), 599 LDB_SCOPE_BASE, attrs, NULL); 600 if (ret == LDB_SUCCESS && res->count == 1) { 601 int domain_behaviour_version 602 = ldb_msg_find_attr_as_int(res->msgs[0], 603 "msDS-Behavior-Version", -1); 604 if (domain_behaviour_version != -1) { 605 int *val = talloc(ldb, int); 606 if (!val) { 607 ldb_oom(ldb); 608 talloc_free(mem_ctx); 609 return LDB_ERR_OPERATIONS_ERROR; 610 } 611 *val = domain_behaviour_version; 612 ret = ldb_set_opaque(ldb, "domainFunctionality", val); 613 if (ret != LDB_SUCCESS) { 614 talloc_free(mem_ctx); 615 return ret; 616 } 617 } 618 } 619 620 ret = ldb_search(ldb, mem_ctx, &res, 621 samdb_partitions_dn(ldb, mem_ctx), 622 LDB_SCOPE_BASE, attrs, NULL); 623 if (ret == LDB_SUCCESS && res->count == 1) { 624 int forest_behaviour_version 625 = ldb_msg_find_attr_as_int(res->msgs[0], 626 "msDS-Behavior-Version", -1); 627 if (forest_behaviour_version != -1) { 628 int *val = talloc(ldb, int); 629 if (!val) { 630 ldb_oom(ldb); 631 talloc_free(mem_ctx); 632 return LDB_ERR_OPERATIONS_ERROR; 633 } 634 *val = forest_behaviour_version; 635 ret = ldb_set_opaque(ldb, "forestFunctionality", val); 636 if (ret != LDB_SUCCESS) { 637 talloc_free(mem_ctx); 638 return ret; 639 } 640 } 641 } 642 643 ret = ldb_search(ldb, mem_ctx, &res, 644 ldb_dn_new(mem_ctx, ldb, ""), 645 LDB_SCOPE_BASE, ds_attrs, NULL); 646 if (ret == LDB_SUCCESS && res->count == 1) { 647 struct ldb_dn *ds_dn 648 = ldb_msg_find_attr_as_dn(ldb, mem_ctx, res->msgs[0], 649 "dsServiceName"); 650 if (ds_dn) { 651 ret = ldb_search(ldb, mem_ctx, &res, ds_dn, 652 LDB_SCOPE_BASE, attrs, NULL); 653 if (ret == LDB_SUCCESS && res->count == 1) { 654 int domain_controller_behaviour_version 655 = ldb_msg_find_attr_as_int(res->msgs[0], 656 "msDS-Behavior-Version", -1); 657 if (domain_controller_behaviour_version != -1) { 658 int *val = talloc(ldb, int); 659 if (!val) { 660 ldb_oom(ldb); 661 talloc_free(mem_ctx); 662 return LDB_ERR_OPERATIONS_ERROR; 663 } 664 *val = domain_controller_behaviour_version; 665 ret = ldb_set_opaque(ldb, 666 "domainControllerFunctionality", val); 667 if (ret != LDB_SUCCESS) { 668 talloc_free(mem_ctx); 669 return ret; 670 } 671 } 672 } 673 } 674 } 675 676 talloc_free(mem_ctx); 677 678 return LDB_SUCCESS; 679} 680 681static int rootdse_modify(struct ldb_module *module, struct ldb_request *req) 682{ 683 struct ldb_context *ldb; 684 struct ldb_result *ext_res; 685 int ret; 686 struct ldb_dn *schema_dn; 687 struct ldb_message_element *schemaUpdateNowAttr; 688 689 /* 690 If dn is not "" we should let it pass through 691 */ 692 if (!ldb_dn_is_null(req->op.mod.message->dn)) { 693 return ldb_next_request(module, req); 694 } 695 696 ldb = ldb_module_get_ctx(module); 697 698 /* 699 dn is empty so check for schemaUpdateNow attribute 700 "The type of modification and values specified in the LDAP modify operation do not matter." MSDN 701 */ 702 schemaUpdateNowAttr = ldb_msg_find_element(req->op.mod.message, "schemaUpdateNow"); 703 if (!schemaUpdateNowAttr) { 704 return LDB_ERR_OPERATIONS_ERROR; 705 } 706 707 schema_dn = samdb_schema_dn(ldb); 708 if (!schema_dn) { 709 ldb_reset_err_string(ldb); 710 ldb_debug(ldb, LDB_DEBUG_WARNING, 711 "rootdse_modify: no schema dn present: (skip ldb_extended call)\n"); 712 return ldb_next_request(module, req); 713 } 714 715 ret = ldb_extended(ldb, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID, schema_dn, &ext_res); 716 if (ret != LDB_SUCCESS) { 717 return LDB_ERR_OPERATIONS_ERROR; 718 } 719 720 talloc_free(ext_res); 721 return ldb_request_done(req, ret); 722} 723 724_PUBLIC_ const struct ldb_module_ops ldb_rootdse_module_ops = { 725 .name = "rootdse", 726 .init_context = rootdse_init, 727 .search = rootdse_search, 728 .request = rootdse_request, 729 .modify = rootdse_modify 730}; 731