# Copyright (C) 2010-2013 Claudio Guarnieri.
# Copyright (C) 2014-2016 Cuckoo Foundation.
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import os
import getpass
import logging
import subprocess

from lib.cuckoo.common.abstracts import Auxiliary
from lib.cuckoo.common.constants import CUCKOO_ROOT, CUCKOO_GUEST_PORT
from lib.cuckoo.common.exceptions import CuckooOperationalError

log = logging.getLogger(__name__)

class Sniffer(Auxiliary):
    def __init__(self):
        Auxiliary.__init__(self)
        self.proc = None

    def start(self):
        if not self.machine.interface:
            log.error("Network interface not defined, network capture aborted")
            return

        # Handle special pcap dumping options.
        if "nictrace" in self.machine.options:
            return

        tcpdump = self.options.get("tcpdump", "/usr/sbin/tcpdump")
        bpf = self.options.get("bpf", "")
        file_path = os.path.join(CUCKOO_ROOT, "storage", "analyses",
                                 "%s" % self.task.id, "dump.pcap")

        if not os.path.exists(tcpdump):
            log.error("Tcpdump does not exist at path \"%s\", network "
                      "capture aborted", tcpdump)
            return

        # TODO: this isn't working. need to fix.
        # mode = os.stat(tcpdump)[stat.ST_MODE]
        # if (mode & stat.S_ISUID) == 0:
        #    log.error("Tcpdump is not accessible from this user, "
        #              "network capture aborted")
        #    return

        pargs = [
            tcpdump, "-U", "-q", "-s", "0", "-n",
            "-i", self.machine.interface,
        ]

        # Trying to save pcap with the same user which cuckoo is running.
        try:
            user = getpass.getuser()
            pargs.extend(["-Z", user])
        except:
            pass

        pargs.extend(["-w", file_path])
        pargs.extend(["host", self.machine.ip])

        if self.task.options.get("sniffer.debug") != "1":
            # Do not capture Agent traffic.
            pargs.extend([
                "and", "not", "(",
                "dst", "host", self.machine.ip, "and",
                "dst", "port", str(CUCKOO_GUEST_PORT),
                ")", "and", "not", "(",
                "src", "host", self.machine.ip, "and",
                "src", "port", str(CUCKOO_GUEST_PORT),
                ")",
            ])

            # Do not capture ResultServer traffic.
            pargs.extend([
                "and", "not", "(",
                "dst", "host", self.machine.resultserver_ip, "and",
                "dst", "port", self.machine.resultserver_port,
                ")", "and", "not", "(",
                "src", "host", self.machine.resultserver_ip, "and",
                "src", "port", self.machine.resultserver_port,
                ")",
            ])

            if bpf:
                pargs.extend(["and", "(", bpf, ")"])

        try:
            self.proc = subprocess.Popen(
                pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE
            )
        except (OSError, ValueError):
            log.exception(
                "Failed to start sniffer (interface=%s, host=%s, pcap=%s)",
                self.machine.interface, self.machine.ip, file_path,
            )
            return

        log.info(
            "Started sniffer with PID %d (interface=%s, host=%s, pcap=%s)",
            self.proc.pid, self.machine.interface, self.machine.ip, file_path,
        )

    def stop(self):
        """Stop sniffing.
        @return: operation status.
        """
        # The tcpdump process was never started in the first place.
        if not self.proc:
            return

        # The tcpdump process has already quit, generally speaking this
        # indicates an error such as "permission denied".
        if self.proc.poll():
            out, err = self.proc.communicate()
            raise CuckooOperationalError(
                "Error running tcpdump to sniff the network traffic during "
                "the analysis; stdout = %r and stderr = %r. Did you enable "
                "the extra capabilities to allow running tcpdump as non-root "
                "user and disable AppArmor properly (only applies to Ubuntu)?"
                % (out, err)
            )

        try:
            self.proc.terminate()
        except:
            try:
                if not self.proc.poll():
                    log.debug("Killing sniffer")
                    self.proc.kill()
            except OSError as e:
                log.debug("Error killing sniffer: %s. Continue", e)
            except Exception as e:
                log.exception("Unable to stop the sniffer with pid %d: %s",
                              self.proc.pid, e)

@@ -3531,24 +3531,811 @@
 %0A        )%0A%0A
+    def _check_output(self, out, err):%0A        if out:%0A            raise CuckooOperationalError(%0A                %22Potential error while running tcpdump, did not expect %22%0A                %22standard output, got: %25r.%22 %25 out%0A            )%0A%0A        err_whitelist = (%0A            %22packets captured%22,%0A            %22packets received by filter%22,%0A            %22packets dropped by kernel%22,%0A        )%0A%0A        for line in err.split(%22%5Cn%22):%0A            if not line or line.startswith(%22tcpdump: listening on %22):%0A                continue%0A%0A            if line.endswith(err_whitelist):%0A                continue%0A%0A            raise CuckooOperationalError(%0A                %22Potential error while running tcpdump, did not expect %22%0A                %22the following standard error output: %25r.%22 %25 line%0A            )%0A%0A
     def stop
@@ -5596,12 +5596,153 @@
 roc.pid, e)%0A
+%0A        # Ensure expected output was received from tcpdump.%0A        out, err = self.proc.communicate()%0A        self._check_output(out, err)%0A

whitelist known tcpdump output and report unknown output as an error