root/usr/src/cmd/ndmpd/ndmp/ndmpd_comm.c
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2014 Nexenta Systems, Inc.  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 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libinetutil.h>
#include "ndmpd.h"
#include "ndmpd_common.h"

#define NDMP_PROC_ERR   -1
#define NDMP_PROC_MSG   1
#define NDMP_PROC_REP   0
#define NDMP_PROC_REP_ERR       2

/*
 * The ndmp connection version can be set through command line. If command line
 * is not specified it will be set from the ndmp SMF version property.
 */
int ndmp_ver = 0;

/*
 * The NDMP listening port number
 */
int ndmp_port = 0;

/*
 * Restore path mechanism definition
 * 0 means partial path restore and
 * 1 means full path restore.
 * Refer to NDMP_FULL_RESTORE_PATH for partial path and full path definition.
 */
int ndmp_full_restore_path = 1;

/*
 * Do we support Direct Access Restore?
 */
int ndmp_dar_support = 0;

/*
 * ndmp_connection_t handler function
 */
static ndmpd_file_handler_func_t connection_file_handler;

extern ndmp_handler_t ndmp_msghdl_tab[];

static int ndmp_readit(void *connection_handle,
    caddr_t buf,
    int len);
static int ndmp_writeit(void *connection_handle,
    caddr_t buf,
    int len);
static int ndmp_recv_msg(ndmp_connection_t *connection);
static int ndmp_process_messages(ndmp_connection_t *connection,
    boolean_t reply_expected);
static ndmp_msg_handler_t *ndmp_get_handler(ndmp_connection_t *connection,
    ndmp_message message);
static boolean_t ndmp_check_auth_required(ndmp_message message);
static ndmp_handler_t *ndmp_get_interface(ndmp_message message);
void *ndmpd_worker(void *ptarg);

#ifdef  lint
bool_t
xdr_ndmp_header(XDR *xdrs, ndmp_header *objp)
{
        xdrs = xdrs;
        objp = objp;
        return (0);
}
#endif  /* lint */

/*
 * ndmp_create_connection
 *
 * Allocate and initialize a connection structure.
 *
 * Parameters:
 *   handler_tbl (input) - message handlers.
 *
 * Returns:
 *   NULL - error
 *   connection pointer
 *
 * Notes:
 *   The returned connection should be destroyed using
 *   ndmp_destroy_connection().
 */
ndmp_connection_t *
ndmp_create_connection(void)
{
        ndmp_connection_t *connection;

        connection = ndmp_malloc(sizeof (ndmp_connection_t));
        if (connection == NULL)
                return (NULL);

        connection->conn_sock = -1;
        connection->conn_my_sequence = 0;
        connection->conn_authorized = FALSE;
        connection->conn_eof = FALSE;
        connection->conn_msginfo.mi_body = 0;
        connection->conn_version = ndmp_ver;
        connection->conn_client_data = 0;
        (void) mutex_init(&connection->conn_lock, 0, NULL);
        connection->conn_xdrs.x_ops = 0;

        xdrrec_create(&connection->conn_xdrs, 0, 0, (caddr_t)connection,
            ndmp_readit, ndmp_writeit);

        if (connection->conn_xdrs.x_ops == 0) {
                NDMP_LOG(LOG_DEBUG, "xdrrec_create failed");
                (void) mutex_destroy(&connection->conn_lock);
                (void) close(connection->conn_sock);
                free(connection);
                return (0);
        }
        return ((ndmp_connection_t *)connection);
}

/*
 * ndmp_destroy_connection
 *
 * Shutdown a connection and release allocated resources.
 *
 * Parameters:
 *   connection_handle (Input) - connection handle.
 *
 * Returns:
 *   void
 */
void
ndmp_destroy_connection(ndmp_connection_t *connection_handle)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        if (connection->conn_sock >= 0) {
                (void) mutex_destroy(&connection->conn_lock);
                (void) close(connection->conn_sock);
                connection->conn_sock = -1;
        }
        xdr_destroy(&connection->conn_xdrs);
        free(connection);
}


/*
 * ndmp_close
 *
 * Close a connection.
 *
 * Parameters:
 *   connection_handle (Input) - connection handle.
 *
 * Returns:
 *   void
 */
void
ndmp_close(ndmp_connection_t *connection_handle)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        ndmpd_audit_disconnect(connection);
        if (connection->conn_sock >= 0) {
                (void) mutex_destroy(&connection->conn_lock);
                (void) close(connection->conn_sock);
                connection->conn_sock = -1;
        }
        connection->conn_eof = TRUE;

        /*
         * We should close all the tapes that are used by this connection.
         * In some cases the ndmp client opens a tape, but does not close the
         * tape and closes the connection.
         */
        ndmp_open_list_release(connection_handle);
}

/*
 * ndmp_start_worker
 *
 * Initializes and starts a ndmp_worker thread
 */
