1 /*
2 ldb database library
3
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
5 Copyright (C) Andrew Tridgell 2005
6 Copyright (C) Simo Sorce 2006-2008
7 Copyright (C) Matthias Dieter Wallnöfer 2009
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 /*
24 handle operational attributes
25 */
26
27 /*
28 createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
29 modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
30
31 for the above two, we do the search as normal, and if
32 createTimeStamp or modifyTimeStamp is asked for, then do
33 additional searches for whenCreated and whenChanged and fill in
34 the resulting values
35
36 we also need to replace these with the whenCreated/whenChanged
37 equivalent in the search expression trees
38
39 whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
40 whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
41
42 on init we need to setup attribute handlers for these so
43 comparisons are done correctly. The resolution is 1 second.
44
45 on add we need to add both the above, for current time
46
47 on modify we need to change whenChanged
48
49 structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
50
51 for this one we do the search as normal, then if requested ask
52 for objectclass, change the attribute name, and add it
53
54 primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
55
56 contains the RID of a certain group object
57
58
59 attributeTypes: in schema only
60 objectClasses: in schema only
61 matchingRules: in schema only
62 matchingRuleUse: in schema only
63 creatorsName: not supported by w2k3?
64 modifiersName: not supported by w2k3?
65 */
66
67 #include "includes.h"
68 #include <ldb.h>
69 #include <ldb_module.h>
70
71 #include "librpc/gen_ndr/ndr_misc.h"
72 #include "librpc/gen_ndr/ndr_drsblobs.h"
73 #include "param/param.h"
74 #include "dsdb/samdb/samdb.h"
75 #include "dsdb/samdb/ldb_modules/util.h"
76
77 #include "libcli/security/security.h"
78
79 #ifndef ARRAY_SIZE
80 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
81 #endif
82
83 struct operational_data {
84 struct ldb_dn *aggregate_dn;
85 };
86
87 enum search_type {
88 TOKEN_GROUPS,
89 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
90 TOKEN_GROUPS_NO_GC_ACCEPTABLE,
91
92 /*
93 * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
94 * all account groups in a given domain, excluding built-in groups.
95 * (Used internally for msDS-ResultantPSO support)
96 */
97 ACCOUNT_GROUPS
98 };
99
100 static int get_pso_for_user(struct ldb_module *module,
101 struct ldb_message *user_msg,
102 struct ldb_request *parent,
103 struct ldb_message **pso_msg);
104
105 /*
106 construct a canonical name from a message
107 */
construct_canonical_name(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)108 static int construct_canonical_name(struct ldb_module *module,
109 struct ldb_message *msg, enum ldb_scope scope,
110 struct ldb_request *parent)
111 {
112 char *canonicalName;
113 canonicalName = ldb_dn_canonical_string(msg, msg->dn);
114 if (canonicalName == NULL) {
115 return ldb_operr(ldb_module_get_ctx(module));
116 }
117 return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
118 }
119
120 /*
121 construct a primary group token for groups from a message
122 */
construct_primary_group_token(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)123 static int construct_primary_group_token(struct ldb_module *module,
124 struct ldb_message *msg, enum ldb_scope scope,
125 struct ldb_request *parent)
126 {
127 struct ldb_context *ldb;
128 uint32_t primary_group_token;
129
130 ldb = ldb_module_get_ctx(module);
131 if (ldb_match_msg_objectclass(msg, "group") == 1) {
132 primary_group_token
133 = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
134 if (primary_group_token == 0) {
135 return LDB_SUCCESS;
136 }
137
138 return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
139 primary_group_token);
140 } else {
141 return LDB_SUCCESS;
142 }
143 }
144
145 /*
146 * Returns the group SIDs for the user in the given LDB message
147 */
get_group_sids(struct ldb_context * ldb,TALLOC_CTX * mem_ctx,struct ldb_message * msg,const char * attribute_string,enum search_type type,struct dom_sid ** groupSIDs,unsigned int * num_groupSIDs)148 static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
149 struct ldb_message *msg, const char *attribute_string,
150 enum search_type type, struct dom_sid **groupSIDs,
151 unsigned int *num_groupSIDs)
152 {
153 const char *filter = NULL;
154 NTSTATUS status;
155 struct dom_sid *primary_group_sid;
156 const char *primary_group_string;
157 const char *primary_group_dn;
158 DATA_BLOB primary_group_blob;
159 struct dom_sid *account_sid;
160 const char *account_sid_string;
161 const char *account_sid_dn;
162 DATA_BLOB account_sid_blob;
163 struct dom_sid *domain_sid;
164
165 /* If it's not a user, it won't have a primaryGroupID */
166 if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
167 return LDB_SUCCESS;
168 }
169
170 /* Ensure it has an objectSID too */
171 account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
172 if (account_sid == NULL) {
173 return LDB_SUCCESS;
174 }
175
176 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
177 if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
178 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
179 } else if (!NT_STATUS_IS_OK(status)) {
180 return LDB_ERR_OPERATIONS_ERROR;
181 }
182
183 primary_group_sid = dom_sid_add_rid(mem_ctx,
184 domain_sid,
185 ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
186 if (!primary_group_sid) {
187 return ldb_oom(ldb);
188 }
189
190 /* only return security groups */
191 switch(type) {
192 case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
193 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
194 GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
195 break;
196 case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
197 case TOKEN_GROUPS:
198 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
199 GROUP_TYPE_SECURITY_ENABLED);
200 break;
201
202 /* for RevMembGetAccountGroups, exclude built-in groups */
203 case ACCOUNT_GROUPS:
204 filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))",
205 GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
206 break;
207 }
208
209 if (!filter) {
210 return ldb_oom(ldb);
211 }
212
213 primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
214 if (!primary_group_string) {
215 return ldb_oom(ldb);
216 }
217
218 primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
219 if (!primary_group_dn) {
220 return ldb_oom(ldb);
221 }
222
223 primary_group_blob = data_blob_string_const(primary_group_dn);
224
225 account_sid_string = dom_sid_string(mem_ctx, account_sid);
226 if (!account_sid_string) {
227 return ldb_oom(ldb);
228 }
229
230 account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
231 if (!account_sid_dn) {
232 return ldb_oom(ldb);
233 }
234
235 account_sid_blob = data_blob_string_const(account_sid_dn);
236
237 status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
238 true, /* We don't want to add the object's SID itself,
239 it's not returend in this attribute */
240 filter,
241 mem_ctx, groupSIDs, num_groupSIDs);
242
243 if (!NT_STATUS_IS_OK(status)) {
244 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
245 attribute_string, account_sid_string,
246 nt_errstr(status));
247 return LDB_ERR_OPERATIONS_ERROR;
248 }
249
250 /* Expands the primary group - this function takes in
251 * memberOf-like values, so we fake one up with the
252 * <SID=S-...> format of DN and then let it expand
253 * them, as long as they meet the filter - so only
254 * domain groups, not builtin groups
255 */
256 status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
257 mem_ctx, groupSIDs, num_groupSIDs);
258 if (!NT_STATUS_IS_OK(status)) {
259 ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
260 attribute_string, account_sid_string,
261 nt_errstr(status));
262 return LDB_ERR_OPERATIONS_ERROR;
263 }
264
265 return LDB_SUCCESS;
266 }
267
268 /*
269 construct the token groups for SAM objects from a message
270 */
construct_generic_token_groups(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent,const char * attribute_string,enum search_type type)271 static int construct_generic_token_groups(struct ldb_module *module,
272 struct ldb_message *msg, enum ldb_scope scope,
273 struct ldb_request *parent,
274 const char *attribute_string,
275 enum search_type type)
276 {
277 struct ldb_context *ldb = ldb_module_get_ctx(module);
278 TALLOC_CTX *tmp_ctx = talloc_new(msg);
279 unsigned int i;
280 int ret;
281 struct dom_sid *groupSIDs = NULL;
282 unsigned int num_groupSIDs = 0;
283
284 if (scope != LDB_SCOPE_BASE) {
285 ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
286 return LDB_ERR_OPERATIONS_ERROR;
287 }
288
289 /* calculate the group SIDs for this object */
290 ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
291 &groupSIDs, &num_groupSIDs);
292
293 if (ret != LDB_SUCCESS) {
294 talloc_free(tmp_ctx);
295 return LDB_ERR_OPERATIONS_ERROR;
296 }
297
298 /* add these SIDs to the search result */
299 for (i=0; i < num_groupSIDs; i++) {
300 ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
301 if (ret) {
302 talloc_free(tmp_ctx);
303 return ret;
304 }
305 }
306
307 return LDB_SUCCESS;
308 }
309
construct_token_groups(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)310 static int construct_token_groups(struct ldb_module *module,
311 struct ldb_message *msg, enum ldb_scope scope,
312 struct ldb_request *parent)
313 {
314 /**
315 * TODO: Add in a limiting domain when we start to support
316 * trusted domains.
317 */
318 return construct_generic_token_groups(module, msg, scope, parent,
319 "tokenGroups",
320 TOKEN_GROUPS);
321 }
322
construct_token_groups_no_gc(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)323 static int construct_token_groups_no_gc(struct ldb_module *module,
324 struct ldb_message *msg, enum ldb_scope scope,
325 struct ldb_request *parent)
326 {
327 /**
328 * TODO: Add in a limiting domain when we start to support
329 * trusted domains.
330 */
331 return construct_generic_token_groups(module, msg, scope, parent,
332 "tokenGroupsNoGCAcceptable",
333 TOKEN_GROUPS);
334 }
335
construct_global_universal_token_groups(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)336 static int construct_global_universal_token_groups(struct ldb_module *module,
337 struct ldb_message *msg, enum ldb_scope scope,
338 struct ldb_request *parent)
339 {
340 return construct_generic_token_groups(module, msg, scope, parent,
341 "tokenGroupsGlobalAndUniversal",
342 TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
343 }
344 /*
345 construct the parent GUID for an entry from a message
346 */
construct_parent_guid(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)347 static int construct_parent_guid(struct ldb_module *module,
348 struct ldb_message *msg, enum ldb_scope scope,
349 struct ldb_request *parent)
350 {
351 struct ldb_result *res, *parent_res;
352 const struct ldb_val *parent_guid;
353 const char *attrs[] = { "instanceType", NULL };
354 const char *attrs2[] = { "objectGUID", NULL };
355 uint32_t instanceType;
356 int ret;
357 struct ldb_dn *parent_dn;
358 struct ldb_val v;
359
360 /* determine if the object is NC by instance type */
361 ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
362 DSDB_FLAG_NEXT_MODULE |
363 DSDB_SEARCH_SHOW_RECYCLED, parent);
364 if (ret != LDB_SUCCESS) {
365 return ret;
366 }
367
368 instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
369 "instanceType", 0);
370 talloc_free(res);
371 if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
372 DEBUG(4,(__location__ ": Object %s is NC\n",
373 ldb_dn_get_linearized(msg->dn)));
374 return LDB_SUCCESS;
375 }
376 parent_dn = ldb_dn_get_parent(msg, msg->dn);
377
378 if (parent_dn == NULL) {
379 DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
380 ldb_dn_get_linearized(msg->dn)));
381 return LDB_ERR_OTHER;
382 }
383 ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
384 DSDB_FLAG_NEXT_MODULE |
385 DSDB_SEARCH_SHOW_RECYCLED, parent);
386 /* not NC, so the object should have a parent*/
387 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
388 ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
389 talloc_asprintf(msg, "Parent dn %s for %s does not exist",
390 ldb_dn_get_linearized(parent_dn),
391 ldb_dn_get_linearized(msg->dn)));
392 talloc_free(parent_dn);
393 return ret;
394 } else if (ret != LDB_SUCCESS) {
395 talloc_free(parent_dn);
396 return ret;
397 }
398 talloc_free(parent_dn);
399
400 parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
401 if (!parent_guid) {
402 talloc_free(parent_res);
403 return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
404 }
405
406 v = data_blob_dup_talloc(parent_res, *parent_guid);
407 if (!v.data) {
408 talloc_free(parent_res);
409 return ldb_oom(ldb_module_get_ctx(module));
410 }
411 ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
412 talloc_free(parent_res);
413 return ret;
414 }
415
construct_modifyTimeStamp(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)416 static int construct_modifyTimeStamp(struct ldb_module *module,
417 struct ldb_message *msg, enum ldb_scope scope,
418 struct ldb_request *parent)
419 {
420 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
421 struct ldb_context *ldb = ldb_module_get_ctx(module);
422
423 /* We may be being called before the init function has finished */
424 if (!data) {
425 return LDB_SUCCESS;
426 }
427
428 /* Try and set this value up, if possible. Don't worry if it
429 * fails, we may not have the DB set up yet.
430 */
431 if (!data->aggregate_dn) {
432 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
433 }
434
435 if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
436 /*
437 * If we have the DN for the object with common name = Aggregate and
438 * the request is for this DN then let's do the following:
439 * 1) search the object which changedUSN correspond to the one of the loaded
440 * schema.
441 * 2) Get the whenChanged attribute
442 * 3) Generate the modifyTimestamp out of the whenChanged attribute
443 */
444 const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
445 char *value = ldb_timestring(msg, schema->ts_last_change);
446
447 return ldb_msg_add_string(msg, "modifyTimeStamp", value);
448 }
449 return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
450 }
451
452 /*
453 construct a subSchemaSubEntry
454 */
construct_subschema_subentry(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)455 static int construct_subschema_subentry(struct ldb_module *module,
456 struct ldb_message *msg, enum ldb_scope scope,
457 struct ldb_request *parent)
458 {
459 struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
460 char *subSchemaSubEntry;
461
462 /* We may be being called before the init function has finished */
463 if (!data) {
464 return LDB_SUCCESS;
465 }
466
467 /* Try and set this value up, if possible. Don't worry if it
468 * fails, we may not have the DB set up yet, and it's not
469 * really vital anyway */
470 if (!data->aggregate_dn) {
471 struct ldb_context *ldb = ldb_module_get_ctx(module);
472 data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
473 }
474
475 if (data->aggregate_dn) {
476 subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
477 return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
478 }
479 return LDB_SUCCESS;
480 }
481
482
construct_msds_isrodc_with_dn(struct ldb_module * module,struct ldb_message * msg,struct ldb_message_element * object_category)483 static int construct_msds_isrodc_with_dn(struct ldb_module *module,
484 struct ldb_message *msg,
485 struct ldb_message_element *object_category)
486 {
487 struct ldb_context *ldb;
488 struct ldb_dn *dn;
489 const struct ldb_val *val;
490
491 ldb = ldb_module_get_ctx(module);
492 if (!ldb) {
493 DEBUG(4, (__location__ ": Failed to get ldb \n"));
494 return LDB_ERR_OPERATIONS_ERROR;
495 }
496
497 dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
498 if (!dn) {
499 DEBUG(4, (__location__ ": Failed to create dn from %s \n",
500 (const char *)object_category->values[0].data));
501 return ldb_operr(ldb);
502 }
503
504 val = ldb_dn_get_rdn_val(dn);
505 if (!val) {
506 DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
507 ldb_dn_get_linearized(dn)));
508 return ldb_operr(ldb);
509 }
510
511 if (strequal((const char *)val->data, "NTDS-DSA")) {
512 ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
513 } else {
514 ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
515 }
516 return LDB_SUCCESS;
517 }
518
construct_msds_isrodc_with_server_dn(struct ldb_module * module,struct ldb_message * msg,struct ldb_dn * dn,struct ldb_request * parent)519 static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
520 struct ldb_message *msg,
521 struct ldb_dn *dn,
522 struct ldb_request *parent)
523 {
524 struct ldb_dn *server_dn;
525 const char *attr_obj_cat[] = { "objectCategory", NULL };
526 struct ldb_result *res;
527 struct ldb_message_element *object_category;
528 int ret;
529
530 server_dn = ldb_dn_copy(msg, dn);
531 if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
532 DEBUG(4, (__location__ ": Failed to add child to %s \n",
533 ldb_dn_get_linearized(server_dn)));
534 return ldb_operr(ldb_module_get_ctx(module));
535 }
536
537 ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
538 DSDB_FLAG_NEXT_MODULE, parent);
539 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
540 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
541 ldb_dn_get_linearized(server_dn)));
542 return LDB_SUCCESS;
543 } else if (ret != LDB_SUCCESS) {
544 return ret;
545 }
546
547 object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
548 if (!object_category) {
549 DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
550 ldb_dn_get_linearized(res->msgs[0]->dn)));
551 return LDB_SUCCESS;
552 }
553 return construct_msds_isrodc_with_dn(module, msg, object_category);
554 }
555
construct_msds_isrodc_with_computer_dn(struct ldb_module * module,struct ldb_message * msg,struct ldb_request * parent)556 static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
557 struct ldb_message *msg,
558 struct ldb_request *parent)
559 {
560 int ret;
561 struct ldb_dn *server_dn;
562
563 ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
564 &server_dn, parent);
565 if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
566 /* it's OK if we can't find serverReferenceBL attribute */
567 DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
568 ldb_dn_get_linearized(msg->dn)));
569 return LDB_SUCCESS;
570 } else if (ret != LDB_SUCCESS) {
571 return ret;
572 }
573
574 return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
575 }
576
577 /*
578 construct msDS-isRODC attr
579 */
construct_msds_isrodc(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)580 static int construct_msds_isrodc(struct ldb_module *module,
581 struct ldb_message *msg, enum ldb_scope scope,
582 struct ldb_request *parent)
583 {
584 struct ldb_message_element * object_class;
585 struct ldb_message_element * object_category;
586 unsigned int i;
587
588 object_class = ldb_msg_find_element(msg, "objectClass");
589 if (!object_class) {
590 DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
591 ldb_dn_get_linearized(msg->dn)));
592 return ldb_operr(ldb_module_get_ctx(module));
593 }
594
595 for (i=0; i<object_class->num_values; i++) {
596 if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
597 /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
598 * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
599 */
600 object_category = ldb_msg_find_element(msg, "objectCategory");
601 if (!object_category) {
602 DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
603 ldb_dn_get_linearized(msg->dn)));
604 return LDB_SUCCESS;
605 }
606 return construct_msds_isrodc_with_dn(module, msg, object_category);
607 }
608 if (strequal((const char*)object_class->values[i].data, "server")) {
609 /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
610 * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
611 * substituting TN for TO.
612 */
613 return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
614 }
615 if (strequal((const char*)object_class->values[i].data, "computer")) {
616 /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
617 * rule for the "TO is a server object" case, substituting TS for TO.
618 */
619 return construct_msds_isrodc_with_computer_dn(module, msg, parent);
620 }
621 }
622
623 return LDB_SUCCESS;
624 }
625
626
627 /*
628 construct msDS-keyVersionNumber attr
629
630 TODO: Make this based on the 'win2k' DS huristics bit...
631
632 */
construct_msds_keyversionnumber(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)633 static int construct_msds_keyversionnumber(struct ldb_module *module,
634 struct ldb_message *msg,
635 enum ldb_scope scope,
636 struct ldb_request *parent)
637 {
638 uint32_t i;
639 enum ndr_err_code ndr_err;
640 const struct ldb_val *omd_value;
641 struct replPropertyMetaDataBlob *omd;
642 int ret;
643
644 omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
645 if (!omd_value) {
646 /* We can't make up a key version number without meta data */
647 return LDB_SUCCESS;
648 }
649
650 omd = talloc(msg, struct replPropertyMetaDataBlob);
651 if (!omd) {
652 ldb_module_oom(module);
653 return LDB_SUCCESS;
654 }
655
656 ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
657 (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
658 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
659 DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
660 ldb_dn_get_linearized(msg->dn)));
661 return ldb_operr(ldb_module_get_ctx(module));
662 }
663
664 if (omd->version != 1) {
665 DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
666 omd->version, ldb_dn_get_linearized(msg->dn)));
667 talloc_free(omd);
668 return LDB_SUCCESS;
669 }
670 for (i=0; i<omd->ctr.ctr1.count; i++) {
671 if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
672 ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
673 msg, msg,
674 "msDS-KeyVersionNumber",
675 omd->ctr.ctr1.array[i].version);
676 if (ret != LDB_SUCCESS) {
677 talloc_free(omd);
678 return ret;
679 }
680 break;
681 }
682 }
683 return LDB_SUCCESS;
684
685 }
686
687 #define _UF_TRUST_ACCOUNTS ( \
688 UF_WORKSTATION_TRUST_ACCOUNT | \
689 UF_SERVER_TRUST_ACCOUNT | \
690 UF_INTERDOMAIN_TRUST_ACCOUNT \
691 )
692 #define _UF_NO_EXPIRY_ACCOUNTS ( \
693 UF_SMARTCARD_REQUIRED | \
694 UF_DONT_EXPIRE_PASSWD | \
695 _UF_TRUST_ACCOUNTS \
696 )
697
698
699 /*
700 * Returns the Effective-MaximumPasswordAge for a user
701 */
get_user_max_pwd_age(struct ldb_module * module,struct ldb_message * user_msg,struct ldb_request * parent,struct ldb_dn * nc_root)702 static int64_t get_user_max_pwd_age(struct ldb_module *module,
703 struct ldb_message *user_msg,
704 struct ldb_request *parent,
705 struct ldb_dn *nc_root)
706 {
707 int ret;
708 struct ldb_message *pso = NULL;
709 struct ldb_context *ldb = ldb_module_get_ctx(module);
710
711 /* if a PSO applies to the user, use its maxPwdAge */
712 ret = get_pso_for_user(module, user_msg, parent, &pso);
713 if (ret != LDB_SUCCESS) {
714
715 /* log the error, but fallback to the domain default */
716 DBG_ERR("Error retrieving PSO for %s\n",
717 ldb_dn_get_linearized(user_msg->dn));
718 }
719
720 if (pso != NULL) {
721 return ldb_msg_find_attr_as_int64(pso,
722 "msDS-MaximumPasswordAge", 0);
723 }
724
725 /* otherwise return the default domain value */
726 return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
727 }
728
729 /*
730 calculate msDS-UserPasswordExpiryTimeComputed
731 */
get_msds_user_password_expiry_time_computed(struct ldb_module * module,struct ldb_message * msg,struct ldb_request * parent,struct ldb_dn * domain_dn)732 static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
733 struct ldb_message *msg,
734 struct ldb_request *parent,
735 struct ldb_dn *domain_dn)
736 {
737 int64_t pwdLastSet, maxPwdAge;
738 uint32_t userAccountControl;
739 NTTIME ret;
740
741 userAccountControl = ldb_msg_find_attr_as_uint(msg,
742 "userAccountControl",
743 0);
744 if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
745 return 0x7FFFFFFFFFFFFFFFULL;
746 }
747
748 pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
749 if (pwdLastSet == 0) {
750 return 0;
751 }
752
753 if (pwdLastSet <= -1) {
754 /*
755 * This can't really happen...
756 */
757 return 0x7FFFFFFFFFFFFFFFULL;
758 }
759
760 if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
761 /*
762 * Somethings wrong with the clock...
763 */
764 return 0x7FFFFFFFFFFFFFFFULL;
765 }
766
767 /*
768 * Note that maxPwdAge is a stored as negative value.
769 *
770 * Possible values are in the range of:
771 *
772 * maxPwdAge: -864000000001
773 * to
774 * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
775 *
776 */
777 maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
778 if (maxPwdAge >= -864000000000) {
779 /*
780 * This is not really possible...
781 */
782 return 0x7FFFFFFFFFFFFFFFULL;
783 }
784
785 if (maxPwdAge == -0x8000000000000000LL) {
786 return 0x7FFFFFFFFFFFFFFFULL;
787 }
788
789 /*
790 * Note we already catched maxPwdAge == -0x8000000000000000ULL
791 * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
792 *
793 * Remember maxPwdAge is a negative number,
794 * so it results in the following.
795 *
796 * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
797 * =
798 * 0xFFFFFFFFFFFFFFFFULL
799 */
800 ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
801 if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
802 return 0x7FFFFFFFFFFFFFFFULL;
803 }
804
805 return ret;
806 }
807
808 /*
809 * Returns the Effective-LockoutDuration for a user
810 */
get_user_lockout_duration(struct ldb_module * module,struct ldb_message * user_msg,struct ldb_request * parent,struct ldb_dn * nc_root)811 static int64_t get_user_lockout_duration(struct ldb_module *module,
812 struct ldb_message *user_msg,
813 struct ldb_request *parent,
814 struct ldb_dn *nc_root)
815 {
816 int ret;
817 struct ldb_message *pso = NULL;
818 struct ldb_context *ldb = ldb_module_get_ctx(module);
819
820 /* if a PSO applies to the user, use its lockoutDuration */
821 ret = get_pso_for_user(module, user_msg, parent, &pso);
822 if (ret != LDB_SUCCESS) {
823
824 /* log the error, but fallback to the domain default */
825 DBG_ERR("Error retrieving PSO for %s\n",
826 ldb_dn_get_linearized(user_msg->dn));
827 }
828
829 if (pso != NULL) {
830 return ldb_msg_find_attr_as_int64(pso,
831 "msDS-LockoutDuration", 0);
832 }
833
834 /* otherwise return the default domain value */
835 return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
836 NULL);
837 }
838
839 /*
840 construct msDS-User-Account-Control-Computed attr
841 */
construct_msds_user_account_control_computed(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)842 static int construct_msds_user_account_control_computed(struct ldb_module *module,
843 struct ldb_message *msg, enum ldb_scope scope,
844 struct ldb_request *parent)
845 {
846 uint32_t userAccountControl;
847 uint32_t msDS_User_Account_Control_Computed = 0;
848 struct ldb_context *ldb = ldb_module_get_ctx(module);
849 NTTIME now;
850 struct ldb_dn *nc_root;
851 int ret;
852
853 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
854 if (ret != 0) {
855 ldb_asprintf_errstring(ldb,
856 "Failed to find NC root of DN: %s: %s",
857 ldb_dn_get_linearized(msg->dn),
858 ldb_errstring(ldb_module_get_ctx(module)));
859 return ret;
860 }
861 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
862 /* Only calculate this on our default NC */
863 return 0;
864 }
865 /* Test account expire time */
866 unix_to_nt_time(&now, time(NULL));
867
868 userAccountControl = ldb_msg_find_attr_as_uint(msg,
869 "userAccountControl",
870 0);
871 if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
872
873 int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
874 if (lockoutTime != 0) {
875 int64_t lockoutDuration;
876
877 lockoutDuration = get_user_lockout_duration(module, msg,
878 parent,
879 nc_root);
880
881 /* zero locks out until the administrator intervenes */
882 if (lockoutDuration >= 0) {
883 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
884 } else if (lockoutTime - lockoutDuration >= now) {
885 msDS_User_Account_Control_Computed |= UF_LOCKOUT;
886 }
887 }
888 }
889
890 if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
891 NTTIME must_change_time
892 = get_msds_user_password_expiry_time_computed(module,
893 msg,
894 parent,
895 nc_root);
896 /* check for expired password */
897 if (must_change_time < now) {
898 msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
899 }
900 }
901
902 return samdb_msg_add_int64(ldb,
903 msg->elements, msg,
904 "msDS-User-Account-Control-Computed",
905 msDS_User_Account_Control_Computed);
906 }
907
908 /*
909 construct msDS-UserPasswordExpiryTimeComputed
910 */
construct_msds_user_password_expiry_time_computed(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)911 static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
912 struct ldb_message *msg, enum ldb_scope scope,
913 struct ldb_request *parent)
914 {
915 struct ldb_context *ldb = ldb_module_get_ctx(module);
916 struct ldb_dn *nc_root;
917 int64_t password_expiry_time;
918 int ret;
919
920 ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
921 if (ret != 0) {
922 ldb_asprintf_errstring(ldb,
923 "Failed to find NC root of DN: %s: %s",
924 ldb_dn_get_linearized(msg->dn),
925 ldb_errstring(ldb));
926 return ret;
927 }
928
929 if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
930 /* Only calculate this on our default NC */
931 return 0;
932 }
933
934 password_expiry_time
935 = get_msds_user_password_expiry_time_computed(module, msg,
936 parent, nc_root);
937
938 return samdb_msg_add_int64(ldb,
939 msg->elements, msg,
940 "msDS-UserPasswordExpiryTimeComputed",
941 password_expiry_time);
942 }
943
944 /*
945 * Checks whether the msDS-ResultantPSO attribute is supported for a given
946 * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
947 */
pso_is_supported(struct ldb_context * ldb,struct ldb_message * msg)948 static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
949 {
950 int functional_level;
951 uint32_t uac;
952 uint32_t user_rid;
953
954 functional_level = dsdb_functional_level(ldb);
955 if (functional_level < DS_DOMAIN_FUNCTION_2008) {
956 return false;
957 }
958
959 /* msDS-ResultantPSO is only supported for user objects */
960 if (!ldb_match_msg_objectclass(msg, "user")) {
961 return false;
962 }
963
964 /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
965 uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
966 if (!(uac & UF_NORMAL_ACCOUNT)) {
967 return false;
968 }
969
970 /* skip it if it's the special KRBTGT default account */
971 user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
972 if (user_rid == DOMAIN_RID_KRBTGT) {
973 return false;
974 }
975
976 /* ...or if it's a special KRBTGT account for an RODC KDC */
977 if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
978 return false;
979 }
980
981 return true;
982 }
983
984 /*
985 * Returns the number of PSO objects that exist in the DB
986 */
get_pso_count(struct ldb_module * module,TALLOC_CTX * mem_ctx,struct ldb_request * parent,int * pso_count)987 static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
988 struct ldb_request *parent, int *pso_count)
989 {
990 static const char * const attrs[] = { NULL };
991 int ret;
992 struct ldb_dn *domain_dn = NULL;
993 struct ldb_dn *psc_dn = NULL;
994 struct ldb_result *res = NULL;
995 struct ldb_context *ldb = ldb_module_get_ctx(module);
996
997 *pso_count = 0;
998 domain_dn = ldb_get_default_basedn(ldb);
999 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1000 "CN=Password Settings Container,CN=System,%s",
1001 ldb_dn_get_linearized(domain_dn));
1002 if (psc_dn == NULL) {
1003 return ldb_oom(ldb);
1004 }
1005
1006 /* get the number of PSO children */
1007 ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
1008 LDB_SCOPE_ONELEVEL, attrs,
1009 DSDB_FLAG_NEXT_MODULE, parent,
1010 "(objectClass=msDS-PasswordSettings)");
1011
1012 /*
1013 * Just ignore PSOs if the container doesn't exist. This is a weird
1014 * corner-case where the AD DB was created from a pre-2008 base schema,
1015 * and then the FL was manually upgraded.
1016 */
1017 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
1018 DBG_NOTICE("No Password Settings Container exists\n");
1019 return LDB_SUCCESS;
1020 }
1021
1022 if (ret != LDB_SUCCESS) {
1023 return ret;
1024 }
1025
1026 *pso_count = res->count;
1027 talloc_free(res);
1028 talloc_free(psc_dn);
1029
1030 return LDB_SUCCESS;
1031 }
1032
1033 /*
1034 * Compares two PSO objects returned by a search, to work out the better PSO.
1035 * The PSO with the lowest precedence is better, otherwise (if the precedence
1036 * is equal) the PSO with the lower GUID wins.
1037 */
pso_compare(struct ldb_message ** m1,struct ldb_message ** m2)1038 static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
1039 {
1040 uint32_t prec1;
1041 uint32_t prec2;
1042
1043 prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
1044 0xffffffff);
1045 prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
1046 0xffffffff);
1047
1048 /* if precedence is equal, use the lowest GUID */
1049 if (prec1 == prec2) {
1050 struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
1051 struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
1052
1053 return ndr_guid_compare(&guid1, &guid2);
1054 } else {
1055 return prec1 - prec2;
1056 }
1057 }
1058
1059 /*
1060 * Search for PSO objects that apply to the object SIDs specified
1061 */
pso_search_by_sids(struct ldb_module * module,TALLOC_CTX * mem_ctx,struct ldb_request * parent,struct dom_sid * sid_array,unsigned int num_sids,struct ldb_result ** result)1062 static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1063 struct ldb_request *parent,
1064 struct dom_sid *sid_array, unsigned int num_sids,
1065 struct ldb_result **result)
1066 {
1067 int ret;
1068 int i;
1069 struct ldb_context *ldb = ldb_module_get_ctx(module);
1070 char *sid_filter = NULL;
1071 struct ldb_dn *domain_dn = NULL;
1072 struct ldb_dn *psc_dn = NULL;
1073 const char *attrs[] = {
1074 "msDS-PasswordSettingsPrecedence",
1075 "objectGUID",
1076 "msDS-LockoutDuration",
1077 "msDS-MaximumPasswordAge",
1078 NULL
1079 };
1080
1081 /* build a query for PSO objects that apply to any of the SIDs given */
1082 sid_filter = talloc_strdup(mem_ctx, "");
1083
1084 for (i = 0; sid_filter && i < num_sids; i++) {
1085 struct dom_sid_buf sid_buf;
1086
1087 sid_filter = talloc_asprintf_append(
1088 sid_filter,
1089 "(msDS-PSOAppliesTo=<SID=%s>)",
1090 dom_sid_str_buf(&sid_array[i], &sid_buf));
1091 }
1092
1093 if (sid_filter == NULL) {
1094 return ldb_oom(ldb);
1095 }
1096
1097 /* only PSOs located in the Password Settings Container are valid */
1098 domain_dn = ldb_get_default_basedn(ldb);
1099 psc_dn = ldb_dn_new_fmt(mem_ctx, ldb,
1100 "CN=Password Settings Container,CN=System,%s",
1101 ldb_dn_get_linearized(domain_dn));
1102 if (psc_dn == NULL) {
1103 return ldb_oom(ldb);
1104 }
1105
1106 ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
1107 LDB_SCOPE_ONELEVEL, attrs,
1108 DSDB_FLAG_NEXT_MODULE, parent,
1109 "(&(objectClass=msDS-PasswordSettings)(|%s))",
1110 sid_filter);
1111 talloc_free(sid_filter);
1112 return ret;
1113 }
1114
1115 /*
1116 * Returns the best PSO object that applies to the object SID(s) specified
1117 */
pso_find_best(struct ldb_module * module,TALLOC_CTX * mem_ctx,struct ldb_request * parent,struct dom_sid * sid_array,unsigned int num_sids,struct ldb_message ** best_pso)1118 static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
1119 struct ldb_request *parent, struct dom_sid *sid_array,
1120 unsigned int num_sids, struct ldb_message **best_pso)
1121 {
1122 struct ldb_result *res = NULL;
1123 int ret;
1124
1125 *best_pso = NULL;
1126
1127 /* find any PSOs that apply to the SIDs specified */
1128 ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
1129 &res);
1130 if (ret != LDB_SUCCESS) {
1131 DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
1132 return ret;
1133 }
1134
1135 /* sort the list so that the best PSO is first */
1136 TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
1137
1138 if (res->count > 0) {
1139 *best_pso = res->msgs[0];
1140 }
1141
1142 return LDB_SUCCESS;
1143 }
1144
1145 /*
1146 * Determines the Password Settings Object (PSO) that applies to the given user
1147 */
get_pso_for_user(struct ldb_module * module,struct ldb_message * user_msg,struct ldb_request * parent,struct ldb_message ** pso_msg)1148 static int get_pso_for_user(struct ldb_module *module,
1149 struct ldb_message *user_msg,
1150 struct ldb_request *parent,
1151 struct ldb_message **pso_msg)
1152 {
1153 bool pso_supported;
1154 struct dom_sid *groupSIDs = NULL;
1155 unsigned int num_groupSIDs = 0;
1156 struct ldb_context *ldb = ldb_module_get_ctx(module);
1157 struct ldb_message *best_pso = NULL;
1158 struct ldb_dn *pso_dn = NULL;
1159 int ret;
1160 struct ldb_message_element *el = NULL;
1161 TALLOC_CTX *tmp_ctx = NULL;
1162 int pso_count = 0;
1163 struct ldb_result *res = NULL;
1164 static const char *attrs[] = {
1165 "msDS-LockoutDuration",
1166 "msDS-MaximumPasswordAge",
1167 NULL
1168 };
1169
1170 *pso_msg = NULL;
1171
1172 /* first, check msDS-ResultantPSO is supported for this object */
1173 pso_supported = pso_is_supported(ldb, user_msg);
1174
1175 if (!pso_supported) {
1176 return LDB_SUCCESS;
1177 }
1178
1179 tmp_ctx = talloc_new(user_msg);
1180
1181 /*
1182 * Several different constructed attributes try to use the PSO info. If
1183 * we've already constructed the msDS-ResultantPSO for this user, we can
1184 * just re-use the result, rather than calculating it from scratch again
1185 */
1186 pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
1187 "msDS-ResultantPSO");
1188
1189 if (pso_dn != NULL) {
1190 ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
1191 attrs, DSDB_FLAG_NEXT_MODULE,
1192 parent);
1193 if (ret != LDB_SUCCESS) {
1194 DBG_ERR("Error %d retrieving PSO %s\n", ret,
1195 ldb_dn_get_linearized(pso_dn));
1196 talloc_free(tmp_ctx);
1197 return ret;
1198 }
1199
1200 if (res->count == 1) {
1201 *pso_msg = res->msgs[0];
1202 return LDB_SUCCESS;
1203 }
1204 }
1205
1206 /*
1207 * if any PSOs apply directly to the user, they are considered first
1208 * before we check group membership PSOs
1209 */
1210 el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
1211
1212 if (el != NULL && el->num_values > 0) {
1213 struct dom_sid *user_sid = NULL;
1214
1215 /* lookup the best PSO object, based on the user's SID */
1216 user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
1217
1218 ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
1219 &best_pso);
1220 if (ret != LDB_SUCCESS) {
1221 talloc_free(tmp_ctx);
1222 return ret;
1223 }
1224
1225 if (best_pso != NULL) {
1226 *pso_msg = best_pso;
1227 return LDB_SUCCESS;
1228 }
1229 }
1230
1231 /*
1232 * If no valid PSO applies directly to the user, then try its groups.
1233 * The group expansion is expensive, so check there are actually
1234 * PSOs in the DB first (which is a quick search). Note in the above
1235 * cases we could tell that a PSO applied to the user, based on info
1236 * already retrieved by the user search.
1237 */
1238 ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
1239 if (ret != LDB_SUCCESS) {
1240 DBG_ERR("Error %d determining PSOs in system\n", ret);
1241 talloc_free(tmp_ctx);
1242 return ret;
1243 }
1244
1245 if (pso_count == 0) {
1246 talloc_free(tmp_ctx);
1247 return LDB_SUCCESS;
1248 }
1249
1250 /* Work out the SIDs of any account groups the user is a member of */
1251 ret = get_group_sids(ldb, tmp_ctx, user_msg,
1252 "msDS-ResultantPSO", ACCOUNT_GROUPS,
1253 &groupSIDs, &num_groupSIDs);
1254 if (ret != LDB_SUCCESS) {
1255 DBG_ERR("Error %d determining group SIDs for %s\n", ret,
1256 ldb_dn_get_linearized(user_msg->dn));
1257 talloc_free(tmp_ctx);
1258 return ret;
1259 }
1260
1261 /* lookup the best PSO that applies to any of these groups */
1262 ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
1263 num_groupSIDs, &best_pso);
1264 if (ret != LDB_SUCCESS) {
1265 talloc_free(tmp_ctx);
1266 return ret;
1267 }
1268
1269 *pso_msg = best_pso;
1270 return LDB_SUCCESS;
1271 }
1272
1273 /*
1274 * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
1275 * Settings Object (PSO) that applies to that user.
1276 */
construct_resultant_pso(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,struct ldb_request * parent)1277 static int construct_resultant_pso(struct ldb_module *module,
1278 struct ldb_message *msg,
1279 enum ldb_scope scope,
1280 struct ldb_request *parent)
1281 {
1282 struct ldb_message *pso = NULL;
1283 int ret;
1284
1285 /* work out the PSO (if any) that applies to this user */
1286 ret = get_pso_for_user(module, msg, parent, &pso);
1287 if (ret != LDB_SUCCESS) {
1288 DBG_ERR("Couldn't determine PSO for %s\n",
1289 ldb_dn_get_linearized(msg->dn));
1290 return ret;
1291 }
1292
1293 if (pso != NULL) {
1294 DBG_INFO("%s is resultant PSO for user %s\n",
1295 ldb_dn_get_linearized(pso->dn),
1296 ldb_dn_get_linearized(msg->dn));
1297 return ldb_msg_add_string(msg, "msDS-ResultantPSO",
1298 ldb_dn_get_linearized(pso->dn));
1299 }
1300
1301 /* no PSO applies to this user */
1302 return LDB_SUCCESS;
1303 }
1304
1305 struct op_controls_flags {
1306 bool sd;
1307 bool bypassoperational;
1308 };
1309
check_keep_control_for_attribute(struct op_controls_flags * controls_flags,const char * attr)1310 static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
1311 if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
1312 return true;
1313 }
1314 return false;
1315 }
1316
1317 /*
1318 a list of attribute names that should be substituted in the parse
1319 tree before the search is done
1320 */
1321 static const struct {
1322 const char *attr;
1323 const char *replace;
1324 } parse_tree_sub[] = {
1325 { "createTimeStamp", "whenCreated" },
1326 { "modifyTimeStamp", "whenChanged" }
1327 };
1328
1329
1330 struct op_attributes_replace {
1331 const char *attr;
1332 const char *replace;
1333 const char * const *extra_attrs;
1334 int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
1335 };
1336
1337 /* the 'extra_attrs' required for msDS-ResultantPSO */
1338 #define RESULTANT_PSO_COMPUTED_ATTRS \
1339 "msDS-PSOApplied", \
1340 "userAccountControl", \
1341 "objectSid", \
1342 "msDS-SecondaryKrbTgtNumber", \
1343 "primaryGroupID"
1344
1345 /*
1346 * any other constructed attributes that want to work out the PSO also need to
1347 * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
1348 */
1349 #define PSO_ATTR_DEPENDENCIES \
1350 RESULTANT_PSO_COMPUTED_ATTRS, \
1351 "objectClass"
1352
1353 static const char *objectSid_attr[] =
1354 {
1355 "objectSid",
1356 NULL
1357 };
1358
1359
1360 static const char *objectCategory_attr[] =
1361 {
1362 "objectCategory",
1363 NULL
1364 };
1365
1366
1367 static const char *user_account_control_computed_attrs[] =
1368 {
1369 "lockoutTime",
1370 "pwdLastSet",
1371 PSO_ATTR_DEPENDENCIES,
1372 NULL
1373 };
1374
1375
1376 static const char *user_password_expiry_time_computed_attrs[] =
1377 {
1378 "pwdLastSet",
1379 PSO_ATTR_DEPENDENCIES,
1380 NULL
1381 };
1382
1383 static const char *resultant_pso_computed_attrs[] =
1384 {
1385 RESULTANT_PSO_COMPUTED_ATTRS,
1386 NULL
1387 };
1388
1389 /*
1390 a list of attribute names that are hidden, but can be searched for
1391 using another (non-hidden) name to produce the correct result
1392 */
1393 static const struct op_attributes_replace search_sub[] = {
1394 { "createTimeStamp", "whenCreated", NULL , NULL },
1395 { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
1396 { "structuralObjectClass", "objectClass", NULL , NULL },
1397 { "canonicalName", NULL, NULL , construct_canonical_name },
1398 { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
1399 { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
1400 { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
1401 { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
1402 { "parentGUID", NULL, NULL, construct_parent_guid },
1403 { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
1404 { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
1405 { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
1406 { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
1407 construct_msds_user_account_control_computed },
1408 { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
1409 construct_msds_user_password_expiry_time_computed },
1410 { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
1411 construct_resultant_pso }
1412 };
1413
1414
1415 enum op_remove {
1416 OPERATIONAL_REMOVE_ALWAYS, /* remove always */
1417 OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
1418 OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
1419 OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
1420 };
1421
1422 /*
1423 a list of attributes that may need to be removed from the
1424 underlying db return
1425
1426 Some of these are attributes that were once stored, but are now calculated
1427 */
1428 struct op_attributes_operations {
1429 const char *attr;
1430 enum op_remove op;
1431 };
1432
1433 static const struct op_attributes_operations operational_remove[] = {
1434 { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
1435 { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
1436 { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
1437 { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
1438 #define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
1439 { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
1440 };
1441
1442
1443 /*
1444 post process a search result record. For any search_sub[] attributes that were
1445 asked for, we need to call the appropriate copy routine to copy the result
1446 into the message, then remove any attributes that we added to the search but
1447 were not asked for by the user
1448 */
operational_search_post_process(struct ldb_module * module,struct ldb_message * msg,enum ldb_scope scope,const char * const * attrs_from_user,const char * const * attrs_searched_for,struct op_controls_flags * controls_flags,struct op_attributes_operations * list,unsigned int list_size,struct op_attributes_replace * list_replace,unsigned int list_replace_size,struct ldb_request * parent)1449 static int operational_search_post_process(struct ldb_module *module,
1450 struct ldb_message *msg,
1451 enum ldb_scope scope,
1452 const char * const *attrs_from_user,
1453 const char * const *attrs_searched_for,
1454 struct op_controls_flags* controls_flags,
1455 struct op_attributes_operations *list,
1456 unsigned int list_size,
1457 struct op_attributes_replace *list_replace,
1458 unsigned int list_replace_size,
1459 struct ldb_request *parent)
1460 {
1461 struct ldb_context *ldb;
1462 unsigned int i, a = 0;
1463 bool constructed_attributes = false;
1464
1465 ldb = ldb_module_get_ctx(module);
1466
1467 /* removed any attrs that should not be shown to the user */
1468 for (i=0; i < list_size; i++) {
1469 ldb_msg_remove_attr(msg, list[i].attr);
1470 }
1471
1472 for (a=0; a < list_replace_size; a++) {
1473 if (check_keep_control_for_attribute(controls_flags,
1474 list_replace[a].attr)) {
1475 continue;
1476 }
1477
1478 /* construct the new attribute, using either a supplied
1479 constructor or a simple copy */
1480 constructed_attributes = true;
1481 if (list_replace[a].constructor != NULL) {
1482 if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
1483 goto failed;
1484 }
1485 } else if (ldb_msg_copy_attr(msg,
1486 list_replace[a].replace,
1487 list_replace[a].attr) != LDB_SUCCESS) {
1488 goto failed;
1489 }
1490 }
1491
1492 /* Deletion of the search helper attributes are needed if:
1493 * - we generated constructed attributes and
1494 * - we aren't requesting all attributes
1495 */
1496 if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
1497 for (i=0; i < list_replace_size; i++) {
1498 /* remove the added search helper attributes, unless
1499 * they were asked for by the user */
1500 if (list_replace[i].replace != NULL &&
1501 !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
1502 ldb_msg_remove_attr(msg, list_replace[i].replace);
1503 }
1504 if (list_replace[i].extra_attrs != NULL) {
1505 unsigned int j;
1506 for (j=0; list_replace[i].extra_attrs[j]; j++) {
1507 if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
1508 ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
1509 }
1510 }
1511 }
1512 }
1513 }
1514
1515 return 0;
1516
1517 failed:
1518 ldb_debug_set(ldb, LDB_DEBUG_WARNING,
1519 "operational_search_post_process failed for attribute '%s' - %s",
1520 list_replace[a].attr, ldb_errstring(ldb));
1521 return -1;
1522 }
1523
1524 /*
1525 hook search operations
1526 */
1527
1528 struct operational_context {
1529 struct ldb_module *module;
1530 struct ldb_request *req;
1531 enum ldb_scope scope;
1532 const char * const *attrs;
1533 struct op_controls_flags* controls_flags;
1534 struct op_attributes_operations *list_operations;
1535 unsigned int list_operations_size;
1536 struct op_attributes_replace *attrs_to_replace;
1537 unsigned int attrs_to_replace_size;
1538 };
1539
operational_callback(struct ldb_request * req,struct ldb_reply * ares)1540 static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
1541 {
1542 struct operational_context *ac;
1543 int ret;
1544
1545 ac = talloc_get_type(req->context, struct operational_context);
1546
1547 if (!ares) {
1548 return ldb_module_done(ac->req, NULL, NULL,
1549 LDB_ERR_OPERATIONS_ERROR);
1550 }
1551 if (ares->error != LDB_SUCCESS) {
1552 return ldb_module_done(ac->req, ares->controls,
1553 ares->response, ares->error);
1554 }
1555
1556 switch (ares->type) {
1557 case LDB_REPLY_ENTRY:
1558 /* for each record returned post-process to add any derived
1559 attributes that have been asked for */
1560 ret = operational_search_post_process(ac->module,
1561 ares->message,
1562 ac->scope,
1563 ac->attrs,
1564 req->op.search.attrs,
1565 ac->controls_flags,
1566 ac->list_operations,
1567 ac->list_operations_size,
1568 ac->attrs_to_replace,
1569 ac->attrs_to_replace_size,
1570 req);
1571 if (ret != 0) {
1572 return ldb_module_done(ac->req, NULL, NULL,
1573 LDB_ERR_OPERATIONS_ERROR);
1574 }
1575 return ldb_module_send_entry(ac->req, ares->message, ares->controls);
1576
1577 case LDB_REPLY_REFERRAL:
1578 return ldb_module_send_referral(ac->req, ares->referral);
1579
1580 case LDB_REPLY_DONE:
1581
1582 return ldb_module_done(ac->req, ares->controls,
1583 ares->response, LDB_SUCCESS);
1584 }
1585
1586 talloc_free(ares);
1587 return LDB_SUCCESS;
1588 }
1589
operation_get_op_list(TALLOC_CTX * ctx,const char * const * attrs,const char * const * searched_attrs,struct op_controls_flags * controls_flags)1590 static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
1591 const char* const* attrs,
1592 const char* const* searched_attrs,
1593 struct op_controls_flags* controls_flags)
1594 {
1595 int idx = 0;
1596 int i;
1597 struct op_attributes_operations *list = talloc_zero_array(ctx,
1598 struct op_attributes_operations,
1599 ARRAY_SIZE(operational_remove) + 1);
1600
1601 if (list == NULL) {
1602 return NULL;
1603 }
1604
1605 for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
1606 switch (operational_remove[i].op) {
1607 case OPERATIONAL_REMOVE_UNASKED:
1608 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1609 continue;
1610 }
1611 if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
1612 continue;
1613 }
1614 list[idx].attr = operational_remove[i].attr;
1615 list[idx].op = OPERATIONAL_REMOVE_UNASKED;
1616 idx++;
1617 break;
1618
1619 case OPERATIONAL_REMOVE_ALWAYS:
1620 list[idx].attr = operational_remove[i].attr;
1621 list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
1622 idx++;
1623 break;
1624
1625 case OPERATIONAL_REMOVE_UNLESS_CONTROL:
1626 if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
1627 list[idx].attr = operational_remove[i].attr;
1628 list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
1629 idx++;
1630 }
1631 break;
1632
1633 case OPERATIONAL_SD_FLAGS:
1634 if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
1635 continue;
1636 }
1637 if (controls_flags->sd) {
1638 if (attrs == NULL) {
1639 continue;
1640 }
1641 if (attrs[0] == NULL) {
1642 continue;
1643 }
1644 if (ldb_attr_in_list(attrs, "*")) {
1645 continue;
1646 }
1647 }
1648 list[idx].attr = operational_remove[i].attr;
1649 list[idx].op = OPERATIONAL_SD_FLAGS;
1650 idx++;
1651 break;
1652 }
1653 }
1654
1655 return list;
1656 }
1657
operational_search(struct ldb_module * module,struct ldb_request * req)1658 static int operational_search(struct ldb_module *module, struct ldb_request *req)
1659 {
1660 struct ldb_context *ldb;
1661 struct operational_context *ac;
1662 struct ldb_request *down_req;
1663 const char **search_attrs = NULL;
1664 unsigned int i, a;
1665 int ret;
1666
1667 /* There are no operational attributes on special DNs */
1668 if (ldb_dn_is_special(req->op.search.base)) {
1669 return ldb_next_request(module, req);
1670 }
1671
1672 ldb = ldb_module_get_ctx(module);
1673
1674 ac = talloc(req, struct operational_context);
1675 if (ac == NULL) {
1676 return ldb_oom(ldb);
1677 }
1678
1679 ac->module = module;
1680 ac->req = req;
1681 ac->scope = req->op.search.scope;
1682 ac->attrs = req->op.search.attrs;
1683
1684 /* FIXME: We must copy the tree and keep the original
1685 * unmodified. SSS */
1686 /* replace any attributes in the parse tree that are
1687 searchable, but are stored using a different name in the
1688 backend */
1689 for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
1690 ldb_parse_tree_attr_replace(req->op.search.tree,
1691 parse_tree_sub[i].attr,
1692 parse_tree_sub[i].replace);
1693 }
1694
1695 ac->controls_flags = talloc(ac, struct op_controls_flags);
1696 /* remember if the SD_FLAGS_OID was set */
1697 ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
1698 /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
1699 ac->controls_flags->bypassoperational =
1700 (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
1701
1702 ac->attrs_to_replace = NULL;
1703 ac->attrs_to_replace_size = 0;
1704 /* in the list of attributes we are looking for, rename any
1705 attributes to the alias for any hidden attributes that can
1706 be fetched directly using non-hidden names.
1707 Note that order here can affect performance, e.g. we should process
1708 msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
1709 latter is also dependent on the PSO information) */
1710 for (a=0;ac->attrs && ac->attrs[a];a++) {
1711 if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
1712 continue;
1713 }
1714 for (i=0;i<ARRAY_SIZE(search_sub);i++) {
1715
1716 if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
1717 continue;
1718 }
1719
1720 ac->attrs_to_replace = talloc_realloc(ac,
1721 ac->attrs_to_replace,
1722 struct op_attributes_replace,
1723 ac->attrs_to_replace_size + 1);
1724
1725 ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
1726 ac->attrs_to_replace_size++;
1727 if (!search_sub[i].replace) {
1728 continue;
1729 }
1730
1731 if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
1732 unsigned int j;
1733 const char **search_attrs2;
1734 /* Only adds to the end of the list */
1735 for (j = 0; search_sub[i].extra_attrs[j]; j++) {
1736 search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
1737 ? search_attrs
1738 : ac->attrs,
1739 search_sub[i].extra_attrs[j]);
1740 if (search_attrs2 == NULL) {
1741 return ldb_operr(ldb);
1742 }
1743 /* may be NULL, talloc_free() doesn't mind */
1744 talloc_free(search_attrs);
1745 search_attrs = search_attrs2;
1746 }
1747 }
1748
1749 if (!search_attrs) {
1750 search_attrs = ldb_attr_list_copy(req, ac->attrs);
1751 if (search_attrs == NULL) {
1752 return ldb_operr(ldb);
1753 }
1754 }
1755 /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
1756 search_attrs[a] = search_sub[i].replace;
1757 }
1758 }
1759 ac->list_operations = operation_get_op_list(ac, ac->attrs,
1760 search_attrs == NULL?req->op.search.attrs:search_attrs,
1761 ac->controls_flags);
1762 ac->list_operations_size = 0;
1763 i = 0;
1764
1765 while (ac->list_operations && ac->list_operations[i].attr != NULL) {
1766 i++;
1767 }
1768 ac->list_operations_size = i;
1769 ret = ldb_build_search_req_ex(&down_req, ldb, ac,
1770 req->op.search.base,
1771 req->op.search.scope,
1772 req->op.search.tree,
1773 /* use new set of attrs if any */
1774 search_attrs == NULL?req->op.search.attrs:search_attrs,
1775 req->controls,
1776 ac, operational_callback,
1777 req);
1778 LDB_REQ_SET_LOCATION(down_req);
1779 if (ret != LDB_SUCCESS) {
1780 return ldb_operr(ldb);
1781 }
1782
1783 /* perform the search */
1784 return ldb_next_request(module, down_req);
1785 }
1786
operational_init(struct ldb_module * ctx)1787 static int operational_init(struct ldb_module *ctx)
1788 {
1789 struct operational_data *data;
1790 int ret;
1791
1792 ret = ldb_next_init(ctx);
1793
1794 if (ret != LDB_SUCCESS) {
1795 return ret;
1796 }
1797
1798 data = talloc_zero(ctx, struct operational_data);
1799 if (!data) {
1800 return ldb_module_oom(ctx);
1801 }
1802
1803 ldb_module_set_private(ctx, data);
1804
1805 return LDB_SUCCESS;
1806 }
1807
1808 static const struct ldb_module_ops ldb_operational_module_ops = {
1809 .name = "operational",
1810 .search = operational_search,
1811 .init_context = operational_init
1812 };
1813
ldb_operational_module_init(const char * version)1814 int ldb_operational_module_init(const char *version)
1815 {
1816 LDB_MODULE_CHECK_VERSION(version);
1817 return ldb_register_module(&ldb_operational_module_ops);
1818 }
1819