diff --git a/builder/data/etc/systemd/system/pwnagotchi.service b/builder/data/etc/systemd/system/pwnagotchi.service index c0071f3ef..84287c508 100644 --- a/builder/data/etc/systemd/system/pwnagotchi.service +++ b/builder/data/etc/systemd/system/pwnagotchi.service @@ -10,6 +10,8 @@ PermissionsStartOnly=true ExecStart=/usr/bin/pwnagotchi-launcher Restart=always RestartSec=30 +TasksMax=infinity +LimitNPROC=infinity [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py index dfd41678f..89fe3fee0 100644 --- a/pwnagotchi/__init__.py +++ b/pwnagotchi/__init__.py @@ -1,12 +1,12 @@ -import subprocess import os import logging import time import re + import pwnagotchi.ui.view as view import pwnagotchi -version = '1.4.1' +version = '1.4.3' _name = None @@ -27,17 +27,17 @@ def set_name(new_name): if new_name != current: global _name - logging.info("setting unit hostname '%s' -> '%s'" % (current, new_name)) + logging.info("setting unit hostname '%s' -> '%s'", current, new_name) with open('/etc/hostname', 'wt') as fp: fp.write(new_name) with open('/etc/hosts', 'rt') as fp: prev = fp.read() - logging.debug("old hosts:\n%s\n" % prev) + logging.debug("old hosts:\n%s\n", prev) with open('/etc/hosts', 'wt') as fp: patched = prev.replace(current, new_name, -1) - logging.debug("new hosts:\n%s\n" % patched) + logging.debug("new hosts:\n%s\n", patched) fp.write(patched) os.system("hostname '%s'" % new_name) @@ -109,7 +109,7 @@ def shutdown(): def restart(mode): - logging.warning("restarting in %s mode ..." % mode) + logging.warning("restarting in %s mode ...", mode) if mode == 'AUTO': os.system("touch /root/.pwnagotchi-auto") @@ -123,7 +123,7 @@ def restart(mode): def reboot(mode=None): if mode is not None: mode = mode.upper() - logging.warning("rebooting in %s mode ..." % mode) + logging.warning("rebooting in %s mode ...", mode) else: logging.warning("rebooting ...") diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py index 1cdf3bf0c..b5e2d2f54 100644 --- a/pwnagotchi/agent.py +++ b/pwnagotchi/agent.py @@ -212,7 +212,7 @@ def get_access_points_by_channel(self): ch = ap['channel'] # if we're sticking to a channel, skip anything # which is not on that channel - if not channels and ch not in channels: + if channels and ch not in channels: continue if ch not in grouped: diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py index d4e008fea..d087c4711 100644 --- a/pwnagotchi/automata.py +++ b/pwnagotchi/automata.py @@ -12,19 +12,18 @@ def __init__(self, config, view): self._epoch = Epoch(config) def _on_miss(self, who): - logging.info("it looks like %s is not in range anymore :/" % who) + logging.info("it looks like %s is not in range anymore :/", who) self._epoch.track(miss=True) self._view.on_miss(who) def _on_error(self, who, e): - error = "%s" % e # when we're trying to associate or deauth something that is not in range anymore # (if we are moving), we get the following error from bettercap: # error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list. - if 'is an unknown BSSID' in error: + if 'is an unknown BSSID' in str(e): self._on_miss(who) else: - logging.error("%s" % e) + logging.error(e) def set_starting(self): self._view.on_starting() @@ -58,7 +57,7 @@ def set_lonely(self): def set_bored(self): factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs'] if not self._has_support_network_for(factor): - logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for) + logging.warning("%d epochs with no activity -> bored", self._epoch.inactive_for) self._view.on_bored() plugins.on('bored', self) else: @@ -68,7 +67,7 @@ def set_bored(self): def set_sad(self): factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs'] if not self._has_support_network_for(factor): - logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for) + logging.warning("%d epochs with no activity -> sad", self._epoch.inactive_for) self._view.on_sad() plugins.on('sad', self) else: @@ -77,7 +76,7 @@ def set_sad(self): def set_angry(self, factor): if not self._has_support_network_for(factor): - logging.warning("%d epochs with no activity -> angry" % self._epoch.inactive_for) + logging.warning("%d epochs with no activity -> angry", self._epoch.inactive_for) self._view.on_angry() plugins.on('angry', self) else: @@ -85,7 +84,7 @@ def set_angry(self, factor): self.set_grateful() def set_excited(self): - logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) + logging.warning("%d epochs with activity -> excited", self._epoch.active_for) self._view.on_excited() plugins.on('excited', self) @@ -118,7 +117,7 @@ def next_epoch(self): if factor >= 2.0: self.set_angry(factor) else: - logging.warning("agent missed %d interactions -> lonely" % did_miss) + logging.warning("agent missed %d interactions -> lonely", did_miss) self.set_lonely() # after X times being bored, the status is set to sad or angry elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: @@ -139,6 +138,6 @@ def next_epoch(self): plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data()) if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: - logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) + logging.critical("%d epochs without visible access points -> rebooting ...", self._epoch.blind_for) self._reboot() self._epoch.blind_for = 0 diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py index 9e7428b6a..72a39096d 100644 --- a/pwnagotchi/plugins/__init__.py +++ b/pwnagotchi/plugins/__init__.py @@ -1,6 +1,7 @@ import os import glob import _thread +import threading import importlib, importlib.util import logging from pwnagotchi.ui import view @@ -8,17 +9,27 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") loaded = {} database = {} +locks = {} + class Plugin: @classmethod def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) - global loaded + global loaded, locks + plugin_name = cls.__module__.split('.')[0] plugin_instance = cls() logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance)) loaded[plugin_name] = plugin_instance + for attr_name in plugin_instance.__dir__(): + if attr_name.startswith('on_'): + cb = getattr(plugin_instance, attr_name, None) + if cb is not None and callable(cb): + locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock() + + def toggle_plugin(name, enable=True): """ Load or unload a plugin @@ -47,15 +58,24 @@ def on(event_name, *args, **kwargs): one(plugin_name, event_name, *args, **kwargs) +def locked_cb(lock_name, cb, *args, **kwargs): + global locks + with locks[lock_name]: + cb(*args, *kwargs) + + def one(plugin_name, event_name, *args, **kwargs): global loaded + if plugin_name in loaded: plugin = loaded[plugin_name] cb_name = 'on_%s' % event_name callback = getattr(plugin, cb_name, None) if callback is not None and callable(callback): try: - _thread.start_new_thread(callback, (*args, *kwargs)) + lock_name = "%s::%s" % (plugin_name, cb_name) + locked_cb_args = (lock_name, callback, *args, *kwargs) + _thread.start_new_thread(locked_cb, locked_cb_args) except Exception as e: logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error(e, exc_info=True) diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py index 7100c9a15..d3cce8dd6 100644 --- a/pwnagotchi/plugins/default/gps.py +++ b/pwnagotchi/plugins/default/gps.py @@ -113,6 +113,13 @@ def on_ui_setup(self, ui): ), ) + + def on_unload(self, ui): + with ui._lock: + ui.remove_element('latitude') + ui.remove_element('longitude') + ui.remove_element('altitude') + def on_ui_update(self, ui): if self.coordinates and all([ # avoid 0.000... measurements diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py index 4d5dd6400..f64e287dc 100644 --- a/pwnagotchi/plugins/default/memtemp.py +++ b/pwnagotchi/plugins/default/memtemp.py @@ -53,6 +53,9 @@ def on_ui_setup(self, ui): elif ui.is_inky(): h_pos = (140, 68) v_pos = (165, 54) + elif ui.is_waveshare27inch(): + h_pos = (192, 138) + v_pos = (216, 122) else: h_pos = (155, 76) v_pos = (180, 61) @@ -67,6 +70,10 @@ def on_ui_setup(self, ui): position=h_pos, label_font=fonts.Small, text_font=fonts.Small)) + def on_unload(self, ui): + with ui._lock: + ui.remove_element('memtemp') + def on_ui_update(self, ui): if self.options['scale'] == "fahrenheit": temp = (pwnagotchi.temperature() * 9 / 5) + 32 @@ -75,7 +82,7 @@ def on_ui_update(self, ui): temp = pwnagotchi.temperature() + 273.15 symbol = "k" else: - # default to celsius + # default to celsius temp = pwnagotchi.temperature() symbol = "c" diff --git a/pwnagotchi/plugins/default/ups_lite.py b/pwnagotchi/plugins/default/ups_lite.py index 871b0f770..ba864123d 100644 --- a/pwnagotchi/plugins/default/ups_lite.py +++ b/pwnagotchi/plugins/default/ups_lite.py @@ -58,5 +58,9 @@ def on_ui_setup(self, ui): ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 + 15, 0), label_font=fonts.Bold, text_font=fonts.Medium)) + def on_unload(self, ui): + with ui._lock: + ui.remove_element('ups') + def on_ui_update(self, ui): ui.set('ups', "%2i%%" % self.ups.capacity()) diff --git a/pwnagotchi/plugins/default/webgpsmap.py b/pwnagotchi/plugins/default/webgpsmap.py index 85479f9b6..3d7ae4df6 100644 --- a/pwnagotchi/plugins/default/webgpsmap.py +++ b/pwnagotchi/plugins/default/webgpsmap.py @@ -304,6 +304,9 @@ def timestamp_last(self): elif 'Updated' in self._json: # convert gps datetime to unix timestamp: "2019-10-05T23:12:40.422996+01:00" date_iso_formated = self._json['Updated'] + #fix timezone "Z": "2019-11-28T04:44:46.79231Z" >> "2019-11-28T04:44:46.79231+00:00" + if date_iso_formated.endswith("Z"): + date_iso_formated = date_iso_formated[:-1] + "+00:00" # bad microseconds fix: fill/cut microseconds to 6 numbers part1, part2, part3 = re.split('\.|\+', date_iso_formated) part2 = part2.ljust(6, '0')[:6] diff --git a/pwnagotchi/ui/hw/waveshare27inch.py b/pwnagotchi/ui/hw/waveshare27inch.py index c554744b8..0cbc168e5 100644 --- a/pwnagotchi/ui/hw/waveshare27inch.py +++ b/pwnagotchi/ui/hw/waveshare27inch.py @@ -6,7 +6,7 @@ class Waveshare27inch(DisplayImpl): def __init__(self, config): - super(Waveshare27inch, self).__init__(config, 'waveshare_2_7inch') + super(Waveshare27inch, self).__init__(config, 'waveshare27inch') self._display = None def layout(self): diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 4d0ba90c5..0f912c841 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -10,6 +10,8 @@ import json import shutil import gzip +import contextlib +import tempfile import pwnagotchi @@ -345,6 +347,17 @@ def extract_from_pcap(path, fields): return results +@contextlib.contextmanager +def ensure_write(filename, mode='w'): + path = os.path.dirname(filename) + fd, tmp = tempfile.mkstemp(dir=path) + + with os.fdopen(fd, mode) as f: + yield f + f.flush() + os.fsync(f.fileno()) + + os.replace(tmp, filename) class StatusFile(object): def __init__(self, path, data_format='raw'): @@ -378,7 +391,7 @@ def newer_then_days(self, days): def update(self, data=None): self._updated = datetime.now() self.data = data - with open(self._path, 'w') as fp: + with ensure_write(self._path, 'w') as fp: if data is None: fp.write(str(self._updated)) diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py index b1abd8a54..b69fefcfb 100644 --- a/pwnagotchi/voice.py +++ b/pwnagotchi/voice.py @@ -161,7 +161,10 @@ def on_rebooting(self): def on_last_session_data(self, last_session): status = self._('Kicked {num} stations\n').format(num=last_session.deauthed) - status += self._('Made {num} new friends\n').format(num=last_session.associated) + if last_session.associated > 999: + status += self._('Made >999 new friends\n') + else: + status += self._('Made {num} new friends\n').format(num=last_session.associated) status += self._('Got {num} handshakes\n').format(num=last_session.handshakes) if last_session.peers == 1: status += self._('Met 1 peer') diff --git a/requirements.txt b/requirements.txt index 20c2617b7..2b8eecfbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ requests==2.21.0 PyYAML==5.1 scapy==2.4.3 gym==0.14.0 +scipy==1.3.1 stable-baselines==2.7.0 tensorflow==1.13.1 tensorflow-estimator==1.14.0 @@ -16,4 +17,4 @@ spidev==3.4 gast==0.2.2 flask==1.0.2 flask-cors==3.0.7 -flask-wtf==0.14.2 \ No newline at end of file +flask-wtf==0.14.2