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