Ejemplo n.º 1
0
static int wrap_is_usable_file(char *filename) {
  struct stat st;
  pr_fh_t *fh = NULL;

  /* check the easy case first */
  if (filename == NULL)
    return FALSE;

  /* Make sure that the current process can _read_ the file. */
  fh = pr_fsio_open(filename, O_RDONLY);
  if (fh == NULL) {
    int xerrno = errno;

    pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": failed to read \"%s\": %s",
      filename, strerror(xerrno));

    errno = xerrno;
    return FALSE;
  }

  if (pr_fsio_fstat(fh, &st) < 0) {
    int xerrno = errno;

    pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": failed to stat \"%s\": %s",
      filename, strerror(xerrno));

    pr_fsio_close(fh);
    errno = xerrno;
    return FALSE;
  }

  if (S_ISDIR(st.st_mode)) {
    int xerrno = EISDIR;

    pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": unable to use \"%s\": %s",
      filename, strerror(xerrno));

    pr_fsio_close(fh);
    errno = xerrno;
    return FALSE;
  }

  pr_fsio_close(fh);
  return TRUE;
}
Ejemplo n.º 2
0
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_log_debug(DEBUG10, "FS: unable to retrieve first ACL entry for '%s': %s",
      path, strerror(errno));
    errno = EACCES;
    return -1;
  }

  if (res == 0) {
    pr_log_debug(DEBUG3, "FS: 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_log_debug(DEBUG5,
        "FS: 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_copy_entry(acl_user_entry, ae);

    } else if (ae_type & ACL_USER) {
      acl_entry_t *ae_dup = push_array(acl_users);
      acl_copy_entry(*ae_dup, ae);

    } else if (ae_type & ACL_GROUP_OBJ) {
      acl_copy_entry(acl_group_entry, ae);

    } else if (ae_type & ACL_GROUP) {
      acl_entry_t *ae_dup = push_array(acl_groups);
      acl_copy_entry(*ae_dup, ae);

    } else if (ae_type & ACL_OTHER) {
      acl_copy_entry(acl_other_entry, ae);

    } else if (ae_type & ACL_MASK) {
      acl_copy_entry(acl_mask_entry, ae);
    }

    res = acl_get_entry(facl, ACL_NEXT_ENTRY, &ae);
  }

  /* 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. */
    acl_copy_entry(ae, acl_user_entry);
    ae_type = ACL_USER_OBJ;
    have_access_entry = TRUE;
  }

  /* 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++) {
    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.
       */
      acl_copy_entry(ae, e);
      ae_type = ACL_USER;
      have_access_entry = TRUE;
      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.
     */
    acl_permset_t perms;
    acl_get_permset(acl_group_entry, &perms);

