return result;
}
-/*******************************************************************
- Reduce a file name, removing .. elements and checking that
- it is below dir in the hierarchy. This uses realpath.
-
- If cwd_name == NULL then fname is a client given path relative
- to the root path of the share.
-
- If cwd_name != NULL then fname is a client given path relative
- to cwd_name. cwd_name is relative to the root path of the share.
-********************************************************************/
-
-NTSTATUS check_reduced_name(connection_struct *conn,
- const struct smb_filename *cwd_fname,
- const struct smb_filename *smb_fname)
-{
- TALLOC_CTX *ctx = talloc_tos();
- const char *cwd_name = cwd_fname ? cwd_fname->base_name : NULL;
- const char *fname = smb_fname->base_name;
- struct smb_filename *resolved_fname;
- char *resolved_name = NULL;
- char *new_fname = NULL;
- bool allow_symlinks = true;
- const char *conn_rootdir;
- size_t rootdir_len;
- bool parent_dir_checked = false;
-
- DBG_DEBUG("check_reduced_name [%s] [%s]\n", fname, conn->connectpath);
-
- resolved_fname = SMB_VFS_REALPATH(conn, ctx, smb_fname);
-
- if (resolved_fname == NULL) {
- NTSTATUS status;
- struct smb_filename *dir_fname = NULL;
- struct smb_filename *last_component = NULL;
-
- if (errno == ENOTDIR) {
- DBG_NOTICE("Component not a directory in getting "
- "realpath for %s\n",
- fname);
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
- if (errno != ENOENT) {
- status = map_nt_error_from_unix(errno);
- DBG_NOTICE("couldn't get realpath for %s: %s\n",
- fname,
- strerror(errno));
- return status;
- }
-
- /* errno == ENOENT */
-
- /*
- * Last component didn't exist. Remove it and try and
- * canonicalise the directory name.
- */
-
- status = SMB_VFS_PARENT_PATHNAME(conn,
- ctx,
- smb_fname,
- &dir_fname,
- &last_component);
- if (!NT_STATUS_IS_OK(status)) {
- return status;
- }
-
- resolved_fname = SMB_VFS_REALPATH(conn, ctx, dir_fname);
- if (resolved_fname == NULL) {
- status = map_nt_error_from_unix(errno);
-
- if (errno == ENOENT || errno == ENOTDIR) {
- status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
- }
-
- DBG_NOTICE("couldn't get realpath for "
- "%s (%s)\n",
- smb_fname_str_dbg(dir_fname),
- nt_errstr(status));
- return status;
- }
- resolved_name = talloc_asprintf(ctx,
- "%s/%s",
- resolved_fname->base_name,
- last_component->base_name);
- if (resolved_name == NULL) {
- return NT_STATUS_NO_MEMORY;
- }
- parent_dir_checked = true;
- } else {
- resolved_name = resolved_fname->base_name;
- }
-
- DEBUG(10,("check_reduced_name realpath [%s] -> [%s]\n", fname,
- resolved_name));
-
- if (*resolved_name != '/') {
- DEBUG(0,("check_reduced_name: realpath doesn't return "
- "absolute paths !\n"));
- TALLOC_FREE(resolved_fname);
- return NT_STATUS_OBJECT_NAME_INVALID;
- }
-
- /* Common widelinks and symlinks checks. */
- conn_rootdir = SMB_VFS_CONNECTPATH(conn, NULL, smb_fname);
- if (conn_rootdir == NULL) {
- DBG_NOTICE("Could not get conn_rootdir\n");
- TALLOC_FREE(resolved_fname);
- return NT_STATUS_ACCESS_DENIED;
- }
-
- rootdir_len = strlen(conn_rootdir);
-
- /*
- * In the case of rootdir_len == 1, we know that
- * conn_rootdir is "/", and we also know that
- * resolved_name starts with a slash. So, in this
- * corner case, resolved_name is automatically a
- * sub-directory of the conn_rootdir. Thus we can skip
- * the string comparison and the next character checks
- * (which are even wrong in this case).
- */
- if (rootdir_len != 1) {
- bool matched;
-
- matched = (strncmp(conn_rootdir, resolved_name,
- rootdir_len) == 0);
- if (!matched || (resolved_name[rootdir_len] != '/' &&
- resolved_name[rootdir_len] != '\0')) {
- DBG_NOTICE("Bad access attempt: %s is a symlink "
- "outside the "
- "share path\n"
- "conn_rootdir =%s\n"
- "resolved_name=%s\n",
- fname,
- conn_rootdir,
- resolved_name);
- TALLOC_FREE(resolved_fname);
- if (parent_dir_checked) {
- /* Part of a component path. */
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- } else {
- /* End of a path. */
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
- }
- }
-
- /* Extra checks if all symlinks are disallowed. */
- allow_symlinks = lp_follow_symlinks(SNUM(conn));
- if (!allow_symlinks) {
- /* fname can't have changed in resolved_path. */
- const char *p = &resolved_name[rootdir_len];
-
- /*
- * UNIX filesystem semantics, names consisting
- * only of "." or ".." CANNOT be symlinks.
- */
- if (ISDOT(fname) || ISDOTDOT(fname)) {
- goto out;
- }
-
- if (*p != '/') {
- DBG_NOTICE("logic error (%c) "
- "in resolved_name: %s\n",
- *p,
- fname);
- TALLOC_FREE(resolved_fname);
- return NT_STATUS_ACCESS_DENIED;
- }
-
- p++;
-
- /*
- * If cwd_name is present and not ".",
- * then fname is relative to that, not
- * the root of the share. Make sure the
- * path we check is the one the client
- * sent (cwd_name+fname).
- */
- if (cwd_name != NULL && !ISDOT(cwd_name)) {
- new_fname = talloc_asprintf(ctx,
- "%s/%s",
- cwd_name,
- fname);
- if (new_fname == NULL) {
- TALLOC_FREE(resolved_fname);
- return NT_STATUS_NO_MEMORY;
- }
- fname = new_fname;
- }
-
- if (strcmp(fname, p)!=0) {
- DBG_NOTICE("Bad access "
- "attempt: %s is a symlink to %s\n",
- fname,
- p);
- TALLOC_FREE(resolved_fname);
- TALLOC_FREE(new_fname);
- if (parent_dir_checked) {
- /* Part of a component path. */
- return NT_STATUS_OBJECT_PATH_NOT_FOUND;
- } else {
- /* End of a path. */
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
- }
- }
- }
-
- out:
-
- DBG_INFO("%s reduced to %s\n", fname, resolved_name);
- TALLOC_FREE(resolved_fname);
- TALLOC_FREE(new_fname);
- return NT_STATUS_OK;
-}
-
/*
* Ensure LSTAT is called for POSIX paths.
*/