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) {
87 if (cli_lsa_open_policy(&lsa_cli, True,
88 SEC_RIGHTS_MAXIMUM_ALLOWED, &pol)
89 != NT_STATUS_NOPROBLEMO) {
93 got_policy_hnd = True;
99 /* convert a SID to a string, either numeric or username/group */
100 static void SidToString(fstring str, DOM_SID *sid)
103 uint32 *types = NULL;
106 sid_to_string(str, sid);
110 /* Ask LSA to convert the sid to a name */
112 if (!open_policy_hnd() ||
113 cli_lsa_lookup_sids(&lsa_cli, &pol, 1, sid, &names, &types,
114 &num_names) != NT_STATUS_NOPROBLEMO) {
120 fstrcpy(str, names[0]);
127 /* convert a string to a SID, either numeric or username/group */
128 static BOOL StringToSid(DOM_SID *sid, char *str)
130 uint32 *types = NULL;
131 DOM_SID *sids = NULL;
135 if (strncmp(str, "S-", 2) == 0) {
136 return string_to_sid(sid, str);
139 if (!open_policy_hnd() ||
140 cli_lsa_lookup_names(&lsa_cli, &pol, 1, &str, &sids, &types,
141 &num_sids) != NT_STATUS_NOPROBLEMO) {
146 sid_copy(sid, &sids[0]);
157 /* print an ACE on a FILE, using either numeric or ascii representation */
158 static void print_ace(FILE *f, SEC_ACE *ace)
160 struct perm_value *v;
165 SidToString(sidstr, &ace->sid);
167 fprintf(f, "%s:", sidstr);
170 fprintf(f, "%d/%d/0x%08x\n",
171 ace->type, ace->flags, ace->info.mask);
177 if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
178 fprintf(f, "ALLOWED");
179 } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
180 fprintf(f, "DENIED");
182 fprintf(f, "%d", ace->type);
185 /* Not sure what flags can be set in a file ACL */
187 fprintf(f, "/%d/", ace->flags);
189 /* Standard permissions */
191 for (v = standard_values; v->perm; v++) {
192 if (ace->info.mask == v->mask) {
193 fprintf(f, "%s\n", v->perm);
198 /* Special permissions. Print out a hex value if we have
199 leftover bits in the mask. */
201 got_mask = ace->info.mask;
204 for (v = special_values; v->perm; v++) {
205 if ((ace->info.mask & v->mask) == v->mask) {
207 fprintf(f, "%s", v->perm);
209 got_mask &= ~v->mask;
215 fprintf(f, "0x%08x", ace->info.mask);
226 /* parse an ACE in the same format as print_ace() */
227 static BOOL parse_ace(SEC_ACE *ace, char *str)
231 unsigned atype, aflags, amask;
234 struct perm_value *v;
238 if (!p) return False;
242 /* Try to parse numeric form */
244 if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 &&
245 StringToSid(&sid, str)) {
249 /* Try to parse text form */
251 if (!StringToSid(&sid, str)) {
255 if (!next_token(&p, tok, "/", sizeof(fstring))) {
259 if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
260 atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
261 } else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
262 atype = SEC_ACE_TYPE_ACCESS_DENIED;
267 /* Only numeric form accepted for flags at present */
269 if (!(next_token(NULL, tok, "/", sizeof(fstring)) &&
270 sscanf(tok, "%i", &aflags))) {
274 if (!next_token(NULL, tok, "/", sizeof(fstring))) {
278 if (strncmp(tok, "0x", 2) == 0) {
279 if (sscanf(tok, "%i", &amask) != 1) {
285 for (v = standard_values; v->perm; v++) {
286 if (strcmp(tok, v->perm) == 0) {
297 for (v = special_values; v->perm; v++) {
298 if (v->perm[0] == *p) {
304 if (!found) return False;
314 init_sec_ace(ace, &sid, atype, mask, aflags);
318 /* add an ACE to a list of ACEs in a SEC_ACL */
319 static BOOL add_ace(SEC_ACL **the_acl, SEC_ACE *ace)
324 (*the_acl) = make_sec_acl(3, 1, ace);
328 aces = calloc(1+(*the_acl)->num_aces,sizeof(SEC_ACE));
329 memcpy(aces, (*the_acl)->ace, (*the_acl)->num_aces * sizeof(SEC_ACE));
330 memcpy(aces+(*the_acl)->num_aces, ace, sizeof(SEC_ACE));
331 new = make_sec_acl((*the_acl)->revision,1+(*the_acl)->num_aces, aces);
332 free_sec_acl(the_acl);
338 /* parse a ascii version of a security descriptor */
339 static SEC_DESC *sec_desc_parse(char *str)
345 DOM_SID *grp_sid=NULL, *owner_sid=NULL;
349 while (next_token(&p, tok, "\t,\r\n", sizeof(tok))) {
351 if (strncmp(tok,"REVISION:", 9) == 0) {
352 revision = strtol(tok+9, NULL, 16);
356 if (strncmp(tok,"OWNER:", 6) == 0) {
357 owner_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
359 !StringToSid(owner_sid, tok+6)) {
360 printf("Failed to parse owner sid\n");
366 if (strncmp(tok,"GROUP:", 6) == 0) {
367 grp_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
369 !StringToSid(grp_sid, tok+6)) {
370 printf("Failed to parse group sid\n");
376 if (strncmp(tok,"ACL:", 4) == 0) {
378 if (!parse_ace(&ace, tok+4)) {
379 printf("Failed to parse ACL %s\n", tok);
382 if(!add_ace(&dacl, &ace)) {
383 printf("Failed to add ACL %s\n", tok);
389 printf("Failed to parse security descriptor\n");
393 ret = make_sec_desc(revision, owner_sid, grp_sid,
394 NULL, dacl, &sd_size);
398 if (grp_sid) free(grp_sid);
399 if (owner_sid) free(owner_sid);
405 /* print a ascii version of a security descriptor on a FILE handle */
406 static void sec_desc_print(FILE *f, SEC_DESC *sd)
411 printf("REVISION:%d\n", sd->revision);
413 /* Print owner and group sid */
416 SidToString(sidstr, sd->owner_sid);
421 printf("OWNER:%s\n", sidstr);
424 SidToString(sidstr, sd->grp_sid);
429 fprintf(f, "GROUP:%s\n", sidstr);
432 for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
433 SEC_ACE *ace = &sd->dacl->ace[i];
440 /* Some systems seem to require unicode pathnames for the ntcreate&x call
441 despite Samba negotiating ascii filenames. Try with unicode pathname if
442 the ascii version fails. */
444 int do_cli_nt_create(struct cli_state *cli, char *fname, uint32 DesiredAccess)
448 result = cli_nt_create(cli, fname, DesiredAccess);
451 uint32 errnum, nt_rpc_error;
454 cli_error(cli, &errclass, &errnum, &nt_rpc_error);
456 if (errclass == ERRDOS && errnum == ERRbadpath) {
457 result = cli_nt_create_uni(cli, fname, DesiredAccess);
464 /*****************************************************
465 dump the acls for a file
466 *******************************************************/
467 static int cacl_dump(struct cli_state *cli, char *filename)
472 if (test_args) return EXIT_OK;
474 fnum = do_cli_nt_create(cli, filename, 0x20000);
476 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
480 sd = cli_query_secdesc(cli, fnum);
483 printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
487 sec_desc_print(stdout, sd);
491 cli_close(cli, fnum);
496 /*****************************************************
497 Change the ownership or group ownership of a file. Just
498 because the NT docs say this can't be done :-). JRA.
499 *******************************************************/
501 static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
502 char *filename, char *new_username)
509 fnum = do_cli_nt_create(cli, filename,
510 READ_CONTROL_ACCESS | WRITE_DAC_ACCESS
511 | WRITE_OWNER_ACCESS);
514 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
518 if (!StringToSid(&sid, new_username))
519 return EXIT_PARSE_ERROR;
521 old = cli_query_secdesc(cli, fnum);
523 sd = make_sec_desc(old->revision,
524 (change_mode == REQUEST_CHOWN) ? &sid : old->owner_sid,
525 (change_mode == REQUEST_CHGRP) ? &sid : old->grp_sid,
526 NULL, old->dacl, &sd_size);
528 if (!cli_set_secdesc(cli, fnum, sd)) {
529 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
535 cli_close(cli, fnum);
540 /* The MSDN is contradictory over the ordering of ACE entries in an ACL.
541 However NT4 gives a "The information may have been modified by a
542 computer running Windows NT 5.0" if denied ACEs do not appear before
545 static void sort_acl(SEC_ACL *the_acl)
549 BOOL do_denied = True;
551 tmp_ace = (SEC_ACE *)malloc(sizeof(SEC_ACE) * the_acl->num_aces);
553 if (!tmp_ace) return;
557 for (i = 0; i < the_acl->num_aces; i++) {
559 /* Copy denied ACEs */
562 the_acl->ace[i].type == SEC_ACE_TYPE_ACCESS_DENIED) {
563 tmp_ace[ace_ndx] = the_acl->ace[i];
567 /* Copy other ACEs */
570 the_acl->ace[i].type != SEC_ACE_TYPE_ACCESS_DENIED) {
571 tmp_ace[ace_ndx] = the_acl->ace[i];
582 the_acl->ace = tmp_ace;
585 /*****************************************************
586 set the ACLs on a file given an ascii description
587 *******************************************************/
588 static int cacl_set(struct cli_state *cli, char *filename,
589 char *the_acl, enum acl_mode mode)
595 int result = EXIT_OK;
597 sd = sec_desc_parse(the_acl);
599 if (!sd) return EXIT_PARSE_ERROR;
600 if (test_args) return EXIT_OK;
602 /* The desired access below is the only one I could find that works
603 with NT4, W2KP and Samba */
605 fnum = do_cli_nt_create(cli, filename,
606 MAXIMUM_ALLOWED_ACCESS | 0x60000);
609 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
613 old = cli_query_secdesc(cli, fnum);
615 /* the logic here is rather more complex than I would like */
618 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
621 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
622 if (sec_ace_equal(&sd->dacl->ace[i],
623 &old->dacl->ace[j])) {
624 if (j != old->dacl->num_aces-1) {
625 old->dacl->ace[j] = old->dacl->ace[j+1];
627 old->dacl->num_aces--;
628 if (old->dacl->num_aces == 0) {
629 free(old->dacl->ace);
643 SidToString(str, &sd->dacl->ace[i].sid);
644 printf("ACL for SID %s not found\n", str);
650 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
653 for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
654 if (sid_equal(&sd->dacl->ace[i].sid,
655 &old->dacl->ace[j].sid)) {
656 old->dacl->ace[j] = sd->dacl->ace[i];
664 SidToString(str, &sd->dacl->ace[i].sid);
665 printf("ACL for SID %s not found\n", str);
672 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
673 add_ace(&old->dacl, &sd->dacl->ace[i]);
687 /* Denied ACE entries must come before allowed ones */
691 /* Create new security descriptor and set it */
693 sd = make_sec_desc(old->revision, old->owner_sid, old->grp_sid,
694 NULL, old->dacl, &sd_size);
696 if (!cli_set_secdesc(cli, fnum, sd)) {
697 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
698 result = EXIT_FAILED;
706 cli_close(cli, fnum);
712 /*****************************************************
713 return a connection to a server
714 *******************************************************/
715 struct cli_state *connect_one(char *share)
718 struct nmb_name called, calling;
721 extern struct in_addr ipzero;
722 extern pstring global_myname;
724 fstrcpy(server,share+2);
725 share = strchr(server,'\\');
726 if (!share) return NULL;
734 make_nmb_name(&calling, global_myname, 0x0);
735 make_nmb_name(&called , server, 0x20);
740 /* have to open a new connection */
741 if (!(c=cli_initialise(NULL)) || (cli_set_port(c, 139) == 0) ||
742 !cli_connect(c, server_n, &ip)) {
743 DEBUG(0,("Connection to %s failed\n", server_n));
749 if (!cli_session_request(c, &calling, &called)) {
750 DEBUG(0,("session request to %s failed\n", called.name));
753 if (strcmp(called.name, "*SMBSERVER")) {
754 make_nmb_name(&called , "*SMBSERVER", 0x20);
760 DEBUG(4,(" session request ok\n"));
762 if (!cli_negprot(c)) {
763 DEBUG(0,("protocol negotiation failed\n"));
770 char *pass = getpass("Password: ");
772 pstrcpy(password, pass);
776 if (!cli_session_setup(c, username,
777 password, strlen(password),
778 password, strlen(password),
780 DEBUG(0,("session setup failed: %s\n", cli_errstr(c)));
786 DEBUG(4,(" session setup ok\n"));
788 if (!cli_send_tconX(c, share, "?????",
789 password, strlen(password)+1)) {
790 DEBUG(0,("tree connect failed: %s\n", cli_errstr(c)));
796 DEBUG(4,(" tconx ok\n"));
802 static void usage(void)
805 "Usage: smbcacls //server1/share1 filename [options]\n\
807 \t-D <acls> delete an acl\n\
808 \t-M <acls> modify an acl\n\
809 \t-A <acls> add an acl\n\
810 \t-S <acls> set acls\n\
811 \t-C username change ownership of a file\n\
812 \t-G username change group ownership of a file\n\
813 \t-n don't resolve sids or masks to names\n\
816 The username can be of the form username%%password or\n\
817 workgroup\\username%%password.\n\n\
818 An acl is of the form ACL:<SID>:type/flags/mask\n\
819 You can string acls together with spaces, commas or newlines\n\
823 /****************************************************************************
825 ****************************************************************************/
826 int main(int argc,char *argv[])
836 static pstring servicesf = CONFIGFILE;
837 struct cli_state *cli;
839 char *the_acl = NULL;
840 enum chown_mode change_mode = REQUEST_NONE;
847 if (argc < 3 || argv[1][0] == '-') {
849 exit(EXIT_PARSE_ERROR);
852 setup_logging(argv[0],True);
856 all_string_sub(share,"/","\\",0);
862 charset_initialise();
864 lp_load(servicesf,True,False,False);
865 codepage_initialise(lp_client_code_page());
868 if (getenv("USER")) {
869 pstrcpy(username,getenv("USER"));
871 if ((p=strchr(username,'%'))) {
873 pstrcpy(password,p+1);
875 memset(strchr(getenv("USER"), '%') + 1, 'X',
882 while ((opt = getopt(argc, argv, "U:nhS:D:A:M:C:G:t")) != EOF) {
885 pstrcpy(username,optarg);
886 p = strchr(username,'%');
889 pstrcpy(password, p+1);
915 pstrcpy(owner_username,optarg);
916 change_mode = REQUEST_CHOWN;
920 pstrcpy(owner_username,optarg);
921 change_mode = REQUEST_CHGRP;
934 exit(EXIT_PARSE_ERROR);
937 printf("Unknown option %c (%d)\n", (char)opt, opt);
938 exit(EXIT_PARSE_ERROR);
947 exit(EXIT_PARSE_ERROR);
950 /* Make connection to server */
953 cli = connect_one(share);
954 if (!cli) exit(EXIT_FAILED);
962 if (*s == '/') *s = '\\';
967 /* Perform requested action */
969 if (change_mode != REQUEST_NONE) {
970 result = owner_set(cli, change_mode, filename, owner_username);
971 } else if (the_acl) {
972 result = cacl_set(cli, filename, the_acl, mode);
974 result = cacl_dump(cli, filename);