#  if defined(HAVE_BSD_POSIX_ACL)
    if (acl_get_perm_np(perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
    if (acl_get_perm(perms, mode) == 1) {
#  endif
      acl_copy_entry(ae, acl_group_entry);
      ae_type = ACL_GROUP_OBJ;
      have_access_entry = TRUE;
    }
  }

  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.
         */
        acl_permset_t perms;
        acl_get_permset(acl_group_entry, &perms);

#  if defined(HAVE_BSD_POSIX_ACL)
        if (acl_get_perm_np(perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
        if (acl_get_perm(perms, mode) == 1) {
#  endif
          acl_copy_entry(ae, acl_group_entry);
          ae_type = ACL_GROUP_OBJ;
          have_access_entry = TRUE;
          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++) {
    acl_entry_t e = ((acl_entry_t *) acl_groups->elts)[i];

    if (gid == *((gid_t *) acl_get_qualifier(e))) {

      /* Check this entry for access. Note that it'll need to
       * be modified by the mask, if any, later.
       */
      acl_permset_t perms;
      acl_get_permset(e, &perms);

#  if defined(HAVE_BSD_POSIX_ACL)
      if (acl_get_perm_np(perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
      if (acl_get_perm(perms, mode) == 1) {
#  endif
        acl_copy_entry(ae, e);
        ae_type = ACL_GROUP;
        have_access_entry = TRUE;
        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 == *((gid_t *) acl_get_qualifier(e))) {
          /* Check this entry for access. Note that it'll need to
           * be modified by the mask, if any, later.
           */
          acl_permset_t perms;
          acl_get_permset(e, &perms);

#  if defined(HAVE_BSD_POSIX_ACL)
          if (acl_get_perm_np(perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
          if (acl_get_perm(perms, mode) == 1) {
#  endif
            acl_copy_entry(ae, e);
            ae_type = ACL_GROUP;
            have_access_entry = TRUE;
            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.
   */

  /* 7. If not matched above, the other entry determines access.
   */
  if (!have_access_entry) {
    acl_copy_entry(ae, acl_other_entry);
    ae_type = ACL_OTHER;
    have_access_entry = TRUE;
  }

  /* 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: {
      acl_permset_t perms;
      acl_get_permset(ae, &perms);

#  if defined(HAVE_BSD_POSIX_ACL)
      if (acl_get_perm_np(perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
      if (acl_get_perm(perms, mode) == 1) {
#  endif
        res = 0;
      }

      break;
    }

    default: {
      acl_permset_t ent_perms, mask_perms;
      acl_get_permset(ae, &ent_perms);
      acl_get_permset(acl_mask_entry, &mask_perms);

#  if defined(HAVE_BSD_POSIX_ACL)
      if (acl_get_perm_np(ent_perms, mode) == 1 &&
          acl_get_perm_np(mask_perms, mode) == 1) {
#  elif defined(HAVE_LINUX_POSIX_ACL)
      if (acl_get_perm(ent_perms, mode) == 1 &&
          acl_get_perm(mask_perms, mode) == 1) {
#  endif
        res = 0;
      }

      break;
    }
  }

  destroy_pool(acl_pool);

  if (res < 0)
    errno = EACCES;
  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_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "too many GROUP entries");
      errno = EACCES;
      return -1;

    case USER_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "too many USER entries");
      errno = EACCES;
      return -1;

    case OTHER_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "too many OTHER entries");
      errno = EACCES;
      return -1;

    case CLASS_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "too many CLASS entries");
      errno = EACCES;
      return -1;

    case DUPLICATE_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "duplicate entries");
      errno = EACCES;
      return -1;

    case MISS_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "missing required entry");
      errno = EACCES;
      return -1;

    case MEM_ERROR:
      pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path,
        "Out of memory!");
      errno = EACCES;
      return -1;

    case ENTRY_ERROR:
      pr_log_debug(DEBUG3, "FS: 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;
  }

  /* 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;
      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;
    }
  }

  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;
          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;
        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;
            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.
   */

  /* 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;
  }

  /* 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;
  return res;
# endif /* HAVE_SOLARIS_POSIX_ACL */
}

/* 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_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s",
      path, strerror(errno));
    return -1;
  }

# elif defined(HAVE_SOLARIS_POSIX_ACL)

  nents = acl(path, GETACLCNT, 0, NULL);
  if (nents < 0) {
    pr_log_debug(DEBUG10, "FS: unable to retrieve ACL count for '%s': %s",
      path, strerror(errno));
    return -1;
  }

  acls = pcalloc(fs->fs_pool, nents * sizeof(aclent_t));

  nents = acl(path, GETACL, nents, acls);
  if (nents < 0) {
    pr_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s",
      path, strerror(errno));
    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_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s",
      fh->fh_path, strerror(errno));
    return -1;
  }

# elif defined(HAVE_SOLARIS_POSIX_ACL)

  nents = facl(PR_FH_FD(fh), GETACLCNT, 0, NULL);
  if (nents < 0) {
    pr_log_debug(DEBUG10, "FS: unable to retrieve ACL count for '%s': %s",
      fh->fh_path, strerror(errno));
    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_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s",
      fh->fh_path, strerror(errno));
    return -1;
  }
# endif

  return check_facl(fh->fh_fs->fs_pool, fh->fh_path, mode, acls, nents, &st,
    uid, gid, suppl_gids);
}
#endif /* HAVE_POSIX_ACL */

/* Initialization routines
 */

static int facl_init(void) {
#if defined(PR_USE_FACL) && defined(HAVE_POSIX_ACL)
  pr_fs_t *fs = pr_register_fs(permanent_pool, "facl", "/");
  if (!fs) {
    pr_log_pri(PR_LOG_ERR, MOD_FACL_VERSION ": error registering fs: %s",
      strerror(errno));
    return -1;
  }

  /* Ensure that our ACL-checking handlers are used. */
  fs->access = facl_fsio_access;
  fs->faccess = facl_fsio_faccess;
#endif /* PR_USE_FACL and HAVE_POSIX_ACL */

  return 0;
}

/* Module Tables
 */

module facl_module = {
  /* Always NULL */
  NULL, NULL,

  /* Module API version */
  0x20,

  /* Module name */
  "facl",

  /* Module configuration directive handlers */
  NULL,

  /* Module command handlers */
  NULL,

  /* Module authentication handlers */
  NULL,

  /* Module initialization */
  facl_init,

  /* Session initialization */
  NULL,

  /* Module version */
  MOD_FACL_VERSION
};
Ejemplo n.º 3
0
static sftp_keystore_t *filestore_open(pool *parent_pool,
    int requested_key_type, const char *store_info, const char *user) {
  int xerrno;
  sftp_keystore_t *store;
  pool *filestore_pool;
  struct filestore_data *store_data;
  pr_fh_t *fh;
  char buf[PR_TUNABLE_PATH_MAX+1], *path;
  struct stat st;

  filestore_pool = make_sub_pool(parent_pool);
  pr_pool_tag(filestore_pool, "SFTP File-based Keystore Pool");

  store = pcalloc(filestore_pool, sizeof(sftp_keystore_t));
  store->keystore_pool = filestore_pool;

  /* Open the file.  The given path (store_info) may need to be
   * interpolated.
   */
  session.user = (char *) user;

  memset(buf, '\0', sizeof(buf));
  switch (pr_fs_interpolate(store_info, buf, sizeof(buf)-1)) {
    case 1:
      /* Interpolate occurred; make a copy of the interpolated path. */
      path = pstrdup(filestore_pool, buf);
      break;

    default:
      /* Otherwise, use the path as is. */
      path = pstrdup(filestore_pool, store_info);
      break;
  }

  session.user = NULL;

  PRIVS_ROOT
  fh = pr_fsio_open(path, O_RDONLY|O_NONBLOCK);
  xerrno = errno;
  PRIVS_RELINQUISH

  if (fh == NULL) {
    destroy_pool(filestore_pool);
    errno = xerrno;
    return NULL;
  }

  if (pr_fsio_set_block(fh) < 0) {
   xerrno = errno;

    destroy_pool(filestore_pool);
    (void) pr_fsio_close(fh);

    errno = xerrno;
    return NULL;
  }

  /* Stat the opened file to determine the optimal buffer size for IO. */
  memset(&st, 0, sizeof(st));
  if (pr_fsio_fstat(fh, &st) < 0) {
    xerrno = errno;

    destroy_pool(filestore_pool);
    (void) pr_fsio_close(fh);

    errno = xerrno;
    return NULL;
  }

  if (S_ISDIR(st.st_mode)) {
    destroy_pool(filestore_pool);
    (void) pr_fsio_close(fh);

    errno = EISDIR;
    return NULL;
  }

  fh->fh_iosz = st.st_blksize;

  store_data = pcalloc(filestore_pool, sizeof(struct filestore_data));
  store->keystore_data = store_data;

  store_data->path = path;
  store_data->fh = fh;
  store_data->lineno = 0;

  store->store_ktypes = requested_key_type;

  switch (requested_key_type) {
    case SFTP_SSH2_HOST_KEY_STORE:
      store->verify_host_key = filestore_verify_host_key;
      break;

    case SFTP_SSH2_USER_KEY_STORE:
      store->verify_user_key = filestore_verify_user_key; 
      break;
  }

  store->store_close = filestore_close;
  return store;
}
Ejemplo n.º 4
0
int pr_parser_parse_file(pool *p, const char *path, config_rec *start,
    int flags) {
  pr_fh_t *fh;
  struct stat st;
  struct config_src *cs;
  cmd_rec *cmd;
  pool *tmp_pool;
  char *buf, *report_path;
  size_t bufsz;

  if (path == NULL) {
    errno = EINVAL;
    return -1;
  }

  if (parser_servstack == NULL) {
    errno = EPERM;
    return -1;
  }

  tmp_pool = make_sub_pool(p ? p : permanent_pool);
  pr_pool_tag(tmp_pool, "parser file pool");

  report_path = (char *) path;
  if (session.chroot_path) {
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);
  }

  if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
    pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path);
  }

  fh = pr_fsio_open(path, O_RDONLY);
  if (fh == NULL) {
    int xerrno = errno;

    destroy_pool(tmp_pool);

    errno = xerrno;
    return -1;
  }

  /* Stat the opened file to determine the optimal buffer size for IO. */
  memset(&st, 0, sizeof(st));
  if (pr_fsio_fstat(fh, &st) < 0) {
    int xerrno = errno;

    pr_fsio_close(fh);
    destroy_pool(tmp_pool);

    errno = xerrno;
    return -1;
  }

  if (S_ISDIR(st.st_mode)) {
    pr_fsio_close(fh);
    destroy_pool(tmp_pool);

    errno = EISDIR;
    return -1;
  }

  /* Advise the platform that we will be only reading this file
   * sequentially.
   */
  pr_fs_fadvise(PR_FH_FD(fh), 0, 0, PR_FS_FADVISE_SEQUENTIAL);

  /* Check for world-writable files (and later, files in world-writable
   * directories).
   *
   * For now, just warn about these; later, we will be more draconian.
   */
  if (st.st_mode & S_IWOTH) {
    pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable",
     path); 
  }

  fh->fh_iosz = st.st_blksize;

  /* Push the configuration information onto the stack of configuration
   * sources.
   */
  cs = add_config_source(fh);

  if (start != NULL) {
    (void) pr_parser_config_ctxt_push(start);
  }

  bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE;
  buf = pcalloc(tmp_pool, bufsz + 1);

  while (pr_parser_read_line(buf, bufsz) != NULL) {
    pr_signals_handle();

    cmd = pr_parser_parse_line(tmp_pool, buf, 0);
    if (cmd == NULL) {
      continue;
    }

    if (cmd->argc) {
      conftable *conftab;
      char found = FALSE;

      cmd->server = *parser_curr_server;
      cmd->config = *parser_curr_config;

      conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL,
        &cmd->stash_index, &cmd->stash_hash);
      while (conftab != NULL) {
        modret_t *mr;

        pr_signals_handle();

        cmd->argv[0] = conftab->directive;

        pr_trace_msg(trace_channel, 7,
          "dispatching directive '%s' to module mod_%s", conftab->directive,
          conftab->m->name);

        mr = pr_module_call(conftab->m, conftab->handler, cmd);
        if (mr != NULL) {
          if (MODRET_ISERROR(mr)) {
            if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
              pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'",
                MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
              destroy_pool(tmp_pool);
              errno = EPERM;
              return -1;
            }

            pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'",
              MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
          }
        }

        if (!MODRET_ISDECLINED(mr)) {
          found = TRUE;
        }

        conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab,
          &cmd->stash_index, &cmd->stash_hash);
      }

      if (cmd->tmp_pool) {
        destroy_pool(cmd->tmp_pool);
      }

      if (found == FALSE) {
        register unsigned int i;
        char *name;
        size_t namelen;
        int non_ascii = FALSE;

        /* I encountered a case where a particular configuration file had
         * what APPEARED to be a valid directive, but the parser kept reporting
         * that the directive was unknown.  I now suspect that the file in
         * question had embedded UTF8 characters (spaces, perhaps), which
         * would appear as normal spaces in e.g. UTF8-aware editors/terminals,
         * but which the parser would rightly refuse.
         *
         * So to indicate that this might be the case, check for any non-ASCII
         * characters in the "unknown" directive name, and if found, log
         * about them.
         */

        name = cmd->argv[0];
        namelen = strlen(name);

        for (i = 0; i < namelen; i++) {
          if (!isascii((int) name[i])) {
            non_ascii = TRUE;
            break;
          }
        }

        if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
          pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive "
            "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
          if (non_ascii) {
            pr_log_pri(PR_LOG_WARNING, "fatal: malformed directive name "
              "'%s' (contains non-ASCII characters)", name);

          } else {
            array_header *directives, *similars;

            directives = get_all_directives(tmp_pool);
            similars = pr_str_get_similars(tmp_pool, name, directives, 0,
              PR_STR_FL_IGNORE_CASE);
            if (similars != NULL &&
                similars->nelts > 0) {
              unsigned int nelts;
              const char **names, *msg;

              names = similars->elts;
              nelts = similars->nelts;
              if (nelts > 4) {
                nelts = 4;
              }

              msg = "fatal: Did you mean:";

              if (nelts == 1) {
                msg = pstrcat(tmp_pool, msg, " ", names[0], NULL);

              } else {
                for (i = 0; i < nelts; i++) {
                  msg = pstrcat(tmp_pool, msg, "\n  ", names[i], NULL);
                }
              }

              pr_log_pri(PR_LOG_WARNING, "%s", msg);
            }
          }

          destroy_pool(tmp_pool);
          errno = EPERM;
          return -1;
        }

        pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive "
          "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path);
        if (non_ascii) {
          pr_log_pri(PR_LOG_WARNING, "warning: malformed directive name "
            "'%s' (contains non-ASCII characters)", name);
        }
      }
    }

    destroy_pool(cmd->pool);
    memset(buf, '\0', bufsz);
  }

  /* Pop this configuration stream from the stack. */
  remove_config_source();

  pr_fsio_close(fh);

  destroy_pool(tmp_pool);
  return 0;
}
Ejemplo n.º 5
0
int pr_parser_parse_file(pool *p, const char *path, config_rec *start,
    int flags) {
  pr_fh_t *fh;
  struct stat st;
  struct config_src *cs;
  cmd_rec *cmd;
  pool *tmp_pool;
  char *report_path;

  if (path == NULL) {
    errno = EINVAL;
    return -1;
  }

  tmp_pool = make_sub_pool(p ? p : permanent_pool);
  pr_pool_tag(tmp_pool, "parser file pool");

  report_path = (char *) path;
  if (session.chroot_path)
    report_path = pdircat(tmp_pool, session.chroot_path, path, NULL);

  if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG))
    pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path);

  fh = pr_fsio_open(path, O_RDONLY);
  if (fh == NULL) {
    int xerrno = errno;

    destroy_pool(tmp_pool);

    errno = xerrno;
    return -1;
  }

  /* Stat the opened file to determine the optimal buffer size for IO. */
  memset(&st, 0, sizeof(st));
  if (pr_fsio_fstat(fh, &st) < 0) {
    int xerrno = errno;

    pr_fsio_close(fh);
    destroy_pool(tmp_pool);

    errno = xerrno;
    return -1;
  }

  if (S_ISDIR(st.st_mode)) {
    pr_fsio_close(fh);
    destroy_pool(tmp_pool);

    errno = EISDIR;
    return -1;
  }

  /* Check for world-writable files (and later, files in world-writable
   * directories).
   *
   * For now, just warn about these; later, we will be more draconian.
   */
  if (st.st_mode & S_IWOTH) {
    pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable",
     path); 
  }

  fh->fh_iosz = st.st_blksize;

  /* Push the configuration information onto the stack of configuration
   * sources.
   */
  cs = add_config_source(fh);

  if (start) 
    add_config_ctxt(start);

  while ((cmd = pr_parser_parse_line(tmp_pool)) != NULL) {
    pr_signals_handle();

    if (cmd->argc) {
      conftable *conftab;
      char found = FALSE;

      cmd->server = *parser_curr_server;
      cmd->config = *parser_curr_config;

      conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL,
        &cmd->stash_index, &cmd->stash_hash);

      while (conftab) {
        modret_t *mr;

        pr_signals_handle();

        cmd->argv[0] = conftab->directive;

        pr_trace_msg(trace_channel, 7,
          "dispatching directive '%s' to module mod_%s", conftab->directive,
          conftab->m->name);

        mr = pr_module_call(conftab->m, conftab->handler, cmd);
        if (mr != NULL) {
          if (MODRET_ISERROR(mr)) {

            if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
              pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'",
                MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
              exit(1);

            } else {
              pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'",
                MODRET_ERRMSG(mr), cs->cs_lineno, report_path);
            }
          }
        }

        if (!MODRET_ISDECLINED(mr)) {
          found = TRUE;
        }

        conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab,
          &cmd->stash_index, &cmd->stash_hash);
      }

      if (cmd->tmp_pool)
        destroy_pool(cmd->tmp_pool);

      if (!found) {

        if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) {
          pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive "
            "'%s' on line %u of '%s'", cmd->argv[0], cs->cs_lineno,
            report_path);
          exit(1);

        } else {
          pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive "
            "'%s' on line %u of '%s'", cmd->argv[0], cs->cs_lineno,
            report_path);
        }
      }
    }

    destroy_pool(cmd->pool);
  }

  /* Pop this configuration stream from the stack. */
  remove_config_source();

  pr_fsio_close(fh);

  destroy_pool(tmp_pool);
  return 0;
}
Ejemplo n.º 6
0
static int display_fh(pr_fh_t *fh, const char *fs, const char *code,
    int flags) {
  struct stat st;
  char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
  int len, res;
  unsigned int *current_clients = NULL;
  unsigned int *max_clients = NULL;
  off_t fs_size = 0;
  pool *p;
  void *v;
  xaset_t *s;
  config_rec *c = NULL;
  const char *serverfqdn = main_server->ServerFQDN;
  char *outs, mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'},
    mg_max[12] = "unlimited";
  char total_files_in[12] = {'\0'}, total_files_out[12] = {'\0'},
    total_files_xfer[12] = {'\0'};
  char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'},
    mg_xfer_bytes[12] = {'\0'}, mg_cur_class[12] = {'\0'};
  char mg_xfer_units[12] = {'\0'}, *user;
  const char *mg_time;
  char *rfc1413_ident = NULL;

  /* Stat the opened file to determine the optimal buffer size for IO. */
  memset(&st, 0, sizeof(st));
  if (pr_fsio_fstat(fh, &st) == 0) {
    fh->fh_iosz = st.st_blksize;
  }

  /* Note: The size provided by pr_fs_getsize() is in KB, not bytes. */
  res = pr_fs_fgetsize(fh->fh_fd, &fs_size);
  if (res < 0 &&
      errno != ENOSYS) {
    (void) pr_log_debug(DEBUG7, "error getting filesystem size for '%s': %s",
      fh->fh_path, strerror(errno));
    fs_size = 0;
  }

  snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size);
  format_size_str(mg_size_units, sizeof(mg_size_units), fs_size);

  p = make_sub_pool(session.pool);
  pr_pool_tag(p, "Display Pool");

  s = (session.anon_config ? session.anon_config->subset : main_server->conf);

  mg_time = pr_strtime(time(NULL));

  max_clients = get_param_ptr(s, "MaxClients", FALSE);

  v = pr_table_get(session.notes, "client-count", NULL);
  if (v) {
    current_clients = v;
  }

  snprintf(mg_cur, sizeof(mg_cur), "%u", current_clients ? *current_clients: 1);

  if (session.conn_class != NULL &&
      session.conn_class->cls_name != NULL) {
    unsigned int *class_clients = NULL;
    config_rec *maxc = NULL;
    unsigned int maxclients = 0;

    v = pr_table_get(session.notes, "class-client-count", NULL);
    if (v) {
      class_clients = v;
    }

    snprintf(mg_cur_class, sizeof(mg_cur_class), "%u",
      class_clients ? *class_clients : 0);

    /* For the %z variable, first we scan through the MaxClientsPerClass,
     * and use the first applicable one.  If none are found, look for
     * any MaxClients set.
     */

    maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass",
      FALSE);

    while (maxc) {
      pr_signals_handle();

      if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) {
        maxc = find_config_next(maxc, maxc->next, CONF_PARAM,
          "MaxClientsPerClass", FALSE);
        continue;
      }

      maxclients = *((unsigned int *) maxc->argv[1]);
      break;
    }

    if (maxclients == 0) {
      maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE);

      if (maxc)
        maxclients = *((unsigned int *) maxc->argv[0]);
    }

    snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);

  } else {
    snprintf(mg_class_limit, sizeof(mg_class_limit), "%u",
      max_clients ? *max_clients : 0);
    snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0);
  }

  snprintf(mg_xfer_bytes, sizeof(mg_xfer_bytes), "%" PR_LU,
    (pr_off_t) session.total_bytes >> 10);
  snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "B",
    (pr_off_t) session.total_bytes);

  if (session.total_bytes >= 10240) {
    snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "kB",
      (pr_off_t) session.total_bytes >> 10);

  } else if ((session.total_bytes >> 10) >= 10240) {
Ejemplo n.º 7
0
const char *sftp_display_fh_get_msg(pool *p, pr_fh_t *fh) {
  struct stat st;
  char buf[PR_TUNABLE_BUFFER_SIZE], *msg = "";
  int len, res;
  unsigned int *current_clients = NULL;
  unsigned int *max_clients = NULL;
  off_t fs_size = 0;
  void *v;
  const char *serverfqdn = main_server->ServerFQDN;
  char *outs, mg_size[12] = {'\0'}, mg_size_units[12] = {'\0'},
    mg_max[12] = "unlimited";
  char mg_class_limit[12] = {'\0'}, mg_cur[12] = {'\0'},
    mg_cur_class[12] = {'\0'};
  const char *mg_time;
  char *rfc1413_ident = NULL, *user = NULL;

  /* Stat the opened file to determine the optimal buffer size for IO. */
  memset(&st, 0, sizeof(st));
  if (pr_fsio_fstat(fh, &st) == 0) {
    fh->fh_iosz = st.st_blksize;
  }

  res = pr_fs_fgetsize(fh->fh_fd, &fs_size);
  if (res < 0 &&
      errno != ENOSYS) {
    (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
      "error getting filesystem size for '%s': %s", fh->fh_path,
      strerror(errno));
    fs_size = 0;
  }

  snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size);
  format_size_str(mg_size_units, sizeof(mg_size_units), fs_size);

  mg_time = pr_strtime(time(NULL));

  max_clients = get_param_ptr(main_server->conf, "MaxClients", FALSE);

  v = pr_table_get(session.notes, "client-count", NULL);
  if (v) {
    current_clients = v;
  }

  snprintf(mg_cur, sizeof(mg_cur), "%u", current_clients ? *current_clients: 1);

  if (session.conn_class != NULL &&
      session.conn_class->cls_name) {
    unsigned int *class_clients = NULL;
    config_rec *maxc = NULL;
    unsigned int maxclients = 0;

    v = pr_table_get(session.notes, "class-client-count", NULL);
    if (v) {
      class_clients = v;
    }

    snprintf(mg_cur_class, sizeof(mg_cur_class), "%u",
      class_clients ? *class_clients : 0);

    /* For the %z variable, first we scan through the MaxClientsPerClass,
     * and use the first applicable one.  If none are found, look for
     * any MaxClients set.
     */

    maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass",
      FALSE);

    while (maxc) {
      pr_signals_handle();

      if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) {
        maxc = find_config_next(maxc, maxc->next, CONF_PARAM,
          "MaxClientsPerClass", FALSE);
        continue;
      }

      maxclients = *((unsigned int *) maxc->argv[1]);
      break;
    }

    if (maxclients == 0) {
      maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE);

      if (maxc)
        maxclients = *((unsigned int *) maxc->argv[0]);
    }

    snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);

  } else {
    snprintf(mg_class_limit, sizeof(mg_class_limit), "%u",
      max_clients ? *max_clients : 0);
    snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0);
  }

  snprintf(mg_max, sizeof(mg_max), "%u", max_clients ? *max_clients : 0);

  user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
  if (user == NULL)
    user = "";

  rfc1413_ident = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
  if (rfc1413_ident == NULL) {
    rfc1413_ident = "UNKNOWN";
  }

  memset(buf, '\0', sizeof(buf));
  while (pr_fsio_gets(buf, sizeof(buf), fh) != NULL) {
    char *tmp;

    pr_signals_handle();

    buf[sizeof(buf)-1] = '\0';
    len = strlen(buf);

    while (len > 0 &&
           (buf[len-1] == '\r' || buf[len-1] == '\n')) {
      pr_signals_handle();

      buf[len-1] = '\0';
      len--;
    }

    /* Check for any Variable-type strings. */
    tmp = strstr(buf, "%{");
    while (tmp) {
      char *key, *tmp2;
      const char *val;

      pr_signals_handle();

      tmp2 = strchr(tmp, '}');
      if (tmp2 == NULL) {
        /* No closing '}' found in this string, so no need to look for any
         * aother '%{' opening sequence.  Just move on.
         */
        tmp = NULL;
        break;
      }

      key = pstrndup(p, tmp, tmp2 - tmp + 1);

      /* There are a couple of special-case keys to watch for:
       *
       *   env:$var
       *   time:$fmt
       *
       * The Var API does not easily support returning values for keys
       * where part of the value depends on part of the key.  That's why
       * these keys are handled here, instead of in pr_var_get().
       */

      if (strncmp(key, "%{time:", 7) == 0) {
        char time_str[128], *fmt;
        time_t now;
        struct tm *time_info;

        fmt = pstrndup(p, key + 7, strlen(key) - 8);

        now = time(NULL);
        time_info = pr_localtime(NULL, &now);

        memset(time_str, 0, sizeof(time_str));
        strftime(time_str, sizeof(time_str), fmt, time_info);

        val = pstrdup(p, time_str);

      } else if (strncmp(key, "%{env:", 6) == 0) {
        char *env_var;

        env_var = pstrndup(p, key + 6, strlen(key) - 7);
        val = pr_env_get(p, env_var);
        if (val == NULL) {
          pr_trace_msg("var", 4,
            "no value set for environment variable '%s', using \"(none)\"",
            env_var);
          val = "(none)";
        }

      } else {
        val = pr_var_get(key);
        if (val == NULL) {
          pr_trace_msg("var", 4,
            "no value set for name '%s', using \"(none)\"", key);
          val = "(none)";
        }
      }

      outs = sreplace(p, buf, key, val, NULL);
      sstrncpy(buf, outs, sizeof(buf));

      tmp = strstr(outs, "%{");
    }

    outs = sreplace(p, buf,
      "%C", (session.cwd[0] ? session.cwd : "(none)"),
      "%E", main_server->ServerAdmin,
      "%F", mg_size,
      "%f", mg_size_units,
      "%i", "0",
      "%K", "0",
      "%k", "0B",
      "%L", serverfqdn,
      "%M", mg_max,
      "%N", mg_cur,
      "%o", "0",
      "%R", (session.c && session.c->remote_name ?
        session.c->remote_name : "(unknown)"),
      "%T", mg_time,
      "%t", "0",
      "%U", user,
      "%u", rfc1413_ident,
      "%V", main_server->ServerName,
      "%x", session.conn_class ? session.conn_class->cls_name : "(unknown)",
      "%y", mg_cur_class,
      "%z", mg_class_limit,
      NULL);

    /* Always make sure that the lines we send are CRLF-terminated. */
    msg = pstrcat(p, msg, outs, "\r\n", NULL);

    /* Clear the buffer for the next read. */
    memset(buf, '\0', sizeof(buf));
  }

  return msg;
}
Ejemplo n.º 8
0
int sftp_misc_chown_file(pr_fh_t *fh) {
  struct stat st;
  int res, xerrno;
 
  if (fh == NULL) {
    errno = EINVAL;
    return -1;
  }

  /* session.fsgid defaults to -1, so chown(2) won't chgrp unless specifically
   * requested via GroupOwner.
   */
  if (session.fsuid != (uid_t) -1) {

    PRIVS_ROOT
    res = pr_fsio_fchown(fh, session.fsuid, session.fsgid);
    xerrno = errno;
    PRIVS_RELINQUISH

    if (res < 0) {
      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
        "chown(%s) as root failed: %s", fh->fh_path, strerror(xerrno));

    } else {
      if (session.fsgid != (gid_t) -1) {
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
          "root chown(%s) to UID %lu, GID %lu successful", fh->fh_path,
          (unsigned long) session.fsuid, (unsigned long) session.fsgid);

      } else {
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
          "root chown(%s) to UID %lu successful", fh->fh_path,
          (unsigned long) session.fsuid);
      }

      pr_fs_clear_cache();
      pr_fsio_fstat(fh, &st);

      /* The chmod happens after the chown because chown will remove the
       * S{U,G}ID bits on some files (namely, directories); the subsequent
       * chmod is used to restore those dropped bits.  This makes it necessary
       * to use root privs when doing the chmod as well (at least in the case
       * of chown'ing the file via root privs) in order to ensure that the mode
       * can be set (a file might be being "given away", and if root privs
       * aren't used, the chmod() will fail because the old owner/session user
       * doesn't have the necessary privileges to do so).
       */
      PRIVS_ROOT
      res = pr_fsio_fchmod(fh, st.st_mode);
      xerrno = errno;
      PRIVS_RELINQUISH

      if (res < 0) {
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
          "root chmod(%s) to %04o failed: %s", fh->fh_path,
          (unsigned int) st.st_mode, strerror(xerrno));

      } else {
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
          "root chmod(%s) to %04o successful", fh->fh_path,
          (unsigned int) st.st_mode);
      }
    }

  } else if (session.fsgid != (gid_t) -1) {