/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * BSD 3 Clause License
 *
 * Copyright (c) 2007, The Storage Networking Industry Association.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 	- Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 *
 * 	- Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in
 *	  the documentation and/or other materials provided with the
 *	  distribution.
 *
 *	- Neither the name of The Storage Networking Industry Association (SNIA)
 *	  nor the names of its contributors may be used to endorse or promote
 *	  products derived from this software without specific prior written
 *	  permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/* Copyright (c) 2007, The Storage Networking Industry Association. */
/* Copyright (c) 1996, 1997 PDC, Network Appliance. All Rights Reserved */
/* Copyright 2014 Nexenta Systems, Inc.  All rights reserved. */

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/mntio.h>
#include <sys/statvfs.h>
#include <sys/utsname.h>
#include <sys/scsi/scsi.h>
#include <unistd.h>
#include <sys/systeminfo.h>
#include "ndmpd_common.h"
#include "ndmpd.h"

static void simple_get_attrs(ulong_t *attributes);

/*
 * Number of environment variable for the file system
 * info in V3 net_fs_info.
 */
#define	V3_N_FS_ENVS	4

/*
 * Is the file system a valid one to be reported to the
 * clients?
 */
#define	IS_VALID_FS(fs) (fs && ( \
	strcasecmp(fs->mnt_fstype, MNTTYPE_UFS) == 0 || \
	strcasecmp(fs->mnt_fstype, MNTTYPE_ZFS) == 0 || \
	strcasecmp(fs->mnt_fstype, MNTTYPE_NFS) == 0 || \
	strcasecmp(fs->mnt_fstype, MNTTYPE_NFS3) == 0 || \
	strcasecmp(fs->mnt_fstype, MNTTYPE_NFS4) == 0))

#define	MNTTYPE_LEN	10

extern struct fs_ops sfs2_ops;
extern struct fs_ops sfs2cpv_ops;


/*
 * ************************************************************************
 * NDMP V2 HANDLERS
 * ************************************************************************
 */

/*
 * ndmpd_config_get_host_info_v2
 *
 * This handler handles the ndmp_config_get_host_info request.
 * Host specific information is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_host_info_v2(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_host_info_reply_v2 reply;
	ndmp_auth_type auth_types[2];
	char buf[HOSTNAMELEN + 1];
	struct utsname uts;
	char hostidstr[16];
	ulong_t hostid;

	(void) memset((void*)&reply, 0, sizeof (reply));
	(void) memset(buf, 0, sizeof (buf));
	(void) gethostname(buf, sizeof (buf));

	reply.error = NDMP_NO_ERR;
	reply.hostname = buf;
	(void) uname(&uts);
	reply.os_type = uts.sysname;
	reply.os_vers = uts.release;

	if (sysinfo(SI_HW_SERIAL, hostidstr, sizeof (hostidstr)) < 0) {
		NDMP_LOG(LOG_DEBUG, "sysinfo error: %m.");
		reply.error = NDMP_UNDEFINED_ERR;
	}

	/*
	 * Convert the hostid to hex. The returned string must match
	 * the string returned by hostid(1).
	 */
	hostid = strtoul(hostidstr, 0, 0);
	(void) snprintf(hostidstr, sizeof (hostidstr), "%lx", hostid);
	reply.hostid = hostidstr;

	auth_types[0] = NDMP_AUTH_TEXT;
	reply.auth_type.auth_type_len = 1;
	reply.auth_type.auth_type_val = auth_types;

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_host_info reply");
}


