/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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); }