Merge pull request from evilsocket/master

update
This commit is contained in:
hmax42 2019-12-01 07:49:23 +01:00 committed by GitHub
commit 11476433ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 766 additions and 199 deletions

@ -39,6 +39,7 @@ main:
enabled: false
api_key: ~
api_url: "https://wpa-sec.stanev.org"
download_results: false
wigle:
enabled: false
api_key: ~

Binary file not shown.

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 20:56+0200\n"
"POT-Creation-Date: 2019-11-14 21:15+0100\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
@ -20,7 +20,7 @@ msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hi, ich bin ein Pwnagotchi! Starte ..."
msgstr "Hi, ich bin ein Pwnagotchi! Starte..."
msgid "New day, new hunt, new pwns!"
msgstr "Neuer Tag, neue Jagd, neue Pwns!"
@ -35,23 +35,30 @@ msgid "The neural network is ready."
msgstr "Das neurale Netz ist bereit."
msgid "Generating keys, do not turn off ..."
msgstr "Generiere Keys, nicht ausschalten ..."
msgstr "Generiere Keys, nicht ausschalten..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken."
msgstr "Hey, Channel {channel} ist frei! Dein AP wird es Dir danken."
msgid "Reading last session logs ..."
msgstr "Lese die Logs der letzten Session..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Bisher {lines_so_far} Zeilen im Log gelesen..."
msgid "I'm bored ..."
msgstr "Mir ist langweilig..."
msgid "Let's go for a walk!"
msgstr "Lass uns laufen gehen!"
msgstr "Lass uns spazieren gehen!"
msgid "This is the best day of my life!"
msgstr "Das ist der beste Tag meines Lebens."
msgstr "Das ist der beste Tag meines Lebens!"
msgid "Shitty day :/"
msgstr "Scheis Tag :/"
msgstr "Scheißtag :/"
msgid "I'm extremely bored ..."
msgstr "Mir ist sau langweilig..."
@ -62,6 +69,13 @@ msgstr "Ich bin sehr traurig..."
msgid "I'm sad"
msgstr "Ich bin traurig"
#, fuzzy
msgid "Leave me alone ..."
msgstr "Lass mich in Ruhe..."
msgid "I'm mad at you!"
msgstr "Ich bin sauer auf Dich!"
msgid "I'm living the life!"
msgstr "Ich lebe das Leben!"
@ -75,27 +89,35 @@ msgid "I'm having so much fun!"
msgstr "Ich habe sooo viel Spaß!"
msgid "My crime is that of curiosity ..."
msgstr "Mein Verbrechen ist das der Neugier ..."
msgstr "Mein Verbrechen ist das der Neugier..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hallo {name}, nett Dich kennenzulernen."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Jo {name}! Was geht!?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hey {name}, wie geht's?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Gerät {name} ist in der nähe!!"
msgstr "Gerät {name} ist in der Nähe!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ...tschüß {name}"
msgstr "Uhm... tschüß {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} ist weg ..."
msgstr "{name} ist weg..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ...{name} ist weg."
msgstr "Whoops... {name} ist weg."
#, python-brace-format
msgid "{name} missed!"
@ -111,17 +133,17 @@ msgid "I love my friends!"
msgstr "Ich liebe meine Freunde!"
msgid "Nobody wants to play with me ..."
msgstr "Niemand will mit mir spielen ..."
msgstr "Niemand will mit mir spielen..."
msgid "I feel so alone ..."
msgstr "Ich fühl michso alleine ..."
msgstr "Ich fühl' mich so allein..."
msgid "Where's everybody?!"
msgstr "Wo sind denn alle?"
msgstr "Wo sind denn alle?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Schlafe für {secs}s"
msgstr "Schlafe für {secs}s..."
msgid "Zzzzz"
msgstr ""
@ -138,7 +160,7 @@ msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Warte für {secs}s ..."
msgstr "Warte für {secs}s..."
#, python-brace-format
msgid "Looking around ({secs}s)"
@ -158,7 +180,7 @@ msgstr "Jo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Ich denke, dass {mac} kein WiFi brauch!"
msgstr "Ich denke, dass {mac} kein WiFi braucht!"
#, python-brace-format
msgid "Deauthenticating {mac}"
@ -177,7 +199,7 @@ msgid "You have {count} new message{plural}!"
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..."
msgstr "Ops, da ist was schief gelaufen... Starte neu..."
#, python-brace-format
msgid "Kicked {num} stations\n"

Binary file not shown.