/*
 * ndmpd_config_get_butype_attr_v2
 *
 * This handler handles the ndmp_config_get_butype_attr request.
 * Information about the specified backup type is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
void
ndmpd_config_get_butype_attr_v2(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_butype_attr_request *request;
	ndmp_config_get_butype_attr_reply reply;

	request = (ndmp_config_get_butype_attr_request *)body;

	reply.error = NDMP_NO_ERR;

	if (strcmp(request->name, "dump") == 0) {
		(void) simple_get_attrs(&reply.attrs);
	} else if (strcmp(request->name, "tar") == 0) {
		reply.attrs = NDMP_NO_BACKUP_FILELIST;
	} else {
		NDMP_LOG(LOG_ERR, "Invalid backup type: %s.", request->name);
		NDMP_LOG(LOG_ERR,
		    "Supported backup types are 'dump' and 'tar' only.");
		reply.error = NDMP_ILLEGAL_ARGS_ERR;
	}

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_butype_attr reply");
}


/*
 * ndmpd_config_get_mover_type_v2
 *
 * This handler handles the ndmp_config_get_mover_type request.
 * Information about the supported mover types is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_mover_type_v2(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_mover_type_reply reply;
	ndmp_addr_type types[2];

	types[0] = NDMP_ADDR_LOCAL;
	types[1] = NDMP_ADDR_TCP;

	reply.error = NDMP_NO_ERR;
	reply.methods.methods_len = 2;
	reply.methods.methods_val = types;

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_mover_type reply");
}



/*
 * ndmpd_config_get_auth_attr_v2
 *
 * This handler handles the ndmp_config_get_auth_attr request.
 * Authorization type specific information is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
void
ndmpd_config_get_auth_attr_v2(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_auth_attr_request *request;
	ndmp_config_get_auth_attr_reply reply;
	ndmpd_session_t *session = ndmp_get_client_data(connection);

	request = (ndmp_config_get_auth_attr_request *)body;

	reply.error = NDMP_NO_ERR;
	reply.server_attr.auth_type = request->auth_type;

	switch (request->auth_type) {
	case NDMP_AUTH_TEXT:
		break;
	case NDMP_AUTH_MD5:
		/* Create a 64 byte random session challenge */
		randomize(session->ns_challenge, MD5_CHALLENGE_SIZE);
		(void) memcpy(reply.server_attr.ndmp_auth_attr_u.challenge,
		    session->ns_challenge, MD5_CHALLENGE_SIZE);
		break;
	case NDMP_AUTH_NONE:
		/* FALL THROUGH */
	default:
		NDMP_LOG(LOG_ERR, "Invalid authentication type: %d.",
		    request->auth_type);
		NDMP_LOG(LOG_ERR,
		    "Supported authentication types are md5 and cleartext.");
		reply.error = NDMP_ILLEGAL_ARGS_ERR;
		break;
	}

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_auth_attr reply");
}


/*
 * ************************************************************************
 * NDMP V3 HANDLERS
 * ************************************************************************
 */

/*
 * ndmpd_config_get_host_info_v3
 *
 * This handler handles the ndmp_config_get_host_info request.
 * Host specific information is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_host_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_host_info_reply_v3 reply;
	char buf[HOSTNAMELEN+1];
	struct utsname uts;
	char hostidstr[16];
	ulong_t hostid;

	(void) memset((void*)&reply, 0, sizeof (reply));
	(void) memset(buf, 0, sizeof (buf));
	(void) gethostname(buf, sizeof (buf));


	reply.error = NDMP_NO_ERR;
	reply.hostname = buf;
	(void) uname(&uts);
	reply.os_type = uts.sysname;
	reply.os_vers = uts.release;

	if (sysinfo(SI_HW_SERIAL, hostidstr, sizeof (hostidstr)) < 0) {

		NDMP_LOG(LOG_DEBUG, "sysinfo error: %m.");
		reply.error = NDMP_UNDEFINED_ERR;
	}

	/*
	 * Convert the hostid to hex. The returned string must match
	 * the string returned by hostid(1).
	 */
	hostid = strtoul(hostidstr, 0, 0);
	(void) snprintf(hostidstr, sizeof (hostidstr), "%lx", hostid);
	reply.hostid = hostidstr;

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_host_info reply");
}


