2 Unix SMB/Netbios implementation.
6 Copyright (C) Andrew Tridgell 2000
7 Copyright (C) Tim Potter 2000
8 Copyright (C) Jeremy Allison 2000
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 2 of the License, or
13 (at your option) any later version.
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.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 static fstring password;
28 static pstring username;
29 static pstring owner_username;
30 static fstring server;
34 /* numeric is set when the user wants numeric SIDs and ACEs rather
35 than going via LSA calls to resolve them */
38 enum acl_mode {ACL_SET, ACL_DELETE, ACL_MODIFY, ACL_ADD };
39 enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP};
40 enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
47 /* These values discovered by inspection */
49 static struct perm_value special_values[] = {
59 static struct perm_value standard_values[] = {
60 { "READ", 0x001200a9 },
61 { "CHANGE", 0x001301bf },
62 { "FULL", 0x001f01ff },
66 struct cli_state lsa_cli;
68 struct ntuser_creds creds;
71 /* Open cli connection and policy handle */
73 static BOOL open_policy_hnd(void)
75 creds.pwd.null_pwd = 1;
77 /* Initialise cli LSA connection */
79 if (!lsa_cli.initialised &&
80 !cli_lsa_initialise(&lsa_cli, server, &creds)) {
84 /* Open policy handle */
86 if (!got_policy_hnd) {
88 /* Some systems don't support SEC_RIGHTS_MAXIMUM_ALLOWED,
89 but NT sends 0x2000000 so we might as well do it too. */
91 if (cli_lsa_open_policy(&lsa_cli, True,
92 GENERIC_EXECUTE_ACCESS, &pol)
93 != NT_STATUS_NOPROBLEMO) {
97 got_policy_hnd = True;
103 /* convert a SID to a string, either numeric or username/group */
104 static void SidToString(fstring str, DOM_SID *sid)
107 uint32 *types = NULL;
110 sid_to_string(str, sid);
114 /* Ask LSA to convert the sid to a name */
116 if (!open_policy_hnd() ||
117 cli_lsa_lookup_sids(&lsa_cli, &pol, 1, sid, &names, &types,
118 &num_names) != NT_STATUS_NOPROBLEMO) {
124 fstrcpy(str, names[0]);
131 /* convert a string to a SID, either numeric or username/group */
132 static BOOL StringToSid(DOM_SID *sid, char *str)
134 uint32 *types = NULL;
135 DOM_SID *sids = NULL;
139 if (strncmp(str, "S-", 2) == 0) {
140 return string_to_sid(sid, str);
143 if (!open_policy_hnd() ||
144 cli_lsa_lookup_names(&lsa_cli, &pol, 1, &str, &sids, &types,
145 &num_sids) != NT_STATUS_NOPROBLEMO) {
150 sid_copy(sid, &sids[0]);
161 /* print an ACE on a FILE, using either numeric or ascii representation */
162 static void print_ace(FILE *f, SEC_ACE *ace)
164 struct perm_value *v;
169 SidToString(sidstr, &ace->sid);
171 fprintf(f, "%s:", sidstr);
174 fprintf(f, "%d/%d/0x%08x\n",
175 ace->type, ace->flags, ace->info.mask);
181 if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
182 fprintf(f, "ALLOWED");
183 } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
184 fprintf(f, "DENIED");
186 fprintf(f, "%d", ace->type);
189 /* Not sure what flags can be set in a file ACL */
191 fprintf(f, "/%d/", ace->flags);
193 /* Standard permissions */
195 for (v = standard_values; v->perm; v++) {
196 if (ace->info.mask == v->mask) {
197 fprintf(f, "%s\n", v->perm);
202 /* Special permissions. Print out a hex value if we have
203 leftover bits in the mask. */
205 got_mask = ace->info.mask;
208 for (v = special_values; v->perm; v++) {
209 if ((ace->info.mask & v->mask) == v->mask) {
211 fprintf(f, "%s", v->perm);
213 got_mask &= ~v->mask;
219 fprintf(f, "0x%08x", ace->info.mask);
230 /* parse an ACE in the same format as print_ace() */
231 static BOOL parse_ace(SEC_ACE *ace, char *str)
235 unsigned atype, aflags, amask;
238 struct perm_value *v;
242 if (!p) return False;
246 /* Try to parse numeric form */
248 if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 &&
249 StringToSid(&sid, str)) {
253 /* Try to parse text form */
255 if (!StringToSid(&sid, str)) {
259 if (!next_token(&p, tok, "/", sizeof(fstring))) {
263 if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
264 atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
265 } else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
266 atype = SEC_ACE_TYPE_ACCESS_DENIED;
271 /* Only numeric form accepted for flags at present */
273 if (!(next_token(NULL, tok, "/", sizeof(fstring)) &&
274 sscanf(tok, "%i", &aflags))) {
278 if (!next_token(NULL, tok, "/", sizeof(fstring))) {
282 if (strncmp(tok, "0x", 2) == 0) {
283 if (sscanf(tok, "%i", &amask) != 1) {
289 for (v = standard_values; v->perm; v++) {
290 if (strcmp(tok, v->perm) == 0) {
301 for (v = special_values; v->perm; v++) {
302 if (v->perm[0] == *p) {
308 if (!found) return False;
318 init_sec_ace(ace, &sid, atype, mask, aflags);
322 /* add an ACE to a list of ACEs in a SEC_ACL */
323 static BOOL add_ace(SEC_ACL **the_acl, SEC_ACE *ace)
328 (*the_acl) = make_sec_acl(3, 1, ace);
332 aces = calloc(1+(*the_acl)->num_aces,sizeof(SEC_ACE));
333 memcpy(aces, (*the_acl)->ace, (*the_acl)->num_aces * sizeof(SEC_ACE));
334 memcpy(aces+(*the_acl)->num_aces, ace, sizeof(SEC_ACE));
335 new = make_sec_acl((*the_acl)->revision,1+(*the_acl)->num_aces, aces);
336 free_sec_acl(the_acl);
342 /* parse a ascii version of a security descriptor */
343 static SEC_DESC *sec_desc_parse(char *str)
349 DOM_SID *grp_sid=NULL, *owner_sid=NULL;
353 while (next_token(&p, tok, "\t,\r\n", sizeof(tok))) {
355 if (strncmp(tok,"REVISION:", 9) == 0) {
356 revision = strtol(tok+9, NULL, 16);
360 if (strncmp(tok,"OWNER:", 6) == 0) {
361 owner_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
363 !StringToSid(owner_sid, tok+6)) {
364 printf("Failed to parse owner sid\n");
370 if (strncmp(tok,"GROUP:", 6) == 0) {
371 grp_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
373 !StringToSid(grp_sid, tok+6)) {
374 printf("Failed to parse group sid\n");
380 if (strncmp(tok,"ACL:", 4) == 0) {
382 if (!parse_ace(&ace, tok+4)) {
383 printf("Failed to parse ACL %s\n", tok);
386 if(!add_ace(&dacl, &ace)) {
387 printf("Failed to add ACL %s\n", tok);
393 printf("Failed to parse security descriptor\n");
397 ret = make_sec_desc(revision, owner_sid, grp_sid,
398 NULL, dacl, &sd_size);
402 if (grp_sid) free(grp_sid);
403 if (owner_sid) free(owner_sid);
409 /* print a ascii version of a security descriptor on a FILE handle */
410 static void sec_desc_print(FILE *f, SEC_DESC *sd)
415 printf("REVISION:%d\n", sd->revision);
417 /* Print owner and group sid */
420 SidToString(sidstr, sd->owner_sid);
425 printf("OWNER:%s\n", sidstr);
428 SidToString(sidstr, sd->grp_sid);
433 fprintf(f, "GROUP:%s\n", sidstr);
436 for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
437 SEC_ACE *ace = &sd->dacl->ace[i];
444 /*****************************************************
445 dump the acls for a file
446 *******************************************************/
447 static int cacl_dump(struct cli_state *cli, char *filename)
452 if (test_args) return EXIT_OK;
454 fnum = cli_nt_create(cli, filename, 0x20000);
456 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
460 sd = cli_query_secdesc(cli, fnum);
463 printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
467 sec_desc_print(stdout, sd);
471 cli_close(cli, fnum);
476 /*****************************************************
477 Change the ownership or group ownership of a file. Just
478 because the NT docs say this can't be done :-). JRA.
479 *******************************************************/
481 static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
482 char *filename, char *new_username)
489 fnum = cli_nt_create(cli, filename,
490 READ_CONTROL_ACCESS | WRITE_DAC_ACCESS
491 | WRITE_OWNER_ACCESS);
494 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
498 if (!StringToSid(&sid, new_username))
499 return EXIT_PARSE_ERROR;
501 old = cli_query_secdesc(cli, fnum);
503 sd = make_sec_desc(old->revision,
504 (change_mode == REQUEST_CHOWN) ? &sid : old->owner_sid,
505 (change_mode == REQUEST_CHGRP) ? &sid : old->grp_sid,
506 NULL, old->dacl, &sd_size);
508 if (!cli_set_secdesc(cli, fnum, sd)) {
509 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
515 cli_close(cli, fnum);
520 /* The MSDN is contradictory over the ordering of ACE entries in an ACL.
521 However NT4 gives a "The information may have been modified by a
522 computer running Windows NT 5.0" if denied ACEs do not appear before
525 static void sort_acl(SEC_ACL *the_acl)
529 BOOL do_denied = True;
531 tmp_ace = (SEC_ACE *)malloc(sizeof(SEC_ACE) * the_acl->num_aces);
533 if (!tmp_ace) return;
537 for (i = 0; i < the_acl->num_aces; i++) {
539 /* Copy denied ACEs */
542 the_acl->ace[i].type == SEC_ACE_TYPE_ACCESS_DENIED) {
543 tmp_ace[ace_ndx] = the_acl->ace[i];
547 /* Copy other ACEs */
550 the_acl->ace[i].type != SEC_ACE_TYPE_ACCESS_DENIED) {
551 tmp_ace[ace_ndx] = the_acl->ace[i];
562 the_acl->ace = tmp_ace;
565 /*****************************************************
566 set the ACLs on a file given an ascii description
567 *******************************************************/
568 static int cacl_set(struct cli_state *cli, char *filename,
569 char *the_acl, enum acl_mode mode)
575 int result = EXIT_OK;
577 sd = sec_desc_parse(the_acl);
579 if (!sd) return EXIT_PARSE_ERROR;
580 if (test_args) return EXIT_OK;
582 /* The desired access below is the only one I could find that works
583 with NT4, W2KP and Samba */
585 fnum = cli_nt_create(cli, filename,
586 MAXIMUM_ALLOWED_ACCESS | 0x60000);
589 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
593 old = cli_query_secdesc(cli, fnum);
595 /* the logic here is rather more complex than I would like */
598 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
601 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
602 if (sec_ace_equal(&sd->dacl->ace[i],
603 &old->dacl->ace[j])) {
604 if (j != old->dacl->num_aces-1) {
605 old->dacl->ace[j] = old->dacl->ace[j+1];
607 old->dacl->num_aces--;
608 if (old->dacl->num_aces == 0) {
609 free(old->dacl->ace);
623 SidToString(str, &sd->dacl->ace[i].sid);
624 printf("ACL for SID %s not found\n", str);
630 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
633 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
634 if (sid_equal(&sd->dacl->ace[i].sid,
635 &old->dacl->ace[j].sid)) {
636 old->dacl->ace[j] = sd->dacl->ace[i];
644 SidToString(str, &sd->dacl->ace[i].sid);
645 printf("ACL for SID %s not found\n", str);
652 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
653 add_ace(&old->dacl, &sd->dacl->ace[i]);
667 /* Denied ACE entries must come before allowed ones */
671 /* Create new security descriptor and set it */
673 sd = make_sec_desc(old->revision, old->owner_sid, old->grp_sid,
674 NULL, old->dacl, &sd_size);
676 if (!cli_set_secdesc(cli, fnum, sd)) {
677 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
678 result = EXIT_FAILED;
686 cli_close(cli, fnum);
692 /*****************************************************
693 return a connection to a server
694 *******************************************************/
695 struct cli_state *connect_one(char *share)
698 struct nmb_name called, calling;
701 extern struct in_addr ipzero;
702 extern pstring global_myname;
704 fstrcpy(server,share+2);
705 share = strchr(server,'\\');
706 if (!share) return NULL;
714 make_nmb_name(&calling, global_myname, 0x0);
715 make_nmb_name(&called , server, 0x20);
720 /* have to open a new connection */
721 if (!(c=cli_initialise(NULL)) || (cli_set_port(c, 139) == 0) ||
722 !cli_connect(c, server_n, &ip)) {
723 DEBUG(0,("Connection to %s failed\n", server_n));
729 if (!cli_session_request(c, &calling, &called)) {
730 DEBUG(0,("session request to %s failed\n", called.name));
733 if (strcmp(called.name, "*SMBSERVER")) {
734 make_nmb_name(&called , "*SMBSERVER", 0x20);
740 DEBUG(4,(" session request ok\n"));
742 if (!cli_negprot(c)) {
743 DEBUG(0,("protocol negotiation failed\n"));
750 char *pass = getpass("Password: ");
752 pstrcpy(password, pass);
756 if (!cli_session_setup(c, username,
757 password, strlen(password),
758 password, strlen(password),
760 DEBUG(0,("session setup failed: %s\n", cli_errstr(c)));
766 DEBUG(4,(" session setup ok\n"));
768 if (!cli_send_tconX(c, share, "?????",
769 password, strlen(password)+1)) {
770 DEBUG(0,("tree connect failed: %s\n", cli_errstr(c)));
776 DEBUG(4,(" tconx ok\n"));
782 static void usage(void)
785 "Usage: smbcacls //server1/share1 filename [options]\n\
787 \t-D <acls> delete an acl\n\
788 \t-M <acls> modify an acl\n\
789 \t-A <acls> add an acl\n\
790 \t-S <acls> set acls\n\
791 \t-C username change ownership of a file\n\
792 \t-G username change group ownership of a file\n\
793 \t-n don't resolve sids or masks to names\n\
796 The username can be of the form username%%password or\n\
797 workgroup\\username%%password.\n\n\
798 An acl is of the form ACL:<SID>:type/flags/mask\n\
799 You can string acls together with spaces, commas or newlines\n\
803 /****************************************************************************
805 ****************************************************************************/
806 int main(int argc,char *argv[])
816 static pstring servicesf = CONFIGFILE;
817 struct cli_state *cli;
819 char *the_acl = NULL;
820 enum chown_mode change_mode = REQUEST_NONE;
827 if (argc < 3 || argv[1][0] == '-') {
829 exit(EXIT_PARSE_ERROR);
832 setup_logging(argv[0],True);
836 all_string_sub(share,"/","\\",0);
842 charset_initialise();
844 lp_load(servicesf,True,False,False);
845 codepage_initialise(lp_client_code_page());
848 if (getenv("USER")) {
849 pstrcpy(username,getenv("USER"));
851 if ((p=strchr(username,'%'))) {
853 pstrcpy(password,p+1);
855 memset(strchr(getenv("USER"), '%') + 1, 'X',
862 while ((opt = getopt(argc, argv, "U:nhS:D:A:M:C:G:t")) != EOF) {
865 pstrcpy(username,optarg);
866 p = strchr(username,'%');
869 pstrcpy(password, p+1);
895 pstrcpy(owner_username,optarg);
896 change_mode = REQUEST_CHOWN;
900 pstrcpy(owner_username,optarg);
901 change_mode = REQUEST_CHGRP;
914 exit(EXIT_PARSE_ERROR);
917 printf("Unknown option %c (%d)\n", (char)opt, opt);
918 exit(EXIT_PARSE_ERROR);
927 exit(EXIT_PARSE_ERROR);
930 /* Make connection to server */
933 cli = connect_one(share);
934 if (!cli) exit(EXIT_FAILED);
942 if (*s == '/') *s = '\\';
947 /* Perform requested action */
949 if (change_mode != REQUEST_NONE) {
950 result = owner_set(cli, change_mode, filename, owner_username);
951 } else if (the_acl) {
952 result = cacl_set(cli, filename, the_acl, mode);
954 result = cacl_dump(cli, filename);