1 /*- 2 * Copyright (c) 2001 Chris D. Faulhaber 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include <sys/param.h> 31 #include <sys/acl.h> 32 #include <sys/queue.h> 33 34 #include <err.h> 35 #include <errno.h> 36 #include <fts.h> 37 #include <stdbool.h> 38 #include <stdint.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 #include "setfacl.h" 45 46 /* file operations */ 47 #define OP_MERGE_ACL 0x00 /* merge acl's (-mM) */ 48 #define OP_REMOVE_DEF 0x01 /* remove default acl's (-k) */ 49 #define OP_REMOVE_EXT 0x02 /* remove extended acl's (-b) */ 50 #define OP_REMOVE_ACL 0x03 /* remove acl's (-xX) */ 51 #define OP_REMOVE_BY_NUMBER 0x04 /* remove acl's (-xX) by acl entry number */ 52 #define OP_ADD_ACL 0x05 /* add acls entries at a given position */ 53 54 /* TAILQ entry for acl operations */ 55 struct sf_entry { 56 uint op; 57 acl_t acl; 58 uint entry_number; 59 TAILQ_ENTRY(sf_entry) next; 60 }; 61 static TAILQ_HEAD(, sf_entry) entrylist; 62 63 bool have_mask; 64 bool have_stdin; 65 bool n_flag; 66 static bool h_flag; 67 static bool H_flag; 68 static bool L_flag; 69 static bool R_flag; 70 static bool need_mask; 71 static acl_type_t acl_type = ACL_TYPE_ACCESS; 72 73 static int handle_file(FTS *ftsp, FTSENT *file); 74 static acl_t clear_inheritance_flags(acl_t acl); 75 static char **stdin_files(void); 76 static void usage(void); 77 78 static void 79 usage(void) 80 { 81 82 fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] " 83 "[-a position entries] [-m entries] [-M file] " 84 "[-x entries] [-X file] [file ...]\n"); 85 exit(1); 86 } 87 88 static char ** 89 stdin_files(void) 90 { 91 char **files_list; 92 char filename[PATH_MAX]; 93 size_t fl_count, i; 94 95 if (have_stdin) 96 err(1, "cannot have more than one stdin"); 97 98 i = 0; 99 have_stdin = true; 100 bzero(&filename, sizeof(filename)); 101 /* Start with an array size sufficient for basic cases. */ 102 fl_count = 1024; 103 files_list = zmalloc(fl_count * sizeof(char *)); 104 while (fgets(filename, (int)sizeof(filename), stdin)) { 105 /* remove the \n */ 106 filename[strlen(filename) - 1] = '\0'; 107 files_list[i] = strdup(filename); 108 if (files_list[i] == NULL) 109 err(1, "strdup() failed"); 110 /* Grow array if necessary. */ 111 if (++i == fl_count) { 112 fl_count <<= 1; 113 if (fl_count > SIZE_MAX / sizeof(char *)) 114 errx(1, "Too many input files"); 115 files_list = zrealloc(files_list, 116 fl_count * sizeof(char *)); 117 } 118 } 119 120 /* fts_open() requires the last array element to be NULL. */ 121 files_list[i] = NULL; 122 123 return (files_list); 124 } 125 126 /* 127 * Remove any inheritance flags from NFSv4 ACLs when running in recursive 128 * mode. This is to avoid files being assigned identical ACLs to their 129 * parent directory while also being set to inherit them. 130 * 131 * The acl argument is assumed to be valid. 132 */ 133 static acl_t 134 clear_inheritance_flags(acl_t acl) 135 { 136 acl_t nacl; 137 acl_entry_t acl_entry; 138 acl_flagset_t acl_flagset; 139 int acl_brand, entry_id; 140 141 (void)acl_get_brand_np(acl, &acl_brand); 142 if (acl_brand != ACL_BRAND_NFS4) 143 return (acl); 144 145 nacl = acl_dup(acl); 146 if (nacl == NULL) { 147 warn("acl_dup() failed"); 148 return (acl); 149 } 150 151 entry_id = ACL_FIRST_ENTRY; 152 while (acl_get_entry(nacl, entry_id, &acl_entry) == 1) { 153 entry_id = ACL_NEXT_ENTRY; 154 if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { 155 warn("acl_get_flagset_np() failed"); 156 continue; 157 } 158 if (acl_get_flag_np(acl_flagset, ACL_ENTRY_INHERIT_ONLY) == 1) { 159 if (acl_delete_entry(nacl, acl_entry) != 0) 160 warn("acl_delete_entry() failed"); 161 continue; 162 } 163 if (acl_delete_flag_np(acl_flagset, 164 ACL_ENTRY_FILE_INHERIT | 165 ACL_ENTRY_DIRECTORY_INHERIT | 166 ACL_ENTRY_NO_PROPAGATE_INHERIT) != 0) 167 warn("acl_delete_flag_np() failed"); 168 } 169 170 return (nacl); 171 } 172 173 static int 174 handle_file(FTS *ftsp, FTSENT *file) 175 { 176 acl_t acl, nacl; 177 acl_entry_t unused_entry; 178 int local_error, ret; 179 struct sf_entry *entry; 180 bool follow_symlink; 181 182 local_error = 0; 183 switch (file->fts_info) { 184 case FTS_D: 185 /* Do not recurse if -R not specified. */ 186 if (!R_flag) 187 fts_set(ftsp, file, FTS_SKIP); 188 break; 189 case FTS_DP: 190 /* Skip the second visit to a directory. */ 191 return (0); 192 case FTS_DNR: 193 case FTS_ERR: 194 warnx("%s: %s", file->fts_path, strerror(file->fts_errno)); 195 return (0); 196 default: 197 break; 198 } 199 200 if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) { 201 warnx("%s: default ACL may only be set on a directory", 202 file->fts_path); 203 return (1); 204 } 205 206 follow_symlink = (!R_flag && !h_flag) || (R_flag && L_flag) || 207 (R_flag && H_flag && file->fts_level == FTS_ROOTLEVEL); 208 209 if (follow_symlink) 210 ret = pathconf(file->fts_accpath, _PC_ACL_NFS4); 211 else 212 ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4); 213 if (ret > 0) { 214 if (acl_type == ACL_TYPE_DEFAULT) { 215 warnx("%s: there are no default entries in NFSv4 ACLs", 216 file->fts_path); 217 return (1); 218 } 219 acl_type = ACL_TYPE_NFS4; 220 } else if (ret == 0) { 221 if (acl_type == ACL_TYPE_NFS4) 222 acl_type = ACL_TYPE_ACCESS; 223 } else if (ret < 0 && errno != EINVAL && errno != ENOENT) { 224 warn("%s: pathconf(_PC_ACL_NFS4) failed", 225 file->fts_path); 226 } 227 228 if (follow_symlink) 229 acl = acl_get_file(file->fts_accpath, acl_type); 230 else 231 acl = acl_get_link_np(file->fts_accpath, acl_type); 232 if (acl == NULL) { 233 if (follow_symlink) 234 warn("%s: acl_get_file() failed", file->fts_path); 235 else 236 warn("%s: acl_get_link_np() failed", file->fts_path); 237 return (1); 238 } 239 240 /* Cycle through each option. */ 241 TAILQ_FOREACH(entry, &entrylist, next) { 242 nacl = entry->acl; 243 switch (entry->op) { 244 case OP_ADD_ACL: 245 if (R_flag && file->fts_info != FTS_D && 246 acl_type == ACL_TYPE_NFS4) 247 nacl = clear_inheritance_flags(nacl); 248 local_error += add_acl(nacl, entry->entry_number, &acl, 249 file->fts_path); 250 break; 251 case OP_MERGE_ACL: 252 if (R_flag && file->fts_info != FTS_D && 253 acl_type == ACL_TYPE_NFS4) 254 nacl = clear_inheritance_flags(nacl); 255 local_error += merge_acl(nacl, &acl, file->fts_path); 256 need_mask = true; 257 break; 258 case OP_REMOVE_EXT: 259 /* 260 * Don't try to call remove_ext() for empty 261 * default ACL. 262 */ 263 if (acl_type == ACL_TYPE_DEFAULT && 264 acl_get_entry(acl, ACL_FIRST_ENTRY, 265 &unused_entry) == 0) { 266 local_error += remove_default(&acl, 267 file->fts_path); 268 break; 269 } 270 remove_ext(&acl, file->fts_path); 271 need_mask = false; 272 break; 273 case OP_REMOVE_DEF: 274 if (acl_type == ACL_TYPE_NFS4) { 275 warnx("%s: there are no default entries in " 276 "NFSv4 ACLs; cannot remove", 277 file->fts_path); 278 local_error++; 279 break; 280 } 281 if (acl_delete_def_file(file->fts_accpath) == -1) { 282 warn("%s: acl_delete_def_file() failed", 283 file->fts_path); 284 local_error++; 285 } 286 if (acl_type == ACL_TYPE_DEFAULT) 287 local_error += remove_default(&acl, 288 file->fts_path); 289 need_mask = false; 290 break; 291 case OP_REMOVE_ACL: 292 local_error += remove_acl(nacl, &acl, file->fts_path); 293 need_mask = true; 294 break; 295 case OP_REMOVE_BY_NUMBER: 296 local_error += remove_by_number(entry->entry_number, 297 &acl, file->fts_path); 298 need_mask = true; 299 break; 300 } 301 302 if (nacl != entry->acl) { 303 acl_free(nacl); 304 nacl = NULL; 305 } 306 if (local_error) 307 break; 308 } 309 310 ret = 0; 311 312 /* 313 * Don't try to set an empty default ACL; it will always fail. 314 * Use acl_delete_def_file(3) instead. 315 */ 316 if (acl_type == ACL_TYPE_DEFAULT && 317 acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) { 318 if (acl_delete_def_file(file->fts_accpath) == -1) { 319 warn("%s: acl_delete_def_file() failed", 320 file->fts_path); 321 ret = 1; 322 } 323 goto out; 324 } 325 326 /* Don't bother setting the ACL if something is broken. */ 327 if (local_error) { 328 ret = 1; 329 } else if (acl_type != ACL_TYPE_NFS4 && need_mask && 330 set_acl_mask(&acl, file->fts_path) == -1) { 331 warnx("%s: failed to set ACL mask", file->fts_path); 332 ret = 1; 333 } else if (follow_symlink) { 334 if (acl_set_file(file->fts_accpath, acl_type, acl) == -1) { 335 warn("%s: acl_set_file() failed", file->fts_path); 336 ret = 1; 337 } 338 } else { 339 if (acl_set_link_np(file->fts_accpath, acl_type, acl) == -1) { 340 warn("%s: acl_set_link_np() failed", file->fts_path); 341 ret = 1; 342 } 343 } 344 345 out: 346 acl_free(acl); 347 return (ret); 348 } 349 350 int 351 main(int argc, char *argv[]) 352 { 353 int carried_error, ch, entry_number, fts_options; 354 FTS *ftsp; 355 FTSENT *file; 356 char **files_list; 357 struct sf_entry *entry; 358 char *end; 359 360 acl_type = ACL_TYPE_ACCESS; 361 carried_error = fts_options = 0; 362 have_mask = have_stdin = n_flag = false; 363 364 TAILQ_INIT(&entrylist); 365 366 while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1) 367 switch(ch) { 368 case 'H': 369 H_flag = true; 370 L_flag = false; 371 break; 372 case 'L': 373 L_flag = true; 374 H_flag = false; 375 break; 376 case 'M': 377 entry = zmalloc(sizeof(struct sf_entry)); 378 entry->acl = get_acl_from_file(optarg); 379 if (entry->acl == NULL) 380 err(1, "%s: get_acl_from_file() failed", 381 optarg); 382 entry->op = OP_MERGE_ACL; 383 TAILQ_INSERT_TAIL(&entrylist, entry, next); 384 break; 385 case 'P': 386 H_flag = L_flag = false; 387 break; 388 case 'R': 389 R_flag = true; 390 break; 391 case 'X': 392 entry = zmalloc(sizeof(struct sf_entry)); 393 entry->acl = get_acl_from_file(optarg); 394 entry->op = OP_REMOVE_ACL; 395 TAILQ_INSERT_TAIL(&entrylist, entry, next); 396 break; 397 case 'a': 398 entry = zmalloc(sizeof(struct sf_entry)); 399 400 entry_number = strtol(optarg, &end, 10); 401 if (end - optarg != (int)strlen(optarg)) 402 errx(1, "%s: invalid entry number", optarg); 403 if (entry_number < 0) 404 errx(1, 405 "%s: entry number cannot be less than zero", 406 optarg); 407 entry->entry_number = entry_number; 408 409 if (argv[optind] == NULL) 410 errx(1, "missing ACL"); 411 entry->acl = acl_from_text(argv[optind]); 412 if (entry->acl == NULL) 413 err(1, "%s", argv[optind]); 414 optind++; 415 entry->op = OP_ADD_ACL; 416 TAILQ_INSERT_TAIL(&entrylist, entry, next); 417 break; 418 case 'b': 419 entry = zmalloc(sizeof(struct sf_entry)); 420 entry->op = OP_REMOVE_EXT; 421 TAILQ_INSERT_TAIL(&entrylist, entry, next); 422 break; 423 case 'd': 424 acl_type = ACL_TYPE_DEFAULT; 425 break; 426 case 'h': 427 h_flag = 1; 428 break; 429 case 'k': 430 entry = zmalloc(sizeof(struct sf_entry)); 431 entry->op = OP_REMOVE_DEF; 432 TAILQ_INSERT_TAIL(&entrylist, entry, next); 433 break; 434 case 'm': 435 entry = zmalloc(sizeof(struct sf_entry)); 436 entry->acl = acl_from_text(optarg); 437 if (entry->acl == NULL) 438 err(1, "%s", optarg); 439 entry->op = OP_MERGE_ACL; 440 TAILQ_INSERT_TAIL(&entrylist, entry, next); 441 break; 442 case 'n': 443 n_flag = true; 444 break; 445 case 'x': 446 entry = zmalloc(sizeof(struct sf_entry)); 447 entry_number = strtol(optarg, &end, 10); 448 if (end - optarg == (int)strlen(optarg)) { 449 if (entry_number < 0) 450 errx(1, 451 "%s: entry number cannot be less than zero", 452 optarg); 453 entry->entry_number = entry_number; 454 entry->op = OP_REMOVE_BY_NUMBER; 455 } else { 456 entry->acl = acl_from_text(optarg); 457 if (entry->acl == NULL) 458 err(1, "%s", optarg); 459 entry->op = OP_REMOVE_ACL; 460 } 461 TAILQ_INSERT_TAIL(&entrylist, entry, next); 462 break; 463 default: 464 usage(); 465 break; 466 } 467 argc -= optind; 468 argv += optind; 469 470 if (!n_flag && TAILQ_EMPTY(&entrylist)) 471 usage(); 472 473 /* Take list of files from stdin. */ 474 if (argc == 0 || strcmp(argv[0], "-") == 0) { 475 files_list = stdin_files(); 476 } else 477 files_list = argv; 478 479 if (R_flag) { 480 if (h_flag) 481 errx(1, "the -R and -h options may not be " 482 "specified together."); 483 if (L_flag) { 484 fts_options = FTS_LOGICAL; 485 } else { 486 fts_options = FTS_PHYSICAL; 487 488 if (H_flag) { 489 fts_options |= FTS_COMFOLLOW; 490 } 491 } 492 } else if (h_flag) { 493 fts_options = FTS_PHYSICAL; 494 } else { 495 fts_options = FTS_LOGICAL; 496 } 497 498 /* Open all files. */ 499 if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL) 500 err(1, "fts_open"); 501 while ((file = fts_read(ftsp)) != NULL) 502 carried_error += handle_file(ftsp, file); 503 504 return (carried_error); 505 } 506