// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/cli/cli_node.cc,v 1.36 2007/02/16 22:45:28 pavlin Exp $"


//
// CLI (Command-Line Interface) implementation for XORP.
//


#include "cli_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/ipvx.hh"
#include "libxorp/ipvxnet.hh"
#include "libxorp/token.hh"
#include "libxorp/utils.hh"

#include "libcomm/comm_api.h"

#include "cli_client.hh"
#include "cli_node.hh"
#include "cli_private.hh"


//
// Exported variables
//

//
// Local constants definitions
//

//
// Local structures/classes, typedefs and macros
//


//
// Local variables
//

//
// Local functions prototypes
//


/**
 * CliNode::CliNode:
 * @init_family: The address family (%AF_INET or %AF_INET6
 * for IPv4 and IPv6 respectively).
 * @init_module_id: The module ID (must be %XORP_MODULE_CLI).
 * @init_eventloop: The event loop.
 * 
 * CLI node constructor.
 **/
CliNode::CliNode(int init_family, xorp_module_id module_id,
		 EventLoop& init_eventloop)
    : ProtoNode<Vif>(init_family, module_id, init_eventloop),
      _cli_port(0),		// XXX: not defined yet
      _next_session_id(0),
      _startup_cli_prompt(XORP_CLI_PROMPT),
      _cli_command_root(NULL, "", ""),
      _is_log_trace(false)
{
    string error_msg;

    XLOG_ASSERT(module_id == XORP_MODULE_CLI);
    if (module_id != XORP_MODULE_CLI) {
	XLOG_FATAL("Invalid module ID = %d (must be 'XORP_MODULE_CLI' = %d)",
		   module_id, XORP_MODULE_CLI);
    }
    
    cli_command_root()->set_allow_cd(true, _startup_cli_prompt);
    cli_command_root()->create_default_cli_commands();
    if (cli_command_root()->add_pipes(error_msg) != XORP_OK) {
	XLOG_FATAL("Cannot add command pipes: %s", error_msg.c_str());
    }
}

/**
 * CliNode::~CliNode:
 * @: 
 * 
 * CLI node destructor.
 **/
CliNode::~CliNode()
{
    stop();
    
    // TODO: perform CLI-specific operations.
}

int
CliNode::start()
{
    string error_msg;

    if (! is_enabled())
	return (XORP_OK);

    if (is_up() || is_pending_up())
	return (XORP_OK);

    if (ProtoNode<Vif>::start() < 0)
	return (XORP_ERROR);
    
    // Perform misc. CLI-specific start operations
    
    //
    // Start accepting connections
    //
    if (_cli_port != 0) {
	if (sock_serv_open().is_valid()) {
	    eventloop().add_ioevent_cb(_cli_socket, IOT_ACCEPT,
				       callback(this, &CliNode::accept_connection));
	}
    }
    
    if (add_internal_cli_commands(error_msg) != XORP_OK) {
	XLOG_ERROR("Cannot add internal CLI commands: %s", error_msg.c_str());
	return (XORP_ERROR);
    }

    XLOG_TRACE(is_log_trace(), "CLI started");

    return (XORP_OK);
}

/**
 * CliNode::stop:
 * @: 
 * 
 * Stop the CLI operation.
 * 
 * Return value: %XORP_OK on success, otherwise %XORP_ERROR.
 **/
int
CliNode::stop()
{
    if (is_down())
	return (XORP_OK);

    if (! is_up())
	return (XORP_ERROR);

    if (ProtoNode<Vif>::pending_stop() < 0)
	return (XORP_ERROR);

    // Perform misc. CLI-specific stop operations
    // TODO: add as necessary

    delete_pointers_list(_client_list);

    eventloop().remove_ioevent_cb(_cli_socket, IOT_ACCEPT);
    sock_serv_close();

    if (ProtoNode<Vif>::stop() < 0)
	return (XORP_ERROR);

    XLOG_TRACE(is_log_trace(), "CLI stopped");

    return (XORP_OK);
}

/**
 * Enable the node operation.
 * 
 * If an unit is not enabled, it cannot be start, or pending-start.
 */
void
CliNode::enable()
{
    ProtoUnit::enable();

    XLOG_TRACE(is_log_trace(), "CLI enabled");
}

