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