/*
 * Samba Unix/Linux SMB client library
 * Distributed SMB/CIFS Server Management Utility
 * Copyright (C) 2019 Ralph Boehme <slow@samba.org>
 *
 * 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 <talloc.h>
#include <tevent.h>
#include <ftw.h>
#include "system/filesys.h"
#include "system/passwd.h"
#include "lib/param/loadparm.h"
#include "lib/param/param.h"
#include "libcli/security/security.h"
#include "smbd/proto.h"
#include "locking/share_mode_lock.h"
#include "locking/proto.h"
#include "auth.h"
#include "client.h"
#include "util_sd.h"
#include "lib/adouble.h"
#include "lib/string_replace.h"
#include "utils/net.h"
#include "lib/global_contexts.h"

#define NET_VFS_CMD_STREAM_TO_ADOUBLE "stream2adouble"

static struct net_vfs_state {
	TALLOC_CTX *mem_ctx;
	struct net_context *c;
	struct auth_session_info *session_info;
	struct conn_struct_tos *conn_tos;
} state;

static void net_vfs_usage(void)
{
	fprintf(stderr,
		"Usage:\n"
		"net vfs [OPTIONS] <share> ....\n");
}

static void net_vfs_getntacl_usage(void)
{
	fprintf(stderr,
		"Usage:\n"
		"net vfs getntacl <share> <path>\n");
}

static void net_vfs_stream_to_appledouble_usage(void)
{
	fprintf(stderr,
		"Usage:\n"
		"net vfs " NET_VFS_CMD_STREAM_TO_ADOUBLE
		" [OPTIONS] <share> <path> [<path> ...]\n"
		"Options:\n"
		"  --verbose             verbose output\n"
		"  --continue            continue on error\n"
		"  --recursive           traverse directory hierarchy\n"
		"  --follow-symlinks     follow symlinks\n");
}

static bool net_vfs_make_session_info(struct auth_session_info **session_info)
{
	NTSTATUS status;

	if (non_root_mode()) {
		struct passwd *p = NULL;

		p = getpwuid(geteuid());
		if (p == NULL) {
			fprintf(stderr, "getpwuid(%d) failed\n", geteuid());
			return false;
		}

		status = make_session_info_from_username(state.mem_ctx,
							 p->pw_name,
							 false,
							 session_info);
		if (!NT_STATUS_IS_OK(status)) {
			fprintf(stderr, "session_info from username failed\n");
			return false;
		}

		return true;
	}

	status = init_system_session_info(state.mem_ctx);
	if (!NT_STATUS_IS_OK(status)) {
		fprintf(stderr, "init_system_session_info failed\n");
		return false;
	}

	status = make_session_info_system(state.mem_ctx, session_info);
	if (!NT_STATUS_IS_OK(status)) {
		fprintf(stderr, "make_session_info_system failed\n");
		return false;
	}

	return true;
}

static int net_vfs_init(struct net_context *c, int argc, const char **argv)
{
	const struct loadparm_substitution *lp_sub =
		loadparm_s3_global_substitution();
	const char *service = NULL;
	char *share_root = NULL;
	int snum;
	NTSTATUS status;
	bool ok;
	int rc = 1;

	state = (struct net_vfs_state) {
		.c = c,
		.mem_ctx = c,
	};

	if (argc < 1) {
		net_vfs_usage();
		goto done;
	}

	if (geteuid() != 0 && !uid_wrapper_enabled()) {
		fprintf(stderr, "'net vfs' must be run as root.\n");
		goto done;
	}

	smb_init_locale();
	umask(0);
	sec_init();
	setup_logging("net", DEBUG_STDOUT);
	lpcfg_set_cmdline(c->lp_ctx, "log level", "0");

	ok = lp_load_with_registry_shares(get_dyn_CONFIGFILE());
	if (!ok) {
		fprintf(stderr, "lp_load_with_registry_shares failed\n");
		goto done;
	}

	ok = locking_init();
	if (!ok) {
		fprintf(stderr, "locking init failed\n");
		goto done;
	}

	ok = net_vfs_make_session_info(&state.session_info);
	if (!ok) {
		goto done;
	}

	service = argv[0];
	snum = lp_servicenumber(service);
	if (snum == -1) {
		fprintf(stderr, "unknown service: %s\n", service);
		goto done;
	}

	share_root = lp_path(state.mem_ctx, lp_sub, snum);
	if (share_root == NULL) {
		fprintf(stderr, "Failed to find share root for service: %s\n",
			service);
		goto done;
	}

	status = create_conn_struct_tos_cwd(global_messaging_context(),
					    snum,
					    share_root,
					    state.session_info,
					    &state.conn_tos);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	state.conn_tos->conn->share_access = FILE_GENERIC_ALL;
	state.conn_tos->conn->read_only = false;
	file_init(state.conn_tos->conn->sconn);

	ok = become_user_without_service_by_session(state.conn_tos->conn,
						    state.session_info);
	if (!ok) {
		fprintf(stderr,
			"become_user_without_service_by_session failed\n");
		goto done;
	}

	rc = 0;
done:
	return rc;
}

static int net_vfs_get_ntacl(struct net_context *net,
			     int argc,
			     const char **argv)
{
	const char *path = NULL;
	struct smb_filename *smb_fname = NULL;
	files_struct *fsp = NULL;
	struct security_descriptor *sd = NULL;
	NTSTATUS status;
	int ret;
	int rc = 1;

	if (argc < 2 || net->display_usage) {
		net_vfs_getntacl_usage();
		goto done;
	}

	ret = net_vfs_init(net, argc, argv);
	if (ret != 0) {
		goto done;
	}

	path = argv[1];
	smb_fname = synthetic_smb_fname(state.mem_ctx,
					path,
					NULL,
					NULL,
					0,
					0);
	if (smb_fname == NULL) {
		goto done;
	}

	ret = SMB_VFS_STAT(state.conn_tos->conn, smb_fname);
	if (ret != 0) {
		fprintf(stderr, "stat [%s] failed: %s\n",
			smb_fname_str_dbg(smb_fname), strerror(errno));
		goto done;
	}

	status = openat_pathref_fsp(state.conn_tos->conn->cwd_fsp, smb_fname);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("openat_pathref_fsp [%s] failed: %s\n",
			smb_fname_str_dbg(smb_fname), nt_errstr(status));
		goto done;
	}

	status = SMB_VFS_CREATE_FILE(
		state.conn_tos->conn,
		NULL,				/* req */
		NULL,
		smb_fname,
		FILE_READ_ATTRIBUTES|READ_CONTROL_ACCESS,
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		FILE_OPEN,
		0,				/* create_options */
		0,				/* file_attributes */
		INTERNAL_OPEN_ONLY,		/* oplock_request */
		NULL,				/* lease */
		0,				/* allocation_size */
		0,				/* private_flags */
		NULL,				/* sd */
		NULL,				/* ea_list */
		&fsp,
		NULL,				/* info */
		NULL, NULL);			/* create context */
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed: %s\n",
			smb_fname_str_dbg(smb_fname), nt_errstr(status));
		goto done;
	}

	status = SMB_VFS_FGET_NT_ACL(fsp,
				     SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL,
				     talloc_tos(),
				     &sd);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("SMB_VFS_FGET_NT_ACL [%s] failed: %s\n",
			smb_fname_str_dbg(smb_fname), nt_errstr(status));
		goto done;
	}

	status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("close_file [%s] failed: %s\n",
			smb_fname_str_dbg(smb_fname),
			nt_errstr(status));
		goto done;
	}

	sec_desc_print(NULL, stdout, sd, true);

	rc = 0;