/*
 * ndmpd_config_get_connection_type_v3
 *
 * This handler handles the ndmp_config_get_connection_type_request.
 * A list of supported data connection types is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_connection_type_v3(ndmp_connection_t *connection,
    void *body)
{
	ndmp_config_get_connection_type_reply_v3 reply;
	ndmp_addr_type addr_types[2];

	(void) memset((void*)&reply, 0, sizeof (reply));

	reply.error = NDMP_NO_ERR;

	addr_types[0] = NDMP_ADDR_LOCAL;
	addr_types[1] = NDMP_ADDR_TCP;
	reply.addr_types.addr_types_len = 2;
	reply.addr_types.addr_types_val = addr_types;

	ndmp_send_reply(connection, (void *) &reply,
	    "sending config_get_connection_type_v3 reply");
}



/*
 * ndmpd_config_get_auth_attr_v3
 *
 * This handler handles the ndmp_config_get_auth_attr request.
 * Authorization type specific information is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
void
ndmpd_config_get_auth_attr_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_auth_attr_request *request;
	ndmp_config_get_auth_attr_reply reply;
	ndmpd_session_t *session = ndmp_get_client_data(connection);

	request = (ndmp_config_get_auth_attr_request *)body;

	(void) memset((void*)&reply, 0, sizeof (reply));
	reply.error = NDMP_NO_ERR;
	reply.server_attr.auth_type = request->auth_type;

	switch (request->auth_type) {
	case NDMP_AUTH_TEXT:
		break;
	case NDMP_AUTH_MD5:
		/* Create a 64 bytes random session challenge */
		randomize(session->ns_challenge, MD5_CHALLENGE_SIZE);
		(void) memcpy(reply.server_attr.ndmp_auth_attr_u.challenge,
		    session->ns_challenge, MD5_CHALLENGE_SIZE);
		break;
	case NDMP_AUTH_NONE:
		/* FALL THROUGH */
	default:
		NDMP_LOG(LOG_ERR, "Invalid authentication type: %d.",
		    request->auth_type);
		NDMP_LOG(LOG_ERR,
		    "Supported authentication types are md5 and cleartext.");
		reply.error = NDMP_ILLEGAL_ARGS_ERR;
		break;
	}

	ndmp_send_reply(connection, (void *) &reply,
	    "sending ndmp_config_get_auth_attr_v3 reply");
}


/*
 * ndmpd_config_get_butype_info_v3
 *
 * This handler handles the ndmp_config_get_butype_info_request.
 * Information about all supported backup types are returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_butype_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_butype_info_reply_v3 reply;
	ndmp_butype_info info[3];
	ndmp_pval envs[8];
	ulong_t attrs;
	ndmp_pval *envp = envs;

	ndmp_pval zfs_envs[9];
	ulong_t zfs_attrs;
	ndmp_pval *zfs_envp = zfs_envs;

	(void) memset((void*)&reply, 0, sizeof (reply));

	/*
	 * Supported environment variables and their default values
	 * for dump and tar.
	 *
	 * The environment variables for dump and tar format are the
	 * same, because we use the same backup engine for both.
	 */
	NDMP_SETENV(envp, "PREFIX", "");
	NDMP_SETENV(envp, "TYPE", "");
	NDMP_SETENV(envp, "DIRECT", "n");
	NDMP_SETENV(envp, "HIST", "n");
	NDMP_SETENV(envp, "FILESYSTEM", "");
	NDMP_SETENV(envp, "LEVEL", "0");
	NDMP_SETENV(envp, "UPDATE", "TRUE");
	NDMP_SETENV(envp, "BASE_DATE", "");

	attrs = NDMP_BUTYPE_BACKUP_FILE_HISTORY |
	    NDMP_BUTYPE_RECOVER_FILELIST |
	    NDMP_BUTYPE_BACKUP_DIRECT |
	    NDMP_BUTYPE_BACKUP_INCREMENTAL |
	    NDMP_BUTYPE_BACKUP_UTF8 |
	    NDMP_BUTYPE_RECOVER_UTF8;

	/* If DAR supported */
	if (ndmp_dar_support)
		attrs |= NDMP_BUTYPE_RECOVER_DIRECT;

	/* tar backup type */
	info[0].butype_name = "tar";
	info[0].default_env.default_env_len = ARRAY_LEN(envs, ndmp_pval);
	info[0].default_env.default_env_val = envs;
	info[0].attrs = attrs;

	/* dump backup type */
	info[1].butype_name = "dump";
	info[1].default_env.default_env_len = ARRAY_LEN(envs, ndmp_pval);
	info[1].default_env.default_env_val = envs;
	info[1].attrs = attrs;

	/*
	 * Supported environment variables and their default values
	 * for type "zfs."
	 */

	NDMP_SETENV(zfs_envp, "PREFIX", "");
	NDMP_SETENV(zfs_envp, "FILESYSTEM", "");
	NDMP_SETENV(zfs_envp, "TYPE", "zfs");
	NDMP_SETENV(zfs_envp, "HIST", "n");
	NDMP_SETENV(zfs_envp, "LEVEL", "0");
	NDMP_SETENV(zfs_envp, "ZFS_MODE", "recursive");
	NDMP_SETENV(zfs_envp, "ZFS_FORCE", "FALSE");
	NDMP_SETENV(zfs_envp, "UPDATE", "TRUE");
	NDMP_SETENV(zfs_envp, "DMP_NAME", "level");

	zfs_attrs = NDMP_BUTYPE_BACKUP_UTF8 |
	    NDMP_BUTYPE_RECOVER_UTF8 |
	    NDMP_BUTYPE_BACKUP_DIRECT |
	    NDMP_BUTYPE_BACKUP_INCREMENTAL;

	/* zfs backup type */
	info[2].butype_name = "zfs";
	info[2].default_env.default_env_len = ARRAY_LEN(zfs_envs, ndmp_pval);
	info[2].default_env.default_env_val = zfs_envs;
	info[2].attrs = zfs_attrs;

	reply.error = NDMP_NO_ERR;
	reply.butype_info.butype_info_len = ARRAY_LEN(info, ndmp_butype_info);
	reply.butype_info.butype_info_val = info;

	ndmp_send_reply(connection, (void *)&reply,
	    "sending ndmp_config_get_butype_info reply");
}