@ -0,0 +1,249 @@
# Pwnagotchi translation.
# Copyright (C) 2019
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <radu.ungureanu@techie.com>, 2019.
#
#,
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-04 12:57+0100\n"
"PO-Revision-Date: 2019-11-20 00:18+594\n"
"Last-Translator: Ungureanu Radu-Andrei <radu.ungureanu@techie.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github."
"com>\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Buna, sunt Pwnagotchi! Pornesc..."
msgid "New day, new hunt, new pwns!"
msgstr "O noua zi, o noua vanatoare, noi pwn-uri!"
msgid "Hack the Planet!"
msgstr "Pirateaza planeta!"
msgid "AI ready."
msgstr "AI-ul e gata."
msgid "The neural network is ready."
msgstr "Rețeaua neuronală este gata."
msgid "Generating keys, do not turn off ..."
msgstr "Se generează chei, nu închide..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, canalul {channel} este liber! AP-ul tău îti va mulțumi."
msgid "Reading last session logs ..."
msgstr "Se citesc log-urile din sesiunea anterioara..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Am citit {lines_so_far} linii din log pana acum..."
msgid "I'm bored ..."
msgstr "Sunt plictisit..."
msgid "Let's go for a walk!"
msgstr "Hai să ne plimbăm!"
msgid "This is the best day of my life!"
msgstr "Asta este cea mai buna zi din viața mea!"
msgid "Shitty day :/"
msgstr "O zi proasta :/"
msgid "I'm extremely bored ..."
msgstr "Sunt extrem de plictisit..."
msgid "I'm very sad ..."
msgstr "Sunt foarte trist..."
msgid "I'm sad"
msgstr "Sunt trist"
msgid "Leave me alone ..."
msgstr "Lasă-mă in pace..."
msgid "I'm mad at you!"
msgstr "Sunt supărat pe tine!"
msgid "I'm living the life!"
msgstr "Trăiesc viața!"
msgid "I pwn therefore I am."
msgstr "Eu pwn-ez, deci aici sunt."
msgid "So many networks!!!"
msgstr "Atât de multe rețele!"
msgid "I'm having so much fun!"
msgstr "Mă distrez așa de mult!"
msgid "My crime is that of curiosity ..."
msgstr "Crima mea este una de curiozitate..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Bună {name}! Mă bucur să te cunosc."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Yo {name}! Cmf?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hey {nume} ce mai faci?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Unitatea {name} este aproape!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm... Pa {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} a dispărut."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Oops... {name} a dispărut."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} ratat!"
msgid "Missed!"
msgstr "Ratat!"
msgid "Good friends are a blessing!"
msgstr "Prietenii buni sunt o binecuvântare!"
msgid "I love my friends!"
msgstr "Îmi iubesc prietenii!"
msgid "Nobody wants to play with me ..."
msgstr "Nimeni nu vrea sa se joace cu mine..."
msgid "I feel so alone ..."
msgstr "Mă simt așa de singuratic..."
msgid "Where's everybody?!"
msgstr "Unde-i toată lumea?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Dorm pentru {secs}s..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Noapte bună."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Aștept pentru {secs}s..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Mă uit împrejur ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what} hai să fim prieteni!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Mă asociez cu {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Yo {what}"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Am decis că lui {mac} nu-i trebuie WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Îl deautentific pe {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Îi dau kickban lui {mac}"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Șmecher, avem {num} de handshake-uri noi!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Ai {count} mesaj(e) nou/noi!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "OOps, ceva s-a întamplat... Îmi dau reboot...+"
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Am dat afară {num} de stații\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Am făcut {num} prieteni noi \n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Am primit {num} de handshake-uri\n"
msgid "Met 1 peer"
msgstr "Am întalnit un peer"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Am întalnit {num} de peer-uri"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "Eu am făcut pwning pentru {duration} și am dat afara {deauthed} clienți! "
"De asemenea, am întalnit {associated} prieteni noi și am mancat {handshakes} de "
"handshake-uri! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "ore"
msgid "minutes"
msgstr "minute"
msgid "seconds"
msgstr "secunde"
msgid "hour"
msgstr "oră"
msgid "minute"
msgstr "minut"
msgid "second"
msgstr "secundă"

@ -1,45 +1,51 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Pwnagotchi Russian translation.
# Copyright (C) 2019
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <25989971+adolfaka@users.noreply.github.com>, 2019.
#
# Second author <https://github.com/mbgroot>, 2019
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
"PO-Revision-Date: 2019-10-05 18:50+0300\n"
"Language-Team: \n"
"Project-Id-Version: 0.0.2"
"Report-Msgid-Bugs-To: m-b-g@yandex.ru"
"POT-Creation-Date: 2019-11-27 16:47+0200"
"PO-Revision-Date: 2019-11-27 18:50+0300"
"Last-Translator: Evgeny Zelenin <m-b-g@yandex.ru>"
"Language-Team: ru"
"Language: ru"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.4\n"
"Last-Translator: Elliot Manson\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: ru_RU\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgstr "Хрррр..."
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Привет, я Pwnagotchi! Поехали …"
msgstr "Привет, я Pwnagotchi! Стартуем!"
msgid "New day, new hunt, new pwns!"
msgstr "Новый день, новая охота, новые взломы!"
msgid "Hack the Planet!"
msgstr "Хак зе планет!"
msgstr "Взломай эту Планету!"
msgid "AI ready."
msgstr "AI готов."
msgstr "A.I. готов."
msgid "The neural network is ready."
msgstr "Нейронная сеть готова."
msgid "Generating keys, do not turn off ..."
msgstr "Генерация ключей, не выключайте..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Эй, канал {channel} свободен! Ваша точка доступа скажет спасибо."
msgid "Reading last session logs ..."
msgstr "Чтение логов последнего сеанса..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Чтение {lines_so_far} строк журнала..."
msgid "I'm bored ..."
msgstr "Мне скучно …"
@ -61,9 +67,14 @@ msgstr "Мне очень грустно …"
msgid "I'm sad"
msgstr "Мне грустно"
msgid "Leave me alone ..."
msgstr "Оставь меня в покое..."
msgid "I'm mad at you!"
msgstr "Я зол на тебя!"
msgid "I'm living the life!"
msgstr "Угараю по полной!"
msgstr "Живу полной жизнью!"
msgid "I pwn therefore I am."
msgstr "Я взламываю, поэтому я существую."
@ -75,15 +86,22 @@ msgid "I'm having so much fun!"
msgstr "Мне так весело!"
msgid "My crime is that of curiosity ..."
msgstr "Моe преступление - это любопытство …"
msgstr "Моё преступление - это любопытство…"
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Привет, {name}! Приятно познакомиться. {name}"
msgstr "Привет, {name}! Рад встрече с тобой!"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Цель {name} близко! {name}"
msgstr "Цель {name} близко!"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Хэй {nume}! Как дела?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Цель {name} рядом!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
@ -91,11 +109,11 @@ msgstr "Хм … до свидания {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} исчезла …"
msgstr "{name} ушла…"
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Упс … {name} исчезла."
msgstr "Упс… {name} исчезла."
#, python-brace-format
msgid "{name} missed!"
@ -103,12 +121,17 @@ msgstr "{name} упустил!"
msgid "Missed!"
msgstr "Промахнулся!"
msgid "Good friends are a blessing!"
msgstr "Хорошие друзья - это благословение!"
msgid "I love my friends!"
msgstr "Я люблю своих друзей!"
msgid "Nobody wants to play with me ..."
msgstr "Никто не хочет со мной играть ..."
msgid "I feel so alone ..."
msgstr "Мне так одиноко …"
msgstr "Я так одинок…"
msgid "Where's everybody?!"
msgstr "Где все?!"
@ -118,11 +141,16 @@ msgid "Napping for {secs}s ..."
msgstr "Дремлет {secs}с …"
msgid "Zzzzz"
msgstr "Zzzzz"
msgstr "Хррр..."
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}c)"
msgstr "Хррррр.. ({secs}c)"
msgid "Good night."
msgstr "Доброй ночи."
msgid "Zzz"
msgstr "Хрррр"
#, python-brace-format
msgid "Waiting for {secs}s ..."
@ -130,7 +158,7 @@ msgstr "Ждем {secs}c …"
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Оглядываюсь вокруг ({secs}с)"
msgstr "Осматриваюсь вокруг ({secs}с)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
@ -173,7 +201,7 @@ msgstr "Заимел {num} новых друзей\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Получил {num} рукопожатие\n"
msgstr "Получил {num} рукопожатий\n"
msgid "Met 1 peer"
msgstr "Встретился один знакомый"

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-04 12:57+0100\n"
"POT-Creation-Date: 2019-11-18 18:29+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

