/*
   Unix SMB/CIFS implementation.
   service (connection) opening and closing
   Copyright (C) Andrew Tridgell 1992-1998

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "system/filesys.h"
#include "system/passwd.h" /* uid_wrapper */
#include "../lib/tsocket/tsocket.h"
#include "smbd/smbd.h"
#include "smbd/globals.h"
#include "../librpc/gen_ndr/netlogon.h"
#include "../libcli/security/security.h"
#include "printing/pcap.h"
#include "passdb/lookup_sid.h"
#include "auth.h"
#include "../auth/auth_util.h"
#include "lib/param/loadparm.h"
#include "messages.h"
#include "lib/afs/afs_funcs.h"
#include "lib/util_path.h"
#include "lib/util/string_wrappers.h"
#include "source3/lib/substitute.h"

bool canonicalize_connect_path(connection_struct *conn)
{
	bool ret;
	struct smb_filename con_fname = { .base_name = conn->connectpath };
	struct smb_filename *resolved_fname = SMB_VFS_REALPATH(conn, talloc_tos(),
						&con_fname);
	if (resolved_fname == NULL) {
		return false;
	}
	ret = set_conn_connectpath(conn,resolved_fname->base_name);
	TALLOC_FREE(resolved_fname);
	return ret;
}

/****************************************************************************
 Ensure when setting connectpath it is a canonicalized (no ./ // or ../)
 absolute path stating in / and not ending in /.
****************************************************************************/

bool set_conn_connectpath(connection_struct *conn, const char *connectpath)
{
	char *destname;

	if (connectpath == NULL || connectpath[0] == '\0') {
		return false;
	}

	destname = canonicalize_absolute_path(conn, connectpath);
	if (destname == NULL) {
		return false;
	}

	DBG_DEBUG("service %s, connectpath = %s\n",
		  lp_const_servicename(SNUM(conn)), destname);

	talloc_free(conn->connectpath);
	conn->connectpath = destname;
	/*
	 * Ensure conn->cwd_fsp->fsp_name is initialized.
	 * start as conn->connectpath.
	 */
	TALLOC_FREE(conn->cwd_fsp->fsp_name);
	conn->cwd_fsp->fsp_name = synthetic_smb_fname(conn,
				conn->connectpath,
				NULL,
				NULL,
				0,
				0);
	if (conn->cwd_fsp->fsp_name == NULL) {
		return false;
	}
	return true;
}

bool chdir_current_service(connection_struct *conn)
{
	const struct smb_filename connectpath_fname = {
		.base_name = conn->connectpath,
	};
	int saved_errno = 0;
	char *utok_str = NULL;
	int ret;

	conn->lastused_count++;

	ret = vfs_ChDir(conn, &connectpath_fname);
	if (ret == 0) {
		return true;
	}
	saved_errno = errno;

	utok_str = utok_string(talloc_tos(),
			       conn->session_info->unix_token);
	if (utok_str == NULL) {
		errno = saved_errno;
		return false;
	}

	DBG_ERR("vfs_ChDir(%s) failed: %s. Current token: %s\n",
		conn->connectpath,
		strerror(saved_errno),
		utok_str);

	if (saved_errno != 0) {
		errno = saved_errno;
	}
	return false;
}

/****************************************************************************
 do some basic sainity checks on the share.
 This function modifies dev, ecode.
****************************************************************************/