int
ndmp_start_worker(ndmpd_worker_arg_t *argp)
{
        pthread_attr_t tattr;
        int rc;

        (void) pthread_attr_init(&tattr);
        (void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
        rc = pthread_create(NULL, &tattr, ndmpd_worker, (void *)argp);
        (void) pthread_attr_destroy(&tattr);
        return (rc);
}

/*
 * ndmp_run
 *
 * Creates a socket for listening and accepting connections
 * from NDMP clients.
 * Accepts connections and passes each connection to the connection
 * handler.
 *
 * Parameters:
 *   port (input)   -  NDMP server port.
 *                   If 0, the port number will be retrieved from
 *                   the network service database. If not found there,
 *                   the default NDMP port number (from ndmp.x)
 *                   will be used.
 *   handler (input) - connection handler function.
 *
 * Returns:
 *   This function normally never returns unless there's error.
 *   -1 : error
 *
 * Notes:
 *   This function does not return unless encountering an error
 *   related to the listen socket.
 */
int
ndmp_run(ulong_t port, ndmp_con_handler_func_t con_handler_func)
{
        int ns;
        int on;
        int server_socket;
        unsigned int ipaddr;
        struct sockaddr_in sin;
        ndmpd_worker_arg_t *argp;

        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;
        sin.sin_port = htons(port);

        if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                NDMP_LOG(LOG_DEBUG, "Socket error: %m");
                return (-1);
        }

        on = 1;
        (void) setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,
            (char *)&on, sizeof (on));


        if (bind(server_socket, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
                NDMP_LOG(LOG_DEBUG, "bind error: %m");
                (void) close(server_socket);
                return (-1);
        }
        if (listen(server_socket, 5) < 0) {
                NDMP_LOG(LOG_DEBUG, "listen error: %m");
                (void) close(server_socket);
                return (-1);
        }

        for (; ; ) {
                if ((ns = tcp_accept(server_socket, &ipaddr)) < 0) {
                        NDMP_LOG(LOG_DEBUG, "tcp_accept error: %m");
                        continue;
                }
                NDMP_LOG(LOG_DEBUG, "connection fd: %d", ns);
                set_socket_options(ns);

                if ((argp = ndmp_malloc(sizeof (ndmpd_worker_arg_t))) != NULL) {
                        argp->nw_sock = ns;
                        argp->nw_ipaddr = ipaddr;
                        argp->nw_con_handler_func = con_handler_func;
                        (void) ndmp_start_worker(argp);
                }
        }
}

/*
 * ndmpd_worker thread
 *
 * Parameters:
 *   argp (input) - structure containing socket and handler function
 *
 * Returns:
 *   0 - successful connection.
 *  -1 - error.
 */
void *
ndmpd_worker(void *ptarg)
{
        int sock;
        ndmp_connection_t *connection;
        ndmpd_worker_arg_t *argp = (ndmpd_worker_arg_t *)ptarg;

        if (!argp)
                return ((void *)-1);

        NS_INC(trun);
        sock = argp->nw_sock;

        if ((connection = ndmp_create_connection()) == NULL) {
                (void) close(sock);
                free(argp);
                exit(1);
        }

        /* initialize auditing session */
        if (adt_start_session(&connection->conn_ah, NULL, 0) != 0) {
                free(argp);
                return ((void *)-1);
        }

        ((ndmp_connection_t *)connection)->conn_sock = sock;
        (*argp->nw_con_handler_func)(connection);
        (void) adt_end_session(connection->conn_ah);
        ndmp_destroy_connection(connection);
        NS_DEC(trun);

        free(argp);
        return (NULL);
}

/*
 * ndmp_process_requests
 *
 * Reads the next request message into the stream buffer.
 * Processes messages until the stream buffer is empty.
 *
 * Parameters:
 *   connection_handle (input) - connection handle.
 *
 * Returns:
 *   0 - 1 or more messages successfully processed.
 *  -1 - error; connection no longer established.
 */
int
ndmp_process_requests(ndmp_connection_t *connection_handle)
{
        int rv;
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        (void) mutex_lock(&connection->conn_lock);
        rv = 0;
        if (ndmp_process_messages(connection, FALSE) < 0)
                rv = -1;

        (void) mutex_unlock(&connection->conn_lock);
        return (rv);
}


/*
 * ndmp_send_request
 *
 * Send an NDMP request message.
 *
 * Parameters:
 *   connection_handle (input) - connection pointer.
 *   message (input) - message number.
 *   err (input)  - error code to place in header.
 *   request_data (input) - message body.
 *   reply (output) - reply message. If 0, reply will be
 *                              discarded.
 *
 * Returns:
 *   0  - successful send.
 *  -1  - error.
 *   otherwise - error from reply header.
 *
 * Notes:
 *   - The reply body is only returned if the error code is NDMP_NO_ERR.
 */
int
ndmp_send_request(ndmp_connection_t *connection_handle, ndmp_message message,
    ndmp_error err, void *request_data, void **reply)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;
        ndmp_header header;
        ndmp_msg_handler_t *handler;
        int r;
        struct timeval time;

        /* Lookup info necessary for processing this request. */
        if (!(handler = ndmp_get_handler(connection, message))) {
                NDMP_LOG(LOG_DEBUG, "Sending message 0x%x: not supported",
                    message);
                return (-1);
        }
        (void) gettimeofday(&time, 0);

        header.sequence = ++(connection->conn_my_sequence);
        header.time_stamp = time.tv_sec;
        header.message_type = NDMP_MESSAGE_REQUEST;
        header.message = message;
        header.reply_sequence = 0;
        header.error = err;

        connection->conn_xdrs.x_op = XDR_ENCODE;
        if (!xdr_ndmp_header(&connection->conn_xdrs, &header)) {
                NDMP_LOG(LOG_DEBUG,
                    "Sending message 0x%x: encoding request header", message);
                (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);
                return (-1);
        }
        if (err == NDMP_NO_ERR && handler->mh_xdr_request && request_data) {
                if (!(*handler->mh_xdr_request)(&connection->conn_xdrs,
                    request_data)) {
                        NDMP_LOG(LOG_DEBUG,
                            "Sending message 0x%x: encoding request body",
                            message);
                        (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);
                        return (-1);
                }
        }
        (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);

        if (handler->mh_xdr_reply == 0) {
                NDMP_LOG(LOG_DEBUG, "handler->mh_xdr_reply == 0");
                return (0);
        }

        /*
         * Process messages until the reply to this request has been
         * processed.
         */
        for (; ; ) {
                r = ndmp_process_messages(connection, TRUE);

                /* connection error? */
                if (r < 0)
                        return (-1);

                /* no reply received? */
                if (r == 0)
                        continue;

                /* reply received? */
                if (r == 1) {
                        if (message !=
                            connection->conn_msginfo.mi_hdr.message) {
                                NDMP_LOG(LOG_DEBUG,
                                    "Received unexpected reply 0x%x",
                                    connection->conn_msginfo.mi_hdr.message);
                                ndmp_free_message(connection_handle);
                                return (-1);
                        }
                        if (reply != NULL)
                                *reply = connection->conn_msginfo.mi_body;
                        else
                                ndmp_free_message(connection_handle);

                        return (connection->conn_msginfo.mi_hdr.error);
                }
                /* error handling reply */

                return (-1);
        }
}


