/* * smb_odir_lookup_link * * If the file is a symlink we lookup the object to which the * symlink refers so that we can return its attributes. * This can cause a problem if a symlink in a sub-directory * points to a parent directory (some UNIX GUI's create a symlink * in $HOME/.desktop that points to the user's home directory). * Some Windows applications (e.g. virus scanning) loop/hang * trying to follow this recursive path and there is little * we can do because the path is constructed on the client. * smb_dirsymlink_enable allows an end-user to disable * symlinks to directories. Symlinks to other object types * should be unaffected. * * Returns: B_TRUE - followed link. tgt_node and tgt_attr set * B_FALSE - link not followed */ static boolean_t smb_odir_lookup_link(smb_request_t *sr, smb_odir_t *od, char *fname, smb_node_t **tgt_node) { int rc; uint32_t flags = SMB_FOLLOW_LINKS | SMB_CASE_SENSITIVE; rc = smb_fsop_lookup(sr, od->d_cred, flags, od->d_tree->t_snode, od->d_dnode, fname, tgt_node); if (rc != 0) { *tgt_node = NULL; return (B_FALSE); } if (smb_node_is_dir(*tgt_node) && (!smb_dirsymlink_enable)) { smb_node_release(*tgt_node); *tgt_node = NULL; return (B_FALSE); } return (B_TRUE); }
/* * smb_common_rename * * Common code for renaming a file. * * If the source and destination are identical, we go through all * the checks but we don't actually do the rename. If the source * and destination files differ only in case, we do a case-sensitive * rename. Otherwise, we do a full case-insensitive rename. * * Returns NT status values. * * Similar to smb_make_link(), below. */ uint32_t smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi) { smb_node_t *src_fnode, *src_dnode, *dst_dnode; smb_node_t *dst_fnode = 0; smb_node_t *tnode; char *new_name, *path; DWORD status; int rc, count; tnode = sr->tid_tree->t_snode; path = dst_fqi->fq_path.pn_path; /* Check if attempting to rename a stream - not yet supported */ rc = smb_rename_check_stream(src_fqi, dst_fqi); if (rc != 0) return (smb_rename_errno2status(rc)); /* * The source node may already have been provided, * i.e. when called by SMB1/SMB2 smb_setinfo_rename. * Not provided by smb_com_rename, smb_com_nt_rename. */ if (src_fqi->fq_fnode) { smb_node_start_crit(src_fqi->fq_fnode, RW_READER); smb_node_ref(src_fqi->fq_fnode); smb_node_ref(src_fqi->fq_dnode); } else { /* lookup and validate src node */ rc = smb_rename_lookup_src(sr); if (rc != 0) return (smb_rename_errno2status(rc)); } src_fnode = src_fqi->fq_fnode; src_dnode = src_fqi->fq_dnode; /* * Find the destination dnode and last component. * May already be provided, i.e. when called via * SMB1 trans2 setinfo. */ if (dst_fqi->fq_dnode) { /* called via smb_set_rename_info */ smb_node_ref(dst_fqi->fq_dnode); } else { /* called via smb2_setf_rename, smb_com_rename, etc. */ rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode, &dst_fqi->fq_dnode, dst_fqi->fq_last_comp); if (rc != 0) { smb_rename_release_src(sr); return (smb_rename_errno2status(rc)); } } dst_dnode = dst_fqi->fq_dnode; new_name = dst_fqi->fq_last_comp; /* If exact name match in same directory, we're done */ if ((src_dnode == dst_dnode) && (strcmp(src_fnode->od_name, new_name) == 0)) { smb_rename_release_src(sr); smb_node_release(dst_dnode); return (0); } /* Lookup destination node */ rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode, dst_dnode, new_name, &dst_fqi->fq_fnode); /* If the destination node doesn't already exist, validate new_name. */ if (rc == ENOENT) { if (smb_is_invalid_filename(new_name)) { smb_rename_release_src(sr); smb_node_release(dst_dnode); return (NT_STATUS_OBJECT_NAME_INVALID); } } /* * Handle case where changing case of the same directory entry. * * If we found the dst node in the same directory as the src node, * and their names differ only in case: * * If the tree is case sensitive (or mixed): * Do case sensitive lookup to see if exact match exists. * If the exact match is the same node as src_node we're done. * * If the tree is case insensitive: * There is currently no way to tell if the case is different * or not, so do the rename (unless the specified new name was * mangled). */ if ((rc == 0) && (src_dnode == dst_dnode) && (smb_strcasecmp(src_fnode->od_name, dst_fqi->fq_fnode->od_name, 0) == 0)) { smb_node_release(dst_fqi->fq_fnode); dst_fqi->fq_fnode = NULL; if (smb_tree_has_feature(sr->tid_tree, SMB_TREE_NO_CASESENSITIVE)) { if (smb_strcasecmp(src_fnode->od_name, dst_fqi->fq_last_comp, 0) != 0) { smb_rename_release_src(sr); smb_node_release(dst_dnode); return (0); } } else { rc = smb_fsop_lookup(sr, sr->user_cr, SMB_CASE_SENSITIVE, tnode, dst_dnode, new_name, &dst_fqi->fq_fnode); if ((rc == 0) && (dst_fqi->fq_fnode == src_fnode)) { smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_fnode); smb_node_release(dst_dnode); return (0); } } } if ((rc != 0) && (rc != ENOENT)) { smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_dnode); return (smb_rename_errno2status(rc)); } if (dst_fqi->fq_fnode) { /* * Destination already exists. Do delete checks. */ dst_fnode = dst_fqi->fq_fnode; if (!(sr->arg.dirop.flags && SMB_RENAME_FLAG_OVERWRITE)) { smb_rename_release_src(sr); smb_node_release(dst_fnode); smb_node_release(dst_dnode); return (NT_STATUS_OBJECT_NAME_COLLISION); } (void) smb_oplock_break(sr, dst_fnode, SMB_OPLOCK_BREAK_TO_NONE | SMB_OPLOCK_BREAK_BATCH); /* * Wait (a little) for the oplock break to be * responded to by clients closing handles. * Hold node->n_lock as reader to keep new * ofiles from showing up after we check. */ smb_node_rdlock(dst_fnode); for (count = 0; count <= 12; count++) { status = smb_node_delete_check(dst_fnode); if (status != NT_STATUS_SHARING_VIOLATION) break; smb_node_unlock(dst_fnode); delay(MSEC_TO_TICK(100)); smb_node_rdlock(dst_fnode); } if (status != NT_STATUS_SUCCESS) { smb_node_unlock(dst_fnode); smb_rename_release_src(sr); smb_node_release(dst_fnode); smb_node_release(dst_dnode); return (NT_STATUS_ACCESS_DENIED); } /* * Note, the combination of these two: * smb_node_rdlock(node); * nbl_start_crit(node->vp, RW_READER); * is equivalent to this call: * smb_node_start_crit(node, RW_READER) * * Cleanup after this point should use: * smb_node_end_crit(dst_fnode) */ nbl_start_crit(dst_fnode->vp, RW_READER); /* * This checks nbl_share_conflict, nbl_lock_conflict */ status = smb_nbl_conflict(dst_fnode, 0, UINT64_MAX, NBL_REMOVE); if (status != NT_STATUS_SUCCESS) { smb_node_end_crit(dst_fnode); smb_rename_release_src(sr); smb_node_release(dst_fnode); smb_node_release(dst_dnode); return (NT_STATUS_ACCESS_DENIED); } new_name = dst_fnode->od_name; } rc = smb_fsop_rename(sr, sr->user_cr, src_dnode, src_fnode->od_name, dst_dnode, new_name); if (rc == 0) { /* * Note that renames in the same directory are normally * delivered in {old,new} pairs, and clients expect them * in that order, if both events are delivered. */ int a_src, a_dst; /* action codes */ if (src_dnode == dst_dnode) { a_src = FILE_ACTION_RENAMED_OLD_NAME; a_dst = FILE_ACTION_RENAMED_NEW_NAME; } else { a_src = FILE_ACTION_REMOVED; a_dst = FILE_ACTION_ADDED; } smb_node_notify_change(src_dnode, a_src, src_fnode->od_name); smb_node_notify_change(dst_dnode, a_dst, new_name); } smb_rename_release_src(sr); if (dst_fqi->fq_fnode) { smb_node_end_crit(dst_fnode); smb_node_release(dst_fnode); } smb_node_release(dst_dnode); return (smb_rename_errno2status(rc)); }
/* * smb_rename_lookup_src * * Lookup the src node, checking for sharing violations and * breaking any existing BATCH oplock. * Populate sr->arg.dirop.fqi * * Upon success, the dnode and fnode will have holds and the * fnode will be in a critical section. These should be * released using smb_rename_release_src(). * * Returns errno values. */ static int smb_rename_lookup_src(smb_request_t *sr) { smb_node_t *src_node, *tnode; DWORD status; int rc; int count; char *path; smb_fqi_t *src_fqi = &sr->arg.dirop.fqi; if (smb_is_stream_name(src_fqi->fq_path.pn_path)) return (EINVAL); /* Lookup the source node */ tnode = sr->tid_tree->t_snode; path = src_fqi->fq_path.pn_path; rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode, &src_fqi->fq_dnode, src_fqi->fq_last_comp); if (rc != 0) return (rc); rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode, src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode); if (rc != 0) { smb_node_release(src_fqi->fq_dnode); return (rc); } src_node = src_fqi->fq_fnode; rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr); if (rc != 0) { smb_node_release(src_fqi->fq_fnode); smb_node_release(src_fqi->fq_dnode); return (rc); } /* * Break BATCH oplock before ofile checks. If a client * has a file open, this will force a flush or close, * which may affect the outcome of any share checking. */ (void) smb_oplock_break(sr, src_node, SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH); /* * Wait (a little) for the oplock break to be * responded to by clients closing handles. * Hold node->n_lock as reader to keep new * ofiles from showing up after we check. */ smb_node_rdlock(src_node); for (count = 0; count <= 12; count++) { status = smb_node_rename_check(src_node); if (status != NT_STATUS_SHARING_VIOLATION) break; smb_node_unlock(src_node); delay(MSEC_TO_TICK(100)); smb_node_rdlock(src_node); } if (status != NT_STATUS_SUCCESS) { smb_node_unlock(src_node); smb_node_release(src_fqi->fq_fnode); smb_node_release(src_fqi->fq_dnode); return (EPIPE); /* = ERRbadshare */ } /* * Note, the combination of these two: * smb_node_rdlock(node); * nbl_start_crit(node->vp, RW_READER); * is equivalent to this call: * smb_node_start_crit(node, RW_READER) * * Cleanup after this point should use: * smb_node_end_crit(src_node) */ nbl_start_crit(src_node->vp, RW_READER); /* * This checks nbl_share_conflict, nbl_lock_conflict */ status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME); if (status != NT_STATUS_SUCCESS) { smb_node_end_crit(src_node); smb_node_release(src_fqi->fq_fnode); smb_node_release(src_fqi->fq_dnode); if (status == NT_STATUS_SHARING_VIOLATION) return (EPIPE); /* = ERRbadshare */ return (EACCES); } /* NB: Caller expects holds on src_fqi fnode, dnode */ return (0); }
/* * smb_make_link * * Creating a hard link (adding an additional name) for a file. * * If the source and destination are identical, we go through all * the checks but we don't create a link. * * If the file is a symlink we create the hardlink on the target * of the symlink (i.e. use SMB_FOLLOW_LINKS when looking up src). * If the target of the symlink does not exist we fail with ENOENT. * * Returns NT status values. * * Similar to smb_common_rename() above. */ uint32_t smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi) { smb_node_t *tnode; char *path; int rc; tnode = sr->tid_tree->t_snode; path = dst_fqi->fq_path.pn_path; /* Cannnot create link on named stream */ if (smb_is_stream_name(src_fqi->fq_path.pn_path) || smb_is_stream_name(dst_fqi->fq_path.pn_path)) { return (NT_STATUS_INVALID_PARAMETER); } /* The source node may already have been provided */ if (src_fqi->fq_fnode) { smb_node_start_crit(src_fqi->fq_fnode, RW_READER); smb_node_ref(src_fqi->fq_fnode); smb_node_ref(src_fqi->fq_dnode); } else { /* lookup and validate src node */ rc = smb_rename_lookup_src(sr); if (rc != 0) return (smb_rename_errno2status(rc)); } /* Not valid to create hardlink for directory */ if (smb_node_is_dir(src_fqi->fq_fnode)) { smb_rename_release_src(sr); return (NT_STATUS_FILE_IS_A_DIRECTORY); } /* * Find the destination dnode and last component. * May already be provided, i.e. when called via * SMB1 trans2 setinfo. */ if (dst_fqi->fq_dnode) { smb_node_ref(dst_fqi->fq_dnode); } else { rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode, &dst_fqi->fq_dnode, dst_fqi->fq_last_comp); if (rc != 0) { smb_rename_release_src(sr); return (smb_rename_errno2status(rc)); } } /* If CI name match in same directory, we're done */ if ((src_fqi->fq_dnode == dst_fqi->fq_dnode) && (smb_strcasecmp(src_fqi->fq_fnode->od_name, dst_fqi->fq_last_comp, 0) == 0)) { smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_dnode); return (0); } if (smb_is_invalid_filename(dst_fqi->fq_last_comp)) { smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_dnode); return (NT_STATUS_OBJECT_NAME_INVALID); } /* Lookup the destination node. It MUST NOT exist. */ rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode, dst_fqi->fq_dnode, dst_fqi->fq_last_comp, &dst_fqi->fq_fnode); if (rc == 0) { smb_node_release(dst_fqi->fq_fnode); rc = EEXIST; } if (rc != ENOENT) { smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_dnode); return (smb_rename_errno2status(rc)); } rc = smb_fsop_link(sr, sr->user_cr, src_fqi->fq_fnode, dst_fqi->fq_dnode, dst_fqi->fq_last_comp); if (rc == 0) { smb_node_notify_change(dst_fqi->fq_dnode, FILE_ACTION_ADDED, dst_fqi->fq_last_comp); } smb_rename_release_src(sr); smb_node_release(dst_fqi->fq_dnode); return (smb_rename_errno2status(rc)); }
static smb_tree_t * smb_tree_connect_disk(smb_request_t *sr, const char *sharename) { smb_user_t *user = sr->uid_user; smb_node_t *dnode = NULL; smb_node_t *snode = NULL; char last_component[MAXNAMELEN]; smb_tree_t *tree; smb_share_t *si; cred_t *u_cred; int rc; uint32_t access = 0; /* read/write is assumed */ uint32_t hostaccess = ACE_ALL_PERMS; uint32_t aclaccess; smb_execsub_info_t subs; ASSERT(user); u_cred = user->u_cred; ASSERT(u_cred); if (user->u_flags & SMB_USER_FLAG_IPC) { smb_tree_log(sr, sharename, "access denied: IPC only"); smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); return (NULL); } si = kmem_zalloc(sizeof (smb_share_t), KM_SLEEP); if (smb_kshare_getinfo(sr->sr_server->sv_lmshrd, (char *)sharename, si, &sr->session->ipaddr) != NERR_Success) { smb_tree_log(sr, sharename, "share not found"); smbsr_error(sr, 0, ERRSRV, ERRinvnetname); kmem_free(si, sizeof (smb_share_t)); return (NULL); } if (user->u_flags & SMB_USER_FLAG_GUEST) { if ((si->shr_flags & SMB_SHRF_GUEST_OK) == 0) { smb_tree_log(sr, sharename, "access denied: guest disabled"); smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); kmem_free(si, sizeof (smb_share_t)); return (NULL); } } /* * Handle the default administration shares: C$, D$ etc. * Only a user with admin rights is allowed to map these * shares. */ if (si->shr_flags & SMB_SHRF_ADMIN) { if (!smb_user_is_admin(user)) { smb_tree_log(sr, sharename, "access denied: not admin"); smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); kmem_free(si, sizeof (smb_share_t)); return (NULL); } } /* * Set up the OptionalSupport for this share. */ sr->arg.tcon.optional_support = SMB_SUPPORT_SEARCH_BITS; switch (si->shr_flags & SMB_SHRF_CSC_MASK) { case SMB_SHRF_CSC_DISABLED: sr->arg.tcon.optional_support |= SMB_CSC_CACHE_NONE; break; case SMB_SHRF_CSC_AUTO: sr->arg.tcon.optional_support |= SMB_CSC_CACHE_AUTO_REINT; break; case SMB_SHRF_CSC_VDO: sr->arg.tcon.optional_support |= SMB_CSC_CACHE_VDO; break; case SMB_SHRF_CSC_MANUAL: default: /* * Default to SMB_CSC_CACHE_MANUAL_REINT. */ break; } /* ABE support */ if (si->shr_flags & SMB_SHRF_ABE) sr->arg.tcon.optional_support |= SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM; access = si->shr_access_value & SMB_SHRF_ACC_ALL; if (access == SMB_SHRF_ACC_RO) { hostaccess &= ~ACE_ALL_WRITE_PERMS; } else if (access == SMB_SHRF_ACC_NONE) { kmem_free(si, sizeof (smb_share_t)); smb_tree_log(sr, sharename, "access denied: host access"); smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); return (NULL); } /* * Check that the shared directory exists. */ rc = smb_pathname_reduce(sr, u_cred, si->shr_path, 0, 0, &dnode, last_component); if (rc == 0) { rc = smb_fsop_lookup(sr, u_cred, SMB_FOLLOW_LINKS, sr->sr_server->si_root_smb_node, dnode, last_component, &snode); smb_node_release(dnode); } if (rc) { if (snode) smb_node_release(snode); smb_tree_log(sr, sharename, "bad path: %s", si->shr_path); smbsr_error(sr, 0, ERRSRV, ERRinvnetname); kmem_free(si, sizeof (smb_share_t)); return (NULL); } /* * Find share level ACL if it exists in the designated * location. Needs to be done after finding a valid path but * before the tree is allocated. */ smb_tree_acl_access(u_cred, sharename, snode->vp, &aclaccess); if ((aclaccess & ACE_ALL_PERMS) == 0) { smb_tree_log(sr, sharename, "access denied: share ACL"); smbsr_error(sr, 0, ERRSRV, ERRaccess); kmem_free(si, sizeof (smb_share_t)); smb_node_release(snode); return (NULL); } /* * Set tree ACL access to the minimum ACL permissions based on * hostaccess (those allowed by host based access) and * aclaccess (those from the ACL object for the share). This * is done during the alloc. */ (void) strlcpy(si->shr_name, sharename, MAXNAMELEN); tree = smb_tree_alloc(user, si, STYPE_DISKTREE, snode, hostaccess & aclaccess); smb_node_release(snode); if (tree == NULL) smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); else { tree->t_shr_flags = si->shr_flags; if (tree->t_shr_flags & SMB_SHRF_MAP) { (void) smb_tree_set_execsub_info(tree, &subs); rc = smb_kshare_exec(sr->sr_server->sv_lmshrd, (char *)sharename, &subs, SMB_SHR_MAP); if (rc != 0 && tree->t_shr_flags & SMB_SHRF_DISP_TERM) { smb_tree_disconnect(tree, B_FALSE); smb_tree_release(tree); smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess); kmem_free(si, sizeof (smb_share_t)); return (NULL); } } } kmem_free(si, sizeof (smb_share_t)); return (tree); }
smb_sdrc_t smb_com_check_directory(smb_request_t *sr) { int rc; smb_fqi_t *fqi; smb_node_t *tnode; smb_node_t *node; char *path; smb_pathname_t *pn; if (STYPE_ISIPC(sr->tid_tree->t_res_type)) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); return (SDRC_ERROR); } fqi = &sr->arg.dirop.fqi; pn = &fqi->fq_path; if (pn->pn_path[0] == '\0') { rc = smbsr_encode_empty_result(sr); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); } smb_pathname_init(sr, pn, pn->pn_path); if (!smb_pathname_validate(sr, pn) || !smb_validate_dirname(sr, pn)) { return (SDRC_ERROR); } path = pn->pn_path; tnode = sr->tid_tree->t_snode; rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode, &fqi->fq_dnode, fqi->fq_last_comp); if (rc != 0) { smbsr_errno(sr, rc); return (SDRC_ERROR); } rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS, tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode); smb_node_release(fqi->fq_dnode); if (rc != 0) { if (rc == ENOENT) smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, ERRDOS, ERROR_PATH_NOT_FOUND); else smbsr_errno(sr, rc); return (SDRC_ERROR); } node = fqi->fq_fnode; if (!smb_node_is_dir(node)) { smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, ERRDOS, ERROR_PATH_NOT_FOUND); smb_node_release(node); return (SDRC_ERROR); } if ((sr->smb_flg2 & SMB_FLAGS2_DFS) && smb_node_is_dfslink(node)) { smbsr_error(sr, NT_STATUS_PATH_NOT_COVERED, ERRSRV, ERRbadpath); smb_node_release(node); return (SDRC_ERROR); } rc = smb_fsop_access(sr, sr->user_cr, node, FILE_TRAVERSE); smb_node_release(node); if (rc != 0) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); return (SDRC_ERROR); } rc = smbsr_encode_empty_result(sr); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); }
smb_sdrc_t smb_com_delete_directory(smb_request_t *sr) { int rc; uint32_t flags = 0; smb_fqi_t *fqi; smb_node_t *tnode; if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); return (SDRC_ERROR); } fqi = &sr->arg.dirop.fqi; tnode = sr->tid_tree->t_snode; smb_pathname_init(sr, &fqi->fq_path, fqi->fq_path.pn_path); if (!smb_pathname_validate(sr, &fqi->fq_path) || !smb_validate_dirname(sr, &fqi->fq_path)) { return (SDRC_ERROR); } rc = smb_pathname_reduce(sr, sr->user_cr, fqi->fq_path.pn_path, tnode, tnode, &fqi->fq_dnode, fqi->fq_last_comp); if (rc != 0) { smbsr_errno(sr, rc); return (SDRC_ERROR); } rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS, tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode); if (rc != 0) { if (rc == ENOENT) smbsr_error(sr, NT_STATUS_OBJECT_NAME_NOT_FOUND, ERRDOS, ERROR_FILE_NOT_FOUND); else smbsr_errno(sr, rc); smb_node_release(fqi->fq_dnode); return (SDRC_ERROR); } /* * Delete should fail if this is the root of a share * or a DFS link */ if ((fqi->fq_fnode == tnode) || smb_node_is_dfslink(fqi->fq_fnode)) { smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRDOS, ERROR_ACCESS_DENIED); smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (SDRC_ERROR); } if (!smb_node_is_dir(fqi->fq_fnode)) { smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, ERRDOS, ERROR_PATH_NOT_FOUND); smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (SDRC_ERROR); } /* * Using kcred because we just want the DOS attrs * and don't want access errors for this. */ fqi->fq_fattr.sa_mask = SMB_AT_DOSATTR; rc = smb_node_getattr(sr, fqi->fq_fnode, zone_kcred(), NULL, &fqi->fq_fattr); if (rc != 0) { smbsr_errno(sr, rc); smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (SDRC_ERROR); } if ((fqi->fq_fattr.sa_dosattr & FILE_ATTRIBUTE_READONLY) || (smb_fsop_access(sr, sr->user_cr, fqi->fq_fnode, DELETE) != NT_STATUS_SUCCESS)) { smbsr_error(sr, NT_STATUS_CANNOT_DELETE, ERRDOS, ERROR_ACCESS_DENIED); smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (SDRC_ERROR); } if (SMB_TREE_SUPPORTS_CATIA(sr)) flags |= SMB_CATIA; rc = smb_fsop_rmdir(sr, sr->user_cr, fqi->fq_dnode, fqi->fq_fnode->od_name, flags); smb_node_release(fqi->fq_fnode); smb_node_release(fqi->fq_dnode); if (rc != 0) { if (rc == EEXIST) smbsr_error(sr, NT_STATUS_DIRECTORY_NOT_EMPTY, ERRDOS, ERROR_DIR_NOT_EMPTY); else smbsr_errno(sr, rc); return (SDRC_ERROR); } rc = smbsr_encode_empty_result(sr); return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR); }
/* * smb_common_create_directory * * Currently called from: * smb_com_create_directory * smb_com_trans2_create_directory * * Returns errno values. */ int smb_common_create_directory(smb_request_t *sr) { int rc; smb_attr_t new_attr; smb_fqi_t *fqi; smb_node_t *tnode; fqi = &sr->arg.dirop.fqi; tnode = sr->tid_tree->t_snode; rc = smb_pathname_reduce(sr, sr->user_cr, fqi->fq_path.pn_path, tnode, tnode, &fqi->fq_dnode, fqi->fq_last_comp); if (rc != 0) return (rc); if (smb_is_invalid_filename(fqi->fq_last_comp)) { smb_node_release(fqi->fq_dnode); return (EILSEQ); /* NT_STATUS_OBJECT_NAME_INVALID */ } /* lookup node - to ensure that it does NOT exist */ rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS, tnode, fqi->fq_dnode, fqi->fq_last_comp, &fqi->fq_fnode); if (rc == 0) { smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (EEXIST); } if (rc != ENOENT) { smb_node_release(fqi->fq_dnode); return (rc); } rc = smb_fsop_access(sr, sr->user_cr, fqi->fq_dnode, FILE_ADD_SUBDIRECTORY); if (rc != NT_STATUS_SUCCESS) { smb_node_release(fqi->fq_dnode); return (EACCES); } /* * Explicitly set sa_dosattr, otherwise the file system may * automatically apply FILE_ATTRIBUTE_ARCHIVE which, for * compatibility with windows servers, should not be set. */ bzero(&new_attr, sizeof (new_attr)); new_attr.sa_dosattr = FILE_ATTRIBUTE_DIRECTORY; new_attr.sa_vattr.va_type = VDIR; new_attr.sa_vattr.va_mode = 0777; new_attr.sa_mask = SMB_AT_TYPE | SMB_AT_MODE | SMB_AT_DOSATTR; rc = smb_fsop_mkdir(sr, sr->user_cr, fqi->fq_dnode, fqi->fq_last_comp, &new_attr, &fqi->fq_fnode); if (rc != 0) { smb_node_release(fqi->fq_dnode); return (rc); } sr->arg.open.create_options = FILE_DIRECTORY_FILE; smb_node_release(fqi->fq_dnode); smb_node_release(fqi->fq_fnode); return (0); }
/* * smb_odir_read_streaminfo * * Find the next directory entry whose name begins with SMB_STREAM_PREFIX, * and thus represents an NTFS named stream. * No search attribute matching is performed. * No case conflict name mangling is required for NTFS named stream names. * * Returns: * 0 - success. * - If a matching entry was found eof will be B_FALSE and * sinfo will be populated. * - If there are no matching entries eof will be B_TRUE. * -1 - error, error details set in sr. */ int smb_odir_read_streaminfo(smb_request_t *sr, smb_odir_t *od, smb_streaminfo_t *sinfo, boolean_t *eof) { int rc; smb_odirent_t *odirent; smb_node_t *fnode; smb_attr_t attr; ASSERT(sr); ASSERT(sr->sr_magic == SMB_REQ_MAGIC); ASSERT(od); ASSERT(od->d_magic == SMB_ODIR_MAGIC); ASSERT(sinfo); mutex_enter(&od->d_mutex); ASSERT(od->d_refcnt > 0); switch (od->d_state) { case SMB_ODIR_STATE_IN_USE: case SMB_ODIR_STATE_CLOSING: break; case SMB_ODIR_STATE_OPEN: case SMB_ODIR_STATE_CLOSED: default: mutex_exit(&od->d_mutex); return (-1); } /* Check that odir represents an xattr directory */ if (!(od->d_flags & SMB_ODIR_FLAG_XATTR)) { *eof = B_TRUE; mutex_exit(&od->d_mutex); return (0); } odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP); bzero(&attr, sizeof (attr)); for (;;) { bzero(sinfo, sizeof (smb_streaminfo_t)); if ((rc = smb_odir_next_odirent(od, odirent)) != 0) break; if (strncmp(odirent->od_name, SMB_STREAM_PREFIX, SMB_STREAM_PREFIX_LEN)) { continue; } rc = smb_fsop_lookup(sr, od->d_cred, 0, od->d_tree->t_snode, od->d_dnode, odirent->od_name, &fnode); if (rc == 0) { attr.sa_mask = SMB_AT_SIZE | SMB_AT_ALLOCSZ; rc = smb_node_getattr(sr, fnode, od->d_cred, NULL, &attr); smb_node_release(fnode); } if (rc == 0) { (void) strlcpy(sinfo->si_name, odirent->od_name + SMB_STREAM_PREFIX_LEN, sizeof (sinfo->si_name)); sinfo->si_size = attr.sa_vattr.va_size; sinfo->si_alloc_size = attr.sa_allocsz; break; } } mutex_exit(&od->d_mutex); kmem_free(odirent, sizeof (smb_odirent_t)); switch (rc) { case 0: *eof = B_FALSE; return (0); case ENOENT: *eof = B_TRUE; return (0); default: smbsr_errno(sr, rc); return (-1); } }
/* * smb_odir_wildcard_fileinfo * * odirent contains a directory entry, obtained from a vop_readdir. * If a case conflict is identified the filename is mangled and the * shortname is used as 'name', in place of odirent->od_name. * * If the looked up file is a link, we attempt to lookup the link target * to use its attributes in place of those of the files's. * If we fail to lookup the target of the link we use the original * file's attributes. * Check if the attributes match the search attributes. * * Although some file systems can have directories larger than * SMB_MAXDIRSIZE smb_odir_next_odirent ensures that no offset larger * than SMB_MAXDIRSIZE is returned. It is therefore safe to use the * offset as the cookie (uint32_t). * * Returns: 0 - success * ENOENT - no match, proceed to next entry * errno - error */ static int smb_odir_wildcard_fileinfo(smb_request_t *sr, smb_odir_t *od, smb_odirent_t *odirent, smb_fileinfo_t *fileinfo) { int rc; cred_t *cr; smb_node_t *fnode, *tgt_node; smb_attr_t attr; char *name; boolean_t case_conflict; ASSERT(sr); ASSERT(sr->sr_magic == SMB_REQ_MAGIC); ASSERT(od); ASSERT(od->d_magic == SMB_ODIR_MAGIC); ASSERT(MUTEX_HELD(&od->d_mutex)); bzero(fileinfo, sizeof (smb_fileinfo_t)); rc = smb_fsop_lookup(sr, od->d_cred, SMB_CASE_SENSITIVE, od->d_tree->t_snode, od->d_dnode, odirent->od_name, &fnode); if (rc != 0) return (rc); /* follow link to get target node & attr */ if (smb_node_is_symlink(fnode) && smb_odir_lookup_link(sr, od, odirent->od_name, &tgt_node)) { smb_node_release(fnode); fnode = tgt_node; } /* skip system files */ if (smb_node_is_system(fnode)) { smb_node_release(fnode); return (ENOENT); } /* * Windows directory listings return not only names, but * also some attributes. In Unix, you need some access to * get those attributes. Which credential should we use to * get those? If we're doing Access Based Enumeration (ABE) * we want this getattr to fail, which will cause the caller * to skip this entry. If we're NOT doing ABE, we normally * want to show all the directory entries (including their * attributes) so we want this getattr to succeed! */ if (smb_tree_has_feature(od->d_tree, SMB_TREE_ABE)) cr = od->d_cred; else cr = zone_kcred(); bzero(&attr, sizeof (attr)); attr.sa_mask = SMB_AT_ALL; rc = smb_node_getattr(NULL, fnode, cr, NULL, &attr); if (rc != 0) { smb_node_release(fnode); return (rc); } /* check search attributes */ if (!smb_sattr_check(attr.sa_dosattr, od->d_sattr)) { smb_node_release(fnode); return (ENOENT); } name = odirent->od_name; if (od->d_flags & SMB_ODIR_FLAG_SHORTNAMES) { case_conflict = ((od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) && (odirent->od_eflags & ED_CASE_CONFLICT)); if (case_conflict || smb_needs_mangled(name)) { smb_mangle(name, odirent->od_ino, fileinfo->fi_shortname, SMB_SHORTNAMELEN); } if (case_conflict) name = fileinfo->fi_shortname; } (void) strlcpy(fileinfo->fi_name, name, sizeof (fileinfo->fi_name)); fileinfo->fi_cookie = (uint32_t)od->d_offset; fileinfo->fi_dosattr = attr.sa_dosattr; fileinfo->fi_nodeid = attr.sa_vattr.va_nodeid; fileinfo->fi_size = attr.sa_vattr.va_size; fileinfo->fi_alloc_size = attr.sa_allocsz; fileinfo->fi_atime = attr.sa_vattr.va_atime; fileinfo->fi_mtime = attr.sa_vattr.va_mtime; fileinfo->fi_ctime = attr.sa_vattr.va_ctime; if (attr.sa_crtime.tv_sec) fileinfo->fi_crtime = attr.sa_crtime; else fileinfo->fi_crtime = attr.sa_vattr.va_mtime; smb_node_release(fnode); return (0); }
/* * smb_odir_single_fileinfo * * Lookup the file identified by od->d_pattern. * * If the looked up file is a link, we attempt to lookup the link target * to use its attributes in place of those of the files's. * If we fail to lookup the target of the link we use the original * file's attributes. * Check if the attributes match the search attributes. * * Returns: 0 - success * ENOENT - no match * errno - error */ static int smb_odir_single_fileinfo(smb_request_t *sr, smb_odir_t *od, smb_fileinfo_t *fileinfo) { int rc; smb_node_t *fnode, *tgt_node; smb_attr_t attr; ino64_t fid; char *name; boolean_t case_conflict = B_FALSE; int lookup_flags, flags = 0; vnode_t *vp; ASSERT(sr); ASSERT(sr->sr_magic == SMB_REQ_MAGIC); ASSERT(od); ASSERT(od->d_magic == SMB_ODIR_MAGIC); ASSERT(MUTEX_HELD(&od->d_mutex)); bzero(fileinfo, sizeof (smb_fileinfo_t)); rc = smb_fsop_lookup(sr, od->d_cred, 0, od->d_tree->t_snode, od->d_dnode, od->d_pattern, &fnode); if (rc != 0) return (rc); /* * If case sensitive, do a case insensitive smb_vop_lookup to * check for case conflict */ if (od->d_flags & SMB_ODIR_FLAG_IGNORE_CASE) { lookup_flags = SMB_IGNORE_CASE; if (od->d_flags & SMB_ODIR_FLAG_CATIA) lookup_flags |= SMB_CATIA; rc = smb_vop_lookup(od->d_dnode->vp, fnode->od_name, &vp, NULL, lookup_flags, &flags, od->d_tree->t_snode->vp, NULL, od->d_cred); if (rc != 0) return (rc); VN_RELE(vp); if (flags & ED_CASE_CONFLICT) case_conflict = B_TRUE; } bzero(&attr, sizeof (attr)); attr.sa_mask = SMB_AT_ALL; rc = smb_node_getattr(sr, fnode, zone_kcred(), NULL, &attr); if (rc != 0) { smb_node_release(fnode); return (rc); } /* follow link to get target node & attr */ if (smb_node_is_symlink(fnode) && smb_odir_lookup_link(sr, od, fnode->od_name, &tgt_node)) { smb_node_release(fnode); fnode = tgt_node; attr.sa_mask = SMB_AT_ALL; rc = smb_node_getattr(sr, fnode, zone_kcred(), NULL, &attr); if (rc != 0) { smb_node_release(fnode); return (rc); } } /* check search attributes */ if (!smb_sattr_check(attr.sa_dosattr, od->d_sattr)) { smb_node_release(fnode); return (ENOENT); } name = fnode->od_name; if (od->d_flags & SMB_ODIR_FLAG_SHORTNAMES) { fid = attr.sa_vattr.va_nodeid; if (case_conflict || smb_needs_mangled(name)) { smb_mangle(name, fid, fileinfo->fi_shortname, SMB_SHORTNAMELEN); } if (case_conflict) name = fileinfo->fi_shortname; } (void) strlcpy(fileinfo->fi_name, name, sizeof (fileinfo->fi_name)); fileinfo->fi_dosattr = attr.sa_dosattr; fileinfo->fi_nodeid = attr.sa_vattr.va_nodeid; fileinfo->fi_size = attr.sa_vattr.va_size; fileinfo->fi_alloc_size = attr.sa_allocsz; fileinfo->fi_atime = attr.sa_vattr.va_atime; fileinfo->fi_mtime = attr.sa_vattr.va_mtime; fileinfo->fi_ctime = attr.sa_vattr.va_ctime; if (attr.sa_crtime.tv_sec) fileinfo->fi_crtime = attr.sa_crtime; else fileinfo->fi_crtime = attr.sa_vattr.va_mtime; smb_node_release(fnode); return (0); }