END_TEST START_TEST (fs_clean_path2_test) { char res[PR_TUNABLE_PATH_MAX+1], *path, *expected; res[sizeof(res)-1] = '\0'; path = "test.txt"; pr_fs_clean_path2(path, res, sizeof(res)-1, 0); fail_unless(strcmp(res, path) == 0, "Expected cleaned path '%s', got '%s'", path, res); res[sizeof(res)-1] = '\0'; path = "/./test.txt"; pr_fs_clean_path2(path, res, sizeof(res)-1, 0); expected = "/test.txt"; fail_unless(strcmp(res, expected) == 0, "Expected cleaned path '%s', got '%s'", expected, res); res[sizeof(res)-1] = '\0'; path = "test.d///test.txt"; pr_fs_clean_path2(path, res, sizeof(res)-1, 0); expected = "test.d/test.txt"; fail_unless(strcmp(res, expected) == 0, "Expected cleaned path '%s', got '%s'", expected, res); res[sizeof(res)-1] = '\0'; path = "/test.d///test.txt"; pr_fs_clean_path2(path, res, sizeof(res)-1, PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH); expected = "/test.d/test.txt"; fail_unless(strcmp(res, expected) == 0, "Expected cleaned path '%s', got '%s'", expected, res); }
/* 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; }