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 #include <sys/param.h> 29 #include <sys/acl.h> 30 #include <sys/queue.h> 31 32 #include <err.h> 33 #include <errno.h> 34 #include <fts.h> 35 #include <stdbool.h> 36 #include <stdint.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include "setfacl.h" 43 44 /* file operations */ 45 #define OP_MERGE_ACL 0x00 /* merge acl's (-mM) */ 46 #define OP_REMOVE_DEF 0x01 /* remove default acl's (-k) */ 47 #define OP_REMOVE_EXT 0x02 /* remove extended acl's (-b) */ 48 #define OP_REMOVE_ACL 0x03 /* remove acl's (-xX) */ 49 #define OP_REMOVE_BY_NUMBER 0x04 /* remove acl's (-xX) by acl entry number */ 50 #define OP_ADD_ACL 0x05 /* add acls entries at a given position */ 51 52 /* TAILQ entry for acl operations */ 53 struct sf_entry { 54 uint op; 55 acl_t acl; 56 uint entry_number; 57 TAILQ_ENTRY(sf_entry) next; 58 }; 59 static TAILQ_HEAD(, sf_entry) entrylist; 60 61 bool have_mask; 62 bool have_stdin; 63 bool n_flag; 64 static bool h_flag; 65 static bool H_flag; 66 static bool L_flag; 67 static bool R_flag; 68 static bool need_mask; 69 static acl_type_t acl_type = ACL_TYPE_ACCESS; 70 71 static int handle_file(FTS *ftsp, FTSENT *file); 72 static acl_t clear_inheritance_flags(acl_t acl); 73 static char **stdin_files(void); 74 static void usage(void); 75 76 static void 77 usage(void) 78 { 79 80 fprintf(stderr, "usage: setfacl [-R [-H | -L | -P]] [-bdhkn] " 81 "[-a position entries] [-m entries] [-M file] " 82 "[-x entries] [-X file] [file ...]\n"); 83 exit(1); 84 } 85 86 static char ** 87 stdin_files(void) 88 { 89 char **files_list; 90 char filename[PATH_MAX]; 91 size_t fl_count, i; 92 93 if (have_stdin) 94 err(1, "cannot have more than one stdin"); 95 96 i = 0; 97 have_stdin = true; 98 bzero(&filename, sizeof(filename)); 99 /* Start with an array size sufficient for basic cases. */ 100 fl_count = 1024; 101 files_list = zmalloc(fl_count * sizeof(char *)); 102 while (fgets(filename, (int)sizeof(filename), stdin)) { 103 /* remove the \n */ 104 filename[strlen(filename) - 1] = '\0'; 105 files_list[i] = strdup(filename); 106 if (files_list[i] == NULL) 107 err(1, "strdup() failed"); 108 /* Grow array if necessary. */ 109 if (++i == fl_count) { 110 fl_count <<= 1; 111 if (fl_count > SIZE_MAX / sizeof(char *)) 112 errx(1, "Too many input files"); 113 files_list = zrealloc(files_list, 114 fl_count * sizeof(char *)); 115 } 116 } 117 118 /* fts_open() requires the last array element to be NULL. */ 119 files_list[i] = NULL; 120 121 return (files_list); 122 } 123 124 /* 125 * Remove any inheritance flags from NFSv4 ACLs when running in recursive 126 * mode. This is to avoid files being assigned identical ACLs to their 127 * parent directory while also being set to inherit them. 128 * 129 * The acl argument is assumed to be valid. 130 */ 131 static acl_t 132 clear_inheritance_flags(acl_t acl) 133 { 134 acl_t nacl; 135 acl_entry_t acl_entry; 136 acl_flagset_t acl_flagset; 137 int acl_brand, entry_id; 138 139 (void)acl_get_brand_np(acl, &acl_brand); 140 if (acl_brand != ACL_BRAND_NFS4) 141 return (acl); 142 143 nacl = acl_dup(acl); 144 if (nacl == NULL) { 145 warn("acl_dup() failed"); 146 return (acl); 147 } 148 149 entry_id = ACL_FIRST_ENTRY; 150 while (acl_get_entry(nacl, entry_id, &acl_entry) == 1) { 151 entry_id = ACL_NEXT_ENTRY; 152 if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { 153 warn("acl_get_flagset_np() failed"); 154 continue; 155 } 156 if (acl_get_flag_np(acl_flagset, ACL_ENTRY_INHERIT_ONLY) == 1) { 157 if (acl_delete_entry(nacl, acl_entry) != 0) 158 warn("acl_delete_entry() failed"); 159 continue; 160 } 161 if (acl_delete_flag_np(acl_flagset, 162 ACL_ENTRY_FILE_INHERIT | 163 ACL_ENTRY_DIRECTORY_INHERIT | 164 ACL_ENTRY_NO_PROPAGATE_INHERIT) != 0) 165 warn("acl_delete_flag_np() failed"); 166 } 167 168 return (nacl); 169 } 170 171 static int 172 handle_file(FTS *ftsp, FTSENT *file) 173 { 174 acl_t acl, nacl; 175 acl_entry_t unused_entry; 176 int local_error, ret; 177 struct sf_entry *entry; 178 bool follow_symlink; 179 180 local_error = 0; 181 switch (file->fts_info) { 182 case FTS_D: 183 /* Do not recurse if -R not specified. */ 184 if (!R_flag) 185 fts_set(ftsp, file, FTS_SKIP); 186 break; 187 case FTS_DP: 188 /* Skip the second visit to a directory. */ 189 return (0); 190 case FTS_DNR: 191 case FTS_ERR: 192 warnx("%s: %s", file->fts_path, strerror(file->fts_errno)); 193 return (0); 194 default: 195 break; 196 } 197 198 if (acl_type == ACL_TYPE_DEFAULT && file->fts_info != FTS_D) { 199 warnx("%s: default ACL may only be set on a directory", 200 file->fts_path); 201 return (1); 202 } 203 204 follow_symlink = (!R_flag && !h_flag) || (R_flag && L_flag) || 205 (R_flag && H_flag && file->fts_level == FTS_ROOTLEVEL); 206 207 if (follow_symlink) 208 ret = pathconf(file->fts_accpath, _PC_ACL_NFS4); 209 else 210 ret = lpathconf(file->fts_accpath, _PC_ACL_NFS4); 211 if (ret > 0) { 212 if (acl_type == ACL_TYPE_DEFAULT) { 213 warnx("%s: there are no default entries in NFSv4 ACLs", 214 file->fts_path); 215 return (1); 216 } 217 acl_type = ACL_TYPE_NFS4; 218 } else if (ret == 0) { 219 if (acl_type == ACL_TYPE_NFS4) 220 acl_type = ACL_TYPE_ACCESS; 221 } else if (ret < 0 && errno != EINVAL && errno != ENOENT) { 222 warn("%s: pathconf(_PC_ACL_NFS4) failed", 223 file->fts_path); 224 } 225 226 if (follow_symlink) 227 acl = acl_get_file(file->fts_accpath, acl_type); 228 else 229 acl = acl_get_link_np(file->fts_accpath, acl_type); 230 if (acl == NULL) { 231 if (follow_symlink) 232 warn("%s: acl_get_file() failed", file->fts_path); 233 else 234 warn("%s: acl_get_link_np() failed", file->fts_path); 235 return (1); 236 } 237 238 /* Cycle through each option. */ 239 TAILQ_FOREACH(entry, &entrylist, next) { 240 nacl = entry->acl; 241 switch (entry->op) { 242 case OP_ADD_ACL: 243 if (R_flag && file->fts_info != FTS_D && 244 acl_type == ACL_TYPE_NFS4) 245 nacl = clear_inheritance_flags(nacl); 246 local_error += add_acl(nacl, entry->entry_number, &acl, 247 file->fts_path); 248 break; 249 case OP_MERGE_ACL: 250 if (R_flag && file->fts_info != FTS_D && 251 acl_type == ACL_TYPE_NFS4) 252 nacl = clear_inheritance_flags(nacl); 253 local_error += merge_acl(nacl, &acl, file->fts_path); 254 need_mask = true; 255 break; 256 case OP_REMOVE_EXT: 257 /* 258 * Don't try to call remove_ext() for empty 259 * default ACL. 260 */ 261 if (acl_type == ACL_TYPE_DEFAULT && 262 acl_get_entry(acl, ACL_FIRST_ENTRY, 263 &unused_entry) == 0) { 264 local_error += remove_default(&acl, 265 file->fts_path); 266 break; 267 } 268 remove_ext(&acl, file->fts_path); 269 need_mask = false; 270 break; 271 case OP_REMOVE_DEF: 272 if (acl_type == ACL_TYPE_NFS4) { 273 warnx("%s: there are no default entries in " 274 "NFSv4 ACLs; cannot remove", 275 file->fts_path); 276 local_error++; 277 break; 278 } 279 if (acl_delete_def_file(file->fts_accpath) == -1) { 280 warn("%s: acl_delete_def_file() failed", 281 file->fts_path); 282 local_error++; 283 } 284 if (acl_type == ACL_TYPE_DEFAULT) 285 local_error += remove_default(&acl, 286 file->fts_path); 287 need_mask = false; 288 break; 289 case OP_REMOVE_ACL: 290 local_error += remove_acl(nacl, &acl, file->fts_path); 291 need_mask = true; 292 break; 293 case OP_REMOVE_BY_NUMBER: 294 local_error += remove_by_number(entry->entry_number, 295 &acl, file->fts_path); 296 need_mask = true; 297 break; 298 } 299 300 if (nacl != entry->acl) { 301 acl_free(nacl); 302 nacl = NULL; 303 } 304 if (local_error) 305 break; 306 } 307 308 ret = 0; 309 310 /* 311 * Don't try to set an empty default ACL; it will always fail. 312 * Use acl_delete_def_file(3) instead. 313 */ 314 if (acl_type == ACL_TYPE_DEFAULT && 315 acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) { 316 if (acl_delete_def_file(file->fts_accpath) == -1) { 317 warn("%s: acl_delete_def_file() failed", 318 file->fts_path); 319 ret = 1; 320 } 321 goto out; 322 } 323 324 /* Don't bother setting the ACL if something is broken. */ 325 if (local_error) { 326 ret = 1; 327 } else if (acl_type != ACL_TYPE_NFS4 && need_mask && 328 set_acl_mask(&acl, file->fts_path) == -1) { 329 warnx("%s: failed to set ACL mask", file->fts_path); 330 ret = 1; 331 } else if (follow_symlink) { 332 if (acl_set_file(file->fts_accpath, acl_type, acl) == -1) { 333 warn("%s: acl_set_file() failed", file->fts_path); 334 ret = 1; 335 } 336 } else { 337 if (acl_set_link_np(file->fts_accpath, acl_type, acl) == -1) { 338 warn("%s: acl_set_link_np() failed", file->fts_path); 339 ret = 1; 340 } 341 } 342 343 out: 344 acl_free(acl); 345 return (ret); 346 } 347 348 int 349 main(int argc, char *argv[]) 350 { 351 int carried_error, ch, entry_number, fts_options; 352 FTS *ftsp; 353 FTSENT *file; 354 char **files_list; 355 struct sf_entry *entry; 356 char *end; 357 358 acl_type = ACL_TYPE_ACCESS; 359 carried_error = fts_options = 0; 360 have_mask = have_stdin = n_flag = false; 361 362 TAILQ_INIT(&entrylist); 363 364 while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1) 365 switch(ch) { 366 case 'H': 367 H_flag = true; 368 L_flag = false; 369 break; 370 case 'L': 371 L_flag = true; 372 H_flag = false; 373 break; 374 case 'M': 375 entry = zmalloc(sizeof(struct sf_entry)); 376 entry->acl = get_acl_from_file(optarg); 377 if (entry->acl == NULL) 378 err(1, "%s: get_acl_from_file() failed", 379 optarg); 380 entry->op = OP_MERGE_ACL; 381 TAILQ_INSERT_TAIL(&entrylist, entry, next); 382 break; 383 case 'P': 384 H_flag = L_flag = false; 385 break; 386 case 'R': 387 R_flag = true; 388 break; 389 case 'X': 390 entry = zmalloc(sizeof(struct sf_entry)); 391 entry->acl = get_acl_from_file(optarg); 392 entry->op = OP_REMOVE_ACL; 393 TAILQ_INSERT_TAIL(&entrylist, entry, next); 394 break; 395 case 'a': 396 entry = zmalloc(sizeof(struct sf_entry)); 397 398 entry_number = strtol(optarg, &end, 10); 399 if (end - optarg != (int)strlen(optarg)) 400 errx(1, "%s: invalid entry number", optarg); 401 if (entry_number < 0) 402 errx(1, 403 "%s: entry number cannot be less than zero", 404 optarg); 405 entry->entry_number = entry_number; 406 407 if (argv[optind] == NULL) 408 errx(1, "missing ACL"); 409 entry->acl = acl_from_text(argv[optind]); 410 if (entry->acl == NULL) 411 err(1, "%s", argv[optind]); 412 optind++; 413 entry->op = OP_ADD_ACL; 414 TAILQ_INSERT_TAIL(&entrylist, entry, next); 415 break; 416 case 'b': 417 entry = zmalloc(sizeof(struct sf_entry)); 418 entry->op = OP_REMOVE_EXT; 419 TAILQ_INSERT_TAIL(&entrylist, entry, next); 420 break; 421 case 'd': 422 acl_type = ACL_TYPE_DEFAULT; 423 break; 424 case 'h': 425 h_flag = 1; 426 break; 427 case 'k': 428 entry = zmalloc(sizeof(struct sf_entry)); 429 entry->op = OP_REMOVE_DEF; 430 TAILQ_INSERT_TAIL(&entrylist, entry, next); 431 break; 432 case 'm': 433 entry = zmalloc(sizeof(struct sf_entry)); 434 entry->acl = acl_from_text(optarg); 435 if (entry->acl == NULL) 436 err(1, "%s", optarg); 437 entry->op = OP_MERGE_ACL; 438 TAILQ_INSERT_TAIL(&entrylist, entry, next); 439 break; 440 case 'n': 441 n_flag = true; 442 break; 443 case 'x': 444 entry = zmalloc(sizeof(struct sf_entry)); 445 entry_number = strtol(optarg, &end, 10); 446 if (end - optarg == (int)strlen(optarg)) { 447 if (entry_number < 0) 448 errx(1, 449 "%s: entry number cannot be less than zero", 450 optarg); 451 entry->entry_number = entry_number; 452 entry->op = OP_REMOVE_BY_NUMBER; 453 } else { 454 entry->acl = acl_from_text(optarg); 455 if (entry->acl == NULL) 456 err(1, "%s", optarg); 457 entry->op = OP_REMOVE_ACL; 458 } 459 TAILQ_INSERT_TAIL(&entrylist, entry, next); 460 break; 461 default: 462 usage(); 463 break; 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