@ -6,7 +6,7 @@ import logging
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {}
database = {}
class Plugin:
@classmethod
@ -18,6 +18,26 @@ class Plugin:
logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance))
loaded[plugin_name] = plugin_instance
def toggle_plugin(name, enable=True):
"""
Load or unload a plugin
returns True if changed, otherwise False
"""
global loaded, database
if not enable and name in loaded:
if getattr(loaded[name], 'on_unload', None):
loaded[name].on_unload()
del loaded[name]
return True
if enable and name in database and name not in loaded:
load_from_file(database[name])
one(name, 'loaded')
return True
return False
def on(event_name, *args, **kwargs):
for plugin_name, plugin in loaded.items():
@ -48,10 +68,11 @@ def load_from_file(filename):
def load_from_path(path, enabled=()):
global loaded
global loaded, database
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
database[plugin_name] = filename
if plugin_name in enabled:
try:
load_from_file(filename)

@ -466,7 +466,11 @@ class BTTether(plugins.Plugin):
logging.info("BT-TETHER: Successfully loaded ...")
self.ready = True
def on_unload(self):
self.ui.remove_element('bluetooth')
def on_ui_setup(self, ui):
self.ui = ui
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))

@ -25,6 +25,10 @@ class Example(plugins.Plugin):
def on_loaded(self):
logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
# called before the plugin is unloaded
def on_unload(self):
pass
# called hen there's internet connectivity
def on_internet_available(self, agent):
pass
@ -118,6 +122,11 @@ class Example(plugins.Plugin):
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

@ -32,6 +32,7 @@ class GPIOButtons(plugins.Plugin):
GPIO.setmode(GPIO.BCM)
for gpio, command in gpios.items():
gpio = int(gpio)
self.ports[gpio] = command
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=600)

@ -67,7 +67,8 @@ class Grid(plugins.Plugin):
logging.info("grid plugin loaded.")
def set_reported(self, reported, net_id):
reported.append(net_id)
if net_id not in reported:
reported.append(net_id)
self.report.update(data={'reported': reported})
def check_inbox(self, agent):

@ -4,10 +4,7 @@ import pwnagotchi.plugins as plugins
'''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
NEW BETTER GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
Old guide here, (not recommended if you plan on using it with the webgpsmap plugin)
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
'''
@ -30,7 +27,7 @@ class PawGPS(plugins.Plugin):
ip = self.options['ip']
gps = requests.get('http://' + ip + '/gps.xhtml')
gps_filename = filename.replace('.pcap', '.gps.json')
gps_filename = filename.replace('.pcap', '.paw-gps.json')
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
with open(gps_filename, 'w+t') as f:

