s3: libsmbclient: Fix smbc_getxattr() to return 0 on success.
[Samba.git] / source3 / libsmb / libsmb_xattr.c
blob1e8d2718a2299288db9d95c38280f2d5bf386d46
1 /*
2 Unix SMB/Netbios implementation.
3 SMB client library implementation
4 Copyright (C) Andrew Tridgell 1998
5 Copyright (C) Richard Sharpe 2000, 2002
6 Copyright (C) John Terpstra 2000
7 Copyright (C) Tom Jansen (Ninja ISD) 2002
8 Copyright (C) Derrell Lipman 2003-2008
9 Copyright (C) Jeremy Allison 2007, 2008
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 3 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include "includes.h"
26 #include "libsmb/libsmb.h"
27 #include "libsmbclient.h"
28 #include "libsmb_internal.h"
29 #include "../librpc/gen_ndr/ndr_lsa.h"
30 #include "rpc_client/rpc_client.h"
31 #include "rpc_client/cli_lsarpc.h"
32 #include "../libcli/security/security.h"
33 #include "lib/util/string_wrappers.h"
36 * Find an lsa pipe handle associated with a cli struct.
38 static struct rpc_pipe_client *
39 find_lsa_pipe_hnd(struct cli_state *ipc_cli)
41 struct rpc_pipe_client *pipe_hnd;
43 for (pipe_hnd = ipc_cli->pipe_list;
44 pipe_hnd;
45 pipe_hnd = pipe_hnd->next) {
46 if (ndr_syntax_id_equal(&pipe_hnd->abstract_syntax,
47 &ndr_table_lsarpc.syntax_id)) {
48 return pipe_hnd;
51 return NULL;
55 * Sort ACEs according to the documentation at
56 * http://support.microsoft.com/kb/269175, at least as far as it defines the
57 * order.
60 static int
61 ace_compare(struct security_ace *ace1,
62 struct security_ace *ace2)
64 bool b1;
65 bool b2;
67 /* If the ACEs are equal, we have nothing more to do. */
68 if (security_ace_equal(ace1, ace2)) {
69 return 0;
72 /* Inherited follow non-inherited */
73 b1 = ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0);
74 b2 = ((ace2->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0);
75 if (b1 != b2) {
76 return (b1 ? 1 : -1);
80 * What shall we do with AUDITs and ALARMs? It's undefined. We'll
81 * sort them after DENY and ALLOW.
83 b1 = (ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED &&
84 ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT &&
85 ace1->type != SEC_ACE_TYPE_ACCESS_DENIED &&
86 ace1->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
87 b2 = (ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED &&
88 ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT &&
89 ace2->type != SEC_ACE_TYPE_ACCESS_DENIED &&
90 ace2->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
91 if (b1 != b2) {
92 return (b1 ? 1 : -1);
95 /* Allowed ACEs follow denied ACEs */
96 b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED ||
97 ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT);
98 b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED ||
99 ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT);
100 if (b1 != b2) {
101 return (b1 ? 1 : -1);
105 * ACEs applying to an entity's object follow those applying to the
106 * entity itself
108 b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT ||
109 ace1->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
110 b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT ||
111 ace2->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
112 if (b1 != b2) {
113 return (b1 ? 1 : -1);
117 * If we get this far, the ACEs are similar as far as the
118 * characteristics we typically care about (those defined by the
119 * referenced MS document). We'll now sort by characteristics that
120 * just seems reasonable.
123 if (ace1->type != ace2->type) {
124 return ace2->type - ace1->type;
127 if (dom_sid_compare(&ace1->trustee, &ace2->trustee)) {
128 return dom_sid_compare(&ace1->trustee, &ace2->trustee);
131 if (ace1->flags != ace2->flags) {
132 return ace1->flags - ace2->flags;
135 if (ace1->access_mask != ace2->access_mask) {
136 return ace1->access_mask - ace2->access_mask;
139 if (ace1->size != ace2->size) {
140 return ace1->size - ace2->size;
143 return memcmp(ace1, ace2, sizeof(struct security_ace));
147 static void
148 sort_acl(struct security_acl *the_acl)
150 uint32_t i;
151 if (!the_acl) return;
153 TYPESAFE_QSORT(the_acl->aces, the_acl->num_aces, ace_compare);
155 for (i=1;i<the_acl->num_aces;) {
156 if (security_ace_equal(&the_acl->aces[i-1],
157 &the_acl->aces[i])) {
158 ARRAY_DEL_ELEMENT(
159 the_acl->aces, i, the_acl->num_aces);
160 the_acl->num_aces--;
161 } else {
162 i++;
167 /* convert a SID to a string, either numeric or username/group */
168 static void
169 convert_sid_to_string(struct cli_state *ipc_cli,
170 struct policy_handle *pol,
171 fstring str,
172 bool numeric,
173 struct dom_sid *sid)
175 char **domains = NULL;
176 char **names = NULL;
177 enum lsa_SidType *types = NULL;
178 struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli);
179 TALLOC_CTX *ctx;
181 sid_to_fstring(str, sid);
183 if (numeric) {
184 return; /* no lookup desired */
187 if (!pipe_hnd) {
188 return;
191 /* Ask LSA to convert the sid to a name */
193 ctx = talloc_stackframe();
195 if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_sids(pipe_hnd, ctx,
196 pol, 1, sid, &domains,
197 &names, &types)) ||
198 !domains || !domains[0] || !names || !names[0]) {
199 TALLOC_FREE(ctx);
200 return;
203 /* Converted OK */
205 fstr_sprintf(str, "%s%s%s",
206 domains[0], lp_winbind_separator(), names[0]);
208 TALLOC_FREE(ctx);
211 /* convert a string to a SID, either numeric or username/group */
212 static bool
213 convert_string_to_sid(struct cli_state *ipc_cli,
214 struct policy_handle *pol,
215 bool numeric,
216 struct dom_sid *sid,
217 const char *str)
219 enum lsa_SidType *types = NULL;
220 struct dom_sid *sids = NULL;
221 bool result = True;
222 TALLOC_CTX *ctx = NULL;
223 struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli);
225 if (!pipe_hnd) {
226 return False;
229 if (numeric) {
230 if (strncmp(str, "S-", 2) == 0) {
231 return string_to_sid(sid, str);
234 result = False;
235 goto done;
238 ctx = talloc_stackframe();
239 if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_names(pipe_hnd, ctx,
240 pol, 1, &str,
241 NULL, 1, &sids,
242 &types))) {
243 result = False;
244 goto done;
247 sid_copy(sid, &sids[0]);
248 done:
249 TALLOC_FREE(ctx);
250 return result;
254 /* parse an struct security_ace in the same format as print_ace() */
255 static bool
256 parse_ace(struct cli_state *ipc_cli,
257 struct policy_handle *pol,
258 struct security_ace *ace,
259 bool numeric,
260 char *str)
262 char *p;
263 const char *cp;
264 char *tok;
265 unsigned int atype;
266 unsigned int aflags;
267 unsigned int amask;
268 struct dom_sid sid;
269 uint32_t mask;
270 const struct perm_value *v;
271 struct perm_value {
272 const char perm[7];
273 uint32_t mask;
275 TALLOC_CTX *frame = talloc_stackframe();
277 /* These values discovered by inspection */
278 static const struct perm_value special_values[] = {
279 { "R", 0x00120089 },
280 { "W", 0x00120116 },
281 { "X", 0x001200a0 },
282 { "D", 0x00010000 },
283 { "P", 0x00040000 },
284 { "O", 0x00080000 },
285 { "", 0 },
288 static const struct perm_value standard_values[] = {
289 { "READ", 0x001200a9 },
290 { "CHANGE", 0x001301bf },
291 { "FULL", 0x001f01ff },
292 { "", 0 },
295 ZERO_STRUCTP(ace);
296 p = strchr_m(str,':');
297 if (!p) {
298 TALLOC_FREE(frame);
299 return False;
301 *p = '\0';
302 p++;
303 /* Try to parse numeric form */
305 if (sscanf(p, "%u/%u/%u", &atype, &aflags, &amask) == 3 &&
306 convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) {
307 goto done;
310 /* Try to parse text form */
312 if (!convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) {
313 TALLOC_FREE(frame);
314 return false;
317 cp = p;
318 if (!next_token_talloc(frame, &cp, &tok, "/")) {
319 TALLOC_FREE(frame);
320 return false;
323 if (strncasecmp_m(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
324 atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
325 } else if (strncasecmp_m(tok, "DENIED", strlen("DENIED")) == 0) {
326 atype = SEC_ACE_TYPE_ACCESS_DENIED;
327 } else {
328 TALLOC_FREE(frame);
329 return false;
332 /* Only numeric form accepted for flags at present */
334 if (!(next_token_talloc(frame, &cp, &tok, "/") &&
335 sscanf(tok, "%u", &aflags))) {
336 TALLOC_FREE(frame);
337 return false;
340 if (!next_token_talloc(frame, &cp, &tok, "/")) {
341 TALLOC_FREE(frame);
342 return false;
345 if (strncmp(tok, "0x", 2) == 0) {
346 if (sscanf(tok, "%u", &amask) != 1) {
347 TALLOC_FREE(frame);
348 return false;
350 goto done;
353 for (v = standard_values; v != NULL; v++) {
354 if (strcmp(tok, v->perm) == 0) {
355 amask = v->mask;
356 goto done;
360 p = tok;
362 while(*p) {
363 bool found = False;
365 for (v = special_values; v != NULL; v++) {
366 if (v->perm[0] == *p) {
367 amask |= v->mask;
368 found = True;
372 if (!found) {
373 TALLOC_FREE(frame);
374 return false;
376 p++;
379 if (*p) {
380 TALLOC_FREE(frame);
381 return false;
384 done:
385 mask = amask;
386 init_sec_ace(ace, &sid, atype, mask, aflags);
387 TALLOC_FREE(frame);
388 return true;
391 /* add an struct security_ace to a list of struct security_aces in a struct security_acl */
392 static bool
393 add_ace(struct security_acl **the_acl,
394 const struct security_ace *ace,
395 TALLOC_CTX *ctx)
397 struct security_acl *acl = *the_acl;
399 if (acl == NULL) {
400 acl = make_sec_acl(ctx, 3, 0, NULL);
401 if (acl == NULL) {
402 return false;
406 if (acl->num_aces == UINT32_MAX) {
407 return false;
409 ADD_TO_ARRAY(
410 acl, struct security_ace, *ace, &acl->aces, &acl->num_aces);
411 *the_acl = acl;
412 return True;
416 /* parse a ascii version of a security descriptor */
417 static struct security_descriptor *
418 sec_desc_parse(TALLOC_CTX *ctx,
419 struct cli_state *ipc_cli,
420 struct policy_handle *pol,
421 bool numeric,
422 const char *str)
424 const char *p = str;
425 char *tok;
426 struct security_descriptor *ret = NULL;
427 size_t sd_size;
428 struct dom_sid owner_sid = { .num_auths = 0 };
429 struct dom_sid group_sid = { .num_auths = 0 };
430 bool have_owner = false, have_group = false;
431 struct security_acl *dacl=NULL;
432 int revision=1;
434 while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) {
436 if (strncasecmp_m(tok,"REVISION:", 9) == 0) {
437 revision = strtol(tok+9, NULL, 16);
438 continue;
441 if (strncasecmp_m(tok,"OWNER:", 6) == 0) {
442 if (have_owner) {
443 DEBUG(5,("OWNER specified more than once!\n"));
444 goto done;
446 if (!convert_string_to_sid(ipc_cli, pol,
447 numeric,
448 &owner_sid, tok+6)) {
449 DEBUG(5, ("Failed to parse owner sid\n"));
450 goto done;
452 have_owner = true;
453 continue;
456 if (strncasecmp_m(tok,"OWNER+:", 7) == 0) {
457 if (have_owner) {
458 DEBUG(5,("OWNER specified more than once!\n"));
459 goto done;
461 if (!convert_string_to_sid(ipc_cli, pol,
462 False,
463 &owner_sid, tok+7)) {
464 DEBUG(5, ("Failed to parse owner sid\n"));
465 goto done;
467 have_owner = true;
468 continue;
471 if (strncasecmp_m(tok,"GROUP:", 6) == 0) {
472 if (have_group) {
473 DEBUG(5,("GROUP specified more than once!\n"));
474 goto done;
476 if (!convert_string_to_sid(ipc_cli, pol,
477 numeric,
478 &group_sid, tok+6)) {
479 DEBUG(5, ("Failed to parse group sid\n"));
480 goto done;
482 have_group = true;
483 continue;
486 if (strncasecmp_m(tok,"GROUP+:", 7) == 0) {
487 if (have_group) {
488 DEBUG(5,("GROUP specified more than once!\n"));
489 goto done;
491 if (!convert_string_to_sid(ipc_cli, pol,
492 False,
493 &group_sid, tok+6)) {
494 DEBUG(5, ("Failed to parse group sid\n"));
495 goto done;
497 have_group = true;
498 continue;
501 if (strncasecmp_m(tok,"ACL:", 4) == 0) {
502 struct security_ace ace;
503 if (!parse_ace(ipc_cli, pol, &ace, numeric, tok+4)) {
504 DEBUG(5, ("Failed to parse ACL %s\n", tok));
505 goto done;
507 if(!add_ace(&dacl, &ace, ctx)) {
508 DEBUG(5, ("Failed to add ACL %s\n", tok));
509 goto done;
511 continue;
514 if (strncasecmp_m(tok,"ACL+:", 5) == 0) {
515 struct security_ace ace;
516 if (!parse_ace(ipc_cli, pol, &ace, False, tok+5)) {
517 DEBUG(5, ("Failed to parse ACL %s\n", tok));
518 goto done;
520 if(!add_ace(&dacl, &ace, ctx)) {
521 DEBUG(5, ("Failed to add ACL %s\n", tok));
522 goto done;
524 continue;
527 DEBUG(5, ("Failed to parse security descriptor\n"));
528 goto done;
531 ret = make_sec_desc(
532 ctx,
533 revision,
534 SEC_DESC_SELF_RELATIVE,
535 have_owner ? &owner_sid : NULL,
536 have_group ? &group_sid : NULL,
537 NULL,
538 dacl,
539 &sd_size);
541 done:
542 return ret;
546 /* Obtain the current dos attributes */
547 static struct DOS_ATTR_DESC *
548 dos_attr_query(SMBCCTX *context,
549 TALLOC_CTX *ctx,
550 const char *filename,
551 SMBCSRV *srv)
553 struct stat sb = {0};
554 struct DOS_ATTR_DESC *ret = NULL;
555 NTSTATUS status;
557 ret = talloc(ctx, struct DOS_ATTR_DESC);
558 if (!ret) {
559 errno = ENOMEM;
560 return NULL;
563 /* Obtain the DOS attributes */
564 status = SMBC_getatr(context, srv, filename, &sb);
565 if (!NT_STATUS_IS_OK(status)) {
566 DEBUG(5, ("dos_attr_query Failed to query old attributes\n"));
567 TALLOC_FREE(ret);
568 errno = cli_status_to_errno(status);
569 return NULL;
572 ret->mode = sb.st_mode;
573 ret->size = sb.st_size;
574 ret->create_time = sb.st_ctime;
575 ret->access_time = sb.st_atime;
576 ret->write_time = sb.st_mtime;
577 ret->change_time = sb.st_mtime;
578 ret->inode = sb.st_ino;
580 return ret;
584 /* parse a ascii version of a security descriptor */
585 static void
586 dos_attr_parse(SMBCCTX *context,
587 struct DOS_ATTR_DESC *dad,
588 SMBCSRV *srv,
589 char *str)
591 int n;
592 const char *p = str;
593 char *tok = NULL;
594 TALLOC_CTX *frame = NULL;
595 struct {
596 const char * create_time_attr;
597 const char * access_time_attr;
598 const char * write_time_attr;
599 const char * change_time_attr;
600 } attr_strings;
602 /* Determine whether to use old-style or new-style attribute names */
603 if (context->internal->full_time_names) {
604 /* new-style names */
605 attr_strings.create_time_attr = "CREATE_TIME";
606 attr_strings.access_time_attr = "ACCESS_TIME";
607 attr_strings.write_time_attr = "WRITE_TIME";
608 attr_strings.change_time_attr = "CHANGE_TIME";
609 } else {
610 /* old-style names */
611 attr_strings.create_time_attr = NULL;
612 attr_strings.access_time_attr = "A_TIME";
613 attr_strings.write_time_attr = "M_TIME";
614 attr_strings.change_time_attr = "C_TIME";
617 /* if this is to set the entire ACL... */
618 if (*str == '*') {
619 /* ... then increment past the first colon if there is one */
620 if ((p = strchr(str, ':')) != NULL) {
621 ++p;
622 } else {
623 p = str;
627 frame = talloc_stackframe();
628 while (next_token_talloc(frame, &p, &tok, "\t,\r\n")) {
629 if (strncasecmp_m(tok, "MODE:", 5) == 0) {
630 long request = strtol(tok+5, NULL, 16);
631 if (request == 0) {
632 dad->mode = (request |
633 (IS_DOS_DIR(dad->mode)
634 ? FILE_ATTRIBUTE_DIRECTORY
635 : FILE_ATTRIBUTE_NORMAL));
636 } else {
637 dad->mode = request;
639 continue;
642 if (strncasecmp_m(tok, "SIZE:", 5) == 0) {
643 dad->size = (off_t)atof(tok+5);
644 continue;
647 n = strlen(attr_strings.access_time_attr);
648 if (strncasecmp_m(tok, attr_strings.access_time_attr, n) == 0) {
649 dad->access_time = (time_t)strtol(tok+n+1, NULL, 10);
650 continue;
653 n = strlen(attr_strings.change_time_attr);
654 if (strncasecmp_m(tok, attr_strings.change_time_attr, n) == 0) {
655 dad->change_time = (time_t)strtol(tok+n+1, NULL, 10);
656 continue;
659 n = strlen(attr_strings.write_time_attr);
660 if (strncasecmp_m(tok, attr_strings.write_time_attr, n) == 0) {
661 dad->write_time = (time_t)strtol(tok+n+1, NULL, 10);
662 continue;
665 if (attr_strings.create_time_attr != NULL) {
666 n = strlen(attr_strings.create_time_attr);
667 if (strncasecmp_m(tok, attr_strings.create_time_attr,
668 n) == 0) {
669 dad->create_time = (time_t)strtol(tok+n+1,
670 NULL, 10);
671 continue;
675 if (strncasecmp_m(tok, "INODE:", 6) == 0) {
676 dad->inode = (SMB_INO_T)atof(tok+6);
677 continue;
680 TALLOC_FREE(frame);
683 /*****************************************************
684 Retrieve the acls for a file.
685 *******************************************************/
687 static int
688 cacl_get(SMBCCTX *context,
689 TALLOC_CTX *ctx,
690 SMBCSRV *srv,
691 struct cli_state *ipc_cli,
692 struct policy_handle *pol,
693 const char *filename,
694 const char *attr_name,
695 char *buf,
696 int bufsize)
698 uint32_t i;
699 int n = 0;
700 int n_used;
701 bool all;
702 bool all_nt;
703 bool all_nt_acls;
704 bool all_dos;
705 bool some_nt;
706 bool some_dos;
707 bool exclude_nt_revision = False;
708 bool exclude_nt_owner = False;
709 bool exclude_nt_group = False;
710 bool exclude_nt_acl = False;
711 bool exclude_dos_mode = False;
712 bool exclude_dos_size = False;
713 bool exclude_dos_create_time = False;
714 bool exclude_dos_access_time = False;
715 bool exclude_dos_write_time = False;
716 bool exclude_dos_change_time = False;
717 bool exclude_dos_inode = False;
718 bool numeric = True;
719 bool determine_size = (bufsize == 0);
720 uint16_t fnum;
721 struct security_descriptor *sd;
722 fstring sidstr;
723 fstring name_sandbox;
724 char *name;
725 char *pExclude;
726 char *p;
727 struct cli_state *cli = srv->cli;
728 struct {
729 const char * create_time_attr;
730 const char * access_time_attr;
731 const char * write_time_attr;
732 const char * change_time_attr;
733 } attr_strings;
734 struct {
735 const char * create_time_attr;
736 const char * access_time_attr;
737 const char * write_time_attr;
738 const char * change_time_attr;
739 } excl_attr_strings;
741 /* Determine whether to use old-style or new-style attribute names */
742 if (context->internal->full_time_names) {
743 /* new-style names */
744 attr_strings.create_time_attr = "CREATE_TIME";
745 attr_strings.access_time_attr = "ACCESS_TIME";
746 attr_strings.write_time_attr = "WRITE_TIME";
747 attr_strings.change_time_attr = "CHANGE_TIME";
749 excl_attr_strings.create_time_attr = "CREATE_TIME";
750 excl_attr_strings.access_time_attr = "ACCESS_TIME";
751 excl_attr_strings.write_time_attr = "WRITE_TIME";
752 excl_attr_strings.change_time_attr = "CHANGE_TIME";
753 } else {
754 /* old-style names */
755 attr_strings.create_time_attr = NULL;
756 attr_strings.access_time_attr = "A_TIME";
757 attr_strings.write_time_attr = "M_TIME";
758 attr_strings.change_time_attr = "C_TIME";
760 excl_attr_strings.create_time_attr = NULL;
761 excl_attr_strings.access_time_attr = "dos_attr.A_TIME";
762 excl_attr_strings.write_time_attr = "dos_attr.M_TIME";
763 excl_attr_strings.change_time_attr = "dos_attr.C_TIME";
766 /* Copy name so we can strip off exclusions (if any are specified) */
767 fstrcpy(name_sandbox, attr_name);
769 /* Ensure name is null terminated */
770 name_sandbox[sizeof(name_sandbox) - 1] = '\0';
772 /* Play in the sandbox */
773 name = name_sandbox;
775 /* If there are any exclusions, point to them and mask them from name */
776 if ((pExclude = strchr(name, '!')) != NULL)
778 *pExclude++ = '\0';
781 all = (strncasecmp_m(name, "system.*", 8) == 0);
782 all_nt = (strncasecmp_m(name, "system.nt_sec_desc.*", 20) == 0);
783 all_nt_acls = (strncasecmp_m(name, "system.nt_sec_desc.acl.*", 24) == 0);
784 all_dos = (strncasecmp_m(name, "system.dos_attr.*", 17) == 0);
785 some_nt = (strncasecmp_m(name, "system.nt_sec_desc.", 19) == 0);
786 some_dos = (strncasecmp_m(name, "system.dos_attr.", 16) == 0);
787 numeric = (* (name + strlen(name) - 1) != '+');
789 /* Look for exclusions from "all" requests */
790 if (all || all_nt || all_dos) {
791 /* Exclusions are delimited by '!' */
792 for (;
793 pExclude != NULL;
794 pExclude = (p == NULL ? NULL : p + 1)) {
796 /* Find end of this exclusion name */
797 if ((p = strchr(pExclude, '!')) != NULL)
799 *p = '\0';
802 /* Which exclusion name is this? */
803 if (strcasecmp_m(pExclude,
804 "nt_sec_desc.revision") == 0) {
805 exclude_nt_revision = True;
807 else if (strcasecmp_m(pExclude,
808 "nt_sec_desc.owner") == 0) {
809 exclude_nt_owner = True;
811 else if (strcasecmp_m(pExclude,
812 "nt_sec_desc.group") == 0) {
813 exclude_nt_group = True;
815 else if (strcasecmp_m(pExclude,
816 "nt_sec_desc.acl") == 0) {
817 exclude_nt_acl = True;
819 else if (strcasecmp_m(pExclude,
820 "dos_attr.mode") == 0) {
821 exclude_dos_mode = True;
823 else if (strcasecmp_m(pExclude,
824 "dos_attr.size") == 0) {
825 exclude_dos_size = True;
827 else if (excl_attr_strings.create_time_attr != NULL &&
828 strcasecmp_m(pExclude,
829 excl_attr_strings.change_time_attr) == 0) {
830 exclude_dos_create_time = True;
832 else if (strcasecmp_m(pExclude,
833 excl_attr_strings.access_time_attr) == 0) {
834 exclude_dos_access_time = True;
836 else if (strcasecmp_m(pExclude,
837 excl_attr_strings.write_time_attr) == 0) {
838 exclude_dos_write_time = True;
840 else if (strcasecmp_m(pExclude,
841 excl_attr_strings.change_time_attr) == 0) {
842 exclude_dos_change_time = True;
844 else if (strcasecmp_m(pExclude, "dos_attr.inode") == 0) {
845 exclude_dos_inode = True;
847 else {
848 DEBUG(5, ("cacl_get received unknown exclusion: %s\n",
849 pExclude));
850 errno = ENOATTR;
851 return -1;
856 n_used = 0;
859 * If we are (possibly) talking to an NT or new system and some NT
860 * attributes have been requested...
862 if (ipc_cli && (all || some_nt || all_nt_acls)) {
863 char *targetpath = NULL;
864 struct cli_state *targetcli = NULL;
865 struct cli_credentials *creds = NULL;
866 NTSTATUS status;
868 /* Point to the portion after "system.nt_sec_desc." */
869 name += 19; /* if (all) this will be invalid but unused */
871 creds = context->internal->creds;
873 status = cli_resolve_path(
874 ctx, "",
875 creds,
876 cli, filename, &targetcli, &targetpath);
877 if (!NT_STATUS_IS_OK(status)) {
878 DEBUG(5, ("cacl_get Could not resolve %s\n",
879 filename));
880 errno = ENOENT;
881 return -1;
884 /* ... then obtain any NT attributes which were requested */
885 status = cli_ntcreate(
886 targetcli, /* cli */
887 targetpath, /* fname */
888 0, /* CreatFlags */
889 READ_CONTROL_ACCESS, /* DesiredAccess */
890 0, /* FileAttributes */
891 FILE_SHARE_READ|
892 FILE_SHARE_WRITE, /* ShareAccess */
893 FILE_OPEN, /* CreateDisposition */
894 0x0, /* CreateOptions */
895 0x0, /* SecurityFlags */
896 &fnum, /* pfid */
897 NULL); /* cr */
898 if (!NT_STATUS_IS_OK(status)) {
899 DEBUG(5, ("cacl_get failed to open %s: %s\n",
900 targetpath, nt_errstr(status)));
901 errno = 0;
902 return -1;
905 status = cli_query_secdesc(targetcli, fnum, ctx, &sd);
906 if (!NT_STATUS_IS_OK(status)) {
907 DEBUG(5,("cacl_get Failed to query old descriptor "
908 "of %s: %s\n",
909 targetpath, nt_errstr(status)));
910 errno = 0;
911 return -1;
914 cli_close(targetcli, fnum);
916 if (! exclude_nt_revision) {
917 if (all || all_nt) {
918 if (determine_size) {
919 p = talloc_asprintf(ctx,
920 "REVISION:%d",
921 sd->revision);
922 if (!p) {
923 errno = ENOMEM;
924 return -1;
926 n = strlen(p);
927 } else {
928 n = snprintf(buf, bufsize,
929 "REVISION:%d",
930 sd->revision);
932 } else if (strcasecmp_m(name, "revision") == 0) {
933 if (determine_size) {
934 p = talloc_asprintf(ctx, "%d",
935 sd->revision);
936 if (!p) {
937 errno = ENOMEM;
938 return -1;
940 n = strlen(p);
941 } else {
942 n = snprintf(buf, bufsize, "%d",
943 sd->revision);
947 if (!determine_size && n > bufsize) {
948 errno = ERANGE;
949 return -1;
951 buf += n;
952 n_used += n;
953 bufsize -= n;
954 n = 0;
957 if (! exclude_nt_owner) {
958 /* Get owner and group sid */
959 if (sd->owner_sid) {
960 convert_sid_to_string(ipc_cli, pol,
961 sidstr,
962 numeric,
963 sd->owner_sid);
964 } else {
965 fstrcpy(sidstr, "");
968 if (all || all_nt) {
969 if (determine_size) {
970 p = talloc_asprintf(ctx, ",OWNER:%s",
971 sidstr);
972 if (!p) {
973 errno = ENOMEM;
974 return -1;
976 n = strlen(p);
977 } else if (sidstr[0] != '\0') {
978 n = snprintf(buf, bufsize,
979 ",OWNER:%s", sidstr);
981 } else if (strncasecmp_m(name, "owner", 5) == 0) {
982 if (determine_size) {
983 p = talloc_asprintf(ctx, "%s", sidstr);
984 if (!p) {
985 errno = ENOMEM;
986 return -1;
988 n = strlen(p);
989 } else {
990 n = snprintf(buf, bufsize, "%s",
991 sidstr);
995 if (!determine_size && n > bufsize) {
996 errno = ERANGE;
997 return -1;
999 buf += n;
1000 n_used += n;
1001 bufsize -= n;
1002 n = 0;
1005 if (! exclude_nt_group) {
1006 if (sd->group_sid) {
1007 convert_sid_to_string(ipc_cli, pol,
1008 sidstr, numeric,
1009 sd->group_sid);
1010 } else {
1011 fstrcpy(sidstr, "");
1014 if (all || all_nt) {
1015 if (determine_size) {
1016 p = talloc_asprintf(ctx, ",GROUP:%s",
1017 sidstr);
1018 if (!p) {
1019 errno = ENOMEM;
1020 return -1;
1022 n = strlen(p);
1023 } else if (sidstr[0] != '\0') {
1024 n = snprintf(buf, bufsize,
1025 ",GROUP:%s", sidstr);
1027 } else if (strncasecmp_m(name, "group", 5) == 0) {
1028 if (determine_size) {
1029 p = talloc_asprintf(ctx, "%s", sidstr);
1030 if (!p) {
1031 errno = ENOMEM;
1032 return -1;
1034 n = strlen(p);
1035 } else {
1036 n = snprintf(buf, bufsize,
1037 "%s", sidstr);
1041 if (!determine_size && n > bufsize) {
1042 errno = ERANGE;
1043 return -1;
1045 buf += n;
1046 n_used += n;
1047 bufsize -= n;
1048 n = 0;
1051 if (! exclude_nt_acl) {
1052 /* Add aces to value buffer */
1053 for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
1055 struct security_ace *ace = &sd->dacl->aces[i];
1056 convert_sid_to_string(ipc_cli, pol,
1057 sidstr, numeric,
1058 &ace->trustee);
1060 if (all || all_nt) {
1061 if (determine_size) {
1062 p = talloc_asprintf(
1063 ctx,
1064 ",ACL:"
1065 "%s:%d/%d/0x%08x",
1066 sidstr,
1067 ace->type,
1068 ace->flags,
1069 ace->access_mask);
1070 if (!p) {
1071 errno = ENOMEM;
1072 return -1;
1074 n = strlen(p);
1075 } else {
1076 n = snprintf(
1077 buf, bufsize,
1078 ",ACL:%s:%d/%d/0x%08x",
1079 sidstr,
1080 ace->type,
1081 ace->flags,
1082 ace->access_mask);
1084 } else if ((strncasecmp_m(name, "acl", 3) == 0 &&
1085 strcasecmp_m(name+3, sidstr) == 0) ||
1086 (strncasecmp_m(name, "acl+", 4) == 0 &&
1087 strcasecmp_m(name+4, sidstr) == 0)) {
1088 if (determine_size) {
1089 p = talloc_asprintf(
1090 ctx,
1091 "%d/%d/0x%08x",
1092 ace->type,
1093 ace->flags,
1094 ace->access_mask);
1095 if (!p) {
1096 errno = ENOMEM;
1097 return -1;
1099 n = strlen(p);
1100 } else {
1101 n = snprintf(buf, bufsize,
1102 "%d/%d/0x%08x",
1103 ace->type,
1104 ace->flags,
1105 ace->access_mask);
1107 } else if (all_nt_acls) {
1108 if (determine_size) {
1109 p = talloc_asprintf(
1110 ctx,
1111 "%s%s:%d/%d/0x%08x",
1112 i ? "," : "",
1113 sidstr,
1114 ace->type,
1115 ace->flags,
1116 ace->access_mask);
1117 if (!p) {
1118 errno = ENOMEM;
1119 return -1;
1121 n = strlen(p);
1122 } else {
1123 n = snprintf(buf, bufsize,
1124 "%s%s:%d/%d/0x%08x",
1125 i ? "," : "",
1126 sidstr,
1127 ace->type,
1128 ace->flags,
1129 ace->access_mask);
1132 if (!determine_size && n > bufsize) {
1133 errno = ERANGE;
1134 return -1;
1136 buf += n;
1137 n_used += n;
1138 bufsize -= n;
1139 n = 0;
1143 /* Restore name pointer to its original value */
1144 name -= 19;
1147 if (all || some_dos) {
1148 struct stat sb = {0};
1149 time_t create_time = (time_t)0;
1150 time_t write_time = (time_t)0;
1151 time_t access_time = (time_t)0;
1152 time_t change_time = (time_t)0;
1153 off_t size = 0;
1154 uint16_t mode = 0;
1155 SMB_INO_T ino = 0;
1156 NTSTATUS status;
1158 /* Point to the portion after "system.dos_attr." */
1159 name += 16; /* if (all) this will be invalid but unused */
1161 /* Obtain the DOS attributes */
1162 status = SMBC_getatr(context, srv, filename, &sb);
1163 if (!NT_STATUS_IS_OK(status)) {
1164 errno = cli_status_to_errno(status);
1165 return -1;
1168 create_time = sb.st_ctime;
1169 access_time = sb.st_atime;
1170 write_time = sb.st_mtime;
1171 change_time = sb.st_mtime;
1172 size = sb.st_size;
1173 mode = sb.st_mode;
1174 ino = sb.st_ino;
1176 if (! exclude_dos_mode) {
1177 if (all || all_dos) {
1178 if (determine_size) {
1179 p = talloc_asprintf(ctx,
1180 "%sMODE:0x%x",
1181 (ipc_cli &&
1182 (all || some_nt)
1183 ? ","
1184 : ""),
1185 mode);
1186 if (!p) {
1187 errno = ENOMEM;
1188 return -1;
1190 n = strlen(p);
1191 } else {
1192 n = snprintf(buf, bufsize,
1193 "%sMODE:0x%x",
1194 (ipc_cli &&
1195 (all || some_nt)
1196 ? ","
1197 : ""),
1198 mode);
1200 } else if (strcasecmp_m(name, "mode") == 0) {
1201 if (determine_size) {
1202 p = talloc_asprintf(ctx, "0x%x", mode);
1203 if (!p) {
1204 errno = ENOMEM;
1205 return -1;
1207 n = strlen(p);
1208 } else {
1209 n = snprintf(buf, bufsize,
1210 "0x%x", mode);
1214 if (!determine_size && n > bufsize) {
1215 errno = ERANGE;
1216 return -1;
1218 buf += n;
1219 n_used += n;
1220 bufsize -= n;
1221 n = 0;
1224 if (! exclude_dos_size) {
1225 if (all || all_dos) {
1226 if (determine_size) {
1227 p = talloc_asprintf(
1228 ctx,
1229 ",SIZE:%.0f",
1230 (double)size);
1231 if (!p) {
1232 errno = ENOMEM;
1233 return -1;
1235 n = strlen(p);
1236 } else {
1237 n = snprintf(buf, bufsize,
1238 ",SIZE:%.0f",
1239 (double)size);
1241 } else if (strcasecmp_m(name, "size") == 0) {
1242 if (determine_size) {
1243 p = talloc_asprintf(
1244 ctx,
1245 "%.0f",
1246 (double)size);
1247 if (!p) {
1248 errno = ENOMEM;
1249 return -1;
1251 n = strlen(p);
1252 } else {
1253 n = snprintf(buf, bufsize,
1254 "%.0f",
1255 (double)size);
1259 if (!determine_size && n > bufsize) {
1260 errno = ERANGE;
1261 return -1;
1263 buf += n;
1264 n_used += n;
1265 bufsize -= n;
1266 n = 0;
1269 if (! exclude_dos_create_time &&
1270 attr_strings.create_time_attr != NULL) {
1271 if (all || all_dos) {
1272 if (determine_size) {
1273 p = talloc_asprintf(ctx,
1274 ",%s:%lu",
1275 attr_strings.create_time_attr,
1276 (unsigned long) create_time);
1277 if (!p) {
1278 errno = ENOMEM;
1279 return -1;
1281 n = strlen(p);
1282 } else {
1283 n = snprintf(buf, bufsize,
1284 ",%s:%lu",
1285 attr_strings.create_time_attr,
1286 (unsigned long) create_time);
1288 } else if (strcasecmp_m(name, attr_strings.create_time_attr) == 0) {
1289 if (determine_size) {
1290 p = talloc_asprintf(ctx, "%lu", (unsigned long) create_time);
1291 if (!p) {
1292 errno = ENOMEM;
1293 return -1;
1295 n = strlen(p);
1296 } else {
1297 n = snprintf(buf, bufsize,
1298 "%lu", (unsigned long) create_time);
1302 if (!determine_size && n > bufsize) {
1303 errno = ERANGE;
1304 return -1;
1306 buf += n;
1307 n_used += n;
1308 bufsize -= n;
1309 n = 0;
1312 if (! exclude_dos_access_time) {
1313 if (all || all_dos) {
1314 if (determine_size) {
1315 p = talloc_asprintf(ctx,
1316 ",%s:%lu",
1317 attr_strings.access_time_attr,
1318 (unsigned long) access_time);
1319 if (!p) {
1320 errno = ENOMEM;
1321 return -1;
1323 n = strlen(p);
1324 } else {
1325 n = snprintf(buf, bufsize,
1326 ",%s:%lu",
1327 attr_strings.access_time_attr,
1328 (unsigned long) access_time);
1330 } else if (strcasecmp_m(name, attr_strings.access_time_attr) == 0) {
1331 if (determine_size) {
1332 p = talloc_asprintf(ctx, "%lu", (unsigned long) access_time);
1333 if (!p) {
1334 errno = ENOMEM;
1335 return -1;
1337 n = strlen(p);
1338 } else {
1339 n = snprintf(buf, bufsize,
1340 "%lu", (unsigned long) access_time);
1344 if (!determine_size && n > bufsize) {
1345 errno = ERANGE;
1346 return -1;
1348 buf += n;
1349 n_used += n;
1350 bufsize -= n;
1351 n = 0;
1354 if (! exclude_dos_write_time) {
1355 if (all || all_dos) {
1356 if (determine_size) {
1357 p = talloc_asprintf(ctx,
1358 ",%s:%lu",
1359 attr_strings.write_time_attr,
1360 (unsigned long) write_time);
1361 if (!p) {
1362 errno = ENOMEM;
1363 return -1;
1365 n = strlen(p);
1366 } else {
1367 n = snprintf(buf, bufsize,
1368 ",%s:%lu",
1369 attr_strings.write_time_attr,
1370 (unsigned long) write_time);
1372 } else if (strcasecmp_m(name, attr_strings.write_time_attr) == 0) {
1373 if (determine_size) {
1374 p = talloc_asprintf(ctx, "%lu", (unsigned long) write_time);
1375 if (!p) {
1376 errno = ENOMEM;
1377 return -1;
1379 n = strlen(p);
1380 } else {
1381 n = snprintf(buf, bufsize,
1382 "%lu", (unsigned long) write_time);
1386 if (!determine_size && n > bufsize) {
1387 errno = ERANGE;
1388 return -1;
1390 buf += n;
1391 n_used += n;
1392 bufsize -= n;
1393 n = 0;
1396 if (! exclude_dos_change_time) {
1397 if (all || all_dos) {
1398 if (determine_size) {
1399 p = talloc_asprintf(ctx,
1400 ",%s:%lu",
1401 attr_strings.change_time_attr,
1402 (unsigned long) change_time);
1403 if (!p) {
1404 errno = ENOMEM;
1405 return -1;
1407 n = strlen(p);
1408 } else {
1409 n = snprintf(buf, bufsize,
1410 ",%s:%lu",
1411 attr_strings.change_time_attr,
1412 (unsigned long) change_time);
1414 } else if (strcasecmp_m(name, attr_strings.change_time_attr) == 0) {
1415 if (determine_size) {
1416 p = talloc_asprintf(ctx, "%lu", (unsigned long) change_time);
1417 if (!p) {
1418 errno = ENOMEM;
1419 return -1;
1421 n = strlen(p);
1422 } else {
1423 n = snprintf(buf, bufsize,
1424 "%lu", (unsigned long) change_time);
1428 if (!determine_size && n > bufsize) {
1429 errno = ERANGE;
1430 return -1;
1432 buf += n;
1433 n_used += n;
1434 bufsize -= n;
1435 n = 0;
1438 if (! exclude_dos_inode) {
1439 if (all || all_dos) {
1440 if (determine_size) {
1441 p = talloc_asprintf(
1442 ctx,
1443 ",INODE:%.0f",
1444 (double)ino);
1445 if (!p) {
1446 errno = ENOMEM;
1447 return -1;
1449 n = strlen(p);
1450 } else {
1451 n = snprintf(buf, bufsize,
1452 ",INODE:%.0f",
1453 (double) ino);
1455 } else if (strcasecmp_m(name, "inode") == 0) {
1456 if (determine_size) {
1457 p = talloc_asprintf(
1458 ctx,
1459 "%.0f",
1460 (double) ino);
1461 if (!p) {
1462 errno = ENOMEM;
1463 return -1;
1465 n = strlen(p);
1466 } else {
1467 n = snprintf(buf, bufsize,
1468 "%.0f",
1469 (double) ino);
1473 if (!determine_size && n > bufsize) {
1474 errno = ERANGE;
1475 return -1;
1477 buf += n;
1478 n_used += n;
1479 bufsize -= n;
1480 n = 0;
1483 /* Restore name pointer to its original value */
1484 name -= 16;
1487 if (n_used == 0) {
1488 errno = ENOATTR;
1489 return -1;
1492 return n_used;
1495 /*****************************************************
1496 set the ACLs on a file given an ascii description
1497 *******************************************************/
1498 static int
1499 cacl_set(SMBCCTX *context,
1500 TALLOC_CTX *ctx,
1501 struct cli_state *cli,
1502 struct cli_state *ipc_cli,
1503 struct policy_handle *pol,
1504 const char *filename,
1505 char *the_acl,
1506 int mode,
1507 int flags)
1509 uint16_t fnum = (uint16_t)-1;
1510 int err = 0;
1511 struct security_descriptor *sd = NULL, *old;
1512 struct security_acl *dacl = NULL;
1513 struct dom_sid *owner_sid = NULL;
1514 struct dom_sid *group_sid = NULL;
1515 uint32_t i, j;
1516 size_t sd_size;
1517 int ret = 0;
1518 char *p;
1519 bool numeric = True;
1520 char *targetpath = NULL;
1521 struct cli_state *targetcli = NULL;
1522 struct cli_credentials *creds = NULL;
1523 NTSTATUS status;
1525 /* the_acl will be null for REMOVE_ALL operations */
1526 if (the_acl) {
1527 numeric = ((p = strchr(the_acl, ':')) != NULL &&
1528 p > the_acl &&
1529 p[-1] != '+');
1531 /* if this is to set the entire ACL... */
1532 if (*the_acl == '*') {
1533 /* ... then increment past the first colon */
1534 the_acl = p + 1;
1537 sd = sec_desc_parse(ctx, ipc_cli, pol, numeric, the_acl);
1538 if (!sd) {
1539 errno = EINVAL;
1540 return -1;
1544 /* SMBC_XATTR_MODE_REMOVE_ALL is the only caller
1545 that doesn't deref sd */
1547 if (!sd && (mode != SMBC_XATTR_MODE_REMOVE_ALL)) {
1548 errno = EINVAL;
1549 return -1;
1552 creds = context->internal->creds;
1554 status = cli_resolve_path(ctx, "",
1555 creds,
1556 cli, filename, &targetcli, &targetpath);
1557 if (!NT_STATUS_IS_OK(status)) {
1558 DEBUG(5,("cacl_set: Could not resolve %s\n", filename));
1559 errno = ENOENT;
1560 return -1;
1563 /* The desired access below is the only one I could find that works
1564 with NT4, W2KP and Samba */
1566 status = cli_ntcreate(
1567 targetcli, /* cli */
1568 targetpath, /* fname */
1569 0, /* CreatFlags */
1570 READ_CONTROL_ACCESS, /* DesiredAccess */
1571 0, /* FileAttributes */
1572 FILE_SHARE_READ|
1573 FILE_SHARE_WRITE, /* ShareAccess */
1574 FILE_OPEN, /* CreateDisposition */
1575 0x0, /* CreateOptions */
1576 0x0, /* SecurityFlags */
1577 &fnum, /* pfid */
1578 NULL); /* cr */
1579 if (!NT_STATUS_IS_OK(status)) {
1580 DEBUG(5, ("cacl_set failed to open %s: %s\n",
1581 targetpath, nt_errstr(status)));
1582 errno = 0;
1583 return -1;
1586 status = cli_query_secdesc(targetcli, fnum, ctx, &old);
1587 if (!NT_STATUS_IS_OK(status)) {
1588 DEBUG(5,("cacl_set Failed to query old descriptor of %s: %s\n",
1589 targetpath, nt_errstr(status)));
1590 errno = 0;
1591 return -1;
1594 cli_close(targetcli, fnum);
1596 switch (mode) {
1597 case SMBC_XATTR_MODE_REMOVE_ALL:
1598 old->dacl->num_aces = 0;
1599 dacl = old->dacl;
1600 break;
1602 case SMBC_XATTR_MODE_REMOVE:
1603 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
1604 bool found = False;
1606 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
1607 if (security_ace_equal(&sd->dacl->aces[i],
1608 &old->dacl->aces[j])) {
1609 uint32_t k;
1610 for (k=j; k<old->dacl->num_aces-1;k++) {
1611 old->dacl->aces[k] =
1612 old->dacl->aces[k+1];
1614 old->dacl->num_aces--;
1615 found = True;
1616 dacl = old->dacl;
1617 break;
1621 if (!found) {
1622 err = ENOATTR;
1623 ret = -1;
1624 goto failed;
1627 break;
1629 case SMBC_XATTR_MODE_ADD:
1630 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
1631 bool found = False;
1633 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
1634 if (dom_sid_equal(&sd->dacl->aces[i].trustee,
1635 &old->dacl->aces[j].trustee)) {
1636 if (!(flags & SMBC_XATTR_FLAG_CREATE)) {
1637 err = EEXIST;
1638 ret = -1;
1639 goto failed;
1641 old->dacl->aces[j] = sd->dacl->aces[i];
1642 ret = -1;
1643 found = True;
1647 if (!found && (flags & SMBC_XATTR_FLAG_REPLACE)) {
1648 err = ENOATTR;
1649 ret = -1;
1650 goto failed;
1653 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
1654 add_ace(&old->dacl, &sd->dacl->aces[i], ctx);
1657 dacl = old->dacl;
1658 break;
1660 case SMBC_XATTR_MODE_SET:
1661 old = sd;
1662 owner_sid = old->owner_sid;
1663 group_sid = old->group_sid;
1664 dacl = old->dacl;
1665 break;
1667 case SMBC_XATTR_MODE_CHOWN:
1668 owner_sid = sd->owner_sid;
1669 break;
1671 case SMBC_XATTR_MODE_CHGRP:
1672 group_sid = sd->group_sid;
1673 break;
1676 /* Denied ACE entries must come before allowed ones */
1677 sort_acl(old->dacl);
1679 /* Create new security descriptor and set it */
1680 sd = make_sec_desc(ctx, old->revision, SEC_DESC_SELF_RELATIVE,
1681 owner_sid, group_sid, NULL, dacl, &sd_size);
1683 status = cli_ntcreate(targetcli, targetpath, 0,
1684 WRITE_DAC_ACCESS | WRITE_OWNER_ACCESS, 0,
1685 FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
1686 0x0, 0x0, &fnum, NULL);
1687 if (!NT_STATUS_IS_OK(status)) {
1688 DEBUG(5, ("cacl_set failed to open %s: %s\n",
1689 targetpath, nt_errstr(status)));
1690 errno = 0;
1691 return -1;
1694 status = cli_set_secdesc(targetcli, fnum, sd);
1695 if (!NT_STATUS_IS_OK(status)) {
1696 DEBUG(5, ("ERROR: secdesc set failed: %s\n",
1697 nt_errstr(status)));
1698 ret = -1;
1701 /* Clean up */
1703 failed:
1704 cli_close(targetcli, fnum);
1706 if (err != 0) {
1707 errno = err;
1710 return ret;
1715 SMBC_setxattr_ctx(SMBCCTX *context,
1716 const char *fname,
1717 const char *name,
1718 const void *value,
1719 size_t size,
1720 int flags)
1722 int ret;
1723 int ret2;
1724 SMBCSRV *srv = NULL;
1725 SMBCSRV *ipc_srv = NULL;
1726 char *server = NULL;
1727 char *share = NULL;
1728 char *user = NULL;
1729 char *password = NULL;
1730 char *workgroup = NULL;
1731 char *path = NULL;
1732 struct DOS_ATTR_DESC *dad = NULL;
1733 struct {
1734 const char * create_time_attr;
1735 const char * access_time_attr;
1736 const char * write_time_attr;
1737 const char * change_time_attr;
1738 } attr_strings;
1739 uint16_t port = 0;
1740 TALLOC_CTX *frame = talloc_stackframe();
1742 if (!context || !context->internal->initialized) {
1743 errno = EINVAL; /* Best I can think of ... */
1744 TALLOC_FREE(frame);
1745 return -1;
1748 if (!fname) {
1749 errno = EINVAL;
1750 TALLOC_FREE(frame);
1751 return -1;
1754 DEBUG(4, ("smbc_setxattr(%s, %s, %.*s)\n",
1755 fname, name, (int) size, (const char*)value));
1757 if (SMBC_parse_path(frame,
1758 context,
1759 fname,
1760 &workgroup,
1761 &server,
1762 &port,
1763 &share,
1764 &path,
1765 &user,
1766 &password,
1767 NULL)) {
1768 errno = EINVAL;
1769 TALLOC_FREE(frame);
1770 return -1;
1773 if (!user || user[0] == (char)0) {
1774 user = talloc_strdup(frame, smbc_getUser(context));
1775 if (!user) {
1776 errno = ENOMEM;
1777 TALLOC_FREE(frame);
1778 return -1;
1782 srv = SMBC_server(frame, context, True,
1783 server, port, share, &workgroup, &user, &password);
1784 if (!srv) {
1785 TALLOC_FREE(frame);
1786 return -1; /* errno set by SMBC_server */
1789 if (! srv->no_nt_session) {
1790 ipc_srv = SMBC_attr_server(frame, context, server, port, share,
1791 &workgroup, &user, &password);
1792 if (! ipc_srv) {
1793 srv->no_nt_session = True;
1795 } else {
1796 ipc_srv = NULL;
1800 * Are they asking to set the entire set of known attributes?
1802 if (strcasecmp_m(name, "system.*") == 0 ||
1803 strcasecmp_m(name, "system.*+") == 0) {
1804 /* Yup. */
1805 char *namevalue =
1806 talloc_asprintf(talloc_tos(), "%s:%s",
1807 name+7, (const char *) value);
1808 if (! namevalue) {
1809 errno = ENOMEM;
1810 ret = -1;
1811 TALLOC_FREE(frame);
1812 return -1;
1815 if (ipc_srv) {
1816 ret = cacl_set(context, talloc_tos(), srv->cli,
1817 ipc_srv->cli, &ipc_srv->pol, path,
1818 namevalue,
1819 (*namevalue == '*'
1820 ? SMBC_XATTR_MODE_SET
1821 : SMBC_XATTR_MODE_ADD),
1822 flags);
1823 } else {
1824 ret = 0;
1827 /* get a DOS Attribute Descriptor with current attributes */
1828 dad = dos_attr_query(context, talloc_tos(), path, srv);
1829 if (dad) {
1830 bool ok;
1832 /* Overwrite old with new, using what was provided */
1833 dos_attr_parse(context, dad, srv, namevalue);
1835 /* Set the new DOS attributes */
1836 ok = SMBC_setatr(
1837 context,
1838 srv,
1839 path,
1840 (struct timespec) {
1841 .tv_sec = dad->create_time },
1842 (struct timespec) {
1843 .tv_sec = dad->access_time },
1844 (struct timespec) {
1845 .tv_sec = dad->write_time },
1846 (struct timespec) {
1847 .tv_sec = dad->change_time },
1848 dad->mode);
1849 if (!ok) {
1850 /* cause failure if NT failed too */
1851 dad = NULL;
1855 /* we only fail if both NT and DOS sets failed */
1856 if (ret < 0 && ! dad) {
1857 ret = -1; /* in case dad was null */
1859 else {
1860 ret = 0;
1863 TALLOC_FREE(frame);
1864 return ret;
1868 * Are they asking to set an access control element or to set
1869 * the entire access control list?
1871 if (strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
1872 strcasecmp_m(name, "system.nt_sec_desc.*+") == 0 ||
1873 strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
1874 strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
1875 strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0) {
1877 /* Yup. */
1878 char *namevalue =
1879 talloc_asprintf(talloc_tos(), "%s:%s",
1880 name+19, (const char *) value);
1882 if (! ipc_srv) {
1883 ret = -1; /* errno set by SMBC_server() */
1885 else if (! namevalue) {
1886 errno = ENOMEM;
1887 ret = -1;
1888 } else {
1889 ret = cacl_set(context, talloc_tos(), srv->cli,
1890 ipc_srv->cli, &ipc_srv->pol, path,
1891 namevalue,
1892 (*namevalue == '*'
1893 ? SMBC_XATTR_MODE_SET
1894 : SMBC_XATTR_MODE_ADD),
1895 flags);
1897 TALLOC_FREE(frame);
1898 return ret;
1902 * Are they asking to set the owner?
1904 if (strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
1905 strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0) {
1907 /* Yup. */
1908 char *namevalue =
1909 talloc_asprintf(talloc_tos(), "%s:%s",
1910 name+19, (const char *) value);
1912 if (! ipc_srv) {
1913 ret = -1; /* errno set by SMBC_server() */
1915 else if (! namevalue) {
1916 errno = ENOMEM;
1917 ret = -1;
1918 } else {
1919 ret = cacl_set(context, talloc_tos(), srv->cli,
1920 ipc_srv->cli, &ipc_srv->pol, path,
1921 namevalue, SMBC_XATTR_MODE_CHOWN, 0);
1923 TALLOC_FREE(frame);
1924 return ret;
1928 * Are they asking to set the group?
1930 if (strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
1931 strcasecmp_m(name, "system.nt_sec_desc.group+") == 0) {
1933 /* Yup. */
1934 char *namevalue =
1935 talloc_asprintf(talloc_tos(), "%s:%s",
1936 name+19, (const char *) value);
1938 if (! ipc_srv) {
1939 /* errno set by SMBC_server() */
1940 ret = -1;
1942 else if (! namevalue) {
1943 errno = ENOMEM;
1944 ret = -1;
1945 } else {
1946 ret = cacl_set(context, talloc_tos(), srv->cli,
1947 ipc_srv->cli, &ipc_srv->pol, path,
1948 namevalue, SMBC_XATTR_MODE_CHGRP, 0);
1950 TALLOC_FREE(frame);
1951 return ret;
1954 /* Determine whether to use old-style or new-style attribute names */
1955 if (context->internal->full_time_names) {
1956 /* new-style names */
1957 attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME";
1958 attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME";
1959 attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME";
1960 attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME";
1961 } else {
1962 /* old-style names */
1963 attr_strings.create_time_attr = NULL;
1964 attr_strings.access_time_attr = "system.dos_attr.A_TIME";
1965 attr_strings.write_time_attr = "system.dos_attr.M_TIME";
1966 attr_strings.change_time_attr = "system.dos_attr.C_TIME";
1970 * Are they asking to set a DOS attribute?
1972 if (strcasecmp_m(name, "system.dos_attr.*") == 0 ||
1973 strcasecmp_m(name, "system.dos_attr.mode") == 0 ||
1974 (attr_strings.create_time_attr != NULL &&
1975 strcasecmp_m(name, attr_strings.create_time_attr) == 0) ||
1976 strcasecmp_m(name, attr_strings.access_time_attr) == 0 ||
1977 strcasecmp_m(name, attr_strings.write_time_attr) == 0 ||
1978 strcasecmp_m(name, attr_strings.change_time_attr) == 0) {
1980 /* get a DOS Attribute Descriptor with current attributes */
1981 dad = dos_attr_query(context, talloc_tos(), path, srv);
1982 if (dad) {
1983 char *namevalue =
1984 talloc_asprintf(talloc_tos(), "%s:%s",
1985 name+16, (const char *) value);
1986 if (! namevalue) {
1987 errno = ENOMEM;
1988 ret = -1;
1989 } else {
1990 /* Overwrite old with provided new params */
1991 dos_attr_parse(context, dad, srv, namevalue);
1993 /* Set the new DOS attributes */
1994 ret2 = SMBC_setatr(
1995 context,
1996 srv,
1997 path,
1998 (struct timespec) {
1999 .tv_sec = dad->create_time },
2000 (struct timespec) {
2001 .tv_sec = dad->access_time },
2002 (struct timespec) {
2003 .tv_sec = dad->write_time },
2004 (struct timespec) {
2005 .tv_sec = dad->change_time },
2006 dad->mode);
2008 /* ret2 has True (success) / False (failure) */
2009 if (ret2) {
2010 ret = 0;
2011 } else {
2012 ret = -1;
2015 } else {
2016 ret = -1;
2019 TALLOC_FREE(frame);
2020 return ret;
2023 /* Unsupported attribute name */
2024 errno = EINVAL;
2025 TALLOC_FREE(frame);
2026 return -1;
2030 SMBC_getxattr_ctx(SMBCCTX *context,
2031 const char *fname,
2032 const char *name,
2033 const void *value,
2034 size_t size)
2036 int ret;
2037 SMBCSRV *srv = NULL;
2038 SMBCSRV *ipc_srv = NULL;
2039 char *server = NULL;
2040 char *share = NULL;
2041 char *user = NULL;
2042 char *password = NULL;
2043 char *workgroup = NULL;
2044 char *path = NULL;
2045 struct {
2046 const char * create_time_attr;
2047 const char * access_time_attr;
2048 const char * write_time_attr;
2049 const char * change_time_attr;
2050 } attr_strings;
2051 uint16_t port = 0;
2052 TALLOC_CTX *frame = talloc_stackframe();
2054 if (!context || !context->internal->initialized) {
2055 errno = EINVAL; /* Best I can think of ... */
2056 TALLOC_FREE(frame);
2057 return -1;
2060 if (!fname) {
2061 errno = EINVAL;
2062 TALLOC_FREE(frame);
2063 return -1;
2066 DEBUG(4, ("smbc_getxattr(%s, %s)\n", fname, name));
2068 if (SMBC_parse_path(frame,
2069 context,
2070 fname,
2071 &workgroup,
2072 &server,
2073 &port,
2074 &share,
2075 &path,
2076 &user,
2077 &password,
2078 NULL)) {
2079 errno = EINVAL;
2080 TALLOC_FREE(frame);
2081 return -1;
2084 if (!user || user[0] == '\0') {
2085 user = talloc_strdup(frame, smbc_getUser(context));
2086 if (!user) {
2087 errno = ENOMEM;
2088 TALLOC_FREE(frame);
2089 return -1;
2093 srv = SMBC_server(frame, context, True,
2094 server, port, share, &workgroup, &user, &password);
2095 if (!srv) {
2096 TALLOC_FREE(frame);
2097 return -1; /* errno set by SMBC_server */
2100 if (! srv->no_nt_session) {
2101 ipc_srv = SMBC_attr_server(frame, context, server, port, share,
2102 &workgroup, &user, &password);
2104 * SMBC_attr_server() can cause the original
2105 * server to be removed from the cache.
2106 * If so we must error out here as the srv
2107 * pointer has been freed.
2109 if (smbc_getFunctionGetCachedServer(context)(context,
2110 server,
2111 share,
2112 workgroup,
2113 user) != srv) {
2114 #if defined(ECONNRESET)
2115 errno = ECONNRESET;
2116 #else
2117 errno = ETIMEDOUT;
2118 #endif
2119 TALLOC_FREE(frame);
2120 return -1;
2122 if (! ipc_srv) {
2123 srv->no_nt_session = True;
2125 } else {
2126 ipc_srv = NULL;
2129 /* Determine whether to use old-style or new-style attribute names */
2130 if (context->internal->full_time_names) {
2131 /* new-style names */
2132 attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME";
2133 attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME";
2134 attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME";
2135 attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME";
2136 } else {
2137 /* old-style names */
2138 attr_strings.create_time_attr = NULL;
2139 attr_strings.access_time_attr = "system.dos_attr.A_TIME";
2140 attr_strings.write_time_attr = "system.dos_attr.M_TIME";
2141 attr_strings.change_time_attr = "system.dos_attr.C_TIME";
2144 /* Are they requesting a supported attribute? */
2145 if (strcasecmp_m(name, "system.*") == 0 ||
2146 strncasecmp_m(name, "system.*!", 9) == 0 ||
2147 strcasecmp_m(name, "system.*+") == 0 ||
2148 strncasecmp_m(name, "system.*+!", 10) == 0 ||
2149 strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
2150 strncasecmp_m(name, "system.nt_sec_desc.*!", 21) == 0 ||
2151 strcasecmp_m(name, "system.nt_sec_desc.*+") == 0 ||
2152 strncasecmp_m(name, "system.nt_sec_desc.*+!", 22) == 0 ||
2153 strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
2154 strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
2155 strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0 ||
2156 strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
2157 strcasecmp_m(name, "system.nt_sec_desc.group+") == 0 ||
2158 strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
2159 strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0 ||
2160 strcasecmp_m(name, "system.dos_attr.*") == 0 ||
2161 strncasecmp_m(name, "system.dos_attr.*!", 18) == 0 ||
2162 strcasecmp_m(name, "system.dos_attr.mode") == 0 ||
2163 strcasecmp_m(name, "system.dos_attr.size") == 0 ||
2164 (attr_strings.create_time_attr != NULL &&
2165 strcasecmp_m(name, attr_strings.create_time_attr) == 0) ||
2166 strcasecmp_m(name, attr_strings.access_time_attr) == 0 ||
2167 strcasecmp_m(name, attr_strings.write_time_attr) == 0 ||
2168 strcasecmp_m(name, attr_strings.change_time_attr) == 0 ||
2169 strcasecmp_m(name, "system.dos_attr.inode") == 0) {
2171 /* Yup. */
2172 const char *filename = name;
2173 ret = cacl_get(context, talloc_tos(), srv,
2174 ipc_srv == NULL ? NULL : ipc_srv->cli,
2175 &ipc_srv->pol, path,
2176 filename,
2177 discard_const_p(char, value),
2178 size);
2179 if (ret < 0 && errno == 0) {
2180 errno = SMBC_errno(context, srv->cli);
2182 TALLOC_FREE(frame);
2184 * static function cacl_get returns a value greater than zero
2185 * on success. Map this to zero meaning success.
2187 return ret < 0 ? -1 : 0;
2190 /* Unsupported attribute name */
2191 errno = EINVAL;
2192 TALLOC_FREE(frame);
2193 return -1;
2198 SMBC_removexattr_ctx(SMBCCTX *context,
2199 const char *fname,
2200 const char *name)
2202 int ret;
2203 SMBCSRV *srv = NULL;
2204 SMBCSRV *ipc_srv = NULL;
2205 char *server = NULL;
2206 char *share = NULL;
2207 char *user = NULL;
2208 char *password = NULL;
2209 char *workgroup = NULL;
2210 char *path = NULL;
2211 uint16_t port = 0;
2212 TALLOC_CTX *frame = talloc_stackframe();
2214 if (!context || !context->internal->initialized) {
2215 errno = EINVAL; /* Best I can think of ... */
2216 TALLOC_FREE(frame);
2217 return -1;
2220 if (!fname) {
2221 errno = EINVAL;
2222 TALLOC_FREE(frame);
2223 return -1;
2226 DEBUG(4, ("smbc_removexattr(%s, %s)\n", fname, name));
2228 if (SMBC_parse_path(frame,
2229 context,
2230 fname,
2231 &workgroup,
2232 &server,
2233 &port,
2234 &share,
2235 &path,
2236 &user,
2237 &password,
2238 NULL)) {
2239 errno = EINVAL;
2240 TALLOC_FREE(frame);
2241 return -1;
2244 if (!user || user[0] == (char)0) {
2245 user = talloc_strdup(frame, smbc_getUser(context));
2246 if (!user) {
2247 errno = ENOMEM;
2248 TALLOC_FREE(frame);
2249 return -1;
2253 srv = SMBC_server(frame, context, True,
2254 server, port, share, &workgroup, &user, &password);
2255 if (!srv) {
2256 TALLOC_FREE(frame);
2257 return -1; /* errno set by SMBC_server */
2260 if (! srv->no_nt_session) {
2261 int saved_errno;
2262 ipc_srv = SMBC_attr_server(frame, context, server, port, share,
2263 &workgroup, &user, &password);
2264 saved_errno = errno;
2266 * SMBC_attr_server() can cause the original
2267 * server to be removed from the cache.
2268 * If so we must error out here as the srv
2269 * pointer has been freed.
2271 if (smbc_getFunctionGetCachedServer(context)(context,
2272 server,
2273 share,
2274 workgroup,
2275 user) != srv) {
2276 #if defined(ECONNRESET)
2277 errno = ECONNRESET;
2278 #else
2279 errno = ETIMEDOUT;
2280 #endif
2281 TALLOC_FREE(frame);
2282 return -1;
2284 if (! ipc_srv) {
2285 errno = saved_errno;
2286 srv->no_nt_session = True;
2288 } else {
2289 ipc_srv = NULL;
2292 if (! ipc_srv) {
2293 TALLOC_FREE(frame);
2294 return -1; /* errno set by SMBC_attr_server */
2297 /* Are they asking to set the entire ACL? */
2298 if (strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
2299 strcasecmp_m(name, "system.nt_sec_desc.*+") == 0) {
2301 /* Yup. */
2302 ret = cacl_set(context, talloc_tos(), srv->cli,
2303 ipc_srv->cli, &ipc_srv->pol, path,
2304 NULL, SMBC_XATTR_MODE_REMOVE_ALL, 0);
2305 TALLOC_FREE(frame);
2306 return ret;
2310 * Are they asking to remove one or more specific security descriptor
2311 * attributes?
2313 if (strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
2314 strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
2315 strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0 ||
2316 strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
2317 strcasecmp_m(name, "system.nt_sec_desc.group+") == 0 ||
2318 strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
2319 strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0) {
2321 /* Yup. */
2322 ret = cacl_set(context, talloc_tos(), srv->cli,
2323 ipc_srv->cli, &ipc_srv->pol, path,
2324 discard_const_p(char, name) + 19,
2325 SMBC_XATTR_MODE_REMOVE, 0);
2326 TALLOC_FREE(frame);
2327 return ret;
2330 /* Unsupported attribute name */
2331 errno = EINVAL;
2332 TALLOC_FREE(frame);
2333 return -1;
2337 SMBC_listxattr_ctx(SMBCCTX *context,
2338 const char *fname,
2339 char *list,
2340 size_t size)
2343 * This isn't quite what listxattr() is supposed to do. This returns
2344 * the complete set of attribute names, always, rather than only those
2345 * attribute names which actually exist for a file. Hmmm...
2347 size_t retsize;
2348 static const char supported_old[] =
2349 "system.*\0"
2350 "system.*+\0"
2351 "system.nt_sec_desc.revision\0"
2352 "system.nt_sec_desc.owner\0"
2353 "system.nt_sec_desc.owner+\0"
2354 "system.nt_sec_desc.group\0"
2355 "system.nt_sec_desc.group+\0"
2356 "system.nt_sec_desc.acl.*\0"
2357 "system.nt_sec_desc.acl\0"
2358 "system.nt_sec_desc.acl+\0"
2359 "system.nt_sec_desc.*\0"
2360 "system.nt_sec_desc.*+\0"
2361 "system.dos_attr.*\0"
2362 "system.dos_attr.mode\0"
2363 "system.dos_attr.c_time\0"
2364 "system.dos_attr.a_time\0"
2365 "system.dos_attr.m_time\0"
2367 static const char supported_new[] =
2368 "system.*\0"
2369 "system.*+\0"
2370 "system.nt_sec_desc.revision\0"
2371 "system.nt_sec_desc.owner\0"
2372 "system.nt_sec_desc.owner+\0"
2373 "system.nt_sec_desc.group\0"
2374 "system.nt_sec_desc.group+\0"
2375 "system.nt_sec_desc.acl.*\0"
2376 "system.nt_sec_desc.acl\0"
2377 "system.nt_sec_desc.acl+\0"
2378 "system.nt_sec_desc.*\0"
2379 "system.nt_sec_desc.*+\0"
2380 "system.dos_attr.*\0"
2381 "system.dos_attr.mode\0"
2382 "system.dos_attr.create_time\0"
2383 "system.dos_attr.access_time\0"
2384 "system.dos_attr.write_time\0"
2385 "system.dos_attr.change_time\0"
2387 const char * supported;
2389 if (context->internal->full_time_names) {
2390 supported = supported_new;
2391 retsize = sizeof(supported_new);
2392 } else {
2393 supported = supported_old;
2394 retsize = sizeof(supported_old);
2397 if (size == 0) {
2398 return retsize;
2401 if (retsize > size) {
2402 errno = ERANGE;
2403 return -1;
2406 /* this can't be strcpy() because there are embedded null characters */
2407 memcpy(list, supported, retsize);
2408 return retsize;