/* * ProFTPD - FTP server daemon * Copyright (c) 2004-2007 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, the ProFTPD Project team and other respective * copyright holders give permission to link this program with OpenSSL, and * distribute the resulting executable, without including the source code * for OpenSSL in the source distribution. */ /* * POSIX ACL checking code (aka POSIX.1e hell) * $Id: mod_facl.c,v 1.11 2007/07/18 16:41:47 castaglia Exp $ */ #include "conf.h" #define MOD_FACL_VERSION "mod_facl/0.4" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030101 # error "ProFTPD 1.3.1rc1 or later required" #endif module facl_module; static int facl_engine = TRUE; static const char *trace_channel = "facl"; #ifdef HAVE_POSIX_ACL #ifdef HAVE_SYS_ACL_H # include #endif /* This header appears on Linux. */ #ifdef HAVE_ACL_LIBACL_H # include #endif static int is_errno_eperm(void) { if (errno == EPERM) return 1; #ifdef EOPNOTSUPP if (errno == EOPNOTSUPP) return 1; #endif /* !EOPNOTSUPP */ return 0; } /* Copied directory from src/fsio.c, since these functions are not * accessible outside of that file. */ static int sys_access(pr_fs_t *fs, const char *path, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { mode_t mask; struct stat st; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) < 0) return -1; /* Root always succeeds. */ if (uid == PR_ROOT_UID) return 0; /* Initialize mask to reflect the permission bits that are applicable for * the given user. mask contains the user-bits if the user ID equals the * ID of the file owner. mask contains the group bits if the group ID * belongs to the group of the file. mask will always contain the other * bits of the permission bits. */ mask = S_IROTH|S_IWOTH|S_IXOTH; if (st.st_uid == uid) mask |= S_IRUSR|S_IWUSR|S_IXUSR; /* Check the current group, as well as all supplementary groups. * Fortunately, we have this information cached, so accessing it is * almost free. */ if (st.st_gid == gid) { mask |= S_IRGRP|S_IWGRP|S_IXGRP; } else { if (suppl_gids) { register unsigned int i = 0; for (i = 0; i < suppl_gids->nelts; i++) { if (st.st_gid == ((gid_t *) suppl_gids->elts)[i]) { mask |= S_IRGRP|S_IWGRP|S_IXGRP; break; } } } } mask &= st.st_mode; /* Perform requested access checks. */ if (mode & R_OK) { if (!(mask & (S_IRUSR|S_IRGRP|S_IROTH))) { errno = EACCES; return -1; } } if (mode & W_OK) { if (!(mask & (S_IWUSR|S_IWGRP|S_IWOTH))) { errno = EACCES; return -1; } } if (mode & X_OK) { if (!(mask & (S_IXUSR|S_IXGRP|S_IXOTH))) { errno = EACCES; return -1; } } /* F_OK already checked by checking the return value of stat. */ return 0; } static int sys_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { return sys_access(fh->fh_fs, fh->fh_path, mode, uid, gid, suppl_gids); } #if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) static acl_perm_t get_facl_perm_for_mode(int mode) { acl_perm_t res; memset(&res, 0, sizeof(acl_perm_t)); if (mode & R_OK) res |= ACL_READ; if (mode & W_OK) res |= ACL_WRITE; if (mode & X_OK) res |= ACL_EXECUTE; return res; } #endif static int check_facl(pool *p, const char *path, int mode, void *acl, int nents, struct stat *st, uid_t uid, gid_t gid, array_header *suppl_gids) { # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) register unsigned int i; int have_access_entry = FALSE, res = -1; pool *acl_pool; acl_t facl = acl; acl_entry_t ae; acl_tag_t ae_type; acl_entry_t acl_user_entry = NULL; acl_entry_t acl_group_entry = NULL; acl_entry_t acl_other_entry = NULL; acl_entry_t acl_mask_entry = NULL; array_header *acl_groups; array_header *acl_users; /* Iterate through all of the ACL entries, sorting them for later * checking. */ res = acl_get_entry(facl, ACL_FIRST_ENTRY, &ae); if (res < 0) { pr_trace_msg(trace_channel, 8, "unable to retrieve first ACL entry for '%s': [%d] %s", path, errno, strerror(errno)); errno = EACCES; return -1; } if (res == 0) { pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s' has no entries", path); errno = EACCES; return -1; } acl_pool = make_sub_pool(p); acl_groups = make_array(acl_pool, 1, sizeof(acl_entry_t)); acl_users = make_array(acl_pool, 1, sizeof(acl_entry_t)); while (res > 0) { if (acl_get_tag_type(ae, &ae_type) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving type of ACL entry for '%s': %s", path, strerror(errno)); res = acl_get_entry(facl, ACL_NEXT_ENTRY, &ae); continue; } if (ae_type & ACL_USER_OBJ) { acl_user_entry = ae; } else if (ae_type & ACL_USER) { acl_entry_t *ae_dup = push_array(acl_users); *ae_dup = ae; } else if (ae_type & ACL_GROUP_OBJ) { acl_group_entry = ae; } else if (ae_type & ACL_GROUP) { acl_entry_t *ae_dup = push_array(acl_groups); *ae_dup = ae; } else if (ae_type & ACL_OTHER) { acl_other_entry = ae; } else if (ae_type & ACL_MASK) { acl_mask_entry = ae; } res = acl_get_entry(facl, ACL_NEXT_ENTRY, &ae); } ae = NULL; /* Select the ACL entry that determines access. */ res = -1; /* 1. If the given user ID matches the file owner, use that entry for * access. */ if (uid == st->st_uid) { /* Check the acl_user_entry for access. */ ae = acl_user_entry; ae_type = ACL_USER_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "user ID %lu matches ACL owner user ID", (unsigned long) uid); } /* 2. If not matched above, and if the given user ID matches one of the * named user entries, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_users->nelts; i++) { acl_entry_t e = ((acl_entry_t *) acl_users->elts)[i]; if (uid == *((uid_t *) acl_get_qualifier(e))) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ ae = e; ae_type = ACL_USER; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "user ID %lu matches ACL allowed users list", (unsigned long) uid); break; } } /* 3. If not matched above, and if one of the group IDs matches the * group owner entry, and the group owner entry contains the * requested permissions, use that entry for access. */ if (!have_access_entry && gid == st->st_gid) { int ret; /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ acl_permset_t perms; if (acl_get_permset(acl_group_entry, &perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret = acl_get_perm_np(perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret = acl_get_perm(perms, get_facl_perm_for_mode(mode)); # endif if (ret == 1) { ae = acl_group_entry; ae_type = ACL_GROUP_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "primary group ID %lu matches ACL owner group ID", (unsigned long) gid); } else if (ret < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in permission set: %s", strerror(errno)); } } if (suppl_gids) { for (i = 0; !have_access_entry && i < suppl_gids->nelts; i++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[i]; if (suppl_gid == st->st_gid) { int ret; /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ acl_permset_t perms; if (acl_get_permset(acl_group_entry, &perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret = acl_get_perm_np(perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret = acl_get_perm(perms, get_facl_perm_for_mode(mode)); # endif if (ret == 1) { ae = acl_group_entry; ae_type = ACL_GROUP_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "supplemental group ID %lu matches ACL owner group ID", (unsigned long) suppl_gid); break; } else if (ret < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in permission set: %s", strerror(errno)); } } } } /* 5. If not matched above, and if one of the group IDs matches one * of the named group entries, and that entry contains the requested * permissions, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_groups->nelts; i++) { acl_entry_t e = ((acl_entry_t *) acl_groups->elts)[i]; if (gid == *((gid_t *) acl_get_qualifier(e))) { int ret; /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ acl_permset_t perms; if (acl_get_permset(e, &perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret = acl_get_perm_np(perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret = acl_get_perm(perms, get_facl_perm_for_mode(mode)); # endif if (ret == 1) { ae = e; ae_type = ACL_GROUP; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "primary group ID %lu matches ACL allowed groups list", (unsigned long) gid); break; } else if (ret < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in permission set: %s", strerror(errno)); } } if (suppl_gids) { register unsigned int j; for (j = 0; !have_access_entry && j < suppl_gids->nelts; j++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[j]; if (suppl_gid == *((gid_t *) acl_get_qualifier(e))) { int ret; /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ acl_permset_t perms; if (acl_get_permset(e, &perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret = acl_get_perm_np(perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret = acl_get_perm(perms, get_facl_perm_for_mode(mode)); # endif if (ret == 1) { ae = e; ae_type = ACL_GROUP; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "supplemental group ID %lu matches ACL allowed groups list", (unsigned long) suppl_gid); break; } else if (ret < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in permission set: %s", strerror(errno)); } } } } } /* 6. If not matched above, and if one of the group IDs matches * the group owner or any of the named group entries, but neither * the group owner entry nor any of the named group entries contains * the requested permissions, access is denied. */ /* XXX implement this condition properly */ /* 7. If not matched above, the other entry determines access. */ if (!have_access_entry) { ae = acl_other_entry; ae_type = ACL_OTHER; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "using ACL 'other' list"); } /* Access determination: * * If either the user owner entry or other entry were used, and the * entry contains the requested permissions, access is permitted. * * Otherwise, if the selected entry and the mask entry both contain * the requested permissions, access is permitted. * * Otherwise, access is denied. */ switch (ae_type) { case ACL_USER_OBJ: case ACL_OTHER: { int ret; acl_permset_t perms; if (acl_get_permset(ae, &perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret = acl_get_perm_np(perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret = acl_get_perm(perms, get_facl_perm_for_mode(mode)); # endif if (ret == 1) { res = 0; } else if (ret < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in permission set: %s", strerror(errno)); } break; } default: { int ret1, ret2; acl_permset_t ent_perms, mask_perms; if (acl_get_permset(ae, &ent_perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving permission set: %s", strerror(errno)); } if (acl_get_permset(acl_mask_entry, &mask_perms) < 0) { pr_trace_msg(trace_channel, 5, "error retrieving mask permission set: %s", strerror(errno)); } # if defined(HAVE_BSD_POSIX_ACL) ret1 = acl_get_perm_np(ent_perms, get_facl_perm_for_mode(mode)); ret2 = acl_get_perm_np(mask_perms, get_facl_perm_for_mode(mode)); # elif defined(HAVE_LINUX_POSIX_ACL) ret1 = acl_get_perm(ent_perms, get_facl_perm_for_mode(mode)); ret2 = acl_get_perm(mask_perms, get_facl_perm_for_mode(mode)); # endif if (ret1 == 1 && ret2 == 1) { res = 0; } else { if (ret1 < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in entry permission set: %s", strerror(errno)); } if (ret2 < 0) { pr_trace_msg(trace_channel, 5, "error checking permissions in mask permission set: %s", strerror(errno)); } } break; } } destroy_pool(acl_pool); if (res < 0) { errno = EACCES; pr_trace_msg(trace_channel, 3, "returning EACCES for path '%s', user ID %lu", path, (unsigned long) uid); } return res; # elif defined(HAVE_SOLARIS_POSIX_ACL) register unsigned int i; int have_access_entry = FALSE, idx, res = -1; pool *acl_pool; aclent_t *acls = acl; aclent_t ae; int ae_type = 0; aclent_t acl_user_entry; aclent_t acl_group_entry; aclent_t acl_other_entry; aclent_t acl_mask_entry; array_header *acl_groups; array_header *acl_users; /* In the absence of any clear documentation, I'll assume that * Solaris ACLs follow the same selection and checking algorithm * as do BSD and Linux. */ res = aclcheck(acls, nents, &idx); switch (res) { case 0: break; case GRP_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "too many GROUP entries"); errno = EACCES; return -1; case USER_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "too many USER entries"); errno = EACCES; return -1; case OTHER_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "too many OTHER entries"); errno = EACCES; return -1; case CLASS_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "too many CLASS entries"); errno = EACCES; return -1; case DUPLICATE_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "duplicate entries"); errno = EACCES; return -1; case MISS_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "missing required entry"); errno = EACCES; return -1; case MEM_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "Out of memory!"); errno = EACCES; return -1; case ENTRY_ERROR: pr_trace_msg(trace_channel, 3, "ill-formed ACL for '%s': %s", path, "invalid entry type"); errno = EACCES; return -1; } /* Iterate through all of the ACL entries, sorting them for later * checking. */ acl_pool = make_sub_pool(p); acl_groups = make_array(acl_pool, 1, sizeof(aclent_t)); acl_users = make_array(acl_pool, 1, sizeof(aclent_t)); for (i = 0; i < nents; i++) { if (acls[i].a_type & USER_OBJ) { memcpy(&acl_user_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & USER) { aclent_t *ae_dup = push_array(acl_users); memcpy(ae_dup, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & GROUP_OBJ) { memcpy(&acl_group_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & GROUP) { aclent_t *ae_dup = push_array(acl_groups); memcpy(ae_dup, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & OTHER_OBJ) { memcpy(&acl_other_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & CLASS_OBJ) { memcpy(&acl_mask_entry, &(acls[i]), sizeof(aclent_t)); } } /* Select the ACL entry that determines access. */ res = -1; /* 1. If the given user ID matches the file owner, use that entry for * access. */ if (uid == st->st_uid) { /* Check the acl_user_entry for access. */ memcpy(&ae, &acl_user_entry, sizeof(aclent_t)); ae_type = USER_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "user ID %lu matches ACL owner user ID", (unsigned long) uid); } /* 2. If not matched above, and f the given user ID matches one of the * named user entries, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_users->nelts; i++) { aclent_t e; memcpy(&e, &(((aclent_t *) acl_users->elts)[i]), sizeof(aclent_t)); if (uid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ memcpy(&ae, &e, sizeof(aclent_t)); ae_type = USER; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "user ID %lu matches ACL allowed users list", (unsigned long) uid); break; } } /* 3. If not matched above, and if one of the group IDs matches the * group owner entry, and the group owner entry contains the * requested permissions, use that entry for access. */ if (!have_access_entry && gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ if (acl_group_entry.a_perm & mode) { memcpy(&ae, &acl_group_entry, sizeof(aclent_t)); ae_type = GROUP_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "primary group ID %lu matches ACL owner group ID", (unsigned long) gid); } } if (suppl_gids) { for (i = 0; !have_access_entry && i < suppl_gids->nelts; i++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[i]; if (suppl_gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ if (acl_group_entry.a_perm & mode) { memcpy(&ae, &acl_group_entry, sizeof(aclent_t)); ae_type = GROUP_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "supplemental group ID %lu matches ACL owner group ID", (unsigned long) suppl_gid); break; } } } } /* 5. If not matched above, and if one of the group IDs matches one * of the named group entries, and that entry contains the requested * permissions, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_groups->nelts; i++) { aclent_t e; memcpy(&e, &(((aclent_t *) acl_groups->elts)[i]), sizeof(aclent_t)); if (gid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ if (e.a_perm & mode) { memcpy(&ae, &e, sizeof(aclent_t)); ae_type = GROUP; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "primary group ID %lu matches ACL allowed groups list", (unsigned long) gid); break; } } if (suppl_gids) { register unsigned int j; for (j = 0; !have_access_entry && j < suppl_gids->nelts; j++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[j]; if (suppl_gid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ if (e.a_perm & mode) { memcpy(&ae, &e, sizeof(aclent_t)); ae_type = GROUP; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "supplemental group ID %lu matches ACL allowed groups list", (unsigned long) suppl_gid); break; } } } } } /* 6. If not matched above, and if one of the group IDs matches * the group owner or any of the named group entries, but neither * the group owner entry nor any of the named group entries contains * the requested permissions, access is denied. */ /* XXX implement this condition properly */ /* 7. If not matched above, the other entry determines access. */ if (!have_access_entry) { memcpy(&ae, &acl_other_entry, sizeof(aclent_t)); ae_type = OTHER_OBJ; have_access_entry = TRUE; pr_trace_msg(trace_channel, 10, "using ACL 'other' list"); } /* Access determination: * * If either the user owner entry or other entry were used, and the * entry contains the requested permissions, access is permitted. * * Otherwise, if the selected entry and the mask entry both contain * the requested permissions, access is permitted. * * Otherwise, access is denied. */ switch (ae_type) { case USER_OBJ: case OTHER_OBJ: if (ae.a_perm & mode) res = 0; break; default: if ((ae.a_perm & mode) && (acl_mask_entry.a_perm & mode)) res = 0; break; } destroy_pool(acl_pool); if (res < 0) { errno = EACCES; pr_trace_msg(trace_channel, 3, "returning EACCES for path '%s', user ID %lu", path, (unsigned long) uid); } return res; # endif /* HAVE_SOLARIS_POSIX_ACL */ } # if defined(PR_USE_FACL) /* FSIO handlers */ static int facl_fsio_access(pr_fs_t *fs, const char *path, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { int nents = 0; struct stat st; void *acls; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) < 0) return -1; /* Look up the acl for this path. */ # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) acls = acl_get_file(path, ACL_TYPE_ACCESS); if (!acls) { pr_trace_msg(trace_channel, 5, "unable to retrieve ACL for '%s': [%d] %s", path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", path); /* Fall back to the custom access() function defined in src/fsio. * Since that sys_access() function there is not public, we have * to duplicate the code. For now, that is, until a more clean * arrangement can be found. */ if (sys_access(fs, path, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", path, strerror(errno)); return -1; } return 0; } else { return -1; } } # elif defined(HAVE_SOLARIS_POSIX_ACL) nents = acl(path, GETACLCNT, 0, NULL); if (nents < 0) { pr_trace_msg(trace_channel, 5, "unable to retrieve ACL count for '%s': [%d] %s", path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", path); if (sys_access(fs, path, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", path, strerror(errno)); return -1; } return 0; } else { return -1; } } pr_trace_msg(trace_channel, 10, "acl(2) returned %d ACL entries for path '%s'", nents, path); acls = pcalloc(fs->fs_pool, nents * sizeof(aclent_t)); nents = acl(path, GETACL, nents, acls); if (nents < 0) { pr_trace_msg(trace_channel, 5, "unable to retrieve ACL for '%s': [%d] %s", path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", path); if (sys_access(fs, path, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", path, strerror(errno)); return -1; } return 0; } else { return -1; } } # endif return check_facl(fs->fs_pool, path, mode, acls, nents, &st, uid, gid, suppl_gids); } static int facl_fsio_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { int nents = 0; struct stat st; void *acls; pr_fs_clear_cache(); if (pr_fsio_fstat(fh, &st) < 0) return -1; /* Look up the acl for this fd. */ # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) acls = acl_get_fd(PR_FH_FD(fh)); if (!acls) { pr_trace_msg(trace_channel, 10, "unable to retrieve ACL for '%s': [%d] %s", fh->fh_path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", fh->fh_path); /* Fall back to the custom faccess() function defined in src/fsio. * Since that sys_faccess() function there is not public, we have * to duplicate the code. For now, that is, until a more clean * arrangement can be found. */ if (sys_faccess(fh, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", fh->fh_path, strerror(errno)); return -1; } return 0; } else { return -1; } } # elif defined(HAVE_SOLARIS_POSIX_ACL) nents = facl(PR_FH_FD(fh), GETACLCNT, 0, NULL); if (nents < 0) { pr_trace_msg(trace_channel, 10, "unable to retrieve ACL count for '%s': [%d] %s", fh->fh_path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", fh->fh_path); if (sys_faccess(fh, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", fh->fh_path, strerror(errno)); return -1; } return 0; } else { return -1; } } acls = pcalloc(fh->fh_fs->fs_pool, nents * sizeof(aclent_t)); nents = facl(PR_FH_FD(fh), GETACL, nents, acls); if (nents < 0) { pr_trace_msg(trace_channel, 10, "unable to retrieve ACL for '%s': [%d] %s", fh->fh_path, errno, strerror(errno)); if (is_errno_eperm()) { pr_trace_msg(trace_channel, 3, "ACL retrieval operation not supported " "for '%s', falling back to normal access check", fh->fh_path); if (sys_faccess(fh, mode, uid, gid, suppl_gids) < 0) { pr_trace_msg(trace_channel, 6, "normal access check for '%s' " "failed: %s", fh->fh_path, strerror(errno)); return -1; } return 0; } else { return -1; } } # endif return check_facl(fh->fh_fs->fs_pool, fh->fh_path, mode, acls, nents, &st, uid, gid, suppl_gids); } # endif /* !PR_USE_FACL */ #endif /* HAVE_POSIX_ACL */ #if defined(PR_SHARED_MODULE) static void facl_mod_unload_ev(const void *event_data, void *user_data) { if (strcmp("mod_facl.c", (const char *) event_data) == 0) { pr_event_unregister(&facl_module, NULL, NULL); pr_unregister_fs("facl"); } } #endif /* !PR_SHARED_MODULE */ /* Initialization routines */ static int facl_init(void) { #if defined(PR_USE_FACL) && defined(HAVE_POSIX_ACL) pr_fs_t *fs; #endif /* PR_USE_FACL and HAVE_POSIX_ACL */ #if defined(PR_SHARED_MODULE) pr_event_register(&facl_module, "core.module-unload", facl_mod_unload_ev, NULL); #endif /* PR_SHARED_MODULE */ if (!facl_engine) return 0; #if defined(PR_USE_FACL) && defined(HAVE_POSIX_ACL) fs = pr_register_fs(permanent_pool, "facl", "/"); if (!fs) { pr_log_pri(PR_LOG_ERR, MOD_FACL_VERSION ": error registering 'facl' FS: %s", strerror(errno)); return -1; } pr_log_debug(DEBUG6, MOD_FACL_VERSION ": registered 'facl' FS"); /* Ensure that our ACL-checking handlers are used. */ fs->access = facl_fsio_access; fs->faccess = facl_fsio_faccess; # if defined(PR_SHARED_MODULE) pr_event_register(&facl_module, "core.module-unload", facl_mod_unload_ev, NULL); # endif /* !PR_SHARED_MODULE */ #endif /* PR_USE_FACL and HAVE_POSIX_ACL */ return 0; } /* Configuration handlers */ /* usage: FACLEngine on|off */ MODRET set_faclengine(cmd_rec *cmd) { int bool = -1; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); bool = get_boolean(cmd, 1); if (bool == -1) CONF_ERROR(cmd, "expected Boolean parameter"); facl_engine = bool; return PR_HANDLED(cmd); } /* Module Tables */ static conftable facl_conftab[] = { { "FACLEngine", set_faclengine, NULL }, { NULL } }; module facl_module = { /* Always NULL */ NULL, NULL, /* Module API version */ 0x20, /* Module name */ "facl", /* Module configuration directive handlers */ facl_conftab, /* Module command handlers */ NULL, /* Module authentication handlers */ NULL, /* Module initialization */ facl_init, /* Session initialization */ NULL, /* Module version */ MOD_FACL_VERSION };