Replace cli_rpc_pipe_close by a talloc destructor on rpc_pipe_struct
[kai/samba.git] / source3 / utils / smbcacls.c
index f18899d01d82d6bd9ce081b764e36ae68853c7b3..af14c622dc9616e685945eded431f88b97c84a58 100644 (file)
@@ -1,13 +1,15 @@
 /* 
-   Unix SMB/Netbios implementation.
+   Unix SMB/CIFS implementation.
    ACL get/set utility
-   Version 3.0
    
    Copyright (C) Andrew Tridgell 2000
+   Copyright (C) Tim Potter      2000
+   Copyright (C) Jeremy Allison  2000
+   Copyright (C) Jelmer Vernooij 2003
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define NO_SYSLOG
-
 #include "includes.h"
 
-static fstring password;
-static fstring username;
-static int got_pass;
+static int test_args = False;
+
+#define CREATE_ACCESS_READ READ_CONTROL_ACCESS
 
 /* numeric is set when the user wants numeric SIDs and ACEs rather
    than going via LSA calls to resolve them */
-static int numeric;
+static int numeric = False;
+
+enum acl_mode {SMB_ACL_SET, SMB_ACL_DELETE, SMB_ACL_MODIFY, SMB_ACL_ADD };
+enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP};
+enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
+
+struct perm_value {
+       const char *perm;
+       uint32 mask;
+};
+
+/* These values discovered by inspection */
+
+static const struct perm_value special_values[] = {
+       { "R", 0x00120089 },
+       { "W", 0x00120116 },
+       { "X", 0x001200a0 },
+       { "D", 0x00010000 },
+       { "P", 0x00040000 },
+       { "O", 0x00080000 },
+       { NULL, 0 },
+};
+
+static const struct perm_value standard_values[] = {
+       { "READ",   0x001200a9 },
+       { "CHANGE", 0x001301bf },
+       { "FULL",   0x001f01ff },
+       { NULL, 0 },
+};
+
+/* Open cli connection and policy handle */
+
+static NTSTATUS cli_lsa_lookup_sid(struct cli_state *cli,
+                                  const DOM_SID *sid,
+                                  TALLOC_CTX *mem_ctx,
+                                  enum lsa_SidType *type,
+                                  char **domain, char **name)
+{
+       uint16 orig_cnum = cli->cnum;
+       struct rpc_pipe_client *p;
+       struct policy_handle handle;
+       NTSTATUS status;
+       TALLOC_CTX *frame = talloc_stackframe();
+       enum lsa_SidType *types;
+       char **domains;
+       char **names;
+
+       if (!cli_send_tconX(cli, "IPC$", "?????", "", 0)) {
+               return cli_nt_error(cli);
+       }
+
+       p = cli_rpc_pipe_open_noauth(cli, PI_LSARPC, &status);
+       if (p == NULL) {
+               goto fail;
+       }
+
+       status = rpccli_lsa_open_policy(p, talloc_tos(), True,
+                                       GENERIC_EXECUTE_ACCESS, &handle);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       status = rpccli_lsa_lookup_sids(p, talloc_tos(), &handle, 1, sid,
+                                       &domains, &names, &types);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       *type = types[0];
+       *domain = talloc_move(mem_ctx, &domains[0]);
+       *name = talloc_move(mem_ctx, &names[0]);
+
+       status = NT_STATUS_OK;
+ fail:
+       TALLOC_FREE(p);
+       cli_tdis(cli);
+       cli->cnum = orig_cnum;
+       TALLOC_FREE(frame);
+       return status;
+}
+
+static NTSTATUS cli_lsa_lookup_name(struct cli_state *cli,
+                                   const char *name,
+                                   enum lsa_SidType *type,
+                                   DOM_SID *sid)
+{
+       uint16 orig_cnum = cli->cnum;
+       struct rpc_pipe_client *p;
+       struct policy_handle handle;
+       NTSTATUS status;
+       TALLOC_CTX *frame = talloc_stackframe();
+       DOM_SID *sids;
+       enum lsa_SidType *types;
+
+       if (!cli_send_tconX(cli, "IPC$", "?????", "", 0)) {
+               return cli_nt_error(cli);
+       }
+
+       p = cli_rpc_pipe_open_noauth(cli, PI_LSARPC, &status);
+       if (p == NULL) {
+               goto fail;
+       }
+
+       status = rpccli_lsa_open_policy(p, talloc_tos(), True,
+                                       GENERIC_EXECUTE_ACCESS, &handle);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       status = rpccli_lsa_lookup_names(p, talloc_tos(), &handle, 1, &name,
+                                        NULL, 1, &sids, &types);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       *type = types[0];
+       *sid = sids[0];
+
+       status = NT_STATUS_OK;
+ fail:
+       TALLOC_FREE(p);
+       cli_tdis(cli);
+       cli->cnum = orig_cnum;
+       TALLOC_FREE(frame);
+       return status;
+}
 
 /* convert a SID to a string, either numeric or username/group */