/*
 * ndmpd_config_get_fs_info_v3
 *
 * This handler handles the ndmp_config_get_fs_info_request.
 * Information about all mounted file systems is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_fs_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_fs_info_reply_v3 reply;
	ndmp_fs_info_v3 *fsip = NULL, *fsip_save = NULL; /* FS info pointer */
	int len = 0, nmnt, fd;
	int log_dev_len;
	FILE *fp = NULL;
	struct mnttab mt, *fs;
	struct statvfs64 stat_buf;
	ndmp_pval *envp, *save;

	(void) memset((void*)&reply, 0, sizeof (reply));
	reply.error = NDMP_NO_ERR;

	if ((fd = open(MNTTAB, O_RDONLY)) == -1) {
		NDMP_LOG(LOG_ERR, "File mnttab open error: %m.");
		reply.error = NDMP_UNDEFINED_ERR;
		goto send_reply;
	}

	/* nothing was found, send an empty reply */
	if (ioctl(fd, MNTIOC_NMNTS, &nmnt) != 0 || nmnt <= 0) {
		(void) close(fd);
		NDMP_LOG(LOG_ERR, "No file system found.");
		goto send_reply;
	}

	fp = fdopen(fd, "r");
	if (!fp) {
		(void) close(fd);
		NDMP_LOG(LOG_ERR, "File mnttab open error: %m.");
		reply.error = NDMP_UNDEFINED_ERR;
		goto send_reply;
	}

	fsip_save = fsip = ndmp_malloc(sizeof (ndmp_fs_info_v3) * nmnt);
	if (!fsip) {
		(void) fclose(fp);
		reply.error = NDMP_NO_MEM_ERR;
		goto send_reply;
	}

	/*
	 * Re-read the directory and set up file system information.
	 */
	rewind(fp);
	while (len < nmnt && (getmntent(fp, &mt) == 0))

	{
		fs = &mt;
		log_dev_len = strlen(mt.mnt_mountp)+2;
		if (!IS_VALID_FS(fs))
			continue;

		fsip->fs_logical_device = ndmp_malloc(log_dev_len);
		fsip->fs_type = ndmp_malloc(MNTTYPE_LEN);
		if (!fsip->fs_logical_device || !fsip->fs_type) {
			free(fsip->fs_logical_device);
			free(fsip->fs_type);
			reply.error = NDMP_NO_MEM_ERR;
			break;
		}
		(void) snprintf(fsip->fs_type, MNTTYPE_LEN, "%s",
		    fs->mnt_fstype);
		(void) snprintf(fsip->fs_logical_device, log_dev_len, "%s",
		    fs->mnt_mountp);
		fsip->invalid = 0;

		if (statvfs64(fs->mnt_mountp, &stat_buf) < 0) {
			NDMP_LOG(LOG_DEBUG,
			    "statvfs(%s) error.", fs->mnt_mountp);
			fsip->fs_status =
			    "statvfs error: unable to determine filesystem"
			    " attributes";
		} else {
			fsip->invalid = 0;
			fsip->total_size =
			    long_long_to_quad((u_longlong_t)stat_buf.f_frsize *
			    (u_longlong_t)stat_buf.f_blocks);
			fsip->used_size =
			    long_long_to_quad((u_longlong_t)stat_buf.f_frsize *
			    (u_longlong_t)(stat_buf.f_blocks-stat_buf.f_bfree));

			fsip->avail_size =
			    long_long_to_quad((u_longlong_t)stat_buf.f_frsize *
			    (u_longlong_t)stat_buf.f_bfree);
			fsip->total_inodes =
			    long_long_to_quad((u_longlong_t)stat_buf.f_files);
			fsip->used_inodes =
			    long_long_to_quad((u_longlong_t)(stat_buf.f_files -
			    stat_buf.f_ffree));
			fsip->fs_status = "";
		}
		save = envp = ndmp_malloc(sizeof (ndmp_pval) * V3_N_FS_ENVS);
		if (!envp) {
			free(fsip->fs_logical_device);
			free(fsip->fs_type);
			reply.error = NDMP_NO_MEM_ERR;
			break;
		}
		(void) memset((void*)save, 0,
		    V3_N_FS_ENVS * sizeof (ndmp_pval));

		fsip->fs_env.fs_env_val = envp;
		NDMP_SETENV(envp, "LOCAL", "y");
		NDMP_SETENV(envp, "TYPE", fsip->fs_type);
		NDMP_SETENV(envp, "AVAILABLE_BACKUP", "tar,dump");

		if (FS_READONLY(fs) == 0) {
			NDMP_SETENV(envp, "AVAILABLE_RECOVERY", "tar,dump");
		}

		fsip->fs_env.fs_env_len = envp - save;
		len++;
		fsip++;
	}
	(void) fclose(fp);