@ -500,8 +500,9 @@ class WebConfig(plugins.Plugin):
elif request.method == "POST":
if path == "save-config":
try:
parsed_yaml = yaml.safe_load(str(request.get_json()))
with open('/etc/pwnagotchi/config.yml', 'w') as config_file:
yaml.safe_dump(request.get_json(), config_file, encoding='utf-8',
yaml.safe_dump(parsed_yaml, config_file, encoding='utf-8',
allow_unicode=True, default_flow_style=False)
_thread.start_new_thread(restart, (self.mode,))

@ -9,7 +9,7 @@
<script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js'></script>
<style type="text/css">
/* for map */
html, body, #mapdiv { height: 100%; width: 100%; margin:0; background-color:#000;}
html, body { height: 100%; width: 100%; margin:0; background-color:#000;}
.pwnAPPin path {
fill: #ce7575;
}
@ -85,11 +85,19 @@
display: none;
}
#loading .face { font-size:8vw; }
#loading .text {position:absolute;bottom:0;text-align:center; font-size: 1vw;color:#a0a0a0;}
#loading .text { position:absolute;bottom:0;text-align:center; font-size: 2vw;color:#a0a0a0;}
#filterbox { position: fixed;top:0px;left:0px;z-index:999999;margin-left:55px;width:100%;height:20px;border-bottom:2px solid #303030;display: grid;grid-template-columns: 1fr 0.1fr;grid-template-rows: 1fr;grid-template-areas: ". .";}
#search { grid-area: 1 / 1 / 2 / 2;height:30px;padding:3px;background-color:#000;color:#e0e0e0;border:none;}
#matchcount { grid-area: 1 / 2 / 2 / 3;height:30px;margin-right:55px;padding-right:5px;background-color:#000;color:#a0a0a0;font-weight:bold;}
#mapdiv { width:100%; height: 100%; }
</style>
</head>
<body>
<div id="mapdiv"></div>
<div id="filterbox">
<input type="text" id="search" placeholder="filter: #cracked #notcracked AA:BB:CC aabbcc AndroidAP ..."/>
<div id="matchcount">0&nbsp;APs</div>
</div>
<div id="loading"><div class="face"><nobr>(⌐■&nbsp;<span id="loading_ap_img"></span>&nbsp;■)</nobr></div><div class="text" id="loading_infotext">loading positions...</div></div>
<script type="text/javascript">
function loadJSON(url, callback) {
@ -133,11 +141,9 @@
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
opacity:0.8,
maxZoom: 19
// maxZoom: 19
});
var mymap = L.map('mapdiv');
Esri_WorldImagery.addTo(mymap);
CartoDB_DarkMatter.addTo(mymap);
var svg = '<svg class="pwnAPPin" width="80px" height="60px" viewBox="0 0 44 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><desc>pwnagotchi AP icon.</desc><defs><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1"><stop stop-color="#FFFFFF" offset="0%"></stop><stop stop-color="#000000" offset="100%"></stop></linearGradient></defs><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="marker"><path class="ring_outer" d="M28.6,8 C34.7,9.4 39,12.4 39,16 C39,20.7 31.3,24.6 21.7,24.6 C12.1,24.6 4.3,20.7 4.3,16 C4.3,12.5 8.5,9.5 14.6,8.1 C15.3,8 14.2,6.6 13.3,6.8 C5.5,8.4 0,12.2 0,16.7 C0,22.7 9.7,27.4 21.7,27.4 C33.7,27.4 43.3,22.6 43.3,16.7 C43.3,12.1 37.6,8.3 29.6,6.7 C28.8,6.5 27.8,7.9 28.6,8.1 L28.6,8 Z" id="Shape" fill="#878787" fill-rule="nonzero"></path><path class="ring_inner" d="M28.1427313,11.0811939 C30.4951542,11.9119726 32.0242291,13.2174821 32.0242291,14.6416742 C32.0242291,17.2526931 27.6722467,19.2702986 22.261674,19.2702986 C16.8511013,19.2702986 12.4991189,17.2526931 12.4991189,14.7603569 C12.4991189,13.5735301 13.4400881,12.505386 15.0867841,11.6746073 C15.792511,11.3185592 14.7339207,9.30095371 13.9105727,9.77568442 C10.6171806,10.9625112 8.5,12.9801167 8.5,15.2350876 C8.5,19.0329333 14.4986784,22.0000002 21.9088106,22.0000002 C29.2013216,22.0000002 35.2,19.0329333 35.2,15.2350876 C35.2,12.861434 32.7299559,10.6064632 28.8484581,9.30095371 C28.0251101,9.18227103 27.4370044,10.8438285 28.0251101,11.0811939 L28.1427313,11.0811939 Z" id="Shape" fill="#5F5F5F" fill-rule="nonzero"></path><g id="ap" transform="translate(13.000000, 0.000000)"><rect id="apfront" fill="#000000" x="0" y="14" width="18" height="4"></rect><polygon id="apbody" fill="url(#linearGradient-1)" points="3.83034404 10 14.169656 10 18 14 0 14"></polygon><circle class="ring_outer" id="led1" fill="#931F1F" cx="3" cy="16" r="1"></circle><circle class="ring_inner" id="led2" fill="#931F1F" cx="7" cy="16" r="1"></circle><circle class="ring_outer" id="led3" fill="#931F1F" cx="11" cy="16" r="1"></circle><circle class="ring_inner" id="led4" fill="#931F1F" cx="15" cy="16" r="1"></circle><polygon id="antenna2" fill="#000000" points="8.8173082 0 9.1826918 0 9.5 11 8.5 11"></polygon><polygon id="antenna3" fill="#000000" transform="translate(15.000000, 5.500000) rotate(15.000000) translate(-15.000000, -5.500000) " points="14.8173082 0 15.1826918 0 15.5 11 14.5 11"></polygon><polygon id="antenna1" fill="#000000" transform="translate(3.000000, 5.500000) rotate(-15.000000) translate(-3.000000, -5.500000) " points="2.8173082 0 3.1826918 0 3.5 11 2.5 11"></polygon></g></g></g></svg>';
var svgOpen = '<svg class="pwnAPPinOpen" width="80px" height="60px" viewBox="0 0 44 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><desc>pwnagotchi AP icon.</desc><defs><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1"><stop stop-color="#FFFFFF" offset="0%"></stop><stop stop-color="#000000" offset="100%"></stop></linearGradient></defs><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="marker"><path class="ring_outer" d="M28.6,8 C34.7,9.4 39,12.4 39,16 C39,20.7 31.3,24.6 21.7,24.6 C12.1,24.6 4.3,20.7 4.3,16 C4.3,12.5 8.5,9.5 14.6,8.1 C15.3,8 14.2,6.6 13.3,6.8 C5.5,8.4 0,12.2 0,16.7 C0,22.7 9.7,27.4 21.7,27.4 C33.7,27.4 43.3,22.6 43.3,16.7 C43.3,12.1 37.6,8.3 29.6,6.7 C28.8,6.5 27.8,7.9 28.6,8.1 L28.6,8 Z" id="Shape" fill="#878787" fill-rule="nonzero"></path><path class="ring_inner" d="M28.1427313,11.0811939 C30.4951542,11.9119726 32.0242291,13.2174821 32.0242291,14.6416742 C32.0242291,17.2526931 27.6722467,19.2702986 22.261674,19.2702986 C16.8511013,19.2702986 12.4991189,17.2526931 12.4991189,14.7603569 C12.4991189,13.5735301 13.4400881,12.505386 15.0867841,11.6746073 C15.792511,11.3185592 14.7339207,9.30095371 13.9105727,9.77568442 C10.6171806,10.9625112 8.5,12.9801167 8.5,15.2350876 C8.5,19.0329333 14.4986784,22.0000002 21.9088106,22.0000002 C29.2013216,22.0000002 35.2,19.0329333 35.2,15.2350876 C35.2,12.861434 32.7299559,10.6064632 28.8484581,9.30095371 C28.0251101,9.18227103 27.4370044,10.8438285 28.0251101,11.0811939 L28.1427313,11.0811939 Z" id="Shape" fill="#5F5F5F" fill-rule="nonzero"></path><g id="ap" transform="translate(13.000000, 0.000000)"><rect id="apfront" fill="#000000" x="0" y="14" width="18" height="4"></rect><polygon id="apbody" fill="url(#linearGradient-1)" points="3.83034404 10 14.169656 10 18 14 0 14"></polygon><circle class="ring_outer" id="led1" fill="#1f9321" cx="3" cy="16" r="1"></circle><circle class="ring_inner" id="led2" fill="#1f9321" cx="7" cy="16" r="1"></circle><circle class="ring_outer" id="led3" fill="#1f9321" cx="11" cy="16" r="1"></circle><circle class="ring_inner" id="led4" fill="#1f9321" cx="15" cy="16" r="1"></circle><polygon id="antenna2" fill="#000000" points="8.8173082 0 9.1826918 0 9.5 11 8.5 11"></polygon><polygon id="antenna3" fill="#000000" transform="translate(15.000000, 5.500000) rotate(15.000000) translate(-15.000000, -5.500000) " points="14.8173082 0 15.1826918 0 15.5 11 14.5 11"></polygon><polygon id="antenna1" fill="#000000" transform="translate(3.000000, 5.500000) rotate(-15.000000) translate(-3.000000, -5.500000) " points="2.8173082 0 3.1826918 0 3.5 11 2.5 11"></polygon></g></g></g></svg>';
@ -157,54 +163,88 @@
popupAnchor : [0, -30],
});
var positionsLoaded = false;
var positions = [];
var accuracys = [];
var markers = [];
var marker_pos = [];
var markerClusters = L.markerClusterGroup();
loadJSON("/plugins/webgpsmap/all", function(response) {
var positions = JSON.parse(response);
function drawPositions() {
count = 0;
//mymap.removeLayer(markerClusters);
mymap.eachLayer(function (layer) {
mymap.removeLayer(layer);
});
Esri_WorldImagery.addTo(mymap);
CartoDB_DarkMatter.addTo(mymap);
markerClusters = L.markerClusterGroup();
accuracys = [];
markers = [];
marker_pos = [];
filterText = document.getElementById("search").value;
//console.log(filterText);
Object.keys(positions).forEach(function(key) {
count++;
if(positions[key].lng){
new_marker_pos = [positions[key].lat, positions[key].lng];
if (positions[key].acc) {
radius = Math.round(Math.min(positions[key].acc, 500));
markerColor = 'red';
markerColorCode = '#f03';
fillOpacity = 0.002;
if (positions[key].pass) {
markerColor = 'green';
markerColorCode = '#1aff00';
fillOpacity = 0.1;
}
accuracys.push(
L.circle(new_marker_pos, {
color: markerColor,
fillColor: markerColorCode,
fillOpacity: fillOpacity,
weight: 1,
opacity: 0.1,
radius: Math.min(positions[key].acc, 500),
}).setStyle({'className': 'radar'}).addTo(mymap)
);
}
filterPattern =
positions[key].ssid + ' ' +
formatMacAddress(positions[key].mac) + ' ' +
positions[key].mac
;
if (positions[key].pass) {
newMarker = L.marker(new_marker_pos, { icon: myIconOpen, title: positions[key].ssid }); //.addTo(mymap);
filterPattern += positions[key].pass + ' #cracked';
} else {
newMarker = L.marker(new_marker_pos, { icon: myIcon, title: positions[key].ssid }); //.addTo(mymap);
filterPattern += ' #notcracked';
}
passInfo = '';
if (positions[key].pass) {
filterPattern = filterPattern.toLowerCase();
//console.log(filterPattern);
var matched = true;
if (filterText) {
filterText.split(" ").forEach(function (item) {
if (!filterPattern.includes(item.toLowerCase())) {
matched = false;
}
});
}
if (matched) {
count++;
new_marker_pos = [positions[key].lat, positions[key].lng];
if (positions[key].acc) {
radius = Math.round(Math.min(positions[key].acc, 500));
markerColor = 'red';
markerColorCode = '#f03';
fillOpacity = 0.002;
if (positions[key].pass) {
markerColor = 'green';
markerColorCode = '#1aff00';
fillOpacity = 0.1;
}
accuracys.push(
L.circle(new_marker_pos, {
color: markerColor,
fillColor: markerColorCode,
fillOpacity: fillOpacity,
weight: 1,
opacity: 0.1,
radius: Math.min(positions[key].acc, 500),
}).setStyle({'className': 'radar'}).addTo(mymap)
);
}
passInfo = '';
if (positions[key].pass) {
passInfo = '<br/><b>Pass:</b> '+escapeHtml(positions[key].pass);
newMarker = L.marker(new_marker_pos, { icon: myIconOpen, title: positions[key].ssid }); //.addTo(mymap);
} else {
newMarker = L.marker(new_marker_pos, { icon: myIcon, title: positions[key].ssid }); //.addTo(mymap);
}
newMarker.bindPopup("<b>"+escapeHtml(positions[key].ssid)+"</b><br><nobr>MAC: "+escapeHtml(formatMacAddress(positions[key].mac))+"</nobr><br/>"+"<nobr>position type: "+escapeHtml(positions[key].type)+"</nobr><br/>"+"<nobr>position accuracy: "+escapeHtml(Math.round(positions[key].acc))+"</nobr>"+passInfo, { maxWidth: "auto" });
markers.push(newMarker);
marker_pos.push(new_marker_pos);
markerClusters.addLayer( newMarker );
}
newMarker.bindPopup("<b>"+escapeHtml(positions[key].ssid)+"</b><br><nobr>MAC: "+escapeHtml(formatMacAddress(positions[key].mac))+"</nobr><br/>"+"<nobr>position type: "+escapeHtml(positions[key].type)+"</nobr><br/>"+"<nobr>position accuracy: "+escapeHtml(Math.round(positions[key].acc))+"</nobr>"+passInfo, { maxWidth: "auto" });
markers.push(newMarker);
marker_pos.push(new_marker_pos);
markerClusters.addLayer( newMarker );
}
});
document.getElementById("matchcount").innerHTML = count + "&nbsp;APs";
if (count > 0) {
mymap.addLayer( markerClusters );
var bounds = new L.LatLngBounds(marker_pos);
@ -213,6 +253,22 @@
} else {
document.getElementById("loading_infotext").innerHTML = "NO POSITION DATA FOUND :(";
}
}
// draw map on Enter in FilterInputField
const node = document.getElementById("search").addEventListener("keyup", function(event) {
if (event.key === "Enter") {
if (positionsLoaded) {
drawPositions();
}
}
});
// load positions
loadJSON("/plugins/webgpsmap/all", function(response) {
positions = JSON.parse(response);
positionsLoaded = true;
drawPositions();
});
</script>
</body></html>
</body></html>

@ -9,7 +9,7 @@ from functools import lru_cache
'''
2do:
- make the cache handling multiple clients
- make+test the cache handling multiple clients
- cleanup the javascript in a class and handle "/newest" additions
- create map filters (only cracked APs, only last xx days, between 2 days with slider)
http://www.gistechsolutions.com/leaflet/DEMO/filter/filter.html
@ -22,16 +22,10 @@ from functools import lru_cache
class Webgpsmap(plugins.Plugin):
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
__version__ = '1.2.2'
__version__ = '1.3.0'
__name__ = 'webgpsmap'
__license__ = 'GPL3'
__description__ = 'a plugin for pwnagotchi that shows a openstreetmap with positions of ap-handshakes in your webbrowser'
__help__ = """
- install: copy "webgpsmap.py" and "webgpsmap.html" to your configured "custom_plugins" directory
- add webgpsmap.yml to your config
- connect your PC/Smartphone/* with USB, BT or other to your pwnagotchi and browse to http://pwnagotchi.local:8080/plugins/webgpsmap/
(change pwnagotchi.local to your pwnagotchis IP, if needed)
"""
ALREADY_SENT = list()
SKIP = list()
@ -47,7 +41,7 @@ class Webgpsmap(plugins.Plugin):
"""
Plugin got loaded
"""
logging.info("webgpsmap plugin loaded")
logging.info("[webgpsmap]: plugin loaded")
def on_webhook(self, path, request):
"""
@ -68,8 +62,8 @@ class Webgpsmap(plugins.Plugin):
response_status = 500
response_mimetype = "application/xhtml+xml"
response_header_contenttype = 'text/html'
except Exception as ex:
logging.error(ex)
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
return
else:
if request.method == "GET":
@ -78,8 +72,8 @@ class Webgpsmap(plugins.Plugin):
self.ALREADY_SENT = list()
try:
response_data = bytes(self.get_html(), "utf-8")
except Exception as ex:
logging.error(ex)
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
return
response_status = 200
response_mimetype = "application/xhtml+xml"
@ -92,8 +86,8 @@ class Webgpsmap(plugins.Plugin):
response_status = 200
response_mimetype = "application/json"
response_header_contenttype = 'application/json'
except Exception as ex:
logging.error(ex)
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
return
# elif path.startswith('/newest'):
# # returns all positions newer then timestamp
@ -118,7 +112,7 @@ class Webgpsmap(plugins.Plugin):
<meta charset="utf-8"/>
<style>body{font-size:1000%;}</style>
</head>
<body>4😋4</body>
<body>4😋4 for bad boys</body>
</html>''', "utf-8")
response_status = 404
try:
@ -126,12 +120,12 @@ class Webgpsmap(plugins.Plugin):
if response_header_contenttype is not None:
r.headers["Content-Type"] = response_header_contenttype
return r
except Exception as ex:
logging.error(ex)
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
return
# cache 1024 items
@lru_cache(maxsize=1024, typed=False)
# cache 2048 items
@lru_cache(maxsize=2048, typed=False)
def _get_pos_from_file(self, path):
return PositionFile(path)
@ -144,7 +138,7 @@ class Webgpsmap(plugins.Plugin):
handshake_dir = gpsdir
gps_data = dict()
logging.info("webgpsmap: scanning %s", handshake_dir)
logging.info(f"[webgpsmap] scanning {handshake_dir}")
all_files = os.listdir(handshake_dir)
@ -156,33 +150,40 @@ class Webgpsmap(plugins.Plugin):
all_geo_or_gps_files = []
for filename_pcap in all_pcap_files:
filename_base = filename_pcap[:-5] # remove ".pcap"
logging.debug("webgpsmap: found: " + filename_base)
logging.debug(f"[webgpsmap] found: {filename_base}")
filename_position = None
logging.debug("[webgpsmap] search for .gps.json")
check_for = os.path.basename(filename_base) + ".gps.json"
if check_for in all_files:
filename_position = str(os.path.join(handshake_dir, check_for))
logging.debug("[webgpsmap] search for .geo.json")
check_for = os.path.basename(filename_base) + ".geo.json"
if check_for in all_files:
filename_position = str(os.path.join(handshake_dir, check_for))
logging.debug("[webgpsmap] search for .paw-gps.json")
check_for = os.path.basename(filename_base) + ".paw-gps.json"
if check_for in all_files:
filename_position = str(os.path.join(handshake_dir, check_for))
logging.debug(f"[webgpsmap] end search for position data files and use {filename_position}")
if filename_position is not None:
# logging.debug("webgpsmap: -- found: %s %d" % (check_for, len(all_geo_or_gps_files)) )
all_geo_or_gps_files.append(filename_position)
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skiped networks? No!
# all_geo_or_gps_files = set(all_geo_or_gps_files) - set(SKIP) # remove skipped networks? No!
if newest_only:
all_geo_or_gps_files = set(all_geo_or_gps_files) - set(self.ALREADY_SENT)
logging.info("webgpsmap: Found %d .(geo|gps).json files from %d handshakes. Fetching positions ...",
len(all_geo_or_gps_files), len(all_pcap_files))
logging.info(f"[webgpsmap] Found {len(all_geo_or_gps_files)} position-data files from {len(all_pcap_files)} handshakes. Fetching positions ...")
for pos_file in all_geo_or_gps_files:
try:
pos = self._get_pos_from_file(pos_file)
if not pos.type() == PositionFile.GPS and not pos.type() == PositionFile.GEO:
if not pos.type() == PositionFile.GPS and not pos.type() == PositionFile.GEO and not pos.type() == PositionFile.PAWGPS:
continue
ssid, mac = pos.ssid(), pos.mac()
@ -190,10 +191,17 @@ class Webgpsmap(plugins.Plugin):
# invalid mac is strange and should abort; ssid is ok
if not mac:
raise ValueError("Mac can't be parsed from filename")
pos_type = 'unknown'
if pos.type() == PositionFile.GPS:
pos_type = 'gps'
elif pos.type() == PositionFile.GEO:
pos_type = 'geo'
elif pos.type() == PositionFile.PAWGPS:
pos_type = 'paw'
gps_data[ssid+"_"+mac] = {
'ssid': ssid,
'mac': mac,
'type': 'gps' if pos.type() == PositionFile.GPS else 'geo',
'type': pos_type,
'lng': pos.lng(),
'lat': pos.lat(),
'acc': pos.accuracy(),
@ -201,24 +209,25 @@ class Webgpsmap(plugins.Plugin):
'ts_last': pos.timestamp_last(),
}
# get ap password if exist
check_for = os.path.basename(pos_file[:-9]) + ".pcap.cracked"
if check_for in all_files:
gps_data[ssid + "_" + mac]["pass"] = pos.password()
self.ALREADY_SENT += pos_file
except json.JSONDecodeError as js_e:
except json.JSONDecodeError as error:
self.SKIP += pos_file
logging.error(js_e)
logging.error(f"[webgpsmap] JSONDecodeError in: {error}")
continue
except ValueError as v_e:
except ValueError as error:
self.SKIP += pos_file
logging.error(v_e)
logging.error(f"[webgpsmap] ValueError: {error}")
continue
except OSError as os_e:
except OSError as error:
self.SKIP += pos_file
logging.error(os_e)
logging.error(f"[webgpsmap] OSError: {error}")
continue
logging.info("webgpsmap loaded %d positions", len(gps_data))
logging.info(f"[webgpsmap] loaded {len(gps_data)} positions")
return gps_data
def get_html(self):
@ -226,11 +235,10 @@ class Webgpsmap(plugins.Plugin):
Returns the html page
"""
try:
template_file = os.path.dirname(os.path.realpath(__file__))+"/"+"webgpsmap.html"
template_file = os.path.dirname(os.path.realpath(__file__)) + "/" + "webgpsmap.html"
html_data = open(template_file, "r").read()
except Exception as ex:
logging.error("error loading template file: %s", template_file)
logging.error(ex)
except Exception as error:
logging.error(f"[webgpsmap] error loading template file {template_file} - error: {error}")
return html_data
@ -238,15 +246,18 @@ class PositionFile:
"""
Wraps gps / net-pos files
"""
GPS = 0
GEO = 1
GPS = 1
GEO = 2
PAWGPS = 3
def __init__(self, path):
self._file = path
self._filename = os.path.basename(path)
try:
logging.debug(f"[webgpsmap] loading {path}")
with open(path, 'r') as json_file:
self._json = json.load(json_file)
logging.debug(f"[webgpsmap] loaded {path}")
except json.JSONDecodeError as js_e:
raise js_e
@ -254,7 +265,7 @@ class PositionFile:
"""
Returns the mac from filename
"""
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo)\.json', self._filename)
parsed_mac = re.search(r'.*_?([a-zA-Z0-9]{12})\.(?:gps|geo|paw-gps)\.json', self._filename)
if parsed_mac:
mac = parsed_mac.groups()[0]
return mac
@ -264,7 +275,7 @@ class PositionFile:
"""
Returns the ssid from filename
"""
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo)\.json', self._filename)
parsed_ssid = re.search(r'(.+)_[a-zA-Z0-9]{12}\.(?:gps|geo|paw-gps)\.json', self._filename)
if parsed_ssid:
return parsed_ssid.groups()[0]
return None
@ -293,32 +304,37 @@ class PositionFile:
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']
# fill milliseconds to 6 numbers
# bad microseconds fix: fill/cut microseconds to 6 numbers
part1, part2, part3 = re.split('\.|\+', date_iso_formated)
part2 = part2.ljust(6, '0')
part2 = part2.ljust(6, '0')[:6]
# timezone fix: 0200 >>> 02:00
if len(part3) == 4:
part3 = part3[1:2].rjust(2, '0') + ':' + part3[3:4].rjust(2, '0')
date_iso_formated = part1 + "." + part2 + "+" + part3
dateObj = datetime.datetime.fromisoformat(date_iso_formated)
return_ts = int("%.0f" % dateObj.timestamp())
else:
# use file timestamp last modification of the pcap file
# use file timestamp last modification of the json file
return_ts = int("%.0f" % os.path.getmtime(self._file))
return return_ts
def password(self):
"""
returns the password from file.pcap.cracked od None
returns the password from file.pcap.cracked or None
"""
return_pass = None
password_file_path = self._file[:-9] + ".pcap.cracked"
# 2do: make better filename split/remove extension because this one has problems with "." in path
base_filename, ext1, ext2 = re.split('\.', self._file)
password_file_path = base_filename + ".pcap.cracked"
if os.path.isfile(password_file_path):
try:
password_file = open(password_file_path, 'r')
return_pass = password_file.read()
password_file.close()
except OSError as err:
print("OS error: {0}".format(err))
except OSError as error:
logging.error(f"[webgpsmap] OS error: {format(error)}")
except:
print("Unexpected error:", sys.exc_info()[0])
logging.error(f"[webgpsmap] Unexpected error: {sys.exc_info()[0]}")
raise
return return_pass
@ -330,37 +346,57 @@ class PositionFile:
return PositionFile.GPS
if self._file.endswith('.geo.json'):
return PositionFile.GEO
if self._file.endswith('.paw-gps.json'):
return PositionFile.PAWGPS
return None
def lat(self):
try:
if self.type() == PositionFile.GPS:
lat = None
# try to get value from known formats
if 'Latitude' in self._json:
lat = self._json['Latitude']
if self.type() == PositionFile.GEO:
lat = self._json['location']['lat']
if lat != 0:
return lat
raise ValueError("Lat is 0")
if 'lat' in self._json:
lat = self._json['lat'] # an old paw-gps format: {"long": 14.693561, "lat": 40.806375}
if 'location' in self._json:
if 'lat' in self._json['location']:
lat = self._json['location']['lat']
# check value
if lat is None:
raise ValueError(f"Lat is None in {self._filename}")
if lat == 0:
raise ValueError(f"Lat is 0 in {self._filename}")
return lat
except KeyError:
pass
return None
def lng(self):
try:
if self.type() == PositionFile.GPS:
lng = None
# try to get value from known formats
if 'Longitude' in self._json:
lng = self._json['Longitude']
if self.type() == PositionFile.GEO:
lng = self._json['location']['lng']
if lng != 0:
return lng
raise ValueError("Lng is 0")
if 'long' in self._json:
lng = self._json['long'] # an old paw-gps format: {"long": 14.693561, "lat": 40.806375}
if 'location' in self._json:
if 'lng' in self._json['location']:
lng = self._json['location']['lng']
# check value
if lng is None:
raise ValueError(f"Lng is None in {self._filename}")
if lng == 0:
raise ValueError(f"Lng is 0 in {self._filename}")
return lng
except KeyError:
pass
return None
def accuracy(self):
if self.type() == PositionFile.GPS:
return 50.0
return 50.0 # a default
if self.type() == PositionFile.PAWGPS:
return 50.0 # a default
if self.type() == PositionFile.GEO:
try:
return self._json['accuracy']

@ -1,19 +1,27 @@
import os
import logging
import threading
import requests
from datetime import datetime
from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
from pwnagotchi import plugins
from json.decoder import JSONDecodeError
class WpaSec(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.1'
__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.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
self.lock = threading.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()
@ -35,6 +43,29 @@ class WpaSec(plugins.Plugin):
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
@ -53,32 +84,48 @@ class WpaSec(plugins.Plugin):
"""
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())
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)
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")
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
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)
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:
self.skip.append(handshake)
logging.error("WPA_SEC: %s", req_e)
continue
logging.debug("WPA_SEC: %s", req_e)
except OSError as os_e:
logging.error("WPA_SEC: %s", os_e)
continue
logging.debug("WPA_SEC: %s", os_e)

