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