s4:rpc-dnsserver: Implement DirectoryPartitionInfo RPC operation
[ddiss/samba.git] / source4 / rpc_server / dnsserver / dnsdb.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    DNS Server
5
6    Copyright (C) Amitay Isaacs 2011
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "dnsserver.h"
24 #include "lib/util/dlinklist.h"
25 #include "librpc/gen_ndr/ndr_dnsp.h"
26 #include "dsdb/samdb/samdb.h"
27
28 /* There are only 2 fixed partitions for DNS */
29 struct dnsserver_partition *dnsserver_db_enumerate_partitions(TALLOC_CTX *mem_ctx,
30                                                         struct dnsserver_serverinfo *serverinfo,
31                                                         struct ldb_context *samdb)
32 {
33         struct dnsserver_partition *partitions, *p;
34
35         partitions = NULL;
36
37         /* Domain partition */
38         p = talloc_zero(mem_ctx, struct dnsserver_partition);
39         if (p == NULL) {
40                 goto failed;
41         }
42
43         p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszDomainDirectoryPartition);
44         if (p->partition_dn == NULL) {
45                 goto failed;
46         }
47
48         p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn);
49         p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_DOMAIN_DEFAULT | DNS_DP_ENLISTED;
50         p->is_forest = false;
51
52         DLIST_ADD_END(partitions, p, NULL);
53
54         /* Forest Partition */
55         p = talloc_zero(mem_ctx, struct dnsserver_partition);
56         if (p == NULL) {
57                 goto failed;
58         }
59
60         p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszForestDirectoryPartition);
61         if (p->partition_dn == NULL) {
62                 goto failed;
63         }
64
65         p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn);
66         p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_FOREST_DEFAULT | DNS_DP_ENLISTED;
67         p->is_forest = true;
68
69         DLIST_ADD_END(partitions, p, NULL);
70
71         return partitions;
72
73 failed:
74         return NULL;
75
76 }
77
78
79 /* Search for all dnsZone records */
80 struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
81                                                 struct ldb_context *samdb,
82                                                 struct dnsserver_partition *p)
83 {
84         TALLOC_CTX *tmp_ctx;
85         const char * const attrs[] = {"name", NULL};
86         struct ldb_dn *dn;
87         struct ldb_result *res;
88         struct dnsserver_zone *zones, *z;
89         int i, ret;
90
91         tmp_ctx = talloc_new(mem_ctx);
92         if (tmp_ctx == NULL) {
93                 return NULL;
94         }
95
96         dn = ldb_dn_copy(tmp_ctx, p->partition_dn);
97         if (dn == NULL) {
98                 goto failed;
99         }
100         if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) {
101                 goto failed;
102         }
103
104         ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
105                           attrs, "(objectClass=dnsZone)");
106         if (ret != LDB_SUCCESS) {
107                 DEBUG(0, ("dnsserver: Failed to find DNS Zones in %s\n",
108                         ldb_dn_get_linearized(dn)));
109                 goto failed;
110         }
111
112         zones = NULL;
113         for(i=0; i<res->count; i++) {
114                 char *name;
115                 z = talloc_zero(mem_ctx, struct dnsserver_zone);
116                 if (z == NULL) {
117                         goto failed;
118                 }
119
120                 z->partition = p;
121                 name = talloc_strdup(z,
122                                 ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL));
123                 if (strcmp(name, "RootDNSServers") == 0) {
124                         talloc_free(name);
125                         z->name = talloc_strdup(z, ".");
126                 } else {
127                         z->name = name;
128                 }
129                 z->zone_dn = talloc_steal(z, res->msgs[i]->dn);
130
131                 DLIST_ADD_END(zones, z, NULL);
132                 DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name));
133         }
134
135         return zones;
136
137 failed:
138         talloc_free(tmp_ctx);
139         return NULL;
140 }
141
142
143 /* Find DNS partition information */
144 struct dnsserver_partition_info *dnsserver_db_partition_info(TALLOC_CTX *mem_ctx,
145                                                         struct ldb_context *samdb,
146                                                         struct dnsserver_partition *p)
147 {
148         const char * const attrs[] = { "instanceType", "msDs-masteredBy", NULL };
149         const char * const attrs_none[] = { NULL };
150         struct ldb_result *res;
151         struct ldb_message_element *el;
152         struct ldb_dn *dn;
153         struct dnsserver_partition_info *partinfo;
154         int i, ret, instance_type;
155         TALLOC_CTX *tmp_ctx;
156
157         tmp_ctx = talloc_new(mem_ctx);
158         if (tmp_ctx == NULL) {
159                 return NULL;
160         }
161
162         partinfo = talloc_zero(mem_ctx, struct dnsserver_partition_info);
163         if (partinfo == NULL) {
164                 talloc_free(tmp_ctx);
165                 return NULL;
166         }
167
168         /* Search for the active replica and state */
169         ret = ldb_search(samdb, tmp_ctx, &res, p->partition_dn, LDB_SCOPE_BASE,
170                         attrs, NULL);
171         if (ret != LDB_SUCCESS || res->count != 1) {
172                 goto failed;
173         }
174
175         /* Set the state of the partition */
176         instance_type = ldb_msg_find_attr_as_int(res->msgs[0], "instanceType", -1);
177         if (instance_type == -1) {
178                 partinfo->dwState = DNS_DP_STATE_UNKNOWN;
179         } else if (instance_type & INSTANCE_TYPE_NC_COMING) {
180                 partinfo->dwState = DNS_DP_STATE_REPL_INCOMING;
181         } else if (instance_type & INSTANCE_TYPE_NC_GOING) {
182                 partinfo->dwState = DNS_DP_STATE_REPL_OUTGOING;
183         } else {
184                 partinfo->dwState = DNS_DP_OKAY;
185         }
186
187         el = ldb_msg_find_element(res->msgs[0], "msDs-masteredBy");
188         if (el == NULL) {
189                 partinfo->dwReplicaCount = 0;
190                 partinfo->ReplicaArray = NULL;
191         } else {
192                 partinfo->dwReplicaCount = el->num_values;
193                 partinfo->ReplicaArray = talloc_zero_array(partinfo,
194                                                            struct DNS_RPC_DP_REPLICA *,
195                                                            el->num_values);
196                 if (partinfo->ReplicaArray == NULL) {
197                         goto failed;
198                 }
199                 for (i=0; i<el->num_values; i++) {
200                         partinfo->ReplicaArray[i] = talloc_zero(partinfo,
201                                                         struct DNS_RPC_DP_REPLICA);
202                         if (partinfo->ReplicaArray[i] == NULL) {
203                                 goto failed;
204                         }
205                         partinfo->ReplicaArray[i]->pszReplicaDn = talloc_strdup(
206                                                                         partinfo,
207                                                                         (const char *)el->values[i].data);
208                         if (partinfo->ReplicaArray[i]->pszReplicaDn == NULL) {
209                                 goto failed;
210                         }
211                 }
212         }
213         talloc_free(res);
214
215         /* Search for cross-reference object */
216         dn = ldb_dn_copy(tmp_ctx, ldb_get_config_basedn(samdb));
217         if (dn == NULL) {
218                 goto failed;
219         }
220
221         ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_DEFAULT, attrs_none,
222                         "(nCName=%s)", ldb_dn_get_linearized(p->partition_dn));
223         if (ret != LDB_SUCCESS || res->count != 1) {
224                 goto failed;
225         }
226         partinfo->pszCrDn = talloc_strdup(partinfo, ldb_dn_get_linearized(res->msgs[0]->dn));
227         if (partinfo->pszCrDn == NULL) {
228                 goto failed;
229         }
230         talloc_free(res);
231
232         talloc_free(tmp_ctx);
233         return partinfo;
234
235 failed:
236         talloc_free(tmp_ctx);
237         talloc_free(partinfo);
238         return NULL;
239 }
240
241
242 static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx,
243                                 struct ldb_context *samdb,
244                                 struct dnsserver_zone *z)
245 {
246         const char * const attrs[] = { "dnsRecord", NULL };
247         struct ldb_result *res;
248         struct dnsp_DnssrvRpcRecord rec;
249         struct ldb_message_element *el;
250         enum ndr_err_code ndr_err;
251         int ret, i, serial = -1;
252         NTTIME t;
253
254         unix_to_nt_time(&t, time(NULL));
255         t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
256         t /= 3600;         /* convert to hours */
257
258         ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
259                         "(&(objectClass=dnsNode)(name=@))");
260         if (ret != LDB_SUCCESS || res->count == 0) {
261                 return -1;
262         }
263
264         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
265         if (el == NULL) {
266                 return -1;
267         }
268
269         for (i=0; i<el->num_values; i++) {
270                 ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec,
271                                         (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
272                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
273                         continue;
274                 }
275
276                 if (rec.wType == DNS_TYPE_SOA) {
277                         serial = rec.data.soa.serial + 1;
278                         rec.dwSerial = serial;
279                         rec.dwTimeStamp = (uint32_t)t;
280                         rec.data.soa.serial = serial;
281
282                         ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, &rec,
283                                         (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
284                         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
285                                 return -1;
286                         }
287                         break;
288                 }
289         }
290
291         if (serial != -1) {
292                 el->flags = LDB_FLAG_MOD_REPLACE;
293                 ret = ldb_modify(samdb, res->msgs[0]);
294                 if (ret != LDB_SUCCESS) {
295                         return -1;
296                 }
297         }
298
299         return serial;
300 }
301
302
303 static WERROR dnsserver_db_do_add_rec(TALLOC_CTX *mem_ctx,
304                                 struct ldb_context *samdb,
305                                 struct ldb_dn *dn,
306                                 struct dnsp_DnssrvRpcRecord *rec)
307 {
308         struct ldb_message *msg;
309         struct ldb_val v;
310         int ret;
311         enum ndr_err_code ndr_err;
312
313         msg = ldb_msg_new(mem_ctx);
314         W_ERROR_HAVE_NO_MEMORY(msg);
315
316         msg->dn = dn;
317         ret = ldb_msg_add_string(msg, "objectClass", "dnsNode");
318         if (ret != LDB_SUCCESS) {
319                 return WERR_NOMEM;
320         }
321
322         if (rec) {
323                 ndr_err = ndr_push_struct_blob(&v, mem_ctx, rec,
324                                 (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
325                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
326                         return WERR_GENERAL_FAILURE;
327                 }
328
329                 ret = ldb_msg_add_value(msg, "dnsRecord", &v, NULL);
330                 if (ret != LDB_SUCCESS) {
331                         return WERR_NOMEM;
332                 }
333         }
334
335         ret = ldb_add(samdb, msg);
336         if (ret != LDB_SUCCESS) {
337                 return WERR_INTERNAL_DB_ERROR;
338         }
339
340         return WERR_OK;
341 }
342
343
344 WERROR dnsserver_db_add_empty_node(TALLOC_CTX *mem_ctx,
345                                         struct ldb_context *samdb,
346                                         struct dnsserver_zone *z,
347                                         const char *name)
348 {
349         const char * const attrs[] = { "name", NULL };
350         struct ldb_result *res;
351         struct ldb_dn *dn;
352         int ret;
353
354         ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, attrs,
355                         "(&(objectClass=dnsNode)(name=%s))", name);
356         if (ret != LDB_SUCCESS) {
357                 return WERR_INTERNAL_DB_ERROR;
358         }
359
360         if (res->count > 0) {
361                 talloc_free(res);
362                 return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
363         }
364
365         dn = ldb_dn_copy(mem_ctx, z->zone_dn);
366         W_ERROR_HAVE_NO_MEMORY(dn);
367
368         if (!ldb_dn_add_child_fmt(dn, "DC=%s", name)) {
369                 return WERR_NOMEM;
370         }
371
372         return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, NULL);
373 }
374
375
376 WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx,
377                                         struct ldb_context *samdb,
378                                         struct dnsserver_zone *z,
379                                         const char *name,
380                                         struct DNS_RPC_RECORD *add_record)
381 {
382         const char * const attrs[] = { "dnsRecord", NULL };
383         struct ldb_result *res;
384         struct dnsp_DnssrvRpcRecord *rec;
385         struct ldb_message_element *el;
386         struct ldb_dn *dn;
387         enum ndr_err_code ndr_err;
388         NTTIME t;
389         int ret, i;
390         int serial;
391
392         rec = dns_to_dnsp_copy(mem_ctx, add_record);
393         W_ERROR_HAVE_NO_MEMORY(rec);
394
395         serial = dnsserver_update_soa(mem_ctx, samdb, z);
396         if (serial < 0) {
397                 return WERR_INTERNAL_DB_ERROR;
398         }
399
400         unix_to_nt_time(&t, time(NULL));
401         t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
402         t /= 3600;         /* convert to hours */
403
404         rec->dwSerial = serial;
405         rec->dwTimeStamp = t;
406
407         ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
408                         "(&(objectClass=dnsNode)(name=%s))", name);
409         if (ret != LDB_SUCCESS) {
410                 return WERR_INTERNAL_DB_ERROR;
411         }
412
413         if (res->count == 0) {
414                 dn = dnsserver_name_to_dn(mem_ctx, z, name);
415                 W_ERROR_HAVE_NO_MEMORY(dn);
416
417                 return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, rec);
418         }
419
420         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
421         if (el == NULL) {
422                 ret = ldb_msg_add_empty(res->msgs[0], "dnsRecord", 0, &el);
423                 if (ret != LDB_SUCCESS) {
424                         return WERR_NOMEM;
425                 }
426         }
427
428         for (i=0; i<el->num_values; i++) {
429                 struct dnsp_DnssrvRpcRecord rec2;
430
431                 ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
432                                                 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
433                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
434                         return WERR_GENERAL_FAILURE;
435                 }
436
437                 if (dns_record_match(rec, &rec2)) {
438                         break;
439                 }
440         }
441         if (i < el->num_values) {
442                 return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
443         }
444         if (i == el->num_values) {
445                 /* adding a new value */
446                 el->values = talloc_realloc(el, el->values, struct ldb_val, el->num_values+1);
447                 W_ERROR_HAVE_NO_MEMORY(el->values);
448                 el->num_values++;
449         }
450
451         ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, rec,
452                                         (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
453         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
454                 return WERR_GENERAL_FAILURE;
455         }
456
457         el->flags = LDB_FLAG_MOD_REPLACE;
458         ret = ldb_modify(samdb, res->msgs[0]);
459         if (ret != LDB_SUCCESS) {
460                 return WERR_INTERNAL_DB_ERROR;
461         }
462
463         return WERR_OK;
464 }
465
466 WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
467                                         struct ldb_context *samdb,
468                                         struct dnsserver_zone *z,
469                                         const char *name,
470                                         struct DNS_RPC_RECORD *add_record,
471                                         struct DNS_RPC_RECORD *del_record)
472 {
473         const char * const attrs[] = { "dnsRecord", NULL };
474         struct ldb_result *res;
475         struct dnsp_DnssrvRpcRecord *arec, *drec;
476         struct ldb_message_element *el;
477         enum ndr_err_code ndr_err;
478         NTTIME t;
479         int ret, i;
480         int serial;
481
482         serial = dnsserver_update_soa(mem_ctx, samdb, z);
483         if (serial < 0) {
484                 return WERR_INTERNAL_DB_ERROR;
485         }
486
487         arec = dns_to_dnsp_copy(mem_ctx, add_record);
488         W_ERROR_HAVE_NO_MEMORY(arec);
489
490         drec = dns_to_dnsp_copy(mem_ctx, del_record);
491         W_ERROR_HAVE_NO_MEMORY(drec);
492
493         unix_to_nt_time(&t, time(NULL));
494         t /= 10*1000*1000;
495
496         arec->dwSerial = serial;
497         arec->dwTimeStamp = t;
498
499         ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
500                         "(&(objectClass=dnsNode)(name=%s))", name);
501         if (ret != LDB_SUCCESS) {
502                 return WERR_INTERNAL_DB_ERROR;
503         }
504
505         if (res->count == 0) {
506                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
507         }
508
509         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
510         if (el == NULL || el->num_values == 0) {
511                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
512         }
513
514         for (i=0; i<el->num_values; i++) {
515                 struct dnsp_DnssrvRpcRecord rec2;
516
517                 ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
518                                                 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
519                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
520                         return WERR_GENERAL_FAILURE;
521                 }
522
523                 if (dns_record_match(arec, &rec2)) {
524                         break;
525                 }
526         }
527         if (i < el->num_values) {
528                 return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
529         }
530
531
532         for (i=0; i<el->num_values; i++) {
533                 struct dnsp_DnssrvRpcRecord rec2;
534
535                 ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
536                                                 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
537                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
538                         return WERR_GENERAL_FAILURE;
539                 }
540
541                 if (dns_record_match(drec, &rec2)) {
542                         break;
543                 }
544         }
545         if (i == el->num_values) {
546                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
547         }
548
549         ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, arec,
550                                         (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
551         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
552                 return WERR_GENERAL_FAILURE;
553         }
554
555         el->flags = LDB_FLAG_MOD_REPLACE;
556         ret = ldb_modify(samdb, res->msgs[0]);
557         if (ret != LDB_SUCCESS) {
558                 return WERR_INTERNAL_DB_ERROR;
559         }
560
561         return WERR_OK;
562 }
563
564 WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx,
565                                         struct ldb_context *samdb,
566                                         struct dnsserver_zone *z,
567                                         const char *name,
568                                         struct DNS_RPC_RECORD *del_record)
569 {
570         const char * const attrs[] = { "dnsRecord", NULL };
571         struct ldb_result *res;
572         struct dnsp_DnssrvRpcRecord *rec;
573         struct ldb_message_element *el;
574         enum ndr_err_code ndr_err;
575         int ret, i;
576         int serial;
577
578         serial = dnsserver_update_soa(mem_ctx, samdb, z);
579         if (serial < 0) {
580                 return WERR_INTERNAL_DB_ERROR;
581         }
582
583         rec = dns_to_dnsp_copy(mem_ctx, del_record);
584         W_ERROR_HAVE_NO_MEMORY(rec);
585
586         ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
587                         "(&(objectClass=dnsNode)(name=%s))", name);
588         if (ret != LDB_SUCCESS) {
589                 return WERR_INTERNAL_DB_ERROR;
590         }
591
592         if (res->count == 0) {
593                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
594         }
595
596         el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
597         if (el == NULL || el->num_values == 0) {
598                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
599         }
600
601         for (i=0; i<el->num_values; i++) {
602                 struct dnsp_DnssrvRpcRecord rec2;
603
604                 ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
605                                                 (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
606                 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
607                         return WERR_GENERAL_FAILURE;
608                 }
609
610                 if (dns_record_match(rec, &rec2)) {
611                         break;
612                 }
613         }
614         if (i == el->num_values) {
615                 return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
616         }
617         if (i < el->num_values-1) {
618                 memmove(&el->values[i], &el->values[i+1], sizeof(el->values[0])*((el->num_values-1)-i));
619         }
620         el->num_values--;
621
622         if (el->num_values == 0) {
623                 ret = ldb_delete(samdb, res->msgs[0]->dn);
624         } else {
625                 el->flags = LDB_FLAG_MOD_REPLACE;
626                 ret = ldb_modify(samdb, res->msgs[0]);
627         }
628         if (ret != LDB_SUCCESS) {
629                 return WERR_INTERNAL_DB_ERROR;
630         }
631
632         return WERR_OK;
633 }