/*
 * ndmp_send_request_lock
 *
 * A wrapper for ndmp_send_request with locks.
 *
 * Parameters:
 *   connection_handle (input) - connection pointer.
 *   message (input) - message number.
 *   err (input) - error code to place in header.
 *   request_data (input) - message body.
 *   reply (output) - reply message. If 0, reply will be
 *                              discarded.
 *
 * Returns:
 *   0  - successful send.
 *  -1  - error.
 *   otherwise - error from reply header.
 *
 * Notes:
 *   - The reply body is only returned if the error code is NDMP_NO_ERR.
 */
int
ndmp_send_request_lock(ndmp_connection_t *connection_handle,
    ndmp_message message, ndmp_error err, void *request_data, void **reply)
{
        int rv;
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        (void) mutex_lock(&connection->conn_lock);

        rv = ndmp_send_request(connection_handle, message, err, request_data,
            reply);
        (void) mutex_unlock(&connection->conn_lock);
        return (rv);
}


/*
 * ndmp_send_response
 *
 * Send an NDMP reply message.
 *
 * Parameters:
 *   connection_handle  (input)  - connection pointer.
 *   err               (input)  - error code to place in header.
 *   reply           (input)  - reply message body.
 *
 * Returns:
 *   0 - successful send.
 *  -1 - error.
 *
 * Notes:
 *   - The body is only sent if the error code is NDMP_NO_ERR.
 */
int
ndmp_send_response(ndmp_connection_t *connection_handle, ndmp_error err,
    void *reply)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;
        ndmp_header header;
        struct timeval time;

        (void) gettimeofday(&time, 0);

        header.sequence = ++(connection->conn_my_sequence);
        header.time_stamp = time.tv_sec;
        header.message_type = NDMP_MESSAGE_REPLY;
        header.message = connection->conn_msginfo.mi_hdr.message;
        header.reply_sequence = connection->conn_msginfo.mi_hdr.sequence;
        header.error = err;

        connection->conn_xdrs.x_op = XDR_ENCODE;
        if (!xdr_ndmp_header(&connection->conn_xdrs, &header)) {
                NDMP_LOG(LOG_DEBUG, "Sending message 0x%x: "
                    "encoding reply header",
                    header.message);
                (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);
                return (-1);
        }
        if (err == NDMP_NO_ERR &&
            connection->conn_msginfo.mi_handler->mh_xdr_reply &&
            reply) {
                if (!(*connection->conn_msginfo.mi_handler->mh_xdr_reply)(
                    &connection->conn_xdrs, reply)) {
                        NDMP_LOG(LOG_DEBUG,
                            "Sending message 0x%x: encoding reply body",
                            header.message);
                        (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);
                        return (-1);
        }
        }
        (void) xdrrec_endofrecord(&connection->conn_xdrs, 1);
        return (0);
}

/*
 * ndmp_free_message
 *
 * Free the memory of NDMP message body.
 *
 * Parameters:
 *   connection_handle  (input)  - connection pointer.
 *
 * Returns:
 *   void
 *
 */
void
ndmp_free_message(ndmp_connection_t *connection_handle)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        if (connection->conn_msginfo.mi_handler == NULL ||
            connection->conn_msginfo.mi_body == NULL)
                return;

        connection->conn_xdrs.x_op = XDR_FREE;
        if (connection->conn_msginfo.mi_hdr.message_type ==
            NDMP_MESSAGE_REQUEST) {
                if (connection->conn_msginfo.mi_handler->mh_xdr_request)
                        (*connection->conn_msginfo.mi_handler->mh_xdr_request)(
                            &connection->conn_xdrs,
                            connection->conn_msginfo.mi_body);
        } else {
                if (connection->conn_msginfo.mi_handler->mh_xdr_reply)
                        (*connection->conn_msginfo.mi_handler->mh_xdr_reply)(
                            &connection->conn_xdrs,
                            connection->conn_msginfo.mi_body);
        }

        (void) free(connection->conn_msginfo.mi_body);
        connection->conn_msginfo.mi_body = 0;
}

/*
 * ndmp_get_fd
 *
 * Returns the connection file descriptor.
 *
 * Parameters:
 *   connection_handle (input) - connection handle
 *
 * Returns:
 *   >=0 - file descriptor.
 *   -1  - connection not open.
 */
int
ndmp_get_fd(ndmp_connection_t *connection_handle)
{
        return (((ndmp_connection_t *)connection_handle)->conn_sock);
}


/*
 * ndmp_set_client_data
 *
 * This function provides a means for the library client to provide
 * a pointer to some user data structure that is retrievable by
 * each message handler via ndmp_get_client_data.
 *
 * Parameters:
 *   connection_handle  (input) - connection handle.
 *   client_data        (input) - user data pointer.
 *
 * Returns:
 *   void
 */
void
ndmp_set_client_data(ndmp_connection_t *connection_handle, void *client_data)
{
        ((ndmp_connection_t *)connection_handle)->conn_client_data =
            client_data;
}


/*
 * ndmp_get_client_data
 *
 * This function provides a means for the library client to provide
 * a pointer to some user data structure that is retrievable by
 * each message handler via ndmp_get_client_data.
 *
 * Parameters:
 *   connection_handle (input) - connection handle.
 *
 * Returns:
 *   client data pointer.
 */
void *
ndmp_get_client_data(ndmp_connection_t *connection_handle)
{
        return (((ndmp_connection_t *)connection_handle)->conn_client_data);
}


/*
 * ndmp_set_version
 *
 * Sets the NDMP protocol version to be used on the connection.
 *
 * Parameters:
 *   connection_handle  (input) - connection handle.
 *   version       (input) - protocol version.
 *
 * Returns:
 *   void
 */