-static void SidToString(fstring str, DOM_SID *sid)
+static void SidToString(struct cli_state *cli, fstring str, const DOM_SID *sid)
 {
+       char *domain = NULL;
+       char *name = NULL;
+       enum lsa_SidType type;
+       NTSTATUS status;
+
+       sid_to_fstring(str, sid);
+
        if (numeric) {
-               sid_to_string(str, sid);
-       } else {
-               printf("need to add LSA lookups\n");
-               sid_to_string(str, sid);
+               return;
+       }
+
+       status = cli_lsa_lookup_sid(cli, sid, talloc_tos(), &type,
+                                   &domain, &name);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               return;
        }
+
+       slprintf(str, sizeof(fstring) - 1, "%s%s%s",
+                domain, lp_winbind_separator(), name);
+       
 }
 
 /* convert a string to a SID, either numeric or username/group */
-static BOOL StringToSid(DOM_SID *sid, fstring str)
+static bool StringToSid(struct cli_state *cli, DOM_SID *sid, const char *str)
 {
-       if (strncmp(str,"S-", 2) == 0) {
+       enum lsa_SidType type;
+
+       if (strncmp(str, "S-", 2) == 0) {
                return string_to_sid(sid, str);
-       } else {
-               printf("need to add LSA lookups\n");
-               return False;
        }
+
+       return NT_STATUS_IS_OK(cli_lsa_lookup_name(cli, str, &type, sid));
 }
 
 
 /* print an ACE on a FILE, using either numeric or ascii representation */
-static void print_ace(FILE *f, SEC_ACE *ace)
+static void print_ace(struct cli_state *cli, FILE *f, SEC_ACE *ace)
 {
+       const struct perm_value *v;
        fstring sidstr;
-       char *perm;
+       int do_print = 0;
+       uint32 got_mask;
 
-       SidToString(sidstr, &ace->sid);
+       SidToString(cli, sidstr, &ace->trustee);
 
        fprintf(f, "%s:", sidstr);
 
        if (numeric) {
-               fprintf(f, "%x/%x/%08x\n", 
-                       ace->type, ace->flags, ace->info.mask);
+               fprintf(f, "%d/%d/0x%08x", 
+                       ace->type, ace->flags, ace->access_mask);
                return;
        }
 
-       /* this interpretation is almost certainly wrong, Tim, please
-          have a look at these */
-       if (ace->info.mask == 0x001f01ff) {
-               perm = "F";
-       } else if (ace->info.mask == 0x001301bf) {
-               perm = "C";
-       } else if (ace->info.mask == 0x001200a9) {
-               perm = "R";
-       } else if (ace->info.mask == 0x00000000) {
-               perm = "N";
+       /* Ace type */
+
+       if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
+               fprintf(f, "ALLOWED");
+       } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
+               fprintf(f, "DENIED");
        } else {
-               perm = "?";
+               fprintf(f, "%d", ace->type);
+       }
+
+       /* Not sure what flags can be set in a file ACL */
+
+       fprintf(f, "/%d/", ace->flags);
+
+       /* Standard permissions */
+
+       for (v = standard_values; v->perm; v++) {
+               if (ace->access_mask == v->mask) {
+                       fprintf(f, "%s", v->perm);
+                       return;
+               }
        }
 
-       fprintf(f,"%s\n", perm);
+       /* Special permissions.  Print out a hex value if we have
+          leftover bits in the mask. */
+
+       got_mask = ace->access_mask;
+
+ again:
+       for (v = special_values; v->perm; v++) {
+               if ((ace->access_mask & v->mask) == v->mask) {
+                       if (do_print) {
+                               fprintf(f, "%s", v->perm);
+                       }
+                       got_mask &= ~v->mask;
+               }
+       }
+
+       if (!do_print) {
+               if (got_mask != 0) {
+                       fprintf(f, "0x%08x", ace->access_mask);
+               } else {
+                       do_print = 1;
+                       goto again;
+               }
+       }
 }
 
 
 /* parse an ACE in the same format as print_ace() */
-static BOOL parse_ace(SEC_ACE *ace, char *str)
+static bool parse_ace(struct cli_state *cli, SEC_ACE *ace,
+                     const char *orig_str)
 {
        char *p;
-       unsigned atype, aflags, amask;
+       const char *cp;
+       char *tok;
+       unsigned int atype = 0;
+       unsigned int aflags = 0;
+       unsigned int amask = 0;
+       DOM_SID sid;
+       SEC_ACCESS mask;
+       const struct perm_value *v;
+       char *str = SMB_STRDUP(orig_str);
+       TALLOC_CTX *frame = talloc_stackframe();
+
+       if (!str) {
+               TALLOC_FREE(frame);
+               return False;
+       }
+
        ZERO_STRUCTP(ace);
-       p = strchr(str,':');
-       if (!p) return False;
-       *p = 0;
-       if (sscanf(p+1, "%x/%x/%08x", 
-                  &atype, &aflags, &amask) != 3 ||
-           !StringToSid(&ace->sid, str)) {
+       p = strchr_m(str,':');
+       if (!p) {
+               printf("ACE '%s': missing ':'.\n", orig_str);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
                return False;
        }
-       ace->type = atype;
-       ace->flags = aflags;
-       ace->info.mask = amask;
-       return True;
-}
+       *p = '\0';
+       p++;
+       /* Try to parse numeric form */
+
+       if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 &&
+           StringToSid(cli, &sid, str)) {
+               goto done;
+       }
+
+       /* Try to parse text form */
 