/**
 * Disable the node operation.
 * 
 * If an unit is disabled, it cannot be start or pending-start.
 * If the unit was runnning, it will be stop first.
 */
void
CliNode::disable()
{
    stop();
    ProtoUnit::disable();

    XLOG_TRACE(is_log_trace(), "CLI disabled");
}

/**
 * CliNode::add_enable_cli_access_from_subnet:
 * @subnet_addr: The subnet address to add.
 * 
 * Add a subnet address to the list of subnet addresses enabled
 * for CLI access.
 * This method can be called more than once to add a number of
 * subnet addresses.
 **/
void
CliNode::add_enable_cli_access_from_subnet(const IPvXNet& subnet_addr)
{
    list<IPvXNet>::iterator iter;
    
    // Test if we have this subnet already
    for (iter = _enable_cli_access_subnet_list.begin();
	 iter != _enable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& tmp_ipvxnet = *iter;
	if (tmp_ipvxnet == subnet_addr)
	    return;		// Subnet address already added
    }
    
    _enable_cli_access_subnet_list.push_back(subnet_addr);
}

/**
 * CliNode::delete_enable_cli_access_from_subnet:
 * @subnet_addr: The subnet address to delete.
 * 
 * Delete a subnet address from the list of subnet addresses enabled
 * for CLI access.
 * 
 * Return value: %XORP_OK on success, otherwise %XORP_ERROR.
 **/
int
CliNode::delete_enable_cli_access_from_subnet(const IPvXNet& subnet_addr)
{
    list<IPvXNet>::iterator iter;
    
    // Test if we have this subnet already
    for (iter = _enable_cli_access_subnet_list.begin();
	 iter != _enable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& tmp_ipvxnet = *iter;
	if (tmp_ipvxnet == subnet_addr) {
	    _enable_cli_access_subnet_list.erase(iter);
	    return (XORP_OK);	// Entry erased
	}
    }
    
    return (XORP_ERROR);		// No entry found
}

/**
 * CliNode::add_disable_cli_access_from_subnet:
 * @subnet_addr: The subnet address to add.
 * 
 * Add a subnet address to the list of subnet addresses disabled
 * for CLI access.
 * This method can be called more than once to add a number of
 * subnet addresses.
 **/
void
CliNode::add_disable_cli_access_from_subnet(const IPvXNet& subnet_addr)
{
    list<IPvXNet>::iterator iter;
    
    // Test if we have this subnet already
    for (iter = _disable_cli_access_subnet_list.begin();
	 iter != _disable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& tmp_ipvxnet = *iter;
	if (tmp_ipvxnet == subnet_addr)
	    return;		// Subnet address already added
    }
    
    _disable_cli_access_subnet_list.push_back(subnet_addr);
}

/**
 * CliNode::delete_disable_cli_access_from_subnet:
 * @subnet_addr: The subnet address to delete.
 * 
 * Delete a subnet address from the list of subnet addresses disabled
 * for CLI access.
 * 
 * Return value: %XORP_OK on success, otherwise %XORP_ERROR.
 **/
int
CliNode::delete_disable_cli_access_from_subnet(const IPvXNet& subnet_addr)
{
    list<IPvXNet>::iterator iter;
    
    // Test if we have this subnet already
    for (iter = _disable_cli_access_subnet_list.begin();
	 iter != _disable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& tmp_ipvxnet = *iter;
	if (tmp_ipvxnet == subnet_addr) {
	    _disable_cli_access_subnet_list.erase(iter);
	    return (XORP_OK);	// Entry erased
	}
    }
    
    return (XORP_ERROR);		// No entry found
}

