1/*
2   Unix SMB/CIFS implementation.
3   ACL get/set utility
4
5   Copyright (C) Andrew Tridgell 2000
6   Copyright (C) Tim Potter      2000
7   Copyright (C) Jeremy Allison  2000
8   Copyright (C) Jelmer Vernooij 2003
9
10   This program is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 3 of the License, or
13   (at your option) any later version.
14
15   This program 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
18   GNU General Public License for more details.
19
20   You should have received a copy of the GNU General Public License
21   along with this program.  If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#include "includes.h"
25
26extern bool AllowDebugChange;
27
28static int test_args;
29
30#define CREATE_ACCESS_READ READ_CONTROL_ACCESS
31
32/* numeric is set when the user wants numeric SIDs and ACEs rather
33   than going via LSA calls to resolve them */
34static int numeric;
35
36enum acl_mode {SMB_ACL_SET, SMB_ACL_DELETE, SMB_ACL_MODIFY, SMB_ACL_ADD };
37enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP};
38enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
39
40struct perm_value {
41	const char *perm;
42	uint32 mask;
43};
44
45/* These values discovered by inspection */
46
47static const struct perm_value special_values[] = {
48	{ "R", 0x00120089 },
49	{ "W", 0x00120116 },
50	{ "X", 0x001200a0 },
51	{ "D", 0x00010000 },
52	{ "P", 0x00040000 },
53	{ "O", 0x00080000 },
54	{ NULL, 0 },
55};
56
57static const struct perm_value standard_values[] = {
58	{ "READ",   0x001200a9 },
59	{ "CHANGE", 0x001301bf },
60	{ "FULL",   0x001f01ff },
61	{ NULL, 0 },
62};
63
64/* Open cli connection and policy handle */
65
66static NTSTATUS cli_lsa_lookup_sid(struct cli_state *cli,
67				   const DOM_SID *sid,
68				   TALLOC_CTX *mem_ctx,
69				   enum lsa_SidType *type,
70				   char **domain, char **name)
71{
72	uint16 orig_cnum = cli->cnum;
73	struct rpc_pipe_client *p = NULL;
74	struct policy_handle handle;
75	NTSTATUS status;
76	TALLOC_CTX *frame = talloc_stackframe();
77	enum lsa_SidType *types;
78	char **domains;
79	char **names;
80
81	status = cli_tcon_andx(cli, "IPC$", "?????", "", 0);
82	if (!NT_STATUS_IS_OK(status)) {
83		return status;
84	}
85
86	status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc.syntax_id,
87					  &p);
88	if (!NT_STATUS_IS_OK(status)) {
89		goto fail;
90	}
91
92	status = rpccli_lsa_open_policy(p, talloc_tos(), True,
93					GENERIC_EXECUTE_ACCESS, &handle);
94	if (!NT_STATUS_IS_OK(status)) {
95		goto fail;
96	}
97
98	status = rpccli_lsa_lookup_sids(p, talloc_tos(), &handle, 1, sid,
99					&domains, &names, &types);
100	if (!NT_STATUS_IS_OK(status)) {
101		goto fail;
102	}
103
104	*type = types[0];
105	*domain = talloc_move(mem_ctx, &domains[0]);
106	*name = talloc_move(mem_ctx, &names[0]);
107
108	status = NT_STATUS_OK;
109 fail:
110	TALLOC_FREE(p);
111	cli_tdis(cli);
112	cli->cnum = orig_cnum;
113	TALLOC_FREE(frame);
114	return status;
115}
116
117static NTSTATUS cli_lsa_lookup_name(struct cli_state *cli,
118				    const char *name,
119				    enum lsa_SidType *type,
120				    DOM_SID *sid)
121{
122	uint16 orig_cnum = cli->cnum;
123	struct rpc_pipe_client *p;
124	struct policy_handle handle;
125	NTSTATUS status;
126	TALLOC_CTX *frame = talloc_stackframe();
127	DOM_SID *sids;
128	enum lsa_SidType *types;
129
130	status = cli_tcon_andx(cli, "IPC$", "?????", "", 0);
131	if (!NT_STATUS_IS_OK(status)) {
132		return status;
133	}
134
135	status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc.syntax_id,
136					  &p);
137	if (!NT_STATUS_IS_OK(status)) {
138		goto fail;
139	}
140
141	status = rpccli_lsa_open_policy(p, talloc_tos(), True,
142					GENERIC_EXECUTE_ACCESS, &handle);
143	if (!NT_STATUS_IS_OK(status)) {
144		goto fail;
145	}
146
147	status = rpccli_lsa_lookup_names(p, talloc_tos(), &handle, 1, &name,
148					 NULL, 1, &sids, &types);
149	if (!NT_STATUS_IS_OK(status)) {
150		goto fail;
151	}
152
153	*type = types[0];
154	*sid = sids[0];
155
156	status = NT_STATUS_OK;
157 fail:
158	TALLOC_FREE(p);
159	cli_tdis(cli);
160	cli->cnum = orig_cnum;
161	TALLOC_FREE(frame);
162	return status;
163}
164
165/* convert a SID to a string, either numeric or username/group */
166static void SidToString(struct cli_state *cli, fstring str, const DOM_SID *sid)
167{
168	char *domain = NULL;
169	char *name = NULL;
170	enum lsa_SidType type;
171	NTSTATUS status;
172
173	sid_to_fstring(str, sid);
174
175	if (numeric) {
176		return;
177	}
178
179	status = cli_lsa_lookup_sid(cli, sid, talloc_tos(), &type,
180				    &domain, &name);
181
182	if (!NT_STATUS_IS_OK(status)) {
183		return;
184	}
185
186	if (*domain) {
187		slprintf(str, sizeof(fstring) - 1, "%s%s%s",
188			domain, lp_winbind_separator(), name);
189	} else {
190		fstrcpy(str, name);
191	}
192}
193
194/* convert a string to a SID, either numeric or username/group */
195static bool StringToSid(struct cli_state *cli, DOM_SID *sid, const char *str)
196{
197	enum lsa_SidType type;
198
199	if (strncmp(str, "S-", 2) == 0) {
200		return string_to_sid(sid, str);
201	}
202
203	return NT_STATUS_IS_OK(cli_lsa_lookup_name(cli, str, &type, sid));
204}
205
206static void print_ace_flags(FILE *f, uint8_t flags)
207{
208	char *str = talloc_strdup(NULL, "");
209
210	if (!str) {
211		goto out;
212	}
213
214	if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) {
215		str = talloc_asprintf(str, "%s%s",
216				str, "OI|");
217		if (!str) {
218			goto out;
219		}
220	}
221	if (flags & SEC_ACE_FLAG_CONTAINER_INHERIT) {
222		str = talloc_asprintf(str, "%s%s",
223				str, "CI|");
224		if (!str) {
225			goto out;
226		}
227	}
228	if (flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) {
229		str = talloc_asprintf(str, "%s%s",
230				str, "NP|");
231		if (!str) {
232			goto out;
233		}
234	}
235	if (flags & SEC_ACE_FLAG_INHERIT_ONLY) {
236		str = talloc_asprintf(str, "%s%s",
237				str, "IO|");
238		if (!str) {
239			goto out;
240		}
241	}
242	if (flags & SEC_ACE_FLAG_INHERITED_ACE) {
243		str = talloc_asprintf(str, "%s%s",
244				str, "I|");
245		if (!str) {
246			goto out;
247		}
248	}
249	/* Ignore define SEC_ACE_FLAG_SUCCESSFUL_ACCESS ( 0x40 )
250	   and SEC_ACE_FLAG_FAILED_ACCESS ( 0x80 ) as they're
251	   audit ace flags. */
252
253	if (str[strlen(str)-1] == '|') {
254		str[strlen(str)-1] = '\0';
255		fprintf(f, "/%s/", str);
256	} else {
257		fprintf(f, "/0x%x/", flags);
258	}
259	TALLOC_FREE(str);
260	return;
261
262  out:
263	fprintf(f, "/0x%x/", flags);
264}
265
266/* print an ACE on a FILE, using either numeric or ascii representation */
267static void print_ace(struct cli_state *cli, FILE *f, SEC_ACE *ace)
268{
269	const struct perm_value *v;
270	fstring sidstr;
271	int do_print = 0;
272	uint32 got_mask;
273
274	SidToString(cli, sidstr, &ace->trustee);
275
276	fprintf(f, "%s:", sidstr);
277
278	if (numeric) {
279		fprintf(f, "%d/0x%x/0x%08x",
280			ace->type, ace->flags, ace->access_mask);
281		return;
282	}
283
284	/* Ace type */
285
286	if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
287		fprintf(f, "ALLOWED");
288	} else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
289		fprintf(f, "DENIED");
290	} else {
291		fprintf(f, "%d", ace->type);
292	}
293
294	print_ace_flags(f, ace->flags);
295
296	/* Standard permissions */
297
298	for (v = standard_values; v->perm; v++) {
299		if (ace->access_mask == v->mask) {
300			fprintf(f, "%s", v->perm);
301			return;
302		}
303	}
304
305	/* Special permissions.  Print out a hex value if we have
306	   leftover bits in the mask. */
307
308	got_mask = ace->access_mask;
309
310 again:
311	for (v = special_values; v->perm; v++) {
312		if ((ace->access_mask & v->mask) == v->mask) {
313			if (do_print) {
314				fprintf(f, "%s", v->perm);
315			}
316			got_mask &= ~v->mask;
317		}
318	}
319
320	if (!do_print) {
321		if (got_mask != 0) {
322			fprintf(f, "0x%08x", ace->access_mask);
323		} else {
324			do_print = 1;
325			goto again;
326		}
327	}
328}
329
330static bool parse_ace_flags(const char *str, unsigned int *pflags)
331{
332	const char *p = str;
333	*pflags = 0;
334
335	while (*p) {
336		if (strnequal(p, "OI", 2)) {
337			*pflags |= SEC_ACE_FLAG_OBJECT_INHERIT;
338			p += 2;
339		} else if (strnequal(p, "CI", 2)) {
340			*pflags |= SEC_ACE_FLAG_CONTAINER_INHERIT;
341			p += 2;
342		} else if (strnequal(p, "NP", 2)) {
343			*pflags |= SEC_ACE_FLAG_NO_PROPAGATE_INHERIT;
344			p += 2;
345		} else if (strnequal(p, "IO", 2)) {
346			*pflags |= SEC_ACE_FLAG_INHERIT_ONLY;
347			p += 2;
348		} else if (*p == 'I') {
349			*pflags |= SEC_ACE_FLAG_INHERITED_ACE;
350			p += 1;
351		} else if (*p) {
352			return false;
353		}
354
355		if (*p != '|' && *p != '\0') {
356			return false;
357		}
358	}
359	return true;
360}
361
362/* parse an ACE in the same format as print_ace() */
363static bool parse_ace(struct cli_state *cli, SEC_ACE *ace,
364		      const char *orig_str)
365{
366	char *p;
367	const char *cp;
368	char *tok;
369	unsigned int atype = 0;
370	unsigned int aflags = 0;
371	unsigned int amask = 0;
372	DOM_SID sid;
373	uint32_t mask;
374	const struct perm_value *v;
375	char *str = SMB_STRDUP(orig_str);
376	TALLOC_CTX *frame = talloc_stackframe();
377
378	if (!str) {
379		TALLOC_FREE(frame);
380		return False;
381	}
382
383	ZERO_STRUCTP(ace);
384	p = strchr_m(str,':');
385	if (!p) {
386		printf("ACE '%s': missing ':'.\n", orig_str);
387		SAFE_FREE(str);
388		TALLOC_FREE(frame);
389		return False;
390	}
391	*p = '\0';
392	p++;
393	/* Try to parse numeric form */
394
395	if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 &&
396	    StringToSid(cli, &sid, str)) {
397		goto done;
398	}
399
400	/* Try to parse text form */
401
402	if (!StringToSid(cli, &sid, str)) {
403		printf("ACE '%s': failed to convert '%s' to SID\n",
404			orig_str, str);
405		SAFE_FREE(str);
406		TALLOC_FREE(frame);
407		return False;
408	}
409
410	cp = p;
411	if (!next_token_talloc(frame, &cp, &tok, "/")) {
412		printf("ACE '%s': failed to find '/' character.\n",
413			orig_str);
414		SAFE_FREE(str);
415		TALLOC_FREE(frame);
416		return False;
417	}
418
419	if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
420		atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
421	} else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
422		atype = SEC_ACE_TYPE_ACCESS_DENIED;
423	} else {
424		printf("ACE '%s': missing 'ALLOWED' or 'DENIED' entry at '%s'\n",
425			orig_str, tok);
426		SAFE_FREE(str);
427		TALLOC_FREE(frame);
428		return False;
429	}
430
431	/* Only numeric form accepted for flags at present */
432
433	if (!next_token_talloc(frame, &cp, &tok, "/")) {
434		printf("ACE '%s': bad flags entry at '%s'\n",
435			orig_str, tok);
436		SAFE_FREE(str);
437		TALLOC_FREE(frame);
438		return False;
439	}
440
441	if (tok[0] < '0' || tok[0] > '9') {
442		if (!parse_ace_flags(tok, &aflags)) {
443			printf("ACE '%s': bad named flags entry at '%s'\n",
444				orig_str, tok);
445			SAFE_FREE(str);
446			TALLOC_FREE(frame);
447			return False;
448		}
449	} else if (strnequal(tok, "0x", 2)) {
450		if (!sscanf(tok, "%x", &aflags)) {
451			printf("ACE '%s': bad hex flags entry at '%s'\n",
452				orig_str, tok);
453			SAFE_FREE(str);
454			TALLOC_FREE(frame);
455			return False;
456		}
457	} else {
458		if (!sscanf(tok, "%i", &aflags)) {
459			printf("ACE '%s': bad integer flags entry at '%s'\n",
460				orig_str, tok);
461			SAFE_FREE(str);
462			TALLOC_FREE(frame);
463			return False;
464		}
465	}
466
467	if (!next_token_talloc(frame, &cp, &tok, "/")) {
468		printf("ACE '%s': missing / at '%s'\n",
469			orig_str, tok);
470		SAFE_FREE(str);
471		TALLOC_FREE(frame);
472		return False;
473	}
474
475	if (strncmp(tok, "0x", 2) == 0) {
476		if (sscanf(tok, "%i", &amask) != 1) {
477			printf("ACE '%s': bad hex number at '%s'\n",
478				orig_str, tok);
479			SAFE_FREE(str);
480			TALLOC_FREE(frame);
481			return False;
482		}
483		goto done;
484	}
485
486	for (v = standard_values; v->perm; v++) {
487		if (strcmp(tok, v->perm) == 0) {
488			amask = v->mask;
489			goto done;
490		}
491	}
492
493	p = tok;
494
495	while(*p) {
496		bool found = False;
497
498		for (v = special_values; v->perm; v++) {
499			if (v->perm[0] == *p) {
500				amask |= v->mask;
501				found = True;
502			}
503		}
504
505		if (!found) {
506			printf("ACE '%s': bad permission value at '%s'\n",
507				orig_str, p);
508			SAFE_FREE(str);
509			TALLOC_FREE(frame);
510		 	return False;
511		}
512		p++;
513	}
514
515	if (*p) {
516		TALLOC_FREE(frame);
517		SAFE_FREE(str);
518		return False;
519	}
520
521 done:
522	mask = amask;
523	init_sec_ace(ace, &sid, atype, mask, aflags);
524	TALLOC_FREE(frame);
525	SAFE_FREE(str);
526	return True;
527}
528
529/* add an ACE to a list of ACEs in a SEC_ACL */
530static bool add_ace(SEC_ACL **the_acl, SEC_ACE *ace)
531{
532	SEC_ACL *new_ace;
533	SEC_ACE *aces;
534	if (! *the_acl) {
535		return (((*the_acl) = make_sec_acl(talloc_tos(), 3, 1, ace))
536			!= NULL);
537	}
538
539	if (!(aces = SMB_CALLOC_ARRAY(SEC_ACE, 1+(*the_acl)->num_aces))) {
540		return False;
541	}
542	memcpy(aces, (*the_acl)->aces, (*the_acl)->num_aces * sizeof(SEC_ACE));
543	memcpy(aces+(*the_acl)->num_aces, ace, sizeof(SEC_ACE));
544	new_ace = make_sec_acl(talloc_tos(),(*the_acl)->revision,1+(*the_acl)->num_aces, aces);
545	SAFE_FREE(aces);
546	(*the_acl) = new_ace;
547	return True;
548}
549
550/* parse a ascii version of a security descriptor */
551static SEC_DESC *sec_desc_parse(TALLOC_CTX *ctx, struct cli_state *cli, char *str)
552{
553	const char *p = str;
554	char *tok;
555	SEC_DESC *ret = NULL;
556	size_t sd_size;
557	DOM_SID *grp_sid=NULL, *owner_sid=NULL;
558	SEC_ACL *dacl=NULL;
559	int revision=1;
560
561	while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) {
562		if (strncmp(tok,"REVISION:", 9) == 0) {
563			revision = strtol(tok+9, NULL, 16);
564			continue;
565		}
566
567		if (strncmp(tok,"OWNER:", 6) == 0) {
568			if (owner_sid) {
569				printf("Only specify owner once\n");
570				goto done;
571			}
572			owner_sid = SMB_CALLOC_ARRAY(DOM_SID, 1);
573			if (!owner_sid ||
574			    !StringToSid(cli, owner_sid, tok+6)) {
575				printf("Failed to parse owner sid\n");
576				goto done;
577			}
578			continue;
579		}
580
581		if (strncmp(tok,"GROUP:", 6) == 0) {
582			if (grp_sid) {
583				printf("Only specify group once\n");
584				goto done;
585			}
586			grp_sid = SMB_CALLOC_ARRAY(DOM_SID, 1);
587			if (!grp_sid ||
588			    !StringToSid(cli, grp_sid, tok+6)) {
589				printf("Failed to parse group sid\n");
590				goto done;
591			}
592			continue;
593		}
594
595		if (strncmp(tok,"ACL:", 4) == 0) {
596			SEC_ACE ace;
597			if (!parse_ace(cli, &ace, tok+4)) {
598				goto done;
599			}
600			if(!add_ace(&dacl, &ace)) {
601				printf("Failed to add ACL %s\n", tok);
602				goto done;
603			}
604			continue;
605		}
606
607		printf("Failed to parse token '%s' in security descriptor,\n", tok);
608		goto done;
609	}
610
611	ret = make_sec_desc(ctx,revision, SEC_DESC_SELF_RELATIVE, owner_sid, grp_sid,
612			    NULL, dacl, &sd_size);
613
614  done:
615	SAFE_FREE(grp_sid);
616	SAFE_FREE(owner_sid);
617
618	return ret;
619}
620
621
622/* print a ascii version of a security descriptor on a FILE handle */
623static void sec_desc_print(struct cli_state *cli, FILE *f, SEC_DESC *sd)
624{
625	fstring sidstr;
626	uint32 i;
627
628	fprintf(f, "REVISION:%d\n", sd->revision);
629	fprintf(f, "CONTROL:0x%x\n", sd->type);
630
631	/* Print owner and group sid */
632
633	if (sd->owner_sid) {
634		SidToString(cli, sidstr, sd->owner_sid);
635	} else {
636		fstrcpy(sidstr, "");
637	}
638
639	fprintf(f, "OWNER:%s\n", sidstr);
640
641	if (sd->group_sid) {
642		SidToString(cli, sidstr, sd->group_sid);
643	} else {
644		fstrcpy(sidstr, "");
645	}
646
647	fprintf(f, "GROUP:%s\n", sidstr);
648
649	/* Print aces */
650	for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
651		SEC_ACE *ace = &sd->dacl->aces[i];
652		fprintf(f, "ACL:");
653		print_ace(cli, f, ace);
654		fprintf(f, "\n");
655	}
656
657}
658
659/*****************************************************
660dump the acls for a file
661*******************************************************/
662static int cacl_dump(struct cli_state *cli, char *filename)
663{
664	int result = EXIT_FAILED;
665	uint16_t fnum = (uint16_t)-1;
666	SEC_DESC *sd;
667
668	if (test_args)
669		return EXIT_OK;
670
671	if (!NT_STATUS_IS_OK(cli_ntcreate(cli, filename, 0, CREATE_ACCESS_READ, 0,
672				FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0, &fnum))) {
673		printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
674		goto done;
675	}
676
677	sd = cli_query_secdesc(cli, fnum, talloc_tos());
678
679	if (!sd) {
680		printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
681		goto done;
682	}
683
684	sec_desc_print(cli, stdout, sd);
685
686	result = EXIT_OK;
687
688done:
689	if (fnum != (uint16_t)-1)
690		cli_close(cli, fnum);
691
692	return result;
693}
694
695/*****************************************************
696Change the ownership or group ownership of a file. Just
697because the NT docs say this can't be done :-). JRA.
698*******************************************************/
699
700static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
701			const char *filename, const char *new_username)
702{
703	uint16_t fnum;
704	DOM_SID sid;
705	SEC_DESC *sd, *old;
706	size_t sd_size;
707
708	if (!NT_STATUS_IS_OK(cli_ntcreate(cli, filename, 0, CREATE_ACCESS_READ, 0,
709				FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0, &fnum))) {
710		printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
711		return EXIT_FAILED;
712	}
713
714	if (!StringToSid(cli, &sid, new_username))
715		return EXIT_PARSE_ERROR;
716
717	old = cli_query_secdesc(cli, fnum, talloc_tos());
718
719	cli_close(cli, fnum);
720
721	if (!old) {
722		printf("owner_set: Failed to query old descriptor\n");
723		return EXIT_FAILED;
724	}
725
726	sd = make_sec_desc(talloc_tos(),old->revision, old->type,
727				(change_mode == REQUEST_CHOWN) ? &sid : NULL,
728				(change_mode == REQUEST_CHGRP) ? &sid : NULL,
729			   NULL, NULL, &sd_size);
730
731	if (!NT_STATUS_IS_OK(cli_ntcreate(cli, filename, 0, WRITE_OWNER_ACCESS, 0,
732			FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0, &fnum))) {
733		printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
734		return EXIT_FAILED;
735	}
736
737	if (!cli_set_secdesc(cli, fnum, sd)) {
738		printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
739		cli_close(cli, fnum);
740		return EXIT_FAILED;
741	}
742
743	cli_close(cli, fnum);
744
745	return EXIT_OK;
746}
747
748
749/* The MSDN is contradictory over the ordering of ACE entries in an
750   ACL.  However NT4 gives a "The information may have been modified
751   by a computer running Windows NT 5.0" if denied ACEs do not appear
752   before allowed ACEs. At
753   http://technet.microsoft.com/en-us/library/cc781716.aspx the
754   canonical order is specified as "Explicit Deny, Explicit Allow,
755   Inherited ACEs unchanged" */
756
757static int ace_compare(SEC_ACE *ace1, SEC_ACE *ace2)
758{
759	if (sec_ace_equal(ace1, ace2))
760		return 0;
761
762	if ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
763			!(ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
764		return 1;
765	if (!(ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
766			(ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
767		return -1;
768	if ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
769			(ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
770		return ace1 - ace2;
771
772	if (ace1->type != ace2->type)
773		return ace2->type - ace1->type;
774
775	if (sid_compare(&ace1->trustee, &ace2->trustee))
776		return sid_compare(&ace1->trustee, &ace2->trustee);
777
778	if (ace1->flags != ace2->flags)
779		return ace1->flags - ace2->flags;
780
781	if (ace1->access_mask != ace2->access_mask)
782		return ace1->access_mask - ace2->access_mask;
783
784	if (ace1->size != ace2->size)
785		return ace1->size - ace2->size;
786
787	return memcmp(ace1, ace2, sizeof(SEC_ACE));
788}
789
790static void sort_acl(SEC_ACL *the_acl)
791{
792	uint32 i;
793	if (!the_acl) return;
794
795	qsort(the_acl->aces, the_acl->num_aces, sizeof(the_acl->aces[0]), QSORT_CAST ace_compare);
796
797	for (i=1;i<the_acl->num_aces;) {
798		if (sec_ace_equal(&the_acl->aces[i-1], &the_acl->aces[i])) {
799			int j;
800			for (j=i; j<the_acl->num_aces-1; j++) {
801				the_acl->aces[j] = the_acl->aces[j+1];
802			}
803			the_acl->num_aces--;
804		} else {
805			i++;
806		}
807	}
808}
809
810/*****************************************************
811set the ACLs on a file given an ascii description
812*******************************************************/
813
814static int cacl_set(struct cli_state *cli, char *filename,
815		    char *the_acl, enum acl_mode mode)
816{
817	uint16_t fnum;
818	SEC_DESC *sd, *old;
819	uint32 i, j;
820	size_t sd_size;
821	int result = EXIT_OK;
822
823	sd = sec_desc_parse(talloc_tos(), cli, the_acl);
824
825	if (!sd) return EXIT_PARSE_ERROR;
826	if (test_args) return EXIT_OK;
827
828	/* The desired access below is the only one I could find that works
829	   with NT4, W2KP and Samba */
830
831	if (!NT_STATUS_IS_OK(cli_ntcreate(cli, filename, 0, CREATE_ACCESS_READ, 0,
832				FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0, &fnum))) {
833		printf("cacl_set failed to open %s: %s\n", filename, cli_errstr(cli));
834		return EXIT_FAILED;
835	}
836
837	old = cli_query_secdesc(cli, fnum, talloc_tos());
838
839	if (!old) {
840		printf("calc_set: Failed to query old descriptor\n");
841		return EXIT_FAILED;
842	}
843
844	cli_close(cli, fnum);
845
846	/* the logic here is rather more complex than I would like */
847	switch (mode) {
848	case SMB_ACL_DELETE:
849		for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
850			bool found = False;
851
852			for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
853				if (sec_ace_equal(&sd->dacl->aces[i],
854						  &old->dacl->aces[j])) {
855					uint32 k;
856					for (k=j; k<old->dacl->num_aces-1;k++) {
857						old->dacl->aces[k] = old->dacl->aces[k+1];
858					}
859					old->dacl->num_aces--;
860					found = True;
861					break;
862				}
863			}
864
865			if (!found) {
866				printf("ACL for ACE:");
867				print_ace(cli, stdout, &sd->dacl->aces[i]);
868				printf(" not found\n");
869			}
870		}
871		break;
872
873	case SMB_ACL_MODIFY:
874		for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
875			bool found = False;
876
877			for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
878				if (sid_equal(&sd->dacl->aces[i].trustee,
879					      &old->dacl->aces[j].trustee)) {
880					old->dacl->aces[j] = sd->dacl->aces[i];
881					found = True;
882				}
883			}
884
885			if (!found) {
886				fstring str;
887
888				SidToString(cli, str,
889					    &sd->dacl->aces[i].trustee);
890				printf("ACL for SID %s not found\n", str);
891			}
892		}
893
894		if (sd->owner_sid) {
895			old->owner_sid = sd->owner_sid;
896		}
897
898		if (sd->group_sid) {
899			old->group_sid = sd->group_sid;
900		}
901
902		break;
903
904	case SMB_ACL_ADD:
905		for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
906			add_ace(&old->dacl, &sd->dacl->aces[i]);
907		}
908		break;
909
910	case SMB_ACL_SET:
911 		old = sd;
912		break;
913	}
914
915	/* Denied ACE entries must come before allowed ones */
916	sort_acl(old->dacl);
917
918	/* Create new security descriptor and set it */
919
920	/* We used to just have "WRITE_DAC_ACCESS" without WRITE_OWNER.
921	   But if we're sending an owner, even if it's the same as the one
922	   that already exists then W2K3 insists we open with WRITE_OWNER access.
923	   I need to check that setting a SD with no owner set works against WNT
924	   and W2K. JRA.
925	*/
926
927	sd = make_sec_desc(talloc_tos(),old->revision, old->type,
928			   old->owner_sid, old->group_sid,
929			   NULL, old->dacl, &sd_size);
930
931	if (!NT_STATUS_IS_OK(cli_ntcreate(cli, filename, 0, WRITE_DAC_ACCESS|WRITE_OWNER_ACCESS, 0,
932			FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, 0x0, 0x0, &fnum))) {
933		printf("cacl_set failed to open %s: %s\n", filename, cli_errstr(cli));
934		return EXIT_FAILED;
935	}
936
937	if (!cli_set_secdesc(cli, fnum, sd)) {
938		printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
939		result = EXIT_FAILED;
940	}
941
942	/* Clean up */
943
944	cli_close(cli, fnum);
945
946	return result;
947}
948
949
950/*****************************************************
951 Return a connection to a server.
952*******************************************************/
953static struct cli_state *connect_one(struct user_auth_info *auth_info,
954				     const char *server, const char *share)
955{
956	struct cli_state *c = NULL;
957	struct sockaddr_storage ss;
958	NTSTATUS nt_status;
959	uint32_t flags = 0;
960
961	zero_sockaddr(&ss);
962
963	if (get_cmdline_auth_info_use_kerberos(auth_info)) {
964		flags |= CLI_FULL_CONNECTION_USE_KERBEROS |
965			 CLI_FULL_CONNECTION_FALLBACK_AFTER_KERBEROS;
966	}
967
968	if (get_cmdline_auth_info_use_machine_account(auth_info) &&
969	    !set_cmdline_auth_info_machine_account_creds(auth_info)) {
970		return NULL;
971	}
972
973	set_cmdline_auth_info_getpass(auth_info);
974
975	nt_status = cli_full_connection(&c, global_myname(), server,
976				&ss, 0,
977				share, "?????",
978				get_cmdline_auth_info_username(auth_info),
979				lp_workgroup(),
980				get_cmdline_auth_info_password(auth_info),
981				flags,
982				get_cmdline_auth_info_signing_state(auth_info),
983				NULL);
984	if (!NT_STATUS_IS_OK(nt_status)) {
985		DEBUG(0,("cli_full_connection failed! (%s)\n", nt_errstr(nt_status)));
986		return NULL;
987	}
988
989	if (get_cmdline_auth_info_smb_encrypt(auth_info)) {
990		nt_status = cli_cm_force_encryption(c,
991					get_cmdline_auth_info_username(auth_info),
992					get_cmdline_auth_info_password(auth_info),
993					lp_workgroup(),
994					share);
995                if (!NT_STATUS_IS_OK(nt_status)) {
996			cli_shutdown(c);
997			c = NULL;
998                }
999	}
1000
1001	return c;
1002}
1003
1004/****************************************************************************
1005  main program
1006****************************************************************************/
1007 int main(int argc, const char *argv[])
1008{
1009	char *share;
1010	int opt;
1011	enum acl_mode mode = SMB_ACL_SET;
1012	static char *the_acl = NULL;
1013	enum chown_mode change_mode = REQUEST_NONE;
1014	int result;
1015	char *path;
1016	char *filename = NULL;
1017	poptContext pc;
1018	struct poptOption long_options[] = {
1019		POPT_AUTOHELP
1020		{ "delete", 'D', POPT_ARG_STRING, NULL, 'D', "Delete an acl", "ACL" },
1021		{ "modify", 'M', POPT_ARG_STRING, NULL, 'M', "Modify an acl", "ACL" },
1022		{ "add", 'a', POPT_ARG_STRING, NULL, 'a', "Add an acl", "ACL" },
1023		{ "set", 'S', POPT_ARG_STRING, NULL, 'S', "Set acls", "ACLS" },
1024		{ "chown", 'C', POPT_ARG_STRING, NULL, 'C', "Change ownership of a file", "USERNAME" },
1025		{ "chgrp", 'G', POPT_ARG_STRING, NULL, 'G', "Change group ownership of a file", "GROUPNAME" },
1026		{ "numeric", 0, POPT_ARG_NONE, &numeric, 1, "Don't resolve sids or masks to names" },
1027		{ "test-args", 't', POPT_ARG_NONE, &test_args, 1, "Test arguments"},
1028		POPT_COMMON_SAMBA
1029		POPT_COMMON_CONNECTION
1030		POPT_COMMON_CREDENTIALS
1031		POPT_TABLEEND
1032	};
1033
1034	struct cli_state *cli;
1035	TALLOC_CTX *frame = talloc_stackframe();
1036	const char *owner_username = "";
1037	char *server;
1038	struct user_auth_info *auth_info;
1039
1040	load_case_tables();
1041
1042
1043	/* set default debug level to 1 regardless of what smb.conf sets */
1044	setup_logging( "smbcacls", True );
1045	DEBUGLEVEL_CLASS[DBGC_ALL] = 1;
1046	dbf = x_stderr;
1047	x_setbuf( x_stderr, NULL );
1048	AllowDebugChange = false;
1049
1050	setlinebuf(stdout);
1051
1052	lp_load(get_dyn_CONFIGFILE(),True,False,False,True);
1053	load_interfaces();
1054
1055	auth_info = user_auth_info_init(frame);
1056	if (auth_info == NULL) {
1057		exit(1);
1058	}
1059	popt_common_set_auth_info(auth_info);
1060
1061	pc = poptGetContext("smbcacls", argc, argv, long_options, 0);
1062
1063	poptSetOtherOptionHelp(pc, "//server1/share1 filename\nACLs look like: "
1064		"'ACL:user:[ALLOWED|DENIED]/flags/permissions'");
1065
1066	while ((opt = poptGetNextOpt(pc)) != -1) {
1067		switch (opt) {
1068		case 'S':
1069			the_acl = smb_xstrdup(poptGetOptArg(pc));
1070			mode = SMB_ACL_SET;
1071			break;
1072
1073		case 'D':
1074			the_acl = smb_xstrdup(poptGetOptArg(pc));
1075			mode = SMB_ACL_DELETE;
1076			break;
1077
1078		case 'M':
1079			the_acl = smb_xstrdup(poptGetOptArg(pc));
1080			mode = SMB_ACL_MODIFY;
1081			break;
1082
1083		case 'a':
1084			the_acl = smb_xstrdup(poptGetOptArg(pc));
1085			mode = SMB_ACL_ADD;
1086			break;
1087
1088		case 'C':
1089			owner_username = poptGetOptArg(pc);
1090			change_mode = REQUEST_CHOWN;
1091			break;
1092
1093		case 'G':
1094			owner_username = poptGetOptArg(pc);
1095			change_mode = REQUEST_CHGRP;
1096			break;
1097		}
1098	}
1099
1100	/* Make connection to server */
1101	if(!poptPeekArg(pc)) {
1102		poptPrintUsage(pc, stderr, 0);
1103		return -1;
1104	}
1105
1106	path = talloc_strdup(frame, poptGetArg(pc));
1107	if (!path) {
1108		return -1;
1109	}
1110
1111	if(!poptPeekArg(pc)) {
1112		poptPrintUsage(pc, stderr, 0);
1113		return -1;
1114	}
1115
1116	filename = talloc_strdup(frame, poptGetArg(pc));
1117	if (!filename) {
1118		return -1;
1119	}
1120
1121	string_replace(path,'/','\\');
1122
1123	server = talloc_strdup(frame, path+2);
1124	if (!server) {
1125		return -1;
1126	}
1127	share = strchr_m(server,'\\');
1128	if (!share) {
1129		printf("Invalid argument: %s\n", share);
1130		return -1;
1131	}
1132
1133	*share = 0;
1134	share++;
1135
1136	if (!test_args) {
1137		cli = connect_one(auth_info, server, share);
1138		if (!cli) {
1139			exit(EXIT_FAILED);
1140		}
1141	} else {
1142		exit(0);
1143	}
1144
1145	string_replace(filename, '/', '\\');
1146	if (filename[0] != '\\') {
1147		filename = talloc_asprintf(frame,
1148				"\\%s",
1149				filename);
1150		if (!filename) {
1151			return -1;
1152		}
1153	}
1154
1155	/* Perform requested action */
1156
1157	if (change_mode != REQUEST_NONE) {
1158		result = owner_set(cli, change_mode, filename, owner_username);
1159	} else if (the_acl) {
1160		result = cacl_set(cli, filename, the_acl, mode);
1161	} else {
1162		result = cacl_dump(cli, filename);
1163	}
1164
1165	TALLOC_FREE(frame);
1166
1167	return result;
1168}
1169