1/* 2 File: do_set.c 3 (Linux Access Control List Management) 4 5 Copyright (C) 1999, 2000 6 Andreas Gruenbacher, <a.gruenbacher@computer.org> 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Library General Public 10 License as published by the Free Software Foundation; either 11 version 2 of the License, or (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Library General Public License for more details. 17 18 You should have received a copy of the GNU Library General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21*/ 22 23#include <stdio.h> 24#include <errno.h> 25#include <sys/acl.h> 26#include <acl/libacl.h> 27 28#include <stdlib.h> 29#include <string.h> 30#include <getopt.h> 31#include <sys/types.h> 32#include <sys/stat.h> 33#include <unistd.h> 34#include <dirent.h> 35#include <ftw.h> 36#include "sequence.h" 37#include "parse.h" 38 39#include <libintl.h> 40 41 42extern const char *progname; 43extern int opt_recursive; 44extern int opt_recalculate; 45extern int opt_test; 46extern int print_options; 47 48acl_entry_t 49find_entry( 50 acl_t acl, 51 acl_tag_t type, 52 id_t id) 53{ 54 acl_entry_t ent; 55 acl_tag_t e_type; 56 id_t *e_id_p; 57 58 if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1) 59 return NULL; 60 61 for(;;) { 62 acl_get_tag_type(ent, &e_type); 63 if (type == e_type) { 64 if (id != ACL_UNDEFINED_ID) { 65 e_id_p = acl_get_qualifier(ent); 66 if (e_id_p == NULL) 67 return NULL; 68 if (*e_id_p == id) { 69 acl_free(e_id_p); 70 return ent; 71 } 72 acl_free(e_id_p); 73 } else { 74 return ent; 75 } 76 } 77 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1) 78 return NULL; 79 } 80} 81 82int 83has_execute_perms( 84 acl_t acl) 85{ 86 acl_entry_t ent; 87 88 if (acl_get_entry(acl, ACL_FIRST_ENTRY, &ent) != 1) 89 return 0; 90 91 for(;;) { 92 acl_permset_t permset; 93 94 acl_get_permset(ent, &permset); 95 if (acl_get_perm(permset, ACL_EXECUTE) != 0) 96 return 1; 97 98 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &ent) != 1) 99 return 0; 100 } 101} 102 103 104int 105clone_entry( 106 acl_t from_acl, 107 acl_tag_t from_type, 108 acl_t *to_acl, 109 acl_tag_t to_type) 110{ 111 acl_entry_t from_entry, to_entry; 112 from_entry = find_entry(from_acl, from_type, ACL_UNDEFINED_ID); 113 if (from_entry) { 114 if (acl_create_entry(to_acl, &to_entry) != 0) 115 return -1; 116 acl_copy_entry(to_entry, from_entry); 117 acl_set_tag_type(to_entry, to_type); 118 return 0; 119 } else { 120 return 1; 121 } 122} 123 124 125void 126print_test( 127 FILE *file, 128 const char *path_p, 129 const struct stat *st, 130 const acl_t acl, 131 const acl_t default_acl) 132{ 133 char *acl_text, *default_acl_text; 134 135 acl_text = acl_to_any_text(acl, NULL, ',', TEXT_ABBREVIATE); 136 default_acl_text = 137 acl_to_any_text(default_acl, "d:", ',', TEXT_ABBREVIATE); 138 fprintf(file, "%s: %s,%s\n", path_p, 139 acl_text ? acl_text : "*", 140 default_acl_text ? default_acl_text : "*"); 141 acl_free(acl_text); 142 acl_free(default_acl_text); 143} 144 145 146static void 147set_perm( 148 acl_entry_t ent, 149 mode_t add, 150 mode_t remove) 151{ 152 acl_permset_t set; 153 154 acl_get_permset(ent, &set); 155 if (remove & CMD_PERM_READ) 156 acl_delete_perm(set, ACL_READ); 157 if (remove & CMD_PERM_WRITE) 158 acl_delete_perm(set, ACL_WRITE); 159 if (remove & CMD_PERM_EXECUTE) 160 acl_delete_perm(set, ACL_EXECUTE); 161 if (add & CMD_PERM_READ) 162 acl_add_perm(set, ACL_READ); 163 if (add & CMD_PERM_WRITE) 164 acl_add_perm(set, ACL_WRITE); 165 if (add & CMD_PERM_EXECUTE) 166 acl_add_perm(set, ACL_EXECUTE); 167} 168 169 170static int 171retrieve_acl( 172 const char *path_p, 173 acl_type_t type, 174 const struct stat *st, 175 acl_t *old_acl, 176 acl_t *acl) 177{ 178 if (*acl) 179 return 0; 180 *acl = NULL; 181 if (type == ACL_TYPE_ACCESS || S_ISDIR(st->st_mode)) { 182 *old_acl = acl_get_file(path_p, type); 183 if (*old_acl == NULL && (errno == ENOSYS || errno == ENOTSUP)) { 184 if (type == ACL_TYPE_DEFAULT) 185 *old_acl = acl_init(0); 186 else 187 *old_acl = acl_from_mode(st->st_mode); 188 } 189 } else 190 *old_acl = acl_init(0); 191 if (*old_acl == NULL) 192 return -1; 193 *acl = acl_dup(*old_acl); 194 if (*acl == NULL) 195 return -1; 196 return 0; 197} 198 199 200static int 201remove_extended_entries( 202 acl_t acl) 203{ 204 acl_entry_t ent, group_obj; 205 acl_permset_t mask_permset, group_obj_permset; 206 acl_tag_t tag; 207 int error; 208 209 /* 210 * Removing the ACL_MASK entry from the ACL results in 211 * increased permissions for the owning group if the 212 * ACL_GROUP_OBJ entry contains permissions not contained 213 * in the ACL_MASK entry. We remove these permissions from 214 * the ACL_GROUP_OBJ entry to avoid that. 215 * 216 * After removing the ACL, the file owner and the owning group 217 * therefore have the same permissions as before. 218 */ 219 220 ent = find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID); 221 group_obj = find_entry(acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID); 222 if (ent && group_obj) { 223 if (!acl_get_permset(ent, &mask_permset) && 224 !acl_get_permset(group_obj, &group_obj_permset)) { 225 if (!acl_get_perm(mask_permset, ACL_READ)) 226 acl_delete_perm(group_obj_permset, ACL_READ); 227 if (!acl_get_perm(mask_permset, ACL_WRITE)) 228 acl_delete_perm(group_obj_permset, ACL_WRITE); 229 if (!acl_get_perm(mask_permset, ACL_EXECUTE)) 230 acl_delete_perm(group_obj_permset, ACL_EXECUTE); 231 } 232 } 233 234 error = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent); 235 while (error == 1) { 236 acl_get_tag_type(ent, &tag); 237 switch(tag) { 238 case ACL_USER: 239 case ACL_GROUP: 240 case ACL_MASK: 241 acl_delete_entry(acl, ent); 242 break; 243 default: 244 break; 245 } 246 247 error = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent); 248 } 249 if (error < 0) 250 return -1; 251 return 0; 252} 253 254 255#define RETRIEVE_ACL(type) do { \ 256 error = retrieve_acl(path_p, type, st, old_xacl, xacl); \ 257 if (error) \ 258 goto fail; \ 259 } while(0) 260 261int 262do_set( 263 const char *path_p, 264 const struct stat *st, 265 const seq_t seq) 266{ 267 acl_t old_acl = NULL, old_default_acl = NULL; 268 acl_t acl = NULL, default_acl = NULL; 269 acl_t *xacl, *old_xacl; 270 acl_entry_t ent; 271 cmd_t cmd; 272 int which_entry; 273 int errors = 0, error; 274 char *acl_text; 275 int acl_modified = 0, default_acl_modified = 0; 276 int acl_mask_provided = 0, default_acl_mask_provided = 0; 277 278 /* Execute the commands in seq (read ACLs on demand) */ 279 error = seq_get_cmd(seq, SEQ_FIRST_CMD, &cmd); 280 if (error == 0) 281 return 0; 282 while (error == 1) { 283 if (cmd->c_type == ACL_TYPE_ACCESS) { 284 xacl = &acl; 285 old_xacl = &old_acl; 286 acl_modified = 1; 287 if (cmd->c_tag == ACL_MASK) 288 acl_mask_provided = 1; 289 } else { 290 xacl = &default_acl; 291 old_xacl = &old_default_acl; 292 default_acl_modified = 1; 293 if (cmd->c_tag == ACL_MASK) 294 default_acl_mask_provided = 1; 295 } 296 297 RETRIEVE_ACL(cmd->c_type); 298 299 /* Check for `X', and replace with `x' as appropriate. */ 300 if (cmd->c_perm & CMD_PERM_COND_EXECUTE) { 301 cmd->c_perm &= ~CMD_PERM_COND_EXECUTE; 302 if (S_ISDIR(st->st_mode) || has_execute_perms(*xacl)) 303 cmd->c_perm |= CMD_PERM_EXECUTE; 304 } 305 306 switch(cmd->c_cmd) { 307 case CMD_ENTRY_REPLACE: 308 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id); 309 if (!ent) { 310 if (acl_create_entry(xacl, &ent) != 0) 311 goto fail; 312 acl_set_tag_type(ent, cmd->c_tag); 313 if (cmd->c_id != ACL_UNDEFINED_ID) 314 acl_set_qualifier(ent, 315 &cmd->c_id); 316 } 317 set_perm(ent, cmd->c_perm, ~cmd->c_perm); 318 break; 319 320 case CMD_ENTRY_ADD: 321 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id); 322 if (ent) 323 set_perm(ent, cmd->c_perm, 0); 324 break; 325 326 case CMD_ENTRY_SUBTRACT: 327 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id); 328 if (ent) 329 set_perm(ent, 0, cmd->c_perm); 330 break; 331 332 case CMD_REMOVE_ENTRY: 333 ent = find_entry(*xacl, cmd->c_tag, cmd->c_id); 334 if (ent) 335 acl_delete_entry(*xacl, ent); 336 else 337 /* ignore */; 338 break; 339 340 case CMD_REMOVE_EXTENDED_ACL: 341 remove_extended_entries(acl); 342 break; 343 344 case CMD_REMOVE_ACL: 345 acl_free(*xacl); 346 *xacl = acl_init(5); 347 if (!*xacl) 348 goto fail; 349 break; 350 351 default: 352 errno = EINVAL; 353 goto fail; 354 } 355 356 error = seq_get_cmd(seq, SEQ_NEXT_CMD, &cmd); 357 } 358 359 if (error < 0) 360 goto fail; 361 362 /* Try to fill in missing entries */ 363 if (default_acl && acl_entries(default_acl) != 0) { 364 xacl = &acl; 365 old_xacl = &old_acl; 366 367 if (!find_entry(default_acl, ACL_USER_OBJ, ACL_UNDEFINED_ID)) { 368 if (!acl) 369 RETRIEVE_ACL(ACL_TYPE_ACCESS); 370 clone_entry(acl, ACL_USER_OBJ, 371 &default_acl, ACL_USER_OBJ); 372 } 373 if (!find_entry(default_acl, ACL_GROUP_OBJ, ACL_UNDEFINED_ID)) { 374 if (!acl) 375 RETRIEVE_ACL(ACL_TYPE_ACCESS); 376 clone_entry(acl, ACL_GROUP_OBJ, 377 &default_acl, ACL_GROUP_OBJ); 378 } 379 if (!find_entry(default_acl, ACL_OTHER, ACL_UNDEFINED_ID)) { 380 if (!acl) 381 RETRIEVE_ACL(ACL_TYPE_ACCESS); 382 clone_entry(acl, ACL_OTHER, 383 &default_acl, ACL_OTHER); 384 } 385 } 386 387 /* update mask entries and check if ACLs are valid */ 388 if (acl && acl_modified) { 389 if (acl_equiv_mode(acl, NULL) != 0) { 390 if (!acl_mask_provided && 391 !find_entry(acl, ACL_MASK, ACL_UNDEFINED_ID)) 392 clone_entry(acl, ACL_GROUP_OBJ, 393 &acl, ACL_MASK); 394 if (opt_recalculate != -1 && 395 (!acl_mask_provided || opt_recalculate == 1)) 396 acl_calc_mask(&acl); 397 } 398 399 error = acl_check(acl, &which_entry); 400 if (error < 0) 401 goto fail; 402 if (error > 0) { 403 acl_text = acl_to_any_text(acl, NULL, ',', 0); 404 fprintf(stderr, gettext("%s: %s: Malformed access ACL " 405 "`%s': %s at entry %d\n"), progname, path_p, 406 acl_text, acl_error(error), which_entry+1); 407 acl_free(acl_text); 408 errors++; 409 goto cleanup; 410 } 411 } 412 413 if (default_acl && acl_entries(default_acl) != 0 && 414 default_acl_modified) { 415 if (acl_equiv_mode(default_acl, NULL) != 0) { 416 if (!default_acl_mask_provided && 417 !find_entry(default_acl,ACL_MASK,ACL_UNDEFINED_ID)) 418 clone_entry(default_acl, ACL_GROUP_OBJ, 419 &default_acl, ACL_MASK); 420 if (opt_recalculate != -1 && 421 (!default_acl_mask_provided || 422 opt_recalculate == 1)) 423 acl_calc_mask(&default_acl); 424 } 425 426 error = acl_check(default_acl, &which_entry); 427 if (error < 0) 428 goto fail; 429 if (error > 0) { 430 acl_text = acl_to_any_text(default_acl, NULL, ',', 0); 431 fprintf(stderr, gettext("%s: %s: Malformed default ACL " 432 "`%s': %s at entry %d\n"), 433 progname, path_p, acl_text, 434 acl_error(error), which_entry+1); 435 acl_free(acl_text); 436 errors++; 437 goto cleanup; 438 } 439 } 440 441 /* Only directores can have default ACLs */ 442 if (default_acl && !S_ISDIR(st->st_mode) && opt_recursive) { 443 /* In recursive mode, ignore default ACLs for files */ 444 acl_free(default_acl); 445 default_acl = NULL; 446 } 447 448 /* check which ACLs have changed */ 449 if (acl && old_acl && acl_cmp(old_acl, acl) == 0) { 450 acl_free(acl); 451 acl = NULL; 452 } 453 if ((default_acl && old_default_acl && 454 acl_cmp(old_default_acl, default_acl) == 0)) { 455 acl_free(default_acl); 456 default_acl = NULL; 457 } 458 459 /* update the file system */ 460 if (opt_test) { 461 print_test(stdout, path_p, st, 462 acl, default_acl); 463 goto cleanup; 464 } 465 if (acl) { 466 if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) != 0) { 467 if (errno == ENOSYS || errno == ENOTSUP) { 468 int saved_errno = errno; 469 mode_t mode; 470 471 if (acl_equiv_mode(acl, &mode) != 0) { 472 errno = saved_errno; 473 goto fail; 474 } else if (chmod(path_p, mode) != 0) 475 goto fail; 476 } else 477 goto fail; 478 } 479 } 480 if (default_acl) { 481 if (S_ISDIR(st->st_mode)) { 482 if (acl_entries(default_acl) == 0) { 483 if (acl_delete_def_file(path_p) != 0 && 484 errno != ENOSYS && errno != ENOTSUP) 485 goto fail; 486 } else { 487 if (acl_set_file(path_p, ACL_TYPE_DEFAULT, 488 default_acl) != 0) 489 goto fail; 490 } 491 } else { 492 if (acl_entries(default_acl) != 0) { 493 fprintf(stderr, gettext( 494 "%s: %s: Only directories " 495 "can have default ACLs\n"), 496 progname, path_p); 497 errors++; 498 goto cleanup; 499 } 500 } 501 } 502 503 error = 0; 504 505cleanup: 506 if (acl) 507 acl_free(acl); 508 if (old_acl) 509 acl_free(old_acl); 510 if (default_acl) 511 acl_free(default_acl); 512 if (old_default_acl) 513 acl_free(old_default_acl); 514 return errors; 515 516fail: 517 fprintf(stderr, "%s: %s: %s\n", progname, path_p, strerror(errno)); 518 errors++; 519 goto cleanup; 520} 521 522