static NTSTATUS share_sanity_checks(const struct tsocket_address *local_address,
				    const struct tsocket_address *remote_address,
				    const char *rhost,
				    int snum,
				    fstring dev)
{
	char *raddr;

	if (!lp_allow_local_address(snum, local_address)) {
		char *laddr = NULL;

		laddr = tsocket_address_inet_addr_string(
			local_address, talloc_tos());
		if (laddr == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		raddr = tsocket_address_inet_addr_string(
			remote_address, laddr);
		if (raddr == NULL) {
			TALLOC_FREE(laddr);
			return NT_STATUS_NO_MEMORY;
		}

		DBG_ERR("Denied connection from %s (%s) to \\\\%s\\%s\n",
			rhost, raddr, laddr, lp_const_servicename(snum));
		TALLOC_FREE(laddr);

		return NT_STATUS_BAD_NETWORK_NAME;
	}

	raddr = tsocket_address_inet_addr_string(remote_address,
						 talloc_tos());
	if (raddr == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	if (!lp_snum_ok(snum) ||
	    !allow_access(lp_hosts_deny(snum), lp_hosts_allow(snum),
			  rhost, raddr)) {
		return NT_STATUS_ACCESS_DENIED;
	}

	if (dev[0] == '?' || !dev[0]) {
		if (lp_printable(snum)) {
			fstrcpy(dev,"LPT1:");
		} else if (strequal(lp_fstype(snum), "IPC")) {
			fstrcpy(dev, "IPC");
		} else {
			fstrcpy(dev,"A:");
		}
	}

	if (!strupper_m(dev)) {
		DBG_WARNING("strupper_m %s failed\n", dev);
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (lp_printable(snum)) {
		if (!strequal(dev, "LPT1:")) {
			return NT_STATUS_BAD_DEVICE_TYPE;
		}
	} else if (strequal(lp_fstype(snum), "IPC")) {
		if (!strequal(dev, "IPC")) {
			return NT_STATUS_BAD_DEVICE_TYPE;
		}
	} else if (!strequal(dev, "A:")) {
		return NT_STATUS_BAD_DEVICE_TYPE;
	}

	/* Behave as a printer if we are supposed to */
	if (lp_printable(snum) && (strcmp(dev, "A:") == 0)) {
		fstrcpy(dev, "LPT1:");
	}

	return NT_STATUS_OK;
}

/*
 * Go through lookup_name etc to find the force'd group.
 *
 * Create a new token from src_token, replacing the primary group sid with the
 * one found.
 */

static NTSTATUS find_forced_group(bool force_user,
				  int snum, const char *username,
				  struct dom_sid *pgroup_sid,
				  gid_t *pgid)
{
	NTSTATUS result = NT_STATUS_NO_SUCH_GROUP;
	TALLOC_CTX *frame = talloc_stackframe();
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();
	struct dom_sid group_sid;
	enum lsa_SidType type;
	char *groupname;
	bool user_must_be_member = False;
	gid_t gid;

	groupname = lp_force_group(talloc_tos(), lp_sub, snum);
	if (groupname == NULL) {
		DBG_WARNING("talloc_strdup failed\n");
		result = NT_STATUS_NO_MEMORY;
		goto done;
	}

	if (groupname[0] == '+') {
		user_must_be_member = True;
		groupname += 1;
	}

	groupname = talloc_string_sub(talloc_tos(), groupname,
				      "%S", lp_const_servicename(snum));
	if (groupname == NULL) {
		DBG_WARNING("talloc_string_sub failed\n");
		result = NT_STATUS_NO_MEMORY;
		goto done;
	}

	if (!lookup_name_smbconf(talloc_tos(), groupname,
			 LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
			 NULL, NULL, &group_sid, &type)) {
		DBG_DEBUG("lookup_name_smbconf(%s) failed\n",
			   groupname);
		goto done;
	}

	if ((type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) &&
	    (type != SID_NAME_WKN_GRP)) {
		DBG_DEBUG("%s is a %s, not a group\n", groupname,
			   sid_type_lookup(type));
		goto done;
	}

	if (!sid_to_gid(&group_sid, &gid)) {
		struct dom_sid_buf buf;
		DBG_DEBUG("sid_to_gid(%s) for %s failed\n",
			   dom_sid_str_buf(&group_sid, &buf), groupname);
		goto done;
	}

	/*
	 * If the user has been forced and the forced group starts with a '+',
	 * then we only set the group to be the forced group if the forced
	 * user is a member of that group.  Otherwise, the meaning of the '+'
	 * would be ignored.
	 */

	if (force_user && user_must_be_member) {
		if (user_in_group_sid(username, &group_sid)) {
			sid_copy(pgroup_sid, &group_sid);
			*pgid = gid;
			DBG_INFO("Forced group %s for member %s\n",
				 groupname, username);
		} else {
			DBG_ERR("find_forced_group: forced user %s is not a member "
				"of forced group %s. Disallowing access.\n",
				username, groupname );
			result = NT_STATUS_MEMBER_NOT_IN_GROUP;
			goto done;
		}
	} else {
		sid_copy(pgroup_sid, &group_sid);
		*pgid = gid;
		DBG_INFO("Forced group %s\n", groupname);
	}

	result = NT_STATUS_OK;
 done:
	TALLOC_FREE(frame);
	return result;
}

/****************************************************************************
  Create an auth_session_info structure for a connection_struct
****************************************************************************/

static NTSTATUS create_connection_session_info(struct smbd_server_connection *sconn,
					      TALLOC_CTX *mem_ctx, int snum,
                                              struct auth_session_info *session_info,
                                              struct auth_session_info **presult)
{
	struct auth_session_info *result;

        if (lp_guest_only(snum)) {
                return make_session_info_guest(mem_ctx, presult);
        }

	/*
	 * This is the normal security != share case where we have a
	 * valid vuid from the session setup.                 */

	if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
		if (!lp_guest_ok(snum)) {
			DBG_WARNING("guest user (from session setup) "
				  "not permitted to access this share "
				  "(%s)\n", lp_const_servicename(snum));
			return NT_STATUS_ACCESS_DENIED;
		}
	} else {
		if (!user_ok_token(session_info->unix_info->unix_name,
				   session_info->info->domain_name,
				   session_info->security_token, snum)) {
			DBG_WARNING("user '%s' (from session setup) not "
				  "permitted to access this share "
				  "(%s)\n",
				  session_info->unix_info->unix_name,
				  lp_const_servicename(snum));
			return NT_STATUS_ACCESS_DENIED;
		}
	}

	result = copy_session_info(mem_ctx, session_info);
	if (result == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	*presult = result;
	return NT_STATUS_OK;
}

/****************************************************************************
  Set relevant user and group settings corresponding to force user/group
  configuration for the given snum.
****************************************************************************/

NTSTATUS set_conn_force_user_group(connection_struct *conn, int snum)
{
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();
	NTSTATUS status;

	if (*lp_force_user(talloc_tos(), lp_sub, snum)) {

		/*
		 * Replace conn->session_info with a completely faked up one
		 * from the username we are forced into :-)
		 */

		char *fuser;
		char *sanitized_username;
		struct auth_session_info *forced_serverinfo;
		bool guest;

		fuser = talloc_string_sub(conn, lp_force_user(talloc_tos(), lp_sub, snum), "%S",
					  lp_const_servicename(snum));
		if (fuser == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		guest = security_session_user_level(conn->session_info, NULL) < SECURITY_USER;

		status = make_session_info_from_username(
			conn, fuser,
			guest,
			&forced_serverinfo);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		/* We don't want to replace the original sanitized_username
		   as it is the original user given in the connect attempt.
		   This is used in '%U' substitutions. */
		sanitized_username = discard_const_p(char,
			forced_serverinfo->unix_info->sanitized_username);
		TALLOC_FREE(sanitized_username);
		forced_serverinfo->unix_info->sanitized_username =
			talloc_move(forced_serverinfo->unix_info,
				&conn->session_info->unix_info->sanitized_username);

		TALLOC_FREE(conn->session_info);
		conn->session_info = forced_serverinfo;

		conn->force_user = true;
		DBG_INFO("Forced user %s\n", fuser);
	}

	/*
	 * If force group is true, then override
	 * any groupid stored for the connecting user.
	 */

	if (*lp_force_group(talloc_tos(), lp_sub, snum)) {

		status = find_forced_group(
			conn->force_user, snum, conn->session_info->unix_info->unix_name,
			&conn->session_info->security_token->sids[1],
			&conn->session_info->unix_token->gid);

		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		/*
		 * We need to cache this gid, to use within
		 * change_to_user() separately from the conn->session_info
		 * struct. We only use conn->session_info directly if
		 * "force_user" was set.
		 */
		conn->force_group_gid = conn->session_info->unix_token->gid;
	}

	return NT_STATUS_OK;
}

static NTSTATUS notify_init_sconn(struct smbd_server_connection *sconn)
{
	NTSTATUS status;

	if (sconn->notify_ctx != NULL) {
		return NT_STATUS_OK;
	}

	sconn->notify_ctx = notify_init(sconn, sconn->msg_ctx,
					sconn, notify_callback);
	if (sconn->notify_ctx == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = messaging_register(sconn->msg_ctx, sconn,
				    MSG_SMB_NOTIFY_CANCEL_DELETED,
				    smbd_notify_cancel_deleted);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_DEBUG("messaging_register failed: %s\n",
			  nt_errstr(status));
		TALLOC_FREE(sconn->notify_ctx);
		return status;
	}

	status = messaging_register(sconn->msg_ctx, sconn,
				    MSG_SMB_NOTIFY_STARTED,
				    smbd_notifyd_restarted);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_DEBUG("messaging_register failed: %s\n",
			  nt_errstr(status));
		messaging_deregister(sconn->msg_ctx,
				     MSG_SMB_NOTIFY_CANCEL_DELETED, sconn);
		TALLOC_FREE(sconn->notify_ctx);
		return status;
	}

	return NT_STATUS_OK;
}

/****************************************************************************
  Make a connection, given the snum to connect to, and the vuser of the
  connecting user if appropriate.
****************************************************************************/

NTSTATUS make_connection_snum(struct smbXsrv_connection *xconn,
			      connection_struct *conn,
			      int snum,
			      struct smbXsrv_session *session,
			      const char *pdev)
{
	struct smbd_server_connection *sconn = xconn->client->sconn;
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();
	struct smb_filename *smb_fname_cpath = NULL;
	fstring dev;
	int ret;
	bool on_err_call_dis_hook = false;
	uid_t effuid;
	gid_t effgid;
	NTSTATUS status;
	bool ok;

	fstrcpy(dev, pdev);

	status = share_sanity_checks(sconn->local_address,
				       sconn->remote_address,
				       sconn->remote_hostname,
				       snum,
				       dev);
	if (NT_STATUS_IS_ERR(status)) {
		goto err_root_exit;
	}

	conn->params->service = snum;

	status = create_connection_session_info(sconn,
		conn, snum, session->global->auth_session_info,
		&conn->session_info);

	if (!NT_STATUS_IS_OK(status)) {
		DBG_WARNING("create_connection_session_info failed: %s\n",
			  nt_errstr(status));
		goto err_root_exit;
	}

	if (lp_guest_only(snum)) {
		conn->force_user = true;
	}

	conn->num_files_open = 0;
	conn->lastused = conn->lastused_count = time(NULL);
	conn->printer = (strncmp(dev,"LPT",3) == 0);
	conn->ipc = ( (strncmp(dev,"IPC",3) == 0) ||
		      ( lp_enable_asu_support() && strequal(dev,"ADMIN$")) );

	/* Case options for the share. */
	conn_setup_case_options(conn);

	conn->encrypt_level = lp_server_smb_encrypt(snum);
	if (conn->encrypt_level > SMB_ENCRYPTION_OFF) {
		if (lp_server_smb_encrypt(-1) == SMB_ENCRYPTION_OFF) {
			if (conn->encrypt_level == SMB_ENCRYPTION_REQUIRED) {
				DBG_ERR("Service [%s] requires encryption, but "
					"it is disabled globally!\n",
					lp_const_servicename(snum));
				status = NT_STATUS_ACCESS_DENIED;
				goto err_root_exit;
			}
			conn->encrypt_level = SMB_ENCRYPTION_OFF;
		}
	}

	conn->veto_list = NULL;
	conn->hide_list = NULL;
	conn->veto_oplock_list = NULL;
	conn->aio_write_behind_list = NULL;

	conn->read_only = lp_read_only(SNUM(conn));

	status = set_conn_force_user_group(conn, snum);
	if (!NT_STATUS_IS_OK(status)) {
		goto err_root_exit;
	}

	conn->vuid = session->global->session_wire_id;

	{
		char *s = talloc_sub_full(talloc_tos(),
					lp_const_servicename(SNUM(conn)),
					conn->session_info->unix_info->unix_name,
					conn->connectpath,
					conn->session_info->unix_token->gid,
					conn->session_info->unix_info->sanitized_username,
					conn->session_info->info->domain_name,
					lp_path(talloc_tos(), lp_sub, snum));
		if (!s) {
			status = NT_STATUS_NO_MEMORY;
			goto err_root_exit;
		}

		if (!set_conn_connectpath(conn,s)) {
			TALLOC_FREE(s);
			status = NT_STATUS_NO_MEMORY;
			goto err_root_exit;
		}
		DBG_NOTICE("Connect path is '%s' for service [%s]\n", s,
			   lp_const_servicename(snum));
		TALLOC_FREE(s);
	}

	/*
	 * Set up the share security descriptor.
	 * NOTE - we use the *INCOMING USER* session_info
	 * here, as does (indirectly) change_to_user(),
	 * which can be called on any incoming packet.
	 * This way we set up the share access based
	 * on the authenticated user, not the forced
	 * user. See bug:
	 *
	 * https://bugzilla.samba.org/show_bug.cgi?id=9878
	 */

	status = check_user_share_access(conn,
					session->global->auth_session_info,
					&conn->share_access,
					&conn->read_only);
	if (!NT_STATUS_IS_OK(status)) {
		goto err_root_exit;
	}

	/* Initialise VFS function pointers */

	if (!smbd_vfs_init(conn)) {
		DBG_ERR("vfs_init failed for service %s\n",
			lp_const_servicename(snum));
		status = NT_STATUS_BAD_NETWORK_NAME;
		goto err_root_exit;
	}

/* ROOT Activities: */
	/* explicitly check widelinks here so that we can correctly warn
	 * in the logs. */
	widelinks_warning(snum);

	/* Invoke VFS make connection hook - this must be the first
	   filesystem operation that we do. */

	if (SMB_VFS_CONNECT(conn, lp_const_servicename(snum),
			    conn->session_info->unix_info->unix_name) < 0) {
		DBG_WARNING("SMB_VFS_CONNECT for service '%s' at '%s' failed: %s\n",
			    lp_const_servicename(snum), conn->connectpath,
			    strerror(errno));
		status = NT_STATUS_UNSUCCESSFUL;
		goto err_root_exit;
	}

	/* Any error exit after here needs to call the disconnect hook. */
	on_err_call_dis_hook = true;

	if ((!conn->printer) && (!conn->ipc) &&
	    lp_change_notify()) {

		status = notify_init_sconn(sconn);
		if (!NT_STATUS_IS_OK(status)) {
			goto err_root_exit;
		}
	}

	if (lp_kernel_oplocks(snum)) {
		init_kernel_oplocks(conn->sconn);
	}

	/*
	 * Fix compatibility issue pointed out by Volker.
	 * We pass the conn->connectpath to the preexec
	 * scripts as a parameter, so attempt to canonicalize
	 * it here before calling the preexec scripts.
	 * We ignore errors here, as it is possible that
	 * the conn->connectpath doesn't exist yet and
	 * the preexec scripts will create them.
	 */

	(void)canonicalize_connect_path(conn);

	/* Preexecs are done here as they might make the dir we are to ChDir
	 * to below */
	/* execute any "root preexec = " line */
	if (*lp_root_preexec(talloc_tos(), lp_sub, snum)) {
		char *cmd = talloc_sub_full(talloc_tos(),
					lp_const_servicename(SNUM(conn)),
					conn->session_info->unix_info->unix_name,
					conn->connectpath,
					conn->session_info->unix_token->gid,
					conn->session_info->unix_info->sanitized_username,
					conn->session_info->info->domain_name,
					lp_root_preexec(talloc_tos(), lp_sub, snum));
		DBG_INFO("cmd=%s\n",cmd);
		ret = smbrun(cmd, NULL, NULL);
		TALLOC_FREE(cmd);
		if (ret != 0 && lp_root_preexec_close(snum)) {
			DBG_WARNING("root preexec gave %d - failing "
				 "connection\n", ret);
			status = NT_STATUS_ACCESS_DENIED;
			goto err_root_exit;
		}
	}

/* USER Activities: */
	if (!change_to_user_and_service(conn, conn->vuid)) {
		/* No point continuing if they fail the basic checks */
		DBG_ERR("Can't become connected user!\n");
		status = NT_STATUS_LOGON_FAILURE;
		goto err_root_exit;
	}

	effuid = geteuid();
	effgid = getegid();

	/* Remember that a different vuid can connect later without these
	 * checks... */

	/* Preexecs are done here as they might make the dir we are to ChDir
	 * to below */

	/* execute any "preexec = " line */
	if (*lp_preexec(talloc_tos(), lp_sub, snum)) {
		char *cmd = talloc_sub_full(talloc_tos(),
					lp_const_servicename(SNUM(conn)),
					conn->session_info->unix_info->unix_name,
					conn->connectpath,
					conn->session_info->unix_token->gid,
					conn->session_info->unix_info->sanitized_username,
					conn->session_info->info->domain_name,
					lp_preexec(talloc_tos(), lp_sub, snum));
		ret = smbrun(cmd, NULL, NULL);
		TALLOC_FREE(cmd);
		if (ret != 0 && lp_preexec_close(snum)) {
			DBG_WARNING("preexec gave %d - failing connection\n",
				 ret);
			status = NT_STATUS_ACCESS_DENIED;
			goto err_root_exit;
		}
	}

#ifdef WITH_FAKE_KASERVER
	if (lp_afs_share(snum)) {
		afs_login(conn);
	}
#endif

	/*
	 * we've finished with the user stuff - go back to root
	 * so the SMB_VFS_STAT call will only fail on path errors,
	 * not permission problems.
	 */
	change_to_root_user();
/* ROOT Activities: */

	/*
	 * Canonicalise the connect
	 * path here to ensure we don't have any symlinks in the
	 * connectpath. We will be checking all paths on this connection are
	 * below this directory. We must do this after the VFS init as we
	 * depend on the realpath() pointer in the vfs table. JRA.
	 */
	ok = canonicalize_connect_path(conn);
	if (!ok) {
		DBG_ERR("canonicalize_connect_path failed "
		"for service %s, path %s\n",
			lp_const_servicename(snum),
			conn->connectpath);
		status = NT_STATUS_BAD_NETWORK_NAME;
		goto err_root_exit;
	}

	/* Add veto/hide lists */
	if (!IS_IPC(conn) && !IS_PRINT(conn)) {
		ok = set_namearray(conn,
				   lp_veto_oplock_files(talloc_tos(), lp_sub, snum),
				   &conn->veto_oplock_list);
		if (!ok) {
			status = NT_STATUS_NO_MEMORY;
			goto err_root_exit;
		}
		ok = set_namearray(conn,
				   lp_aio_write_behind(talloc_tos(), lp_sub, snum),
				   &conn->aio_write_behind_list);
		if (!ok) {
			status = NT_STATUS_NO_MEMORY;
			goto err_root_exit;
		}
	}
	smb_fname_cpath = synthetic_smb_fname(talloc_tos(),
					conn->connectpath,
					NULL,
					NULL,
					0,
					0);
	if (smb_fname_cpath == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto err_root_exit;
	}

	/* win2000 does not check the permissions on the directory
	   during the tree connect, instead relying on permission
	   check during individual operations. To match this behaviour
	   I have disabled this chdir check (tridge) */
	/* the alternative is just to check the directory exists */

	if ((ret = SMB_VFS_STAT(conn, smb_fname_cpath)) != 0 ||
	    !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
		if (ret == 0 && !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
			DBG_ERR("'%s' is not a directory, when connecting to "
				 "[%s]\n", conn->connectpath,
				 lp_const_servicename(snum));
		} else {
			DBG_ERR("'%s' does not exist or permission denied "
				 "when connecting to [%s] Error was %s\n",
				 conn->connectpath,
				 lp_const_servicename(snum),
				 strerror(errno));
		}
		status = NT_STATUS_BAD_NETWORK_NAME;
		goto err_root_exit;
	}
	conn->base_share_dev = smb_fname_cpath->st.st_ex_dev;

	/* Figure out the characteristics of the underlying filesystem. This
	 * assumes that all the filesystem mounted within a share path have
	 * the same characteristics, which is likely but not guaranteed.
	 */

	if(!IS_IPC(conn) ){
		conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn, &conn->ts_res);
	}
	/*
	 * Print out the 'connected as' stuff here as we need
	 * to know the effective uid and gid we will be using
	 * (at least initially).
	 */

	if( DEBUGLVL( IS_IPC(conn) ?  DBGLVL_INFO : DBGLVL_NOTICE ) ) {
		bool signing_active;

		dbgtext( "%s (%s) ", get_remote_machine_name(),
			 tsocket_address_string(conn->sconn->remote_address,
						talloc_tos()) );
#if defined(WITH_SMB1SERVER)
		if (conn_using_smb2(sconn)) {
#endif
			signing_active = smb2_signing_key_valid(
						session->global->encryption_key);
#if defined(WITH_SMB1SERVER)
		} else {
			signing_active = smb1_srv_is_signing_active(xconn);
		}
#endif
		dbgtext( "%s", signing_active ? "signed " : "");
		dbgtext( "connect to service %s ",
			 lp_const_servicename(snum) );
		dbgtext( "initially as user %s ",
			 conn->session_info->unix_info->unix_name );
		dbgtext( "(uid=%d, gid=%d) ", (int)effuid, (int)effgid );
		dbgtext( "(pid %d)\n", (int)getpid() );
	}

	conn->tcon_done = true;
	return NT_STATUS_OK;

  err_root_exit:

	TALLOC_FREE(smb_fname_cpath);
	/* We must exit this function as root. */
	if (geteuid() != 0) {
		change_to_root_user();
	}
	if (on_err_call_dis_hook) {
		/* Call VFS disconnect hook */
		SMB_VFS_DISCONNECT(conn);
	}
	return status;
}

/****************************************************************************
 Make a connection to a service from SMB2. External SMB2 interface.
 We must set cnum before claiming connection.
****************************************************************************/

connection_struct *make_connection_smb2(struct smbd_smb2_request *req,
					struct smbXsrv_tcon *tcon,
					int snum,
					const char *pdev,
					NTSTATUS *pstatus)
{
	struct smbd_server_connection *sconn = req->sconn;
	connection_struct *conn = conn_new(sconn);
	if (!conn) {
		DBG_ERR("make_connection_smb2: Couldn't find free connection.\n");
		*pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
		return NULL;
	}

	conn->cnum = tcon->global->tcon_wire_id;
	conn->tcon = tcon;

	*pstatus = make_connection_snum(req->xconn,
					conn,
					snum,
					req->session,
					pdev);
	if (!NT_STATUS_IS_OK(*pstatus)) {
		conn_free(conn);
		return NULL;
	}
	return conn;
}

/****************************************************************************
 Close a cnum.
****************************************************************************/

void close_cnum(connection_struct *conn,
		uint64_t vuid,
		enum file_close_type close_type)
{
	char rootpath[2] = { '/', '\0'};
	struct smb_filename root_fname = { .base_name = rootpath };
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();

	file_close_conn(conn, close_type);

	change_to_root_user();

	DEBUG(IS_IPC(conn)?DBGLVL_INFO:DBGLVL_NOTICE, ("%s (%s) closed connection to service %s\n",
				 get_remote_machine_name(),
				 tsocket_address_string(conn->sconn->remote_address,
							talloc_tos()),
				 lp_const_servicename(SNUM(conn))));

	/* make sure we leave the directory available for unmount */
	vfs_ChDir(conn, &root_fname);

	/* Call VFS disconnect hook */
	SMB_VFS_DISCONNECT(conn);

	/* execute any "postexec = " line */
	if (*lp_postexec(talloc_tos(), lp_sub, SNUM(conn)) &&
	    change_to_user_and_service(conn, vuid))  {
		char *cmd = talloc_sub_full(talloc_tos(),
					lp_const_servicename(SNUM(conn)),
					conn->session_info->unix_info->unix_name,
					conn->connectpath,
					conn->session_info->unix_token->gid,
					conn->session_info->unix_info->sanitized_username,
					conn->session_info->info->domain_name,
					lp_postexec(talloc_tos(), lp_sub, SNUM(conn)));
		smbrun(cmd, NULL, NULL);
		TALLOC_FREE(cmd);
		change_to_root_user();
	}

	change_to_root_user();
	/* execute any "root postexec = " line */
	if (*lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn)))  {
		char *cmd = talloc_sub_full(talloc_tos(),
					lp_const_servicename(SNUM(conn)),
					conn->session_info->unix_info->unix_name,
					conn->connectpath,
					conn->session_info->unix_token->gid,
					conn->session_info->unix_info->sanitized_username,
					conn->session_info->info->domain_name,
					lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn)));
		smbrun(cmd, NULL, NULL);
		TALLOC_FREE(cmd);
	}

	conn_free(conn);
}
