diff --git a/bin/tests/fsaccess_test.c b/bin/tests/fsaccess_test.c new file mode 100644 index 0000000000..907378d4e4 --- /dev/null +++ b/bin/tests/fsaccess_test.c @@ -0,0 +1,34 @@ +#include + +#include +#include + +#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); +} diff --git a/lib/isc/fsaccess.c b/lib/isc/fsaccess.c new file mode 100644 index 0000000000..595577b9e7 --- /dev/null +++ b/lib/isc/fsaccess.c @@ -0,0 +1,82 @@ +/* + * This file contains the OS-independent functionality of the API. + */ +#include +#include +#include + +/* + * Shorthand. Maybe ISC__FSACCESS_PERMISSIONBITS should not even be in + * . 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); +} diff --git a/lib/isc/include/isc/fsaccess.h b/lib/isc/include/isc/fsaccess.h new file mode 100644 index 0000000000..9569a6ff62 --- /dev/null +++ b/lib/isc/include/isc/fsaccess.h @@ -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 + +/* + * 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); diff --git a/lib/isc/unix/fsaccess.c b/lib/isc/unix/fsaccess.c new file mode 100644 index 0000000000..a0d9a5fa71 --- /dev/null +++ b/lib/isc/unix/fsaccess.c @@ -0,0 +1,71 @@ +#include +#include + +#include + +#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); +} diff --git a/lib/isc/win32/fsaccess.c b/lib/isc/win32/fsaccess.c new file mode 100644 index 0000000000..5f7634306f --- /dev/null +++ b/lib/isc/win32/fsaccess.c @@ -0,0 +1,159 @@ +#include +#include +#include + +/* + * 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)); +}