示例#1
0
文件: support.c 项目: netdna/proftpd
/* Return the mode (including the file type) of the file pointed to by symlink
 * PATH, or 0 if it doesn't exist. Catch symlink loops using LAST_INODE and
 * RCOUNT.
 */
static mode_t _symlink(const char *path, ino_t last_inode, int rcount) {
  char buf[PR_TUNABLE_PATH_MAX + 1];
  struct stat sbuf;
  int i;

  if (++rcount >= 32) {
    errno = ELOOP;
    return 0;
  }

  memset(buf, '\0', sizeof(buf));

  i = pr_fsio_readlink(path, buf, sizeof(buf) - 1);
  if (i == -1)
    return (mode_t)0;
  buf[i] = '\0';

  pr_fs_clear_cache2(buf);
  if (pr_fsio_lstat(buf, &sbuf) != -1) {
    if (sbuf.st_ino && (ino_t) sbuf.st_ino == last_inode) {
      errno = ELOOP;
      return 0;
    }

    if (S_ISLNK(sbuf.st_mode)) {
      return _symlink(buf, (ino_t) sbuf.st_ino, rcount);
    }

    return sbuf.st_mode;
  }

  return 0;
}
示例#2
0
static int copy_symlink(pool *p, const char *src_path, const char *dst_path) {
  char *link_path = pcalloc(p, PR_TUNABLE_BUFFER_SIZE);
  int len;

  len = pr_fsio_readlink(src_path, link_path, PR_TUNABLE_BUFFER_SIZE-1);
  if (len < 0) {
    int xerrno = errno;

    pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error reading link '%s': %s",
      src_path, strerror(xerrno));

    errno = xerrno;
    return -1;
  }
  link_path[len] = '\0';

  if (pr_fsio_symlink(link_path, dst_path) < 0) {
    int xerrno = errno;

    pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION
      ": error symlinking '%s' to '%s': %s", link_path, dst_path,
      strerror(xerrno));

    errno = xerrno;
    return -1;
  }

  return 0;
}
示例#3
0
static int copy_symlink(pool *p, const char *src_dir, const char *src_path,
    const char *dst_dir, const char *dst_path, uid_t uid, gid_t gid) {
  char *link_path = pcalloc(p, PR_TUNABLE_BUFFER_SIZE);
  int len;

  len = pr_fsio_readlink(src_path, link_path, PR_TUNABLE_BUFFER_SIZE-1);
  if (len < 0) {
    pr_log_pri(PR_LOG_WARNING, "CreateHome: error reading link '%s': %s",
      src_path, strerror(errno));
    return -1;
  }
  link_path[len] = '\0';

  /* If the target of the link lies within the src path, rename that portion
   * of the link to be the corresponding part of the dst path.
   */
  if (strncmp(link_path, src_dir, strlen(src_dir)) == 0) {
    link_path = pdircat(p, dst_dir, link_path + strlen(src_dir), NULL);
  }

  if (pr_fsio_symlink(link_path, dst_path) < 0) {
    pr_log_pri(PR_LOG_WARNING, "CreateHome: error symlinking '%s' to '%s': %s",
      link_path, dst_path, strerror(errno));
    return -1;
  }

  /* Make sure the new symlink has the proper ownership. */
  if (pr_fsio_chown(dst_path, uid, gid) < 0) {
    pr_log_pri(PR_LOG_WARNING, "CreateHome: error chown'ing '%s' to %u/%u: %s",
      dst_path, (unsigned int) uid, (unsigned int) gid, strerror(errno));
  }

  return 0; 
}
示例#4
0
/* Return the mode (including the file type) of the file pointed to by symlink
 * PATH, or 0 if it doesn't exist. Catch symlink loops using LAST_INODE and
 * RCOUNT.
 */
static mode_t _symlink(const char *path, ino_t last_inode, int rcount) {
  char buf[PR_TUNABLE_PATH_MAX + 1];
  struct stat st;
  int i;

  if (++rcount >= PR_FSIO_MAX_LINK_COUNT) {
    errno = ELOOP;
    return 0;
  }

  memset(buf, '\0', sizeof(buf));

  i = pr_fsio_readlink(path, buf, sizeof(buf) - 1);
  if (i < 0) {
    return (mode_t) 0;
  }
  buf[i] = '\0';

  pr_fs_clear_cache2(buf);
  if (pr_fsio_lstat(buf, &st) >= 0) {
    if (st.st_ino > 0 &&
        (ino_t) st.st_ino == last_inode) {
      errno = ELOOP;
      return 0;
    }

    if (S_ISLNK(st.st_mode)) {
      return _symlink(buf, (ino_t) st.st_ino, rcount);
    }

    return st.st_mode;
  }

  return 0;
}
示例#5
0
文件: fsio.c 项目: UIKit0/proftpd
END_TEST