@ -34,6 +34,7 @@ class Handler:
self._app.add_url_rule('/ui', 'ui', self.with_auth(self.ui))
self._app.add_url_rule('/shutdown', 'shutdown', self.with_auth(self.shutdown), methods=['POST'])
self._app.add_url_rule('/reboot', 'reboot', self.with_auth(self.reboot), methods=['POST'])
self._app.add_url_rule('/restart', 'restart', self.with_auth(self.restart), methods=['POST'])
# inbox
@ -179,16 +180,19 @@ class Handler:
def plugins(self, name, subpath):
if name is None:
# show plugins overview
abort(404)
return render_template('plugins.html', loaded=plugins.loaded, database=plugins.database)
if name == 'toggle' and request.method == 'POST':
checked = True if 'enabled' in request.form else False
return 'success' if plugins.toggle_plugin(request.form['plugin'], checked) else 'failed'
if name in plugins.loaded and plugins.loaded[name] is not None and hasattr(plugins.loaded[name], 'on_webhook'):
try:
return plugins.loaded[name].on_webhook(subpath, request)
except Exception:
abort(500)
else:
if name in plugins.loaded and hasattr(plugins.loaded[name], 'on_webhook'):
try:
return plugins.loaded[name].on_webhook(subpath, request)
except Exception:
abort(500)
else:
abort(404)
abort(404)
# serve a message and shuts down the unit
def shutdown(self):
@ -198,6 +202,14 @@ class Handler:
finally:
_thread.start_new_thread(pwnagotchi.shutdown, ())
# serve a message and reboot the unit
def reboot(self):
try:
return render_template('status.html', title=pwnagotchi.name(), go_back_after=60,
message='Rebooting ...')
finally:
_thread.start_new_thread(pwnagotchi.reboot, ())
# serve a message and restart the unit in the other mode
def restart(self):
mode = request.form['mode']