void
ndmp_set_version(ndmp_connection_t *connection_handle, ushort_t version)
{
        ((ndmp_connection_t *)connection_handle)->conn_version = version;
}


/*
 * ndmp_get_version
 *
 * Gets the NDMP protocol version in use on the connection.
 *
 * Parameters:
 *   connection_handle  (input) - connection handle.
 *   version       (input) - protocol version.
 *
 * Returns:
 *   void
 */
ushort_t
ndmp_get_version(ndmp_connection_t *connection_handle)
{
        return (((ndmp_connection_t *)connection_handle)->conn_version);
}


/*
 * ndmp_set_authorized
 *
 * Mark the connection as either having been authorized or not.
 *
 * Parameters:
 *   connection_handle  (input) - connection handle.
 *   authorized (input) - TRUE or FALSE.
 *
 * Returns:
 *   void
 */
void
ndmp_set_authorized(ndmp_connection_t *connection_handle, boolean_t authorized)
{
        ((ndmp_connection_t *)connection_handle)->conn_authorized = authorized;
}


/*
 * ndmpd_main
 *
 * NDMP main function called from main().
 *
 * Parameters:
 *   void
 *
 * Returns:
 *   void
 */
void
ndmpd_main(void)
{
        char *propval;

        ndmp_load_params();

        /*
         * Find ndmp port number to be used. If ndmpd is run as command line
         * and port number is supplied, use that port number. If port number is
         * is not supplied, find out if ndmp port property is set. If ndmp
         * port property is set, use that port number otherwise use the defaule
         * port number.
         */
        if (ndmp_port == 0) {
                if ((propval = ndmpd_get_prop(NDMP_TCP_PORT)) == NULL ||
                    *propval == 0)
                        ndmp_port = NDMPPORT;
                else
                        ndmp_port = strtol(propval, 0, 0);
        }

        if (ndmp_run(ndmp_port, connection_handler) == -1)
                perror("ndmp_run ERROR");
}

/*
 * connection_handler
 *
 * NDMP connection handler.
 * Waits for, reads, and processes NDMP requests on a connection.
 *
 * Parameters:
 *   connection (input) - connection handle.
 *
 * Return:
 *   void
 */
void
connection_handler(ndmp_connection_t *connection)
{
        static int conn_id = 1;
        ndmpd_session_t session;
        ndmp_notify_connected_request req;
        int connection_fd;

        (void) memset(&session, 0, sizeof (session));
        session.ns_connection = connection;
        session.ns_eof = FALSE;
        /*
         * The 'protocol_version' must be 1 at first, since the client talks
         * to the server in version 1 then they can move to a higher
         * protocol version.
         */
        session.ns_protocol_version = ndmp_ver;

        session.ns_scsi.sd_is_open = -1;
        session.ns_scsi.sd_devid = -1;

        session.ns_scsi.sd_sid = 0;
        session.ns_scsi.sd_lun = 0;
        session.ns_scsi.sd_valid_target_set = 0;
        (void) memset(session.ns_scsi.sd_adapter_name, 0,
            sizeof (session.ns_scsi.sd_adapter_name));

        session.ns_tape.td_fd = -1;
        session.ns_tape.td_sid = 0;
        session.ns_tape.td_lun = 0;
        (void) memset(session.ns_tape.td_adapter_name, 0,
            sizeof (session.ns_tape.td_adapter_name));
        session.ns_tape.td_pos = 0;
        session.ns_tape.td_record_count = 0;
        session.ns_file_handler_list = 0;

        (void) ndmpd_data_init(&session);
        ndmpd_file_history_init(&session);
        if (ndmpd_mover_init(&session) < 0)
                return;

        if (ndmp_lbr_init(&session) < 0)
                return;

        /*
         * Setup defaults here. The init functions can not set defaults
         * since the init functions are called by the stop request handlers
         * and client set variables need to persist across data operations.
         */
        session.ns_mover.md_record_size = MAX_RECORD_SIZE;

        ndmp_set_client_data(connection, (void *)&session);

        req.reason = NDMP_CONNECTED;
        req.protocol_version = ndmp_ver;
        req.text_reason = "";

        if (ndmp_send_request_lock(connection, NDMP_NOTIFY_CONNECTION_STATUS,
            NDMP_NO_ERR, (void *)&req, 0) < 0) {
                NDMP_LOG(LOG_DEBUG, "Connection terminated");
                return;
        }
        connection_fd = ndmp_get_fd(connection);

        NDMP_LOG(LOG_DEBUG, "connection_fd: %d", connection_fd);

        /*
         * Add the handler function for the connection to the DMA.
         */
        if (ndmpd_add_file_handler(&session, (void *)&session, connection_fd,
            NDMPD_SELECT_MODE_READ, HC_CLIENT, connection_file_handler) != 0) {
                NDMP_LOG(LOG_DEBUG, "Could not register session handler.");
                return;
        }

        /*
         * Register the connection in the list of active connections.
         */
        if (ndmp_connect_list_add(connection, &conn_id) != 0) {
                NDMP_LOG(LOG_ERR,
                    "Could not register the session to the server.");
                (void) ndmpd_remove_file_handler(&session, connection_fd);
                return;
        }

        session.hardlink_q = hardlink_q_init();

        while (session.ns_eof == FALSE)
                (void) ndmpd_select(&session, TRUE, HC_ALL);

        hardlink_q_cleanup(session.hardlink_q);

        NDMP_LOG(LOG_DEBUG, "Connection terminated");

        (void) ndmpd_remove_file_handler(&session, connection_fd);

        if (session.ns_scsi.sd_is_open != -1) {
                NDMP_LOG(LOG_DEBUG, "scsi.is_open: %d",
                    session.ns_scsi.sd_is_open);
                (void) ndmp_open_list_del(session.ns_scsi.sd_adapter_name,
                    session.ns_scsi.sd_sid, session.ns_scsi.sd_lun);
        }
        if (session.ns_tape.td_fd != -1) {
                NDMP_LOG(LOG_DEBUG, "tape.fd: %d", session.ns_tape.td_fd);
                (void) close(session.ns_tape.td_fd);
                (void) ndmp_open_list_del(session.ns_tape.td_adapter_name,
                    session.ns_tape.td_sid, session.ns_tape.td_lun);
        }
        ndmpd_mover_shut_down(&session);
        ndmp_lbr_cleanup(&session);
        ndmpd_data_cleanup(&session);
        ndmpd_file_history_cleanup(&session, FALSE);
        ndmpd_mover_cleanup(&session);

        (void) ndmp_connect_list_del(connection);
}