send_reply:
	if (reply.error == NDMP_NO_ERR) {
		reply.fs_info.fs_info_len = len;
		reply.fs_info.fs_info_val = fsip_save;
	}
	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_get_fs_info reply");

	while (fsip > fsip_save) {
		fsip--;
		free(fsip->fs_logical_device);
		free(fsip->fs_env.fs_env_val);
		free(fsip->fs_type);
	}

	free(fsip);
}


/*
 * ndmpd_config_get_tape_info_v3
 *
 * This handler handles the ndmp_config_get_tape_info_request.
 * Information about all connected tape drives is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_tape_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_tape_info_reply_v3 reply;
	ndmp_device_info_v3 *tip, *tip_save = NULL; /* tape info pointer */
	ndmp_device_capability_v3 *dcp;
	ndmp_device_capability_v3 *dcp_save = NULL; /* dev capability pointer */
	int i, n, max;
	sasd_drive_t *sd;
	scsi_link_t *sl;
	ndmp_pval *envp, *envp_save = NULL;
	ndmp_pval *envp_head;

	(void) memset((void*)&reply, 0, sizeof (reply));
	max = sasd_dev_count();

	tip_save = tip = ndmp_malloc(sizeof (ndmp_device_info_v3) * max);
	dcp_save = dcp = ndmp_malloc(sizeof (ndmp_device_capability_v3) * max);
	envp_save = envp = ndmp_malloc(sizeof (ndmp_pval) * max * 3);
	if (!tip_save || !dcp_save || !envp_save) {
		free(tip_save);
		free(dcp_save);
		free(envp_save);
		reply.error = NDMP_NO_MEM_ERR;
		ndmp_send_reply(connection, (void *)&reply,
		    "error sending ndmp_config_get_tape_info reply");
		return;
	}

	reply.error = NDMP_NO_ERR;

	for (i = n = 0; i < max; i++) {
		if (!(sl = sasd_dev_slink(i)) || !(sd = sasd_drive(i)))
			continue;
		if (sl->sl_type != DTYPE_SEQUENTIAL)
			continue;
		/*
		 * Don't report dead links.
		 */
		if ((access(sd->sd_name, F_OK) == -1) && (errno == ENOENT))
			continue;

		NDMP_LOG(LOG_DEBUG,
		    "model \"%s\" dev \"%s\"", sd->sd_id, sd->sd_name);

		envp_head = envp;
		NDMP_SETENV(envp, "EXECUTE_CDB", "b");
		NDMP_SETENV(envp, "SERIAL_NUMBER", sd->sd_serial);
		NDMP_SETENV(envp, "WORLD_WIDE_NAME", sd->sd_wwn);

		tip->model = sd->sd_id; /* like "DLT7000	 " */
		tip->caplist.caplist_len = 1;
		tip->caplist.caplist_val = dcp;
		dcp->device = sd->sd_name; /* like "isp1t060" */
		dcp->attr = 0;
		dcp->capability.capability_len = 3;
		dcp->capability.capability_val = envp_head;
		tip++;
		dcp++;
		n++;
	}

	NDMP_LOG(LOG_DEBUG, "n %d", n);

	/*
	 * We should not receive the get_tape_info when three-way backup is
	 * running and we are acting as just data, but some clients try
	 * to get the Tape information anyway.
	 */
	if (n == 0 || max <= 0) {
		reply.error = NDMP_NO_DEVICE_ERR;
		ndmp_send_reply(connection, (void *)&reply,
		    "error sending ndmp_config_get_tape_info reply");
		free(tip_save); free(dcp_save); free(envp_save);
		return;
	}


	reply.tape_info.tape_info_len = n;
	reply.tape_info.tape_info_val = tip_save;

	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_get_tape_info reply");

	free(tip_save);
	free(dcp_save);
	free(envp_save);
}


