Commit a904de0b authored by David Lawrence's avatar David Lawrence
Browse files

OS-independent filesystem permissions

parent ad7bb5bf
#include <stdio.h>
#include <isc/fsaccess.h>
#include <isc/result.h>
#define PATH "/tmp/fsaccess"
int
main(void) {
isc_fsaccess_t access;
isc_result_t result;
remove(PATH);
fopen(PATH, "w");
chmod(PATH, 0);
access = 0;
isc_fsaccess_add(ISC_FSACCESS_OWNER | ISC_FSACCESS_GROUP,
ISC_FSACCESS_READ | ISC_FSACCESS_WRITE,
&access);
printf("fsaccess=%d\n", access);
isc_fsaccess_add(ISC_FSACCESS_OTHER, ISC_FSACCESS_READ, &access);
printf("fsaccess=%d\n", access);
result = isc_fsaccess_set(PATH, access);
if (result != ISC_R_SUCCESS)
fprintf(stderr, "result = %s\n", isc_result_totext(result));
return (0);
}
/*
* This file contains the OS-independent functionality of the API.
*/
#include <isc/fsaccess.h>
#include <isc/result.h>
#include <isc/util.h>
/*
* Shorthand. Maybe ISC__FSACCESS_PERMISSIONBITS should not even be in
* <isc/fsaccess.h>. Could check consistency with sizeof(isc_fsaccess_t)
* and the number of bits in each function.
*/
#define STEP (ISC__FSACCESS_PERMISSIONBITS)
#define GROUP (STEP)
#define OTHER (STEP * 2)
void
isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access) {
REQUIRE(trustee <= 0x7);
REQUIRE(permission <= 0xFF);
if ((trustee & ISC_FSACCESS_OWNER) != 0)
*access |= permission;
if ((trustee & ISC_FSACCESS_GROUP) != 0)
*access |= (permission << GROUP);
if ((trustee & ISC_FSACCESS_OTHER) != 0)
*access |= (permission << OTHER);
}
void
isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access) {
REQUIRE(trustee <= 0x7);
REQUIRE(permission <= 0xFF);
if ((trustee & ISC_FSACCESS_OWNER) != 0)
*access &= ~permission;
if ((trustee & ISC_FSACCESS_GROUP) != 0)
*access &= ~(permission << GROUP);
if ((trustee & ISC_FSACCESS_OTHER) != 0)
*access &= ~(permission << OTHER);
}
static isc_result_t
check_bad_bits(isc_fsaccess_t access, isc_boolean_t is_dir) {
isc_fsaccess_t bits;
/*
* Check for disallowed user bits.
*/
if (is_dir)
bits = ISC_FSACCESS_READ |
ISC_FSACCESS_WRITE |
ISC_FSACCESS_EXECUTE;
else
bits = ISC_FSACCESS_CREATECHILD |
ISC_FSACCESS_ACCESSCHILD |
ISC_FSACCESS_DELETECHILD |
ISC_FSACCESS_LISTDIRECTORY;
/*
* Set group bad bits.
*/
bits |= bits << STEP;
/*
* Set other bad bits.
*/
bits |= bits << STEP;
if ((access & bits) != 0) {
if (is_dir)
return (ISC_R_NOTFILE);
else
return (ISC_R_NOTDIRECTORY);
}
return (ISC_R_SUCCESS);
}
/*
* The ISC filesystem access module encapsulates the setting of file
* and directory access permissions into one API that is meant to be
* portable to multiple operating systems.
*
* The two primary operating system flavors that are initially accomodated are
* POSIX and Windows NT 4.0 and later. The Windows NT access model is
* considerable more flexible than POSIX's model (as much as I am loathe to
* admit it), and so the ISC API has a higher degree of complexity than would
* be needed to simply address POSIX's needs.
*
* The full breadth of NT's flexibility is not available either, for the
* present time. Much of it is to provide compatibility with what Unix
* programmers are expecting. This is also due to not yet really needing all
* of the functionality of an NT system (or, for that matter, a POSIX system)
* in BIND9, and so resolving how to handle the various incompatibilities has
* been a purely theoretical exercise with no operational experience to
* indicate how flawed the thinking may be.
*
* Some of the more notable dumbing down of NT for this API includes:
*
* o Each of FILE_READ_DATA and FILE_READ_EA are set with ISC_FSACCESS_READ.
*
* o All of FILE_WRITE_DATA, FILE_WRITE_EA and FILE_APPEND_DATA are
* set with ISC_FSACCESS_WRITE. FILE_WRITE_ATTRIBUTES is not set
* so as to be consistent with Unix, where only the owner of the file
* or the superuser can change the attributes/mode of a file.
*
* o Both of FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY are set with
* ISC_FSACCESS_CREATECHILD. This is similar to setting the WRITE
* permission on a Unix directory.
*
* o SYNCHRONIZE is always set for files and directories, unless someone
* can give me a reason why this is a bad idea.
*
* o READ_CONTROL and FILE_READ_ATTRIBUTES are always set; this is
* consistent with Unix, where any file or directory can be stat()'d
* unless the directory path disallows complete access somewhere along
* the way.
*
* o WRITE_DAC is only set for the owner. This too is consistent with
* Unix, and is tighter security than allowing anyone else to be
* able to set permissions.
*
* o DELETE is only set for the owner. On Unix the ability to delete
* a file is controlled by the directory permissions, but it isn't
* currently clear to me what happens on NT if the directory has
* FILE_DELETE_CHILD set but a file within it does not have DELETE
* set. Always setting DELETE on the file/directory for the owner
* gives maximum flexibility to the owner without exposing the
* file to deletion by others.
*
* o WRITE_OWNER is never set. This too is consistent with Unix,
* and is also tighter security than allowing anyone to change the
* ownership of the file apart from the superu..ahem, Administrator.
*
* o Inheritance is set to NO_INHERITANCE.
*
* Unix's dumbing down includes:
*
* o The sticky bit cannot be set.
*
* o setuid and setgid cannot be set.
*
* o Only regular files and directories can be set.
*
* The rest of this comment discusses a few of the incompatibilities
* between the two systems that need more thought if this API is to
* be extended to accomodate them.
*
* The Windows standard access right "DELETE" doesn't have a direct
* equivalent in the Unix world, so it isn't clear what should be done
* with it.
*
* The Unix sticky bit is not supported. While NT does have a concept
* of allowing users to create files in a directory but not delete or
* rename them, it does not have a concept of allowing them to be deleted
* if they are owned by the user trying to delete/rename. While it is
* probable that something could be cobbled together in NT 5 with inheritence,
* it can't really be done in NT 4 as a single property that you could
* set on a directory. You'd need to coordinate something with file creation
* so that every file created had DELETE set for the owner but noone else.
*
* On Unix systems, setting ISC_FSACCESS_LISTDIRECTORY sets READ.
* ... setting either of ISC_FSACCESS_(CREATE|DELETE)CHILD sets WRITE.
* ... setting ISC_FSACCESS_ACCESSCHILD sets EXECUTE.
*
* On NT systems, setting ISC_FSACCESS_LISTDIRECTORY sets FILE_LIST_DIRECTORY.
* ... setting ISC_FSACCESS_(CREATE|DELETE)CHILD sets
* FILE_(CREATE|DELETE)_CHILD independently.
* ... setting ISC_FSACCESS_ACCESSCHILD sets FILE_TRAVERSE.
*
* Unresolved: XXXDCL
* What NT access right controls the ability to rename a file?
* How does DELETE work? If a directory has FILE_DELETE_CHILD but a
* file or directory within it does not have DELETE, is that file
* or directory deletable?
* To implement isc_fsaccess_get(), mapping an existing Unix permission
* mode_t back to an isc_fsaccess_t is pretty trivial; however, mapping
* an NT DACL could be impossible to do in a responsible way.
* Similarly, trying to implement the functionality of being able to
* say "add group writability to whatever permissions already exist"
* could be tricky on NT because of the order-of-entry issue combined
* with possibly having one or more matching ACEs already explicitly
* granting or denying access. Because this functionality is
* not yet needed by the ISC, no code has been written to try to
* solve this problem.
*/
#include <isc/types.h>
/*
* Trustees.
*/
#define ISC_FSACCESS_OWNER 0x1 /* User account. */
#define ISC_FSACCESS_GROUP 0x2 /* Primary group owner. */
#define ISC_FSACCESS_OTHER 0x4 /* Not the owner or the group owner. */
#define ISC_FSACCESS_WORLD 0x7 /* User, Group, Other. */
/*
* Types of permission.
*/
#define ISC_FSACCESS_READ 0x00000001 /* File only. */
#define ISC_FSACCESS_WRITE 0x00000002 /* File only. */
#define ISC_FSACCESS_EXECUTE 0x00000004 /* File only. */
#define ISC_FSACCESS_CREATECHILD 0x00000008 /* Dir only. */
#define ISC_FSACCESS_DELETECHILD 0x00000010 /* Dir only. */
#define ISC_FSACCESS_LISTDIRECTORY 0x00000020 /* Dir only. */
#define ISC_FSACCESS_ACCESSCHILD 0x00000040 /* Dir only. */
/*
* Adding any permission bits beyond 0x200 would mean typedef'ing
* isc_fsaccess_t as isc_uint64_t, and redefining this value to
* reflect the new range of permission types, Probably to 21 for
* maximum flexibility. The number of bits has to accomodate all of
* the permission types, and three full sets of them have to fit
* within an isc_fsaccess_t.
*/
#define ISC__FSACCESS_PERMISSIONBITS 10
void
isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access);
void
isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access);
isc_result_t
isc_fsaccess_set(const char *path, isc_fsaccess_t access);
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "errno2result.h"
/*
* The OS-independent part of the API is in lib/isc.
*/
#include "../fsaccess.c"
isc_result_t
isc_fsaccess_set(const char *path, isc_fsaccess_t access) {
struct stat statb;
mode_t mode;
isc_boolean_t is_dir = ISC_FALSE;
isc_fsaccess_t bits;
isc_result_t result;
if (stat(path, &statb) != 0)
return (isc__errno2result(errno));
if ((statb.st_mode & S_IFDIR) != 0)
is_dir = ISC_TRUE;
else if ((statb.st_mode & S_IFREG) == 0)
return (ISC_R_INVALIDFILE);
result = check_bad_bits(access, is_dir);
if (result != ISC_R_SUCCESS)
return (result);
/*
* Done with checking bad bits. Set mode_t.
*/
mode = 0;
#define SET_AND_CLEAR1(modebit) \
if ((access & bits) != 0) { \
mode |= modebit; \
access &= ~bits; \
}
#define SET_AND_CLEAR(user, group, other) \
SET_AND_CLEAR1(user); \
bits <<= STEP; \
SET_AND_CLEAR1(group); \
bits <<= STEP; \
SET_AND_CLEAR1(other);
bits = ISC_FSACCESS_READ | ISC_FSACCESS_LISTDIRECTORY;
SET_AND_CLEAR(S_IRUSR, S_IRGRP, S_IROTH);
bits = ISC_FSACCESS_WRITE |
ISC_FSACCESS_CREATECHILD |
ISC_FSACCESS_DELETECHILD;
SET_AND_CLEAR(S_IWUSR, S_IWGRP, S_IWOTH);
bits = ISC_FSACCESS_EXECUTE |
ISC_FSACCESS_ACCESSCHILD;
SET_AND_CLEAR(S_IXUSR, S_IXGRP, S_IXOTH);
INSIST(access == 0);
if (chmod(path, mode) < 0)
return (isc__errno2result(errno));
return (ISC_R_SUCCESS);
}
#include <windows.h>
#include <winerror.h>
#include <aclapi.h>
/*
* This file is entirely theoretical. It has never been compiled or tested.
* At the very least, even if this is all perfect (HAH!), isc__winerror2result
* needs to be written.
*/
/*
* The OS-independent part of the API is in lib/isc.
*/
#include "../fsaccess.c"
isc_result_t
isc_fsaccess_set(const char *path, isc_fsaccess_t access) {
isc_result_t result;
isc_fsaccess_t bits, mask;
isc_boolean_t is_dir = ISC_FALSE;
int i;
DWORD winerror;
PACL dacl;
PSID psid[3];
#define owner psid[0]
#define group psid[1]
#define world psid[2]
PSECURITY_DESCRIPTOR sd;
EXPLICIT_ACCESS ea[3], *pea;
TRUSTEETYPE trustee_type[3] = {
TRUSTEE_IS_USER, TRUSTEE_IS_GROUP, TRUSTEE_IS_WELL_KNOWN_GROUP
};
owner = group = world = dacl = sd = NULL;
/* XXXDCL -- NEED TO SET is_dir! Maybe use stat; what is native way? */
result = check_bad_bits(access, is_dir);
if (result != ISC_R_SUCCESS)
return (result);
winerror = GetNamedSecurityInfo(path, SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION,
&owner, &group, NULL, NULL, &sd);
/*
* "ERROR_SUCCESS". Heh heh heh.
*/
if (winerror != ERROR_SUCCESS)
return (isc__winerror2result(winerror));
ZeroMemory(&ea, sizeof(ea));
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
/*
* Make a mask for the number of bits per owner/group/other.
*/
for (i = mask = 0; i < ISC__FSACCESS_PERMISSIONBITS; i++) {
mask <<= 1;
mask |= 1;
}
#define MAP(isc, win32) \
if ((bits & (isc)) != 0) { \
ea.grfAccessPermissions |= (win32); \
bits &= ~(isc); \
}
for (i = 0; i < 2; i++) {
bits = access & mask;
pea = &ea[i];
pea->grfAccessPermissions =
SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES;
if (i == 0)
/*
* Owner-only permissions.
*/
pea->grfAccessPermissions |= WRITE_DAC | DELETE;
/*
* File access rights.
*/
MAP(ISC_FSACCESS_READ, FILE_READ_DATA | FILE_READ_EA);
MAP(ISC_FSACCESS_WRITE,
FILE_WRITE_DATA | FILE_WRITE_EA | FILE_APPEND_DATA);
MAP(ISC_FSACCESS_EXECUTE, FILE_EXECUTE);
/*
* Directory access rights.
*/
MAP(ISC_FSACCESS_LISTDIRECTORY, FILE_LIST_DIRECTORY);
MAP(ISC_FSACCESS_CREATECHILD, FILE_CREATE_CHILD);
MAP(ISC_FSACCESS_DELETECHILD, FILE_DELETE_CHILD);
MAP(ISC_FSACCESS_ACCESSCHILD, FILE_TRAVERSE);
/*
* Ensure no other bits were set.
*/
INSIST(bits == 0);
if (i == 2) {
/*
* Setting world.
*/
SID_IDENTIFIER_AUTHORITY authworld =
SECURITY_WORLD_SID_AUTHORITY;
if (AllocateAndInitializeSid(&authworld, 1,
SECURITY_WORLD_RID,
0, 0, 0, 0, 0, 0, 0,
&world)
== 0)
winerror = GetLastError();
else
/*
* This should already be ERROR_SUCCESS.
*/
ENSURE(winerror == ERROR_SUCCESS);
}
if (winerror == ERROR_SUCCESS) {
BuildTrusteeWithSid(&pea->Trustee, psid[i]);
pea->Trustee.Trusteetype = trustee_type[i];
winerror = SetEntriesInAcl(3, ea, NULL, &dacl);
}
if (winerror == ERROR_SUCCESS)
winerror =
SetNamedSecurityInfo(path, SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
NULL, NULL, dacl, NULL);
if (winerror == ERROR_SUCCESS)
access >> shift;
else
break;
}
if (sd != NULL)
LocalFree(sd);
if (dacl != NULL)
LocalFree(dacl);
if (world != NULL)
FreeSid(world);
if (winerror == ERROR_SUCCESS) {
/*
* Ensure no other bits were set.
*/
INSIST(access == 0);
return (ISC_R_SUCCESS);
} else
return (isc__winerror2result(winerror));
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment