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