%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /bin/
Upload File :
Create Path :
Current File : //bin/broctl

#!/usr/bin/python
#
# The BroControl interactive shell.

from __future__ import print_function
import sys
import time
import logging

# This is needed so that we can import BroControl.
sys.path.insert(0, "/usr/lib/broctl")

from BroControl.broctl import BroCtl
from BroControl import brocmd
from BroControl import util
from BroControl import utilcurses
from BroControl import version

# Main command loop.
class BroCtlCmdLoop(brocmd.ExitValueCmd):
    prompt = '[BroControl] > '

    def __init__(self, broctl_class=BroCtl, interactive=False, cmd=""):
        brocmd.ExitValueCmd.__init__(self)
        self.broctl = broctl_class(ui=self)
        self.node_names = self.broctl.node_names()
        self.interactive = interactive

        # Warn user to do broctl install, if needed.  Skip this check when
        # running cron to avoid receiving annoying emails.
        if cmd != "cron":
            self.broctl.warn_broctl_install(cmd in ("install", "deploy"))

    def finish(self):
        self.broctl.finish()

    def info(self, text):
        print(text)
        logging.info(text)

    def warn(self, text):
        self.info("Warning: %s" % text)

    def error(self, text):
        print("Error: %s" % text, file=sys.stderr)
        logging.info(text)
        self.exit_code = 1

    def default(self, line):
        strlist = line.split()

        if not self.broctl.plugins.runCustomCommand(strlist[0], " ".join(strlist[1:]), self):
            if strlist[0] != "--help":
                self.error("unknown command '%s'" % strlist[0])

            if not self.interactive:
                self.do_help(None)

        return True

    def emptyline(self):
        pass

    def precmd(self, line):
        logging.debug(line)
        return line

    def postcmd(self, stop, line):
        logging.debug("done")
        return stop

    def do_EOF(self, args):
        self._stopping = True
        return True

    def do_exit(self, args):
        """Terminates the shell."""
        self._stopping = True
        return True

    def do_quit(self, args):
        """Terminates the shell."""
        self._stopping = True
        return True

    def do_nodes(self, args):
        """Prints a list of all configured nodes."""
        if args:
            raise SyntaxError("nodes does not take any arguments")

        for node in self.broctl.nodes():
            self.info(node)

        return True

    def do_config(self, args):
        """Prints all configuration options with their current values."""
        if args:
            raise SyntaxError("config does not take any arguments")

        for (key, val) in self.broctl.get_config():
            self.info("%s = %s" % (key, val))

        return True

    def do_install(self, args):
        """- [--local]

        Reinstalls on all nodes (unless the ``--local`` option is given, in
        which case nothing will be propagated to other nodes), including all
        configuration files and local policy scripts.  Usually all nodes
        should be reinstalled at the same time, as any inconsistencies between
        them will lead to strange effects.  This command must be
        executed after *all* changes to any part of the broctl configuration
        (and after upgrading to a new version of Bro or BroControl),
        otherwise the modifications will not take effect.  Before executing
        ``install``, it is recommended to verify the configuration
        with check_."""

        local = False

        for arg in args.split():
            if arg == "--local":
                local = True
            else:
                raise SyntaxError("Invalid argument: %s" % arg)

        results = self.broctl.install(local)
        return results.ok

    def do_start(self, args):
        """- [<nodes>]

        Starts the given nodes, or all nodes if none are specified. Nodes
        already running are left untouched.
        """

        results = self.broctl.start(node_list=args)

        return results.ok

    def do_stop(self, args):
        """- [<nodes>]

        Stops the given nodes, or all nodes if none are specified. Nodes not
        running are left untouched.
        """
        results = self.broctl.stop(node_list=args)

        return results.ok

    def do_restart(self, args):
        """- [--clean] [<nodes>]

        Restarts the given nodes, or all nodes if none are specified. The
        effect is the same as first executing stop_ followed
        by a start_, giving the same nodes in both cases.

        If ``--clean`` is given, the installation is reset into a clean state
        before restarting. More precisely, a ``restart --clean`` turns into
        the command sequence stop_, cleanup_, check_, install_, and
        start_.
        """
        clean = False
        if args.startswith("--clean"):
            args = args[7:]
            clean = True

        results = self.broctl.restart(clean=clean, node_list=args)
        return results.ok

    def do_deploy(self, args):
        """
        Checks for errors in Bro policy scripts, then does an install followed
        by a restart on all nodes.  This command should be run after any
        changes to Bro policy scripts or the broctl configuration, and after
        Bro is upgraded or even just recompiled.

        This command is equivalent to running the check_, install_, and
        restart_ commands, in that order.
        """
        if args:
            raise SyntaxError("deploy does not take any arguments")

        results = self.broctl.deploy()

        return results.ok

    def do_status(self, args):
        """- [<nodes>]

        Prints the current status of the given nodes."""

        success = True
        results = self.broctl.status(node_list=args)

        typewidth = 7
        hostwidth = 16
        data = results.get_node_data()
        if data[0][2]["type"] == "standalone":
            # In standalone mode, we need a wider "type" column.
            typewidth = 10
            hostwidth = 13

        showall = "peers" in data[0][2]
        if showall:
            colfmt = "{name:<12} {type:<{0}} {host:<{1}} {status:<9} {pid:<6} {peers:<6} {started}"
        else:
            colfmt = "{name:<12} {type:<{0}} {host:<{1}} {status:<9} {pid:<6} {started}"

        hdrlist = ["name", "type", "host", "status", "pid", "peers", "started"]
        header = dict((x, x.title()) for x in hdrlist)
        self.info(colfmt.format(typewidth, hostwidth, **header))

        colfmtstopped = "{name:<12} {type:<{0}} {host:<{1}} {status}"

        for data in results.get_node_data():
            node_info = data[2]
            if node_info["pid"]:
                mycolfmt = colfmt
            else:
                mycolfmt = colfmtstopped

            self.info(mycolfmt.format(typewidth, hostwidth, **node_info))

            # Return status code of True only if all nodes are running
            if node_info["status"] != "running":
                success = False

        return success

    def _do_top_once(self, args):
        results = self.broctl.top(args)

        typewidth = 7
        hostwidth = 16
        data = results.get_node_data()
        proclist = data[0][2]["procs"]
        if proclist[0]["type"] == "standalone":
            # In standalone mode, we need a wider "type" column.
            typewidth = 10
            hostwidth = 13

        lines = ["%-12s %-*s %-*s %-7s %-7s %-6s %-4s %-5s %s" % ("Name",
                typewidth, "Type", hostwidth, "Host", "Pid", "Proc", "VSize",
                "Rss", "Cpu", "Cmd")]
        for data in results.get_node_data():
            proclist = data[2]["procs"]
            for top_info in proclist:
                msg = ["%-12s" % top_info["name"]]
                msg.append("%-*s" % (typewidth, top_info["type"]))
                msg.append("%-*s" % (hostwidth, top_info["host"]))
                if top_info["error"]:
                    msg.append("<%s>" % top_info["error"])
                else:
                    msg.append("%-7s" % top_info["pid"])
                    msg.append("%-7s" % top_info["proc"])
                    msg.append("%-6s" % util.number_unit_str(top_info["vsize"]))
                    msg.append("%-4s" % util.number_unit_str(top_info["rss"]))
                    msg.append("%3s%% " % top_info["cpu"])
                    msg.append("%s" % top_info["cmd"])

                lines.append(" ".join(msg))

        return (results.ok, lines)

    def do_top(self, args):
        """- [<nodes>]

        For each of the nodes, prints the status of the two Bro
        processes (parent process and child process) in a *top*-like
        format, including CPU usage and memory consumption. If
        executed interactively, the display is updated frequently
        until key ``q`` is pressed. If invoked non-interactively, the
        status is printed only once."""

        if not self.interactive:
            success, lines = self._do_top_once(args)
            for line in lines:
                self.info(line)

            return success

        utilcurses.enterCurses()
        utilcurses.clearScreen()

        count = 0

        while utilcurses.getCh() != "q":
            if count % 10 == 0:
                success, lines = self._do_top_once(args)
                utilcurses.clearScreen()
                utilcurses.printLines(lines)
            time.sleep(.1)
            count += 1

        utilcurses.leaveCurses()

        return success

    def do_diag(self, args):
        """- [<nodes>]

        If a node has terminated unexpectedly, this command prints a (somewhat
        cryptic) summary of its final state including excerpts of any
        stdout/stderr output, resource usage, and also a stack backtrace if a
        core dump is found. The same information is sent out via mail when a
        node is found to have crashed (the "crash report"). While the
        information is mainly intended for debugging, it can also help to find
        misconfigurations (which are usually, but not always, caught by the
        check_ command)."""

        results = self.broctl.diag(node_list=args)

        for (node, success, output) in results.get_node_output():
            self.info("[%s]" % node)
            for line in output:
                self.info(line)

        return results.ok

    def do_cron(self, args):
        """- [enable|disable|?] | [--no-watch]

        This command has two modes of operation. Without arguments (or just
        ``--no-watch``), it performs a set of maintenance tasks, including
        the logging of various statistical information, expiring old log
        files, checking for dead hosts, and restarting nodes which terminated
        unexpectedly (the latter can be suppressed with the ``--no-watch``
        option if no auto-restart is desired). This mode is intended to be
        executed regularly via *cron*, as described in the installation
        instructions. While not intended for interactive use, no harm will be
        caused by executing the command manually: all the maintenance tasks
        will then just be performed one more time.

        The second mode is for interactive usage and determines if the regular
        tasks are indeed performed when ``broctl cron`` is executed. In other
        words, even with ``broctl cron`` in your crontab, you can still
        temporarily disable it by running ``cron disable``, and
        then later reenable with ``cron enable``. This can be helpful while
        working, e.g., on the BroControl configuration and ``cron`` would
        interfere with that. ``cron ?`` can be used to query the current state.
        """

        watch = True

        if args == "--no-watch":
            watch = False
        elif args:
            if args == "enable":
                self.broctl.setcronenabled(True)
            elif args == "disable":
                self.broctl.setcronenabled(False)
            elif args == "?":
                results = self.broctl.cronenabled()
                self.info("cron " + (results and "enabled" or "disabled"))
            else:
                self.error("invalid cron argument")
                return False

            return True

        self.broctl.cron(watch)

        return True


    def do_check(self, args):
        """- [<nodes>]

        Verifies a modified configuration in terms of syntactical correctness
        (most importantly correct syntax in policy scripts). This command
        should be executed for each configuration change *before*
        install_ is used to put the change into place.
        The ``check`` command uses the policy files as found in SitePolicyPath_
        to make sure they compile correctly. If they do, install_
        will then copy them over to an internal place from where the nodes
        will read them at the next start_. This approach
        ensures that new errors in a policy script will not affect currently
        running nodes, even when one or more of them needs to be restarted."""

        results = self.broctl.check(node_list=args)

        for (node, success, output) in results.get_node_output():
            if success:
                self.info("%s scripts are ok." % node)
            else:
                self.info("%s scripts failed." % node)
                self.info("\n".join(output))

        return results.ok

    def do_cleanup(self, args):
        """- [--all] [<nodes>]

        Clears the nodes' spool directories (if they are not running
        currently). This implies that their persistent state is flushed. Nodes
        that were crashed are reset into *stopped* state. If ``--all`` is
        specified, this command also removes the content of the node's
        TmpDir_, in particular deleteing any data
        potentially saved there for reference from previous crashes.
        Generally, if you want to reset the installation back into a clean
        state, you can first stop_ all nodes, then execute
        ``cleanup --all``, and finally start_ all nodes
        again."""

        cleantmp = False
        if args.startswith("--all"):
            args = args[5:]
            cleantmp = True

        self.info("cleaning up nodes ...")

        results = self.broctl.cleanup(cleantmp=cleantmp, node_list=args)

        return results.ok

    def do_capstats(self, args):
        """- [<nodes>] [<interval>]

        Determines the current load on the network interfaces monitored by
        each of the given worker nodes. The load is measured over the
        specified interval (in seconds), or by default over 10 seconds. This
        command uses the :doc:`capstats<../../components/capstats/README>`
        tool, which is installed along with ``broctl``."""

        interval = 10
        args = args.split()

        if args:
            try:
                interval = max(1, int(args[-1]))
                args = args[0:-1]
            except ValueError:
                pass

        args = " ".join(args)

        def outputcapstats(tag, data):
            def output_one(tag, vals):
                return "%-21s %-10s %s" % (tag, vals.get("kpps", ""), vals.get("mbps", ""))

            self.info("\n%-21s %-10s %-10s (%ds average)\n%s" % (tag, "kpps", "mbps", interval, "-" * 40))

            totals = None

            for (node, success, vals) in data:

                if not success:
                    self.info(vals["output"])
                    continue

                if str(node) != "$total":
                    hostnetif = "%s/%s" % (node.host, node.interface)
                    self.info(output_one(hostnetif, vals))
                else:
                    totals = vals

            if totals:
                self.info("")
                self.info(output_one("Total", totals))
                self.info("")

        results = self.broctl.capstats(interval=interval, node_list=args)

        outputcapstats("Interface", results.get_node_data())

        return results.ok

    def do_update(self, args):
        """- [<nodes>]

        After a change to Bro policy scripts, this command updates the Bro
        processes on the given nodes *while they are running* (i.e., without
        requiring a restart_). However, such dynamic
        updates work only for a *subset* of Bro's full configuration. The
        following changes can be applied on the fly:  The value of all
        const variables defined with the ``&redef`` attribute can be changed.
        More extensive script changes are not possible during runtime and
        always require a restart; if you change more than just the values of
        ``&redef``-able consts and still issue ``update``, the results are
        undefined and can lead to crashes. Also note that before running
        ``update``, you still need to do an install_ (preferably after
        check_), as otherwise ``update`` will not see the changes and it will
        resend the old configuration."""

        results = self.broctl.update(node_list=args)

        return results.ok

    def do_df(self, args):
        """- [<nodes>]

        Reports the amount of disk space available on the nodes. Shows only
        paths relevant to the broctl installation."""

        results = self.broctl.df(node_list=args)

        self.info("%27s  %15s  %-5s  %-5s  %-5s" % ("", "", "total", "avail", "capacity"))
        for (node, success, dfs) in results.get_node_data():
            for key, diskinfo in dfs.items():
                if key == "FAIL":
                    self.info("df helper failed on %s: %s" % (node, diskinfo))
                    continue
                nodehost = "%s/%s" % (node.name, node.host)
                self.info("%27s  %15s  %-5s  %-5s  %-5.1f%%" % (nodehost,
                    diskinfo.fs, util.number_unit_str(diskinfo.total),
                    util.number_unit_str(diskinfo.available), diskinfo.percent))

        return results.ok

    def do_print(self, args):
        """- <id> [<nodes>]

        Reports the *current* live value of the given Bro script ID on all of
        the specified nodes (which obviously must be running). This can for
        example be useful to (1) check that policy scripts are working as
        expected, or (2) confirm that configuration changes have in fact been
        applied.  Note that IDs defined inside a Bro namespace must be
        prefixed with ``<namespace>::`` (e.g.,
        ``print HTTP::mime_types_extensions`` to print the corresponding
        table from ``file-ident.bro``)."""

        args = args.split()
        try:
            id = args[0]
            args = " ".join(args[1:])
        except IndexError:
            raise SyntaxError("no id given to print")

        results = self.broctl.print_id(id=id, node_list=args)

        for (node, success, args) in results.get_node_output():
            if success:
                self.info("%12s   %s = %s" % (node, args[0], args[1]))
            else:
                self.info("%12s   <error: %s>" % (node, args))

        return results.ok

    def do_peerstatus(self, args):
        """- [<nodes>]

        Primarily for debugging, ``peerstatus`` reports statistics about the
        network connections cluster nodes are using to communicate with other
        nodes."""

        results = self.broctl.peerstatus(node_list=args)

        for (node, success, msg) in results.get_node_output():
            if success:
                self.info("%11s\n%s" % (node, msg))
            else:
                self.info("%11s   <error: %s>" % (node, msg))

        return results.ok

    def do_netstats(self, args):
        """- [<nodes>]

        Queries each of the nodes for their current counts of captured and
        dropped packets."""

        results = self.broctl.netstats(node_list=args)

        for (node, success, msg) in results.get_node_output():
            if success:
                self.info("%11s: %s" % (node, msg))
            else:
                self.info("%11s: <error: %s>" % (node, msg))

        return results.ok

    def do_exec(self, args):
        """- <command line>

        Executes the given Unix shell command line on all hosts configured to
        run at least one Bro instance. This is handy to quickly perform an
        action across all systems."""

        results = self.broctl.execute(cmd=args)

        for node, success, output in results.get_node_output():
            out = "\n> ".join(output)
            self.info("[%s/%s] %s\n> %s" % (node.name, node.host, (success and " " or "error"), out))

        return results.ok

    def do_scripts(self, args):
        """- [-c] [<nodes>]

        Primarily for debugging Bro configurations, the ``scripts``
        command lists all the Bro scripts loaded by each of the nodes in the
        order they will be parsed by the node at startup.
        If ``-c`` is given, the command operates as check_ does: it reads
        the policy files from their *original* location, not the copies
        installed by install_. The latter option is useful to check a
        not yet installed configuration."""

        check = False

        args = args.split()

        try:
            while args[0].startswith("-"):

                opt = args[0]

                if opt == "-c":
                    # Check non-installed policies.
                    check = True
                else:
                    raise SyntaxError("Unknown option: %s" % opt)

                args = args[1:]

        except IndexError:
            pass

        args = " ".join(args)

        results = self.broctl.scripts(check=check, node_list=args)

        for (node, success, output) in results.get_node_output():
            if success:
                self.info("%s scripts are ok." % node)
                for line in output:
                    self.info("  %s" % line)
            else:
                self.info("%s scripts failed." % node)
                self.info("\n".join(output))

        return results.ok

    def do_process(self, args):
        """- <trace> [options] [-- <scripts>]

        Runs Bro offline on a given trace file using the same configuration as
        when running live. It does, however, use the potentially
        not-yet-installed policy files in SitePolicyPath_ and disables log
        rotation. Additional Bro command line flags and scripts can
        be given (each argument after a ``--`` argument is interpreted as
        a script).

        Upon completion, the command prints a path where the log files can be
        found. Subsequent runs of this command may delete these logs.

        In cluster mode, Bro is run with *both* manager and worker scripts
        loaded into a single instance. While that doesn't fully reproduce the
        live setup, it is often sufficient for debugging analysis scripts.
        """
        options = []
        scripts = []
        trace = None
        in_scripts = False

        for arg in args.split():

            if not trace:
                trace = arg
                continue

            if arg == "--":
                if in_scripts:
                    raise SyntaxError("cannot parse arguments")

                in_scripts = True
                continue

            if not in_scripts:
                options += [arg]

            else:
                scripts += [arg]

        if not trace:
            raise SyntaxError("no trace file given")

        results = self.broctl.process(trace, options, scripts)

        return results.ok

    def completedefault(self, text, line, begidx, endidx):
        # Commands that take a "<nodes>" argument.
        nodes_cmds = ["capstats", "check", "cleanup", "df", "diag", "netstats",
                      "print", "restart", "start", "status", "stop", "top",
                      "update", "peerstatus", "scripts"]

        args = line.split()

        if not args or args[0] not in nodes_cmds:
            return []

        nodes = ["manager", "workers", "proxies", "all"] + self.node_names

        return [n for n in nodes if n.startswith(text)]

    def do_help(self, args):
        """Prints a brief summary of all commands understood by the shell."""

        plugin_help = ""

        for (cmd, args, descr) in self.broctl.plugins.allCustomCommands():
            if not plugin_help:
                plugin_help += "\nCommands provided by plugins:\n\n"

            if args:
                cmd = "%s %s" % (cmd, args)

            plugin_help += "  %-32s - %s\n" % (cmd, descr)

        self.info(
"""
BroControl Version %s

  capstats [<nodes>] [<secs>]      - Report interface statistics with capstats
  check [<nodes>]                  - Check configuration before installing it
  cleanup [--all] [<nodes>]        - Delete working dirs (flush state) on nodes
  config                           - Print broctl configuration
  cron [--no-watch]                - Perform jobs intended to run from cron
  cron enable|disable|?            - Enable/disable \"cron\" jobs
  deploy                           - Check, install, and restart
  df [<nodes>]                     - Print nodes' current disk usage
  diag [<nodes>]                   - Output diagnostics for nodes
  exec <shell cmd>                 - Execute shell command on all hosts
  exit                             - Exit shell
  install                          - Update broctl installation/configuration
  netstats [<nodes>]               - Print nodes' current packet counters
  nodes                            - Print node configuration
  peerstatus [<nodes>]             - Print status of nodes' remote connections
  print <id> [<nodes>]             - Print values of script variable at nodes
  process <trace> [<op>] [-- <sc>] - Run Bro (with options and scripts) on trace
  quit                             - Exit shell
  restart [--clean] [<nodes>]      - Stop and then restart processing
  scripts [-c] [<nodes>]           - List the Bro scripts the nodes will load
  start [<nodes>]                  - Start processing
  status [<nodes>]                 - Summarize node status
  stop [<nodes>]                   - Stop processing
  top [<nodes>]                    - Show Bro processes ala top
  update [<nodes>]                 - Update configuration of nodes on the fly
  %s""" % (version.VERSION, plugin_help))