bool
CliNode::is_allow_cli_access(const IPvX& ipvx) const
{
    list<IPvXNet>::const_iterator iter;
    IPvXNet best_enable = IPvXNet(IPvX::ZERO(ipvx.af()), 0);
    IPvXNet best_disable = IPvXNet(IPvX::ZERO(ipvx.af()), 0);
    bool best_enable_found = false;
    bool best_disable_found = false;
    
    // Find the longest-match subnet address that may enable access
    for (iter = _enable_cli_access_subnet_list.begin();
	 iter != _enable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& ipvxnet = *iter;
	if (ipvx.af() != ipvxnet.masked_addr().af())
	    continue;
	if (! ipvxnet.contains(ipvx))
	    continue;
	if (best_enable.contains(ipvxnet))
	    best_enable = ipvxnet;
	best_enable_found = true;
    }

    // Find the longest-match subnet address that may disable access
    for (iter = _disable_cli_access_subnet_list.begin();
	 iter != _disable_cli_access_subnet_list.end();
	 ++iter) {
	const IPvXNet& ipvxnet = *iter;
	if (ipvx.af() != ipvxnet.masked_addr().af())
	    continue;
	if (! ipvxnet.contains(ipvx))
	    continue;
	if (best_disable.contains(ipvxnet))
	    best_disable = ipvxnet;
	best_disable_found = true;
    }

    if (! best_disable_found) {
	// XXX: no disable match, so enable access by default
	return (true);
    }
    
    if (! best_enable_found) {
	// XXX: no enable match, so definitely disable
	return (false);
    }
    
    // Both enable and disable match
    if (best_enable.prefix_len() > best_disable.prefix_len())
	return (true);
    
    return (false);		// XXX: disable even if conflicting config
}

/**
 * CliNode::find_cli_by_term_name:
 * @term_name: The CLI terminal name to search for.
 * 
 * Find a CLI client #CliClient for a given terminal name.
 * 
 * Return value: The CLI client with name of @term_name on success,
 * otherwise NULL.
 **/
CliClient *
CliNode::find_cli_by_term_name(const string& term_name) const
{
    list<CliClient *>::const_iterator iter;
    
    for (iter = _client_list.begin(); iter != _client_list.end(); ++ iter) {
	CliClient *cli_client = *iter;
	if (term_name == cli_client->cli_session_term_name()) {
	    return (cli_client);
	}
    }
    
    return (NULL);
}

/**
 * CliNode::find_cli_by_session_id:
 * @session_id: The CLI session ID to search for.
 * 
 * Find a CLI client #CliClient for a given session ID.
 * 
 * Return value: The CLI client with session ID of @session_id on success,
 * otherwise NULL.
 **/
CliClient *
CliNode::find_cli_by_session_id(uint32_t session_id) const
{
    list<CliClient *>::const_iterator iter;
    
    for (iter = _client_list.begin();
	 iter != _client_list.end();
	 ++ iter) {
	CliClient *cli_client = *iter;
	if (cli_client->cli_session_session_id() == session_id)
	    return (cli_client);
    }
    
    return (NULL);
}

/**
 * CliNode::xlog_output:
 * @obj: The #CliClient object to apply this function to.
 * @msg: A C-style string with the message to output.
 * 
 * Output a log message to a #CliClient object.
 * 
 * Return value: On success, the number of characters printed,
 * otherwise %XORP_ERROR.
 **/
int
CliNode::xlog_output(void *obj, const char *msg)
{
    CliClient *cli_client = static_cast<CliClient *>(obj);

    int ret_value = cli_client->cli_print(msg);
    if (ret_value >= 0 && 
	cli_client->cli_print("") >= 0 && cli_client->cli_flush() == 0) { 
	    return ret_value;
	}
    return  XORP_ERROR;
}

//
// CLI add_cli_command
//
int
CliNode::add_cli_command(
    // Input values,
    const string&	processor_name,
    const string&	command_name,
    const string&	command_help,
    const bool&		is_command_cd,
    const string&	command_cd_prompt,
    const bool&		is_command_processor,
    // Output values,
    string&		error_msg)
{
    // Reset the error message
    error_msg = "";
    
    //
    // Check the request
    //
    if (command_name.empty()) {
	error_msg = "ERROR: command name is empty";
	return (XORP_ERROR);
    }
    
    CliCommand *c0 = cli_command_root();
    CliCommand *c1 = NULL;
    
    if (! is_command_processor) {
	if (is_command_cd) {
	    c1 = c0->add_command(command_name, command_help,
				 command_cd_prompt, true, error_msg);
	} else {
	    c1 = c0->add_command(command_name, command_help, true, error_msg);
	}
    } else {
	// Command processor
	c1 = c0->add_command(command_name, command_help, true,
			     callback(this, &CliNode::send_process_command),
			     error_msg);
	if (c1 != NULL)
	    c1->set_can_pipe(true);
    }
    
    //
    // TODO: return the result of the command installation
    //
    
    if (c1 == NULL) {
	error_msg = c_format("Cannot install command '%s': %s",
			     command_name.c_str(),
			     error_msg.c_str());
	return (XORP_ERROR);
    }
    
    c1->set_global_name(token_line2vector(command_name));
    c1->set_server_name(processor_name);
    
    return (XORP_OK);
}