/*
 * ndmpd_config_get_scsi_info_v3
 *
 * This handler handles the ndmp_config_get_tape_scsi_request.
 * Information about all connected scsi tape stacker and jukeboxes
 * is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_scsi_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_scsi_info_reply_v3 reply;
	ndmp_device_info_v3 *sip, *sip_save;
	ndmp_device_capability_v3 *dcp, *dcp_save;
	int i, n, max;
	sasd_drive_t *sd;
	scsi_link_t *sl;
	ndmp_pval *envp, *envp_save = NULL;
	ndmp_pval *envp_head;

	(void) memset((void*)&reply, 0, sizeof (reply));
	max = sasd_dev_count();
	sip_save = sip = ndmp_malloc(sizeof (ndmp_device_info_v3) * max);
	dcp_save = dcp = ndmp_malloc(sizeof (ndmp_device_capability_v3) * max);
	envp_save = envp = ndmp_malloc(sizeof (ndmp_pval) * max * 2);
	if (!sip_save || !dcp_save || !envp_save) {
		free(sip_save);
		free(dcp_save);
		free(envp_save);
		reply.error = NDMP_NO_MEM_ERR;
		ndmp_send_reply(connection, (void *)&reply,
		    "error sending ndmp_config_get_scsi_info reply");
		return;
	}

	reply.error = NDMP_NO_ERR;
	for (i = n = 0; i < max; i++) {
		if (!(sl = sasd_dev_slink(i)) || !(sd = sasd_drive(i)))
			continue;
		if (sl->sl_type != DTYPE_CHANGER)
			continue;
		/*
		 * Don't report dead links.
		 */
		if ((access(sd->sd_name, F_OK) == -1) && (errno == ENOENT))
			continue;

		NDMP_LOG(LOG_DEBUG,
		    "model \"%s\" dev \"%s\"", sd->sd_id, sd->sd_name);

		envp_head = envp;
		NDMP_SETENV(envp, "SERIAL_NUMBER", sd->sd_serial);
		NDMP_SETENV(envp, "WORLD_WIDE_NAME", sd->sd_wwn);

		sip->model = sd->sd_id; /* like "Powerstor L200  " */
		sip->caplist.caplist_len = 1;
		sip->caplist.caplist_val = dcp;
		dcp->device = sd->sd_name; /* like "isp1m000" */

		dcp->attr = 0;
		dcp->capability.capability_len = 2;
		dcp->capability.capability_val = envp_head;
		sip++;
		dcp++;
		n++;
	}

	NDMP_LOG(LOG_DEBUG, "n %d", n);

	reply.scsi_info.scsi_info_len = n;
	reply.scsi_info.scsi_info_val = sip_save;

	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_get_scsi_info reply");

	free(sip_save);
	free(dcp_save);
	free(envp_save);
}