def main():
    # Undocumented option to print the documentation.
    if len(sys.argv) == 3 and sys.argv[1] == "--print-doc":
        from BroControl import printdoc
        printdoc.print_broctl_docs(sys.argv[2], BroCtlCmdLoop)
        return 0

    interactive = True
    if len(sys.argv) > 1:
        interactive = False

    cmd = ""
    if len(sys.argv) == 2:
        cmd = sys.argv[1]

    try:
        loop = BroCtlCmdLoop(BroCtl, interactive, cmd)
    except Exception as e:
        print("Error: %s" % e, file=sys.stderr)
        return 1

    if len(sys.argv) > 1:
        cmdline = " ".join(sys.argv[1:])
        loop.precmd(cmdline)
        try:
            cmdsuccess = loop.onecmd(cmdline)
        except Exception as e:
            cmdsuccess = False
            print("Error: %s" % e, file=sys.stderr)
        except KeyboardInterrupt:
            cmdsuccess = False

        loop.postcmd(False, cmdline)
    else:
        try:
            cmdsuccess = loop.cmdloop("\nWelcome to BroControl %s\n\nType \"help\" for help.\n" % version.VERSION)
        except KeyboardInterrupt:
            cmdsuccess = False

    loop.finish()

    return not cmdsuccess

if __name__ == "__main__":
    sys.exit(main())


Zerion Mini Shell 1.0