1139969Simp/*- 274465Srwatson * Copyright (c) 2001 Chris D. Faulhaber 374465Srwatson * All rights reserved. 474465Srwatson * 574465Srwatson * Redistribution and use in source and binary forms, with or without 674465Srwatson * modification, are permitted provided that the following conditions 774465Srwatson * are met: 874465Srwatson * 1. Redistributions of source code must retain the above copyright 974465Srwatson * notice, this list of conditions and the following disclaimer. 1074465Srwatson * 2. Redistributions in binary form must reproduce the above copyright 1174465Srwatson * notice, this list of conditions and the following disclaimer in the 1274465Srwatson * documentation and/or other materials provided with the distribution. 1374465Srwatson * 1474465Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1574465Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1674465Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17204819Sjoel * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18204819Sjoel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19204819Sjoel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20204819Sjoel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21204819Sjoel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22204819Sjoel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23204819Sjoel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24204819Sjoel * SUCH DAMAGE. 2574465Srwatson */ 2674465Srwatson 2799110Sobrien#include <sys/cdefs.h> 2899110Sobrien__FBSDID("$FreeBSD$"); 2999110Sobrien 3074465Srwatson#include <sys/types.h> 3174465Srwatson#include <sys/param.h> 3274465Srwatson#include <sys/stat.h> 3374465Srwatson#include <sys/acl.h> 3474465Srwatson#include <sys/queue.h> 3574465Srwatson 3674465Srwatson#include <err.h> 37196936Strasz#include <errno.h> 3874465Srwatson#include <stdio.h> 3974465Srwatson#include <stdlib.h> 4074465Srwatson#include <string.h> 4174465Srwatson#include <unistd.h> 4274465Srwatson 4374465Srwatson#include "setfacl.h" 4474465Srwatson 45241720Sed/* file operations */ 46241720Sed#define OP_MERGE_ACL 0x00 /* merge acl's (-mM) */ 47241720Sed#define OP_REMOVE_DEF 0x01 /* remove default acl's (-k) */ 48241720Sed#define OP_REMOVE_EXT 0x02 /* remove extended acl's (-b) */ 49241720Sed#define OP_REMOVE_ACL 0x03 /* remove acl's (-xX) */ 50241720Sed#define OP_REMOVE_BY_NUMBER 0x04 /* remove acl's (-xX) by acl entry number */ 51241720Sed#define OP_ADD_ACL 0x05 /* add acls entries at a given position */ 52241720Sed 53241720Sed/* TAILQ entry for acl operations */ 54241720Sedstruct sf_entry { 55241720Sed uint op; 56241720Sed acl_t acl; 57241720Sed uint entry_number; 58241720Sed TAILQ_ENTRY(sf_entry) next; 59241720Sed}; 60241720Sedstatic TAILQ_HEAD(, sf_entry) entrylist; 61241720Sed 62241720Sed/* TAILQ entry for files */ 63241720Sedstruct sf_file { 64241720Sed const char *filename; 65241720Sed TAILQ_ENTRY(sf_file) next; 66241720Sed}; 67241720Sedstatic TAILQ_HEAD(, sf_file) filelist; 68241720Sed 69241720Seduint have_mask; 70241720Seduint need_mask; 71241720Seduint have_stdin; 72241720Seduint n_flag; 73241720Sed 74196936Straszstatic void add_filename(const char *filename); 75196936Straszstatic void usage(void); 7674465Srwatson 7774465Srwatsonstatic void 7874465Srwatsonadd_filename(const char *filename) 7974465Srwatson{ 8074465Srwatson struct sf_file *file; 8174465Srwatson 8274465Srwatson if (strlen(filename) > PATH_MAX - 1) { 8374465Srwatson warn("illegal filename"); 8474465Srwatson return; 8574465Srwatson } 8674465Srwatson file = zmalloc(sizeof(struct sf_file)); 8774465Srwatson file->filename = filename; 8875928Sjedgar TAILQ_INSERT_TAIL(&filelist, file, next); 8974465Srwatson} 9074465Srwatson 9174465Srwatsonstatic void 9274465Srwatsonusage(void) 9374465Srwatson{ 9474465Srwatson 95196936Strasz fprintf(stderr, "usage: setfacl [-bdhkn] [-a position entries] " 96196936Strasz "[-m entries] [-M file] [-x entries] [-X file] [file ...]\n"); 9787254Sjedgar exit(1); 9874465Srwatson} 9974465Srwatson 10074465Srwatsonint 10174465Srwatsonmain(int argc, char *argv[]) 10274465Srwatson{ 103196936Strasz acl_t acl; 104196936Strasz acl_type_t acl_type; 105240083Strasz acl_entry_t unused_entry; 10674465Srwatson char filename[PATH_MAX]; 107196936Strasz int local_error, carried_error, ch, i, entry_number, ret; 108196936Strasz int h_flag; 10974465Srwatson struct sf_file *file; 11074465Srwatson struct sf_entry *entry; 111159463Skib const char *fn_dup; 112196936Strasz char *end; 113196936Strasz struct stat sb; 11474465Srwatson 11574465Srwatson acl_type = ACL_TYPE_ACCESS; 11674465Srwatson carried_error = local_error = 0; 117108450Srwatson h_flag = have_mask = have_stdin = n_flag = need_mask = 0; 11874465Srwatson 11975928Sjedgar TAILQ_INIT(&entrylist); 12075928Sjedgar TAILQ_INIT(&filelist); 12174465Srwatson 122196936Strasz while ((ch = getopt(argc, argv, "M:X:a:bdhkm:nx:")) != -1) 12374465Srwatson switch(ch) { 12474465Srwatson case 'M': 12574465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 12674465Srwatson entry->acl = get_acl_from_file(optarg); 12787254Sjedgar if (entry->acl == NULL) 128196936Strasz err(1, "%s: get_acl_from_file() failed", optarg); 12974465Srwatson entry->op = OP_MERGE_ACL; 13075928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 13174465Srwatson break; 13274465Srwatson case 'X': 13374465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 13474465Srwatson entry->acl = get_acl_from_file(optarg); 13574465Srwatson entry->op = OP_REMOVE_ACL; 13675928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 13774465Srwatson break; 138196936Strasz case 'a': 139196936Strasz entry = zmalloc(sizeof(struct sf_entry)); 140196936Strasz 141196936Strasz entry_number = strtol(optarg, &end, 10); 142196936Strasz if (end - optarg != (int)strlen(optarg)) 143196936Strasz errx(1, "%s: invalid entry number", optarg); 144196936Strasz if (entry_number < 0) 145196936Strasz errx(1, "%s: entry number cannot be less than zero", optarg); 146196936Strasz entry->entry_number = entry_number; 147196936Strasz 148196936Strasz if (argv[optind] == NULL) 149196936Strasz errx(1, "missing ACL"); 150196936Strasz entry->acl = acl_from_text(argv[optind]); 151196936Strasz if (entry->acl == NULL) 152196936Strasz err(1, "%s", argv[optind]); 153196936Strasz optind++; 154196936Strasz entry->op = OP_ADD_ACL; 155196936Strasz TAILQ_INSERT_TAIL(&entrylist, entry, next); 156196936Strasz break; 15774465Srwatson case 'b': 15874465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 15974465Srwatson entry->op = OP_REMOVE_EXT; 16075928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 16174465Srwatson break; 16274465Srwatson case 'd': 16374465Srwatson acl_type = ACL_TYPE_DEFAULT; 16474465Srwatson break; 165108450Srwatson case 'h': 166108450Srwatson h_flag = 1; 167108450Srwatson break; 16874465Srwatson case 'k': 16974465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 17074465Srwatson entry->op = OP_REMOVE_DEF; 17175928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 17274465Srwatson break; 17374465Srwatson case 'm': 17474465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 17574465Srwatson entry->acl = acl_from_text(optarg); 17687254Sjedgar if (entry->acl == NULL) 177117734Srwatson err(1, "%s", optarg); 17874465Srwatson entry->op = OP_MERGE_ACL; 17975928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 18074465Srwatson break; 18174465Srwatson case 'n': 18274465Srwatson n_flag++; 18374465Srwatson break; 18474465Srwatson case 'x': 18574465Srwatson entry = zmalloc(sizeof(struct sf_entry)); 186196936Strasz entry_number = strtol(optarg, &end, 10); 187196936Strasz if (end - optarg == (int)strlen(optarg)) { 188196936Strasz if (entry_number < 0) 189196936Strasz errx(1, "%s: entry number cannot be less than zero", optarg); 190196936Strasz entry->entry_number = entry_number; 191196936Strasz entry->op = OP_REMOVE_BY_NUMBER; 192196936Strasz } else { 193196936Strasz entry->acl = acl_from_text(optarg); 194196936Strasz if (entry->acl == NULL) 195196936Strasz err(1, "%s", optarg); 196196936Strasz entry->op = OP_REMOVE_ACL; 197196936Strasz } 19875928Sjedgar TAILQ_INSERT_TAIL(&entrylist, entry, next); 19974465Srwatson break; 20074465Srwatson default: 20174465Srwatson usage(); 20274465Srwatson break; 20374465Srwatson } 20474465Srwatson argc -= optind; 20574465Srwatson argv += optind; 20674465Srwatson 20787254Sjedgar if (n_flag == 0 && TAILQ_EMPTY(&entrylist)) 20874465Srwatson usage(); 20974465Srwatson 21074465Srwatson /* take list of files from stdin */ 21187254Sjedgar if (argc == 0 || strcmp(argv[0], "-") == 0) { 21274465Srwatson if (have_stdin) 21387254Sjedgar err(1, "cannot have more than one stdin"); 21474465Srwatson have_stdin = 1; 21574465Srwatson bzero(&filename, sizeof(filename)); 21676881Skris while (fgets(filename, (int)sizeof(filename), stdin)) { 21774465Srwatson /* remove the \n */ 21874465Srwatson filename[strlen(filename) - 1] = '\0'; 219159463Skib fn_dup = strdup(filename); 220159463Skib if (fn_dup == NULL) 221159463Skib err(1, "strdup() failed"); 222159463Skib add_filename(fn_dup); 22374465Srwatson } 22474465Srwatson } else 22574465Srwatson for (i = 0; i < argc; i++) 22674465Srwatson add_filename(argv[i]); 22774465Srwatson 22874465Srwatson /* cycle through each file */ 22975928Sjedgar TAILQ_FOREACH(file, &filelist, next) { 230196936Strasz local_error = 0; 231196936Strasz 232196936Strasz if (stat(file->filename, &sb) == -1) { 233196936Strasz warn("%s: stat() failed", file->filename); 234216922Sjh carried_error++; 23574465Srwatson continue; 236196936Strasz } 237196936Strasz 238196936Strasz if (acl_type == ACL_TYPE_DEFAULT && S_ISDIR(sb.st_mode) == 0) { 239196936Strasz warnx("%s: default ACL may only be set on a directory", 240196936Strasz file->filename); 241216922Sjh carried_error++; 24274465Srwatson continue; 24374465Srwatson } 24474465Srwatson 245196936Strasz if (h_flag) 246196936Strasz ret = lpathconf(file->filename, _PC_ACL_NFS4); 247196936Strasz else 248196936Strasz ret = pathconf(file->filename, _PC_ACL_NFS4); 249196936Strasz if (ret > 0) { 250196936Strasz if (acl_type == ACL_TYPE_DEFAULT) { 251196936Strasz warnx("%s: there are no default entries " 252196936Strasz "in NFSv4 ACLs", file->filename); 253216922Sjh carried_error++; 254196936Strasz continue; 255196936Strasz } 256196936Strasz acl_type = ACL_TYPE_NFS4; 257196936Strasz } else if (ret == 0) { 258196936Strasz if (acl_type == ACL_TYPE_NFS4) 259196936Strasz acl_type = ACL_TYPE_ACCESS; 260196936Strasz } else if (ret < 0 && errno != EINVAL) { 261196936Strasz warn("%s: pathconf(..., _PC_ACL_NFS4) failed", 262196936Strasz file->filename); 263196936Strasz } 26474465Srwatson 265196936Strasz if (h_flag) 266196936Strasz acl = acl_get_link_np(file->filename, acl_type); 267196936Strasz else 268196936Strasz acl = acl_get_file(file->filename, acl_type); 269196936Strasz if (acl == NULL) { 270196936Strasz if (h_flag) 271196936Strasz warn("%s: acl_get_link_np() failed", 272196936Strasz file->filename); 273196936Strasz else 274196936Strasz warn("%s: acl_get_file() failed", 275196936Strasz file->filename); 276216922Sjh carried_error++; 277196936Strasz continue; 278196936Strasz } 279196936Strasz 28074465Srwatson /* cycle through each option */ 28175928Sjedgar TAILQ_FOREACH(entry, &entrylist, next) { 28274465Srwatson if (local_error) 28374465Srwatson continue; 28474465Srwatson 28574465Srwatson switch(entry->op) { 286196936Strasz case OP_ADD_ACL: 287196936Strasz local_error += add_acl(entry->acl, 288196936Strasz entry->entry_number, &acl, file->filename); 289196936Strasz break; 29074465Srwatson case OP_MERGE_ACL: 291196936Strasz local_error += merge_acl(entry->acl, &acl, 292196936Strasz file->filename); 29374465Srwatson need_mask = 1; 29474465Srwatson break; 29574465Srwatson case OP_REMOVE_EXT: 296240084Strasz /* 297240084Strasz * Don't try to call remove_ext() for empty 298240084Strasz * default ACL. 299240084Strasz */ 300240084Strasz if (acl_type == ACL_TYPE_DEFAULT && 301240084Strasz acl_get_entry(acl, ACL_FIRST_ENTRY, 302240084Strasz &unused_entry) == 0) { 303240084Strasz local_error += remove_default(&acl, 304240084Strasz file->filename); 305240084Strasz break; 306240084Strasz } 307196936Strasz remove_ext(&acl, file->filename); 30874465Srwatson need_mask = 0; 30974465Srwatson break; 31074465Srwatson case OP_REMOVE_DEF: 311196936Strasz if (acl_type == ACL_TYPE_NFS4) { 312196936Strasz warnx("%s: there are no default entries in NFSv4 ACLs; " 313196936Strasz "cannot remove", file->filename); 314196936Strasz local_error++; 315196936Strasz break; 316196936Strasz } 31774465Srwatson if (acl_delete_def_file(file->filename) == -1) { 318196936Strasz warn("%s: acl_delete_def_file() failed", 319196936Strasz file->filename); 32074465Srwatson local_error++; 32174465Srwatson } 322196936Strasz if (acl_type == ACL_TYPE_DEFAULT) 323196936Strasz local_error += remove_default(&acl, 324196936Strasz file->filename); 32574465Srwatson need_mask = 0; 32674465Srwatson break; 32774465Srwatson case OP_REMOVE_ACL: 328196936Strasz local_error += remove_acl(entry->acl, &acl, 329196936Strasz file->filename); 33074465Srwatson need_mask = 1; 33174465Srwatson break; 332196936Strasz case OP_REMOVE_BY_NUMBER: 333196936Strasz local_error += remove_by_number(entry->entry_number, 334196936Strasz &acl, file->filename); 335196936Strasz need_mask = 1; 336196936Strasz break; 33774465Srwatson } 33874465Srwatson } 33974465Srwatson 340240083Strasz /* 341240083Strasz * Don't try to set an empty default ACL; it will always fail. 342240083Strasz * Use acl_delete_def_file(3) instead. 343240083Strasz */ 344240083Strasz if (acl_type == ACL_TYPE_DEFAULT && 345240083Strasz acl_get_entry(acl, ACL_FIRST_ENTRY, &unused_entry) == 0) { 346240083Strasz if (acl_delete_def_file(file->filename) == -1) { 347240083Strasz warn("%s: acl_delete_def_file() failed", 348240083Strasz file->filename); 349240083Strasz carried_error++; 350240083Strasz } 351240083Strasz continue; 352240083Strasz } 353240083Strasz 35474465Srwatson /* don't bother setting the ACL if something is broken */ 35574465Srwatson if (local_error) { 35674465Srwatson carried_error++; 35774465Srwatson continue; 35874465Srwatson } 35974465Srwatson 360196936Strasz if (acl_type != ACL_TYPE_NFS4 && need_mask && 361196936Strasz set_acl_mask(&acl, file->filename) == -1) { 362196936Strasz warnx("%s: failed to set ACL mask", file->filename); 36374465Srwatson carried_error++; 364167000Smckusick } else if (h_flag) { 365167000Smckusick if (acl_set_link_np(file->filename, acl_type, 366196936Strasz acl) == -1) { 367167000Smckusick carried_error++; 368196936Strasz warn("%s: acl_set_link_np() failed", 369167000Smckusick file->filename); 370167000Smckusick } 371167000Smckusick } else { 372167000Smckusick if (acl_set_file(file->filename, acl_type, 373196936Strasz acl) == -1) { 374167000Smckusick carried_error++; 375196936Strasz warn("%s: acl_set_file() failed", 376167000Smckusick file->filename); 377167000Smckusick } 37874465Srwatson } 37974465Srwatson 380196936Strasz acl_free(acl); 38174465Srwatson } 38274465Srwatson 38387254Sjedgar return (carried_error); 38474465Srwatson} 385