/*
 * connection_file_handler
 *
 * ndmp_connection_t file handler function.
 * Called by ndmpd_select when data is available to be read on the
 * NDMP connection.
 *
 * Parameters:
 *   cookie (input) - session pointer.
 *   fd      (input) - connection file descriptor.
 *   mode    (input) - select mode.
 *
 * Returns:
 *   void.
 */
/*ARGSUSED*/
static void
connection_file_handler(void *cookie, int fd, ulong_t mode)
{
        ndmpd_session_t *session = (ndmpd_session_t *)cookie;

        if (ndmp_process_requests(session->ns_connection) < 0)
                session->ns_eof = TRUE;
}


/* ************* private functions *************************************** */

/*
 * ndmp_readit
 *
 * Low level read routine called by the xdrrec library.
 *
 * Parameters:
 *   connection (input) - connection pointer.
 *   buf        (input) - location to store received data.
 *   len        (input) - max number of bytes to read.
 *
 * Returns:
 *   >0 - number of bytes received.
 *   -1 - error.
 */
static int
ndmp_readit(void *connection_handle, caddr_t buf, int len)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;

        len = read(connection->conn_sock, buf, len);
        if (len <= 0) {
                /* ndmp_connection_t has been closed. */
                connection->conn_eof = TRUE;
                return (-1);
        }
        return (len);
}

/*
 * ndmp_writeit
 *
 * Low level write routine called by the xdrrec library.
 *
 * Parameters:
 *   connection (input) - connection pointer.
 *   buf        (input) - location to store received data.
 *   len        (input) - max number of bytes to read.
 *
 * Returns:
 *   >0 - number of bytes sent.
 *   -1 - error.
 */
static int
ndmp_writeit(void *connection_handle, caddr_t buf, int len)
{
        ndmp_connection_t *connection = (ndmp_connection_t *)connection_handle;
        register int n;
        register int cnt;

        for (cnt = len; cnt > 0; cnt -= n, buf += n) {
                if ((n = write(connection->conn_sock, buf, cnt)) < 0) {
                        connection->conn_eof = TRUE;
                        return (-1);
                }
        }

        return (len);
}


/*
 * ndmp_recv_msg
 *
 * Read the next message.
 *
 * Parameters:
 *   connection (input)  - connection pointer.
 *   msg        (output) - received message.
 *
 * Returns:
 *   0 - Message successfully received.
 *   error number - Message related error.
 *  -1 - Error decoding the message header.
 */
static int
ndmp_recv_msg(ndmp_connection_t *connection)
{
        bool_t(*xdr_func) (XDR *, ...) = NULL;

        /* Decode the header. */
        connection->conn_xdrs.x_op = XDR_DECODE;
        (void) xdrrec_skiprecord(&connection->conn_xdrs);
        if (!xdr_ndmp_header(&connection->conn_xdrs,
            &connection->conn_msginfo.mi_hdr))
                return (-1);

        /* Lookup info necessary for processing this message. */
        if ((connection->conn_msginfo.mi_handler = ndmp_get_handler(connection,
            connection->conn_msginfo.mi_hdr.message)) == 0) {
                NDMP_LOG(LOG_DEBUG, "Message 0x%x not supported",
                    connection->conn_msginfo.mi_hdr.message);
                return (NDMP_NOT_SUPPORTED_ERR);
        }
        connection->conn_msginfo.mi_body = 0;

        if (connection->conn_msginfo.mi_hdr.error != NDMP_NO_ERR)
                return (0);

        /* Determine body type */
        if (connection->conn_msginfo.mi_hdr.message_type ==
            NDMP_MESSAGE_REQUEST) {
                if (ndmp_check_auth_required(
                    connection->conn_msginfo.mi_hdr.message) &&
                    !connection->conn_authorized) {
                        NDMP_LOG(LOG_DEBUG,
                            "Processing request 0x%x:connection not authorized",
                            connection->conn_msginfo.mi_hdr.message);
                        return (NDMP_NOT_AUTHORIZED_ERR);
                }
                if (connection->conn_msginfo.mi_handler->mh_sizeof_request >
                    0) {
                        xdr_func =
                            connection->conn_msginfo.mi_handler->mh_xdr_request;
                        if (xdr_func == NULL) {
                                NDMP_LOG(LOG_DEBUG,
                                    "Processing request 0x%x: no xdr function "
                                    "in handler table",
                                    connection->conn_msginfo.mi_hdr.message);
                                return (NDMP_NOT_SUPPORTED_ERR);
                        }
                        connection->conn_msginfo.mi_body = ndmp_malloc(
                            connection->conn_msginfo.mi_handler->
                            mh_sizeof_request);
                        if (connection->conn_msginfo.mi_body == NULL)
                                return (NDMP_NO_MEM_ERR);

                        (void) memset(connection->conn_msginfo.mi_body, 0,
                            connection->conn_msginfo.mi_handler->
                            mh_sizeof_request);
                }
        } else {
                if (connection->conn_msginfo.mi_handler->mh_sizeof_reply > 0) {
                        xdr_func =
                            connection->conn_msginfo.mi_handler->mh_xdr_reply;
                        if (xdr_func == NULL) {
                                NDMP_LOG(LOG_DEBUG,
                                    "Processing reply 0x%x: no xdr function "
                                    "in handler table",
                                    connection->conn_msginfo.mi_hdr.message);
                                return (NDMP_NOT_SUPPORTED_ERR);
                        }
                        connection->conn_msginfo.mi_body = ndmp_malloc(
                            connection->conn_msginfo.mi_handler->
                            mh_sizeof_reply);
                        if (connection->conn_msginfo.mi_body == NULL)
                                return (NDMP_NO_MEM_ERR);

                        (void) memset(connection->conn_msginfo.mi_body, 0,
                            connection->conn_msginfo.mi_handler->
                            mh_sizeof_reply);
                }
        }

        /* Decode message arguments if needed */
        if (xdr_func) {
                if (!(*xdr_func)(&connection->conn_xdrs,
                    connection->conn_msginfo.mi_body)) {
                        NDMP_LOG(LOG_DEBUG,
                            "Processing message 0x%x: error decoding arguments",
                            connection->conn_msginfo.mi_hdr.message);
                        free(connection->conn_msginfo.mi_body);
                        connection->conn_msginfo.mi_body = 0;
                        return (NDMP_XDR_DECODE_ERR);
                }
        }
        return (0);
}