done:
	TALLOC_FREE(sd);

	if (fsp != NULL) {
		status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
		if (!NT_STATUS_IS_OK(status)) {
			DBG_ERR("close_file_free() [%s] failed: %s\n",
				smb_fname_str_dbg(smb_fname),
				nt_errstr(status));
			rc = 1;
		}
	}
	return rc;
}

static bool do_unfruit(const char *path)
{
	struct smb_filename *smb_fname = NULL;
	char *p = NULL;
	bool converted;
	int ret;
	bool ok;

	p = strrchr_m(path, '/');
	if (p != NULL) {
		if (p[1] == '.' && p[2] == '_') {
			return true;
		}
	}

	smb_fname = synthetic_smb_fname(state.mem_ctx,
					path,
					NULL,
					NULL,
					0,
					0);
	if (smb_fname == NULL) {
		return false;
	}

	ret = SMB_VFS_STAT(state.conn_tos->conn, smb_fname);
	if (ret != 0) {
		fprintf(stderr, "%s: %s\n", path, strerror(errno));
		if (state.c->opt_continue_on_error) {
			return true;
		}
		return false;
	}

	ok = ad_unconvert(state.mem_ctx,
			  state.conn_tos->conn->vfs_handles,
			  macos_string_replace_map,
			  smb_fname,
			  &converted);
	if (!ok) {
		fprintf(stderr, "Converting failed: %s\n", path);
		if (state.c->opt_continue_on_error) {
			return true;
		}
		return false;
	}

	if (converted) {
		fprintf(stdout, "Converted: %s\n", path);
	} else if (state.c->opt_verbose) {
		fprintf(stdout, "%s\n", path);
	}
	return true;
}

