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