Ejemplo n.º 1
0
/*
 * If the domain to be discovered matches the current domain (i.e the
 * value of either domain or fqdn configuration), then get the primary
 * domain information from SMF.
 */
static uint32_t
smb_ddiscover_use_config(char *domain, smb_domainex_t *dxi)
{
	boolean_t use;
	smb_domain_t *dinfo;

	dinfo = &dxi->d_primary;
	bzero(dinfo, sizeof (smb_domain_t));

	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
		return (NT_STATUS_UNSUCCESSFUL);

	smb_config_getdomaininfo(dinfo->di_nbname, dinfo->di_fqname,
	    NULL, NULL, NULL);

	if (SMB_IS_FQDN(domain))
		use = (smb_strcasecmp(dinfo->di_fqname, domain, 0) == 0);
	else
		use = (smb_strcasecmp(dinfo->di_nbname, domain, 0) == 0);

	if (use)
		smb_config_getdomaininfo(NULL, NULL, dinfo->di_sid,
		    dinfo->di_u.di_dns.ddi_forest,
		    dinfo->di_u.di_dns.ddi_guid);

	return ((use) ? NT_STATUS_SUCCESS : NT_STATUS_UNSUCCESSFUL);
}
Ejemplo n.º 2
0
/*
 * [MS-DFSC]: REQ_GET_DFS_REFERRAL
 *
 * Determines the referral type based on the specified path:
 *
 * Domain referral:
 *    ""
 *
 * DC referral:
 *    \<domain>
 *
 * Sysvol referral:
 *    \<domain>\SYSVOL
 *    \<domain>\NETLOGON
 *
 * Root referral:
 *    \<domain>\<dfsname>
 *    \<server>\<dfsname>
 *
 * Link referral:
 *    \<domain>\<dfsname>\<linkpath>
 *    \<server>\<dfsname>\<linkpath>
 */
static dfs_reftype_t
smb_dfs_get_reftype(const char *path)
{
	smb_unc_t unc;
	dfs_reftype_t reftype = 0;

	if (*path == '\0')
		return (DFS_REFERRAL_DOMAIN);

	if (smb_unc_init(path, &unc) != 0)
		return (DFS_REFERRAL_INVALID);

	if (unc.unc_path != NULL) {
		reftype = DFS_REFERRAL_LINK;
	} else if (unc.unc_share != NULL) {
		if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) ||
		    (smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) {
			reftype = DFS_REFERRAL_SYSVOL;
		} else {
			reftype = DFS_REFERRAL_ROOT;
		}
	} else if (unc.unc_server != NULL) {
		reftype = DFS_REFERRAL_DC;
	}

	smb_unc_free(&unc);
	return (reftype);
}
Ejemplo n.º 3
0
/*
 * Lookup the specified target (server, share) in the given
 * target list (targets). If there is a match its index is
 * returned, otherwise -1 will be returned.
 */
static int
dfs_target_find(dfs_target_t *targets, uint32_t ntargets,
    const char *server, const char *share)
{
	dfs_target_t *t;
	int i;

	for (i = 0, t = targets; i < ntargets; i++, t++) {
		if ((smb_strcasecmp(t->t_server, server, 0) == 0) &&
		    (smb_strcasecmp(t->t_share, share, 0) == 0))
			return (i);
	}

	return (-1);
}
Ejemplo n.º 4
0
/*
 * smb_pathname_preprocess_quota
 *
 * There is a special file required by windows so that the quota
 * tab will be displayed by windows clients. This is created in
 * a special directory, $EXTEND, at the root of the shared file
 * system. To hide this directory prepend a '.' (dot).
 */