static int nftw_cb(const char *path,
		   const struct stat *sb,
		   int typeflag,
		   struct FTW *ftwbuf)
{
	bool ok;

	if (typeflag == FTW_SL) {
		if (state.c->opt_verbose) {
			fprintf(stdout, "Ignoring symlink: %s\n", path);
		}
		return 0;
	}

	ok = do_unfruit(path);
	if (!ok) {
		return -1;
	}

	return 0;
}

static int net_vfs_stream_to_appledouble(struct net_context *net,
					 int argc,
					 const char **argv)
{
	int i;
	int ret;
	bool ok;
	int rc = 1;

	if (argc < 2 || net->display_usage) {
		net_vfs_stream_to_appledouble_usage();
		goto done;
	}

	ret = net_vfs_init(net, argc, argv);
	if (ret != 0) {
		goto done;
	}

	for (i = 1; i < argc; i++) {
		const char *path = argv[i];

		if (path[0] == '/') {
			fprintf(stderr, "ignoring absolute path: %s\n", path);
			if (state.c->opt_continue_on_error) {
				continue;
			}
			goto done;
		}

		if (!state.c->opt_recursive) {
			ok = do_unfruit(path);
			if (!ok) {
				if (!state.c->opt_continue_on_error) {
					goto done;
				}
			}
			continue;
		}

		ret = nftw(path,
			   nftw_cb,
			   256,
			   state.c->opt_follow_symlink ? 0 : FTW_PHYS);
		if (ret != 0) {
			fprintf(stderr, "%s: %s\n", path, strerror(errno));
			if (!state.c->opt_continue_on_error) {
				goto done;
			}
		}
	}

	rc = 0;

done:
	return rc;
}

static struct functable func[] = {
	{
		"getntacl",
		net_vfs_get_ntacl,
		NET_TRANSPORT_LOCAL,
		N_("Display security descriptor of a file or directory"),
		N_("net vfs getntacl <share> <path> [<path> ...]")
	},
	{
		NET_VFS_CMD_STREAM_TO_ADOUBLE,
		net_vfs_stream_to_appledouble,
		NET_TRANSPORT_LOCAL,
		N_("Convert streams to AppleDouble files"),
		N_("net vfs " NET_VFS_CMD_STREAM_TO_ADOUBLE " [OPTIONS] <share> <path> [<path> ...]")
	},
	{NULL, NULL, 0, NULL, NULL}
};

int net_vfs(struct net_context *c, int argc, const char **argv)
{
	return net_run_function(c, argc, argv, "net vfs", func);
}