/*
 * ndmp_process_messages
 *
 * Reads the next message into the stream buffer.
 * Processes messages until the stream buffer is empty.
 *
 * This function processes all data in the stream buffer before returning.
 * This allows functions like poll() to be used to determine when new
 * messages have arrived. If only some of the messages in the stream buffer
 * were processed and then poll was called, poll() could block waiting for
 * a message that had already been received and read into the stream buffer.
 *
 * This function processes both request and reply messages.
 * Request messages are dispatched using the appropriate function from the
 * message handling table.
 * Only one reply messages may be pending receipt at a time.
 * A reply message, if received, is placed in connection->conn_msginfo
 * before returning to the caller.
 * Errors are reported if a reply is received but not expected or if
 * more than one reply message is received
 *
 * Parameters:
 *   connection     (input)  - connection pointer.
 *   reply_expected (output) - TRUE  - a reply message is expected.
 *                           FALSE - no reply message is expected and
 *                           an error will be reported if a reply
 *                           is received.
 *
 * Returns:
 *   NDMP_PROC_REP_ERR - 1 or more messages successfully processed,
 *      error processing reply message.
 *   NDMP_PROC_REP_ERR - 1 or more messages successfully processed,
 *      reply seen.
 *   NDMP_PROC_REP_ERR - 1 or more messages successfully processed,
 *      no reply seen.
 *   NDMP_PROC_REP_ERR - error; connection no longer established.
 *
 * Notes:
 *   If the peer is generating a large number of requests, a caller
 *   looking for a reply will be blocked while the requests are handled.
 *   This is because this function does not return until the stream
 *   buffer is empty.
 *   Code needs to be added to allow a return if the stream buffer
 *   is not empty but there is data available on the socket. This will
 *   prevent poll() from blocking and prevent a caller looking for a reply
 *   from getting blocked by a bunch of requests.
 */
static int
ndmp_process_messages(ndmp_connection_t *connection, boolean_t reply_expected)
{
        msg_info_t reply_msginfo;
        boolean_t reply_read = FALSE;
        boolean_t reply_error = FALSE;
        int err;

        NDMP_LOG(LOG_DEBUG, "reply_expected: %s",
            reply_expected == TRUE ? "TRUE" : "FALSE");

        (void) memset((void *)&reply_msginfo, 0, sizeof (msg_info_t));

        do {
                (void) memset((void *)&connection->conn_msginfo, 0,
                    sizeof (msg_info_t));

                if ((err = ndmp_recv_msg(connection)) != NDMP_NO_ERR) {
                        if (connection->conn_eof) {
                                NDMP_LOG(LOG_DEBUG, "detected eof");
                                return (NDMP_PROC_ERR);
                        }
                        if (err < 1) {
                                NDMP_LOG(LOG_DEBUG, "error decoding header");

                                /*
                                 * Error occurred decoding the header.
                                 * Don't send a reply since we don't know
                                 * the message or if the message was even
                                 * a request message.  To be safe, assume
                                 * that the message was a reply if a reply
                                 * was expected. Need to do this to prevent
                                 * hanging ndmp_send_request() waiting for a
                                 * reply.  Don't set reply_read so that the
                                 * reply will be processed if it is received
                                 * later.
                                 */
                                if (reply_read == FALSE)
                                        reply_error = TRUE;

                                continue;
                        }
                        if (connection->conn_msginfo.mi_hdr.message_type
                            != NDMP_MESSAGE_REQUEST) {
                                NDMP_LOG(LOG_DEBUG, "received reply: 0x%x",
                                    connection->conn_msginfo.mi_hdr.message);

                                if (reply_expected == FALSE ||
                                    reply_read == TRUE)
                                        NDMP_LOG(LOG_DEBUG,
                                            "Unexpected reply message: 0x%x",
                                            connection->conn_msginfo.mi_hdr.
                                            message);

                                ndmp_free_message((ndmp_connection_t *)
                                    connection);

                                if (reply_read == FALSE) {
                                        reply_read = TRUE;
                                        reply_error = TRUE;
                                }
                                continue;
                        }
                        NDMP_LOG(LOG_DEBUG, "received request: 0x%x",
                            connection->conn_msginfo.mi_hdr.message);

                        (void) ndmp_send_response((ndmp_connection_t *)
                            connection, err, NULL);
                        ndmp_free_message((ndmp_connection_t *)connection);
                        continue;
                }
                if (connection->conn_msginfo.mi_hdr.message_type
                    != NDMP_MESSAGE_REQUEST) {
                        NDMP_LOG(LOG_DEBUG, "received reply: 0x%x",
                            connection->conn_msginfo.mi_hdr.message);

                        if (reply_expected == FALSE || reply_read == TRUE) {
                                NDMP_LOG(LOG_DEBUG,
                                    "Unexpected reply message: 0x%x",
                                    connection->conn_msginfo.mi_hdr.message);
                                ndmp_free_message((ndmp_connection_t *)
                                    connection);
                                continue;
                        }
                        reply_read = TRUE;
                        reply_msginfo = connection->conn_msginfo;
                        continue;
                }
                NDMP_LOG(LOG_DEBUG, "received request: 0x%x",
                    connection->conn_msginfo.mi_hdr.message);

                /*
                 * The following is needed to catch an improperly constructed
                 * handler table or to deal with an NDMP client that is not
                 * conforming to the negotiated protocol version.
                 */
                if (connection->conn_msginfo.mi_handler->mh_func == NULL) {
                        NDMP_LOG(LOG_DEBUG, "No handler for message 0x%x",
                            connection->conn_msginfo.mi_hdr.message);

                        (void) ndmp_send_response((ndmp_connection_t *)
                            connection, NDMP_NOT_SUPPORTED_ERR, NULL);
                        ndmp_free_message((ndmp_connection_t *)connection);
                        continue;
                }
                /*
                 * Call the handler function.
                 * The handler will send any necessary reply.
                 */
                (*connection->conn_msginfo.mi_handler->mh_func) (connection,
                    connection->conn_msginfo.mi_body);

                ndmp_free_message((ndmp_connection_t *)connection);

        } while (xdrrec_eof(&connection->conn_xdrs) == FALSE &&
            connection->conn_eof == FALSE);

        NDMP_LOG(LOG_DEBUG, "no more messages in stream buffer");

        if (connection->conn_eof == TRUE) {
                if (reply_msginfo.mi_body)
                        free(reply_msginfo.mi_body);
                return (NDMP_PROC_ERR);
        }
        if (reply_error) {
                if (reply_msginfo.mi_body)
                        free(reply_msginfo.mi_body);
                return (NDMP_PROC_REP_ERR);
        }
        if (reply_read) {
                connection->conn_msginfo = reply_msginfo;
                return (NDMP_PROC_MSG);
        }
        return (NDMP_PROC_REP);
}