static void
smb_pathname_preprocess_quota(smb_request_t *sr, smb_pathname_t *pn)
{
	char *name = "$EXTEND";
	char *new_name = ".$EXTEND";
	char *p, *slash;
	int len;

	if (!smb_node_is_vfsroot(sr->tid_tree->t_snode))
		return;

	p = pn->pn_path;

	/* ignore any initial "\\" */
	p += strspn(p, "\\");
	if (smb_strcasecmp(p, name, strlen(name)) != 0)
		return;

	p += strlen(name);
	if ((*p != ':') && (*p != '\\') && (*p != '\0'))
		return;

	slash = (pn->pn_path[0] == '\\') ? "\\" : "";
	len = strlen(pn->pn_path) + 2;
	pn->pn_path = smb_srm_alloc(sr, len);
	(void) snprintf(pn->pn_path, len, "%s%s%s", slash, new_name, p);
	(void) smb_strupr(pn->pn_path);
}
Ejemplo n.º 5
0
/*
 * Lookup well known accounts table
 *
 * Return status:
 *
 *   NT_STATUS_SUCCESS		Account is translated successfully
 *   NT_STATUS_NOT_FOUND	This is not a well known account
 *   NT_STATUS_NONE_MAPPED	Account is found but domains don't match
 *   NT_STATUS_NO_MEMORY	Memory shortage
 *   NT_STATUS_INTERNAL_ERROR	Internal error/unexpected failure
 */
static uint32_t
lsa_lookup_name_builtin(char *domain, char *name, smb_account_t *info)
{
	smb_wka_t *wka;
	char *wkadom;

	bzero(info, sizeof (smb_account_t));

	if ((wka = smb_wka_lookup_name(name)) == NULL)
		return (NT_STATUS_NOT_FOUND);

	if ((wkadom = smb_wka_get_domain(wka->wka_domidx)) == NULL)
		return (NT_STATUS_INTERNAL_ERROR);

	if ((domain != NULL) && (smb_strcasecmp(domain, wkadom, 0) != 0))
		return (NT_STATUS_NONE_MAPPED);

	info->a_name = strdup(name);
	info->a_sid = smb_sid_dup(wka->wka_binsid);
	info->a_domain = strdup(wkadom);
	info->a_domsid = smb_sid_split(wka->wka_binsid, &info->a_rid);
	info->a_type = wka->wka_type;

	if (!smb_account_validate(info)) {
		smb_account_free(info);
		return (NT_STATUS_NO_MEMORY);
	}

	return (NT_STATUS_SUCCESS);
}
Ejemplo n.º 6
0
/*
 * Checks whether the given name matches the name of
 * the cached namespace.
 */
static boolean_t
dfs_namespace_iscached(const char *name)
{
	boolean_t iscached;

	(void) mutex_lock(&dfs_nsmtx);
	iscached = (smb_strcasecmp(name, dfs_cached_ns, 0) == 0);
	(void) mutex_unlock(&dfs_nsmtx);

	return (iscached);
}
Ejemplo n.º 7
0
static smb_lwka_t *
smb_lwka_lookup_name(char *name)
{
	int i;

	for (i = 0; i < SMB_LWKA_NUM; i++) {
		if (smb_strcasecmp(name, lwka_tbl[i].lwka_name, 0) == 0)
			return (&lwka_tbl[i]);
	}

	return (NULL);
}
Ejemplo n.º 8
0
/*
 * If this namespace hasn't been cached then return
 * without flushing the cache; otherwise clear the
 * name and flush the cache.
 */
static void
dfs_cache_flush(const char *name)
{
	(void) mutex_lock(&dfs_nsmtx);
	if (smb_strcasecmp(name, dfs_cached_ns, 0) != 0) {
		(void) mutex_unlock(&dfs_nsmtx);
		return;
	}
	*dfs_cached_ns = '\0';
	(void) smb_config_setnum(SMB_CI_DFS_STDROOT_NUM, 0);
	(void) mutex_unlock(&dfs_nsmtx);

	smb_cache_flush(&dfs_nscache);
}
Ejemplo n.º 9
0
/*
 * Cache compare function, the key is UNC path
 */
static int
dfs_cache_cmp(const void *p1, const void *p2)
{
	smb_cache_node_t *cn1 = (smb_cache_node_t *)p1;
	smb_cache_node_t *cn2 = (smb_cache_node_t *)p2;
	dfs_nscnode_t *dn1 = cn1->cn_data;
	dfs_nscnode_t *dn2 = cn2->cn_data;
	int rc;

	rc = smb_strcasecmp(dn1->nsc_uncpath, dn2->nsc_uncpath, 0);

	if (rc < 0)
		return (-1);

	if (rc > 0)
		return (1);

	return (0);
}
Ejemplo n.º 10
0
/*
 * Tries to find a matching DNS domain for the given NetBIOS domain
 * name by checking the first label of system's configured DNS domains.
 * If a match is found, it'll be returned in the passed buffer.
 */
