Convert names to sids when modifying ACLs. Convert sids to names when
[kai/samba.git] / source3 / utils / smbcacls.c
1 /* 
2    Unix SMB/Netbios implementation.
3    ACL get/set utility
4    Version 3.0
5    
6    Copyright (C) Andrew Tridgell 2000
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24
25 static fstring password;
26 static fstring username;
27 static fstring server;
28 static int got_pass;
29 static int test_args;
30
31 /* numeric is set when the user wants numeric SIDs and ACEs rather
32    than going via LSA calls to resolve them */
33 static int numeric;
34
35 enum acl_mode {ACL_SET, ACL_DELETE, ACL_MODIFY, ACL_ADD};
36
37 struct perm_value {
38         char *perm;
39         uint32 mask;
40 };
41
42 /* These values discovered by inspection */
43
44 static struct perm_value special_values[] = {
45         { "R", 0x00120089 },
46         { "W", 0x00120116 },
47         { "X", 0x001200a0 },
48         { "D", 0x00010000 },
49         { "P", 0x00040000 },
50         { "O", 0x00080000 },
51         { NULL, 0 },
52 };
53
54 static struct perm_value standard_values[] = {
55         { "READ",   0x001200a9 },
56         { "CHANGE", 0x001301bf },
57         { "FULL",   0x001f01ff },
58         { NULL, 0 },
59 };
60
61 /* convert a SID to a string, either numeric or username/group */
62 static void SidToString(fstring str, DOM_SID *sid)
63 {
64         struct cli_state cli;
65         POLICY_HND pol;
66         struct ntuser_creds creds;
67         char **names;
68         uint32 *types;
69         int num_names;
70
71         ZERO_STRUCT(creds);             
72         ZERO_STRUCT(cli);
73         ZERO_STRUCT(pol);
74
75         creds.pwd.null_pwd = 1;
76
77         if (numeric || !cli_lsa_initialise(&cli, server, &creds) ||
78             cli_lsa_open_policy(&cli, True, SEC_RIGHTS_MAXIMUM_ALLOWED,
79                                 &pol) != NT_STATUS_NOPROBLEMO ||
80             cli_lsa_lookup_sids(&cli, &pol, 1, sid, &names, &types, 
81                                 &num_names) != NT_STATUS_NOPROBLEMO) {
82                 sid_to_string(str, sid);
83                 goto done;
84         }
85
86         fstrcpy(str, names[0]);
87
88         safe_free(names[0]);
89         safe_free(names);
90         safe_free(types);
91
92  done:
93         if (cli.initialised) {
94                 cli_lsa_close(&cli, &pol);
95                 cli_lsa_shutdown(&cli);
96         }
97 }
98
99 /* convert a string to a SID, either numeric or username/group */
100 static BOOL StringToSid(DOM_SID *sid, fstring str)
101 {
102         uint32 *types;
103         struct cli_state cli;
104         struct ntuser_creds creds;
105         POLICY_HND pol;
106         int num_sids;
107         BOOL result = True;
108         DOM_SID *sids;
109         
110         /* Short cut */
111
112         if (strncmp(str, "S-", 2) == 0) {
113                 return string_to_sid(sid, str);
114         }
115
116         ZERO_STRUCT(creds);
117         ZERO_STRUCT(cli);
118         ZERO_STRUCT(pol);
119
120         creds.pwd.null_pwd = 1;      
121
122         if (!cli_lsa_initialise(&cli, server, &creds) ||
123             cli_lsa_open_policy(&cli, True, SEC_RIGHTS_MAXIMUM_ALLOWED,
124                                 &pol) != NT_STATUS_NOPROBLEMO ||
125             cli_lsa_lookup_names(&cli, &pol, 1, &str, &sids, &types, 
126                                  &num_sids) != NT_STATUS_NOPROBLEMO) {
127                 result = string_to_sid(sid, str);
128                 goto done;
129         }
130
131         sid_copy(sid, &sids[0]);
132
133         safe_free(sids);
134         safe_free(types);
135
136  done:
137         if (cli.initialised) {
138                 cli_lsa_close(&cli, &pol);
139                 cli_lsa_shutdown(&cli);
140         }
141
142         return result;
143 }
144
145
146 /* print an ACE on a FILE, using either numeric or ascii representation */
147 static void print_ace(FILE *f, SEC_ACE *ace)
148 {
149         struct perm_value *v;
150         fstring sidstr;
151         int do_print = 0;
152         uint32 got_mask;
153
154         SidToString(sidstr, &ace->sid);
155
156         fprintf(f, "%s:", sidstr);
157
158         if (numeric) {
159                 fprintf(f, "%d/%d/0x%08x\n", 
160                         ace->type, ace->flags, ace->info.mask);
161                 return;
162         }
163
164         /* Ace type */
165
166         if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
167                 fprintf(f, "ALLOWED");
168         } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
169                 fprintf(f, "DENIED");
170         } else {
171                 fprintf(f, "%d", ace->type);
172         }
173
174         /* Not sure what flags can be set in a file ACL */
175
176         fprintf(f, "/%d/", ace->flags);
177
178         /* Standard permissions */
179
180         for (v = standard_values; v->perm; v++) {
181                 if (ace->info.mask == v->mask) {
182                         fprintf(f, "%s\n", v->perm);
183                         return;
184                 }
185         }
186
187         /* Special permissions.  Print out a hex value if we have
188            leftover bits in the mask. */
189
190         got_mask = ace->info.mask;
191
192  again:
193         for (v = special_values; v->perm; v++) {
194                 if ((ace->info.mask & v->mask) == v->mask) {
195                         if (do_print) {
196                                 fprintf(f, "%s", v->perm);
197                         }
198                         got_mask &= ~v->mask;
199                 }
200         }
201
202         if (!do_print) {
203                 if (got_mask != 0) {
204                         fprintf(f, "0x%08x", ace->info.mask);
205                 } else {
206                         do_print = 1;
207                         goto again;
208                 }
209         }
210
211         fprintf(f, "\n");
212 }
213
214
215 /* parse an ACE in the same format as print_ace() */
216 static BOOL parse_ace(SEC_ACE *ace, char *str)
217 {
218         char *p;
219         fstring tok;
220         unsigned atype, aflags, amask;
221         DOM_SID sid;
222         SEC_ACCESS mask;
223         struct perm_value *v;
224
225         ZERO_STRUCTP(ace);
226         p = strchr(str,':');
227         if (!p) return False;
228         *p = '\0';
229         p++;
230
231         /* Try to parse numeric form */
232
233         if (sscanf(p, "%i/%i/%i", &atype, &aflags, &amask) == 3 &&
234             StringToSid(&sid, str)) {
235                 goto done;
236         }
237
238         /* Try to parse text form */
239
240         if (!StringToSid(&sid, str)) {
241                 return False;
242         }
243
244         if (!next_token(&p, tok, "/", sizeof(fstring))) {
245                 return False;
246         }
247
248         if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
249                 atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
250         } else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
251                 atype = SEC_ACE_TYPE_ACCESS_DENIED;
252         } else {
253                 return False;
254         }
255
256         /* Only numeric form accepted for flags at present */
257
258         if (!(next_token(NULL, tok, "/", sizeof(fstring)) &&
259               sscanf(tok, "%i", &aflags))) {
260                 return False;
261         }
262
263         if (!next_token(NULL, tok, "/", sizeof(fstring))) {
264                 return False;
265         }
266
267         if (strncmp(tok, "0x", 2) == 0) {
268                 if (sscanf(tok, "%i", &amask) != 1) {
269                         return False;
270                 }
271                 goto done;
272         }
273
274         for (v = standard_values; v->perm; v++) {
275                 if (strcmp(tok, v->perm) == 0) {
276                         amask = v->mask;
277                         goto done;
278                 }
279         }
280
281         p = tok;
282
283         while(*p) {
284                 BOOL found = False;
285
286                 for (v = special_values; v->perm; v++) {
287                         if (v->perm[0] == *p) {
288                                 amask |= v->mask;
289                                 found = True;
290                         }
291                 }
292
293                 if (!found) return False;
294                 p++;
295         }
296
297         if (*p) {
298                 return False;
299         }
300
301  done:
302         mask.mask = amask;
303         init_sec_ace(ace, &sid, atype, mask, aflags);
304         return True;
305 }
306
307 /* add an ACE to a list of ACEs in a SEC_ACL */
308 static BOOL add_ace(SEC_ACL **acl, SEC_ACE *ace)
309 {
310         SEC_ACL *new;
311         SEC_ACE *aces;
312         if (! *acl) {
313                 (*acl) = make_sec_acl(3, 1, ace);
314                 return True;
315         }
316
317         aces = calloc(1+(*acl)->num_aces,sizeof(SEC_ACE));
318         memcpy(aces, (*acl)->ace, (*acl)->num_aces * sizeof(SEC_ACE));
319         memcpy(aces+(*acl)->num_aces, ace, sizeof(SEC_ACE));
320         new = make_sec_acl((*acl)->revision,1+(*acl)->num_aces, aces);
321         free_sec_acl(acl);
322         free(aces);
323         (*acl) = new;
324         return True;
325 }
326
327 /* parse a ascii version of a security descriptor */
328 static SEC_DESC *sec_desc_parse(char *str)
329 {
330         char *p = str;
331         fstring tok;
332         SEC_DESC *ret;
333         unsigned sd_size;
334         DOM_SID *grp_sid=NULL, *owner_sid=NULL;
335         SEC_ACL *dacl=NULL;
336         int revision=1;
337
338         while (next_token(&p, tok, " \t,\r\n", sizeof(tok))) {
339
340                 if (strncmp(tok,"REVISION:", 9) == 0) {
341                         revision = strtol(tok+9, NULL, 16);
342                         continue;
343                 }
344
345                 if (strncmp(tok,"OWNER:", 6) == 0) {
346                         owner_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
347                         if (!owner_sid ||
348                             !StringToSid(owner_sid, tok+6)) {
349                                 printf("Failed to parse owner sid\n");
350                                 return NULL;
351                         }
352                         continue;
353                 }
354
355                 if (strncmp(tok,"GROUP:", 6) == 0) {
356                         grp_sid = (DOM_SID *)calloc(1, sizeof(DOM_SID));
357                         if (!grp_sid ||
358                             !StringToSid(grp_sid, tok+6)) {
359                                 printf("Failed to parse group sid\n");
360                                 return NULL;
361                         }
362                         continue;
363                 }
364
365                 if (strncmp(tok,"ACL:", 4) == 0) {
366                         SEC_ACE ace;
367                         if (!parse_ace(&ace, tok+4) || 
368                             !add_ace(&dacl, &ace)) {
369                                 printf("Failed to parse ACL\n");
370                                 return NULL;
371                         }
372                         continue;
373                 }
374
375                 printf("Failed to parse security descriptor\n");
376                 return NULL;
377         }
378
379         ret = make_sec_desc(revision, owner_sid, grp_sid, 
380                             NULL, dacl, &sd_size);
381
382         free_sec_acl(&dacl);
383
384         if (grp_sid) free(grp_sid);
385         if (owner_sid) free(owner_sid);
386
387         return ret;
388 }
389
390
391 /* print a ascii version of a security descriptor on a FILE handle */
392 static void sec_desc_print(FILE *f, SEC_DESC *sd)
393 {
394         fstring sidstr;
395         int i;
396
397         printf("REVISION:%d\n", sd->revision);
398
399         /* Print owner and group sid */
400
401         if (sd->owner_sid) {
402                 SidToString(sidstr, sd->owner_sid);
403         } else {
404                 fstrcpy(sidstr, "");
405         }
406
407         printf("OWNER:%s\n", sidstr);
408
409         if (sd->grp_sid) {
410                 SidToString(sidstr, sd->grp_sid);
411         } else {
412                 fstrcpy(sidstr, "");
413         }
414
415         fprintf(f, "GROUP:%s\n", sidstr);
416
417         /* Print aces */
418         for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
419                 SEC_ACE *ace = &sd->dacl->ace[i];
420                 fprintf(f, "ACL:");
421                 print_ace(f, ace);
422         }
423
424 }
425
426
427 /***************************************************** 
428 dump the acls for a file
429 *******************************************************/
430 static void cacl_dump(struct cli_state *cli, char *filename)
431 {
432         int fnum;
433         SEC_DESC *sd;
434
435         if (test_args) return;
436
437         fnum = cli_nt_create(cli, filename, 0x20000);
438         if (fnum == -1) {
439                 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
440                 return;
441         }
442
443         sd = cli_query_secdesc(cli, fnum);
444
445         if (!sd) {
446                 printf("ERROR: secdesc query failed: %s\n", cli_errstr(cli));
447                 return;
448         }
449
450         sec_desc_print(stdout, sd);
451
452         free_sec_desc(&sd);
453
454         cli_close(cli, fnum);
455 }
456
457 /***************************************************** 
458 set the ACLs on a file given an ascii description
459 *******************************************************/
460 static void cacl_set(struct cli_state *cli, char *filename, 
461                      char *acl, enum acl_mode mode)
462 {
463         int fnum;
464         SEC_DESC *sd, *old;
465         int i, j;
466         unsigned sd_size;
467
468         sd = sec_desc_parse(acl);
469
470         if (!sd) return;
471         if (test_args) return;
472
473         /* the desired access below is the only one I could find that works with
474            NT4, W2KP and Samba */
475         fnum = cli_nt_create(cli, filename, MAXIMUM_ALLOWED_ACCESS | 0x60000);
476         if (fnum == -1) {
477                 printf("Failed to open %s: %s\n", filename, cli_errstr(cli));
478                 return;
479         }
480
481         old = cli_query_secdesc(cli, fnum);
482
483         /* the logic here is rather more complex than I would like */
484         switch (mode) {
485         case ACL_DELETE:
486                 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
487                         BOOL found = False;
488
489                         for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
490                                 if (sec_ace_equal(&sd->dacl->ace[i],
491                                                   &old->dacl->ace[j])) {
492                                         if (j != old->dacl->num_aces-1) {
493                                                 old->dacl->ace[j] = old->dacl->ace[j+1];
494                                         }
495                                         old->dacl->num_aces--;
496                                         if (old->dacl->num_aces == 0) {
497                                                 free(old->dacl->ace);
498                                                 old->dacl->ace=NULL;
499                                                 free(old->dacl);
500                                                 old->dacl = NULL;
501                                                 old->off_dacl = 0;
502                                         }
503                                         found = True;
504                                         break;
505                                 }
506                         }
507
508                         if (!found) {
509                                 fstring str;
510
511                                 SidToString(str, &sd->dacl->ace[i].sid);
512                                 printf("ACL for SID %s not found\n", str);
513                         }
514                 }
515                 break;
516
517         case ACL_MODIFY:
518                 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
519                         BOOL found = False;
520
521                         for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
522                                 if (sid_equal(&sd->dacl->ace[i].sid,
523                                               &old->dacl->ace[j].sid)) {
524                                         old->dacl->ace[j] = sd->dacl->ace[i];
525                                         found = True;
526                                 }
527                         }
528
529                         if (!found) {
530                                 fstring str;
531
532                                 SidToString(str, &sd->dacl->ace[i].sid);
533                                 printf("ACL for SID %s not found\n", str);
534                         }
535                 }
536
537                 break;
538
539         case ACL_ADD:
540                 for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
541                         add_ace(&old->dacl, &sd->dacl->ace[i]);
542                 }
543                 break;
544
545         case ACL_SET:
546                 free_sec_desc(&old);
547                 old = sd;
548                 break;
549         }
550
551         if (sd != old) {
552                 free_sec_desc(&sd);
553         }
554
555         sd = make_sec_desc(old->revision, old->owner_sid, old->grp_sid, 
556                            NULL, old->dacl, &sd_size);
557
558         if (!cli_set_secdesc(cli, fnum, sd)) {
559                 printf("ERROR: secdesc set failed: %s\n", cli_errstr(cli));
560                 return;
561         }
562
563         free_sec_desc(&sd);
564         free_sec_desc(&old);
565
566         cli_close(cli, fnum);
567 }
568
569
570 /***************************************************** 
571 return a connection to a server
572 *******************************************************/
573 struct cli_state *connect_one(char *share)
574 {
575         struct cli_state *c;
576         struct nmb_name called, calling;
577         char *server_n;
578         struct in_addr ip;
579         extern struct in_addr ipzero;
580         extern pstring global_myname;
581
582         fstrcpy(server,share+2);
583         share = strchr(server,'\\');
584         if (!share) return NULL;
585         *share = 0;
586         share++;
587
588         server_n = server;
589         
590         ip = ipzero;
591
592         make_nmb_name(&calling, global_myname, 0x0);
593         make_nmb_name(&called , server, 0x20);
594
595  again:
596         ip = ipzero;
597
598         /* have to open a new connection */
599         if (!(c=cli_initialise(NULL)) || (cli_set_port(c, 139) == 0) ||
600             !cli_connect(c, server_n, &ip)) {
601                 DEBUG(0,("Connection to %s failed\n", server_n));
602                 return NULL;
603         }
604
605         if (!cli_session_request(c, &calling, &called)) {
606                 DEBUG(0,("session request to %s failed\n", called.name));
607                 cli_shutdown(c);
608                 if (strcmp(called.name, "*SMBSERVER")) {
609                         make_nmb_name(&called , "*SMBSERVER", 0x20);
610                         goto again;
611                 }
612                 return NULL;
613         }
614
615         DEBUG(4,(" session request ok\n"));
616
617         if (!cli_negprot(c)) {
618                 DEBUG(0,("protocol negotiation failed\n"));
619                 cli_shutdown(c);
620                 return NULL;
621         }
622
623         if (!got_pass) {
624                 char *pass = getpass("Password: ");
625                 if (pass) {
626                         pstrcpy(password, pass);
627                 }
628         }
629
630         if (!cli_session_setup(c, username, 
631                                password, strlen(password),
632                                password, strlen(password),
633                                lp_workgroup())) {
634                 DEBUG(0,("session setup failed: %s\n", cli_errstr(c)));
635                 return NULL;
636         }
637
638         DEBUG(4,(" session setup ok\n"));
639
640         if (!cli_send_tconX(c, share, "?????",
641                             password, strlen(password)+1)) {
642                 DEBUG(0,("tree connect failed: %s\n", cli_errstr(c)));
643                 cli_shutdown(c);
644                 return NULL;
645         }
646
647         DEBUG(4,(" tconx ok\n"));
648
649         return c;
650 }
651
652
653 static void usage(void)
654 {
655         printf(
656 "Usage: smbcacls //server1/share1 filename -U username [options]\n\
657 \n\
658 \t-D <acls>               delete an acl\n\
659 \t-M <acls>               modify an acl\n\
660 \t-A <acls>               add an acl\n\
661 \t-S <acls>               set acls\n\
662 \t-n                      don't resolve sids or masks to names\n\
663 \t-h                      print help\n\
664 \n\
665 The username can be of the form username%%password or\n\
666 workgroup\\username%%password.\n\n\
667 An acl is of the form ACL:<SID>:type/flags/mask\n\
668 You can string acls together with spaces, commas or newlines\n\
669 ");
670 }
671
672 /****************************************************************************
673   main program
674 ****************************************************************************/
675  int main(int argc,char *argv[])
676 {
677         char *share;
678         char *filename;
679         extern char *optarg;
680         extern int optind;
681         extern FILE *dbf;
682         int opt;
683         char *p;
684         int seed;
685         static pstring servicesf = CONFIGFILE;
686         struct cli_state *cli;
687         enum acl_mode mode;
688         char *acl = NULL;
689
690         setlinebuf(stdout);
691
692         dbf = stderr;
693
694         if (argc < 4 || argv[1][0] == '-') {
695                 usage();
696                 exit(1);
697         }
698
699         setup_logging(argv[0],True);
700
701         share = argv[1];
702         filename = argv[2];
703         all_string_sub(share,"/","\\",0);
704
705         argc -= 2;
706         argv += 2;
707
708         TimeInit();
709         charset_initialise();
710
711         lp_load(servicesf,True,False,False);
712         codepage_initialise(lp_client_code_page());
713         load_interfaces();
714
715         if (getenv("USER")) {
716                 pstrcpy(username,getenv("USER"));
717         }
718
719         seed = time(NULL);
720
721         while ((opt = getopt(argc, argv, "U:nhS:D:A:M:t")) != EOF) {
722                 switch (opt) {
723                 case 'U':
724                         pstrcpy(username,optarg);
725                         p = strchr(username,'%');
726                         if (p) {
727                                 *p = 0;
728                                 pstrcpy(password, p+1);
729                                 got_pass = 1;
730                         }
731                         break;
732
733                 case 'S':
734                         acl = optarg;
735                         mode = ACL_SET;
736                         break;
737
738                 case 'D':
739                         acl = optarg;
740                         mode = ACL_DELETE;
741                         break;
742
743                 case 'M':
744                         acl = optarg;
745                         mode = ACL_MODIFY;
746                         break;
747
748                 case 'A':
749                         acl = optarg;
750                         mode = ACL_ADD;
751                         break;
752
753                 case 'n':
754                         numeric = 1;
755                         break;
756
757                 case 't':
758                         test_args = 1;
759                         break;
760
761                 case 'h':
762                         usage();
763                         exit(1);
764
765                 default:
766                         printf("Unknown option %c (%d)\n", (char)opt, opt);
767                         exit(1);
768                 }
769         }
770
771         argc -= optind;
772         argv += optind;
773         
774         if (argc > 0) {
775                 usage();
776                 exit(1);
777         }
778
779         if (!test_args) {
780                 cli = connect_one(share);
781                 if (!cli) exit(1);
782         }
783
784         if (acl) {
785                 cacl_set(cli, filename, acl, mode);
786         } else {
787                 cacl_dump(cli, filename);
788         }
789
790         return(0);
791 }