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) { 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 /* 256 * Don't try to set an empty default ACL; it will always fail. 257 * Use acl_delete_def_file(3) instead. 258 */ 259 if (acl_type == ACL_TYPE_DEFAULT && 260 acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) { 261 if (acl_delete_def_file(file->fts_accpath) == -1) { 262 warn("%s: acl_delete_def_file() failed", 263 file->fts_path); 264 return (1); 265 } 266 return (0); 267 } 268 269 /* Don't bother setting the ACL if something is broken. */ 270 if (local_error) { 271 return (1); 272 } 273 274 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 return (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 return (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 return (1); 287 } 288 } 289 290 acl_free(acl); 291 return (0); 292 } 293 294 int 295 main(int argc, char *argv[]) 296 { 297 int carried_error, ch, entry_number, fts_options; 298 FTS *ftsp; 299 FTSENT *file; 300 char **files_list; 301 struct sf_entry *entry; 302 char *end; 303 304 acl_type = ACL_TYPE_ACCESS; 305 carried_error = fts_options = 0; 306 have_mask = have_stdin = n_flag = false; 307 308 TAILQ_INIT(&entrylist); 309 310 while ((ch = getopt(argc, argv, "HLM:PRX:a:bdhkm:nx:")) != -1) 311 switch(ch) { 312 case 'H': 313 H_flag = true; 314 L_flag = false; 315 break; 316 case 'L': 317 L_flag = true; 318 H_flag = false; 319 break; 320 case 'M': 321 entry = zmalloc(sizeof(struct sf_entry)); 322 entry->acl = get_acl_from_file(optarg); 323 if (entry->acl == NULL) 324 err(1, "%s: get_acl_from_file() failed", 325 optarg); 326 entry->op = OP_MERGE_ACL; 327 TAILQ_INSERT_TAIL(&entrylist, entry, next); 328 break; 329 case 'P': 330 H_flag = L_flag = false; 331 break; 332 case 'R': 333 R_flag = true; 334 break; 335 case 'X': 336 entry = zmalloc(sizeof(struct sf_entry)); 337 entry->acl = get_acl_from_file(optarg); 338 entry->op = OP_REMOVE_ACL; 339 TAILQ_INSERT_TAIL(&entrylist, entry, next); 340 break; 341 case 'a': 342 entry = zmalloc(sizeof(struct sf_entry)); 343 344 entry_number = strtol(optarg, &end, 10); 345 if (end - optarg != (int)strlen(optarg)) 346 errx(1, "%s: invalid entry number", optarg); 347 if (entry_number < 0) 348 errx(1, 349 "%s: entry number cannot be less than zero", 350 optarg); 351 entry->entry_number = entry_number; 352 353 if (argv[optind] == NULL) 354 errx(1, "missing ACL"); 355 entry->acl = acl_from_text(argv[optind]); 356 if (entry->acl == NULL) 357 err(1, "%s", argv[optind]); 358 optind++; 359 entry->op = OP_ADD_ACL; 360 TAILQ_INSERT_TAIL(&entrylist, entry, next); 361 break; 362 case 'b': 363 entry = zmalloc(sizeof(struct sf_entry)); 364 entry->op = OP_REMOVE_EXT; 365 TAILQ_INSERT_TAIL(&entrylist, entry, next); 366 break; 367 case 'd': 368 acl_type = ACL_TYPE_DEFAULT; 369 break; 370 case 'h': 371 h_flag = 1; 372 break; 373 case 'k': 374 entry = zmalloc(sizeof(struct sf_entry)); 375 entry->op = OP_REMOVE_DEF; 376 TAILQ_INSERT_TAIL(&entrylist, entry, next); 377 break; 378 case 'm': 379 entry = zmalloc(sizeof(struct sf_entry)); 380 entry->acl = acl_from_text(optarg); 381 if (entry->acl == NULL) 382 err(1, "%s", optarg); 383 entry->op = OP_MERGE_ACL; 384 TAILQ_INSERT_TAIL(&entrylist, entry, next); 385 break; 386 case 'n': 387 n_flag = true; 388 break; 389 case 'x': 390 entry = zmalloc(sizeof(struct sf_entry)); 391 entry_number = strtol(optarg, &end, 10); 392 if (end - optarg == (int)strlen(optarg)) { 393 if (entry_number < 0) 394 errx(1, 395 "%s: entry number cannot be less than zero", 396 optarg); 397 entry->entry_number = entry_number; 398 entry->op = OP_REMOVE_BY_NUMBER; 399 } else { 400 entry->acl = acl_from_text(optarg); 401 if (entry->acl == NULL) 402 err(1, "%s", optarg); 403 entry->op = OP_REMOVE_ACL; 404 } 405 TAILQ_INSERT_TAIL(&entrylist, entry, next); 406 break; 407 default: 408 usage(); 409 break; 410 } 411 argc -= optind; 412 argv += optind; 413 414 if (!n_flag && TAILQ_EMPTY(&entrylist)) 415 usage(); 416 417 /* Take list of files from stdin. */ 418 if (argc == 0 || strcmp(argv[0], "-") == 0) { 419 files_list = stdin_files(); 420 } else 421 files_list = argv; 422 423 if (R_flag) { 424 if (h_flag) 425 errx(1, "the -R and -h options may not be " 426 "specified together."); 427 if (L_flag) { 428 fts_options = FTS_LOGICAL; 429 } else { 430 fts_options = FTS_PHYSICAL; 431 432 if (H_flag) { 433 fts_options |= FTS_COMFOLLOW; 434 } 435 } 436 } else if (h_flag) { 437 fts_options = FTS_PHYSICAL; 438 } else { 439 fts_options = FTS_LOGICAL; 440 } 441 442 /* Open all files. */ 443 if ((ftsp = fts_open(files_list, fts_options | FTS_NOSTAT, 0)) == NULL) 444 err(1, "fts_open"); 445 while ((file = fts_read(ftsp)) != NULL) 446 carried_error += handle_file(ftsp, file); 447 448 return (carried_error); 449 } 450