+       if (!StringToSid(cli, &sid, str)) {
+               printf("ACE '%s': failed to convert '%s' to SID\n",
+                       orig_str, str);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
+               return False;
+       }
+
+       cp = p;
+       if (!next_token_talloc(frame, &cp, &tok, "/")) {
+               printf("ACE '%s': failed to find '/' character.\n",
+                       orig_str);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
+               return False;
+       }
+
+       if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
+               atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
+       } else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
+               atype = SEC_ACE_TYPE_ACCESS_DENIED;
+       } else {
+               printf("ACE '%s': missing 'ALLOWED' or 'DENIED' entry at '%s'\n",
+                       orig_str, tok);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
+               return False;
+       }
+
+       /* Only numeric form accepted for flags at present */
+
+       if (!(next_token_talloc(frame, &cp, &tok, "/") &&
+             sscanf(tok, "%i", &aflags))) {
+               printf("ACE '%s': bad integer flags entry at '%s'\n",
+                       orig_str, tok);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
+               return False;
+       }
+
+       if (!next_token_talloc(frame, &cp, &tok, "/")) {
+               printf("ACE '%s': missing / at '%s'\n",
+                       orig_str, tok);
+               SAFE_FREE(str);
+               TALLOC_FREE(frame);
+               return False;
+       }
 
+       if (strncmp(tok, "0x", 2) == 0) {
+               if (sscanf(tok, "%i", &amask) != 1) {
+                       printf("ACE '%s': bad hex number at '%s'\n",
+                               orig_str, tok);
+                       SAFE_FREE(str);
+                       TALLOC_FREE(frame);
+                       return False;
+               }
+               goto done;
+       }
+
+       for (v = standard_values; v->perm; v++) {
+               if (strcmp(tok, v->perm) == 0) {
+                       amask = v->mask;
+                       goto done;
+               }
+       }
+
+       p = tok;
+
+       while(*p) {
+               bool found = False;
+
+               for (v = special_values; v->perm; v++) {
+                       if (v->perm[0] == *p) {
+                               amask |= v->mask;
+                               found = True;
+                       }
+               }
+
+               if (!found) {
+                       printf("ACE '%s': bad permission value at '%s'\n",
+                               orig_str, p);
+                       SAFE_FREE(str);
+                       TALLOC_FREE(frame);
+                       return False;
+               }
+               p++;
+       }
+
+       if (*p) {
+               TALLOC_FREE(frame);
+               SAFE_FREE(str);
+               return False;
+       }
+
+ done:
+       mask = amask;
+       init_sec_ace(ace, &sid, atype, mask, aflags);
+       TALLOC_FREE(frame);
+       SAFE_FREE(str);
+       return True;
+}
 
 /* add an ACE to a list of ACEs in a SEC_ACL */
