diff options
author | Cameron Katri <me@cameronkatri.com> | 2021-05-09 14:20:58 -0400 |
---|---|---|
committer | Cameron Katri <me@cameronkatri.com> | 2021-05-09 14:20:58 -0400 |
commit | 5fd83771641d15c418f747bd343ba6738d3875f7 (patch) | |
tree | 5abf0f78f680d9837dbd93d4d4c3933bb7509599 /file_cmds/chmod | |
download | apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.gz apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.zst apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.zip |
Import macOS userland
adv_cmds-176
basic_cmds-55
bootstrap_cmds-116.100.1
developer_cmds-66
diskdev_cmds-667.40.1
doc_cmds-53.60.1
file_cmds-321.40.3
mail_cmds-35
misc_cmds-34
network_cmds-606.40.1
patch_cmds-17
remote_cmds-63
shell_cmds-216.60.1
system_cmds-880.60.2
text_cmds-106
Diffstat (limited to 'file_cmds/chmod')
-rw-r--r-- | file_cmds/chmod/chmod.1 | 604 | ||||
-rw-r--r-- | file_cmds/chmod/chmod.c | 458 | ||||
-rw-r--r-- | file_cmds/chmod/chmod_acl.c | 859 | ||||
-rw-r--r-- | file_cmds/chmod/chmod_acl.h | 90 |
4 files changed, 2011 insertions, 0 deletions
diff --git a/file_cmds/chmod/chmod.1 b/file_cmds/chmod/chmod.1 new file mode 100644 index 0000000..0e89f26 --- /dev/null +++ b/file_cmds/chmod/chmod.1 @@ -0,0 +1,604 @@ +.\"- +.\" Copyright (c) 1989, 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)chmod.1 8.4 (Berkeley) 3/31/94 +.\" $FreeBSD$ +.\" +.Dd January 7, 2017 +.Dt CHMOD 1 +.Os +.Sh NAME +.Nm chmod +.Nd change file modes or Access Control Lists +.Sh SYNOPSIS +.Nm chmod +.Op Fl fv +.Op Fl R Op Fl H | L | P +.Ar mode +.Ar +.Nm chmod +.Op Fl fv +.Op Fl R Op Fl H | L | P +.Op -a | +a | =a +.Ar ACE +.Ar +.Nm chmod +.Op Fl fhv +.Op Fl R Op Fl H | L | P +.Op Fl E +.Ar +.Nm chmod +.Op Fl fhv +.Op Fl R Op Fl H | L | P +.Op Fl C +.Ar +.Nm chmod +.Op Fl fhv +.Op Fl R Op Fl H | L | P +.Op Fl N +.Ar +.Sh DESCRIPTION +The +.Nm chmod +utility modifies the file mode bits of the listed files +as specified by the +.Ar mode +operand. It may also be used to modify the Access Control +Lists (ACLs) associated with the listed files. +.Pp +The generic options are as follows: +.Bl -tag -width indent +.It Fl f +Do not display a diagnostic message if +.Nm chmod +could not modify the mode for +.Va file , +nor modify the exit status to reflect such failures. +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed +and hence unaffected by the command. +(Symbolic links encountered during tree traversal are not followed.) +.It Fl h +If the file is a symbolic link, change the mode of the link itself +rather than the file that the link points to. +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl P +If the +.Fl R +option is specified, no symbolic links are followed. +This is the default. +.It Fl R +Change the modes of the file hierarchies rooted in the files, +instead of just the files themselves. +Beware of unintentionally matching the +.Dq Pa ".." +hard link to the parent directory when using wildcards like +.Dq Li ".*" . +.It Fl v +Cause +.Nm chmod +to be verbose, showing filenames as the mode is modified. +If the +.Fl v +flag is specified more than once, the old and new modes of the file +will also be printed, in both octal and symbolic notation. +.El +.Pp +The +.Fl H , +.Fl L +and +.Fl P +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +.Pp +If +.Nm chmod +receives a +.Dv SIGINFO +signal (see the +.Cm status +argument for +.Xr stty 1 ) , +then the current filename as well as the old and new modes are displayed. +.Pp +Only the owner of a file or the super-user is permitted to change +the mode of a file. +.Sh EXIT STATUS +.Ex -std +.Sh MODES +Modes may be absolute or symbolic. +An absolute mode is an octal number constructed from the sum of +one or more of the following values: +.Pp +.Bl -tag -width 6n -compact -offset indent +.It Li 4000 +(the setuid bit). +Executable files with this bit set +will run with effective uid set to the uid of the file owner. +Directories with this bit set will force all files and +sub-directories created in them to be owned by the directory owner +and not by the uid of the creating process, if the underlying file +system supports this feature: see +.Xr chmod 2 +and the +.Cm suiddir +option to +.Xr mount 8 . +.It Li 2000 +(the setgid bit). +Executable files with this bit set +will run with effective gid set to the gid of the file owner. +.It Li 1000 +(the sticky bit). +See +.Xr chmod 2 +and +.Xr sticky 7 . +.It Li 0400 +Allow read by owner. +.It Li 0200 +Allow write by owner. +.It Li 0100 +For files, allow execution by owner. +For directories, allow the owner to +search in the directory. +.It Li 0040 +Allow read by group members. +.It Li 0020 +Allow write by group members. +.It Li 0010 +For files, allow execution by group members. +For directories, allow +group members to search in the directory. +.It Li 0004 +Allow read by others. +.It Li 0002 +Allow write by others. +.It Li 0001 +For files, allow execution by others. +For directories allow others to +search in the directory. +.El +.Pp +For example, the absolute mode that permits read, write and execute by +the owner, read and execute by group members, read and execute by +others, and no set-uid or set-gid behaviour is 755 +(400+200+100+040+010+004+001). +.Pp +The symbolic mode is described by the following grammar: +.Bd -literal -offset indent +mode ::= clause [, clause ...] +clause ::= [who ...] [action ...] action +action ::= op [perm ...] +who ::= a | u | g | o +op ::= + | \- | = +perm ::= r | s | t | w | x | X | u | g | o +.Ed +.Pp +The +.Ar who +symbols ``u'', ``g'', and ``o'' specify the user, group, and other parts +of the mode bits, respectively. +The +.Ar who +symbol ``a'' is equivalent to ``ugo''. +.Pp +The +.Ar perm +symbols represent the portions of the mode bits as follows: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It r +The read bits. +.It s +The set-user-ID-on-execution and set-group-ID-on-execution bits. +.It t +The sticky bit. +.It w +The write bits. +.It x +The execute/search bits. +.It X +The execute/search bits if the file is a directory or any of the +execute/search bits are set in the original (unmodified) mode. +Operations with the +.Ar perm +symbol ``X'' are only meaningful in conjunction with the +.Ar op +symbol ``+'', and are ignored in all other cases. +.It u +The user permission bits in the original mode of the file. +.It g +The group permission bits in the original mode of the file. +.It o +The other permission bits in the original mode of the file. +.El +.Pp +The +.Ar op +symbols represent the operation performed, as follows: +.Bl -tag -width 4n +.It + +If no value is supplied for +.Ar perm , +the ``+'' operation has no effect. +If no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +(see +.Xr umask 2 ) +is clear, is set. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are set. +.It \&\- +If no value is supplied for +.Ar perm , +the ``\-'' operation has no effect. +If no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +is set, is cleared. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are cleared. +.It = +The mode bits specified by the +.Ar who +value are cleared, or, if no +.Ar who +value is specified, the owner, group +and other mode bits are cleared. +Then, if no value is supplied for +.Ar who , +each permission bit specified in +.Ar perm , +for which the corresponding bit in the file mode creation mask +is clear, is set. +Otherwise, the mode bits represented by the specified +.Ar who +and +.Ar perm +values are set. +.El +.Pp +Each +.Ar clause +specifies one or more operations to be performed on the mode +bits, and each operation is applied to the mode bits in the +order specified. +.Pp +Operations upon the other permissions only (specified by the symbol +``o'' by itself), in combination with the +.Ar perm +symbols ``s'' or ``t'', are ignored. +.Pp +The ``w'' permission on directories will permit file creation, relocation, +and copy into that directory. +Files created within the directory itself will inherit its group ID. +.Sh EXAMPLES OF VALID MODES +.Bl -tag -width "u=rwx,go=u-w" -compact +.It Li 644 +make a file readable by anyone and writable by the owner only. +.Pp +.It Li go-w +deny write permission to group and others. +.Pp +.It Li =rw,+X +set the read and write permissions to the usual defaults, but +retain any execute permissions that are currently set. +.Pp +.It Li +X +make a directory or file searchable/executable by everyone if it is +already searchable/executable by anyone. +.Pp +.It Li 755 +.It Li u=rwx,go=rx +.It Li u=rwx,go=u-w +make a file readable/executable by everyone and writable by the owner only. +.Pp +.It Li go= +clear all mode bits for group and others. +.Pp +.It Li g=u-w +set the group bits equal to the user bits, but clear the group write bit. +.El +.Sh ACL MANIPULATION OPTIONS +ACLs are manipulated using extensions to the symbolic mode +grammar. Each file has one ACL, containing an ordered list of entries. +Each entry refers to a user or group, and grants or denies a set of +permissions. +In cases where a user and a group exist with the same name, the +user/group name can be prefixed with "user:" or "group:" in order to +specify the type of name. +.Pp +If the user or group name contains spaces you can use ':' as the delimiter +between name and permission. +.Pp +The following permissions are applicable to all filesystem objects: +.Bl -tag -width 6n -compact -offset indent +.It delete +Delete the item. Deletion may be granted by either this permission +on an object or the delete_child right on the containing directory. +.It readattr +Read an object's basic attributes. This is implicitly granted if +the object can be looked up and not explicitly denied. +.It writeattr +Write an object's basic attributes. +.It readextattr +Read extended attributes. +.It writeextattr +Write extended attributes. +.It readsecurity +Read an object's extended security information (ACL). +.It writesecurity +Write an object's security information (ownership, mode, ACL). +.It chown +Change an object's ownership. +.El +.Pp +The following permissions are applicable to directories: +.Bl -tag -width 6n -compact -offset indent +.It list +List entries. +.It search +Look up files by name. +.It add_file +Add a file. +.It add_subdirectory +Add a subdirectory. +.It delete_child +Delete a contained object. See the file delete permission above. +.El +.Pp +The following permissions are applicable to non-directory filesystem objects: +.Bl -tag -width 6n -compact -offset indent +.It read +Open for reading. +.It write +Open for writing. +.It append +Open for writing, but in a fashion that only allows writes into areas of +the file not previously written. +.It execute +Execute the file as a script or program. +.El +.Pp +ACL inheritance is controlled with the following permissions words, which +may only be applied to directories: +.Bl -tag -width 6n -compact -offset indent +.It file_inherit +Inherit to files. +.It directory_inherit +Inherit to directories. +.It limit_inherit +This flag is only relevant to entries inherited by subdirectories; it +causes the directory_inherit flag to be cleared in the entry that is +inherited, preventing further nested subdirectories from also +inheriting the entry. +.It only_inherit +The entry is inherited by created items but not considered when processing +the ACL. +.El +.Pp +The ACL manipulation options are as follows: +.Bl -tag -width Ds +.It \fB+a\fR +The +a mode parses a new ACL entry from the next argument on +the commandline and inserts it into the canonical location in the +ACL. If the supplied entry refers to an identity already listed, the +two entries are combined. +.Pp +\fBExamples\fR + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + # chmod +a "admin allow write" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: admin allow write + # chmod +a "guest deny read" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write + # chmod +a "admin allow delete" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write,delete + # chmod +a "User 1:allow:read" file + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: User 1 allow read + 3: admin allow write,delete +.Pp +The +a mode strives to maintain correct canonical form for the ACL. + local deny + local allow + inherited deny + inherited allow +.Pp +By default, chmod adds entries to the top of the local deny and local +allow lists. Inherited entries are added by using the +ai mode. +.Pp +\fBExamples\fR + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write,delete + 3: juser inherited deny delete + 4: admin inherited allow delete + 5: backup inherited deny read + 6: admin inherited allow write-security + # chmod +ai "others allow read" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write,delete + 3: juser inherited deny delete + 4: others inherited allow read + 5: admin inherited allow delete + 6: backup inherited deny read + 7: admin inherited allow write-security +.It \fB+a#\fR +When a specific ordering is required, the exact location at which an +entry will be inserted is specified with the +a# mode. +.Pp +\fBExamples\fR + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write + # chmod +a# 2 "others deny read" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: others deny read + 3: admin allow write +.Pp +The +ai# mode may be used to insert inherited entries at a specific +location. Note that these modes allow non-canonical ACL ordering to +be constructed. +.It Fl a +The -a mode is used to delete ACL entries. All entries exactly +matching the supplied entry will be deleted. If the entry lists a +subset of rights granted by an entry, only the rights listed are +removed. Entries may also be deleted by index using the -a# mode. +.Pp +\fBExamples\fR + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: guest deny read + 2: admin allow write,delete + # chmod -a# 1 file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: admin allow write,delete + # chmod -a "admin allow write" file1 + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: admin allow delete +.Pp +Inheritance is not considered when processing the -a mode; rights and +entries will be removed regardless of their inherited state. +.Pp +If the user or group name contains spaces you can use ':' as the delimiter +.Pp +\fBExample\fR + # chmod +a "User 1:allow:read" file +.It \fB=a#\fR +Individual entries are rewritten using the =a# mode. +.Pp +\fBExamples\fR + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: admin allow delete + # chmod =a# 1 "admin allow write,chown" + # ls -le + -rw-r--r--+ 1 juser wheel 0 Apr 28 14:06 file1 + owner: juser + 1: admin allow write,chown +.Pp +This mode may not be used to add new entries. +.It Fl E +Reads the ACL information from stdin, as a sequential list +of ACEs, separated by newlines. If the information parses correctly, +the existing information is replaced. +.It Fl C +Returns false if any of the named files have ACLs in non-canonical order. +.It Fl i +Removes the 'inherited' bit from all entries in the named file(s) ACLs. +.It Fl I +Removes all inherited entries from the named file(s) ACL(s). +.It Fl N +Removes the ACL from the named file(s). +.El +.Sh COMPATIBILITY +The +.Fl v +option is non-standard and its use in scripts is not recommended. +.Sh SEE ALSO +.Xr chflags 1 , +.Xr install 1 , +.Xr chmod 2 , +.Xr stat 2 , +.Xr umask 2 , +.Xr fts 3 , +.Xr setmode 3 , +.Xr sticky 7 , +.Xr symlink 7 , +.Xr chown 8 , +.Xr mount 8 +.Sh STANDARDS +The +.Nm chmod +utility is expected to be +.St -p1003.2 +compatible with the exception of the +.Ar perm +symbol +.Dq t +which is not included in that standard. +.Sh HISTORY +A +.Nm chmod +command appeared in +.At v1 . diff --git a/file_cmds/chmod/chmod.c b/file_cmds/chmod/chmod.c new file mode 100644 index 0000000..442815d --- /dev/null +++ b/file_cmds/chmod/chmod.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +__used static char const copyright[] = +"@(#) Copyright (c) 1989, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__RCSID("$FreeBSD: src/bin/chmod/chmod.c,v 1.27 2002/08/04 05:29:13 obrien Exp $"); + +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef __APPLE__ +#include "chmod_acl.h" + +#endif /*__APPLE__*/ + +int fflag = 0; + +int main(int, char *[]); +void usage(void); + +int +main(int argc, char *argv[]) +{ + FTS *ftsp = NULL; + FTSENT *p = NULL; + mode_t *set = NULL; + long val = 0; + int oct = 0; + int Hflag, Lflag, Pflag, Rflag, ch, fts_options, hflag, rval; + int vflag; + char *ep, *mode; + mode_t newmode, omode; +#ifdef __APPLE__ + unsigned int acloptflags = 0; + long aclpos = -1; + int inheritance_level = 0; + int index = 0; + size_t acloptlen = 0; + int ace_arg_not_required = 0; + acl_t acl_input = NULL; +#endif /* __APPLE__*/ + int (*change_mode)(const char *, mode_t); + + set = NULL; + omode = 0; + Hflag = Lflag = Pflag = Rflag = fflag = hflag = vflag = 0; +#ifndef __APPLE__ + while ((ch = getopt(argc, argv, "HLPRXfghorstuvwx")) != -1) +#else + while ((ch = getopt(argc, argv, "ACEHILNPRVXafghinorstuvwx")) != -1) +#endif + switch (ch) { + case 'H': + Hflag = 1; + Lflag = 0; + Pflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = 0; + Pflag = 0; + break; + case 'P': + Hflag = Lflag = 0; + Pflag = 1; + break; + case 'R': + Rflag = 1; + break; + case 'f': + fflag = 1; + break; + case 'h': + /* + * In System V (and probably POSIX.2) the -h option + * causes chmod to change the mode of the symbolic + * link. 4.4BSD's symbolic links didn't have modes, + * so it was an undocumented noop. In FreeBSD 3.0, + * lchmod(2) is introduced and this option does real + * work. + */ + hflag = 1; + break; +#ifdef __APPLE__ + case 'a': + if (argv[optind - 1][0] == '-' && + argv[optind - 1][1] == ch) + --optind; + goto done; + case 'A': +// acloptflags |= ACL_FLAG | ACL_TO_STDOUT; +// ace_arg_not_required = 1; + errx(1, "-A not implemented"); + goto done; + case 'E': + acloptflags |= ACL_FLAG | ACL_FROM_STDIN; + goto done; + case 'C': + acloptflags |= ACL_FLAG | ACL_CHECK_CANONICITY; + ace_arg_not_required = 1; + goto done; + case 'i': + acloptflags |= ACL_FLAG | ACL_REMOVE_INHERIT_FLAG; + ace_arg_not_required = 1; + goto done; + case 'I': + acloptflags |= ACL_FLAG | ACL_REMOVE_INHERITED_ENTRIES; + ace_arg_not_required = 1; + goto done; + case 'n': + acloptflags |= ACL_FLAG | ACL_NO_TRANSLATE; + break; + case 'N': + acloptflags |= ACL_FLAG | ACL_CLEAR_FLAG; + ace_arg_not_required = 1; + goto done; + case 'V': +// acloptflags |= ACL_FLAG | ACL_INVOKE_EDITOR; +// ace_arg_not_required = 1; + errx(1, "-V not implemented"); + goto done; +#endif /* __APPLE__ */ + /* + * XXX + * "-[rwx]" are valid mode commands. If they are the entire + * argument, getopt has moved past them, so decrement optind. + * Regardless, we're done argument processing. + */ + case 'g': case 'o': case 'r': case 's': + case 't': case 'u': case 'w': case 'X': case 'x': + if (argv[optind - 1][0] == '-' && + argv[optind - 1][1] == ch && + argv[optind - 1][2] == '\0') + --optind; + goto done; + case 'v': + vflag++; + break; + case '?': + default: + usage(); + } +done: argv += optind; + argc -= optind; + +#ifdef __APPLE__ + if (argc < ((acloptflags & ACL_FLAG) ? 1 : 2)) + usage(); + if (!Rflag && (Hflag || Lflag || Pflag)) + warnx("options -H, -L, -P only useful with -R"); +#else /* !__APPLE__ */ + if (argc < 2) + usage(); +#endif /* __APPLE__ */ + +#ifdef __APPLE__ + if (!(acloptflags & ACL_FLAG) && ((acloptlen = strlen(argv[0])) > 1) && (argv[0][1] == 'a')) { + acloptflags |= ACL_FLAG; + switch (argv[0][0]) { + case '+': + acloptflags |= ACL_SET_FLAG; + break; + case '-': + acloptflags |= ACL_DELETE_FLAG; + break; + case '=': + acloptflags |= ACL_REWRITE_FLAG; + break; + default: + acloptflags &= ~ACL_FLAG; + goto apnoacl; + } + + if (argc < 3) + usage(); + + if (acloptlen > 2) { + for (index = 2; index < acloptlen; index++) { + switch (argv[0][index]) { + case '#': + acloptflags |= ACL_ORDER_FLAG; + + if (argc < ((acloptflags & ACL_DELETE_FLAG) + ? 3 : 4)) + usage(); + argv++; + argc--; + errno = 0; + aclpos = strtol(argv[0], &ep, 0); + + if (aclpos > ACL_MAX_ENTRIES + || aclpos < 0) + errno = ERANGE; + if (errno || *ep) + errx(1, "Invalid ACL entry number: %ld", aclpos); + if (acloptflags & ACL_DELETE_FLAG) + ace_arg_not_required = 1; + + goto apdone; + case 'i': + acloptflags |= ACL_INHERIT_FLAG; + /* The +aii.. syntax to specify + * inheritance level is rather unwieldy, + * find an alternative. + */ + inheritance_level++; + if (inheritance_level > 1) + warnx("Inheritance across more than one generation is not currently supported"); + if (inheritance_level >= MAX_INHERITANCE_LEVEL) + goto apdone; + break; + default: + errno = EINVAL; + usage(); + } + } + } +apdone: + argv++; + argc--; + } +apnoacl: +#endif /*__APPLE__*/ + + if (Rflag) { + fts_options = FTS_PHYSICAL; + if (hflag) + errx(1, + "the -R and -h options may not be specified together."); + if (Hflag) + fts_options |= FTS_COMFOLLOW; + if (Lflag) { + fts_options &= ~FTS_PHYSICAL; + fts_options |= FTS_LOGICAL; + } + } else + fts_options = hflag ? FTS_PHYSICAL : FTS_LOGICAL; + + if (hflag) + change_mode = lchmod; + else + change_mode = chmod; +#ifdef __APPLE__ + if (acloptflags & ACL_FROM_STDIN) { + ssize_t readval = 0; + size_t readtotal = 0; + + mode = (char *) malloc(MAX_ACL_TEXT_SIZE); + + if (mode == NULL) + err(1, "Unable to allocate mode string"); + /* Read the ACEs from STDIN */ + do { + readtotal += readval; + readval = read(STDIN_FILENO, mode + readtotal, + MAX_ACL_TEXT_SIZE); + } while ((readval > 0) && (readtotal <= MAX_ACL_TEXT_SIZE)); + + if (0 == readtotal) + errx(1, "-E specified, but read from STDIN failed"); + else + mode[readtotal - 1] = '\0'; + --argv; + } + else +#endif /* __APPLE */ + mode = *argv; + +#ifdef __APPLE__ + if ((acloptflags & ACL_FLAG)) { + + /* Are we deleting by entry number, verifying + * canonicity or performing some other operation that + * does not require an input entry? If so, there's no + * entry to convert. + */ + if (ace_arg_not_required) { + --argv; + } + else { + /* Parse the text into an ACL*/ + acl_input = parse_acl_entries(mode); + if (acl_input == NULL) { + errx(1, "Invalid ACL specification: %s", mode); + } + } + } + else { +#endif /* __APPLE__*/ + if (*mode >= '0' && *mode <= '7') { + errno = 0; + val = strtol(mode, &ep, 8); + if (val > USHRT_MAX || val < 0) + errno = ERANGE; + if (errno) + err(1, "Invalid file mode: %s", mode); + if (*ep) + errx(1, "Invalid file mode: %s", mode); + omode = (mode_t)val; + oct = 1; + } else { + if ((set = setmode(mode)) == NULL) + errx(1, "Invalid file mode: %s", mode); + oct = 0; + } +#ifdef __APPLE__ + } +#endif /* __APPLE__*/ + if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL) + err(1, "fts_open"); + for (rval = 0; (p = fts_read(ftsp)) != NULL;) { + switch (p->fts_info) { + case FTS_D: + if (!Rflag) + (void)fts_set(ftsp, p, FTS_SKIP); + break; + case FTS_DNR: /* Warn, chmod, continue. */ + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = 1; + break; + case FTS_DP: /* Already changed at FTS_D. */ + continue; + case FTS_NS: + if (acloptflags & ACL_FLAG) /* don't need stat for -N */ + break; + case FTS_ERR: /* Warn, continue. */ + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = 1; + continue; + case FTS_SL: /* Ignore. */ + case FTS_SLNONE: + /* + * The only symlinks that end up here are ones that + * don't point to anything and ones that we found + * doing a physical walk. + */ + if (!hflag) + continue; + /* else */ + /* FALLTHROUGH */ + default: + break; + } +#ifdef __APPLE__ +/* If an ACL manipulation option was specified, manipulate */ + if (acloptflags & ACL_FLAG) { + if (0 != modify_file_acl(acloptflags, p->fts_accpath, acl_input, (int)aclpos, inheritance_level, !hflag)) + rval = 1; + } + else { +#endif /* __APPLE__ */ + newmode = oct ? omode : getmode(set, p->fts_statp->st_mode); + if ((newmode & ALLPERMS) == (p->fts_statp->st_mode & ALLPERMS)) + continue; + if ((*change_mode)(p->fts_accpath, newmode) && !fflag) { + warn("Unable to change file mode on %s", p->fts_path); + rval = 1; + } else { + if (vflag) { + (void)printf("%s", p->fts_accpath); + + if (vflag > 1) { + char m1[12], m2[12]; + + strmode(p->fts_statp->st_mode, m1); + strmode((p->fts_statp->st_mode & + S_IFMT) | newmode, m2); + + (void)printf(": 0%o [%s] -> 0%o [%s]", + p->fts_statp->st_mode, m1, + (p->fts_statp->st_mode & S_IFMT) | + newmode, m2); + } + (void)printf("\n"); + } + + } +#ifdef __APPLE__ + } +#endif /* __APPLE__*/ + } + if (errno) + err(1, "fts_read"); +#ifdef __APPLE__ + if (acl_input) + acl_free(acl_input); + if (mode && (acloptflags & ACL_FROM_STDIN)) + free(mode); + +#endif /* __APPLE__ */ + if (set) + free(set); + exit(rval); +} + +void +usage(void) +{ +#ifdef __APPLE__ + (void)fprintf(stderr, + "usage:\tchmod [-fhv] [-R [-H | -L | -P]] [-a | +a | =a [i][# [ n]]] mode|entry file ...\n" + "\tchmod [-fhv] [-R [-H | -L | -P]] [-E | -C | -N | -i | -I] file ...\n"); /* add -A and -V when implemented */ +#else + (void)fprintf(stderr, + "usage: chmod [-fhv] [-R [-H | -L | -P]] mode file ...\n"); +#endif /* __APPLE__ */ + exit(1); +} diff --git a/file_cmds/chmod/chmod_acl.c b/file_cmds/chmod/chmod_acl.c new file mode 100644 index 0000000..5ac4c17 --- /dev/null +++ b/file_cmds/chmod/chmod_acl.c @@ -0,0 +1,859 @@ +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include <membership.h> +#include "chmod_acl.h" + +extern void usage(void); + +#ifdef __APPLE__ +static struct { + acl_perm_t perm; + char *name; + int flags; +#define ACL_PERM_DIR (1<<0) +#define ACL_PERM_FILE (1<<1) +} acl_perms[] = { + {ACL_READ_DATA, "read", ACL_PERM_FILE}, + {ACL_LIST_DIRECTORY, "list", ACL_PERM_DIR}, + {ACL_WRITE_DATA, "write", ACL_PERM_FILE}, + {ACL_ADD_FILE, "add_file", ACL_PERM_DIR}, + {ACL_EXECUTE, "execute", ACL_PERM_FILE}, + {ACL_SEARCH, "search", ACL_PERM_DIR}, + {ACL_DELETE, "delete", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_APPEND_DATA, "append", ACL_PERM_FILE}, + {ACL_ADD_SUBDIRECTORY, "add_subdirectory", ACL_PERM_DIR}, + {ACL_DELETE_CHILD, "delete_child", ACL_PERM_DIR}, + {ACL_READ_ATTRIBUTES, "readattr", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_WRITE_ATTRIBUTES, "writeattr", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_READ_EXTATTRIBUTES, "readextattr", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_READ_SECURITY, "readsecurity", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_WRITE_SECURITY, "writesecurity", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_CHANGE_OWNER, "chown", ACL_PERM_FILE | ACL_PERM_DIR}, + {0, NULL, 0} +}; + +static struct { + acl_flag_t flag; + char *name; + int flags; +} acl_flags[] = { + {ACL_ENTRY_INHERITED, "inherited", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_ENTRY_FILE_INHERIT, "file_inherit", ACL_PERM_DIR}, + {ACL_ENTRY_DIRECTORY_INHERIT, "directory_inherit", ACL_PERM_DIR}, + {ACL_ENTRY_LIMIT_INHERIT, "limit_inherit", ACL_PERM_FILE | ACL_PERM_DIR}, + {ACL_ENTRY_ONLY_INHERIT, "only_inherit", ACL_PERM_DIR}, + {0, NULL, 0} +}; + +/* TBD - Many of these routines could potentially be considered for + * inclusion in a library. If that is done, either avoid use of "err" + * and implement a better fall-through strategy in case of errors, + * or use err_set_exit() and make various structures globals. + */ + +#define NAME_USER (1) +#define NAME_GROUP (2) +#define NAME_EITHER (NAME_USER | NAME_GROUP) + +/* Perform a name to uuid mapping - calls through to memberd */ + +uuid_t * +name_to_uuid(char *tok, int nametype) { + uuid_t *entryg = NULL; + size_t len = strlen(tok); + + if ((entryg = (uuid_t *) calloc(1, sizeof(uuid_t))) == NULL) { + errx(1, "Unable to allocate a uuid"); + } + + if ((nametype & NAME_USER) && mbr_identifier_to_uuid(ID_TYPE_USERNAME, tok, len, *entryg) == 0) { + return entryg; + } + + if ((nametype & NAME_GROUP) && mbr_identifier_to_uuid(ID_TYPE_GROUPNAME, tok, len, *entryg) == 0) { + return entryg; + } + + errx(1, "Unable to translate '%s' to a UUID", tok); +} + +/* Convert an acl entry in string form to an acl_entry_t */ +int +parse_entry(char *entrybuf, acl_entry_t newent) { + char *tok; + char *pebuf; + uuid_t *entryg; + + acl_tag_t tag; + acl_permset_t perms; + acl_flagset_t flags; + unsigned permcount = 0; + unsigned pindex = 0; + char *delimiter = " "; + int nametype = NAME_EITHER; + + acl_get_permset(newent, &perms); + acl_get_flagset_np(newent, &flags); + + pebuf = entrybuf; + + if (0 == strncmp(entrybuf, "user:", 5)) { + nametype = NAME_USER; + pebuf += 5; + } else if (0 == strncmp(entrybuf, "group:", 6)) { + nametype = NAME_GROUP; + pebuf += 6; + } + + if (strchr(pebuf, ':')) /* User/Group names can have spaces */ + delimiter = ":"; + tok = strsep(&pebuf, delimiter); + + if ((tok == NULL) || *tok == '\0') { + errx(1, "Invalid entry format -- expected user or group name"); + } + + /* parse the name into a qualifier */ + entryg = name_to_uuid(tok, nametype); + + tok = strsep(&pebuf, ": "); /* Stick with delimiter? */ + if ((tok == NULL) || *tok == '\0') { + errx(1, "Invalid entry format -- expected allow or deny"); + } + + /* is the verb 'allow' or 'deny'? */ + if (!strcmp(tok, "allow")) { + tag = ACL_EXTENDED_ALLOW; + } else if (!strcmp(tok, "deny")) { + tag = ACL_EXTENDED_DENY; + } else { + errx(1, "Unknown tag type '%s'", tok); + } + + /* parse permissions */ + for (; (tok = strsep(&pebuf, ",")) != NULL;) { + if (*tok != '\0') { + /* is it a permission? */ + for (pindex = 0; acl_perms[pindex].name != NULL; pindex++) { + if (!strcmp(acl_perms[pindex].name, tok)) { + /* got one */ + acl_add_perm(perms, acl_perms[pindex].perm); + permcount++; + goto found; + } + } + /* is it a flag? */ + for (pindex = 0; acl_flags[pindex].name != NULL; pindex++) { + if (!strcmp(acl_flags[pindex].name, tok)) { + /* got one */ + acl_add_flag_np(flags, acl_flags[pindex].flag); + permcount++; + goto found; + } + } + errx(1,"Invalid permission type '%s'", tok); + found: + continue; + } + } + if (0 == permcount) { + errx(1, "No permissions specified"); + } + acl_set_tag_type(newent, tag); + acl_set_qualifier(newent, entryg); + acl_set_permset(newent, perms); + acl_set_flagset_np(newent, flags); + free(entryg); + + return(0); +} + +/* Convert one or more acl entries in string form to an acl_t */ +acl_t +parse_acl_entries(const char *input) { + acl_t acl_input; + acl_entry_t newent; + char *inbuf; + char *oinbuf; + + char **bufp, *entryv[ACL_MAX_ENTRIES]; +#if 0 +/* XXX acl_from_text(), when implemented, will presumably use the canonical + * text representation format, which is what chmod should be using + * We may need to add an entry number to the input + */ + /* Translate the user supplied ACL entry */ + /* acl_input = acl_from_text(input); */ +#else + inbuf = malloc(MAX_ACL_TEXT_SIZE); + + if (inbuf == NULL) + err(1, "malloc() failed"); + strncpy(inbuf, input, MAX_ACL_TEXT_SIZE); + inbuf[MAX_ACL_TEXT_SIZE - 1] = '\0'; + + if ((acl_input = acl_init(1)) == NULL) + err(1, "acl_init() failed"); + + oinbuf = inbuf; + + for (bufp = entryv; (*bufp = strsep(&oinbuf, "\n")) != NULL;) + if (**bufp != '\0') { + if (0 != acl_create_entry(&acl_input, &newent)) + err(1, "acl_create_entry() failed"); + if (0 != parse_entry(*bufp, newent)) { + errx(1, "Failed parsing entry '%s'", *bufp); + } + if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) { + errx(1, "Too many entries"); + } + } + + free(inbuf); + return acl_input; +#endif /* #if 0 */ +} + +/* XXX No Libc support for inherited entries and generation determination yet */ +unsigned +get_inheritance_level(acl_entry_t entry) { +/* XXX to be implemented */ + return 1; +} + +/* Determine a "score" for an acl entry. The entry scores higher if it's + * tagged ACL_EXTENDED_DENY, and non-inherited entries are ranked higher + * than inherited entries. + */ + +int +score_acl_entry(acl_entry_t entry) { + + acl_tag_t tag; + acl_flagset_t flags; + acl_permset_t perms; + + int score = 0; + + if (entry == NULL) + return (MINIMUM_TIER); + + if (acl_get_tag_type(entry, &tag) != 0) { + err(1, "Malformed ACL entry, no tag present"); + } + if (acl_get_flagset_np(entry, &flags) != 0){ + err(1, "Unable to obtain flagset"); + } + if (acl_get_permset(entry, &perms) != 0) + err(1, "Malformed ACL entry, no permset present"); + + switch(tag) { + case ACL_EXTENDED_ALLOW: + break; + case ACL_EXTENDED_DENY: + score++; + break; + default: + errx(1, "Unknown tag type %d present in ACL entry", tag); + /* NOTREACHED */ + } + + if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED)) + score += get_inheritance_level(entry) * INHERITANCE_TIER; + + return score; +} + +int +compare_acl_qualifiers(uuid_t *qa, uuid_t *qb) { + return bcmp(qa, qb, sizeof(uuid_t)); +} + +/* Compare two ACL permsets. + * Returns : + * MATCH_SUBSET if bperms is a subset of aperms + * MATCH_SUPERSET if bperms is a superset of aperms + * MATCH_PARTIAL if the two permsets have a common subset + * MATCH_EXACT if the two permsets are identical + * MATCH_NONE if they are disjoint + */ + +int +compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms) +{ + int i; +/* TBD Implement other match levels as needed */ + for (i = 0; acl_perms[i].name != NULL; i++) { + if (acl_get_perm_np(aperms, acl_perms[i].perm) != + acl_get_perm_np(bperms, acl_perms[i].perm)) + return MATCH_NONE; + } + return MATCH_EXACT; +} + +static int +compare_acl_flagsets(acl_flagset_t aflags, acl_flagset_t bflags) +{ + int i; +/* TBD Implement other match levels as needed */ + for (i = 0; acl_flags[i].name != NULL; i++) { + if (acl_get_flag_np(aflags, acl_flags[i].flag) != + acl_get_flag_np(bflags, acl_flags[i].flag)) + return MATCH_NONE; + } + return MATCH_EXACT; +} + +/* Compares two ACL entries for equality */ +int +compare_acl_entries(acl_entry_t a, acl_entry_t b) +{ + acl_tag_t atag, btag; + acl_permset_t aperms, bperms; + acl_flagset_t aflags, bflags; + int pcmp = 0, fcmp = 0; + void *aqual, *bqual; + + aqual = acl_get_qualifier(a); + bqual = acl_get_qualifier(b); + + int compare = compare_acl_qualifiers(aqual, bqual); + acl_free(aqual); + acl_free(bqual); + + if (compare != 0) + return MATCH_NONE; + + if (0 != acl_get_tag_type(a, &atag)) + err(1, "No tag type present in entry"); + if (0!= acl_get_tag_type(b, &btag)) + err(1, "No tag type present in entry"); + + if (atag != btag) + return MATCH_NONE; + + if ((acl_get_permset(a, &aperms) != 0) || + (acl_get_flagset_np(a, &aflags) != 0) || + (acl_get_permset(b, &bperms) != 0) || + (acl_get_flagset_np(b, &bflags) != 0)) + err(1, "error fetching permissions"); + + pcmp = compare_acl_permsets(aperms, bperms); + fcmp = compare_acl_flagsets(aflags, bflags); + + if ((pcmp == MATCH_NONE) || (fcmp == MATCH_NONE)) + return(MATCH_PARTIAL); + else + return(MATCH_EXACT); +} + +/* Verify that an ACL is in canonical order. Currently, the canonical + * form is: + * local deny + * local allow + * inherited deny (parent) + * inherited allow (parent) + * inherited deny (grandparent) + * inherited allow (grandparent) + * ... + */ +unsigned int +is_canonical(acl_t acl) { + + unsigned aindex; + acl_entry_t entry; + int score = 0, next_score = 0; + +/* XXX - is a zero entry ACL in canonical form? */ + if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry)) + return 1; + + score = score_acl_entry(entry); + + for (aindex = 0; acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 0; + aindex++) { + if (score < (next_score = score_acl_entry(entry))) + return 0; + score = next_score; + } + return 1; +} + + +/* Iterate through an ACL, and find the canonical position for the + * specified entry + */ +unsigned int +find_canonical_position(acl_t acl, acl_entry_t modifier) { + + acl_entry_t entry; + int mscore = 0; + unsigned mpos = 0; + + /* Check if there's an entry with the same qualifier + * and tag type; if not, find the appropriate slot + * for the score. + */ + + if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry)) + return 0; + + mscore = score_acl_entry(modifier); + + while (mscore < score_acl_entry(entry)) { + + mpos++; + + if (0 != acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) + break; + + } + return mpos; +} + +int canonicalize_acl_entries(acl_t acl); + +/* For a given acl_entry_t "modifier", find the first exact or + * partially matching entry from the specified acl_t acl + */ + +int +find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentryp, + unsigned match_inherited) { + + acl_entry_t entry = NULL; + + unsigned aindex; + int cmp, fcmp = MATCH_NONE; + + for (aindex = 0; + acl_get_entry(acl, entry == NULL ? ACL_FIRST_ENTRY : + ACL_NEXT_ENTRY, &entry) == 0; + aindex++) { + cmp = compare_acl_entries(entry, modifier); + if ((cmp == MATCH_EXACT) || (cmp == MATCH_PARTIAL)) { + if (match_inherited) { + acl_flagset_t eflags, mflags; + + if (0 != acl_get_flagset_np(modifier, &mflags)) + err(1, "Unable to get flagset"); + + if (0 != acl_get_flagset_np(entry, &eflags)) + err(1, "Unable to get flagset"); + + if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) { + *rentryp = entry; + fcmp = cmp; + } + } + else { + *rentryp = entry; + fcmp = cmp; + } + } + if (fcmp == MATCH_EXACT) + break; + } + return fcmp; +} + +/* Remove all perms specified in modifier from rentry*/ +int +subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int* valid_perms) +{ + acl_permset_t rperms, mperms; + acl_flagset_t rflags, mflags; + if (valid_perms) + *valid_perms = 0; + int i; + + if ((acl_get_permset(rentry, &rperms) != 0) || + (acl_get_flagset_np(rentry, &rflags) != 0) || + (acl_get_permset(modifier, &mperms) != 0) || + (acl_get_flagset_np(modifier, &mflags) != 0)) + err(1, "error computing ACL modification"); + + for (i = 0; acl_perms[i].name != NULL; i++) { + if (acl_get_perm_np(mperms, acl_perms[i].perm)) + acl_delete_perm(rperms, acl_perms[i].perm); + else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm)) + (*valid_perms)++; + } + for (i = 0; acl_flags[i].name != NULL; i++) { + if (acl_get_flag_np(mflags, acl_flags[i].flag)) + acl_delete_flag_np(rflags, acl_flags[i].flag); + } + acl_set_permset(rentry, rperms); + acl_set_flagset_np(rentry, rflags); + return 0; +} +/* Add the perms specified in modifier to rentry */ +static int +merge_entry_perms(acl_entry_t rentry, acl_entry_t modifier) +{ + acl_permset_t rperms, mperms; + acl_flagset_t rflags, mflags; + int i; + + if ((acl_get_permset(rentry, &rperms) != 0) || + (acl_get_flagset_np(rentry, &rflags) != 0) || + (acl_get_permset(modifier, &mperms) != 0) || + (acl_get_flagset_np(modifier, &mflags) != 0)) + err(1, "error computing ACL modification"); + + for (i = 0; acl_perms[i].name != NULL; i++) { + if (acl_get_perm_np(mperms, acl_perms[i].perm)) + acl_add_perm(rperms, acl_perms[i].perm); + } + for (i = 0; acl_flags[i].name != NULL; i++) { + if (acl_get_flag_np(mflags, acl_flags[i].flag)) + acl_add_flag_np(rflags, acl_flags[i].flag); + } + acl_set_permset(rentry, rperms); + acl_set_flagset_np(rentry, rflags); + return 0; +} + +int +modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags, + int position, int inheritance_level, + unsigned flag_new_acl, const char* path) { + + unsigned cpos = 0; + acl_entry_t newent = NULL; + int dmatch = 0; + acl_entry_t rentry = NULL; + unsigned retval = 0; + acl_t oacl = *oaclp; + +/* Add the inherited flag if requested by the user*/ + if (modifier && (optflags & ACL_INHERIT_FLAG)) { + acl_flagset_t mflags; + + acl_get_flagset_np(modifier, &mflags); + acl_add_flag_np(mflags, ACL_ENTRY_INHERITED); + acl_set_flagset_np(modifier, mflags); + } + + if (optflags & ACL_SET_FLAG) { + if (position != -1) { + if (0 != acl_create_entry_np(&oacl, &newent, position)) + err(1, "acl_create_entry() failed"); + acl_copy_entry(newent, modifier); + } else { +/* If an entry exists, add the new permissions to it, else add an + * entry in the canonical position. + */ + +/* First, check for a matching entry - if one exists, merge flags */ + dmatch = find_matching_entry(oacl, modifier, &rentry, 1); + + if (dmatch != MATCH_NONE) { + if (dmatch == MATCH_EXACT) +/* Nothing to be done */ + goto ma_exit; + + if (dmatch == MATCH_PARTIAL) { + merge_entry_perms(rentry, modifier); + goto ma_exit; + } + } +/* Insert the entry in canonical order */ + cpos = find_canonical_position(oacl, modifier); + if (0!= acl_create_entry_np(&oacl, &newent, cpos)) + err(1, "acl_create_entry() failed"); + acl_copy_entry(newent, modifier); + } + } else if (optflags & ACL_DELETE_FLAG) { + if (flag_new_acl) { + warnx("No ACL present '%s'", path); + retval = 1; + } else if (position != -1 ) { + if (0 != acl_get_entry(oacl, position, &rentry)) { + warnx("Invalid entry number '%s'", path); + retval = 1; + } else { + acl_delete_entry(oacl, rentry); + } + } else { + unsigned match_found = 0, aindex; + for (aindex = 0; + acl_get_entry(oacl, rentry == NULL ? + ACL_FIRST_ENTRY : + ACL_NEXT_ENTRY, &rentry) == 0; + aindex++) { + unsigned cmp; + cmp = compare_acl_entries(rentry, modifier); + if ((cmp == MATCH_EXACT) || + (cmp == MATCH_PARTIAL)) { + match_found++; + if (cmp == MATCH_EXACT) + acl_delete_entry(oacl, rentry); + else { + int valid_perms; +/* In the event of a partial match, remove the specified perms from the + * entry */ + subtract_from_entry(rentry, modifier, &valid_perms); + /* if no perms survived then delete the entry */ + if (valid_perms == 0) + acl_delete_entry(oacl, rentry); + } + } + } + if (0 == match_found) { + warnx("Entry not found when attempting delete '%s'",path); + retval = 1; + } + } + } else if (optflags & ACL_REWRITE_FLAG) { + acl_entry_t rentry; + + if (-1 == position) { + usage(); + } + if (0 == flag_new_acl) { + if (0 != acl_get_entry(oacl, position, + &rentry)) + err(1, "Invalid entry number '%s'", path); + + if (0 != acl_delete_entry(oacl, rentry)) + err(1, "Unable to delete entry '%s'", path); + } + if (0!= acl_create_entry_np(&oacl, &newent, position)) + err(1, "acl_create_entry() failed"); + acl_copy_entry(newent, modifier); + } +ma_exit: + *oaclp = oacl; + return retval; +} + +int +modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) { + + acl_t oacl = NULL; + unsigned aindex = 0, flag_new_acl = 0; + acl_entry_t newent = NULL; + acl_entry_t entry = NULL; + unsigned retval = 0; + + extern int fflag; + +/* XXX acl_get_file() returns a zero entry ACL if an ACL was previously + * associated with the file, and has had its entries removed. + * However, POSIX 1003.1e states that a zero entry ACL should be + * returned if the caller asks for ACL_TYPE_DEFAULT, and no ACL is + * associated with the path; it + * does not specifically state that a request for ACL_TYPE_EXTENDED + * should not return a zero entry ACL, however. + */ + +/* Determine if we've been given a zero entry ACL, or create an ACL if + * none exists. There are some issues to consider here: Should we create + * a zero-entry ACL for a delete or check canonicity operation? + */ + + if (path == NULL) + usage(); + + if (optflags & ACL_CLEAR_FLAG) { + filesec_t fsec = filesec_init(); + if (fsec == NULL) { + err(1, "filesec_init() failed"); + } + if (filesec_set_property(fsec, FILESEC_ACL, _FILESEC_REMOVE_ACL) != 0) { + err(1, "filesec_set_property() failed"); + } + if (follow) { + if (chmodx_np(path, fsec) != 0) { + if (!fflag) { + warn("Failed to clear ACL on file %s", path); + } + retval = 1; + } + } else { + int fd = open(path, O_SYMLINK); + if (fd != -1) { + if (fchmodx_np(fd, fsec) != 0) { + if (!fflag) { + warn("Failed to clear ACL on file %s", path); + } + retval = 1; + } + close(fd); + } else { + if (!fflag) { + warn("Failed to open file %s", path); + } + retval = 1; + } + } + filesec_free(fsec); + return (retval); + } + + if (optflags & ACL_FROM_STDIN) { + oacl = acl_dup(modifier); + } else { + if (follow) { + oacl = acl_get_file(path, ACL_TYPE_EXTENDED); + } else { + int fd = open(path, O_SYMLINK); + if (fd != -1) { + oacl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED); + close(fd); + } + } + if ((oacl == NULL) || + (acl_get_entry(oacl,ACL_FIRST_ENTRY, &newent) != 0)) { + if ((oacl = acl_init(1)) == NULL) + err(1, "acl_init() failed"); + flag_new_acl = 1; + position = 0; + } + + if ((0 == flag_new_acl) && (optflags & (ACL_REMOVE_INHERIT_FLAG | + ACL_REMOVE_INHERITED_ENTRIES))) { + acl_t facl = NULL; + if ((facl = acl_init(1)) == NULL) + err(1, "acl_init() failed"); + for (aindex = 0; + acl_get_entry(oacl, + (entry == NULL ? ACL_FIRST_ENTRY : + ACL_NEXT_ENTRY), &entry) == 0; + aindex++) { + acl_flagset_t eflags; + acl_entry_t fent = NULL; + if (acl_get_flagset_np(entry, &eflags) != 0) { + err(1, "Unable to obtain flagset"); + } + + if (acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) { + if (optflags & ACL_REMOVE_INHERIT_FLAG) { + acl_delete_flag_np(eflags, ACL_ENTRY_INHERITED); + acl_set_flagset_np(entry, eflags); + acl_create_entry(&facl, &fent); + acl_copy_entry(fent, entry); + } + } + else { + acl_create_entry(&facl, &fent); + acl_copy_entry(fent, entry); + } + } + if (oacl) + acl_free(oacl); + oacl = facl; + } else if (optflags & ACL_TO_STDOUT) { + ssize_t len; /* need to get printacl() from ls(1) */ + char *text = acl_to_text(oacl, &len); + puts(text); + acl_free(text); + } else if (optflags & ACL_CHECK_CANONICITY) { + if (flag_new_acl) { + warnx("No ACL currently associated with file '%s'", path); + } + retval = is_canonical(oacl); + } else if ((optflags & ACL_SET_FLAG) && (position == -1) && + (!is_canonical(oacl))) { + warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path); + retval = 1; + } else if (((optflags & ACL_DELETE_FLAG) && (position != -1)) + || (optflags & ACL_CHECK_CANONICITY)) { + retval = modify_acl(&oacl, NULL, optflags, position, + inheritance_level, flag_new_acl, path); + } else if ((optflags & (ACL_REMOVE_INHERIT_FLAG|ACL_REMOVE_INHERITED_ENTRIES)) && flag_new_acl) { + warnx("No ACL currently associated with file '%s'", path); + retval = 1; + } else { + if (!modifier) { /* avoid bus error in acl_get_entry */ + errx(1, "Internal error: modifier should not be NULL"); + } + for (aindex = 0; + acl_get_entry(modifier, + (entry == NULL ? ACL_FIRST_ENTRY : + ACL_NEXT_ENTRY), &entry) == 0; + aindex++) { + + retval += modify_acl(&oacl, entry, optflags, + position, inheritance_level, + flag_new_acl, path); + } + } + } + +/* XXX Potential race here, since someone else could've modified or + * read the ACL on this file (with the intention of modifying it) in + * the interval from acl_get_file() to acl_set_file(); we can + * minimize one aspect of this window by comparing the original acl + * to a fresh one from acl_get_file() but we could consider a + * "changeset" mechanism, common locking strategy, or kernel + * supplied reservation mechanism to prevent this race. + */ + if (!(optflags & (ACL_TO_STDOUT|ACL_CHECK_CANONICITY))) { + int status = -1; + if (follow) { + status = acl_set_file(path, ACL_TYPE_EXTENDED, oacl); + } else { + int fd = open(path, O_SYMLINK); + if (fd != -1) { + status = acl_set_fd_np(fd, oacl, + ACL_TYPE_EXTENDED); + close(fd); + } + } + if (status != 0) { + if (!fflag) + warn("Failed to set ACL on file '%s'", path); + retval = 1; + } + } + + if (oacl) + acl_free(oacl); + + return retval; +} + +#endif /*__APPLE__*/ diff --git a/file_cmds/chmod/chmod_acl.h b/file_cmds/chmod/chmod_acl.h new file mode 100644 index 0000000..b4667e9 --- /dev/null +++ b/file_cmds/chmod/chmod_acl.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CHMOD_ACL_H_ +#define _CHMOD_ACL_H_ + +#ifdef __APPLE__ +#include <pwd.h> +#include <grp.h> +#include <ctype.h> +#include <sys/acl.h> +#include <sys/kauth.h> +#include <uuid/uuid.h> + +#define ACL_FLAG (1<<0) +#define ACL_SET_FLAG (1<<1) +#define ACL_DELETE_FLAG (1<<2) +#define ACL_REWRITE_FLAG (1<<3) +#define ACL_ORDER_FLAG (1<<4) +#define ACL_INHERIT_FLAG (1<<5) +#define ACL_FOLLOW_LINK (1<<6) +#define ACL_FROM_STDIN (1<<7) +#define ACL_CHECK_CANONICITY (1<<8) +#define ACL_REMOVE_INHERIT_FLAG (1<<9) +#define ACL_REMOVE_INHERITED_ENTRIES (1<<10) +#define ACL_NO_TRANSLATE (1<<11) +#define ACL_INVOKE_EDITOR (1<<12) +#define ACL_TO_STDOUT (1<<13) +#define ACL_CLEAR_FLAG (1<<14) + +#define INHERITANCE_TIER (-5) +#define MINIMUM_TIER (-1000) + +#define MATCH_EXACT (2) +#define MATCH_PARTIAL (1) +#define MATCH_NONE (-1) +#define MATCH_SUBSET (-2) +#define MATCH_SUPERSET (-3) + +#define MAX_ACL_TEXT_SIZE 4096 +#define MAX_INHERITANCE_LEVEL 1024 + +extern int search_acl_block(char *tok); +extern int parse_entry(char *entrybuf, acl_entry_t newent); +extern acl_t parse_acl_entries(const char *input); +extern int score_acl_entry(acl_entry_t entry); +extern unsigned get_inheritance_level(acl_entry_t entry); +extern int compare_acl_qualifiers(uuid_t *qa, uuid_t *qb); +extern int compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms); +extern int compare_acl_entries(acl_entry_t a, acl_entry_t b); +extern unsigned is_canonical(acl_t acl); +extern int find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentry, unsigned match_inherited); +extern unsigned find_canonical_position(acl_t acl, acl_entry_t modifier); +extern int subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int *valid_perms); +extern int modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags, int position, int inheritance_level, unsigned flag_new_acl, const char* path); +extern int modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow); +extern uuid_t *name_to_uuid(char *tok, int nametype); +#endif /* __APPLE__*/ + +#endif /* _CHMOD_ACL_H_ */ |