mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-29 13:38:26 +00:00
OS-independent filesystem permissions
This commit is contained in:
parent
ad7bb5bff3
commit
a904de0bc8
34
bin/tests/fsaccess_test.c
Normal file
34
bin/tests/fsaccess_test.c
Normal file
@ -0,0 +1,34 @@
|
||||
#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);
|
||||
}
|
82
lib/isc/fsaccess.c
Normal file
82
lib/isc/fsaccess.c
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
149
lib/isc/include/isc/fsaccess.h
Normal file
149
lib/isc/include/isc/fsaccess.h
Normal file
@ -0,0 +1,149 @@
|
||||
|
||||
/*
|
||||
* 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);
|
71
lib/isc/unix/fsaccess.c
Normal file
71
lib/isc/unix/fsaccess.c
Normal file
@ -0,0 +1,71 @@
|
||||
#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);
|
||||
}
|
159
lib/isc/win32/fsaccess.c
Normal file
159
lib/isc/win32/fsaccess.c
Normal file
@ -0,0 +1,159 @@
|
||||
#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));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user