START_TEST (fsio_readlink_test) {
  int res;
  char buf[PR_TUNABLE_BUFFER_SIZE];
  const char *link_path, *target_path, *path;

  res = pr_fsio_readlink(NULL, NULL, 0);
  fail_unless(res < 0, "Failed to handle null arguments");
  fail_unless(errno == EINVAL, "Expected EINVAL, got %s (%d)", strerror(errno),
    errno);

  /* Read a non-symlink file */
  path = "/";
  res = pr_fsio_readlink(path, buf, sizeof(buf)-1);
  fail_unless(res < 0, "Failed to handle non-symlink path");
  fail_unless(errno == EINVAL, "Expected EINVAL, got %s (%d)", strerror(errno),
    errno);

  /* Read a symlink file */
  target_path = "/tmp";
  link_path = fsio_link_path;
  res = pr_fsio_symlink(target_path, link_path);
  fail_unless(res == 0, "Failed to create symlink from '%s' to '%s': %s",
    link_path, target_path, strerror(errno));

  memset(buf, '\0', sizeof(buf));
  res = pr_fsio_readlink(link_path, buf, sizeof(buf)-1);
  fail_unless(res > 0, "Failed to read symlink '%s': %s", link_path,
    strerror(errno));
  buf[res] = '\0';
  fail_unless(strcmp(buf, target_path) == 0, "Expected '%s', got '%s'",
    target_path, buf);

  /* Read a symlink file using a zero-length buffer */
  res = pr_fsio_readlink(link_path, buf, 0);
  fail_unless(res <= 0, "Expected length <= 0, got %d", res);

  (void) unlink(link_path);
}
示例#6
0
static int af_check_file(pool *p, const char *name, const char *path,
    int flags) {
  struct stat st;
  int res;
  const char *orig_path;

  orig_path = path;

  res = lstat(path, &st);
  if (res < 0) {
    int xerrno = errno;

    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to lstat %s '%s': %s",
      name, path, strerror(xerrno));

    errno = xerrno;
    return -1;
  }

  if (S_ISLNK(st.st_mode)) {
    char buf[PR_TUNABLE_PATH_MAX+1];

    /* Check the permissions on the parent directory; if they're world-writable,
     * then this symlink can be deleted/pointed somewhere else.
     */
    res = af_check_parent_dir(p, name, path);
    if (res < 0) {
      return -1;
    }

    /* Follow the link to the target path; that path will then have its
     * parent directory checked.
     */
    memset(buf, '\0', sizeof(buf));
    res = pr_fsio_readlink(path, buf, sizeof(buf)-1);
    if (res > 0) {
      path = pstrdup(p, buf);
    }

    res = stat(orig_path, &st);
    if (res < 0) {
      int xerrno = errno;

      pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to stat %s '%s': %s",
        name, orig_path, strerror(xerrno));

      errno = xerrno;
      return -1;
    }
  }

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

    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use %s '%s': %s",
      name, orig_path, strerror(xerrno));

    errno = xerrno;
    return -1;
  }

  /* World-readable files MAY be insecure, and are thus not usable/trusted. */
  if ((st.st_mode & S_IROTH) &&
       !(flags & PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE)) {
    int xerrno = EPERM;

    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
      ": unable to use world-readable %s '%s' (perms %04o): %s",
      name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));

    errno = xerrno;
    return -1;
  }

  /* World-writable files are insecure, and are thus not usable/trusted. */
  if (st.st_mode & S_IWOTH) {
    int xerrno = EPERM;

    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
      ": unable to use world-writable %s '%s' (perms %04o): %s",
      name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));

    errno = xerrno;
    return -1;
  }

  if (!S_ISREG(st.st_mode)) {
    pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION
      ": %s '%s' is not a regular file", name, orig_path);
  }

  /* Check the parent directory of this file.  If the parent directory
   * is world-writable, that too is insecure.
   */
  res = af_check_parent_dir(p, name, path);
  if (res < 0) {
    return -1;
  }

  return 0;
}
示例#7
0
文件: support.c 项目: Nakor78/proftpd
/* Performs chroot-aware handling of symlinks. */
int dir_readlink(pool *p, const char *path, char *buf, size_t bufsz,
    int flags) {
  int is_abs_dst, clean_flags, len, res = -1;
  size_t chroot_pathlen = 0, adj_pathlen = 0;
  char *dst_path, *adj_path;
  pool *tmp_pool;

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

  if (bufsz == 0) {
    return 0;
  }

  len = pr_fsio_readlink(path, buf, bufsz);
  if (len < 0) {
    return -1;
  }

  if (len == 0 ||
      len == bufsz) {
    /* If we read nothing in, OR if the given buffer was completely
     * filled WITHOUT terminating NUL, there's really nothing we can/should
     * be doing.
     */
    return len;
  }

  is_abs_dst = FALSE;
  if (*buf == '/') {
    is_abs_dst = TRUE;
  }

  if (session.chroot_path != NULL) {
    chroot_pathlen = strlen(session.chroot_path);
  }

  if (chroot_pathlen <= 1) {
    char *ptr;

    if (is_abs_dst == TRUE ||
        !(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
      return len;
    }

    /* Since we have a relative destination path, we will concat it
     * with the source path's directory, then clean up that path.
     */
    ptr = strrchr(path, '/');
    if (ptr != NULL &&
        ptr != path) {
      char *parent_dir;

      tmp_pool = make_sub_pool(p);
      pr_pool_tag(tmp_pool, "dir_readlink pool");

      parent_dir = pstrndup(tmp_pool, path, (ptr - path));
      dst_path = pdircat(tmp_pool, parent_dir, buf, NULL);

      adj_pathlen = bufsz + 1;
      adj_path = pcalloc(tmp_pool, adj_pathlen);

      res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, 0);
      if (res == 0) {
        pr_trace_msg("fsio", 19,
          "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
        dst_path = adj_path;
      }

      pr_trace_msg("fsio", 19,
        "adjusted relative symlink path '%s', yielding '%s'", buf, dst_path);

      memset(buf, '\0', bufsz);
      sstrncpy(buf, dst_path, bufsz);
      len = strlen(buf);
      destroy_pool(tmp_pool);
    }

    return len;
  }

  if (is_abs_dst == FALSE) {
    /* If we are to ignore relative destination paths, return now. */
    if (!(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) {
      return len;
    }
  }

  if (is_abs_dst == TRUE &&
      len < chroot_pathlen) {
    /* If the destination path length is shorter than the chroot path,
     * AND the destination path is absolute, then by definition it CANNOT
     * point within the chroot.
     */
    return len;
  }

  tmp_pool = make_sub_pool(p);
  pr_pool_tag(tmp_pool, "dir_readlink pool");

  dst_path = pstrdup(tmp_pool, buf);
  if (is_abs_dst == FALSE) {
    char *ptr;

    /* Since we have a relative destination path, we will concat it
     * with the source path's directory, then clean up that path.
     */

    ptr = strrchr(path, '/');
    if (ptr != NULL &&
        ptr != path) {
      char *parent_dir;

      parent_dir = pstrndup(tmp_pool, path, (ptr - path));
      dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL);

    } else {
      dst_path = pdircat(tmp_pool, path, dst_path, NULL);
    }
  }

  adj_pathlen = bufsz + 1;
  adj_path = pcalloc(tmp_pool, adj_pathlen);

  clean_flags = PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH;
  res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, clean_flags);
  if (res == 0) {
    pr_trace_msg("fsio", 19,
      "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path);
    dst_path = adj_path;

    memset(buf, '\0', bufsz);
    sstrncpy(buf, dst_path, bufsz);
    len = strlen(dst_path);
  }

  if (strncmp(dst_path, session.chroot_path, chroot_pathlen) == 0 &&
      *(dst_path + chroot_pathlen) == '/') {
    char *ptr;

    ptr = dst_path + chroot_pathlen;

    if (is_abs_dst == FALSE &&
        res == 0) {
      /* If we originally had a relative destination path, AND we cleaned
       * that adjusted path, then we should try to re-adjust the path
       * back to being a relative path.  Within reason.
       */
      ptr = pstrcat(tmp_pool, ".", ptr, NULL);
    }

    /* Since we are making the destination path shorter, the given buffer
     * (which was big enough for the original destination path) should
     * always be large enough for this adjusted, shorter version.  Right?
     */
    pr_trace_msg("fsio", 19,
      "adjusted symlink path '%s' for chroot '%s', yielding '%s'",
      dst_path, session.chroot_path, ptr);

    memset(buf, '\0', bufsz);
    sstrncpy(buf, ptr, bufsz);
    len = strlen(buf);
  }

  destroy_pool(tmp_pool);
  return len;
}