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 /* Some systems seem to require unicode pathnames for the ntcreate&x call
445 despite Samba negotiating ascii filenames. Try with unicode pathname if
446 the ascii version fails. */
448 int do_cli_nt_create(struct cli_state *cli, char *fname, uint32 DesiredAccess)
452 result = cli_nt_create(cli, fname, DesiredAccess);
455 uint32 errnum, nt_rpc_error;
458 cli_error(cli, &errclass, &errnum, &nt_rpc_error);
460 if (errclass == ERRDOS && errnum == ERRbadpath) {
461 result = cli_nt_create_uni(cli, fname, DesiredAccess);
468 /*****************************************************
469 dump the acls for a file
470 *******************************************************/
471 static int cacl_dump(struct cli_state *cli, char *filename)
476 if (test_args) return EXIT_OK;
478 fnum = do_cli_nt_create(cli, filename, 0x20000);
480 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
484 sd = cli_query_secdesc(cli, fnum);
487 printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
491 sec_desc_print(stdout, sd);
495 cli_close(cli, fnum);
500 /*****************************************************
501 Change the ownership or group ownership of a file. Just
502 because the NT docs say this can't be done :-). JRA.
503 *******************************************************/
505 static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
506 char *filename, char *new_username)
513 fnum = do_cli_nt_create(cli, filename,
514 READ_CONTROL_ACCESS | WRITE_DAC_ACCESS
515 | WRITE_OWNER_ACCESS);
518 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
522 if (!StringToSid(&sid, new_username))
523 return EXIT_PARSE_ERROR;
525 old = cli_query_secdesc(cli, fnum);
527 sd = make_sec_desc(old->revision,
528 (change_mode == REQUEST_CHOWN) ? &sid : old->owner_sid,
529 (change_mode == REQUEST_CHGRP) ? &sid : old->grp_sid,
530 NULL, old->dacl, &sd_size);
532 if (!cli_set_secdesc(cli, fnum, sd)) {
533 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
539 cli_close(cli, fnum);
544 /* The MSDN is contradictory over the ordering of ACE entries in an ACL.
545 However NT4 gives a "The information may have been modified by a
546 computer running Windows NT 5.0" if denied ACEs do not appear before
549 static void sort_acl(SEC_ACL *the_acl)
553 BOOL do_denied = True;
555 tmp_ace = (SEC_ACE *)malloc(sizeof(SEC_ACE) * the_acl->num_aces);
557 if (!tmp_ace) return;
561 for (i = 0; i < the_acl->num_aces; i++) {
563 /* Copy denied ACEs */
566 the_acl->ace[i].type == SEC_ACE_TYPE_ACCESS_DENIED) {
567 tmp_ace[ace_ndx] = the_acl->ace[i];
571 /* Copy other ACEs */
574 the_acl->ace[i].type != SEC_ACE_TYPE_ACCESS_DENIED) {
575 tmp_ace[ace_ndx] = the_acl->ace[i];
586 the_acl->ace = tmp_ace;
589 /*****************************************************
590 set the ACLs on a file given an ascii description
591 *******************************************************/
592 static int cacl_set(struct cli_state *cli, char *filename,
593 char *the_acl, enum acl_mode mode)
599 int result = EXIT_OK;
601 sd = sec_desc_parse(the_acl);
603 if (!sd) return EXIT_PARSE_ERROR;
604 if (test_args) return EXIT_OK;
606 /* The desired access below is the only one I could find that works
607 with NT4, W2KP and Samba */
609 fnum = do_cli_nt_create(cli, filename,
610 MAXIMUM_ALLOWED_ACCESS | 0x60000);
613 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
617 old = cli_query_secdesc(cli, fnum);
619 /* the logic here is rather more complex than I would like */
622 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
625 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
626 if (sec_ace_equal(&sd->dacl->ace[i],
627 &old->dacl->ace[j])) {
628 if (j != old->dacl->num_aces-1) {
629 old->dacl->ace[j] = old->dacl->ace[j+1];
631 old->dacl->num_aces--;
632 if (old->dacl->num_aces == 0) {
633 free(old->dacl->ace);
647 SidToString(str, &sd->dacl->ace[i].sid);
648 printf("ACL for SID %s not found\n", str);
654 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
657 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
658 if (sid_equal(&sd->dacl->ace[i].sid,
659 &old->dacl->ace[j].sid)) {
660 old->dacl->ace[j] = sd->dacl->ace[i];
668 SidToString(str, &sd->dacl->ace[i].sid);
669 printf("ACL for SID %s not found\n", str);
676 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
677 add_ace(&old->dacl, &sd->dacl->ace[i]);
691 /* Denied ACE entries must come before allowed ones */
695 /* Create new security descriptor and set it */
697 sd = make_sec_desc(old->revision, old->owner_sid, old->grp_sid,
698 NULL, old->dacl, &sd_size);
700 if (!cli_set_secdesc(cli, fnum, sd)) {
701 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
702 result = EXIT_FAILED;
710 cli_close(cli, fnum);
716 /*****************************************************
717 return a connection to a server
718 *******************************************************/
719 struct cli_state *connect_one(char *share)
722 struct nmb_name called, calling;
725 extern struct in_addr ipzero;
726 extern pstring global_myname;
728 fstrcpy(server,share+2);
729 share = strchr(server,'\\');
730 if (!share) return NULL;
738 make_nmb_name(&calling, global_myname, 0x0);
739 make_nmb_name(&called , server, 0x20);
744 /* have to open a new connection */
745 if (!(c=cli_initialise(NULL)) || (cli_set_port(c, 139) == 0) ||
746 !cli_connect(c, server_n, &ip)) {
747 DEBUG(0,("Connection to %s failed\n", server_n));
753 if (!cli_session_request(c, &calling, &called)) {
754 DEBUG(0,("session request to %s failed\n", called.name));
757 if (strcmp(called.name, "*SMBSERVER")) {
758 make_nmb_name(&called , "*SMBSERVER", 0x20);
764 DEBUG(4,(" session request ok\n"));
766 if (!cli_negprot(c)) {
767 DEBUG(0,("protocol negotiation failed\n"));
774 char *pass = getpass("Password: ");
776 pstrcpy(password, pass);
780 if (!cli_session_setup(c, username,
781 password, strlen(password),
782 password, strlen(password),
784 DEBUG(0,("session setup failed: %s\n", cli_errstr(c)));
790 DEBUG(4,(" session setup ok\n"));
792 if (!cli_send_tconX(c, share, "?????",
793 password, strlen(password)+1)) {
794 DEBUG(0,("tree connect failed: %s\n", cli_errstr(c)));
800 DEBUG(4,(" tconx ok\n"));
806 static void usage(void)
809 "Usage: smbcacls //server1/share1 filename [options]\n\
811 \t-D <acls> delete an acl\n\
812 \t-M <acls> modify an acl\n\
813 \t-A <acls> add an acl\n\
814 \t-S <acls> set acls\n\
815 \t-C username change ownership of a file\n\
816 \t-G username change group ownership of a file\n\
817 \t-n don't resolve sids or masks to names\n\
820 The username can be of the form username%%password or\n\
821 workgroup\\username%%password.\n\n\
822 An acl is of the form ACL:<SID>:type/flags/mask\n\
823 You can string acls together with spaces, commas or newlines\n\
827 /****************************************************************************
829 ****************************************************************************/
830 int main(int argc,char *argv[])
840 static pstring servicesf = CONFIGFILE;
841 struct cli_state *cli;
843 char *the_acl = NULL;
844 enum chown_mode change_mode = REQUEST_NONE;
851 if (argc < 3 || argv[1][0] == '-') {
853 exit(EXIT_PARSE_ERROR);
856 setup_logging(argv[0],True);
860 all_string_sub(share,"/","\\",0);
866 charset_initialise();
868 lp_load(servicesf,True,False,False);
869 codepage_initialise(lp_client_code_page());
872 if (getenv("USER")) {
873 pstrcpy(username,getenv("USER"));
875 if ((p=strchr(username,'%'))) {
877 pstrcpy(password,p+1);
879 memset(strchr(getenv("USER"), '%') + 1, 'X',
886 while ((opt = getopt(argc, argv, "U:nhS:D:A:M:C:G:t")) != EOF) {
889 pstrcpy(username,optarg);
890 p = strchr(username,'%');
893 pstrcpy(password, p+1);
919 pstrcpy(owner_username,optarg);
920 change_mode = REQUEST_CHOWN;
924 pstrcpy(owner_username,optarg);
925 change_mode = REQUEST_CHGRP;
938 exit(EXIT_PARSE_ERROR);
941 printf("Unknown option %c (%d)\n", (char)opt, opt);
942 exit(EXIT_PARSE_ERROR);
951 exit(EXIT_PARSE_ERROR);
954 /* Make connection to server */
957 cli = connect_one(share);
958 if (!cli) exit(EXIT_FAILED);
966 if (*s == '/') *s = '\\';
971 /* Perform requested action */
973 if (change_mode != REQUEST_NONE) {
974 result = owner_set(cli, change_mode, filename, owner_username);
975 } else if (the_acl) {
976 result = cacl_set(cli, filename, the_acl, mode);
978 result = cacl_dump(cli, filename);