@ -31,4 +31,37 @@ a.read {
p.messagebody {
padding: 1em;
}
}
li.navitem {
width: 16.66% !important;
clear: none !important;
}
/* Custom indentations are needed because the length of custom labels differs from
the length of the standard labels */
.custom-size-flipswitch.ui-flipswitch .ui-btn.ui-flipswitch-on {
text-indent: -5.9em;
}
.custom-size-flipswitch.ui-flipswitch .ui-flipswitch-off {
text-indent: 0.5em;
}
/* Custom widths are needed because the length of custom labels differs from
the length of the standard labels */
.custom-size-flipswitch.ui-flipswitch {
width: 8.875em;
}
.custom-size-flipswitch.ui-flipswitch.ui-flipswitch-active {
padding-left: 7em;
width: 1.875em;
}
@media (min-width: 28em) {
/*Repeated from rule .ui-flipswitch above*/
.ui-field-contain > label + .custom-size-flipswitch.ui-flipswitch {
width: 1.875em;
}
}

@ -47,6 +47,7 @@
( '/inbox/new', 'new', 'mail', 'New' ),
( '/inbox/profile', 'profile', 'info', 'Profile' ),
( '/inbox/peers', 'peers', 'user', 'Peers' ),
( '/plugins', 'plugins', 'grid', 'Plugins' ),
] %}
{% set active_page = active_page|default('inbox') %}
@ -54,7 +55,7 @@
<div data-role="navbar" data-iconpos="left">
<ul>
{% for href, id, icon, caption in navigation %}
<li>
<li class="navitem">
<a href="{{ href }}" id="{{ id }}" data-icon="{{ icon }}" class="{{ 'ui-btn-active' if active_page == id }}">{{ caption }}</a>
</li>
{% endfor %}