-static BOOL add_ace(SEC_ACL **acl, SEC_ACE *ace)
+static bool add_ace(SEC_ACL **the_acl, SEC_ACE *ace)
 {
-       if (! *acl) {
-               *acl = (SEC_ACL *)calloc(1, sizeof(*acl));
-               if (! *acl) return False;
-               (*acl)->revision = 3;
+       SEC_ACL *new_ace;
+       SEC_ACE *aces;
+       if (! *the_acl) {
+               return (((*the_acl) = make_sec_acl(talloc_tos(), 3, 1, ace))
+                       != NULL);
        }
 
-       (*acl)->ace = Realloc((*acl)->ace,(1+((*acl)->num_aces))*sizeof(SEC_ACE));
-       if (!(*acl)->ace) return False;
-       memcpy(&((*acl)->ace[(*acl)->num_aces]), ace, sizeof(SEC_ACE));
-       (*acl)->num_aces++;
+       if (!(aces = SMB_CALLOC_ARRAY(SEC_ACE, 1+(*the_acl)->num_aces))) {
+               return False;
+       }
+       memcpy(aces, (*the_acl)->aces, (*the_acl)->num_aces * sizeof(SEC_ACE));
+       memcpy(aces+(*the_acl)->num_aces, ace, sizeof(SEC_ACE));
+       new_ace = make_sec_acl(talloc_tos(),(*the_acl)->revision,1+(*the_acl)->num_aces, aces);
+       SAFE_FREE(aces);
+       (*the_acl) = new_ace;
        return True;
 }
 
 /* parse a ascii version of a security descriptor */
-static SEC_DESC *sec_desc_parse(char *str)
+static SEC_DESC *sec_desc_parse(TALLOC_CTX *ctx, struct cli_state *cli, char *str)
 {
-       char *p = str;
-       fstring tok;
-       SEC_DESC *ret;
-       int sd_size;
+       const char *p = str;
+       char *tok;
+       SEC_DESC *ret = NULL;
+       size_t sd_size;
        DOM_SID *grp_sid=NULL, *owner_sid=NULL;
-       SEC_ACL *dacl=NULL, *sacl=NULL;
+       SEC_ACL *dacl=NULL;
        int revision=1;
-       int type=0x8004;
-
-       while (next_token(&p, tok, " \t,\r\n", sizeof(tok))) {
 
+       while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) {
                if (strncmp(tok,"REVISION:", 9) == 0) {
                        revision = strtol(tok+9, NULL, 16);
-               }
-
-               if (strncmp(tok,"TYPE:", 5) == 0) {
-                       type = strtol(tok+5, NULL, 16);
+                       continue;
                }
 
                if (strncmp(tok,"OWNER:", 6) == 0) {
-                       owner_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
+                       if (owner_sid) {
+                               printf("Only specify owner once\n");
+                               goto done;
+                       }
+                       owner_sid = SMB_CALLOC_ARRAY(DOM_SID, 1);
                        if (!owner_sid ||
-                           !StringToSid(owner_sid, tok+6)) {
+                           !StringToSid(cli, owner_sid, tok+6)) {
                                printf("Failed to parse owner sid\n");
-                               return NULL;
+                               goto done;
                        }
+                       continue;
                }
 
                if (strncmp(tok,"GROUP:", 6) == 0) {
-                       grp_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
+                       if (grp_sid) {
+                               printf("Only specify group once\n");
+                               goto done;
+                       }
+                       grp_sid = SMB_CALLOC_ARRAY(DOM_SID, 1);
                        if (!grp_sid ||
-                           !StringToSid(grp_sid, tok+6)) {
+                           !StringToSid(cli, grp_sid, tok+6)) {
                                printf("Failed to parse group sid\n");
-                               return NULL;
+                               goto done;
                        }
+                       continue;
                }
 
-               if (strncmp(tok,"DACL:", 5) == 0) {
+               if (strncmp(tok,"ACL:", 4) == 0) {
                        SEC_ACE ace;
-                       if (!parse_ace(&ace, tok+5) || 
-                           !add_ace(&dacl, &ace)) {
-                               printf("Failed to parse DACL\n");
-                               return NULL;
+                       if (!parse_ace(cli, &ace, tok+4)) {
+                               goto done;
                        }
-               }
-
-               if (strncmp(tok,"SACL:", 5) == 0) {
-                       SEC_ACE ace;
-                       if (!parse_ace(&ace, tok+5) || 
-                           !add_ace(&sacl, &ace)) {
-                               printf("Failed to parse SACL\n");
-                               return NULL;
+                       if(!add_ace(&dacl, &ace)) {
+                               printf("Failed to add ACL %s\n", tok);
+                               goto done;
                        }
+                       continue;
                }
+
+               printf("Failed to parse token '%s' in security descriptor,\n", tok);
+               goto done;
        }
 
-       ret = make_sec_desc(revision, type, owner_sid, grp_sid, 
-                           sacl, dacl, &sd_size);
+       ret = make_sec_desc(ctx,revision, SEC_DESC_SELF_RELATIVE, owner_sid, grp_sid,
+                           NULL, dacl, &sd_size);
 
-       free_sec_acl(&sacl);
-       free_sec_acl(&dacl);
-       if (grp_sid) free(grp_sid);
-       if (owner_sid) free(owner_sid);
+  done:
+       SAFE_FREE(grp_sid);
+       SAFE_FREE(owner_sid);
 
        return ret;
 }
 
 
 /* print a ascii version of a security descriptor on a FILE handle */
-static void sec_desc_print(FILE *f, SEC_DESC *sd)
+static void sec_desc_print(struct cli_state *cli, FILE *f, SEC_DESC *sd)
 {
        fstring sidstr;
-       int i;
+       uint32 i;
 
-       printf("REVISION:%x TYPE:%x\n", sd->revision, sd->type);
+       fprintf(f, "REVISION:%d\n", sd->revision);
 
        /* Print owner and group sid */
 
        if (sd->owner_sid) {
-               SidToString(sidstr, sd->owner_sid);
+               SidToString(cli, sidstr, sd->owner_sid);
        } else {
                fstrcpy(sidstr, "");
        }
 
-       printf("OWNER:%s\n", sidstr);
+       fprintf(f, "OWNER:%s\n", sidstr);
 
-       if (sd->grp_sid) {
-               SidToString(sidstr, sd->grp_sid);
+       if (sd->group_sid) {
+               SidToString(cli, sidstr, sd->group_sid);
        } else {
                fstrcpy(sidstr, "");
        }
@@ -226,264 +525,493 @@ static void sec_desc_print(FILE *f, SEC_DESC *sd)
 
        /* Print aces */
        for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
-               SEC_ACE *ace = &sd->dacl->ace[i];
-               fprintf(f, "DACL:");
-               print_ace(f, ace);
+               SEC_ACE *ace = &sd->dacl->aces[i];
+               fprintf(f, "ACL:");
+               print_ace(cli, f, ace);
+               fprintf(f, "\n");
        }
 
-       for (i = 0; sd->sacl && i < sd->sacl->num_aces; i++) {
-               SEC_ACE *ace = &sd->sacl->ace[i];
-               fstring sidstr;
-
-               SidToString(sidstr, &ace->sid);
-
-               fprintf(f, "SACL:%s:%x:%x:%08x\n", sidstr, 
-                       ace->type, ace->flags,
-                       ace->info.mask);
-       }
 }
 
-
 /***************************************************** 
 dump the acls for a file
 *******************************************************/
-static void cacl_dump(struct cli_state *cli, char *filename)
+static int cacl_dump(struct cli_state *cli, char *filename)
 {
-       int fnum;
+       int result = EXIT_FAILED;
+       int fnum = -1;
        SEC_DESC *sd;
 
-       fnum = cli_open(cli, filename, O_RDONLY, 0);
+       if (test_args) 
+               return EXIT_OK;
+
+       fnum = cli_nt_create(cli, filename, CREATE_ACCESS_READ);
+
        if (fnum == -1) {
-               printf("Failed to open %s\n", filename);
-               return;
+               printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
+               goto done;
        }
 
-       sd = cli_query_secdesc(cli, fnum);
+       sd = cli_query_secdesc(cli, fnum, talloc_tos());
 
        if (!sd) {
-               printf("ERROR: secdesc query failed\n");
-               return;
+               printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
+               goto done;
        }
 
-       sec_desc_print(stdout, sd);
+       sec_desc_print(cli, stdout, sd);
 
-       free_sec_desc(&sd);
+       result = EXIT_OK;
 
-       cli_close(cli, fnum);
+done:
+       if (fnum != -1)
+               cli_close(cli, fnum);
+
+       return result;
 }
 
 /***************************************************** 
-set the ACLs on a file given an ascii description
+Change the ownership or group ownership of a file. Just
+because the NT docs say this can't be done :-). JRA.
 *******************************************************/
-static void cacl_set(struct cli_state *cli, char *filename, char *set_acl)
+
+static int owner_set(struct cli_state *cli, enum chown_mode change_mode, 
+                       const char *filename, const char *new_username)
 {
        int fnum;
-       SEC_DESC *sd;
+       DOM_SID sid;
+       SEC_DESC *sd, *old;
+       size_t sd_size;
 
-       sd = sec_desc_parse(set_acl);
-       if (!sd) {
-               printf("Failed to parse security descriptor\n");
-               return;
-       }
+       fnum = cli_nt_create(cli, filename, CREATE_ACCESS_READ);
 
-       fnum = cli_open(cli, filename, O_RDONLY, 0);
        if (fnum == -1) {
-               printf("Failed to open %s\n", filename);
-               return;
+               printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
+               return EXIT_FAILED;
        }
 
-       /* sec_desc_print(stdout, sd); */
+       if (!StringToSid(cli, &sid, new_username))
+               return EXIT_PARSE_ERROR;
 
-       if (!cli_set_secdesc(cli, fnum, sd)) {
-               printf("ERROR: secdesc set failed\n");
-               return;
+       old = cli_query_secdesc(cli, fnum, talloc_tos());
+
+       cli_close(cli, fnum);
+
+       if (!old) {
+               printf("owner_set: Failed to query old descriptor\n");
+               return EXIT_FAILED;
+       }
+
+       sd = make_sec_desc(talloc_tos(),old->revision, old->type,
+                               (change_mode == REQUEST_CHOWN) ? &sid : NULL,
+                               (change_mode == REQUEST_CHGRP) ? &sid : NULL,
+                          NULL, NULL, &sd_size);
+
+       fnum = cli_nt_create(cli, filename, WRITE_OWNER_ACCESS);
+
+       if (fnum == -1) {
+               printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
+               return EXIT_FAILED;
        }
 
-       free_sec_desc(&sd);
+       if (!cli_set_secdesc(cli, fnum, sd)) {
+               printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
+       }
 
        cli_close(cli, fnum);
+
+       return EXIT_OK;
+}
+
+
+/* The MSDN is contradictory over the ordering of ACE entries in an ACL.
+   However NT4 gives a "The information may have been modified by a
+   computer running Windows NT 5.0" if denied ACEs do not appear before
+   allowed ACEs. */
+
+static int ace_compare(SEC_ACE *ace1, SEC_ACE *ace2)
+{
+       if (sec_ace_equal(ace1, ace2)) 
+               return 0;
+
+       if (ace1->type != ace2->type) 
+               return ace2->type - ace1->type;
+
+       if (sid_compare(&ace1->trustee, &ace2->trustee)) 
+               return sid_compare(&ace1->trustee, &ace2->trustee);
+
+       if (ace1->flags != ace2->flags) 
+               return ace1->flags - ace2->flags;
+
+       if (ace1->access_mask != ace2->access_mask) 
+               return ace1->access_mask - ace2->access_mask;
+
+       if (ace1->size != ace2->size) 
+               return ace1->size - ace2->size;
+
+       return memcmp(ace1, ace2, sizeof(SEC_ACE));
 }
 
+static void sort_acl(SEC_ACL *the_acl)
+{
+       uint32 i;
+       if (!the_acl) return;
+
+       qsort(the_acl->aces, the_acl->num_aces, sizeof(the_acl->aces[0]), QSORT_CAST ace_compare);
+
+       for (i=1;i<the_acl->num_aces;) {
+               if (sec_ace_equal(&the_acl->aces[i-1], &the_acl->aces[i])) {
+                       int j;
+                       for (j=i; j<the_acl->num_aces-1; j++) {
+                               the_acl->aces[j] = the_acl->aces[j+1];
+                       }
+                       the_acl->num_aces--;
+               } else {
+                       i++;
+               }
+       }
+}
 
 /***************************************************** 
-return a connection to a server
+set the ACLs on a file given an ascii description
 *******************************************************/
-struct cli_state *connect_one(char *share)
+static int cacl_set(struct cli_state *cli, char *filename, 
+                   char *the_acl, enum acl_mode mode)
 {
-       struct cli_state *c;
-       struct nmb_name called, calling;
-       char *server_n;
-       fstring server;
-       struct in_addr ip;
-       extern struct in_addr ipzero;
-       extern pstring global_myname;
-
-       fstrcpy(server,share+2);
-       share = strchr(server,'\\');
-       if (!share) return NULL;
-       *share = 0;
-       share++;
+       int fnum;
+       SEC_DESC *sd, *old;
+       uint32 i, j;
+       size_t sd_size;
+       int result = EXIT_OK;
 
-       server_n = server;
-       
-       ip = ipzero;
+       sd = sec_desc_parse(talloc_tos(), cli, the_acl);
 
-       make_nmb_name(&calling, global_myname, 0x0);
-       make_nmb_name(&called , server, 0x20);
+       if (!sd) return EXIT_PARSE_ERROR;
+       if (test_args) return EXIT_OK;
 
- again:
-       ip = ipzero;
+       /* The desired access below is the only one I could find that works
+          with NT4, W2KP and Samba */
 
-       /* have to open a new connection */
-       if (!(c=cli_initialise(NULL)) || (cli_set_port(c, 139) == 0) ||
-           !cli_connect(c, server_n, &ip)) {
-               DEBUG(0,("Connection to %s failed\n", server_n));
-               return NULL;
+       fnum = cli_nt_create(cli, filename, CREATE_ACCESS_READ);
+
+       if (fnum == -1) {
+               printf("cacl_set failed to open %s: %s\n", filename, cli_errstr(cli));
+               return EXIT_FAILED;
        }
 
-       if (!cli_session_request(c, &calling, &called)) {
-               DEBUG(0,("session request to %s failed\n", called.name));
-               cli_shutdown(c);
-               if (strcmp(called.name, "*SMBSERVER")) {
-                       make_nmb_name(&called , "*SMBSERVER", 0x20);
-                       goto again;
+       old = cli_query_secdesc(cli, fnum, talloc_tos());
+
+       if (!old) {
+               printf("calc_set: Failed to query old descriptor\n");
+               return EXIT_FAILED;
+       }
+
+       cli_close(cli, fnum);
+
+       /* the logic here is rather more complex than I would like */
+       switch (mode) {
+       case SMB_ACL_DELETE:
+               for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+                       bool found = False;
+
+                       for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+                               if (sec_ace_equal(&sd->dacl->aces[i],
+                                                 &old->dacl->aces[j])) {
+                                       uint32 k;
+                                       for (k=j; k<old->dacl->num_aces-1;k++) {
+                                               old->dacl->aces[k] = old->dacl->aces[k+1];
+                                       }
+                                       old->dacl->num_aces--;
+                                       found = True;
+                                       break;
+                               }
+                       }
+
+                       if (!found) {
+                               printf("ACL for ACE:"); 
+                               print_ace(cli, stdout, &sd->dacl->aces[i]);
+                               printf(" not found\n");
+                       }
                }
-               return NULL;
+               break;
+
+       case SMB_ACL_MODIFY:
+               for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+                       bool found = False;
+
+                       for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+                               if (sid_equal(&sd->dacl->aces[i].trustee,
+                                             &old->dacl->aces[j].trustee)) {
+                                       old->dacl->aces[j] = sd->dacl->aces[i];
+                                       found = True;
+                               }
+                       }
+
+                       if (!found) {
+                               fstring str;
+
+                               SidToString(cli, str,
+                                           &sd->dacl->aces[i].trustee);
+                               printf("ACL for SID %s not found\n", str);
+                       }
+               }
+
+               if (sd->owner_sid) {
+                       old->owner_sid = sd->owner_sid;
+               }
+
+               if (sd->group_sid) { 
+                       old->group_sid = sd->group_sid;
+               }
+
+               break;
+
+       case SMB_ACL_ADD:
+               for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+                       add_ace(&old->dacl, &sd->dacl->aces[i]);
+               }
+               break;
+
+       case SMB_ACL_SET:
+               old = sd;
+               break;
        }
 
-       DEBUG(4,(" session request ok\n"));
+       /* Denied ACE entries must come before allowed ones */
+       sort_acl(old->dacl);
 
-       if (!cli_negprot(c)) {
-               DEBUG(0,("protocol negotiation failed\n"));
-               cli_shutdown(c);
-               return NULL;
+       /* Create new security descriptor and set it */
+
+       /* We used to just have "WRITE_DAC_ACCESS" without WRITE_OWNER.
+          But if we're sending an owner, even if it's the same as the one
+          that already exists then W2K3 insists we open with WRITE_OWNER access.
+          I need to check that setting a SD with no owner set works against WNT
+          and W2K. JRA.
+       */
+
+       sd = make_sec_desc(talloc_tos(),old->revision, old->type,
+                          old->owner_sid, old->group_sid,
+                          NULL, old->dacl, &sd_size);
+
+       fnum = cli_nt_create(cli, filename, WRITE_DAC_ACCESS|WRITE_OWNER_ACCESS);
+
+       if (fnum == -1) {
+               printf("cacl_set failed to open %s: %s\n", filename, cli_errstr(cli));
+               return EXIT_FAILED;
+       }
+
+       if (!cli_set_secdesc(cli, fnum, sd)) {
+               printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
+               result = EXIT_FAILED;
        }
 
-       if (!got_pass) {
+       /* Clean up */
+
+       cli_close(cli, fnum);
+
+       return result;
+}
+
+
+/*****************************************************
+ Return a connection to a server.
+*******************************************************/
+static struct cli_state *connect_one(const char *server, const char *share)
+{
+       struct cli_state *c = NULL;
+       struct sockaddr_storage ss;
+       NTSTATUS nt_status;
+       zero_addr(&ss);
+
+       if (!get_cmdline_auth_info_got_pass()) {
                char *pass = getpass("Password: ");
                if (pass) {
-                       pstrcpy(password, pass);
+                       set_cmdline_auth_info_password(pass);
                }
        }
 
-       if (!cli_session_setup(c, username, 
-                              password, strlen(password),
-                              password, strlen(password),
-                              lp_workgroup())) {
-               DEBUG(0,("session setup failed: %s\n", cli_errstr(c)));
+       nt_status = cli_full_connection(&c, global_myname(), server, 
+                               &ss, 0,
+                               share, "?????",
+                               get_cmdline_auth_info_username(),
+                               lp_workgroup(),
+                               get_cmdline_auth_info_password(),
+                               get_cmdline_auth_info_use_kerberos() ? CLI_FULL_CONNECTION_USE_KERBEROS : 0,
+                               get_cmdline_auth_info_signing_state(),
+                               NULL);
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               DEBUG(0,("cli_full_connection failed! (%s)\n", nt_errstr(nt_status)));
                return NULL;
        }
 
-       DEBUG(4,(" session setup ok\n"));
-
-       if (!cli_send_tconX(c, share, "?????",
-                           password, strlen(password)+1)) {
-               DEBUG(0,("tree connect failed: %s\n", cli_errstr(c)));
-               cli_shutdown(c);
-               return NULL;
+       if (get_cmdline_auth_info_smb_encrypt()) {
+               nt_status = cli_cm_force_encryption(c,
+                                       get_cmdline_auth_info_username(),
+                                       get_cmdline_auth_info_password(),
+                                       lp_workgroup(),
+                                       share);
+                if (!NT_STATUS_IS_OK(nt_status)) {
+                       cli_shutdown(c);
+                       c = NULL;
+                }
        }
 
-       DEBUG(4,(" tconx ok\n"));
-
        return c;
 }
 
-
-static void usage(void)
-{
-       printf(
-"Usage:\n\
-  smbcacls //server1/share1 filename\n\n");
-}
-
 /****************************************************************************
   main program
 ****************************************************************************/
- int main(int argc,char *argv[])
+ int main(int argc, const char *argv[])
 {
        char *share;
-       char *filename;
-       extern char *optarg;
-       extern int optind;
-       extern FILE *dbf;
        int opt;
-       char *p;
-       int seed;
-       static pstring servicesf = CONFIGFILE;
+       enum acl_mode mode = SMB_ACL_SET;
+       static char *the_acl = NULL;
+       enum chown_mode change_mode = REQUEST_NONE;
+       int result;
+       char *path;
+       char *filename = NULL;
+       poptContext pc;
+       struct poptOption long_options[] = {
+               POPT_AUTOHELP
+               { "delete", 'D', POPT_ARG_STRING, NULL, 'D', "Delete an acl", "ACL" },
+               { "modify", 'M', POPT_ARG_STRING, NULL, 'M', "Modify an acl", "ACL" },
+               { "add", 'a', POPT_ARG_STRING, NULL, 'a', "Add an acl", "ACL" },
+               { "set", 'S', POPT_ARG_STRING, NULL, 'S', "Set acls", "ACLS" },
+               { "chown", 'C', POPT_ARG_STRING, NULL, 'C', "Change ownership of a file", "USERNAME" },
+               { "chgrp", 'G', POPT_ARG_STRING, NULL, 'G', "Change group ownership of a file", "GROUPNAME" },
+               { "numeric", 0, POPT_ARG_NONE, &numeric, True, "Don't resolve sids or masks to names" },
+               { "test-args", 't', POPT_ARG_NONE, &test_args, True, "Test arguments"},
+               POPT_COMMON_SAMBA
+               POPT_COMMON_CREDENTIALS
+               { NULL }
+       };
+
        struct cli_state *cli;
-       char *set_acl = NULL;
+       TALLOC_CTX *frame = talloc_stackframe();
+       const char *owner_username = "";
+       char *server;
 
-       setlinebuf(stdout);
+       load_case_tables();
 
-       dbf = stderr;
 
-       if (argc < 4 || argv[1][0] == '-') {
-               usage();
-               exit(1);
-       }
+       /* set default debug level to 1 regardless of what smb.conf sets */
+       setup_logging( "smbcacls", True );
+       DEBUGLEVEL_CLASS[DBGC_ALL] = 1;
+       dbf = x_stderr;
+       x_setbuf( x_stderr, NULL );
 
-       setup_logging(argv[0],True);
+       setlinebuf(stdout);
 
-       share = argv[1];
-       filename = argv[2];
-       all_string_sub(share,"/","\\",0);
+       lp_load(get_dyn_CONFIGFILE(),True,False,False,True);
+       load_interfaces();
 
-       argc -= 2;
-       argv += 2;
+       pc = poptGetContext("smbcacls", argc, argv, long_options, 0);
 
-       TimeInit();
-       charset_initialise();
+       poptSetOtherOptionHelp(pc, "//server1/share1 filename\nACLs look like: "
+               "'ACL:user:[ALLOWED|DENIED]/flags/permissions'");
 
-       lp_load(servicesf,True,False,False);
-       codepage_initialise(lp_client_code_page());
-       load_interfaces();
+       while ((opt = poptGetNextOpt(pc)) != -1) {
+               switch (opt) {
+               case 'S':
+                       the_acl = smb_xstrdup(poptGetOptArg(pc));
+                       mode = SMB_ACL_SET;
+                       break;
 
-       if (getenv("USER")) {
-               pstrcpy(username,getenv("USER"));
-       }
+               case 'D':
+                       the_acl = smb_xstrdup(poptGetOptArg(pc));
+                       mode = SMB_ACL_DELETE;
+                       break;
 
-       seed = time(NULL);
+               case 'M':
+                       the_acl = smb_xstrdup(poptGetOptArg(pc));
+                       mode = SMB_ACL_MODIFY;
+                       break;
 
-       while ((opt = getopt(argc, argv, "U:nhS:")) != EOF) {
-               switch (opt) {
-               case 'U':
-                       pstrcpy(username,optarg);
-                       p = strchr(username,'%');
-                       if (p) {
-                               *p = 0;
-                               pstrcpy(password, p+1);
-                               got_pass = 1;
-                       }
+               case 'a':
+                       the_acl = smb_xstrdup(poptGetOptArg(pc));
+                       mode = SMB_ACL_ADD;
                        break;
 
-               case 'S':
-                       set_acl = optarg;
+               case 'C':
+                       owner_username = poptGetOptArg(pc);
+                       change_mode = REQUEST_CHOWN;
                        break;
 
-               case 'n':
-                       numeric = 1;
+               case 'G':
+                       owner_username = poptGetOptArg(pc);
+                       change_mode = REQUEST_CHGRP;
                        break;
+               }
+       }
+
+       /* Make connection to server */
+       if(!poptPeekArg(pc)) {
+               poptPrintUsage(pc, stderr, 0);
+               return -1;
+       }
+
+       path = talloc_strdup(frame, poptGetArg(pc));
+       if (!path) {
+               return -1;
+       }
 
-               case 'h':
-                       usage();
-                       exit(1);
-               default:
-                       printf("Unknown option %c (%d)\n", (char)opt, opt);
-                       exit(1);
+       if(!poptPeekArg(pc)) {
+               poptPrintUsage(pc, stderr, 0);
+               return -1;
+       }
+
+       filename = talloc_strdup(frame, poptGetArg(pc));
+       if (!filename) {
+               return -1;
+       }
+
+       string_replace(path,'/','\\');
+
+       server = talloc_strdup(frame, path+2);
+       if (!server) {
+               return -1;
+       }
+       share = strchr_m(server,'\\');
+       if (!share) {
+               printf("Invalid argument: %s\n", share);
+               return -1;
+       }
+
+       *share = 0;
+       share++;
+
+       if (!test_args) {
+               cli = connect_one(server, share);
+               if (!cli) {
+                       exit(EXIT_FAILED);
                }
+       } else {
+               exit(0);
        }
 
-       argc -= optind;
-       argv += optind;
+       string_replace(filename, '/', '\\');
+       if (filename[0] != '\\') {
+               filename = talloc_asprintf(frame,
+                               "\\%s",
+                               filename);
+               if (!filename) {
+                       return -1;
+               }
+       }
 
-       cli = connect_one(share);
-       if (!cli) exit(1);
+       /* Perform requested action */
 
-       if (set_acl) {
-               cacl_set(cli, filename, set_acl);
+       if (change_mode != REQUEST_NONE) {
+               result = owner_set(cli, change_mode, filename, owner_username);
+       } else if (the_acl) {
+               result = cacl_set(cli, filename, the_acl, mode);
        } else {
-               cacl_dump(cli, filename);
+               result = cacl_dump(cli, filename);
        }
 
-       return(0);
+       TALLOC_FREE(frame);
+
+       return result;
 }