/*
 * ndmpd_config_get_server_info_v3
 *
 * This handler handles the ndmp_config_get_server_info request.
 * Host specific information is returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_server_info_v3(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_server_info_reply_v3 reply;
	ndmp_auth_type auth_types[2];
	char rev_number[10];
	ndmpd_session_t *session = ndmp_get_client_data(connection);

	(void) memset((void*)&reply, 0, sizeof (reply));
	reply.error = NDMP_NO_ERR;

	if (connection->conn_authorized ||
	    session->ns_protocol_version != NDMPV4) {
		reply.vendor_name = VENDOR_NAME;
		reply.product_name = PRODUCT_NAME;
		(void) snprintf(rev_number, sizeof (rev_number), "%d",
		    ndmp_ver);
		reply.revision_number = rev_number;
	} else {
		reply.vendor_name = "\0";
		reply.product_name = "\0";
		reply.revision_number = "\0";
	}

	NDMP_LOG(LOG_DEBUG,
	    "vendor \"%s\", product \"%s\" rev \"%s\"",
	    reply.vendor_name, reply.product_name, reply.revision_number);

	auth_types[0] = NDMP_AUTH_TEXT;
	auth_types[1] = NDMP_AUTH_MD5;
	reply.auth_type.auth_type_len = ARRAY_LEN(auth_types, ndmp_auth_type);
	reply.auth_type.auth_type_val = auth_types;

	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_get_server_info reply");
}



/*
 * ************************************************************************
 * NDMP V4 HANDLERS
 * ************************************************************************
 */

/*
 * ndmpd_config_get_butype_info_v4
 *
 * This handler handles the ndmp_config_get_butype_info_request.
 * Information about all supported backup types are returned.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_butype_info_v4(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_butype_info_reply_v4 reply;
	ndmp_butype_info info[3];

	ndmp_pval envs[12];
	ulong_t attrs;
	ndmp_pval *envp = envs;

	ndmp_pval zfs_envs[11];
	ulong_t zfs_attrs;
	ndmp_pval *zfs_envp = zfs_envs;


	(void) memset((void*)&reply, 0, sizeof (reply));

	/*
	 * Supported environment variables and their default values
	 * for dump and tar.
	 *
	 * The environment variables for dump and tar format are the
	 * same, because we use the same backup engine for both.
	 */
	NDMP_SETENV(envp, "FILESYSTEM", "");
	NDMP_SETENV(envp, "DIRECT", "n");
	NDMP_SETENV(envp, "RECURSIVE", "n");
	NDMP_SETENV(envp, "TYPE", "");
	NDMP_SETENV(envp, "USER", "");
	NDMP_SETENV(envp, "HIST", "n");
	NDMP_SETENV(envp, "PATHNAME_SEPARATOR", "/");
	NDMP_SETENV(envp, "LEVEL", "0");
	NDMP_SETENV(envp, "EXTRACT", "y");
	NDMP_SETENV(envp, "UPDATE", "y");
	NDMP_SETENV(envp, "CMD", "");
	NDMP_SETENV(envp, "BASE_DATE", "");

	attrs = NDMP_BUTYPE_RECOVER_FILELIST |
	    NDMP_BUTYPE_BACKUP_DIRECT |
	    NDMP_BUTYPE_BACKUP_INCREMENTAL |
	    NDMP_BUTYPE_BACKUP_UTF8 |
	    NDMP_BUTYPE_RECOVER_UTF8 |
	    NDMP_BUTYPE_BACKUP_FH_FILE |
	    NDMP_BUTYPE_BACKUP_FH_DIR |
	    NDMP_BUTYPE_RECOVER_FH_FILE |
	    NDMP_BUTYPE_RECOVER_FH_DIR;

	/* If DAR supported */
	if (ndmp_dar_support)
		attrs |= NDMP_BUTYPE_RECOVER_DIRECT;

	/* tar backup type */
	info[0].butype_name = "tar";
	info[0].default_env.default_env_len = ARRAY_LEN(envs, ndmp_pval);
	info[0].default_env.default_env_val = envs;
	info[0].attrs = attrs;

	/* dump backup type */
	info[1].butype_name = "dump";
	info[1].default_env.default_env_len = ARRAY_LEN(envs, ndmp_pval);
	info[1].default_env.default_env_val = envs;
	info[1].attrs = attrs;

	/*
	 * Supported environment variables and their default values
	 * for type "zfs."
	 */

	NDMP_SETENV(zfs_envp, "USER", "");
	NDMP_SETENV(zfs_envp, "CMD", "");
	NDMP_SETENV(zfs_envp, "FILESYSTEM", "");
	NDMP_SETENV(zfs_envp, "PATHNAME_SEPARATOR", "/");
	NDMP_SETENV(zfs_envp, "TYPE", "zfs");
	NDMP_SETENV(zfs_envp, "HIST", "n");
	NDMP_SETENV(zfs_envp, "LEVEL", "0");
	NDMP_SETENV(zfs_envp, "ZFS_MODE", "recursive");
	NDMP_SETENV(zfs_envp, "ZFS_FORCE", "n");
	NDMP_SETENV(zfs_envp, "UPDATE", "y");
	NDMP_SETENV(zfs_envp, "DMP_NAME", "level");

	zfs_attrs = NDMP_BUTYPE_BACKUP_UTF8 |
	    NDMP_BUTYPE_RECOVER_UTF8 |
	    NDMP_BUTYPE_BACKUP_DIRECT |
	    NDMP_BUTYPE_BACKUP_INCREMENTAL;

	/* zfs backup type */
	info[2].butype_name = "zfs";
	info[2].default_env.default_env_len = ARRAY_LEN(zfs_envs, ndmp_pval);
	info[2].default_env.default_env_val = zfs_envs;
	info[2].attrs = zfs_attrs;

	reply.error = NDMP_NO_ERR;
	reply.butype_info.butype_info_len = ARRAY_LEN(info, ndmp_butype_info);
	reply.butype_info.butype_info_val = info;

	ndmp_send_reply(connection, (void *)&reply,
	    "sending ndmp_config_get_butype_info reply");
}