@ -26,6 +26,13 @@ window.onload = function() {
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
</li>
<li>
<form class="action" method="post" action="/reboot"
onsubmit="return confirm('this will reboot the unit, continue?');">
<input type="submit" class="button ui-btn ui-corner-all" value="Reboot"/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
</li>
<li>
<form class="action" method="post" action="/restart"
onsubmit="return confirm('This will restart the service in {{ other_mode }} mode, continue?');">

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Plugins
{% endblock %}
{% block script %}
$(function(){
$('input[type=checkbox]').change(function(e) {
var checkbox = $(this);
var form = checkbox.closest('form');
var url = form.attr('action');
$.ajax({
type: 'POST',
url: url,
data: form.serialize(),
success: function(data) {
if( data.indexOf('failed') != -1 ) {
alert('Could not be toggled.');
}
}
});
});
});
{% endblock %}
{% block content %}
<div style="padding: 1em">
{% for name in database.keys() %}
<h4>
<a {% if name in loaded and loaded[name].on_webhook is defined %} href="/plugins/{{name}}" {% endif %}>{{name}}</a>
</h4>
<form method="POST" action="/plugins/toggle">
<input type="checkbox" data-role="flipswitch" name="enabled" id="flip-checkbox-{{name}}" data-on-text="Enabled" data-off-text="Disabled" data-wrapper-class="custom-size-flipswitch" {% if name in loaded %} checked {% endif %}>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="plugin" value="{{ name }}"/>
</form>
{% endfor %}
</div>
{% endblock %}

@ -54,4 +54,4 @@ ping -c 1 "${UNIT_HOSTNAME}" > /dev/null 2>&1 || {
}
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
ssh "${UNIT_USERNAME}@${UNIT_HOSTNAME}" "sudo find ${FILES_TO_BACKUP} -print0 | xargs -0 sudo tar cv" | gzip -9 > "$OUTPUT"
ssh "${UNIT_USERNAME}@${UNIT_HOSTNAME}" "sudo find ${FILES_TO_BACKUP} -type f -print0 | xargs -0 sudo tar cv" | gzip -9 > "$OUTPUT"