/*
 * ndmp_get_interface
 *
 * Return the NDMP interface (e.g. config, scsi, tape) for the
 * specific message.
 *
 * Parameters:
 *   message (input) - message number.
 *
 * Returns:
 *   NULL - message not found.
 *   pointer to handler info.
 */
static ndmp_handler_t *
ndmp_get_interface(ndmp_message message)
{
        ndmp_handler_t *ni = &ndmp_msghdl_tab[(message >> 8) % INT_MAXCMD];

        if ((message & 0xff) >= ni->hd_cnt)
                return (NULL);

        /* Sanity check */
        if (ni->hd_msgs[message & 0xff].hm_message != message)
                return (NULL);

        return (ni);
}

/*
 * ndmp_get_handler
 *
 * Return the handler info for the specified NDMP message.
 *
 * Parameters:
 *   connection (input) - connection pointer.
 *   message (input) - message number.
 *
 * Returns:
 *   NULL - message not found.
 *   pointer to handler info.
 */
static ndmp_msg_handler_t *
ndmp_get_handler(ndmp_connection_t *connection, ndmp_message message)
{
        ndmp_msg_handler_t *handler = NULL;

        ndmp_handler_t *ni = ndmp_get_interface(message);
        int ver = connection->conn_version;

        if (ni)
                handler = &ni->hd_msgs[message & 0xff].hm_msg_v[ver - 2];

        return (handler);
}

/*
 * ndmp_check_auth_required
 *
 * Check if the connection needs to be authenticated before
 * this message is being processed.
 *
 * Parameters:
 *   message (input) - message number.
 *
 * Returns:
 *   TRUE - required
 *   FALSE - not required
 */
static boolean_t
ndmp_check_auth_required(ndmp_message message)
{
        boolean_t auth_req = FALSE;
        ndmp_handler_t *ni = ndmp_get_interface(message);

        if (ni)
                auth_req = ni->hd_msgs[message & 0xff].hm_auth_required;

        return (auth_req);
}

/*
 * tcp_accept
 *
 * A wrapper around accept for retrying and getting the IP address
 *
 * Parameters:
 *   listen_sock (input) - the socket for listening
 *   inaddr_p (output) - the IP address of peer connection
 *
 * Returns:
 *   socket for the accepted connection
 *   -1: error
 */
int
tcp_accept(int listen_sock, unsigned int *inaddr_p)
{
        struct sockaddr_in      sin;
        int                     sock, i;
        int                     try;

        for (try = 0; try < 3; try++) {
                i = sizeof (sin);
                sock = accept(listen_sock, (struct sockaddr *)&sin, &i);
                if (sock < 0) {
                        continue;
                }
                *inaddr_p = sin.sin_addr.s_addr;
                return (sock);
        }
        return (-1);
}


/*
 * tcp_get_peer
 *
 * Get the peer IP address for a connection
 *
 * Parameters:
 *   sock (input) - the active socket
 *   inaddr_p (output) - the IP address of peer connection
 *   port_p (output) - the port number of peer connection
 *
 * Returns:
 *   socket for the accepted connection
 *   -1: error
 */
int
tcp_get_peer(int sock, unsigned int *inaddr_p, int *port_p)
{
        struct sockaddr_in sin;
        int i, rc;

        i = sizeof (sin);
        rc = getpeername(sock, (struct sockaddr *)&sin, &i);
        if (rc != 0)
                return (-1);

        if (inaddr_p)
                *inaddr_p = sin.sin_addr.s_addr;

        if (port_p)
                *port_p = ntohs(sin.sin_port);

        return (sock);

}

/*
 * gethostaddr
 *
 * Get the IP address string of the current host
 *
 * Parameters:
 *   void
 *
 * Returns:
 *   IP address
 *   NULL: error
 */