//
// CLI delete_cli_command
//
int
CliNode::delete_cli_command(
    // Input values,
    const string&	processor_name,
    const string&	command_name,
    // Output values,
    string&		error_msg)
{
    // Reset the error message
    error_msg = "";
    
    //
    // Check the request
    //
    if (command_name.empty()) {
	error_msg = "ERROR: command name is empty";
	return (XORP_ERROR);
    }
    
    CliCommand *c0 = cli_command_root();

    if (c0->delete_command(command_name) != XORP_OK) {
	error_msg = c_format("Cannot delete command '%s'",
			     command_name.c_str());
	return (XORP_ERROR);
    }

    UNUSED(processor_name);

    return (XORP_OK);
}

//
// Send a command request to a remote node
//
int
CliNode::send_process_command(const string& server_name,
			      const string& cli_term_name,
			      uint32_t cli_session_id,
			      const vector<string>& command_global_name,
			      const vector<string>& argv)
{
    if (server_name.empty())
	return (XORP_ERROR);
    if (cli_term_name.empty())
	return (XORP_ERROR);
    if (command_global_name.empty())
	return (XORP_ERROR);
    
    CliClient *cli_client = find_cli_by_session_id(cli_session_id);
    if (cli_client == NULL)
	return (XORP_ERROR);
    if (cli_client != find_cli_by_term_name(cli_term_name))
	return (XORP_ERROR);
    
    //
    // Send the request
    //
    if (! _send_process_command_callback.is_empty()) {
	(_send_process_command_callback)->dispatch(server_name,
						   server_name,
						   cli_term_name,
						   cli_session_id,
						   command_global_name,
						   argv);
    }
    
    cli_client->set_is_waiting_for_data(true);
    
    return (XORP_OK);
}

//
// Process the response of a command processed by a remote node
//
void
CliNode::recv_process_command_output(const string * , // processor_name,
				     const string *cli_term_name,
				     const uint32_t *cli_session_id,
				     const string *command_output)
{
    //
    // Find if a client is waiting for that command
    //
    if ((cli_term_name == NULL) || (cli_session_id == NULL))
	return;
    CliClient *cli_client = find_cli_by_session_id(*cli_session_id);
    if (cli_client == NULL)
	return;
    if (cli_client != find_cli_by_term_name(*cli_term_name))
	return;
    
    if (! cli_client->is_waiting_for_data()) {
	// ERROR: client is not waiting for that data (e.g., probably too late)
	return;
    }
    
    //
    // Print the result and reset client status
    //
    if (command_output != NULL) {
	cli_client->cli_print(c_format("%s", command_output->c_str()));
    }
    cli_client->cli_flush();
    cli_client->set_is_waiting_for_data(false);
    cli_client->post_process_command();
}

CliClient *
CliNode::add_client(XorpFd input_fd, XorpFd output_fd, bool is_network,
		    const string& startup_cli_prompt, string& error_msg)
{
    return (add_connection(input_fd, output_fd, is_network,
			   startup_cli_prompt, error_msg));
}

int
CliNode::delete_client(CliClient *cli_client, string& error_msg)
{
    if (delete_connection(cli_client, error_msg) != XORP_OK)
	return (XORP_ERROR);

    // XXX: delete the client itself if it is still around
    list<CliClient *>::iterator iter;

    iter = find(_client_list.begin(), _client_list.end(), cli_client);
    if (iter != _client_list.end()) {
	_client_list.erase(iter);
	delete cli_client;
    }

    return (XORP_OK);
}


syntax highlighted by Code2HTML, v. 0.9.1