Revert chunk applied by mistake as part of the cosmetic fixes fromi Mathias
[Samba/bb.git] / source4 / lib / ldb-samba / ldif_handlers.c
bloba16582d29458a1e0b23cfa7e822c0e1e8fecf4a1
1 /*
2 ldb database library - ldif handlers for Samba
4 Copyright (C) Andrew Tridgell 2005
5 Copyright (C) Andrew Bartlett 2006-2007
6 ** NOTE! The following LGPL license applies to the ldb
7 ** library. This does NOT imply that all of Samba is released
8 ** under the LGPL
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Lesser General Public
12 License as published by the Free Software Foundation; either
13 version 3 of the License, or (at your option) any later version.
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, see <http://www.gnu.org/licenses/>.
24 #include "includes.h"
25 #include "lib/ldb/include/ldb_includes.h"
26 #include "dsdb/samdb/samdb.h"
27 #include "librpc/gen_ndr/ndr_security.h"
28 #include "librpc/gen_ndr/ndr_misc.h"
29 #include "librpc/gen_ndr/ndr_drsblobs.h"
30 #include "libcli/security/security.h"
31 #include "param/param.h"
34 convert a ldif formatted objectSid to a NDR formatted blob
36 static int ldif_read_objectSid(struct ldb_context *ldb, void *mem_ctx,
37 const struct ldb_val *in, struct ldb_val *out)
39 enum ndr_err_code ndr_err;
40 struct dom_sid *sid;
41 sid = dom_sid_parse_length(mem_ctx, in);
42 if (sid == NULL) {
43 return -1;
45 ndr_err = ndr_push_struct_blob(out, mem_ctx, NULL, sid,
46 (ndr_push_flags_fn_t)ndr_push_dom_sid);
47 talloc_free(sid);
48 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
49 return -1;
51 return 0;
55 convert a NDR formatted blob to a ldif formatted objectSid
57 static int ldif_write_objectSid(struct ldb_context *ldb, void *mem_ctx,
58 const struct ldb_val *in, struct ldb_val *out)
60 struct dom_sid *sid;
61 enum ndr_err_code ndr_err;
63 sid = talloc(mem_ctx, struct dom_sid);
64 if (sid == NULL) {
65 return -1;
67 ndr_err = ndr_pull_struct_blob(in, sid, NULL, sid,
68 (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
69 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
70 talloc_free(sid);
71 return -1;
73 *out = data_blob_string_const(dom_sid_string(mem_ctx, sid));
74 talloc_free(sid);
75 if (out->data == NULL) {
76 return -1;
78 return 0;
81 static bool ldb_comparision_objectSid_isString(const struct ldb_val *v)
83 if (v->length < 3) {
84 return false;
87 if (strncmp("S-", (const char *)v->data, 2) != 0) return false;
89 return true;
93 compare two objectSids
95 static int ldb_comparison_objectSid(struct ldb_context *ldb, void *mem_ctx,
96 const struct ldb_val *v1, const struct ldb_val *v2)
98 if (ldb_comparision_objectSid_isString(v1) && ldb_comparision_objectSid_isString(v2)) {
99 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
100 } else if (ldb_comparision_objectSid_isString(v1)
101 && !ldb_comparision_objectSid_isString(v2)) {
102 struct ldb_val v;
103 int ret;
104 if (ldif_read_objectSid(ldb, mem_ctx, v1, &v) != 0) {
105 /* Perhaps not a string after all */
106 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
108 ret = ldb_comparison_binary(ldb, mem_ctx, &v, v2);
109 talloc_free(v.data);
110 return ret;
111 } else if (!ldb_comparision_objectSid_isString(v1)
112 && ldb_comparision_objectSid_isString(v2)) {
113 struct ldb_val v;
114 int ret;
115 if (ldif_read_objectSid(ldb, mem_ctx, v2, &v) != 0) {
116 /* Perhaps not a string after all */
117 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
119 ret = ldb_comparison_binary(ldb, mem_ctx, v1, &v);
120 talloc_free(v.data);
121 return ret;
123 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
127 canonicalise a objectSid
129 static int ldb_canonicalise_objectSid(struct ldb_context *ldb, void *mem_ctx,
130 const struct ldb_val *in, struct ldb_val *out)
132 if (ldb_comparision_objectSid_isString(in)) {
133 if (ldif_read_objectSid(ldb, mem_ctx, in, out) != 0) {
134 /* Perhaps not a string after all */
135 return ldb_handler_copy(ldb, mem_ctx, in, out);
137 return 0;
139 return ldb_handler_copy(ldb, mem_ctx, in, out);
143 convert a ldif formatted objectGUID to a NDR formatted blob
145 static int ldif_read_objectGUID(struct ldb_context *ldb, void *mem_ctx,
146 const struct ldb_val *in, struct ldb_val *out)
148 struct GUID guid;
149 char *guid_string;
150 NTSTATUS status;
151 enum ndr_err_code ndr_err;
152 guid_string = talloc_strndup(mem_ctx, (const char *)in->data, in->length);
153 if (!guid_string) {
154 return -1;
157 status = GUID_from_string(guid_string, &guid);
158 talloc_free(guid_string);
159 if (!NT_STATUS_IS_OK(status)) {
160 return -1;
163 ndr_err = ndr_push_struct_blob(out, mem_ctx, NULL, &guid,
164 (ndr_push_flags_fn_t)ndr_push_GUID);
165 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
166 return -1;
168 return 0;
172 convert a NDR formatted blob to a ldif formatted objectGUID
174 static int ldif_write_objectGUID(struct ldb_context *ldb, void *mem_ctx,
175 const struct ldb_val *in, struct ldb_val *out)
177 struct GUID guid;
178 enum ndr_err_code ndr_err;
179 ndr_err = ndr_pull_struct_blob(in, mem_ctx, NULL, &guid,
180 (ndr_pull_flags_fn_t)ndr_pull_GUID);
181 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
182 return -1;
184 out->data = (uint8_t *)GUID_string(mem_ctx, &guid);
185 if (out->data == NULL) {
186 return -1;
188 out->length = strlen((const char *)out->data);
189 return 0;
192 static bool ldb_comparision_objectGUID_isString(const struct ldb_val *v)
194 struct GUID guid;
195 NTSTATUS status;
197 if (v->length < 33) return false;
199 /* see if the input if null-terninated (safety check for the below) */
200 if (v->data[v->length] != '\0') return false;
202 status = GUID_from_string((const char *)v->data, &guid);
203 if (!NT_STATUS_IS_OK(status)) {
204 return false;
207 return true;
211 compare two objectGUIDs
213 static int ldb_comparison_objectGUID(struct ldb_context *ldb, void *mem_ctx,
214 const struct ldb_val *v1, const struct ldb_val *v2)
216 if (ldb_comparision_objectGUID_isString(v1) && ldb_comparision_objectGUID_isString(v2)) {
217 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
218 } else if (ldb_comparision_objectGUID_isString(v1)
219 && !ldb_comparision_objectGUID_isString(v2)) {
220 struct ldb_val v;
221 int ret;
222 if (ldif_read_objectGUID(ldb, mem_ctx, v1, &v) != 0) {
223 /* Perhaps it wasn't a valid string after all */
224 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
226 ret = ldb_comparison_binary(ldb, mem_ctx, &v, v2);
227 talloc_free(v.data);
228 return ret;
229 } else if (!ldb_comparision_objectGUID_isString(v1)
230 && ldb_comparision_objectGUID_isString(v2)) {
231 struct ldb_val v;
232 int ret;
233 if (ldif_read_objectGUID(ldb, mem_ctx, v2, &v) != 0) {
234 /* Perhaps it wasn't a valid string after all */
235 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
237 ret = ldb_comparison_binary(ldb, mem_ctx, v1, &v);
238 talloc_free(v.data);
239 return ret;
241 return ldb_comparison_binary(ldb, mem_ctx, v1, v2);
245 canonicalise a objectGUID
247 static int ldb_canonicalise_objectGUID(struct ldb_context *ldb, void *mem_ctx,
248 const struct ldb_val *in, struct ldb_val *out)
250 if (ldb_comparision_objectGUID_isString(in)) {
251 if (ldif_read_objectGUID(ldb, mem_ctx, in, out) != 0) {
252 /* Perhaps it wasn't a valid string after all */
253 return ldb_handler_copy(ldb, mem_ctx, in, out);
255 return 0;
257 return ldb_handler_copy(ldb, mem_ctx, in, out);
262 convert a ldif (SDDL) formatted ntSecurityDescriptor to a NDR formatted blob
264 static int ldif_read_ntSecurityDescriptor(struct ldb_context *ldb, void *mem_ctx,
265 const struct ldb_val *in, struct ldb_val *out)
267 struct security_descriptor *sd;
268 enum ndr_err_code ndr_err;
270 sd = sddl_decode(mem_ctx, (const char *)in->data, NULL);
271 if (sd == NULL) {
272 return -1;
274 ndr_err = ndr_push_struct_blob(out, mem_ctx, NULL, sd,
275 (ndr_push_flags_fn_t)ndr_push_security_descriptor);
276 talloc_free(sd);
277 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
278 return -1;
280 return 0;
284 convert a NDR formatted blob to a ldif formatted ntSecurityDescriptor (SDDL format)
286 static int ldif_write_ntSecurityDescriptor(struct ldb_context *ldb, void *mem_ctx,
287 const struct ldb_val *in, struct ldb_val *out)
289 struct security_descriptor *sd;
290 enum ndr_err_code ndr_err;
292 sd = talloc(mem_ctx, struct security_descriptor);
293 if (sd == NULL) {
294 return -1;
296 ndr_err = ndr_pull_struct_blob(in, sd, NULL, sd,
297 (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
298 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
299 talloc_free(sd);
300 return -1;
302 out->data = (uint8_t *)sddl_encode(mem_ctx, sd, NULL);
303 talloc_free(sd);
304 if (out->data == NULL) {
305 return -1;
307 out->length = strlen((const char *)out->data);
308 return 0;
312 canonicalise an objectCategory. We use the short form as the cannoical form:
313 cn=Person,cn=Schema,cn=Configuration,<basedn> becomes 'person'
316 static int ldif_canonicalise_objectCategory(struct ldb_context *ldb, void *mem_ctx,
317 const struct ldb_val *in, struct ldb_val *out)
319 struct ldb_dn *dn1 = NULL;
320 const struct dsdb_schema *schema = dsdb_get_schema(ldb);
321 const struct dsdb_class *class;
322 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
323 if (!tmp_ctx) {
324 return LDB_ERR_OPERATIONS_ERROR;
327 if (!schema) {
328 *out = data_blob_talloc(mem_ctx, in->data, in->length);
329 if (in->data && !out->data) {
330 return LDB_ERR_OPERATIONS_ERROR;
332 return LDB_SUCCESS;
334 dn1 = ldb_dn_from_ldb_val(tmp_ctx, ldb, in);
335 if ( ! ldb_dn_validate(dn1)) {
336 const char *lDAPDisplayName = talloc_strndup(tmp_ctx, (char *)in->data, in->length);
337 class = dsdb_class_by_lDAPDisplayName(schema, lDAPDisplayName);
338 if (class) {
339 struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb,
340 class->defaultObjectCategory);
341 *out = data_blob_string_const(ldb_dn_alloc_casefold(mem_ctx, dn));
342 talloc_free(tmp_ctx);
344 if (!out->data) {
345 return LDB_ERR_OPERATIONS_ERROR;
347 return LDB_SUCCESS;
348 } else {
349 *out = data_blob_talloc(mem_ctx, in->data, in->length);
350 talloc_free(tmp_ctx);
352 if (in->data && !out->data) {
353 return LDB_ERR_OPERATIONS_ERROR;
355 return LDB_SUCCESS;
358 *out = data_blob_string_const(ldb_dn_alloc_casefold(mem_ctx, dn1));
359 talloc_free(tmp_ctx);
361 if (!out->data) {
362 return LDB_ERR_OPERATIONS_ERROR;
364 return LDB_SUCCESS;
367 static int ldif_comparison_objectCategory(struct ldb_context *ldb, void *mem_ctx,
368 const struct ldb_val *v1,
369 const struct ldb_val *v2)
372 int ret, ret1, ret2;
373 struct ldb_val v1_canon, v2_canon;
374 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
376 /* I could try and bail if tmp_ctx was NULL, but what return
377 * value would I use?
379 * It seems easier to continue on the NULL context
381 ret1 = ldif_canonicalise_objectCategory(ldb, tmp_ctx, v1, &v1_canon);
382 ret2 = ldif_canonicalise_objectCategory(ldb, tmp_ctx, v2, &v2_canon);
384 if (ret1 == LDB_SUCCESS && ret2 == LDB_SUCCESS) {
385 ret = data_blob_cmp(&v1_canon, &v2_canon);
386 } else {
387 ret = data_blob_cmp(v1, v2);
389 talloc_free(tmp_ctx);
390 return ret;
394 convert a ldif formatted prefixMap to a NDR formatted blob
396 static int ldif_read_prefixMap(struct ldb_context *ldb, void *mem_ctx,
397 const struct ldb_val *in, struct ldb_val *out)
399 struct prefixMapBlob *blob;
400 enum ndr_err_code ndr_err;
401 char *string, *line, *p, *oid;
403 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
405 if (tmp_ctx == NULL) {
406 return -1;
409 blob = talloc_zero(tmp_ctx, struct prefixMapBlob);
410 if (blob == NULL) {
411 talloc_free(blob);
412 return -1;
415 blob->version = PREFIX_MAP_VERSION_DSDB;
417 string = talloc_strndup(mem_ctx, (const char *)in->data, in->length);
418 if (string == NULL) {
419 talloc_free(blob);
420 return -1;
423 line = string;
424 while (line && line[0]) {
425 p=strchr(line, ';');
426 if (p) {
427 p[0] = '\0';
428 } else {
429 p=strchr(line, '\n');
430 if (p) {
431 p[0] = '\0';
434 /* allow a traling seperator */
435 if (line == p) {
436 break;
439 blob->ctr.dsdb.mappings = talloc_realloc(blob,
440 blob->ctr.dsdb.mappings,
441 struct drsuapi_DsReplicaOIDMapping,
442 blob->ctr.dsdb.num_mappings+1);
443 if (!blob->ctr.dsdb.mappings) {
444 talloc_free(tmp_ctx);
445 return -1;
448 blob->ctr.dsdb.mappings[blob->ctr.dsdb.num_mappings].id_prefix = strtoul(line, &oid, 10);
450 if (oid[0] != ':') {
451 talloc_free(tmp_ctx);
452 return -1;
455 /* we know there must be at least ":" */
456 oid++;
458 blob->ctr.dsdb.mappings[blob->ctr.dsdb.num_mappings].oid.oid
459 = talloc_strdup(blob->ctr.dsdb.mappings, oid);
461 blob->ctr.dsdb.num_mappings++;
463 /* Now look past the terminator we added above */
464 if (p) {
465 line = p + 1;
466 } else {
467 line = NULL;
471 ndr_err = ndr_push_struct_blob(out, mem_ctx,
472 lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
473 blob,
474 (ndr_push_flags_fn_t)ndr_push_prefixMapBlob);
475 talloc_free(tmp_ctx);
476 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
477 return -1;
479 return 0;
483 convert a NDR formatted blob to a ldif formatted prefixMap
485 static int ldif_write_prefixMap(struct ldb_context *ldb, void *mem_ctx,
486 const struct ldb_val *in, struct ldb_val *out)
488 struct prefixMapBlob *blob;
489 enum ndr_err_code ndr_err;
490 char *string;
491 uint32_t i;
493 blob = talloc(mem_ctx, struct prefixMapBlob);
494 if (blob == NULL) {
495 return -1;
497 ndr_err = ndr_pull_struct_blob(in, blob,
498 lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
499 blob,
500 (ndr_pull_flags_fn_t)ndr_pull_prefixMapBlob);
501 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
502 talloc_free(blob);
503 return -1;
505 if (blob->version != PREFIX_MAP_VERSION_DSDB) {
506 return -1;
508 string = talloc_strdup(mem_ctx, "");
509 if (string == NULL) {
510 return -1;
513 for (i=0; i < blob->ctr.dsdb.num_mappings; i++) {
514 if (i > 0) {
515 string = talloc_asprintf_append(string, ";");
517 string = talloc_asprintf_append(string, "%u:%s",
518 blob->ctr.dsdb.mappings[i].id_prefix,
519 blob->ctr.dsdb.mappings[i].oid.oid);
520 if (string == NULL) {
521 return -1;
525 talloc_free(blob);
526 *out = data_blob_string_const(string);
527 return 0;
530 static bool ldif_comparision_prefixMap_isString(const struct ldb_val *v)
532 if (v->length < 4) {
533 return true;
536 if (IVAL(v->data, 0) == PREFIX_MAP_VERSION_DSDB) {
537 return false;
540 return true;
544 canonicalise a prefixMap
546 static int ldif_canonicalise_prefixMap(struct ldb_context *ldb, void *mem_ctx,
547 const struct ldb_val *in, struct ldb_val *out)
549 if (ldif_comparision_prefixMap_isString(in)) {
550 return ldif_read_prefixMap(ldb, mem_ctx, in, out);
552 return ldb_handler_copy(ldb, mem_ctx, in, out);
555 static int ldif_comparison_prefixMap(struct ldb_context *ldb, void *mem_ctx,
556 const struct ldb_val *v1,
557 const struct ldb_val *v2)
560 int ret, ret1, ret2;
561 struct ldb_val v1_canon, v2_canon;
562 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
564 /* I could try and bail if tmp_ctx was NULL, but what return
565 * value would I use?
567 * It seems easier to continue on the NULL context
569 ret1 = ldif_canonicalise_prefixMap(ldb, tmp_ctx, v1, &v1_canon);
570 ret2 = ldif_canonicalise_prefixMap(ldb, tmp_ctx, v2, &v2_canon);
572 if (ret1 == LDB_SUCCESS && ret2 == LDB_SUCCESS) {
573 ret = data_blob_cmp(&v1_canon, &v2_canon);
574 } else {
575 ret = data_blob_cmp(v1, v2);
577 talloc_free(tmp_ctx);
578 return ret;
581 #define LDB_SYNTAX_SAMBA_GUID "LDB_SYNTAX_SAMBA_GUID"
582 #define LDB_SYNTAX_SAMBA_OBJECT_CATEGORY "LDB_SYNTAX_SAMBA_OBJECT_CATEGORY"
583 #define LDB_SYNTAX_SAMBA_PREFIX_MAP "LDB_SYNTAX_SAMBA_PREFIX_MAP"
585 static const struct ldb_schema_syntax samba_syntaxes[] = {
587 .name = LDB_SYNTAX_SAMBA_SID,
588 .ldif_read_fn = ldif_read_objectSid,
589 .ldif_write_fn = ldif_write_objectSid,
590 .canonicalise_fn= ldb_canonicalise_objectSid,
591 .comparison_fn = ldb_comparison_objectSid
593 .name = LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR,
594 .ldif_read_fn = ldif_read_ntSecurityDescriptor,
595 .ldif_write_fn = ldif_write_ntSecurityDescriptor,
596 .canonicalise_fn= ldb_handler_copy,
597 .comparison_fn = ldb_comparison_binary
599 .name = LDB_SYNTAX_SAMBA_GUID,
600 .ldif_read_fn = ldif_read_objectGUID,
601 .ldif_write_fn = ldif_write_objectGUID,
602 .canonicalise_fn= ldb_canonicalise_objectGUID,
603 .comparison_fn = ldb_comparison_objectGUID
605 .name = LDB_SYNTAX_SAMBA_OBJECT_CATEGORY,
606 .ldif_read_fn = ldb_handler_copy,
607 .ldif_write_fn = ldb_handler_copy,
608 .canonicalise_fn= ldif_canonicalise_objectCategory,
609 .comparison_fn = ldif_comparison_objectCategory
611 .name = LDB_SYNTAX_SAMBA_PREFIX_MAP,
612 .ldif_read_fn = ldif_read_prefixMap,
613 .ldif_write_fn = ldif_write_prefixMap,
614 .canonicalise_fn= ldif_canonicalise_prefixMap,
615 .comparison_fn = ldif_comparison_prefixMap
619 static const struct {
620 const char *name;
621 const char *syntax;
622 } samba_attributes[] = {
623 { "objectSid", LDB_SYNTAX_SAMBA_SID },
624 { "securityIdentifier", LDB_SYNTAX_SAMBA_SID },
625 { "ntSecurityDescriptor", LDB_SYNTAX_SAMBA_SECURITY_DESCRIPTOR },
626 { "objectGUID", LDB_SYNTAX_SAMBA_GUID },
627 { "invocationId", LDB_SYNTAX_SAMBA_GUID },
628 { "schemaIDGUID", LDB_SYNTAX_SAMBA_GUID },
629 { "attributeSecurityGUID", LDB_SYNTAX_SAMBA_GUID },
630 { "parentGUID", LDB_SYNTAX_SAMBA_GUID },
631 { "siteGUID", LDB_SYNTAX_SAMBA_GUID },
632 { "pKTGUID", LDB_SYNTAX_SAMBA_GUID },
633 { "fRSVersionGUID", LDB_SYNTAX_SAMBA_GUID },
634 { "fRSReplicaSetGUID", LDB_SYNTAX_SAMBA_GUID },
635 { "netbootGUID", LDB_SYNTAX_SAMBA_GUID },
636 { "objectCategory", LDB_SYNTAX_SAMBA_OBJECT_CATEGORY },
637 { "prefixMap", LDB_SYNTAX_SAMBA_PREFIX_MAP }
640 const struct ldb_schema_syntax *ldb_samba_syntax_by_name(struct ldb_context *ldb, const char *name)
642 uint32_t j;
643 const struct ldb_schema_syntax *s = NULL;
645 for (j=0; j < ARRAY_SIZE(samba_syntaxes); j++) {
646 if (strcmp(name, samba_syntaxes[j].name) == 0) {
647 s = &samba_syntaxes[j];
648 break;
651 return s;
656 register the samba ldif handlers
658 int ldb_register_samba_handlers(struct ldb_context *ldb)
660 uint32_t i;
662 for (i=0; i < ARRAY_SIZE(samba_attributes); i++) {
663 int ret;
664 const struct ldb_schema_syntax *s = NULL;
666 s = ldb_samba_syntax_by_name(ldb, samba_attributes[i].syntax);
668 if (!s) {
669 s = ldb_standard_syntax_by_name(ldb, samba_attributes[i].syntax);
672 if (!s) {
673 return -1;
676 ret = ldb_schema_attribute_add_with_syntax(ldb, samba_attributes[i].name, LDB_ATTR_FLAG_FIXED, s);
677 if (ret != LDB_SUCCESS) {
678 return ret;
682 return LDB_SUCCESS;