static boolean_t
smb_ddiscover_domain_match(char *nb_domain, char *buf, uint32_t len)
{
	struct __res_state res_state;
	int i;
	char *entry, *p;
	char first_label[MAXHOSTNAMELEN];
	boolean_t found;

	if (!nb_domain || !buf)
		return (B_FALSE);

	*buf = '\0';
	bzero(&res_state, sizeof (struct __res_state));
	if (res_ninit(&res_state))
		return (B_FALSE);

	found = B_FALSE;
	entry = res_state.defdname;
	for (i = 0; entry != NULL; i++) {
		(void) strlcpy(first_label, entry, MAXHOSTNAMELEN);
		if ((p = strchr(first_label, '.')) != NULL) {
			*p = '\0';
			if (strlen(first_label) > 15)
				first_label[15] = '\0';
		}

		if (smb_strcasecmp(nb_domain, first_label, 0) == 0) {
			found = B_TRUE;
			(void) strlcpy(buf, entry, len);
			break;
		}

		entry = res_state.dnsrch[i];
	}


	res_ndestroy(&res_state);
	return (found);
}
Ejemplo n.º 11
0
/*
 * Discovers a DC for the specified domain using NETLOGON protocol.
 * If a DC cannot be found using NETLOGON then it will
 * try to resolve it via DNS, i.e. find out if it is the first label
 * of a DNS domain name. If the corresponding DNS name is found, DC
 * discovery will be done via DNS query.
 *
 * If the fully-qualified domain name is derived from the DNS config
 * file, the NetBIOS domain name specified by the user will be compared
 * against the NetBIOS domain name obtained via LSA query.  If there is
 * a mismatch, the DC discovery will fail since the discovered DC is
 * actually for another domain, whose first label of its FQDN somehow
 * matches with the NetBIOS name of the domain we're interested in.
 */
static boolean_t
smb_ddiscover_nbt(char *domain, char *server, smb_domainex_t *dxi)
{
	char dnsdomain[MAXHOSTNAMELEN];
	uint32_t status;

	*dnsdomain = '\0';

	if (!smb_browser_netlogon(domain, dxi->d_dc, MAXHOSTNAMELEN)) {
		if (!smb_ddiscover_domain_match(domain, dnsdomain,
		    MAXHOSTNAMELEN))
			return (B_FALSE);

		if (!smb_ads_lookup_msdcs(dnsdomain, server, dxi->d_dc,
		    MAXHOSTNAMELEN))
			return (B_FALSE);
	}

	status = smb_ddiscover_qinfo(domain, dxi->d_dc, dxi);
	if (status != NT_STATUS_SUCCESS)
		return (B_FALSE);

	if ((*dnsdomain != '\0') &&
	    smb_strcasecmp(domain, dxi->d_primary.di_nbname, 0))
		return (B_FALSE);

	/*
	 * Now that we get the fully-qualified DNS name of the
	 * domain via LSA query. Verifies ADS configuration
	 * if we previously locate a DC via NetBIOS. On success,
	 * ADS cache will be populated.
	 */
	if (smb_ads_lookup_msdcs(dxi->d_primary.di_fqname, server,
	    dxi->d_dc, MAXHOSTNAMELEN) == 0)
		return (B_FALSE);

	return (B_TRUE);
}
Ejemplo n.º 12
0
/*
 * Disallow NetFileClose on certain ofiles to avoid side-effects.
 * Closing a tree root is not allowed: use NetSessionDel or NetShareDel.
 * Closing SRVSVC connections is not allowed because this NetFileClose
 * request may depend on this ofile.
 */
boolean_t
smb_ofile_disallow_fclose(smb_ofile_t *of)
{
	ASSERT(of);
	ASSERT(of->f_magic == SMB_OFILE_MAGIC);
	ASSERT(of->f_refcnt);

	switch (of->f_ftype) {
	case SMB_FTYPE_DISK:
		ASSERT(of->f_tree);
		return (of->f_node == of->f_tree->t_snode);

	case SMB_FTYPE_MESG_PIPE:
		ASSERT(of->f_pipe);
		if (smb_strcasecmp(of->f_pipe->p_name, "SRVSVC", 0) == 0)
			return (B_TRUE);
		break;
	default:
		break;
	}

	return (B_FALSE);
}
Ejemplo n.º 13
0
/*
 * Compare function used by shares AVL
 */
