Example #1
0
class Device:
    def __init__(self, name, share_internet, mac, ip, netmask, interval, priority=10, scantime=15, search_order=0, max_tries=0, **kwargs):
        self.name = name
        self.status = StatusFile(f'/root/.bt-tether-{name}')
        self.status.update()
        self.tries = 0
        self.network = None

        self.max_tries = max_tries
        self.search_order = search_order
        self.share_internet = share_internet
        self.ip = ip
        self.netmask = netmask
        self.interval = interval
        self.mac = mac
        self.scantime = scantime
        self.priority = priority

    def connected(self):
        """
        Checks if device is connected
        """
        return self.network and BTNap.prop_get(self.network, 'Connected')

    def interface(self):
        """
        Returns the interface name or None
        """
        if not self.connected():
            return None
        return BTNap.prop_get(self.network, 'Interface')
class Watchdog(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '1.0.0'
    __license__ = 'GPL3'
    __description__ = 'Restart pwnagotchi when blindbug is detected.'
    __defaults__ = {
        'enabled': False,
    }

    def __init__(self):
        self.options = dict()
        self.pattern = re.compile(
            r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
        self.status = StatusFile('/root/.pwnagotchi-watchdog')
        self.status.update()

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        logging.info("Watchdog plugin loaded.")

    def on_epoch(self, agent, epoch, epoch_data):
        if self.status.newer_then_minutes(5):
            return

        data_keys = ['num_deauths', 'num_associations', 'num_handshakes']
        has_interactions = any(
            [epoch_data[x] for x in data_keys if x in epoch_data])

        if has_interactions:
            return

        epoch_duration = epoch_data['duration_secs']

        # get last 10 lines
        last_lines = ''.join(
            list(
                TextIOWrapper(
                    subprocess.Popen([
                        'journalctl', '-n10', '-k', '--since',
                        f"{epoch_duration} seconds ago"
                    ],
                                     stdout=subprocess.PIPE).stdout))[-10:])

        if len(self.pattern.findall(last_lines)) >= 5:
            display = agent.view()
            display.set('status', 'Blind-Bug detected. Restarting.')
            display.update(force=True)
            logging.info('[WATCHDOG] Blind-Bug detected. Restarting.')
            mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
            import pwnagotchi
            pwnagotchi.reboot(mode=mode)
Example #3
0
class WpaSec(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.1.0'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'

    def __init__(self):
        self.ready = False
        self.lock = Lock()
        try:
            self.report = StatusFile('/root/.wpa_sec_uploads',
                                     data_format='json')
        except JSONDecodeError as json_err:
            os.remove("/root/.wpa_sec_uploads")
            self.report = StatusFile('/root/.wpa_sec_uploads',
                                     data_format='json')
        self.options = dict()
        self.skip = list()

    def _upload_to_wpasec(self, path, timeout=30):
        """
        Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
        """
        with open(path, 'rb') as file_to_upload:
            cookie = {'key': self.options['api_key']}
            payload = {'file': file_to_upload}

            try:
                result = requests.post(self.options['api_url'],
                                       cookies=cookie,
                                       files=payload,
                                       timeout=timeout)
                if ' already submitted' in result.text:
                    logging.warning("%s was already submitted.", path)
            except requests.exceptions.RequestException as req_e:
                raise req_e

    def _download_from_wpasec(self, output, timeout=30):
        """
        Downloads the results from wpasec and safes them to output

        Output-Format: bssid, station_mac, ssid, password
        """
        api_url = self.options['api_url']
        if not api_url.endswith('/'):
            api_url = f"{api_url}/"
        api_url = f"{api_url}?api&dl=1"

        cookie = {'key': self.options['api_key']}
        try:
            result = requests.get(api_url, cookies=cookie, timeout=timeout)
            with open(output, 'wb') as output_file:
                output_file.write(result.content)
        except requests.exceptions.RequestException as req_e:
            raise req_e
        except OSError as os_e:
            raise os_e

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        if 'api_key' not in self.options or ('api_key' in self.options and
                                             self.options['api_key'] is None):
            logging.error(
                "WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org"
            )
            return

        if 'api_url' not in self.options or ('api_url' in self.options and
                                             self.options['api_url'] is None):
            logging.error(
                "WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured."
            )
            return

        self.ready = True

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """
        with self.lock:
            if self.ready:
                config = agent.config()
                display = agent.view()
                reported = self.report.data_field_or('reported',
                                                     default=list())

                handshake_dir = config['bettercap']['handshakes']
                handshake_filenames = os.listdir(handshake_dir)
                handshake_paths = [
                    os.path.join(handshake_dir, filename)
                    for filename in handshake_filenames
                    if filename.endswith('.pcap')
                ]
                handshake_new = set(handshake_paths) - set(reported) - set(
                    self.skip)

                if handshake_new:
                    logging.info(
                        "WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org"
                    )

                    for idx, handshake in enumerate(handshake_new):
                        display.set(
                            'status',
                            f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})"
                        )
                        display.update(force=True)
                        try:
                            self._upload_to_wpasec(handshake)
                            reported.append(handshake)
                            self.report.update(data={'reported': reported})
                            logging.info("WPA_SEC: Successfully uploaded %s",
                                         handshake)
                        except requests.exceptions.RequestException as req_e:
                            self.skip.append(handshake)
                            logging.error("WPA_SEC: %s", req_e)
                            continue
                        except OSError as os_e:
                            logging.error("WPA_SEC: %s", os_e)
                            continue

                if 'download_results' in self.options and self.options[
                        'download_results']:
                    cracked_file = os.path.join(handshake_dir,
                                                'wpa-sec.cracked.potfile')
                    if os.path.exists(cracked_file):
                        last_check = datetime.fromtimestamp(
                            os.path.getmtime(cracked_file))
                        if last_check is not None and (
                            (datetime.now() - last_check).seconds /
                            (60 * 60)) < 1:
                            return

                    try:
                        self._download_from_wpasec(
                            os.path.join(handshake_dir,
                                         'wpa-sec.cracked.potfile'))
                        logging.info("WPA_SEC: Downloaded cracked passwords.")
                    except requests.exceptions.RequestException as req_e:
                        logging.debug("WPA_SEC: %s", req_e)
                    except OSError as os_e:
                        logging.debug("WPA_SEC: %s", os_e)
Example #4
0
class Grid(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '1.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
                      'networks to api.pwnagotchi.ai '

    def __init__(self):
        self.options = dict()
        self.report = StatusFile('/root/.api-report.json', data_format='json')

        self.unread_messages = 0
        self.total_messages = 0

    def is_excluded(self, what):
        for skip in self.options['exclude']:
            skip = skip.lower()
            what = what.lower()
            if skip in what or skip.replace(':', '') in what:
                return True
        return False

    def on_loaded(self):
        logging.info("grid plugin loaded.")

    def set_reported(self, reported, net_id):
        if net_id not in reported:
            reported.append(net_id)
        self.report.update(data={'reported': reported})

    def check_inbox(self, agent):
        logging.debug("checking mailbox ...")
        messages = grid.inbox()
        self.total_messages = len(messages)
        self.unread_messages = len(
            [m for m in messages if m['seen_at'] is None])

        if self.unread_messages:
            plugins.on('unread_inbox', self.unread_messages)
            logging.debug("[grid] unread:%d total:%d" %
                          (self.unread_messages, self.total_messages))
            agent.view().on_unread_messages(self.unread_messages,
                                            self.total_messages)

    def check_handshakes(self, agent):
        logging.debug("checking pcaps")

        pcap_files = glob.glob(
            os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
        num_networks = len(pcap_files)
        reported = self.report.data_field_or('reported', default=[])
        num_reported = len(reported)
        num_new = num_networks - num_reported

        if num_new > 0:
            if self.options['report']:
                logging.info("grid: %d new networks to report" % num_new)
                logging.debug("self.options: %s" % self.options)
                logging.debug("  exclude: %s" % self.options['exclude'])

                for pcap_file in pcap_files:
                    net_id = os.path.basename(pcap_file).replace('.pcap', '')
                    if net_id not in reported:
                        if self.is_excluded(net_id):
                            logging.debug(
                                "skipping %s due to exclusion filter" %
                                pcap_file)
                            self.set_reported(reported, net_id)
                            continue

                        essid, bssid = parse_pcap(pcap_file)
                        if bssid:
                            if self.is_excluded(essid) or self.is_excluded(
                                    bssid):
                                logging.debug(
                                    "not reporting %s due to exclusion filter"
                                    % pcap_file)
                                self.set_reported(reported, net_id)
                            else:
                                if grid.report_ap(essid, bssid):
                                    self.set_reported(reported, net_id)
                                time.sleep(1.5)
                        else:
                            logging.warning("no bssid found?!")
            else:
                logging.debug("grid: reporting disabled")

    def on_internet_available(self, agent):
        logging.debug("internet available")

        try:
            grid.update_data(agent.last_session)
        except Exception as e:
            logging.error("error connecting to the pwngrid-peer service: %s" %
                          e)
            logging.debug(e, exc_info=True)
            return

        try:
            self.check_inbox(agent)
        except Exception as e:
            logging.error("[grid] error while checking inbox: %s" % e)
            logging.debug(e, exc_info=True)

        try:
            self.check_handshakes(agent)
        except Exception as e:
            logging.error("[grid] error while checking pcaps: %s" % e)
            logging.debug(e, exc_info=True)
Example #5
0
class OnlineHashCrack(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.0'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'

    def __init__(self):
        self.ready = False
        self.report = StatusFile('/root/.ohc_uploads', data_format='json')
        self.skip = list()

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        if 'email' not in self.options or ('email' in self.options
                                           and self.options['email'] is None):
            logging.error(
                "OHC: Email isn't set. Can't upload to onlinehashcrack.com")
            return

        self.ready = True

    def _upload_to_ohc(self, path, timeout=30):
        """
        Uploads the file to onlinehashcrack.com
        """
        with open(path, 'rb') as file_to_upload:
            data = {'email': self.options['email']}
            payload = {'file': file_to_upload}

            try:
                result = requests.post('https://api.onlinehashcrack.com',
                                       data=data,
                                       files=payload,
                                       timeout=timeout)
                if 'already been sent' in result.text:
                    logging.warning(f"{path} was already uploaded.")
            except requests.exceptions.RequestException as e:
                logging.error(
                    f"OHC: Got an exception while uploading {path} -> {e}")
                raise e

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """
        if self.ready:
            display = agent.view()
            config = agent.config()
            reported = self.report.data_field_or('reported', default=list())

            handshake_dir = config['bettercap']['handshakes']
            handshake_filenames = os.listdir(handshake_dir)
            handshake_paths = [
                os.path.join(handshake_dir, filename)
                for filename in handshake_filenames
                if filename.endswith('.pcap')
            ]
            handshake_new = set(handshake_paths) - set(reported) - set(
                self.skip)

            if handshake_new:
                logging.info(
                    "OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com"
                )

                for idx, handshake in enumerate(handshake_new):
                    display.set(
                        'status',
                        f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})"
                    )
                    display.update(force=True)
                    try:
                        self._upload_to_ohc(handshake)
                        reported.append(handshake)
                        self.report.update(data={'reported': reported})
                        logging.info(f"OHC: Successfully uploaded {handshake}")
                    except requests.exceptions.RequestException as req_e:
                        self.skip.append(handshake)
                        logging.error("OHC: %s", req_e)
                        continue
                    except OSError as os_e:
                        self.skip.append(handshake)
                        logging.error("OHC: %s", os_e)
                        continue
Example #6
0
class SessionStats(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '0.1.0'
    __license__ = 'GPL3'
    __description__ = 'This plugin displays stats of the current session.'

    def __init__(self):
        self.lock = threading.Lock()
        self.options = dict()
        self.stats = dict()
        self.clock = GhettoClock()

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        # this has to happen in "loaded" because the options are not yet
        # available in the __init__
        os.makedirs(self.options['save_directory'], exist_ok=True)
        self.session_name = "stats_{}.json".format(
            self.clock.now().strftime("%Y_%m_%d_%H_%M"))
        self.session = StatusFile(os.path.join(self.options['save_directory'],
                                               self.session_name),
                                  data_format='json')
        logging.info("Session-stats plugin loaded.")

    def on_epoch(self, agent, epoch, epoch_data):
        """
        Save the epoch_data to self.stats
        """
        with self.lock:
            self.stats[self.clock.now().strftime("%H:%M:%S")] = epoch_data
            self.session.update(data={'data': self.stats})

    @staticmethod
    def extract_key_values(data, subkeys):
        result = dict()
        result['values'] = list()
        result['labels'] = subkeys
        for plot_key in subkeys:
            v = [[ts, d[plot_key]] for ts, d in data.items()]
            result['values'].append(v)
        return result

    def on_webhook(self, path, request):
        if not path or path == "/":
            return render_template_string(TEMPLATE)

        session_param = request.args.get('session')

        if path == "os":
            extract_keys = [
                'cpu_load',
                'mem_usage',
            ]
        elif path == "temp":
            extract_keys = ['temperature']
        elif path == "wifi":
            extract_keys = [
                'missed_interactions',
                'num_hops',
                'num_peers',
                'tot_bond',
                'avg_bond',
                'num_deauths',
                'num_associations',
                'num_handshakes',
            ]
        elif path == "duration":
            extract_keys = [
                'duration_secs',
                'slept_for_secs',
            ]
        elif path == "reward":
            extract_keys = [
                'reward',
            ]
        elif path == "epoch":
            extract_keys = [
                'active_for_epochs',
            ]
        elif path == "session":
            return jsonify(
                {'files': os.listdir(self.options['save_directory'])})

        with self.lock:
            data = self.stats
            if session_param and session_param != 'Current':
                file_stats = StatusFile(os.path.join(
                    self.options['save_directory'], session_param),
                                        data_format='json')
                data = file_stats.data_field_or('data', default=dict())
            return jsonify(SessionStats.extract_key_values(data, extract_keys))
Example #7
0
class OnlineHashCrack(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'

    def __init__(self):
        self.ready = False
        try:
            self.report = StatusFile('/root/.ohc_uploads', data_format='json')
        except JSONDecodeError:
            os.remove('/root/.ohc_uploads')
            self.report = StatusFile('/root/.ohc_uploads', data_format='json')
        self.skip = list()
        self.lock = Lock()

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        if 'email' not in self.options or ('email' in self.options
                                           and not self.options['email']):
            logging.error(
                "OHC: Email isn't set. Can't upload to onlinehashcrack.com")
            return

        if 'whitelist' not in self.options:
            self.options['whitelist'] = list()

        self.ready = True
        logging.info("OHC: OnlineHashCrack plugin loaded.")

    def _upload_to_ohc(self, path, timeout=30):
        """
        Uploads the file to onlinehashcrack.com
        """
        with open(path, 'rb') as file_to_upload:
            data = {'email': self.options['email']}
            payload = {'file': file_to_upload}

            try:
                result = requests.post('https://api.onlinehashcrack.com',
                                       data=data,
                                       files=payload,
                                       timeout=timeout)
                if 'already been sent' in result.text:
                    logging.warning(f"{path} was already uploaded.")
            except requests.exceptions.RequestException as e:
                logging.error(
                    f"OHC: Got an exception while uploading {path} -> {e}")
                raise e

    def _download_cracked(self, save_file, timeout=120):
        """
        Downloads the cracked passwords and saves them

        returns the number of downloaded passwords
        """
        try:
            s = requests.Session()
            dashboard = s.get(self.options['dashboard'], timeout=timeout)
            result = s.get('https://www.onlinehashcrack.com/wpa-exportcsv',
                           timeout=timeout)
            result.raise_for_status()
            with open(save_file, 'wb') as output_file:
                output_file.write(result.content)
        except requests.exceptions.RequestException as req_e:
            raise req_e
        except OSError as os_e:
            raise os_e

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """

        if not self.ready or self.lock.locked():
            return

        with self.lock:
            display = agent.view()
            config = agent.config()
            reported = self.report.data_field_or('reported', default=list())
            handshake_dir = config['bettercap']['handshakes']
            handshake_filenames = os.listdir(handshake_dir)
            handshake_paths = [
                os.path.join(handshake_dir, filename)
                for filename in handshake_filenames
                if filename.endswith('.pcap')
            ]
            # pull out whitelisted APs
            handshake_paths = remove_whitelisted(handshake_paths,
                                                 self.options['whitelist'])
            handshake_new = set(handshake_paths) - set(reported) - set(
                self.skip)
            if handshake_new:
                logging.info(
                    "OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com"
                )
                for idx, handshake in enumerate(handshake_new):
                    display.set(
                        'status',
                        f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})"
                    )
                    display.update(force=True)
                    try:
                        self._upload_to_ohc(handshake)
                        if handshake not in reported:
                            reported.append(handshake)
                            self.report.update(data={'reported': reported})
                            logging.info(
                                f"OHC: Successfully uploaded {handshake}")
                    except requests.exceptions.RequestException as req_e:
                        self.skip.append(handshake)
                        logging.error("OHC: %s", req_e)
                        continue
                    except OSError as os_e:
                        self.skip.append(handshake)
                        logging.error("OHC: %s", os_e)
                        continue
            if 'dashboard' in self.options and self.options['dashboard']:
                cracked_file = os.path.join(handshake_dir,
                                            'onlinehashcrack.cracked')
                if os.path.exists(cracked_file):
                    last_check = datetime.fromtimestamp(
                        os.path.getmtime(cracked_file))
                    if last_check is not None and (
                        (datetime.now() - last_check).seconds / (60 * 60)) < 1:
                        return
                try:
                    self._download_cracked(cracked_file)
                    logging.info("OHC: Downloaded cracked passwords.")
                except requests.exceptions.RequestException as req_e:
                    logging.debug("OHC: %s", req_e)
                except OSError as os_e:
                    logging.debug("OHC: %s", os_e)
                if 'single_files' in self.options and self.options[
                        'single_files']:
                    with open(cracked_file, 'r') as cracked_list:
                        for row in csv.DictReader(cracked_list):
                            if row['password']:
                                filename = re.sub(
                                    r'[^a-zA-Z0-9]', '',
                                    row['ESSID']) + '_' + row['BSSID'].replace(
                                        ':', '')
                                if os.path.exists(
                                        os.path.join(handshake_dir,
                                                     filename + '.pcap')):
                                    with open(
                                            os.path.join(
                                                handshake_dir,
                                                filename + '.pcap.cracked'),
                                            'w') as f:
                                        f.write(row['password'])
Example #8
0
class WpaSec(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'

    def __init__(self):
        self.ready = False
        self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
        self.options = dict()
        self.skip = list()

    def _upload_to_wpasec(self, path, timeout=30):
        """
        Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
        """
        with open(path, 'rb') as file_to_upload:
            cookie = {'key': self.options['api_key']}
            payload = {'file': file_to_upload}

            try:
                result = requests.post(self.options['api_url'],
                                       cookies=cookie,
                                       files=payload,
                                       timeout=timeout)
                if ' already submitted' in result.text:
                    logging.warning("%s was already submitted.", path)
            except requests.exceptions.RequestException as req_e:
                raise req_e

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        if 'api_key' not in self.options or ('api_key' in self.options and
                                             self.options['api_key'] is None):
            logging.error(
                "WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org"
            )
            return

        if 'api_url' not in self.options or ('api_url' in self.options and
                                             self.options['api_url'] is None):
            logging.error(
                "WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured."
            )
            return

        self.ready = True

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """
        if self.ready:
            config = agent.config()
            display = agent.view()
            reported = self.report.data_field_or('reported', default=list())

            handshake_dir = config['bettercap']['handshakes']
            handshake_filenames = os.listdir(handshake_dir)
            handshake_paths = [
                os.path.join(handshake_dir, filename)
                for filename in handshake_filenames
                if filename.endswith('.pcap')
            ]
            handshake_new = set(handshake_paths) - set(reported) - set(
                self.skip)

            if handshake_new:
                logging.info(
                    "WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org"
                )

                for idx, handshake in enumerate(handshake_new):
                    display.set(
                        'status',
                        f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})"
                    )
                    display.update(force=True)
                    try:
                        self._upload_to_wpasec(handshake)
                        reported.append(handshake)
                        self.report.update(data={'reported': reported})
                        logging.info("WPA_SEC: Successfully uploaded %s",
                                     handshake)
                    except requests.exceptions.RequestException as req_e:
                        self.skip.append(handshake)
                        logging.error("WPA_SEC: %s", req_e)
                        continue
                    except OSError as os_e:
                        logging.error("WPA_SEC: %s", os_e)
                        continue
Example #9
0
class nextcloud(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '0.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to a nextcloud webdav endpoint'

    def __init__(self):
        self.ready = False
        self.lock = threading.Lock()
        try:
            self.report = StatusFile('/root/.nextcloud_uploads',
                                     data_format='json')
        except JSONDecodeError as json_err:
            os.remove("/root/.nextcloud_uploads")
            self.report = StatusFile('/root/.nextcloud_uploads',
                                     data_format='json')

        self.options = dict()
        self.skip = list()

        self.session = None
        self.full_url = None

    def _make_path(self, dir):
        return f"{self.options['baseurl']}/remote.php/dav/files/{self.options['user']}/{dir}"

    def _make_session(self):
        s = requests.Session()
        s.auth = (self.options["user"], self.options["pass"])

        logging.info("[nextcloud] >> send first req!")

        # check if creds correct
        try:
            r = s.request("PROPFIND", self._make_path("./"))
            logging.info("[nextcloud] >> send first req 2!")

            if r.status_code == 401:
                logging.info("[nextcloud] >> wrong creds!")
                return False
            elif r.status_code == 404:
                logging.info("[nextcloud] >> path does not exist!")
                return False

            self.session = s
        except requests.exceptions.RequestException as e:
            logging.error("nextcloud: Got an exception checking credentials!")
            raise e

    def _make_dir(self, path):
        try:
            r = self.session.request("MKCOL", path)

            if r.status_code == 201:
                print("created new directory")
                return True
            else:
                return False
        except requests.exceptions.RequestException as e:
            logging.error("nextcloud: Got an exception while creating a dir.")
            raise e

    def _exists_dir(self, path):
        try:
            r = self.session.request("PROPFIND", path)

            if r.status_code == 404:
                return False
            else:
                return True
        except requests.exceptions.RequestException as e:
            logging.error(
                "nextcloud: Got an exception while checking if a dir exists.")
            raise e

    def _upload_to_nextcloud(self, path, timeout=30):
        head, tail = os.path.split(path)
        destFile = self.full_url + '/' + tail

        with open(path, 'rb') as fp:
            try:
                r = self.session.put(destFile, data=fp.read())
            except requests.exceptions.RequestException as e:
                logging.error(
                    f"nextcloud: Got an exception while uploading {path} -> {e}"
                )
                raise e

    def on_loaded(self):
        for opt in ['baseurl', 'user', 'pass', 'path']:
            if opt not in self.options or (opt in self.options
                                           and self.options[opt] is None):
                logging.error(f"NEXTCLOUD: Option {opt} is not set.")
                return

        self.ready = True
        logging.info("NEXTCLOUD: Successfully loaded.")

    def on_internet_available(self, agent):
        with self.lock:
            if self.ready:
                config = agent.config()
                display = agent.view()

                reported = self.report.data_field_or('reported',
                                                     default=dict())

                handshake_dir = config['bettercap']['handshakes']
                handshake_filenames = os.listdir(handshake_dir)
                handshake_paths = [
                    os.path.join(handshake_dir, filename)
                    for filename in handshake_filenames
                    if filename.endswith('.pcap')
                ]

                handshake_new = set(handshake_paths) - set(reported) - set(
                    self.skip)

                # filter for new files
                handshake_new = []
                for hs in set(handshake_paths) - set(self.skip):
                    if hs not in reported:
                        handshake_new.append(hs)
                    else:
                        if os.path.getmtime(hs) > reported[hs]:
                            handshake_new.append(hs)

                logging.info(
                    f"[nx]: found {len(handshake_new)} new handshakes")

                if handshake_new:
                    logging.info(
                        "nextcloud: Internet connectivity detected. Uploading new handshakes"
                    )

                    logging.info("[nextcloud] create session START")

                    self._make_session()

                    logging.info("[nextcloud] check for dir Half")

                    self.full_url = self._make_path(f"{self.options['path']}/")

                    logging.info("[nextcloud] create session DONE")
                    logging.info("[nextcloud] check for dir START")

                    if not self._exists_dir(self.full_url):
                        if not self._make_dir(self.full_url):
                            logging.info("[nextcloud] check for dir Fail")
                            logging.error(
                                "nextcloud: couldn't create necessary directory"
                            )
                            return

                    logging.info("[nextcloud] check for dir DONE")

                    for idx, handshake in enumerate(handshake_new):
                        logging.info(
                            f"[nextcloud] uploading hs {handshake}, nr {idx + 1} out of {len(handshake_new)}"
                        )
                        display.set(
                            'status',
                            f"Uploading handshake to nextcloud ({idx + 1}/{len(handshake_new)})"
                        )
                        display.update(force=True)
                        try:
                            logging.info("[nx] upload 1")
                            self._upload_to_nextcloud(handshake)
                            logging.info("[nx] upload 2")
                            reported[handshake] = os.path.getmtime(handshake)
                            logging.info("[nx] upload 3")
                            self.report.update(data={'reported': reported})
                            logging.info("nextcloud: Successfully uploaded %s",
                                         handshake)
                        except requests.exceptions.RequestException as req_e:
                            self.skip.append(handshake)
                            logging.error("nextcloud: %s", req_e)
                            continue
                        except OSError as os_e:
                            logging.error("nextcloud: %s", os_e)
                            continue
class AutoBackup(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '1.0.0'
    __license__ = 'GPL3'
    __description__ = 'This plugin backups files when internet is available.'

    def __init__(self):
        self.ready = False
        self.tries = 0
        self.status = StatusFile('/root/.auto_backup')

    def on_loaded(self):
        for opt in ['files', 'interval', 'commands', 'max_tries']:
            if opt not in self.options or (opt in self.options
                                           and self.options[opt] is None):
                logging.error(f"[auto_backup] Option {opt} is not set.")
                return

        self.ready = True
        logging.info("[auto_backup] Successfully loaded.")

    def on_internet_available(self, agent):
        if not self.ready:
            return

        if self.options[
                'max_tries'] and self.tries >= self.options['max_tries']:
            return

        if self.status.newer_then_days(self.options['interval']):
            return

        # Only backup existing files to prevent errors
        existing_files = list(
            filter(lambda f: os.path.exists(f), self.options['files']))
        files_to_backup = " ".join(existing_files)

        try:
            display = agent.view()

            logging.info("[auto_backup] Backing up...")
            display.set('status', 'Backing up...')
            display.update()

            for cmd in self.options['commands']:
                logging.info(
                    f"[auto_backup] Running {cmd.format(files=files_to_backup)}"
                )
                process = subprocess.Popen(cmd.format(files=files_to_backup),
                                           shell=True,
                                           stdin=None,
                                           stdout=open("/dev/null", "w"),
                                           stderr=None,
                                           executable="/bin/bash")
                process.wait()
                if process.returncode > 0:
                    raise OSError(f"Command failed (rc: {process.returncode})")

            logging.info("[auto_backup] Backup done.")
            display.set('status', 'Backup done!')
            display.update()
            self.status.update()
        except OSError as os_e:
            self.tries += 1
            logging.info(f"[auto_backup] Error: {os_e}")
            display.set('status', 'Backup failed!')
            display.update()
Example #11
0
class AutoUpdate(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.0'
    __name__ = 'auto-update'
    __license__ = 'GPL3'
    __description__ = 'This plugin checks when updates are available and applies them when internet is available.'
    __defaults__ = {
        'enabled': True,
        'install': True,
        'interval': 1,
    }

    def __init__(self):
        self.ready = False
        self.status = StatusFile('/root/.auto-update', data_format='json')
        self.lock = Lock()
        self.done_caplets_check = False # only check once

    def on_loaded(self):
        self.ready = True
        logging.info("[auto-update] plugin loaded.")

    def on_internet_available(self, agent):
        if self.lock.locked():
            return

        with self.lock:
            logging.debug("[auto-update] internet connectivity is available (ready %s)" % self.ready)

            if not self.ready:
                return

            if self.status.newer_then_hours(self.options['interval']):
                logging.debug("[auto-update] last check happened less than %d hours ago" % self.options['interval'])
                return

            logging.info("[auto-update] checking for updates ...")

            config = agent.config()
            display = agent.view()
            prev_status = display.get('status')

            try:
                display.update(force=True, new_data={'status': 'Checking for updates ...'})

                to_install = []
                to_check = [
                    ('dadav/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
                    ('dadav/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
                ]

                for repo, local_version, is_native, svc_name in to_check:
                    info = check(local_version, repo, is_native)
                    if info['url'] is not None:
                        logging.warning(
                            "[auto-update] for %s available (local version is '%s'): %s" % (
                                repo, info['current'], info['url']))
                        info['service'] = svc_name
                        to_install.append(info)

                num_updates = len(to_install)
                num_installed = 0

                if num_updates > 0:
                    if self.options['install']:
                        for update in to_install:
                            plugins.on('updating')
                            if install(display, update):
                                num_installed += 1
                    else:
                        prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')

                if not self.done_caplets_check:
                    prev_commit = self.status.data_field_or('caplets_version', '')
                    try:
                        logging.info('[auto-update] Checking for new caplets.')
                        current_commit = fetch_last_commit('bettercap/caplets')
                        if prev_commit != current_commit:
                            logging.info('[auto-update] Updating caplets.')
                            rc = os.system('bettercap -eval "caplets.update;q"')
                            if rc == 0:
                                self.status.update(data={'caplets_version': current_commit})
                    except Exception as ex:
                        logging.error("[auto-update] %s", ex)
                    finally:
                        self.done_caplets_check = True

                # update plugins
                logging.info("[auto-update] Checking for new plugins")
                if plugin_update(config) == 0:
                    logging.info("[auto-update] Upgrading plugins")
                    plugin_upgrade(None, config)

                logging.info("[auto-update] done")

                self.status.update(data=self.status.data)

                if num_installed > 0:
                    display.update(force=True, new_data={'status': 'Rebooting ...'})
                    pwnagotchi.reboot()

            except Exception as e:
                logging.error("[auto-update] %s", e)

            display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})
Example #12
0
class dropbox(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '0.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to a dropbox app'

    def __init__(self):
        self.ready = False
        self.lock = threading.Lock()
        try:
            self.report = StatusFile('/root/.dropbox_ul_uploads',
                                     data_format='json')
        except JSONDecodeError as json_err:
            os.remove("/root/.dropbox_ul_uploads")
            self.report = StatusFile('/root/.dropbox_ul_uploads',
                                     data_format='json')

        self.options = dict()
        self.skip = list()

    def _upload_to_dropbox(self, path, timeout=30):
        """
		Uploads the file to dropbox
		"""
        head, tail = os.path.split(path)
        destFile = self.options['path'] + '/' + tail
        dbOpts = {
            'path': destFile,
            'mode': 'add',
            'autorename': True,
            'mute': False,
            'strict_conflict': False
        }

        headers = {
            'Authorization': 'Bearer ' + self.options['app_token'],
            'Dropbox-API-Arg': json.dumps(dbOpts),
            'Content-Type': 'application/octet-stream',
        }
        data = open(path, 'rb').read()

        try:
            response = requests.post(
                'https://content.dropboxapi.com/2/files/upload',
                headers=headers,
                data=data)
            logging.error(response)
        except requests.exceptions.RequestException as e:
            logging.error(
                f"OHC: Got an exception while uploading {path} -> {e}")
            raise e

    def on_loaded(self):
        """
		Gets called when the plugin gets loaded
		"""
        if 'app_token' not in self.options or (
                'app_token' in self.options
                and self.options['app_token'] is None):
            logging.error("dropbox_ul: APP-TOKEN isn't set.")
            return
        logging.info(" [dropbox_ul] plugin loaded")
        self.ready = True

    def on_internet_available(self, agent):
        """
		Called in manual mode when there's internet connectivity
		"""
        with self.lock:
            if self.ready:
                config = agent.config()
                display = agent.view()
                reported = self.report.data_field_or('reported',
                                                     default=list())

                handshake_dir = config['bettercap']['handshakes']
                handshake_filenames = os.listdir(handshake_dir)
                handshake_paths = [
                    os.path.join(handshake_dir, filename)
                    for filename in handshake_filenames
                    if filename.endswith('.pcap')
                ]
                handshake_new = set(handshake_paths) - set(reported) - set(
                    self.skip)

                if handshake_new:
                    logging.info(
                        "dropbox_ul: Internet connectivity detected. Uploading new handshakes"
                    )

                    for idx, handshake in enumerate(handshake_new):
                        display.set(
                            'status',
                            f"Uploading handshake to dropbox ({idx + 1}/{len(handshake_new)})"
                        )
                        display.update(force=True)
                        try:
                            self._upload_to_dropbox(handshake)
                            reported.append(handshake)
                            self.report.update(data={'reported': reported})
                            logging.info(
                                "dropbox_ul: Successfully uploaded %s",
                                handshake)
                        except requests.exceptions.RequestException as req_e:
                            self.skip.append(handshake)
                            logging.error("dropbox_ul: %s", req_e)
                            continue
                        except OSError as os_e:
                            logging.error("dropbox_ul: %s", os_e)
                            continue
Example #13
0
class OnlineHashCrack(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.1'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'

    def __init__(self):
        self.ready = False
        try:
            self.report = StatusFile('/root/.ohc_uploads', data_format='json')
        except JSONDecodeError as json_err:
            os.remove('/root/.ohc_uploads')
            self.report = StatusFile('/root/.ohc_uploads', data_format='json')
        self.skip = list()
        self.lock = Lock()

    def on_loaded(self):
        """
        Gets called when the plugin gets loaded
        """
        if 'email' not in self.options or ('email' in self.options
                                           and not self.options['email']):
            logging.error(
                "OHC: Email isn't set. Can't upload to onlinehashcrack.com")
            return

        if 'whitelist' not in self.options:
            self.options['whitelist'] = []

        # remove special characters from whitelist APs to match on-disk format
        self.options['whitelist'] = set(
            map(lambda x: re.sub(r'[^a-zA-Z0-9]', '', x),
                self.options['whitelist']))

        self.ready = True

    def _filter_handshake_file(self, handshake_filename):
        try:
            basename = os.path.basename(handshake_filename)
            ssid, bssid = basename.split('_')
            # remove the ".pcap" from the bssid (which is really just the end of the filename)
            bssid = bssid[:-5]
        except:
            # something failed in our parsing of the filename. let the file through
            return True

        return ssid not in self.options[
            'whitelist'] and bssid not in self.options['whitelist']

    def _upload_to_ohc(self, path, timeout=30):
        """
        Uploads the file to onlinehashcrack.com
        """
        with open(path, 'rb') as file_to_upload:
            data = {'email': self.options['email']}
            payload = {'file': file_to_upload}

            try:
                result = requests.post('https://api.onlinehashcrack.com',
                                       data=data,
                                       files=payload,
                                       timeout=timeout)
                if 'already been sent' in result.text:
                    logging.warning(f"{path} was already uploaded.")
            except requests.exceptions.RequestException as e:
                logging.error(
                    f"OHC: Got an exception while uploading {path} -> {e}")
                raise e

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """
        with self.lock:
            if self.ready:
                display = agent.view()
                config = agent.config()
                reported = self.report.data_field_or('reported',
                                                     default=list())

                handshake_dir = config['bettercap']['handshakes']
                handshake_filenames = os.listdir(handshake_dir)
                handshake_paths = [
                    os.path.join(handshake_dir, filename)
                    for filename in handshake_filenames
                    if filename.endswith('.pcap')
                ]

                # pull out whitelisted APs
                handshake_paths = filter(
                    lambda path: self._filter_handshake_file(path),
                    handshake_paths)

                handshake_new = set(handshake_paths) - set(reported) - set(
                    self.skip)

                if handshake_new:
                    logging.info(
                        "OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com"
                    )

                    for idx, handshake in enumerate(handshake_new):
                        display.set(
                            'status',
                            f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})"
                        )
                        display.update(force=True)
                        try:
                            self._upload_to_ohc(handshake)
                            if handshake not in reported:
                                reported.append(handshake)
                                self.report.update(data={'reported': reported})
                                logging.info(
                                    f"OHC: Successfully uploaded {handshake}")
                        except requests.exceptions.RequestException as req_e:
                            self.skip.append(handshake)
                            logging.error("OHC: %s", req_e)
                            continue
                        except OSError as os_e:
                            self.skip.append(handshake)
                            logging.error("OHC: %s", os_e)
                            continue
Example #14
0
class AutoUpdate(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '1.1.1'
    __name__ = 'auto-update'
    __license__ = 'GPL3'
    __description__ = 'This plugin checks when updates are available and applies them when internet is available.'

    def __init__(self):
        self.ready = False
        self.status = StatusFile('/root/.auto-update')
        self.lock = Lock()

    def on_loaded(self):
        if 'interval' not in self.options or (
                'interval' in self.options
                and self.options['interval'] is None):
            logging.error(
                "[update] main.plugins.auto-update.interval is not set")
            return
        self.ready = True
        logging.info("[update] plugin loaded.")

    def on_internet_available(self, agent):
        with self.lock:
            logging.debug(
                "[update] internet connectivity is available (ready %s)" %
                self.ready)

            if not self.ready:
                return

            if self.status.newer_then_hours(self.options['interval']):
                logging.debug(
                    "[update] last check happened less than %d hours ago" %
                    self.options['interval'])
                return

            logging.info("[update] checking for updates ...")

            display = agent.view()
            prev_status = display.get('status')

            try:
                display.update(force=True,
                               new_data={'status': 'Checking for updates ...'})

                to_install = []
                to_check = [
                    ('bettercap/bettercap',
                     parse_version('bettercap -version'), True, 'bettercap'),
                    ('evilsocket/pwngrid', parse_version('pwngrid -version'),
                     True, 'pwngrid-peer'),
                    ('evilsocket/pwnagotchi', pwnagotchi.version, False,
                     'pwnagotchi')
                ]

                for repo, local_version, is_native, svc_name in to_check:
                    info = check(local_version, repo, is_native)
                    if info['url'] is not None:
                        logging.warning(
                            "update for %s available (local version is '%s'): %s"
                            % (repo, info['current'], info['url']))
                        info['service'] = svc_name
                        to_install.append(info)

                num_updates = len(to_install)
                num_installed = 0

                if num_updates > 0:
                    if self.options['install']:
                        for update in to_install:
                            plugins.on('updating')
                            if install(display, update):
                                num_installed += 1
                    else:
                        prev_status = '%d new update%c available!' % (
                            num_updates, 's' if num_updates > 1 else '')

                logging.info("[update] done")

                self.status.update()

                if num_installed > 0:
                    display.update(force=True,
                                   new_data={'status': 'Rebooting ...'})
                    pwnagotchi.reboot()

            except Exception as e:
                logging.error("[update] %s" % e)

            display.update(force=True,
                           new_data={
                               'status':
                               prev_status if prev_status is not None else ''
                           })
Example #15
0
class Wigle(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '2.0.0'
    __license__ = 'GPL3'
    __description__ = 'This plugin automatically uploads collected wifis to wigle.net'

    def __init__(self):
        self.ready = False
        self.report = StatusFile('/root/.wigle_uploads', data_format='json')
        self.skip = list()
        self.lock = Lock()

    def on_loaded(self):
        if 'api_key' not in self.options or ('api_key' in self.options and
                                             self.options['api_key'] is None):
            logging.debug(
                "[wigle] api_key isn't set, can't upload to wigle.net.")
            return

        if not 'whitelist' in self.options:
            self.options['whitelist'] = list()

        self.ready = True
        logging.info("[wigle] Plugin loaded.")

    def on_internet_available(self, agent):
        """
        Called in manual mode when there's internet connectivity
        """
        if not self.ready or self.lock.locked():
            return

        from scapy.all import Scapy_Exception

        config = agent.config()
        display = agent.view()
        reported = self.report.data_field_or('reported', default=list())
        handshake_dir = config['bettercap']['handshakes']
        all_files = os.listdir(handshake_dir)
        all_gps_files = [
            os.path.join(handshake_dir, filename) for filename in all_files
            if filename.endswith('.gps.json')
        ]

        all_gps_files = remove_whitelisted(all_gps_files,
                                           self.options['whitelist'])
        new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
        if new_gps_files:
            logging.info(
                "[wigle] Internet connectivity detected, Uploading new handshakes to wigle.net..."
            )
            csv_entries = list()
            no_err_entries = list()
            for gps_file in new_gps_files:
                pcap_filename = gps_file.replace('.gps.json', '.pcap')
                if not os.path.exists(pcap_filename):
                    logging.debug(f"[wigle] Can't find pcap for {gps_file}.")
                    self.skip.append(gps_file)
                    continue
                try:
                    gps_data = _extract_gps_data(gps_file)
                except OSError as os_err:
                    logging.debug(f"[wigle] {os_err}")
                    self.skip.append(gps_file)
                    continue
                except json.JSONDecodeError as json_err:
                    logging.debug(f"[wigle]: {json_err}")
                    self.skip.append(gps_file)
                    continue
                if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
                    logging.debug(
                        f"[wigle] Not enough gps-information for {gps_file}. Trying again next time."
                    )
                    self.skip.append(gps_file)
                    continue
                try:
                    pcap_data = extract_from_pcap(pcap_filename, [
                        WifiInfo.BSSID, WifiInfo.ESSID, WifiInfo.ENCRYPTION,
                        WifiInfo.CHANNEL, WifiInfo.RSSI
                    ])
                except FieldNotFoundError:
                    logging.debug(
                        f"[wigle] Could not extract all information. Skip {gps_file}."
                    )
                    self.skip.append(gps_file)
                    continue
                except Scapy_Exception as sc_e:
                    logging.debug(f"[wigle]: {sc_e}")
                    self.skip.append(gps_file)
                    continue
                new_entry = _transform_wigle_entry(gps_data, pcap_data)
                csv_entries.append(new_entry)
                no_err_entries.append(gps_file)
            if csv_entries:
                display.set('status', "Uploading gps-data to wigle.net...")
                display.update(force=True)
                try:
                    _send_to_wigle(csv_entries, self.options['api_key'])
                    reported += no_err_entries
                    self.report.update(data={'reported': reported})
                    logging.info(
                        f"[wigle] Successfully uploaded {len(no_err_entries)} files."
                    )
                except requests.exceptions.RequestException as re_e:
                    self.skip += no_err_entries
                    logging.debug(
                        f"[wigle] Got an exception while uploading {re_e}")
                except OSError as os_e:
                    self.skip += no_err_entries
                    logging.debug(f"[wigle] Got the following error: {os_e}")
Example #16
0
class NetPos(plugins.Plugin):
    __author__ = 'zenzen san'
    __version__ = '3.0.0'
    __license__ = 'GPL3'
    __description__ = """Saves a json file with the access points with more signal
                         whenever a handshake is captured.
                         When internet is available the files are converted in geo locations
                         using Mozilla LocationService """
    __defaults__ = {
        'enabled': False,
        'api_key': 'test',
        'api_url':
        'https://location.services.mozilla.com/v1/geolocate?key={api}',
    }

    def __init__(self):
        self.report = StatusFile('/root/.net_pos_saved', data_format='json')
        self.skip = list()
        self.ready = False
        self.lock = threading.Lock()
        self.shutdown = False

    def on_before_shutdown(self):
        self.shutdown = True

    def on_loaded(self):
        if 'api_key' not in self.options or ('api_key' in self.options
                                             and not self.options['api_key']):
            logging.error(
                '[net-pos] api_key isn\'t set. Can\'t use mozilla\'s api.')
            return
        self.ready = True
        logging.info('[net-pos] plugin loaded.')
        logging.debug(f"[net-pos] use api_url: {self.options['api_url']}")

    def _append_saved(self, path):
        to_save = list()
        if isinstance(path, str):
            to_save.append(path)
        elif isinstance(path, list):
            to_save += path
        else:
            raise TypeError("Expected list or str, got %s" % type(path))

        with open('/root/.net_pos_saved', 'a') as saved_file:
            for x in to_save:
                saved_file.write(x + "\n")

    def on_internet_available(self, agent):
        if not self.ready or self.lock.locked() or self.shutdown:
            return

        with self.lock:
            config = agent.config()
            display = agent.view()
            reported = self.report.data_field_or('reported', default=list())
            handshake_dir = config['bettercap']['handshakes']

            all_files = os.listdir(handshake_dir)
            all_np_files = [
                os.path.join(handshake_dir, filename) for filename in all_files
                if filename.endswith('.net-pos.json')
            ]
            new_np_files = set(all_np_files) - set(reported) - set(self.skip)

            if new_np_files:
                logging.debug(
                    '[net-pos] Found %d new net-pos files. Fetching positions ...',
                    len(new_np_files))
                display.set(
                    'status',
                    f"Found {len(new_np_files)} new net-pos files. Fetching positions ..."
                )
                display.update(force=True)
                for idx, np_file in enumerate(new_np_files):
                    if self.shutdown:
                        return

                    geo_file = np_file.replace('.net-pos.json', '.geo.json')
                    if os.path.exists(geo_file):
                        # got already the position
                        reported.append(np_file)
                        self.report.update(data={'reported': reported})
                        continue

                    try:
                        geo_data = self._get_geo_data(
                            np_file)  # returns json obj
                    except requests.exceptions.RequestException as req_e:
                        logging.error('[net-pos] %s - RequestException: %s',
                                      np_file, req_e)
                        self.skip += np_file
                        continue
                    except json.JSONDecodeError as js_e:
                        logging.error(
                            '[net-pos] %s - JSONDecodeError: %s, removing it...',
                            np_file, js_e)
                        os.remove(np_file)
                        continue
                    except OSError as os_e:
                        logging.error('[net-pos] %s - OSError: %s', np_file,
                                      os_e)
                        self.skip += np_file
                        continue

                    with open(geo_file, 'w+t') as sf:
                        json.dump(geo_data, sf)

                    reported.append(np_file)
                    self.report.update(data={'reported': reported})

                    display.set(
                        'status',
                        f"Fetching positions ({idx + 1}/{len(new_np_files)})")
                    display.update(force=True)

    def on_handshake(self, agent, filename, access_point, client_station):
        netpos = self._get_netpos(agent)
        if not netpos['wifiAccessPoints']:
            return

        netpos["ts"] = int("%.0f" % time.time())
        netpos_filename = filename.replace('.pcap', '.net-pos.json')
        logging.debug('[net-pos] Saving net-location to %s', netpos_filename)

        try:
            with open(netpos_filename, 'w+t') as net_pos_file:
                json.dump(netpos, net_pos_file)
        except OSError as os_e:
            logging.error('[net-pos] %s', os_e)

    def _get_netpos(self, agent):
        aps = agent.get_access_points()
        netpos = dict()
        netpos['wifiAccessPoints'] = list()
        # 6 seems a good number to save a wifi networks location
        for access_point in sorted(aps, key=lambda i: i['rssi'],
                                   reverse=True)[:6]:
            netpos['wifiAccessPoints'].append({
                'macAddress':
                access_point['mac'],
                'signalStrength':
                access_point['rssi']
            })
        return netpos

    def _get_geo_data(self, path, timeout=30):
        geourl = self.options['api_url'].format(api=self.options['api_key'])

        try:
            with open(path, "r") as json_file:
                data = json.load(json_file)
        except json.JSONDecodeError as js_e:
            raise js_e
        except OSError as os_e:
            raise os_e

        try:
            result = requests.post(geourl, json=data, timeout=timeout)
            return_geo = result.json()
            if data["ts"]:
                return_geo["ts"] = data["ts"]
            return return_geo
        except requests.exceptions.RequestException as req_e:
            raise req_e
class GitBackup(plugins.Plugin):
    __author__ = '*****@*****.**'
    __version__ = '1.0.0'
    __license__ = 'GPL3'
    __description__ = 'git_backup'

    def __init__(self):
        self.ready = False
        self.tries = 0
        logging.debug("GIT BACKUP")
        self.status = StatusFile('/root/.git_backup')

    # called when http://<host>:<port>/plugins/<plugin>/ is called
    # must return a html page
    # IMPORTANT: If you use "POST"s, add a csrf-token (via csrf_token() and render_template_string)
    def on_webhook(self, path, request):
        pass

    # called when the plugin is loaded
    def on_loaded(self):
        self.ready = True
        logging.info("GIT-BACKUP: Successfully loaded.")

    # called before the plugin is unloaded
    def on_unload(self, ui):
        pass

    # called hen there's internet connectivity
    def on_internet_available(self, agent):
        if not self.ready:
            return
        if self.tries >= 2:
            return

        if self.status.newer_then_days(self.options['interval']):
            return
        try:
            f = open("/home/pi/git_err.txt", "a+")
            display = agent.view()
            logging.info("GITBACKUP: Backing up ...")
            display.set('status', 'Backing up ...')
            display.update()
            logging.info(f"GIT-BACKUP: Running")
            process = subprocess.Popen([
                'sudo cp -r /root/brain.nn /root/brain.json /root/.api-report.json /root/handshakes/ /root/peers/ /etc/pwnagotchi/ /var/log/pwnagotchi.log /root/Pwnagotchi/ && cd /root/Pwnagotchi/ && sudo git pull && sudo  git add . && sudo  git commit -am "backup" && sudo git push'
            ],
                                       shell=True,
                                       stderr=f,
                                       stdin=f)
            process.wait()
            f.close()
            logging.info("GIT-BACKUP: backup done")
            display.set('status', 'GIT-Backup done!')
            display.update()
            self.status.update()
        except OSError as os_e:
            self.tries += 1
            logging.info(f"GIT-BACKUP: Error: {os_e}")
            display.set('status', 'Backup failed!')
            display.update()

    # called to setup the ui elements
    def on_ui_setup(self, ui):
        pass
        # called when the ui is updated
    def on_ui_update(self, ui):
        pass  # update those elements

    # called when the hardware display setup is done, display is an hardware specific object
    def on_display_setup(self, display):
        pass

    # called when everything is ready and the main loop is about to start
    def on_ready(self, agent):
        logging.info("unit is ready")
        # you can run custom bettercap commands if you want
        #   agent.run('ble.recon on')
        # or set a custom state
        #   agent.set_bored()

    # called when the AI finished loading
    def on_ai_ready(self, agent):
        pass

    # called when the AI finds a new set of parameters
    def on_ai_policy(self, agent, policy):
        pass

    # called when the AI starts training for a given number of epochs
    def on_ai_training_start(self, agent, epochs):
        pass

    # called after the AI completed a training epoch
    def on_ai_training_step(self, agent, _locals, _globals):
        pass

    # called when the AI has done training
    def on_ai_training_end(self, agent):
        pass

    # called when the AI got the best reward so far
    def on_ai_best_reward(self, agent, reward):
        pass

    # called when the AI got the worst reward so far
    def on_ai_worst_reward(self, agent, reward):
        pass

    # called when a non overlapping wifi channel is found to be free
    def on_free_channel(self, agent, channel):
        pass

    # called when the status is set to bored
    def on_bored(self, agent):
        pass

    # called when the status is set to sad
    def on_sad(self, agent):
        pass

    # called when the status is set to excited
    def on_excited(self, agent):
        pass

    # called when the status is set to lonely
    def on_lonely(self, agent):
        pass

    # called when the agent is rebooting the board
    def on_rebooting(self, agent):
        pass

    # called when the agent is waiting for t seconds
    def on_wait(self, agent, t):
        pass

    # called when the agent is sleeping for t seconds
    def on_sleep(self, agent, t):
        pass

    # called when the agent refreshed its access points list
    def on_wifi_update(self, agent, access_points):
        pass

    # called when the agent refreshed an unfiltered access point list
    # this list contains all access points that were detected BEFORE filtering
    def on_unfiltered_ap_list(self, agent, access_points):
        pass

    # called when the agent is sending an association frame
    def on_association(self, agent, access_point):
        pass

    # called when the agent is deauthenticating a client station from an AP
    def on_deauthentication(self, agent, access_point, client_station):
        pass

    # callend when the agent is tuning on a specific channel
    def on_channel_hop(self, agent, channel):
        pass

    # called when a new handshake is captured, access_point and client_station are json objects
    # if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs
    def on_handshake(self, agent, filename, access_point, client_station):
        pass

    # called when an epoch is over (where an epoch is a single loop of the main algorithm)
    def on_epoch(self, agent, epoch, epoch_data):
        pass

    # called when a new peer is detected
    def on_peer_detected(self, agent, peer):
        pass

    # called when a known peer is lost
    def on_peer_lost(self, agent, peer):
        pass