char *
gethostaddr(void)
{
        static char s[MAXHOSTNAMELEN];
        struct hostent *h;
        struct in_addr in;
        char *p;

        if (gethostname(s, sizeof (s)) == -1)
                return (NULL);

        if ((h = gethostbyname(s)) == NULL)
                return (NULL);

        p = h->h_addr_list[0];
        (void) memcpy(&in.s_addr, p, sizeof (in.s_addr));
        return (inet_ntoa(in));
}


/*
 * get_default_nic_addr
 *
 * Get the IP address of the default NIC
 */
char *
get_default_nic_addr(void)
{
        struct ifaddrlist *al = NULL;
        char errmsg[ERRBUFSIZE];
        struct in_addr addr;
        int nifs;

        nifs = ifaddrlist(&al, AF_INET, LIFC_EXTERNAL_SOURCE, errmsg);
        if (nifs <= 0)
                return (NULL);

        /* pick the first interface's address */
        addr = al[0].addr.addr;
        free(al);

        return (inet_ntoa(IN_ADDR(addr.s_addr)));
}


/*
 * ndmpd_audit_backup
 *
 * Generate AUE_ndmp_backup audit record
 */
/*ARGSUSED*/
void
ndmpd_audit_backup(ndmp_connection_t *conn,
    char *path, int dest, char *local_path, int result)
{
        adt_event_data_t *event;

        if ((event = adt_alloc_event(conn->conn_ah, ADT_ndmp_backup)) == NULL) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                return;
        }
        event->adt_ndmp_backup.source = path;

        if (dest == NDMP_ADDR_LOCAL) {
                event->adt_ndmp_backup.local_dest = local_path;
        } else {
                event->adt_ndmp_backup.remote_dest = conn->conn_sock;
        }

        if (result == 0) {
                if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        } else {
                if (adt_put_event(event, ADT_FAILURE, result) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        }

        adt_free_event(event);
}


/*
 * ndmpd_audit_restore
 *
 * Generate AUE_ndmp_restore audit record
 */
/*ARGSUSED*/
void
ndmpd_audit_restore(ndmp_connection_t *conn,
    char *path, int dest, char *local_path, int result)
{
        adt_event_data_t *event;

        if ((event = adt_alloc_event(conn->conn_ah,
            ADT_ndmp_restore)) == NULL) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                return;
        }
        event->adt_ndmp_restore.destination = path;

        if (dest == NDMP_ADDR_LOCAL) {
                event->adt_ndmp_restore.local_source = local_path;
        } else {
                event->adt_ndmp_restore.remote_source = conn->conn_sock;
        }

        if (result == 0) {
                if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        } else {
                if (adt_put_event(event, ADT_FAILURE, result) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        }

        adt_free_event(event);
}


/*
 * ndmpd_audit_connect
 *
 * Generate AUE_ndmp_connect audit record
 */
/*ARGSUSED*/
void
ndmpd_audit_connect(ndmp_connection_t *conn, int result)
{
        adt_event_data_t *event;
        adt_termid_t *termid;

        if (adt_load_termid(conn->conn_sock, &termid) != 0) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                return;
        }

        if (adt_set_user(conn->conn_ah, ADT_NO_ATTRIB, ADT_NO_ATTRIB,
            ADT_NO_ATTRIB, ADT_NO_ATTRIB, termid, ADT_NEW) != 0) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                free(termid);
                return;
        }
        free(termid);

        if ((event = adt_alloc_event(conn->conn_ah,
            ADT_ndmp_connect)) == NULL) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                return;
        }

        if (result == 0) {
                if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        } else {
                if (adt_put_event(event, ADT_FAILURE, result) != 0)
                        NDMP_LOG(LOG_ERR, "Audit failure: %m.");
        }

        adt_free_event(event);
}


/*
 * ndmpd_audit_disconnect
 *
 * Generate AUE_ndmp_disconnect audit record
 */
/*ARGSUSED*/
void
ndmpd_audit_disconnect(ndmp_connection_t *conn)
{
        adt_event_data_t *event;

        if ((event = adt_alloc_event(conn->conn_ah,
            ADT_ndmp_disconnect)) == NULL) {
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");
                return;
        }
        if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0)
                NDMP_LOG(LOG_ERR, "Audit failure: %m.");

        adt_free_event(event);
}

void *
ndmp_malloc(size_t size)
{
        void *data;

        if ((data = calloc(1, size)) == NULL) {
                NDMP_LOG(LOG_ERR, "Out of memory.");
        }

        return (data);
}

/*
 * get_backup_path_v3
 *
 * Get the backup path from the NDMP environment variables.
 *
 * Parameters:
 *   params (input) - pointer to the parameters structure.
 *
 * Returns:
 *   The backup path: if anything is specified
 *   NULL: Otherwise
 */
char *
get_backup_path_v3(ndmpd_module_params_t *params)
{
        char *bkpath;

        bkpath = MOD_GETENV(params, "PREFIX");
        if (!bkpath)
                bkpath = MOD_GETENV(params, "FILESYSTEM");


        if (!bkpath) {
                MOD_LOGV3(params, NDMP_LOG_ERROR,
                    "Backup path not defined.\n");
        } else {
                NDMP_LOG(LOG_DEBUG, "bkpath: \"%s\"", bkpath);
        }

        return (bkpath);
}

/*
 * get_backup_path
 *
 * Find the backup path from the environment variables (v2)
 */
char *
get_backup_path_v2(ndmpd_module_params_t *params)
{
        char *bkpath;

        bkpath = MOD_GETENV(params, "PREFIX");
        if (bkpath == NULL)
                bkpath = MOD_GETENV(params, "FILESYSTEM");

        if (bkpath == NULL) {
                MOD_LOG(params, "Error: restore path not specified.\n");
                return (NULL);
        }

        if (*bkpath != '/') {
                MOD_LOG(params, "Error: relative backup path not allowed.\n");
                return (NULL);
        }

        return (bkpath);
}