static int
smb_kshare_cmp(const void *p1, const void *p2)
{
	smb_kshare_t *shr1 = (smb_kshare_t *)p1;
	smb_kshare_t *shr2 = (smb_kshare_t *)p2;
	int rc;

	ASSERT(shr1);
	ASSERT(shr1->shr_name);

	ASSERT(shr2);
	ASSERT(shr2->shr_name);

	rc = smb_strcasecmp(shr1->shr_name, shr2->shr_name, 0);

	if (rc < 0)
		return (-1);

	if (rc > 0)
		return (1);

	return (0);
}
Ejemplo n.º 14
0
/*
 * valid DFS I/O path:
 *
 * \server-or-domain\share
 * \server-or-domain\share\path
 *
 * All the returned errors by this function needs to be
 * checked against Windows.
 */
static int
smb_pathname_dfs_preprocess(smb_request_t *sr, char *path, size_t pathsz)
{
	smb_unc_t unc;
	char *linkpath;
	int rc;

	if (sr->tid_tree == NULL)
		return (0);

	if ((rc = smb_unc_init(path, &unc)) != 0)
		return (rc);

	if (smb_strcasecmp(unc.unc_share, sr->tid_tree->t_sharename, 0)) {
		smb_unc_free(&unc);
		return (EINVAL);
	}

	linkpath = unc.unc_path;
	(void) snprintf(path, pathsz, "/%s", (linkpath) ? linkpath : "");

	smb_unc_free(&unc);
	return (0);
}
Ejemplo n.º 15
0
/*
 * Looks up the given name in local account databases:
 *
 * SMB Local users are looked up in /var/smb/smbpasswd
 * SMB Local groups are looked up in /var/smb/smbgroup.db
 *
 * If the account is found, its information is populated
 * in the passed smb_account_t structure. Caller must free
 * allocated memories by calling smb_account_free() upon
 * successful return.
 *
 * The type of account is specified by 'type', which can be user,
 * alias (local group) or unknown. If the caller doesn't know
 * whether the name is a user or group name then SidTypeUnknown
 * should be passed.
 *
 * If a local user and group have the same name, the user will
 * always be picked. Note that this situation cannot happen on
 * Windows systems.
 *
 * If a SMB local user/group is found but it turns out that
 * it'll be mapped to a domain user/group the lookup is considered
 * failed and NT_STATUS_NONE_MAPPED is returned.
 *
 * Return status:
 *
 *   NT_STATUS_NOT_FOUND	This is not a local account
 *   NT_STATUS_NONE_MAPPED	It's a local account but cannot be
 *   				translated.
 *   other error status codes.
 */