/*
 * ndmpd_config_get_ext_list_v4
 *
 * This handler handles the ndmpd_config_get_ext_list_v4 request.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
/*ARGSUSED*/
void
ndmpd_config_get_ext_list_v4(ndmp_connection_t *connection, void *body)
{
	ndmp_config_get_ext_list_reply_v4 reply;
	ndmpd_session_t *session = ndmp_get_client_data(connection);

	(void) memset((void*)&reply, 0, sizeof (reply));

	if (session->ns_set_ext_list) {
		/*
		 * Illegal request if extensions have already been selected.
		 */
		NDMP_LOG(LOG_ERR, "Extensions have already been selected.");
		reply.error = NDMP_EXT_DANDN_ILLEGAL_ERR;
	} else {
		/*
		 * Reply with an empty set of extensions.
		 */
		session->ns_get_ext_list = B_TRUE;
		reply.error = NDMP_NO_ERR;
	}

	reply.class_list.class_list_val = NULL;
	reply.class_list.class_list_len = 0;

	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_get_ext_list reply");
}

/*
 * ndmpd_config_set_ext_list_v4
 *
 * This handler handles the ndmpd_config_get_ext_list_v4 request.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *   body       (input) - request message body.
 *
 * Returns:
 *   void
 */
void
ndmpd_config_set_ext_list_v4(ndmp_connection_t *connection, void *body)
{
	ndmp_config_set_ext_list_reply_v4 reply;
	ndmp_config_set_ext_list_request_v4 *request;
	ndmpd_session_t *session = ndmp_get_client_data(connection);

	request = (ndmp_config_set_ext_list_request_v4 *)body;

	(void) memset((void*)&reply, 0, sizeof (reply));

	if (!session->ns_get_ext_list) {
		/*
		 * The DMA is required to issue a NDMP_GET_EXT_LIST request
		 * prior sending a NDMP_SET_EXT_LIST request.
		 */
		NDMP_LOG(LOG_ERR, "No prior ndmp_config_get_ext_list issued.");
		reply.error = NDMP_PRECONDITION_ERR;
	} else if (session->ns_set_ext_list) {
		/*
		 * Illegal request if extensions have already been selected.
		 */
		NDMP_LOG(LOG_ERR, "Extensions have already been selected.");
		reply.error = NDMP_EXT_DANDN_ILLEGAL_ERR;
	} else {
		/*
		 * We currently do not support any extensions, but the DMA
		 * may test NDMP_CONFIG_SET_EXT_LIST with an empty list.
		 */
		if (request->ndmp_selected_ext.ndmp_selected_ext_len != 0) {
			reply.error = NDMP_CLASS_NOT_SUPPORTED_ERR;
		} else {
			session->ns_set_ext_list = B_TRUE;
			reply.error = NDMP_NO_ERR;
		}
	}

	ndmp_send_reply(connection, (void *)&reply,
	    "error sending ndmp_config_set_ext_list reply");
}



/*
 * ************************************************************************
 * LOCALS
 * ************************************************************************
 */

/*
 * simple_get_attrs
 *
 * Set the default attrs for dump mode
 *
 * Parameters:
 *   attributes (output) - the attributes for dump mode
 *
 * Returns:
 *   void
 */
static void
simple_get_attrs(ulong_t *attributes)
{
	*attributes = NDMP_NO_RECOVER_FHINFO;
}