uint32_t
smb_sam_lookup_name(char *domain, char *name, uint16_t type,
    smb_account_t *account)
{
	smb_domain_t di;
	smb_sid_t *sid;
	uint32_t status;
	smb_lwka_t *lwka;

	bzero(account, sizeof (smb_account_t));

	if (domain != NULL) {
		if (!smb_domain_lookup_name(domain, &di) ||
		    (di.di_type != SMB_DOMAIN_LOCAL))
			return (NT_STATUS_NOT_FOUND);

		/* Only Netbios hostname is accepted */
		if (smb_strcasecmp(domain, di.di_nbname, 0) != 0)
			return (NT_STATUS_NONE_MAPPED);
	} else {
		if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di))
			return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
	}

	if (smb_strcasecmp(name, di.di_nbname, 0) == 0) {
		/* This is the local domain name */
		account->a_type = SidTypeDomain;
		account->a_name = strdup("");
		account->a_domain = strdup(di.di_nbname);
		account->a_sid = smb_sid_dup(di.di_binsid);
		account->a_domsid = smb_sid_dup(di.di_binsid);
		account->a_rid = (uint32_t)-1;

		if (!smb_account_validate(account)) {
			smb_account_free(account);
			return (NT_STATUS_NO_MEMORY);
		}

		return (NT_STATUS_SUCCESS);
	}

	if ((lwka = smb_lwka_lookup_name(name)) != NULL) {
		sid = smb_sid_splice(di.di_binsid, lwka->lwka_rid);
		type = lwka->lwka_type;
	} else {
		switch (type) {
		case SidTypeUser:
			status = smb_sam_lookup_user(name, &sid);
			if (status != NT_STATUS_SUCCESS)
				return (status);
			break;

		case SidTypeAlias:
			status = smb_sam_lookup_group(name, &sid);
			if (status != NT_STATUS_SUCCESS)
				return (status);
			break;

		case SidTypeUnknown:
			type = SidTypeUser;
			status = smb_sam_lookup_user(name, &sid);
			if (status == NT_STATUS_SUCCESS)
				break;

			if (status == NT_STATUS_NONE_MAPPED)
				return (status);

			type = SidTypeAlias;
			status = smb_sam_lookup_group(name, &sid);
			if (status != NT_STATUS_SUCCESS)
				return (status);
			break;

		default:
			return (NT_STATUS_INVALID_PARAMETER);
		}
	}

	account->a_name = strdup(name);
	account->a_sid = sid;
	account->a_domain = strdup(di.di_nbname);
	account->a_domsid = smb_sid_split(sid, &account->a_rid);
	account->a_type = type;

	if (!smb_account_validate(account)) {
		smb_account_free(account);
		return (NT_STATUS_NO_MEMORY);
	}

	return (NT_STATUS_SUCCESS);
}
Ejemplo n.º 16
0
/*
 * Creates a DFS root with the given name and comment.
 *
 * This function does not create the root share, it
 * should already exist.
 */
uint32_t
dfs_namespace_add(const char *rootshr, const char *cmnt)
{
	dfs_info_t info;
	dfs_target_t t;
	smb_share_t si;
	uuid_t uuid;
	uint32_t status;

	if (*rootshr == '\\') {
		/* Windows has a special case here! */
		return (ERROR_BAD_PATHNAME);
	}

	if (smb_shr_get((char *)rootshr, &si) != NERR_Success)
		return (NERR_NetNameNotFound);

	(void) mutex_lock(&dfs_nsmtx);
	if (smb_strcasecmp(dfs_cached_ns, rootshr, 0) == 0) {
		/* This DFS root is already exported */
		(void) mutex_unlock(&dfs_nsmtx);
		return (ERROR_FILE_EXISTS);
	}

	if (*dfs_cached_ns != '\0') {
		syslog(LOG_WARNING, "dfs: trying to add %s namespace."
		    " Only one standalone namespace is supported."
		    " A namespace is already exported for %s",
		    rootshr, dfs_cached_ns);
		(void) mutex_unlock(&dfs_nsmtx);
		return (ERROR_NOT_SUPPORTED);
	}

	bzero(&info, sizeof (info));
	if (cmnt)
		(void) strlcpy(info.i_comment, cmnt, sizeof (info.i_comment));
	info.i_state = DFS_VOLUME_STATE_OK | DFS_VOLUME_FLAVOR_STANDALONE;
	info.i_timeout = DFS_ROOT_TIMEOUT;
	info.i_propflags = 0;

	uuid_generate_random(uuid);
	uuid_unparse(uuid, info.i_guid);

	dfs_target_init(&t, dfs_nbname, rootshr, DFS_STORAGE_STATE_ONLINE);

	info.i_ntargets = 1;
	info.i_targets = &t;

	if ((status = dfs_root_add(si.shr_path, &info)) != ERROR_SUCCESS) {
		(void) mutex_unlock(&dfs_nsmtx);
		return (status);
	}

	status = srvsvc_shr_setdfsroot(&si, B_TRUE);
	if (status == ERROR_SUCCESS) {
		(void) dfs_cache_add_byname(rootshr, NULL, DFS_OBJECT_ROOT);
		(void) strlcpy(dfs_cached_ns, rootshr, sizeof (dfs_cached_ns));
		(void) smb_config_setnum(SMB_CI_DFS_STDROOT_NUM, 1);
	}
	(void) mutex_unlock(&dfs_nsmtx);

	return (status);
}
Ejemplo n.º 17
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));
}
Ejemplo n.º 18
0
/*
 * 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));
}